UNPKG

baobab

Version:

JavaScript persistent data tree with cursors.

207 lines (181 loc) 7.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = update; var _type = _interopRequireDefault(require("./type")); var _helpers = require("./helpers"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } function err(operation, expectedTarget, path) { return (0, _helpers.makeError)("Baobab.update: cannot apply the \"".concat(operation, "\" on ") + "a non ".concat(expectedTarget, " (path: /").concat(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 === void 0 ? {} : _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, l, s; 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 (_type["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 (_type["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 (!_type["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 (!_type["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 (!_type["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 (!_type["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 (!_type["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 (!_type["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 (_type["default"].object(p)) delete p[s];else if (_type["default"].array(p)) p.splice(s, 1); } /** * Merge */ else if (operationType === 'merge') { if (!_type["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 (!_type["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 (_type["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 (_type["default"].lazyGetter(p, s)) return { data: dummy.root }; // Returning new data object return { data: dummy.root, node: p[s] }; }