baobab
Version:
JavaScript persistent data tree with cursors.
230 lines (182 loc) • 8.55 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = update;
var _type = require('./type');
var _type2 = _interopRequireDefault(_type);
var _helpers = require('./helpers');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
* Baobab Update
* ==============
*
* The tree's update scheme.
*/
function err(operation, expectedTarget, path) {
return (0, _helpers.makeError)('Baobab.update: cannot apply the "' + operation + '" on ' + ('a non ' + expectedTarget + ' (path: /' + path.join('/') + ').'), { path: path });
}
/**
* Function aiming at applying a single update operation on the given tree's
* data.
*
* @param {mixed} data - The tree's data.
* @param {path} path - Path of the update.
* @param {object} operation - The operation to apply.
* @param {object} [opts] - Optional options.
* @return {mixed} - Both the new tree's data and the updated node.
*/
function update(data, path, operation) {
var opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var operationType = operation.type,
value = operation.value,
_operation$options = operation.options,
operationOptions = _operation$options === undefined ? {} : _operation$options;
// Dummy root, so we can shift and alter the root
var dummy = { root: data },
dummyPath = ['root'].concat(_toConsumableArray(path)),
currentPath = [];
// Walking the path
var p = dummy,
i = void 0,
l = void 0,
s = void 0;
for (i = 0, l = dummyPath.length; i < l; i++) {
// Current item's reference is therefore p[s]
// The reason why we don't create a variable here for convenience
// is because we actually need to mutate the reference.
s = dummyPath[i];
// Updating the path
if (i > 0) currentPath.push(s);
// If we reached the end of the path, we apply the operation
if (i === l - 1) {
/**
* Set
*/
if (operationType === 'set') {
// Purity check
if (opts.pure && p[s] === value) return { node: p[s] };
if (_type2.default.lazyGetter(p, s)) {
Object.defineProperty(p, s, {
value: value,
enumerable: true,
configurable: true
});
} else if (opts.persistent && !operationOptions.mutableLeaf) {
p[s] = (0, _helpers.shallowClone)(value);
} else {
p[s] = value;
}
}
/**
* Monkey
*/
else if (operationType === 'monkey') {
Object.defineProperty(p, s, {
get: value,
enumerable: true,
configurable: true
});
}
/**
* Apply
*/
else if (operationType === 'apply') {
var result = value(p[s]);
// Purity check
if (opts.pure && p[s] === result) return { node: p[s] };
if (_type2.default.lazyGetter(p, s)) {
Object.defineProperty(p, s, {
value: result,
enumerable: true,
configurable: true
});
} else if (opts.persistent) {
p[s] = (0, _helpers.shallowClone)(result);
} else {
p[s] = result;
}
}
/**
* Push
*/
else if (operationType === 'push') {
if (!_type2.default.array(p[s])) throw err('push', 'array', currentPath);
if (opts.persistent) p[s] = p[s].concat([value]);else p[s].push(value);
}
/**
* Unshift
*/
else if (operationType === 'unshift') {
if (!_type2.default.array(p[s])) throw err('unshift', 'array', currentPath);
if (opts.persistent) p[s] = [value].concat(p[s]);else p[s].unshift(value);
}
/**
* Concat
*/
else if (operationType === 'concat') {
if (!_type2.default.array(p[s])) throw err('concat', 'array', currentPath);
if (opts.persistent) p[s] = p[s].concat(value);else p[s].push.apply(p[s], value);
}
/**
* Splice
*/
else if (operationType === 'splice') {
if (!_type2.default.array(p[s])) throw err('splice', 'array', currentPath);
if (opts.persistent) p[s] = _helpers.splice.apply(null, [p[s]].concat(value));else p[s].splice.apply(p[s], value);
}
/**
* Pop
*/
else if (operationType === 'pop') {
if (!_type2.default.array(p[s])) throw err('pop', 'array', currentPath);
if (opts.persistent) p[s] = (0, _helpers.splice)(p[s], -1, 1);else p[s].pop();
}
/**
* Shift
*/
else if (operationType === 'shift') {
if (!_type2.default.array(p[s])) throw err('shift', 'array', currentPath);
if (opts.persistent) p[s] = (0, _helpers.splice)(p[s], 0, 1);else p[s].shift();
}
/**
* Unset
*/
else if (operationType === 'unset') {
if (_type2.default.object(p)) delete p[s];else if (_type2.default.array(p)) p.splice(s, 1);
}
/**
* Merge
*/
else if (operationType === 'merge') {
if (!_type2.default.object(p[s])) throw err('merge', 'object', currentPath);
if (opts.persistent) p[s] = (0, _helpers.shallowMerge)({}, p[s], value);else p[s] = (0, _helpers.shallowMerge)(p[s], value);
}
/**
* Deep merge
*/
else if (operationType === 'deepMerge') {
if (!_type2.default.object(p[s])) throw err('deepMerge', 'object', currentPath);
if (opts.persistent) p[s] = (0, _helpers.deepMerge)({}, p[s], value);else p[s] = (0, _helpers.deepMerge)(p[s], value);
}
// Deep freezing the resulting value
if (opts.immutable && !operationOptions.mutableLeaf) (0, _helpers.deepFreeze)(p);
break;
}
// If we reached a leaf, we override by setting an empty object
else if (_type2.default.primitive(p[s])) {
p[s] = {};
}
// Else, we shift the reference and continue the path
else if (opts.persistent) {
p[s] = (0, _helpers.shallowClone)(p[s]);
}
// Should we freeze the current step before continuing?
if (opts.immutable && l > 0) (0, _helpers.freeze)(p);
p = p[s];
}
// If we are updating a dynamic node, we need not return the affected node
if (_type2.default.lazyGetter(p, s)) return { data: dummy.root };
// Returning new data object
return { data: dummy.root, node: p[s] };
}