immutability-helper
Version:
mutate a copy of data without changing the original source
251 lines (250 loc) • 10.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
function stringifiable(obj) {
// Safely stringify Object.create(null)
/* istanbul ignore next */
return typeof obj === 'object' && !('toString' in obj) ?
Object.prototype.toString.call(obj).slice(8, -1) :
obj;
}
var isProduction = typeof process === 'object' && process.env.NODE_ENV === 'production';
function invariant(condition, message) {
if (!condition) {
/* istanbul ignore next */
if (isProduction) {
throw new Error('Invariant failed');
}
throw new Error(message());
}
}
exports.invariant = invariant;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var splice = Array.prototype.splice;
var toString = Object.prototype.toString;
function type(obj) {
return toString.call(obj).slice(8, -1);
}
var assign = Object.assign || /* istanbul ignore next */ (function (target, source) {
getAllKeys(source).forEach(function (key) {
if (hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
});
return target;
});
var getAllKeys = typeof Object.getOwnPropertySymbols === 'function'
? function (obj) { return Object.keys(obj).concat(Object.getOwnPropertySymbols(obj)); }
/* istanbul ignore next */
: function (obj) { return Object.keys(obj); };
function copy(object) {
return Array.isArray(object)
? assign(object.constructor(object.length), object)
: (type(object) === 'Map')
? new Map(object)
: (type(object) === 'Set')
? new Set(object)
: (object && typeof object === 'object')
? assign(Object.create(Object.getPrototypeOf(object)), object)
/* istanbul ignore next */
: object;
}
var Context = /** @class */ (function () {
function Context() {
this.commands = assign({}, defaultCommands);
this.update = this.update.bind(this);
// Deprecated: update.extend, update.isEquals and update.newContext
this.update.extend = this.extend = this.extend.bind(this);
this.update.isEquals = function (x, y) { return x === y; };
this.update.newContext = function () { return new Context().update; };
}
Object.defineProperty(Context.prototype, "isEquals", {
get: function () {
return this.update.isEquals;
},
set: function (value) {
this.update.isEquals = value;
},
enumerable: true,
configurable: true
});
Context.prototype.extend = function (directive, fn) {
this.commands[directive] = fn;
};
Context.prototype.update = function (object, $spec) {
var _this = this;
var spec = (typeof $spec === 'function') ? { $apply: $spec } : $spec;
if (!(Array.isArray(object) && Array.isArray(spec))) {
invariant(!Array.isArray(spec), function () { return "update(): You provided an invalid spec to update(). The spec may " +
"not contain an array except as the value of $set, $push, $unshift, " +
"$splice or any custom command allowing an array value."; });
}
invariant(typeof spec === 'object' && spec !== null, function () { return "update(): You provided an invalid spec to update(). The spec and " +
"every included key path must be plain objects containing one of the " +
("following commands: " + Object.keys(_this.commands).join(', ') + "."); });
var nextObject = object;
getAllKeys(spec).forEach(function (key) {
if (hasOwnProperty.call(_this.commands, key)) {
var objectWasNextObject = object === nextObject;
nextObject = _this.commands[key](spec[key], nextObject, spec, object);
if (objectWasNextObject && _this.isEquals(nextObject, object)) {
nextObject = object;
}
}
else {
var nextValueForKey = type(object) === 'Map'
? _this.update(object.get(key), spec[key])
: _this.update(object[key], spec[key]);
var nextObjectValue = type(nextObject) === 'Map'
? nextObject.get(key)
: nextObject[key];
if (!_this.isEquals(nextValueForKey, nextObjectValue)
|| typeof nextValueForKey === 'undefined'
&& !hasOwnProperty.call(object, key)) {
if (nextObject === object) {
nextObject = copy(object);
}
if (type(nextObject) === 'Map') {
nextObject.set(key, nextValueForKey);
}
else {
nextObject[key] = nextValueForKey;
}
}
}
});
return nextObject;
};
return Context;
}());
exports.Context = Context;
var defaultCommands = {
$push: function (value, nextObject, spec) {
invariantPushAndUnshift(nextObject, spec, '$push');
return value.length ? nextObject.concat(value) : nextObject;
},
$unshift: function (value, nextObject, spec) {
invariantPushAndUnshift(nextObject, spec, '$unshift');
return value.length ? value.concat(nextObject) : nextObject;
},
$splice: function (value, nextObject, spec, originalObject) {
invariantSplices(nextObject, spec);
value.forEach(function (args) {
invariantSplice(args);
if (nextObject === originalObject && args.length) {
nextObject = copy(originalObject);
}
splice.apply(nextObject, args);
});
return nextObject;
},
$set: function (value, _nextObject, spec) {
invariantSet(spec);
return value;
},
$toggle: function (targets, nextObject) {
invariantSpecArray(targets, '$toggle');
var nextObjectCopy = targets.length ? copy(nextObject) : nextObject;
targets.forEach(function (target) {
nextObjectCopy[target] = !nextObject[target];
});
return nextObjectCopy;
},
$unset: function (value, nextObject, _spec, originalObject) {
invariantSpecArray(value, '$unset');
value.forEach(function (key) {
if (Object.hasOwnProperty.call(nextObject, key)) {
if (nextObject === originalObject) {
nextObject = copy(originalObject);
}
delete nextObject[key];
}
});
return nextObject;
},
$add: function (values, nextObject, _spec, originalObject) {
invariantMapOrSet(nextObject, '$add');
invariantSpecArray(values, '$add');
if (type(nextObject) === 'Map') {
values.forEach(function (_a) {
var key = _a[0], value = _a[1];
if (nextObject === originalObject && nextObject.get(key) !== value) {
nextObject = copy(originalObject);
}
nextObject.set(key, value);
});
}
else {
values.forEach(function (value) {
if (nextObject === originalObject && !nextObject.has(value)) {
nextObject = copy(originalObject);
}
nextObject.add(value);
});
}
return nextObject;
},
$remove: function (value, nextObject, _spec, originalObject) {
invariantMapOrSet(nextObject, '$remove');
invariantSpecArray(value, '$remove');
value.forEach(function (key) {
if (nextObject === originalObject && nextObject.has(key)) {
nextObject = copy(originalObject);
}
nextObject.delete(key);
});
return nextObject;
},
$merge: function (value, nextObject, _spec, originalObject) {
invariantMerge(nextObject, value);
getAllKeys(value).forEach(function (key) {
if (value[key] !== nextObject[key]) {
if (nextObject === originalObject) {
nextObject = copy(originalObject);
}
nextObject[key] = value[key];
}
});
return nextObject;
},
$apply: function (value, original) {
invariantApply(value);
return value(original);
},
};
var defaultContext = new Context();
exports.isEquals = defaultContext.update.isEquals;
exports.extend = defaultContext.extend;
exports.default = defaultContext.update;
// @ts-ignore
exports.default.default = module.exports = assign(exports.default, exports);
// invariants
function invariantPushAndUnshift(value, spec, command) {
invariant(Array.isArray(value), function () { return "update(): expected target of " + stringifiable(command) + " to be an array; got " + stringifiable(value) + "."; });
invariantSpecArray(spec[command], command);
}
function invariantSpecArray(spec, command) {
invariant(Array.isArray(spec), function () { return "update(): expected spec of " + stringifiable(command) + " to be an array; got " + stringifiable(spec) + ". " +
"Did you forget to wrap your parameter in an array?"; });
}
function invariantSplices(value, spec) {
invariant(Array.isArray(value), function () { return "Expected $splice target to be an array; got " + stringifiable(value); });
invariantSplice(spec.$splice);
}
function invariantSplice(value) {
invariant(Array.isArray(value), function () { return "update(): expected spec of $splice to be an array of arrays; got " + stringifiable(value) + ". " +
"Did you forget to wrap your parameters in an array?"; });
}
function invariantApply(fn) {
invariant(typeof fn === 'function', function () { return "update(): expected spec of $apply to be a function; got " + stringifiable(fn) + "."; });
}
function invariantSet(spec) {
invariant(Object.keys(spec).length === 1, function () { return "Cannot have more than one key in an object with $set"; });
}
function invariantMerge(target, specValue) {
invariant(specValue && typeof specValue === 'object', function () { return "update(): $merge expects a spec of type 'object'; got " + stringifiable(specValue); });
invariant(target && typeof target === 'object', function () { return "update(): $merge expects a target of type 'object'; got " + stringifiable(target); });
}
function invariantMapOrSet(target, command) {
var typeOfTarget = type(target);
invariant(typeOfTarget === 'Map' || typeOfTarget === 'Set', function () { return "update(): " + stringifiable(command) + " expects a target of type Set or Map; got " + stringifiable(typeOfTarget); });
}