Coder Social home page Coder Social logo

rtfeldman / seamless-immutable Goto Github PK

View Code? Open in Web Editor NEW
5.4K 59.0 195.0 562 KB

Immutable data structures for JavaScript which are backwards-compatible with normal JS Arrays and Objects.

License: BSD 3-Clause "New" or "Revised" License

JavaScript 100.00%

seamless-immutable's Introduction

seamless-immutable

Immutable JS data structures which are backwards-compatible with normal Arrays and Objects.

Use them in for loops, pass them to functions expecting vanilla JavaScript data structures, etc.

var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);

array[1] = "I'm going to mutate you!"
array[1] // "immutable"

array[2].hammer = "hm, surely I can mutate this nested object..."
array[2].hammer // "Can’t Touch This"

for (var index in array) { console.log(array[index]); }
// "totally"
// "immutable"
// { hammer: 'Can’t Touch This' }

JSON.stringify(array) // '["totally","immutable",{"hammer":"Can’t Touch This"}]'

This level of backwards compatibility requires ECMAScript 5 features like Object.defineProperty and Object.freeze to exist and work correctly, which limits the browsers that can use this library to the ones shown in the test results below. (tl;dr IE9+)

build status NPM version coverage status

Performance

Whenever you deeply clone large nested objects, it should typically go much faster with Immutable data structures. This is because the library reuses the existing nested objects rather than instantiating new ones.

In the development build, objects are frozen. (Note that Safari is relatively slow to iterate over frozen objects.) The development build also overrides unsupported methods (methods that ordinarily mutate the underlying data structure) to throw helpful exceptions.

The production (minified) build does neither of these, which significantly improves performance.

We generally recommend to use the "development" build that enforces immutability (and this is the default in Node.js). Only switch to the production build when you encounter performance problems. (See #50 for how to do that in Node or using a build tool - essentially do explicitely refer to the production build.)

Intentional Abstraction Leaks

By popular demand, functions, errors, dates, and React components are treated as immutable even though technically they can be mutated. (It turns out that trying to make these immutable leads to more bad things than good.) If you call Immutable() on any of these, be forewarned: they will not actually be immutable!

Add-ons

seamless-immutable is tightly focused on the mechanics of turning existing JavaScript data structures into immutable variants. Additional packages are available to build on this capability and enable additional programming models:

Library Description
Cursor Compact Cursor Library built on top of the excellent seamless-immutable. Cursors can be used to manage transitions and manipulations of immutable structures in an application.
Mergers A collection of mergers for use with seamless-immutable. Also includes documentation about custom mergers, with examples, for writing your own.

API Overview

Immutable() returns a backwards-compatible immutable representation of whatever you pass it, so feel free to pass it absolutely anything that can be serialized as JSON. (As is the case with JSON, objects containing circular references are not allowed. Functions are allowed, unlike in JSON, but they will not be touched.)

Since numbers, strings, undefined, and null are all immutable to begin with, the only unusual things it returns are Immutable Arrays and Immutable Objects. These have the same ES5 methods you’re used to seeing on them, but with these important differences:

  1. All the methods that would normally mutate the data structures instead throw ImmutableError.
  2. All the methods that return a relevant value now return an immutable equivalent of that value.
  3. Attempting to reassign values to their elements (e.g. foo[5] = bar) will not work. Browsers other than Internet Explorer will throw a TypeError if use strict is enabled, and in all other cases it will fail silently.
  4. A few additional methods have been added for convenience.

For example:

Immutable([3, 1, 4]).sort()
// This will throw an ImmutableError, because sort() is a mutating method.

Immutable([1, 2, 3]).concat([10, 9, 8]).sort()
// This will also throw ImmutableError, because an Immutable Array's methods
// (including concat()) are guaranteed to return other immutable values.

[1, 2, 3].concat(Immutable([6, 5, 4])).sort()
// This will succeed, and will yield a sorted mutable array containing
// [1, 2, 3, 4, 5, 6], because a vanilla array's concat() method has
// no knowledge of Immutable.

var obj = Immutable({all: "your base", are: {belong: "to them"}});
Immutable.merge(obj, {are: {belong: "to us"}})
// This will return the following:
// Immutable({all: "your base", are: {belong: "to us"}})

Static or instance syntax

Seamless-immutable supports both static and instance syntaxes:

var Immutable = require("seamless-immutable").static;
var obj = {};

Immutable.setIn(obj, ['key'], data)
var Immutable = require("seamless-immutable");
var obj = {};

obj.setIn(['key'], data)

Although the later is shorter and is the current default, it can lead to collisions and some users may dislike polluting object properties when it comes to debugging. As such the first syntax is recommended, but both are supported.

Immutable.from

If your linter cringes with the use of Immutable without a preceding new (e.g. ESLint's new-cap rule), use Immutable.from:

Immutable.from([1, 2, 3]);
// is functionally the same as calling:
Immutable([1, 2, 3])

Immutable Array

Like a regular Array, but immutable! You can construct these by passing an array to Immutable():

Immutable([1, 2, 3])
// An immutable array containing 1, 2, and 3.

Beyond the usual Array fare, the following methods have been added.

flatMap

var array = Immutable(["here", "we", "go"]);
Immutable.flatMap(array, function(str) {
  return [str, str, str];
});
// returns Immutable(["here", "here", "here", "we", "we", "we", "go", "go", "go"])

var array = Immutable(["drop the numbers!", 3, 2, 1, 0, null, undefined]);
Immutable.flatMap(array, function(value) {
  if (typeof value === "number") {
    return [];
  } else {
    return value;
  }
});
// returns Immutable(["drop the numbers!", null, undefined])

Effectively performs a map over the elements in the array, except that whenever the provided iterator function returns an Array, that Array's elements are each added to the final result.

var array = Immutable([1,2,3]);
array.map(value => [value+2, value+4]);
// returns Immutable([ [ 3, 5 ], [ 4, 6 ], [ 5, 7 ] ])

Immutable.flatMap(array, value => [value+2, value+4]);
// returns Immutable([ 3, 5, 4, 6, 5, 7 ])

asObject

var array = Immutable(["hey", "you"]);
Immutable.asObject(array, function(str) {
  return [str, str.toUpperCase()];
});
// returns Immutable({hey: "HEY", you: "YOU"})

Effectively performs a map over the elements in the array, expecting that the iterator function will return an array of two elements - the first representing a key, the other a value. Then returns an Immutable Object constructed of those keys and values.

You can also call .asObject without passing an iterator, in which case it will proceed assuming the Array is already organized as desired.

asMutable

var array = Immutable(["hello", "world"]);
var mutableArray = Immutable.asMutable(array);

mutableArray.push("!!!");

mutableArray // ["hello", "world", "!!!"]

Returns a mutable copy of the array. For a deeply mutable copy, in which any instances of Immutable contained in nested data structures within the array have been converted back to mutable data structures, call Immutable.asMutable(obj, {deep: true}) instead.

isImmutable

var array = Immutable(["hello", "world"]);
var mutableArray = ["hello", "world"];

Immutable.isImmutable(array)
// returns true

Immutable.isImmutable(mutableArray)
// returns false

Returns whether an object is immutable or not.

Additional Methods

In addition, Immutable Arrays also provide member functions for the set, setIn, update, updateIn, and getIn functions (described below).

var array = Immutable(["hello", "world"]);

// Equivalent to Immutable.set(array, 1, "you");
var mutatedArray = array.set(1, "you");

mutatedArray // ["hello", "you"]

Immutable Object

Like a regular Object, but immutable! You can construct these by passing an object to Immutable().

Immutable({foo: "bar"})
// An immutable object containing the key "foo" and the value "bar".

To construct an Immutable Object with a custom prototype, simply specify the prototype in options (while useful for preserving prototypes, please note that custom mutator methods will not work as the object will be immutable):

function Square(length) { this.length = length };
Square.prototype.area = function() { return Math.pow(this.length, 2) };

Immutable(new Square(2), {prototype: Square.prototype}).area();
// An immutable object, with prototype Square,
// containing the key "length" and method `area()` returning 4

Beyond the usual Object fare, the following methods have been added.

Stack overflow protection

Currently you can't construct Immutable from an object with circular references. To protect from ugly stack overflows, we provide a simple protection during development. We stop at a suspiciously deep stack level and show an error message.

If your objects are deep, but not circular, you can increase this level from default 64. For example:

Immutable(deepObject, null, 256);

This check is not performed in the production build.

merge

var obj = Immutable({status: "good", hypothesis: "plausible", errors: 0});
Immutable.merge(obj, {status: "funky", hypothesis: "confirmed"});
// returns Immutable({status: "funky", hypothesis: "confirmed", errors: 0})

var obj = Immutable({status: "bad", errors: 37});
Immutable.merge(obj, [
  {status: "funky", errors: 1}, {status: "groovy", errors: 2}, {status: "sweet"}]);
// returns Immutable({status: "sweet", errors: 2})
// because passing an Array is shorthand for
// invoking a separate merge for each object in turn.

Returns an Immutable Object containing the properties and values of both this object and the provided object, prioritizing the provided object's values whenever the same key is present in both objects.

Multiple objects can be provided in an Array in which case more merge invocations will be performed using each provided object in turn.

A third argument can be provided to configure the merge. It should be an object with any of the following fields:

{
  deep: true, // perform a deep merge
  merger: yourCustomMerger // supply a custom merger
}

You can find examples and documentation about custom mergers here.

replace

var obj1 = Immutable({a: {b: 'test'}, c: 'test'});
var obj2 = Immutable.replace(obj1, {a: {b: 'test'}}, {deep: true});
// returns Immutable({a: {b: 'test'}});
obj1 === obj2
// returns false
obj1.a === obj2.a
// returns true because child .a objects were identical

Returns an Immutable Object containing the properties and values of the second object only. With deep merge, all child objects are checked for equality and the original immutable object is returned when possible.

A second argument can be provided to perform a deep merge: {deep: true}.

set

var obj = Immutable({type: "parrot", subtype: "Norwegian Blue", status: "alive"});
Immutable.set(obj, "status", "dead");
// returns Immutable({type: "parrot", subtype: "Norwegian Blue", status: "dead"})

Returns an Immutable Object with a single property set to the provided value. Basically a more straightforward way of saying

var obj = Immutable({type: "parrot", subtype: "Norwegian Blue", status: "alive"});
Immutable.merge(obj, {status: "dead"});

(and more convenient with non-literal keys unless you have ES6 [computed_property_names]).

An additional argument can be provided to perform a deep compare: {deep: true}.

When called with an Immutable Array, the property parameter is the index to be changed:

var array = Immutable(["hello", "world"]);
var mutatedArray = Immutable.set(array, 1, "you");

mutatedArray // ["hello", "you"]

If the {deep: true} parameter is provided when using an Immutable Array, the object at the provided index will be merged with the provided value using Immutable.merge().

setIn

Like set, but accepts a nested path to the property.

var obj = Immutable({type: {main: "parrot", sub: "Norwegian Blue"}, status: "alive"});
Immutable.setIn(obj, ["type", "sub"], "Norwegian Ridgeback");
// returns Immutable({type: {main: "parrot", sub: "Norwegian Ridgeback"}, status: "alive"})

An additional argument can be provided to perform a deep compare: {deep: true}.

When called with an Immutable Array, at least the first value in the path should be an index. This also works with nested arrays:

var array = Immutable([["one fish", "two fish"], ["red fish", "blue fish"]]);
var mutatedArray = Immutable.setIn(array, [1, 1], "green fish");

mutatedArray // [["one fish", "two fish"], ["red fish", "green fish"]]

getIn

Returns the value at the given path. A default value can be provided as a second argument.

var obj = Immutable({type: {main: "parrot", subtype: "Norwegian Blue"}, status: "alive"});
Immutable.getIn(obj, ["type", "subtype"]);
// returns "Norwegian Blue"
Immutable.getIn(obj, ["type", "class"], "Aves");
// returns "Aves"

update

Returns an Immutable Object with a single property updated using the provided updater function.

function inc (x) { return x + 1 }
var obj = Immutable({foo: 1});
Immutable.update(obj, "foo", inc);
// returns Immutable({foo: 2})

All additional arguments will be passed to the updater function.

function add (x, y) { return x + y }
var obj = Immutable({foo: 1});
Immutable.update(obj, "foo", add, 10);
// returns Immutable({foo: 11})

updateIn

Like update, but accepts a nested path to the property.

function add (x, y) { return x + y }
var obj = Immutable({foo: {bar: 1}});
Immutable.updateIn(obj, ["foo", "bar"], add, 10);
// returns Immutable({foo: {bar: 11}})

without

var obj = Immutable({the: "forests", will: "echo", with: "laughter"});
Immutable.without(obj, "with");
// returns Immutable({the: "forests", will: "echo"})

var obj = Immutable({the: "forests", will: "echo", with: "laughter"});
Immutable.without(obj, ["will", "with"]);
// returns Immutable({the: "forests"})

var obj = Immutable({the: "forests", will: "echo", with: "laughter"});
Immutable.without(obj, "will", "with");
// returns Immutable({the: "forests"})

var obj = Immutable({the: "forests", will: "echo", with: "laughter"});
Immutable.without(obj, (value, key) => key === "the" || value === "echo");
// returns Immutable({with: "laughter"})

Returns an Immutable Object excluding the given keys or keys/values satisfying the given predicate from the existing object.

Multiple keys can be provided, either in an Array or as extra arguments.

asMutable

var obj = Immutable({when: "the", levee: "breaks"});
var mutableObject = Immutable.asMutable(obj);

mutableObject.have = "no place to go";

mutableObject // {when: "the", levee: "breaks", have: "no place to go"}

Returns a mutable copy of the object. For a deeply mutable copy, in which any instances of Immutable contained in nested data structures within the object have been converted back to mutable data structures, call Immutable.asMutable(obj, {deep: true}) instead.

Releases

7.1.4

Fixed bug with custom mergers treating all non-truthy values as undefined (#244).

7.1.3

Treat Blob instances as immutable. Use Array.isArray over instanceof.

7.1.2

Treat Error instances as immutable.

7.1.1

Fix .npmignore

7.1.0

Add getIn and assumption that Promises are immutable.

7.0.0

Add Immutable.static as the preferred API. Default to development build in webpack.

6.3.0

Adds optional deep compare for .set, .setIn and .replace

6.2.0

Adds static alternatives to methods, e.g. Immutable.setIn

6.1.4

Fixes bug with deep merge() on an array argument.

6.1.3

Fixes bug with setting a new object on an existing leaf array.

6.1.2

Fixes bug where on some systems arrays are treated as plain objects.

6.1.1

without now handles numeric keys the same way as string keys.

6.1.0

Alias Immutable.from() to Immutable() for linters.

6.0.1

React components are now considered immutable.

6.0.0

Add cycle detection.

5.2.0

Add update and updateIn.

5.1.1

Immutable(Object.create(null)) now works as expected.

5.1.0

Add predicate support to without()

5.0.1

Fix missing dev/prod builds for 5.0.0

5.0.0

In development build, freeze Dates and ban mutating methods. (Note: dev and prod builds were mistakenly not generated for this, so to get this functionality in those builds, use 5.0.1)

4.1.1

Make setIn more null safe.

4.1.0

Adds set and setIn

4.0.1

Now when you require("seamless-immutable"), you get the development build by default.

4.0.0

main now points to src/seamless-immutable.js so you can more easily build with envify yourself.

3.0.0

Add support for optional prototyping.

2.4.2

Calling .asMutable({deep: true}) on an Immutable data structure with a nested Date no longer throws an exception.

2.4.1

Arrays with nonstandard prototypes no longer throw exceptions when passed to Immutable.

2.4.0

Custom mergers now check for reference equality and abort early if there is no more work needed, allowing improved performance.

2.3.2

Fixes a bug where indices passed into iterators for flatMap and asObject were strings instead of numbers.

2.3.1

Fixes an IE and Firefox bug related to cloning Dates while preserving their prototypes.

2.3.0

Dates now retain their prototypes, the same way Arrays do.

2.2.0

Adds a minified production build with no freezing or defensive unsupported methods, for a ~2x performance boost.

2.1.0

Adds optional merger function to #merge.

2.0.2

Bugfix: #merge with {deep: true} no longer attempts (unsuccessfully) to deeply merge arrays as though they were regular objects.

2.0.1

Minor documentation typo fix.

2.0.0

Breaking API change: #merge now takes exactly one or exactly two arguments. The second is optional and allows specifying deep: true.

1.3.0

Don't bother returning a new value from #merge if no changes would result.

1.2.0

Make error message for invalid #asObject less fancy, resulting in a performance improvement.

1.1.0

Adds #asMutable

1.0.0

Initial stable release

Development

Run npm install -g grunt-cli, npm install and then grunt to build and test it.

seamless-immutable's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

seamless-immutable's Issues

Please clarify the docs regarding when dev/prod modes are on (Node)

Hello,

perhaps it wasn't my brightest day :-) but I found it difficult to find out when the dev or prod modes are active under Node.js. It would be great clarify that in the docs.

I believe this is true (and exhaustive) but am not sure:

/*1.*/ var Immutable = require('seamless-immutable'); // => dev mode
/*2.*/ var Immutable = require('seamless-immutable/seamless-immutable.production.min'); // => prod mode
/*3.*/ var Immutable = require('seamless-immutable/seamless-immutable.production.min'); process.env.NODE_ENV = "development"; // => dev mode

Thank you!

Circular references throw stack overflows

If you call Immutable(obj) on an obj that contains a circular reference, you get a stack overflow as it tries to clone it.

I don't think supporting objects containing circular references is a good idea, but you should at least get a nice error message.

A heuristic could be useful here for performance, e.g. start by just keep count of how big the stack is getting; if it gets over (let's say) 1,000 frames, start over with circular reference detection.

Internet Explorer 8 compatibility?

I noticed the compatibility list has IE9 and up, but does not list IE8. Unfortunately we have to support that. Has anyone actually tested seamless-immutable in IE8?

Cursors

Just wondering before I test this out, I use cursors to great effect alongside the fynx immutable-flux library. I'd definitely want to test this library out as I am only targeting mobile platforms so I don't need old IE support.

Plans for cursors, compat with other cursor impls?

Immutable doesn't freeze Date objects.

console test:

var mydate = Immutable(new Date());
undefined
mydate
Fri Jun 12 2015 15:24:24 GMT+1000 (AEST)
mydate.setYear(2020)
1591939464409
mydate
Fri Jun 12 2020 15:24:24 GMT+1000 (AEST)

Last entry shows Date object hasn't been made immutable.

Should functions be treated as already immutable?

Technically JS functions are also mutable objects. Is it worth going through the trouble of converting them to be properly immutable? (e.g. freezing them etc.)

I'm defaulting to "no" for the sake of conserving performance, as functions are typically not used to store data, but noting the decision here in case it merits revisiting later.

merge should return the same object when no meaningful changes were applied

I think it would be helpful if merge returned the same object when no changes were made. Here's what I mean:

obj  = Immutable({ key: 'value' })
obj1 = obj.merge({ key: 'new value '}) 
obj2 = obj.merge({ key: 'value' }) //no meaningful change

// current behavior
obj === obj1 //false
obj === obj2 //false

// ideal behavior
obj === obj1 //false
obj === obj2 //true

Immutable.js provides this feature with their 'set' function, which I've found helpful in the past.

This allows users to run quick checks to see if anything has changed after a merge, which would, for instance, be useful in the context of react/flux. While the check can be done after the fact (using a deepEquals function or something like that), combining that logic with merge has obvious performance benefits.

Maybe both versions of merge should be included in the public API, e.g. 'merge' and 'safeMerge' -- but that may also be overkill. Interested to hear thoughts on this.

Add function for deep merge?

I did this a few days ago in a fork:
crudh@8552c98

mergeDeep is like the normal merge function but it merges all children objects too. Right now there is no code reuse from the normal merge (except for 2 lines it is a copy and paste of merge) and the tests are simple copies from the normal merge.

Before cleaning it up for my fork I just wanted to know if there is any interest to pull it back into master? If so, are there any preferences for naming, code reuse and so on?

Handling additional object types

I use moments (moment.js) rather then Date objects and they get lost in translation due to the cloning process. Does it make sense for there to be a way to tell Immutable how to handle additional object types?

Using Map method with React causes infinite recursion

I am not 100% sure what's happening as my research was limited. Still I'll try to explain the problem.

var constants = immutable({
        BASE_PATH: '/my/files'
    }),
    BASE = constants.BASE_PATH,
    menu =  [
        {
            link: BASE,
            title: 'Files'
        },
        {
            link: `${BASE}/:sharing`,
            title: 'Sharing'
        }
    ];

...

    render() {

        var moreParams = ( this.props.params.splat || '' ).split('/'),
            command = moreParams.length && moreParams[0].charAt(0) == ':' ? moreParams[0].substr(1) : null;

        return (
            <div className="_personal-storage">
                <Menu items={ menu } />
                <Storage base={ BASE } callback={ processCommand }>
                    <h1>Personal storage</h1>
                </Storage>
            </div>
        );
    }

Everything works this way, but as soon as I make menu immutable, it goes to the weirdest infinite recursion in seamless-immutable.js:

Uncaught RangeError: Maximum call stack size exceeded
defineProperty  @   (program):779
addPropertyTo   @   seamless-immutable.js:5
makeImmutableObject @   seamless-immutable.js:342
Immutable   @   seamless-immutable.js:371
Immutable   @   seamless-immutable.js:367
Immutable   @   seamless-immutable.js:367
Immutable   @   seamless-immutable.js:367
makeImmutableArray  @   seamless-immutable.js:106
Immutable   @   seamless-immutable.js:354
Immutable   @   seamless-immutable.js:367

The weirdest part is - it happens only if I pass menu as a property to a react component. If I just log it to the console it works just fine. It doesn't matter if I make it immutable on declaration or later - it works the same.

When I've been debugging it, I couldn't believe my eyes - after making immutable my objects, it started making immutable React stuff {$$typeof: Symbol(react.element), key: null, ref: null, props: Object, _owner: ReactCompositeComponentWrapper…}$$typeof: Symbol(react.element)_owner: ReactCompositeComponentWrapper_self: null_source: null_store: Objectkey: nullprops: Objectref: nulltype: _default(props)__proto__: Object and this is where the infinite recursion happens. But why does it make it immutable at all? I have no idea.

README example is mutable

I ran the first code example in a Node 4.1.1 console and everything that should not have mutated did. What could I be missing?

var Immutable = require('seamless-immutable');
var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);

array[1] = "I'm going to mutate you!"
array[1] // "I'm going to mutate you!"

array[2].hammer = "hm, surely I can mutate this nested object..."
array[2].hammer // "hm, surely I can mutate this nested object..."

for (var index in array) { console.log(array[index]); }
// "totally"
// "I'm going to mutate you!"
// { hammer: "hm, surely I can mutate this nested object..." }

JSON.stringify(array) // '["totally","I'm going to mutate you!",{"hammer":"hm, surely I can mutate this nested object..."}]'

Performance Hit while merging really large JS objects even when merge deep is set to false

screen shot 2015-10-20 at 1 32 05 pm

Scenario

Recieved a huge JSON response from an API request.

Problem

While setting the response as a JS object, into an immutable store via merge — the function converts the huge object to an Immutable recurrsively. This is causes performance issues and makes the rendering slow.

Ideally if the user has set {deep: false} at the time of merging, it should not try to convert recursively.

Add deepEquals

Add a deepEquals to both ImmutableArray and ImmutableObject which skips checking any branches where the children are === and immutable.

merge doesn't return the same object in the case of nested immutables.

Here's current behavior as of 1.3.0:

obj = Immutable( {a: [1,2,3]} )
obj1 = obj.merge( {a: Immutable([1,2,3]) } )
obj2 = obj.merge( {a: [1,2,3] } )
obj === obj1 //false
obj === obj2 //false

obj3 = Immutable( {a: {b: 4} } )
obj4 = obj3.merge( {a: {b: 4} } )
obj5 = obj3.merge( Immutable({a: {b: 4} }) )
obj3 === obj4 //false
obj3 === obj5 //false

Missing methods

I love seamless-immutable, but am missing a few methods such as immutablejs' update and setIn. Are there plans to add some? Another option would be to make seamless-immutable pluggable.

breaks on a date

Example:

var Immutable = require('seamless-immutable');
var d = { date: new Date() };
console.log(d.date.toISOString());
var d = Immutable(d);
console.log(d.date.toISOString());

Console:

2015-03-18T03:17:29.895Z
/Users/matt/Playground/seamless-immutable/index.js:5
console.log(d.date.toISOString());
                   ^
TypeError: undefined is not a function
    at Object.<anonymous> (/Users/matt/Playground/seamless-immutable/index.js:5:20)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)
    at node.js:814:3

Validate immutable objects

Is there a way to validate that an array is an immutable array? Like something that would be instanceof for immutable arrays but not for normal mutable arrays.

The use case is actually React.Proptypes validation. Something similar to

propTypes: {
    myobject: React.PropTypes.instanceOf(Immutable.List)
}

I know I can use a custom function in ProtoTypes and use Immutable.isImmutable, but adding a function like that in every component that should receive immutable prop objects, will not be pretty.

Preserve object prototypes

Currently, if you pass an object with a prototype to Immutable, that prototype is discarded. It seems reasonable to preserve that prototype instead, with the understanding that not all of the methods it exposes will necessarily still work.

This would address #24

Incompatible with Object.create(null)

Hello. Thanks for seamless. It's a very nice piece of software.

I was surprised however that I cannot create immutable object from an object without prototype (e.g. acquired with Object.create(null):

Immutable(Object.create(null));
// TypeError: target.hasOwnProperty is not a function

It is also impossible to merge such an object into immutable:

var o1 = Immutable({a: 1});
var o2 = Object.create(null);
o2.b = 2;
o1.merge(o2);
// TypeError: other.hasOwnProperty is not a function

Obviously it depends on hasOwnProperty method of the original object, which is usually available in a prototype chain.

Can we use global Object methods instead, like Object.getOwnPropertyDescriptor? I'll be happy to provide PR, but would like to discuss it first.

Question about `.push`

What do you think about making push immutable operation (returning new extended array) instead of blocking it? The drawback is that you change conventional (but broken) API, but the benefit is that people could stop to use concat where push will satisfy. My arguments:

  1. Barely noone is rely on native push return value (updated array length 😞) right now so this API "change" shouldn't surprise or hurt.
  2. You don't modify Array.prototype so this is safe.
  3. You already added some unexisting but crucial methods like flatMap
    API is already changed why not fix push then.

Immutable.js like Records

I'd like to be able to use this library to approximate Immutable.js Records: https://facebook.github.io/immutable-js/docs/#/Record

The benefits I see from this are helper functions (e.g. calculated fields on a data object- getName() on an Immutable({firsName: ..., lastName: ...}), defaulting fields, and validation.

From #25, I can see you've chosen not to support object prototypes by default, but I was wondering if there was a way to opt into its use. Looking at the code, I couldn't find a simple solution for this.

Do you have suggestions on how this type of functionality could be added, or potential hooks to enable the usecases above (defaulting might be the trickiest, but the others I feel could be handled by methods on the object)?

Thanks!

using seamless-immutable in a jsbin

I'm trying to use seamless-immutable in this jsbin but whenever this line is ran:

var array = Immutable(["foo", "foo2", "bar", "bar2", "baz", "baz2"]);

I get this error:

Uncaught TypeError: Cannot redefine property: [object Object]

And this stack trace:

addPropertyTo   @   seamless-immutable.js:5
makeMethodReturnImmutable   @   seamless-immutable.js:84
makeImmutableArray  @   seamless-immutable.js:94
Immutable   @   seamless-immutable.js:328
(anonymous function)    @   nesomi.js:36
applyStr    @   ember.debug.js:18054
sendEvent   @   ember.debug.js:12373
exports.default.mixin.Mixin.create.trigger  @   ember.debug.js:29705
superFunction   @   ember.debug.js:13704
EmberObject.default.extend.trigger  @   ember.debug.js:40271
superWrapper    @   ember.debug.js:17586
Renderer.default.didInsertElement   @   ember.debug.js:38923
Renderer_renderTree @   ember.debug.js:8480
scheduledRenderTree @   ember.debug.js:8506
Queue.invoke    @   ember.debug.js:871
Queue.flush @   ember.debug.js:936
DeferredActionQueues.flush  @   ember.debug.js:741
Backburner.end  @   ember.debug.js:166
Backburner.run  @   ember.debug.js:221
executeTimers   @   ember.debug.js:603
(anonymous function)    @   ember.debug.js:592

asMutable({deep:true}) doesn't work with date objects.

var test = Immutable({mydate: new Date()})
test.asMutable({deep:true});

result:

Uncaught TypeError: obj.asMutable is not a function
at asDeepMutable (http://localhost:8080/resource-bundles/bowerComponents.resource/seamless-immutable/seamless-immutable.js:215:20)
at Object.asMutableObject (http://localhost:8080/resource-bundles/bowerComponents.resource/seamless-immutable/seamless-immutable.js:304:31)
at Object.eval (eval at evaluate (unknown source), :1:6)
at Object.InjectedScript._evaluateOn (:895:55)
at Object.InjectedScript._evaluateAndWrap (:828:34)
at Object.InjectedScript.evaluateOnCallFrame (:954:21)

Deep merge fails in non-trivial cases

Unless I'm doing something daft (always a strong possibility!). Given

var obj = Immutable({
  streams: {
    a: { name: "Conversations", channel: ["Facebook","Twitter"], loading: false},
    b: { name: "@Mentions", channel: ["Twitter"], loading: false},
    c: { name: "Facebook", channel: ["Facebook"], loading: false}
  },
  streamOrder: ["c", "a", "b"]
})

then running j.merge({streams: {a: { loading: true }}}, true) produces

{
  streams: {
    a: { loading:true }
  },
  streamOrder: {0: "c", 1: "a", 2: "b"}
}

so instead of doing a deep merge it has thrown away all of the data under the streams key! Have I misunderstood how the deep merge is meant to work?

It's also converted the data under streamOder from an Array to an Object, but I can live with that for the time being :-)

update mutable objects in a callback?

Like how immutable-js does it, but maybe it passes a mutable copy into the function?

Like this?

var item = Immutable({name: "bob"})

// new Item is immutable, obj is mutable
var newItem = item.mutate(function(obj) {
   obj.name = "henry"
})

The rationale is that it's really handy to be able to make multiple changes to an object in one go, and get an immutable object back. I'd prefer to have that callback be an immutable function itself, but I can't think of a good way to set properties without breaking type safety (imagine trying to use your .merge with flow).

merge can create partial Immutables

When merging an immutable object with a mutable object, the resulting immutable object can contain mutable objects, but still identifies as immutable. For example, here's current behavior:

obj = Immutable( { key: 'value', collection: [ 1,2,3 ] } )
Immutable.isImmutable( obj ) //true
Immutable.isImmutable( obj['collection'] ) //true

newObj = obj.merge( { collection: [ 1,2,3 ] } )
Immutable.isImmutable( newObj ) //true
Immutable.isImmutable( newObj[ 'collection' ] ) //false
Immutable.isImmutable( newObj[ 'collection' ].push ) //danger!

its unclear what expected behavior should be. Personally, I think .merge should deeply convert everything that gets passed in to be immutable, as these partially immutable objects can create strange bugs. But maybe we should give users more flexibility. We could just add an isDeeplyImmutable function, and let users deal with the consequences.

merge doesn't return the same object in the case of nested immutables.

Here's current behavior as of 1.3.0:

obj = Immutable( {a: [1,2,3]} )
obj1 = obj.merge( {a: Immutable([1,2,3]) } )
obj2 = obj.merge( {a: [1,2,3] } )
obj === obj1 //false
obj === obj2 //false

obj3 = Immutable( {a: {b: 4} } )
obj4 = obj3.merge( {a: {b: 4} } )
obj5 = obj3.merge( Immutable({a: {b: 4} }) )
obj3 === obj4 //false
obj3 === obj5 //false

Map returns an Immutable object

I don't really understand why .map is wrapped to return an Immutable object. This seems needlessly restricting. In combination with #16, this also causes unexpected stack overflow errors.

Immutable([1,2,3]).map(function() {
   return React.createElement( "div", null );
});
// too much recursion

How should I import this module from es6?

Thanks for this module!

Importing like this in es5: var I = require('seamless-immutable') gives me:

{ [Function: Immutable]
  isImmutable: [Function: isImmutable],
  ImmutableError: [Function: ImmutableError] }

but I do not find a way to get the same thing in es6.
Doing import * as I from 'seamless-immutable' gives:

{ isImmutable: [Function: isImmutable],
  ImmutableError: [Function: ImmutableError],
  default: 
   { [Function: Immutable]
     isImmutable: [Function: isImmutable],
     ImmutableError: [Function: ImmutableError] } }

I can see that what I need is the default inside but I do not manage to get it
Thank you!.

instanceof checks can cause issues

Using instanceof to check for arrays etc. can cause issues across frame boundaries. Each document has a different context so one array may not be an instanceof another.

var frame = document.createElement("iframe");

frame.onload = function(e) {
  console.log([] instanceof frame.contentWindow.Array); // false
}

document.body.appendChild(frame);

How to modify an immutable array?

When using seamless-immutable, what is the recommended way of deriving new values from existing one (when map and merge aren't enough)? Typically, immutable data structures (IDS) have functions that create new IDS derived from them, such as Immutable.js' aList.push(1, 2) or Clojure's updateIn, assocIn, dissoc, etc. In seamless-immutable all array/object mutating functions just throw an error so to create a new IDS I suppose I have to do something like

var newArray; var tmp = Immutable([1,2,3]).asMutable(); tmp.push(4); newArray = Immutable(tmp);

Is that correct? Thank you!

Use with AngularJS

I'm attempting to use this in my angularjs app. It doesn't appear to be working. Any clue on how you'd implement it within Angular?

document technique for updating an individual key

with Immutable.js I can do

Immutable.List.of(1,2,3).update(0, x => x + 1)

This is useful if I have view state that is a list of 10 elements and I want to update just one of them that the user acts on.

What is the best practice for accomplishing this with seamless-immutable?

Merge does a copy if a value is an array

Hi,

Thank you for that librairy, much easier to integrate in a React/Flux stack than immutable.js!

I want to use immutable variables to be able to not refresh my UI if the content of my objects doesn't change. In the shouldComponentUpdate of my components, i want to test the strict equality between the old and the new prop. But it won't work as expected because:

> o = {a: [1, 2]}
{ a: [ 1, 2 ] }
> o2 = o
{ a: [ 1, 2 ] }
> o === o2
true
> i = Immutable(o)
{ a: [ 1, 2 ] }
> i2 = i.merge(o2)
{ a: [ 1, 2 ] }
> i === i2
false

I would like to read true here.

The same test with an integer / string is working "as expected":

> o = {a: 'toto'}
{ a: 'toto' }
> o2 = {a: 'toto'}
{ a: 'toto' }
> i = Immutable(o)
{ a: 'toto' }
> i2 = i.merge(o2)
{ a: 'toto' }
> i === i2
true

My hopes are shattered!

But maybe I am missing something?
Thank you for your inputs.

Prod Mode

In production, it's probably unnecessary to actually freeze objects and throw exceptions when mutating methods are invoked. Those are mainly necessary in development, but by the time the code hits production, if any invariants aren't being followed, you should already know about it.

With that in mind, having a way to turn off the freezing and overriding should speed up performance, particularly in Safari. Adding a quick flag to turn this on would be useful to that end.

Have banned methods return a new Immutable instance instead

Hi,

I love the fact that this library is super small and that doesn't try to do anything else than making your data immutable :).

There's only one thing holding me back from using it though:

Immutable([3, 1, 4]).sort()
// This will throw an ImmutableError, because sort() is a mutating method.

Have you considered returning a new immutable structure with the sorted array instead? (the same applies for every other banned method).

Thanks :)
Darío

Question: How does this compare to icepick?

Hello,

I wonder whether I should pick seamless-immutable or icepick. They both seem to be doing the same thing, providing utilities for "modifying" deeply frozen data structures. I can see some differences but know too little to be able to really compare them and decide. Could you be so kind and describe the advantages of seamless over icepick and vice versa? Thanks a lot!

PS: I'll ask at the other project as well

Is there any interest in allowing mutable methods to return copies?

I've moved some bits around in a local clone of the repo so that rather than throwing an exception when any of the banned mutable methods are called, a quick copy is created and is mutated then returned as immutable.

Most of this logic happens in a new function.

function proxyMutableMethod(obj, methodName) {                                                                                                   
   var methodRef = obj[methodName];                                                                                                               

  addPropertyTo(obj, methodName, function() {                                                                                                    
    var host = obj instanceof Array ? [] : {},                                                                                                   
         copy = quickCopy(obj, host);

    methodRef.apply(copy, arguments);
    return Immutable(copy);
  });
}

Rather than calling banProperty for each of the banned methods, proxyMutableMethod is called instead.

Now aside from the obvious performance implications (all these operations become at least O(n)*) is there a reason why this isn't already a feature of the library? I understand that the performance itself may be enough of a reason, but it makes it more friendly for people coming from Clojure or Immutable.js. Just want to know whether I'm missing something before I crack on with writing the tests and amending the docs so I can send a pull request.

* It wouldn't be too much work to add an interface for defining structural sharing overrides for some of the mutable methods though.

Custom merger function

I have a use case where I would like to override the normal merge behaviour for specific types or conditions.

I was thinking that you could send a custom merger function as a parameter with the config to merge. If that function returns undefined the merge would proceed as normal. If it returns something else then that value will be used instead of the normal rules.

A simple example that combines arrays instead of replacing them:

var one = {
  id: 3,
  list: [1, 3, 5]
};

var two = {
  id: 3,
  list: [2, 4, 6]
};

var arrayConcatMerger = function(current, other) {
  if (current instanceof Array && other instanceof Array) {
    return current.concat(other);
  }

  return;
}

var result = one.merge(two, {merger: arrayConcatMerger});

And the result would be:

 {
  id: 3,
  list: [1, 3, 5, 2, 4, 6]
};

This would allow you to do more complex stuff like merging objects in an array if there exists an object with the same id in both arrays and so on.

Benchmarks

Someone on Hacker News brought up benchmarks. It would be interesting to see how seamless-immutable compares to other immutable libraries in js-hashtrie-benchmark (which seems like a fair comparison given API similarities, even though it does not happen to use a hashtrie implementation under the hood), particularly once Prod Mode exists.

Merge ought error out when passed a string.

current behavior:

  foo = Immutable({key: 'value'})
  // Object {key: 'value'}
  foo.merge('whoops')
  // Object {0: "w", 1: "h", 2: "o", 3: "o", 4: "p", 5: "s", key: 'value'}

While this conforms with JavaScript strategy of coercing strings into objects, it leads to crazy errors. FWIW, lodash simply ignores strings that are passed to merge.

should objects with custom prototypes really get immutablized?

After playing with this a bit I realized I really don't think objects with custom prototypes should get Immutableized, recursively or whatever. The most common thing I've run into is trying to pass in myImmutable.map(item => (<MyComponent {...item}/>)) as the children of a React component. map stack overflows on trying to Immutable()-ize the React elements. What are the use cases for trying to immutablize objects with custom prototypes? Even Immutable.js allows you to stick non-immutable things into an immutable type. I prefer this concept of shallow immutability.

case

Very cool! I think it's frowned upon though to have functions that start with a capital letter that are not constructor functions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.