You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
8.0 KiB
273 lines
8.0 KiB
var invariant = require('invariant'); |
|
|
|
var hasOwnProperty = Object.prototype.hasOwnProperty; |
|
var splice = Array.prototype.splice; |
|
|
|
var toString = Object.prototype.toString |
|
var type = function(obj) { |
|
return toString.call(obj).slice(8, -1); |
|
} |
|
|
|
var assign = Object.assign || /* istanbul ignore next */ function assign(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) }; |
|
|
|
/* istanbul ignore next */ |
|
function copy(object) { |
|
if (Array.isArray(object)) { |
|
return assign(object.constructor(object.length), object) |
|
} else if (type(object) === 'Map') { |
|
return new Map(object) |
|
} else if (type(object) === 'Set') { |
|
return new Set(object) |
|
} else if (object && typeof object === 'object') { |
|
var prototype = Object.getPrototypeOf(object); |
|
return assign(Object.create(prototype), object); |
|
} else { |
|
return object; |
|
} |
|
} |
|
|
|
function newContext() { |
|
var commands = assign({}, defaultCommands); |
|
update.extend = function(directive, fn) { |
|
commands[directive] = fn; |
|
}; |
|
update.isEquals = function(a, b) { return a === b; }; |
|
|
|
return update; |
|
|
|
function update(object, spec) { |
|
if (typeof spec === 'function') { |
|
spec = { $apply: spec }; |
|
} |
|
|
|
if (!(Array.isArray(object) && Array.isArray(spec))) { |
|
invariant( |
|
!Array.isArray(spec), |
|
'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, |
|
'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: %s.', |
|
Object.keys(commands).join(', ') |
|
); |
|
|
|
var nextObject = object; |
|
var index, key; |
|
getAllKeys(spec).forEach(function(key) { |
|
if (hasOwnProperty.call(commands, key)) { |
|
var objectWasNextObject = object === nextObject; |
|
nextObject = commands[key](spec[key], nextObject, spec, object); |
|
if (objectWasNextObject && update.isEquals(nextObject, object)) { |
|
nextObject = object; |
|
} |
|
} else { |
|
var nextValueForKey = |
|
type(object) === 'Map' |
|
? update(object.get(key), spec[key]) |
|
: update(object[key], spec[key]); |
|
var nextObjectValue = |
|
type(nextObject) === 'Map' |
|
? nextObject.get(key) |
|
: nextObject[key]; |
|
if (!update.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; |
|
} |
|
|
|
} |
|
|
|
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(value, nextObject, spec, originalObject) { |
|
invariantMapOrSet(nextObject, '$add'); |
|
invariantSpecArray(value, '$add'); |
|
if (type(nextObject) === 'Map') { |
|
value.forEach(function(pair) { |
|
var key = pair[0]; |
|
var value = pair[1]; |
|
if (nextObject === originalObject && nextObject.get(key) !== value) nextObject = copy(originalObject); |
|
nextObject.set(key, value); |
|
}); |
|
} else { |
|
value.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 contextForExport = newContext(); |
|
|
|
module.exports = contextForExport; |
|
module.exports.default = contextForExport; |
|
module.exports.newContext = newContext; |
|
|
|
// invariants |
|
|
|
function invariantPushAndUnshift(value, spec, command) { |
|
invariant( |
|
Array.isArray(value), |
|
'update(): expected target of %s to be an array; got %s.', |
|
command, |
|
value |
|
); |
|
invariantSpecArray(spec[command], command) |
|
} |
|
|
|
function invariantSpecArray(spec, command) { |
|
invariant( |
|
Array.isArray(spec), |
|
'update(): expected spec of %s to be an array; got %s. ' + |
|
'Did you forget to wrap your parameter in an array?', |
|
command, |
|
spec |
|
); |
|
} |
|
|
|
function invariantSplices(value, spec) { |
|
invariant( |
|
Array.isArray(value), |
|
'Expected $splice target to be an array; got %s', |
|
value |
|
); |
|
invariantSplice(spec['$splice']); |
|
} |
|
|
|
function invariantSplice(value) { |
|
invariant( |
|
Array.isArray(value), |
|
'update(): expected spec of $splice to be an array of arrays; got %s. ' + |
|
'Did you forget to wrap your parameters in an array?', |
|
value |
|
); |
|
} |
|
|
|
function invariantApply(fn) { |
|
invariant( |
|
typeof fn === 'function', |
|
'update(): expected spec of $apply to be a function; got %s.', |
|
fn |
|
); |
|
} |
|
|
|
function invariantSet(spec) { |
|
invariant( |
|
Object.keys(spec).length === 1, |
|
'Cannot have more than one key in an object with $set' |
|
); |
|
} |
|
|
|
function invariantMerge(target, specValue) { |
|
invariant( |
|
specValue && typeof specValue === 'object', |
|
'update(): $merge expects a spec of type \'object\'; got %s', |
|
specValue |
|
); |
|
invariant( |
|
target && typeof target === 'object', |
|
'update(): $merge expects a target of type \'object\'; got %s', |
|
target |
|
); |
|
} |
|
|
|
function invariantMapOrSet(target, command) { |
|
var typeOfTarget = type(target); |
|
invariant( |
|
typeOfTarget === 'Map' || typeOfTarget === 'Set', |
|
'update(): %s expects a target of type Set or Map; got %s', |
|
command, |
|
typeOfTarget |
|
); |
|
}
|
|
|