Skip to content

Immutable/Constant Objects for JavaScript

License

Notifications You must be signed in to change notification settings

bloomberg/constant.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Constant.JS Build Status

A minimalistic javascript constant object library. It utilises Object.freeze to prevent mutations, however it does deep-freeze objects making them truly constant.

The constant object is identical (obj === constant(obj)), so it does in fact freeze the original objects. Accessing properties can therefore be done in the standard JS way (obj.prop or obj[propname]).

There are also convenience access methods and mutation methods supported making handling of constant object hierarchies easy.

In general this library shares common tree branches in hierarchies. This goes so far as to return the same object from alteration methods that do not actually modify the object where this is easily detected.

Installation

npm install --save constant.js
npm test

This library has 100% test coverage. Pull requests are always welcome, however they will only be accepted if the test-coverage remains at 100%.

Static Functions

constant(object)

Converts an object to an constant version.

constant.parse(string)

Parses a JSON string into an constant object.

constant.is(object)

Tests if an object has been made constant.

constant.isArray(object)

Tests if an object is an Array and has been made constant.

constant.isObject(object)

Tests if an object is a plain Object and has been made constant.

constant.isDate(object)

Tests if an object is a Date and has been made constant.

constant.isRegExp(object)

Tests if an object is a RegExp and has been made constant.

Common Methods

These methods a available on all constant objects whether they be plain Objects, Arrays or Dates. All prototype methods remain available, but using modifying methods (if they have not been implemented here) will throw in strict mode or not have an effect in non-strict mode.

constantObject.mutate(path, method[, arg, ...])

This method performs a deep mutation on an object by calling one of its child's methods.

Example:

var obj = constant({ a: { b: [ new Date(0) ] } });
var res = obj.mutate([ 'a', 'b', '0' ], 'setFullYear', 1900);
JSON.stringify(res); // { "a": { "b": [ "1900-01-01T00:00:00.000Z"] } }

constantObject.get()

This method gets a value deep inside an object hierarchy.

Example:

var obj = constant({ a: { b: { c: 'value' } } });
obj.get('a', 'b', 'c'); // 'value'

constantObject.set()

This method sets a value deep inside an object hierarchy and returns a new constant object containing the change. The original constant remains unchanged. This method ensures that all values that remain unchanged will be reused. If the "change" isn't actually a change the return value will be the same object.

Example:

var obj = constant({ a: { b: { c: 'value' } }, d: { other: 'branch' } });
var chg = obj.set('a', 'b', 'c', 'new-value');
chg.d === obj.d; // true
chg.a.b.c; // 'new-value'
obj.a.b.c; // 'value'
chg = obj.set('a', 'b', 'c', 'value');
chg === obj; // true

constantObject.mutable()

Creates a shallow mutable version of the object. It's children in the hierarchy remain constant.

Example:

var obj = constant({ a: { b: { c: 'value' } }, d: { other: 'branch' } });
var mut = obj.mutable();
constant.isObject(mut); // false
constant.isObject(mut.a); // true
constant.isObject(mut.d); // true
mut.f = 'hallo';
mut.f; // 'hallo';

constantObject.toJS()

Creates a deep mutable version of the object. This means that the entire hierarchy will be mutable. It will retain cyclical integrity provided there is the global Map builtin.

If that is not the case, then a call on a cyclical constant object will loop indefinitely!

Only use cyclical structures if you have a modern environment!

Example:

var obj = constant({ a: { b: { c: 'value' } }, d: { other: 'branch' } });
var mut = obj.mutable();
constant.isObject(mut); // false
constant.isObject(mut.a); // false
constant.isObject(mut.d); // false
mut.f = 'hallo';
mut.f; // 'hallo';

Object Methods

constantObject.assign(obj[, obj[, ...]])

Similar to Object.assign in that it merges object into a constant object. It then returns a new constant object where the properties of the argument objects have been merged in.

var obj = constant({ a: 'test', b: { c: true } });
var mut = obj.assign({ a: 'hallo', d: { e: 5 } });
constant.isObject(mut); // true
constant.isObject(mut.b); // true
constant.isObject(mut.d); // true
obj.b === mut.b; // true
JSON.stringify(mut); // {"a":"hallo","b":{"c":true},"d":{"e":5}}

constantObject.transform(transformFn, thisPointer)

Works just like Array.prototype.map except on an object and that it returns a new constant object and allows removal of an element by returning undefined.

Example:

var obj = constant({ a: 1, b: 2, c: 3, d: 4, e: 5 });
mut = obj.transform(function(val, key) {
  return key === 'b' ? undefined : (val * 2);
});
constant.isObject(mut); // true
JSON.stringify(mut); // {"a": 2,"c":6,"d":8,"e":10}

mut = obj.transform(function(val) {
    return val;
});
obj === mut; //true

Array Methods

All methods available trough Array.prototype are available on constant Array objects as well except that modifying methods have been replaced with ones returning a new constant Array object instead.

constantArray.splice(idx, delcnt, insitem)

Just like the regular Array.prototype.splice method except that rather than returning the deleted elements, It returns a new constant Array with the changes applied. Also inserted elements will become constant.

Example:

var arr = constant([1, 2, 3, 4, 5]);
var mut = arr.splice(1,3, 'element');
constant.isArray(mut); // true
JSON.stringify(mut); // '[ 1, "element", 5 ]'

constantArray.concat(array)

Just like the regular Array.prototype.concat method except that the resulting array is constant.

Example:

var arr = constant([1, 2, 3 ]);
var mut = arr.concat([ 4, 5, 6 ]);
constant.isArray(mut); // true
JSON.stringify(mut); // '[ 1, 2, 3, 4, 5, 6 ]'

constantArray.reverse()

Just like the regular Array.prototype.reverse method except that the resulting array is constant.

Example:

var arr = constant([1, 2, 3, 4, 5, 6 ]);
var mut = arr.reverse();
constant.isArray(mut); // true
JSON.stringify(mut); // '[ 6, 5, 4, 3, 2, 1 ]'

constantArray.sort(sortFunction)

Just like the regular Array.prototype.sort method except that the resulting array is constant.

Example:

var arr = constant([ 6, 5, 4, 3, 2, 1 ]);
var mut = arr.reverse();
constant.isArray(mut); // true
JSON.stringify(mut); // '[1, 2, 3, 4, 5, 6 ]'

constantArray.insert(idx, element)

Inserts a new element at a specified index. If element isn't constant it will be on insertion.

Example:

var arr = constant([ 1, 2, 4, 5 ]);
var mut = arr.insert(2, 3);
constant.isArray(mut); // true
JSON.stringify(mut); // '[1, 2, 3, 4, 5 ]'

constantArray.delete()

Removes an element from an array and returns the constant changed array.

Example:

var arr = constant([ 1, 2, 3, 4, 5 ]);
var mut = arr.delete(2);
constant.isArray(mut); // true
JSON.stringify(mut); // '[1, 2, 4, 5 ]'

constantArray.shift()

Removes the first element of an array and returns the changed constant array.

Example:

var arr = constant([ 1, 2, 3, 4, 5 ]);
var mut = arr.shift();
constant.isArray(mut); // true
JSON.stringify(mut); // '[ 2, 3, 4, 5  ]'

constantArray.unshift(element)

Adds an element at the beginning of an array. Returns a new constant array with that element.

Example:

var arr = constant([ 1, 2, 3 ]);
var mut = arr.unshift(0);
constant.isArray(mut); // true
JSON.stringify(mut); // '[ 0, 1, 2, 3 ]'

constantArray.pop()

Removes the last element of an array and returns the changed constant array.

Example:

var arr = constant([ 1, 2, 3, 4, 5 ]);
var mut = arr.pop();
constant.isArray(mut); // true
JSON.stringify(mut); // '[ 1, 2, 3, 4 ]'

constantArray.push(element)

Adds an element at the end of an array. Returns a new constant array with that element.

Example:

var arr = constant([ 1, 2, 3 ]);
var mut = arr.push(4);
constant.isArray(mut); // true
JSON.stringify(mut); // '[ 1, 2, 3, 4 ]'

constantArray.transform(transformFn, thisPointer)

Works just like Array.prototype.map except that it returns a new constant array and allows removal of an element by returning undefined.

Example:

var arr = constant([ 0, 1, 2, 3, 4 ]);
var mut = arr.transform(function(val) {
  return val ? (val * 2) : undefined;
});
constant.isArray(mut); // true
JSON.stringify(mut); // '[ 2, 4, 6, 8 ]'

constantArray.first and constantArray.last

.first ist the first element in an array and .last is the last element in an array.

Example:

var arr = constant([ 1, 2, 3]);
arr.first; // 1
arr.last; // 3

Date Methods

All methods available trough Date.prototype are available on constant Date objects as well except that modifying methods have been replaced with ones returning a new constant Date object instead.

constantDate.setDate()

Works just like Date.prototype.setDate except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setFullYear()

Works just like Date.prototype.setFullYear except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setHours()

Works just like Date.prototype.setHours except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setMilliseconds()

Works just like Date.prototype.setMilliseconds except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setMinutes()

Works just like Date.prototype.setMinutes except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setMonth()

Works just like Date.prototype.setMonth except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setSeconds()

Works just like Date.prototype.setSeconds except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setTime()

Works just like Date.prototype.setTime except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setUTCDate()

Works just like Date.prototype.setUTCDate except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setUTCFullYear()

Works just like Date.prototype.setUTCFullYear except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setUTCHours()

Works just like Date.prototype.setUTCHours except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setUTCMilliseconds()

Works just like Date.prototype.setUTCMilliseconds except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setUTCMinutes()

Works just like Date.prototype.setUTCMinutes except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setUTCMonth()

Works just like Date.prototype.setUTCMonth except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setUTCSeconds()

Works just like Date.prototype.setUTCSeconds except that rather than modifying the date, it returns a new constant date with the change applied.

constantDate.setYear()

Works just like Date.prototype.setYear except that rather than modifying the date, it returns a new constant date with the change applied.

Map Methods

constantMap.set(key, val)

Works just like Map.prototype.set except it returns a contant Map instance that is the altered map.

constantMap.delete(key)

Works just like Map.prototype.delete except it returns a constant Map instance that is the altered map.

constantMap.clear()

Works just like Map.prototype.clear except it returns a constant Map instance that is empty.

Set Methods

constantSet.add(val)

Works just like Set.prototype.add except it returns a constant Map instance that is empty.

constantSet.delete(val)

Works just like Set.prototype.delete except it returns a constant Map instance that is empty.

constantSet.get(idx)

Gets the value of an element of a Set by its index. This utilizes the @@iterator so for large sets this will be expensive depending on the value of idx.

constantSet.clear()

Works just like Set.prototype.clear except it returns a constant Map instance that is empty.

Custom Object Types

Some object types require more information to properly handle. This can be done by placing a property on the prototype.

function MyClass() {}
MyClass.prototype[constant.COPY] = function() {
    var copy = new MyClass();
    copy.setValue(this.getValue());
    return copy;
};
MyClass.prototype[constant.COPY].proto = {};
MyClass.prototype[constant.COPY].proto.setValue = function(val) {
    var mut = this.mutable();
    mut.setValue(val);
    return constant(mut);
};
MyClass.isConstant = function(obj) {
    return constant.is(obj) && (obj[constant.COPY] === MyClass.prototype[constant.COPY]);
};

In this fashion it is possible to support any arbitrary object as a constant object and add custom methods for getting mutated versions of them.