var assert = require('./assert'); var isObject = require('./isObject'); var isFunction = require('./isFunction'); var isArray = require('./isArray'); var isNumber = require('./isNumber'); var assign = require('./assign'); function getShallowCopy(x) { if (isObject(x)) { if (x instanceof Date || x instanceof RegExp) { return x; } return assign({}, x); } if (isArray(x)) { return x.concat(); } return x; } function isCommand(k) { return update.commands.hasOwnProperty(k); } function getCommand(k) { return update.commands[k]; } function update(instance, patch) { if (process.env.NODE_ENV !== 'production') { assert(isObject(patch), function () { return 'Invalid argument patch ' + assert.stringify(patch) + ' supplied to function update(instance, patch): expected an object containing commands'; }); } var value = instance; var isChanged = false; var newValue; for (var k in patch) { if (patch.hasOwnProperty(k)) { if (isCommand(k)) { newValue = getCommand(k)(patch[k], value); if (newValue !== instance) { isChanged = true; value = newValue; } else { value = instance; } } else { if (value === instance) { value = getShallowCopy(instance); } newValue = update(value[k], patch[k]); isChanged = isChanged || ( newValue !== value[k] ); value[k] = newValue; } } } return isChanged ? value : instance; } // built-in commands function $apply(f, value) { if (process.env.NODE_ENV !== 'production') { assert(isFunction(f), 'Invalid argument f supplied to immutability helper { $apply: f } (expected a function)'); } return f(value); } function $push(elements, arr) { if (process.env.NODE_ENV !== 'production') { assert(isArray(elements), 'Invalid argument elements supplied to immutability helper { $push: elements } (expected an array)'); assert(isArray(arr), 'Invalid value supplied to immutability helper $push (expected an array)'); } if (elements.length > 0) { return arr.concat(elements); } return arr; } function $remove(keys, obj) { if (process.env.NODE_ENV !== 'production') { assert(isArray(keys), 'Invalid argument keys supplied to immutability helper { $remove: keys } (expected an array)'); assert(isObject(obj), 'Invalid value supplied to immutability helper $remove (expected an object)'); } if (keys.length > 0) { obj = getShallowCopy(obj); for (var i = 0, len = keys.length; i < len; i++ ) { delete obj[keys[i]]; } } return obj; } function $set(value) { return value; } function $splice(splices, arr) { if (process.env.NODE_ENV !== 'production') { assert(isArray(splices) && splices.every(isArray), 'Invalid argument splices supplied to immutability helper { $splice: splices } (expected an array of arrays)'); assert(isArray(arr), 'Invalid value supplied to immutability helper $splice (expected an array)'); } if (splices.length > 0) { arr = getShallowCopy(arr); return splices.reduce(function (acc, splice) { acc.splice.apply(acc, splice); return acc; }, arr); } return arr; } function $swap(config, arr) { if (process.env.NODE_ENV !== 'production') { assert(isObject(config), 'Invalid argument config supplied to immutability helper { $swap: config } (expected an object)'); assert(isNumber(config.from), 'Invalid argument config.from supplied to immutability helper { $swap: config } (expected a number)'); assert(isNumber(config.to), 'Invalid argument config.to supplied to immutability helper { $swap: config } (expected a number)'); assert(isArray(arr), 'Invalid value supplied to immutability helper $swap (expected an array)'); } if (config.from !== config.to) { arr = getShallowCopy(arr); var element = arr[config.to]; arr[config.to] = arr[config.from]; arr[config.from] = element; } return arr; } function $unshift(elements, arr) { if (process.env.NODE_ENV !== 'production') { assert(isArray(elements), 'Invalid argument elements supplied to immutability helper {$unshift: elements} (expected an array)'); assert(isArray(arr), 'Invalid value supplied to immutability helper $unshift (expected an array)'); } if (elements.length > 0) { return elements.concat(arr); } return arr; } function $merge(whatToMerge, value) { var isChanged = false; var result = getShallowCopy(value); for (var k in whatToMerge) { if (whatToMerge.hasOwnProperty(k)) { result[k] = whatToMerge[k]; isChanged = isChanged || ( result[k] !== value[k] ); } } return isChanged ? result : value; } update.commands = { $apply: $apply, $push: $push, $remove: $remove, $set: $set, $splice: $splice, $swap: $swap, $unshift: $unshift, $merge: $merge }; module.exports = update;