diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1066881 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "workbench.colorCustomizations": { + "activityBar.background": "#211B7F", + "titleBar.activeBackground": "#2F26B2", + "titleBar.activeForeground": "#FAF9FE", + "statusBar.background": "#2F26B2", + "statusBar.foreground": "#FAF9FE" + }, + "javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false, + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false, + "javascript.format.insertSpaceBeforeFunctionParenthesis": false, + "javascript.format.semicolons": "insert", + "typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false, + "typescript.format.insertSpaceBeforeFunctionParenthesis": false, + "typescript.format.semicolons": "insert", +} \ No newline at end of file diff --git a/build/baobab.js b/build/baobab.js index ec2541e..aefe43a 100644 --- a/build/baobab.js +++ b/build/baobab.js @@ -2,7 +2,7 @@ * Baobab * * Homepage: https://github.com/Yomguithereal/baobab - * Version: 2.6.1 + * Version: 2.6.2 * Author: Yomguithereal (Guillaume Plique) * License: MIT */ @@ -578,7 +578,7 @@ module.exports = Emitter; "use strict"; exports.__esModule = true; -exports.helpers = exports["default"] = exports.VERSION = exports.dynamic = exports.monkey = void 0; +exports.monkey = exports.helpers = exports.dynamic = exports["default"] = exports.VERSION = void 0; var _emmett = _interopRequireDefault(require("emmett")); @@ -603,15 +603,17 @@ var helpers = _interopRequireWildcard(require("./helpers")); exports.helpers = helpers; -function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } -function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } var arrayFrom = helpers.arrayFrom, coercePath = helpers.coercePath, @@ -663,9 +665,7 @@ var DEFAULTS = { * @param {string} [opts.validationBehaviour] - "rollback" or "notify". */ -var Baobab = -/*#__PURE__*/ -function (_Emitter) { +var Baobab = /*#__PURE__*/function (_Emitter) { _inheritsLoose(Baobab, _Emitter); function Baobab(initialData, opts) { @@ -1115,7 +1115,8 @@ var VERSION = Baobab.VERSION; */ exports.VERSION = VERSION; -var _default = Baobab; +var _default = Baobab; // export * from './sbaobab'; + exports["default"] = _default; },{"./cursor":3,"./helpers":4,"./monkey":5,"./type":6,"./update":7,"./watcher":8,"emmett":1}],3:[function(require,module,exports){ @@ -1136,7 +1137,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "d function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } -function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } /** * Traversal helper function for dynamic cursors. Will throw a legible error @@ -1160,9 +1163,7 @@ function checkPossibilityOfDynamicTraversal(method, solvedPath) { */ -var Cursor = -/*#__PURE__*/ -function (_Emitter) { +var Cursor = /*#__PURE__*/function (_Emitter) { _inheritsLoose(Cursor, _Emitter); function Cursor(tree, path, hash) { @@ -1920,20 +1921,23 @@ makeSetter('merge', _type["default"].object); makeSetter('deepMerge', _type["default"].object); },{"./helpers":4,"./monkey":5,"./type":6,"emmett":1}],4:[function(require,module,exports){ -(function (global){ +(function (global){(function (){ "use strict"; exports.__esModule = true; +exports.Archive = void 0; exports.arrayFrom = arrayFrom; exports.before = before; exports.coercePath = coercePath; +exports.freeze = exports.deepMerge = exports.deepFreeze = exports.deepClone = void 0; exports.getIn = getIn; -exports.makeError = makeError; exports.hashPath = hashPath; +exports.makeError = makeError; +exports.shallowMerge = exports.shallowClone = void 0; exports.solveRelativePath = solveRelativePath; exports.solveUpdate = solveUpdate; exports.splice = splice; -exports.uniqid = exports.deepMerge = exports.shallowMerge = exports.deepFreeze = exports.freeze = exports.deepClone = exports.shallowClone = exports.Archive = void 0; +exports.uniqid = void 0; var _monkey = require("./monkey"); @@ -1996,9 +2000,7 @@ function slice(array) { */ -var Archive = -/*#__PURE__*/ -function () { +var Archive = /*#__PURE__*/function () { function Archive(size) { this.size = size; this.records = []; @@ -2513,12 +2515,12 @@ var uniqid = function () { exports.uniqid = uniqid; -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./monkey":5,"./type":6}],5:[function(require,module,exports){ "use strict"; exports.__esModule = true; -exports.Monkey = exports.MonkeyDefinition = void 0; +exports.MonkeyDefinition = exports.Monkey = void 0; var _type = _interopRequireDefault(require("./type")); @@ -2593,9 +2595,7 @@ var MonkeyDefinition = function MonkeyDefinition(definition) { exports.MonkeyDefinition = MonkeyDefinition; -var Monkey = -/*#__PURE__*/ -function () { +var Monkey = /*#__PURE__*/function () { function Monkey(tree, pathInTree, definition) { var _this2 = this; @@ -3120,107 +3120,107 @@ function update(data, path, operation, opts) { * 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, { - get: value, + value: result, enumerable: true, configurable: true }); + } else if (opts.persistent) { + p[s] = (0, _helpers.shallowClone)(result); + } else { + p[s] = result; } - /** - * 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 + } + /** + * 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? + 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); @@ -3254,7 +3254,9 @@ var _helpers = require("./helpers"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } -function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } /** * Watcher class. @@ -3263,9 +3265,7 @@ function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.crea * @param {Baobab} tree - The watched tree. * @param {object} mapping - A mapping of the paths to watch in the tree. */ -var Watcher = -/*#__PURE__*/ -function (_Emitter) { +var Watcher = /*#__PURE__*/function (_Emitter) { _inheritsLoose(Watcher, _Emitter); function Watcher(tree, mapping) { diff --git a/build/baobab.min.js b/build/baobab.min.js index b2bf9fe..b81de86 100644 --- a/build/baobab.min.js +++ b/build/baobab.min.js @@ -2,8 +2,8 @@ * Baobab * * Homepage: https://github.com/Yomguithereal/baobab - * Version: 2.6.1 + * Version: 2.6.2 * Author: Yomguithereal (Guillaume Plique) * License: MIT */ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Baobab=t()}}((function(){return function t(e,r,n){function i(o,s){if(!r[o]){if(!e[o]){var h="function"==typeof require&&require;if(!s&&h)return h(o,!0);if(a)return a(o,!0);var l=new Error("Cannot find module '"+o+"'");throw l.code="MODULE_NOT_FOUND",l}var u=r[o]={exports:{}};e[o][0].call(u.exports,(function(t){return i(e[o][1][t]||t)}),u,u.exports,t,e,r,n)}return r[o].exports}for(var a="function"==typeof require&&require,o=0;o1?t[e]=a(t[e],{once:!0}):t.push({once:!0}),this.on.apply(this,t)},h.prototype.off=function(t,e){var r,n,i,a;if(1===arguments.length&&"function"==typeof t){e=arguments[0];var h=Object.keys(this._handlers).concat(Object.getOwnPropertySymbols(this._handlers));for(r=0;r1&&(r.data=e),a.fn.call("scope"in a?a.scope:this,r),a.once&&d.push(a);for(l=d.length-1;l>=0;l--){var p=(n=d[l].type?this._handlers[d[l].type]:d[l].pattern?this._handlersComplex:this._handlersAll).indexOf(d[l]);-1!==p&&n.splice(p,1)}}return this},h.prototype.kill=function(){this.unbindAll(),this._handlers=null,this._handlersAll=null,this._handlersComplex=null,this._enabled=!1,this.unbindAll=this.on=this.once=this.off=this.emit=this.listeners=Function.prototype},h.prototype.disable=function(){return this._enabled=!1,this},h.prototype.enable=function(){return this._enabled=!0,this},h.version="3.2.0",e.exports=h},{}],2:[function(t,e,r){"use strict";r.__esModule=!0,r.helpers=r.default=r.VERSION=r.dynamic=r.monkey=void 0;var n=c(t("emmett")),i=c(t("./cursor"));r.Cursor=i.default;var a=t("./monkey");r.MonkeyDefinition=a.MonkeyDefinition,r.Monkey=a.Monkey;var o=c(t("./watcher")),s=c(t("./type"));r.type=s.default;var h=c(t("./update")),l=function(t){if(t&&t.__esModule)return t;if(null===t||"object"!=typeof t&&"function"!=typeof t)return{default:t};var e=u();if(e&&e.has(t))return e.get(t);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in t)if(Object.prototype.hasOwnProperty.call(t,i)){var a=n?Object.getOwnPropertyDescriptor(t,i):null;a&&(a.get||a.set)?Object.defineProperty(r,i,a):r[i]=t[i]}r.default=t,e&&e.set(t,r);return r}(t("./helpers"));function u(){if("function"!=typeof WeakMap)return null;var t=new WeakMap;return u=function(){return t},t}function c(t){return t&&t.__esModule?t:{default:t}}function f(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}r.helpers=l;var d=l.arrayFrom,p=l.coercePath,y=l.deepFreeze,v=l.getIn,g=l.makeError,m=l.deepClone,b=l.deepMerge,_=l.shallowClone,k=l.shallowMerge,w=l.hashPath,P={autoCommit:!0,asynchronous:!0,immutable:!0,lazyMonkeys:!0,monkeyBusiness:!0,persistent:!0,pure:!0,validate:null,validationBehavior:"rollback"},j=function(t){var e,r;function n(e,r){var n;if(n=t.call(this)||this,arguments.length<1&&(e={}),!s.default.object(e)&&!s.default.array(e))throw g("Baobab: invalid data.",{data:e});n.options=k({},P,r),n.options.persistent||(n.options.immutable=!1,n.options.pure=!1),n._identity="[object Baobab]",n._cursors={},n._future=null,n._transaction=[],n._affectedPathsIndex={},n._monkeys={},n._previousData=null,n._data=e,n.root=new i.default(f(n),[],"λ"),delete n.root.release,n.options.immutable&&y(n._data);var a=function(t){n[t]=function(){var e=this.root[t].apply(this.root,arguments);return e instanceof i.default?this:e}};["apply","clone","concat","deepClone","deepMerge","exists","get","push","merge","pop","project","serialize","set","shift","splice","unset","unshift"].forEach(a),n.options.monkeyBusiness&&n._refreshMonkeys();var o=n.validate();if(o)throw Error("Baobab: invalid data.",{error:o});return n}r=t,(e=n).prototype=Object.create(r.prototype),e.prototype.constructor=e,e.__proto__=r;var l=n.prototype;return l._refreshMonkeys=function(t,e,r){var n=this,i=function t(e,r){if(void 0===r&&(r=[]),e instanceof a.Monkey)return e.release(),void(0,h.default)(n._monkeys,r,{type:"unset"},{immutable:!1,persistent:!1,pure:!1});if(s.default.object(e))for(var i in e)t(e[i],r.concat(i))},o=function t(e,r){if(void 0===r&&(r=[]),e instanceof a.MonkeyDefinition||e instanceof a.Monkey){var i=new a.Monkey(n,r,e instanceof a.Monkey?e.definition:e);(0,h.default)(n._monkeys,r,{type:"set",value:i},{immutable:!1,persistent:!1,pure:!1})}else if(s.default.object(e))for(var o in e)t(e[o],r.concat(o))};if(arguments.length){var l=v(this._monkeys,e).data;l&&i(l,e),"unset"!==r&&o(t,e)}else o(this._data);return this},l.validate=function(t){var e=this.options,r=e.validate,n=e.validationBehavior;if("function"!=typeof r)return null;var i=r.call(this,this._previousData,this._data,t||[[]]);return i instanceof Error?("rollback"===n&&(this._data=this._previousData,this._affectedPathsIndex={},this._transaction=[],this._previousData=this._data),this.emit("invalid",{error:i}),i):null},l.select=function(t){if(t=t||[],arguments.length>1&&(t=d(arguments)),!s.default.path(t))throw g("Baobab.select: invalid path.",{path:t});t=[].concat(t);var e=w(t),r=this._cursors[e];return r||(r=new i.default(this,t,e),this._cursors[e]=r),this.emit("select",{path:t,cursor:r}),r},l.update=function(t,e){var r=this;if(t=p(t),!s.default.operationType(e.type))throw g('Baobab.update: unknown operation type "'+e.type+'".',{operation:e});var n=v(this._data,t),i=n.solvedPath,a=n.exists;if(!i)throw g("Baobab.update: could not solve the given path.",{path:i});var o=s.default.monkeyPath(this._monkeys,i);if(o&&i.length>o.length)throw g("Baobab.update: attempting to update a read-only path.",{path:i});if("unset"!==e.type||a){var l=e;if(/merge/i.test(e.type)){var u=v(this._monkeys,i).data;if(s.default.object(u)){l=_(l);var c=v(this._data,i).data;/deep/i.test(l.type)?l.value=b({},b({},c,m(u)),l.value):l.value=k({},b({},c,m(u)),l.value)}}this._transaction.length||(this._previousData=this._data);var f=(0,h.default)(this._data,i,l,this.options),d=f.data,y=f.node;if(!("data"in f))return y;var P=i.concat("push"===e.type?y.length-1:[]),j=w(P);return this._data=d,this._affectedPathsIndex[j]=!0,this._transaction.push(k({},e,{path:P})),this.options.monkeyBusiness&&this._refreshMonkeys(y,i,e.type),this.emit("write",{path:P}),this.options.autoCommit?this.options.asynchronous?(this._future||(this._future=setTimeout((function(){return r.commit()}),0)),y):(this.commit(),y):y}},l.commit=function(){if(!this._transaction.length)return this;this._future&&(this._future=clearTimeout(this._future));var t=Object.keys(this._affectedPathsIndex).map((function(t){return"λ"!==t?t.split("λ").slice(1):[]}));if(this.validate(t))return this;var e=this._transaction,r=this._previousData;return this._affectedPathsIndex={},this._transaction=[],this._previousData=this._data,this.emit("update",{paths:t,currentData:this._data,transaction:e,previousData:r}),this},l.getMonkey=function(t){t=p(t);var e=v(this._monkeys,[].concat(t)).data;return e instanceof a.Monkey?e:null},l.watch=function(t){return new o.default(this,t)},l.release=function(){var t;for(t in this.emit("release"),delete this.root,delete this._data,delete this._previousData,delete this._transaction,delete this._affectedPathsIndex,delete this._monkeys,this._cursors)this._cursors[t].release();delete this._cursors,this.kill()},l.toJSON=function(){return this.serialize()},l.toString=function(){return this._identity},n}(n.default);j.monkey=function(){for(var t=arguments.length,e=new Array(t),r=0;r1&&(t=(0,o.arrayFrom)(arguments)),this.tree.select(this.path.concat(t))},s.up=function(){return this.isRoot()?null:this.tree.select(this.path.slice(0,-1))},s.down=function(){if(l("down",this.solvedPath),!(this._get().data instanceof Array))throw Error("Baobab.Cursor.down: cannot go down on a non-list type.");return this.tree.select(this.solvedPath.concat(0))},s.left=function(){l("left",this.solvedPath);var t=+this.solvedPath[this.solvedPath.length-1];if(isNaN(t))throw Error("Baobab.Cursor.left: cannot go left on a non-list type.");return t?this.tree.select(this.solvedPath.slice(0,-1).concat(t-1)):null},s.right=function(){l("right",this.solvedPath);var t=+this.solvedPath[this.solvedPath.length-1];if(isNaN(t))throw Error("Baobab.Cursor.right: cannot go right on a non-list type.");return t+1===this.up()._get().data.length?null:this.tree.select(this.solvedPath.slice(0,-1).concat(t+1))},s.leftmost=function(){l("leftmost",this.solvedPath);var t=+this.solvedPath[this.solvedPath.length-1];if(isNaN(t))throw Error("Baobab.Cursor.leftmost: cannot go left on a non-list type.");return this.tree.select(this.solvedPath.slice(0,-1).concat(0))},s.rightmost=function(){l("rightmost",this.solvedPath);var t=+this.solvedPath[this.solvedPath.length-1];if(isNaN(t))throw Error("Baobab.Cursor.rightmost: cannot go right on a non-list type.");var e=this.up()._get().data;return this.tree.select(this.solvedPath.slice(0,-1).concat(e.length-1))},s.map=function(t,e){l("map",this.solvedPath);var r=this._get().data,n=arguments.length;if(!a.default.array(r))throw Error("baobab.Cursor.map: cannot map a non-list type.");return r.map((function(i,a){return t.call(n>1?e:this,this.select(a),a,r)}),this)},s._get=function(t){if(void 0===t&&(t=[]),!a.default.path(t))throw(0,o.makeError)("Baobab.Cursor.getters: invalid path.",{path:t});return this.solvedPath?(0,o.getIn)(this.tree._data,this.solvedPath.concat(t)):{data:void 0,solvedPath:null,exists:!1}},s.exists=function(t){return t=(0,o.coercePath)(t),arguments.length>1&&(t=(0,o.arrayFrom)(arguments)),this._get(t).exists},s.get=function(t){t=(0,o.coercePath)(t),arguments.length>1&&(t=(0,o.arrayFrom)(arguments));var e=this._get(t),r=e.data,n=e.solvedPath;return this.tree.emit("get",{data:r,solvedPath:n,path:this.path.concat(t)}),r},s.clone=function(){var t=this.get.apply(this,arguments);return(0,o.shallowClone)(t)},s.deepClone=function(){var t=this.get.apply(this,arguments);return(0,o.deepClone)(t)},s.serialize=function(t){if(t=(0,o.coercePath)(t),arguments.length>1&&(t=(0,o.arrayFrom)(arguments)),!a.default.path(t))throw(0,o.makeError)("Baobab.Cursor.getters: invalid path.",{path:t});if(this.solvedPath){var e=this.solvedPath.concat(t),r=(0,o.deepClone)((0,o.getIn)(this.tree._data,e).data),n=(0,o.getIn)(this.tree._monkeys,e).data,s=function t(e,r){if(a.default.object(r)&&a.default.object(e))for(var n in r)r[n]instanceof i.Monkey?delete e[n]:t(e[n],r[n])};return s(r,n),r}},s.project=function(t){if(a.default.object(t)){var e={};for(var r in t)e[r]=this.get(t[r]);return e}if(a.default.array(t)){for(var n=[],i=0,s=t.length;i2)throw(0,o.makeError)("Baobab.Cursor."+t+": too many arguments.");if(1!==arguments.length||c[t]||(n=r,r=[]),r=(0,o.coercePath)(r),!a.default.path(r))throw(0,o.makeError)("Baobab.Cursor."+t+": invalid path.",{path:r});if(e&&!e(n))throw(0,o.makeError)("Baobab.Cursor."+t+": invalid value.",{path:r,value:n});if(!this.solvedPath)throw(0,o.makeError)("Baobab.Cursor."+t+": the dynamic path of the cursor cannot be solved.",{path:this.path});var i=this.solvedPath.concat(r);return this.tree.update(i,{type:t,value:n})}}f("set"),f("unset"),f("apply",a.default.function),f("push"),f("concat",a.default.array),f("unshift"),f("pop"),f("shift"),f("splice",a.default.splicer),f("merge",a.default.object),f("deepMerge",a.default.object)},{"./helpers":4,"./monkey":5,"./type":6,emmett:1}],4:[function(t,e,r){(function(e){"use strict";r.__esModule=!0,r.arrayFrom=function(t){return h(t)},r.before=function(t,e){return function(){t.apply(null,arguments),e.apply(null,arguments)}},r.coercePath=function(t){return t||0===t||""===t?t:[]},r.getIn=function(t,e){if(!e)return g;var r,n,i,o=[],h=!0,l=t;for(n=0,i=e.length;n3?n-3:0),o=3;o=0?t.slice(0,e).concat(i).concat(t.slice(e+r)):t.slice(0,t.length+e).concat(i).concat(t.slice(t.length+e+r))},r.uniqid=r.deepMerge=r.shallowMerge=r.deepFreeze=r.freeze=r.deepClone=r.shallowClone=r.Archive=void 0;var n,i=t("./monkey"),a=(n=t("./type"))&&n.__esModule?n:{default:n};var o={}.hasOwnProperty;function s(t,e){var r,n;for(r=0,n=t.length;rthis.size&&(this.records.length=this.size),this},e.clear=function(){return this.records=[],this},e.back=function(t){var e=this.records[t-1];return e&&(this.records=this.records.slice(t)),e},t}();function u(t,r){if(!r||"object"!=typeof r||r instanceof Error||r instanceof i.MonkeyDefinition||r instanceof i.Monkey||"ArrayBuffer"in e&&r instanceof ArrayBuffer)return r;if(a.default.array(r)){if(t){for(var n=new Array(r.length),o=0,s=r.length;o1?e-1:0),n=1;n1&&isNaN(+t[1]))&&a(t[0],["number","function","object"]))};var o=["string","number","function","object"];i.path=function(t){return!(!t&&0!==t&&""!==t)&&[].concat(t).every((function(t){return a(t,o)}))},i.dynamicPath=function(t){return t.some((function(t){return i.function(t)||i.object(t)}))},i.monkeyPath=function(t,e){var r,i,a=[],o=t;for(r=0,i=e.length;r0&&v.push(l),s===h-1){if("set"===u){if(n.pure&&g[l]===c)return{node:g[l]};i.default.lazyGetter(g,l)?Object.defineProperty(g,l,{value:c,enumerable:!0,configurable:!0}):n.persistent&&!d.mutableLeaf?g[l]=(0,a.shallowClone)(c):g[l]=c}else if("monkey"===u)Object.defineProperty(g,l,{get:c,enumerable:!0,configurable:!0});else if("apply"===u){var m=c(g[l]);if(n.pure&&g[l]===m)return{node:g[l]};i.default.lazyGetter(g,l)?Object.defineProperty(g,l,{value:m,enumerable:!0,configurable:!0}):n.persistent?g[l]=(0,a.shallowClone)(m):g[l]=m}else if("push"===u){if(!i.default.array(g[l]))throw o("push","array",v);n.persistent?g[l]=g[l].concat([c]):g[l].push(c)}else if("unshift"===u){if(!i.default.array(g[l]))throw o("unshift","array",v);n.persistent?g[l]=[c].concat(g[l]):g[l].unshift(c)}else if("concat"===u){if(!i.default.array(g[l]))throw o("concat","array",v);n.persistent?g[l]=g[l].concat(c):g[l].push.apply(g[l],c)}else if("splice"===u){if(!i.default.array(g[l]))throw o("splice","array",v);n.persistent?g[l]=a.splice.apply(null,[g[l]].concat(c)):g[l].splice.apply(g[l],c)}else if("pop"===u){if(!i.default.array(g[l]))throw o("pop","array",v);n.persistent?g[l]=(0,a.splice)(g[l],-1,1):g[l].pop()}else if("shift"===u){if(!i.default.array(g[l]))throw o("shift","array",v);n.persistent?g[l]=(0,a.splice)(g[l],0,1):g[l].shift()}else if("unset"===u)i.default.object(g)?delete g[l]:i.default.array(g)&&g.splice(l,1);else if("merge"===u){if(!i.default.object(g[l]))throw o("merge","object",v);n.persistent?g[l]=(0,a.shallowMerge)({},g[l],c):g[l]=(0,a.shallowMerge)(g[l],c)}else if("deepMerge"===u){if(!i.default.object(g[l]))throw o("deepMerge","object",v);n.persistent?g[l]=(0,a.deepMerge)({},g[l],c):g[l]=(0,a.deepMerge)(g[l],c)}n.immutable&&!d.mutableLeaf&&(0,a.deepFreeze)(g);break}i.default.primitive(g[l])?g[l]={}:n.persistent&&(g[l]=(0,a.shallowClone)(g[l])),n.immutable&&h>0&&(0,a.freeze)(g),g=g[l]}return i.default.lazyGetter(g,l)?{data:p.root}:{data:p.root,node:g[l]}};var n,i=(n=t("./type"))&&n.__esModule?n:{default:n},a=t("./helpers");function o(t,e,r){return(0,a.makeError)('Baobab.update: cannot apply the "'+t+'" on a non '+e+" (path: /"+r.join("/")+").",{path:r})}},{"./helpers":4,"./type":6}],8:[function(t,e,r){"use strict";r.__esModule=!0,r.default=void 0;var n=s(t("emmett")),i=s(t("./cursor")),a=s(t("./type")),o=t("./helpers");function s(t){return t&&t.__esModule?t:{default:t}}var h=function(t){var e,r;function n(e,r){var n;return(n=t.call(this)||this).tree=e,n.mapping=null,n.state={killed:!1},n.refresh(r),n.handler=function(t){if(!n.state.killed){var e=n.getWatchedPaths();return(0,o.solveUpdate)(t.data.paths,e)?n.emit("update"):void 0}},n.tree.on("update",n.handler),n}r=t,(e=n).prototype=Object.create(r.prototype),e.prototype.constructor=e,e.__proto__=r;var s=n.prototype;return s.getWatchedPaths=function(){var t=this;return Object.keys(this.mapping).map((function(e){var r=t.mapping[e];return r instanceof i.default?r.solvedPath:t.mapping[e]})).reduce((function(e,r){if(r=[].concat(r),a.default.dynamicPath(r)&&(r=(0,o.getIn)(t.tree._data,r).solvedPath),!r)return e;var n=a.default.monkeyPath(t.tree._monkeys,r);return n?e.concat((0,o.getIn)(t.tree._monkeys,n).data.relatedPaths()):e.concat([r])}),[])},s.getCursors=function(){var t=this,e={};return Object.keys(this.mapping).forEach((function(r){var n=t.mapping[r];n instanceof i.default?e[r]=n:e[r]=t.tree.select(n)})),e},s.refresh=function(t){if(!a.default.watcherMapping(t))throw(0,o.makeError)("Baobab.watch: invalid mapping.",{mapping:t});this.mapping=t;var e={};for(var r in t)e[r]=t[r]instanceof i.default?t[r].path:t[r];this.get=this.tree.project.bind(this.tree,e)},s.release=function(){this.tree.off("update",this.handler),this.state.killed=!0,this.kill()},n}(n.default);r.default=h},{"./cursor":3,"./helpers":4,"./type":6,emmett:1}]},{},[2])(2)})); +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Baobab=t()}}((function(){return function t(e,r,n){function i(o,s){if(!r[o]){if(!e[o]){var u="function"==typeof require&&require;if(!s&&u)return u(o,!0);if(a)return a(o,!0);var h=new Error("Cannot find module '"+o+"'");throw h.code="MODULE_NOT_FOUND",h}var l=r[o]={exports:{}};e[o][0].call(l.exports,(function(t){return i(e[o][1][t]||t)}),l,l.exports,t,e,r,n)}return r[o].exports}for(var a="function"==typeof require&&require,o=0;o1?t[e]=a(t[e],{once:!0}):t.push({once:!0}),this.on.apply(this,t)},u.prototype.off=function(t,e){var r,n,i,a;if(1===arguments.length&&"function"==typeof t){e=arguments[0];var u=Object.keys(this._handlers).concat(Object.getOwnPropertySymbols(this._handlers));for(r=0;r1&&(r.data=e),a.fn.call("scope"in a?a.scope:this,r),a.once&&d.push(a);for(h=d.length-1;h>=0;h--){var p=(n=d[h].type?this._handlers[d[h].type]:d[h].pattern?this._handlersComplex:this._handlersAll).indexOf(d[h]);-1!==p&&n.splice(p,1)}}return this},u.prototype.kill=function(){this.unbindAll(),this._handlers=null,this._handlersAll=null,this._handlersComplex=null,this._enabled=!1,this.unbindAll=this.on=this.once=this.off=this.emit=this.listeners=Function.prototype},u.prototype.disable=function(){return this._enabled=!1,this},u.prototype.enable=function(){return this._enabled=!0,this},u.version="3.2.0",e.exports=u},{}],2:[function(t,e,r){"use strict";r.__esModule=!0,r.monkey=r.helpers=r.dynamic=r.default=r.VERSION=void 0;var n=c(t("emmett")),i=c(t("./cursor"));r.Cursor=i.default;var a=t("./monkey");r.MonkeyDefinition=a.MonkeyDefinition,r.Monkey=a.Monkey;var o=c(t("./watcher")),s=c(t("./type"));r.type=s.default;var u=c(t("./update")),h=function(t,e){if(!e&&t&&t.__esModule)return t;if(null===t||"object"!=typeof t&&"function"!=typeof t)return{default:t};var r=l(e);if(r&&r.has(t))return r.get(t);var n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in t)if("default"!==a&&Object.prototype.hasOwnProperty.call(t,a)){var o=i?Object.getOwnPropertyDescriptor(t,a):null;o&&(o.get||o.set)?Object.defineProperty(n,a,o):n[a]=t[a]}n.default=t,r&&r.set(t,n);return n}(t("./helpers"));function l(t){if("function"!=typeof WeakMap)return null;var e=new WeakMap,r=new WeakMap;return(l=function(t){return t?r:e})(t)}function c(t){return t&&t.__esModule?t:{default:t}}function f(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function d(t,e){return(d=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}r.helpers=h;var p=h.arrayFrom,y=h.coercePath,v=h.deepFreeze,g=h.getIn,m=h.makeError,b=h.deepClone,_=h.deepMerge,k=h.shallowClone,w=h.shallowMerge,P=h.hashPath,j={autoCommit:!0,asynchronous:!0,immutable:!0,lazyMonkeys:!0,monkeyBusiness:!0,persistent:!0,pure:!0,validate:null,validationBehavior:"rollback"},M=function(t){var e,r;function n(e,r){var n;if(n=t.call(this)||this,arguments.length<1&&(e={}),!s.default.object(e)&&!s.default.array(e))throw m("Baobab: invalid data.",{data:e});n.options=w({},j,r),n.options.persistent||(n.options.immutable=!1,n.options.pure=!1),n._identity="[object Baobab]",n._cursors={},n._future=null,n._transaction=[],n._affectedPathsIndex={},n._monkeys={},n._previousData=null,n._data=e,n.root=new i.default(f(n),[],"λ"),delete n.root.release,n.options.immutable&&v(n._data);var a=function(t){n[t]=function(){var e=this.root[t].apply(this.root,arguments);return e instanceof i.default?this:e}};["apply","clone","concat","deepClone","deepMerge","exists","get","push","merge","pop","project","serialize","set","shift","splice","unset","unshift"].forEach(a),n.options.monkeyBusiness&&n._refreshMonkeys();var o=n.validate();if(o)throw Error("Baobab: invalid data.",{error:o});return n}r=t,(e=n).prototype=Object.create(r.prototype),e.prototype.constructor=e,d(e,r);var h=n.prototype;return h._refreshMonkeys=function(t,e,r){var n=this,i=function t(e,r){if(void 0===r&&(r=[]),e instanceof a.Monkey)return e.release(),void(0,u.default)(n._monkeys,r,{type:"unset"},{immutable:!1,persistent:!1,pure:!1});if(s.default.object(e))for(var i in e)t(e[i],r.concat(i))},o=function t(e,r){if(void 0===r&&(r=[]),e instanceof a.MonkeyDefinition||e instanceof a.Monkey){var i=new a.Monkey(n,r,e instanceof a.Monkey?e.definition:e);(0,u.default)(n._monkeys,r,{type:"set",value:i},{immutable:!1,persistent:!1,pure:!1})}else if(s.default.object(e))for(var o in e)t(e[o],r.concat(o))};if(arguments.length){var h=g(this._monkeys,e).data;h&&i(h,e),"unset"!==r&&o(t,e)}else o(this._data);return this},h.validate=function(t){var e=this.options,r=e.validate,n=e.validationBehavior;if("function"!=typeof r)return null;var i=r.call(this,this._previousData,this._data,t||[[]]);return i instanceof Error?("rollback"===n&&(this._data=this._previousData,this._affectedPathsIndex={},this._transaction=[],this._previousData=this._data),this.emit("invalid",{error:i}),i):null},h.select=function(t){if(t=t||[],arguments.length>1&&(t=p(arguments)),!s.default.path(t))throw m("Baobab.select: invalid path.",{path:t});t=[].concat(t);var e=P(t),r=this._cursors[e];return r||(r=new i.default(this,t,e),this._cursors[e]=r),this.emit("select",{path:t,cursor:r}),r},h.update=function(t,e){var r=this;if(t=y(t),!s.default.operationType(e.type))throw m('Baobab.update: unknown operation type "'+e.type+'".',{operation:e});var n=g(this._data,t),i=n.solvedPath,a=n.exists;if(!i)throw m("Baobab.update: could not solve the given path.",{path:i});var o=s.default.monkeyPath(this._monkeys,i);if(o&&i.length>o.length)throw m("Baobab.update: attempting to update a read-only path.",{path:i});if("unset"!==e.type||a){var h=e;if(/merge/i.test(e.type)){var l=g(this._monkeys,i).data;if(s.default.object(l)){h=k(h);var c=g(this._data,i).data;/deep/i.test(h.type)?h.value=_({},_({},c,b(l)),h.value):h.value=w({},_({},c,b(l)),h.value)}}this._transaction.length||(this._previousData=this._data);var f=(0,u.default)(this._data,i,h,this.options),d=f.data,p=f.node;if(!("data"in f))return p;var v=i.concat("push"===e.type?p.length-1:[]),j=P(v);return this._data=d,this._affectedPathsIndex[j]=!0,this._transaction.push(w({},e,{path:v})),this.options.monkeyBusiness&&this._refreshMonkeys(p,i,e.type),this.emit("write",{path:v}),this.options.autoCommit?this.options.asynchronous?(this._future||(this._future=setTimeout((function(){return r.commit()}),0)),p):(this.commit(),p):p}},h.commit=function(){if(!this._transaction.length)return this;this._future&&(this._future=clearTimeout(this._future));var t=Object.keys(this._affectedPathsIndex).map((function(t){return"λ"!==t?t.split("λ").slice(1):[]}));if(this.validate(t))return this;var e=this._transaction,r=this._previousData;return this._affectedPathsIndex={},this._transaction=[],this._previousData=this._data,this.emit("update",{paths:t,currentData:this._data,transaction:e,previousData:r}),this},h.getMonkey=function(t){t=y(t);var e=g(this._monkeys,[].concat(t)).data;return e instanceof a.Monkey?e:null},h.watch=function(t){return new o.default(this,t)},h.release=function(){var t;for(t in this.emit("release"),delete this.root,delete this._data,delete this._previousData,delete this._transaction,delete this._affectedPathsIndex,delete this._monkeys,this._cursors)this._cursors[t].release();delete this._cursors,this.kill()},h.toJSON=function(){return this.serialize()},h.toString=function(){return this._identity},n}(n.default);M.monkey=function(){for(var t=arguments.length,e=new Array(t),r=0;r1&&(t=(0,o.arrayFrom)(arguments)),this.tree.select(this.path.concat(t))},s.up=function(){return this.isRoot()?null:this.tree.select(this.path.slice(0,-1))},s.down=function(){if(l("down",this.solvedPath),!(this._get().data instanceof Array))throw Error("Baobab.Cursor.down: cannot go down on a non-list type.");return this.tree.select(this.solvedPath.concat(0))},s.left=function(){l("left",this.solvedPath);var t=+this.solvedPath[this.solvedPath.length-1];if(isNaN(t))throw Error("Baobab.Cursor.left: cannot go left on a non-list type.");return t?this.tree.select(this.solvedPath.slice(0,-1).concat(t-1)):null},s.right=function(){l("right",this.solvedPath);var t=+this.solvedPath[this.solvedPath.length-1];if(isNaN(t))throw Error("Baobab.Cursor.right: cannot go right on a non-list type.");return t+1===this.up()._get().data.length?null:this.tree.select(this.solvedPath.slice(0,-1).concat(t+1))},s.leftmost=function(){l("leftmost",this.solvedPath);var t=+this.solvedPath[this.solvedPath.length-1];if(isNaN(t))throw Error("Baobab.Cursor.leftmost: cannot go left on a non-list type.");return this.tree.select(this.solvedPath.slice(0,-1).concat(0))},s.rightmost=function(){l("rightmost",this.solvedPath);var t=+this.solvedPath[this.solvedPath.length-1];if(isNaN(t))throw Error("Baobab.Cursor.rightmost: cannot go right on a non-list type.");var e=this.up()._get().data;return this.tree.select(this.solvedPath.slice(0,-1).concat(e.length-1))},s.map=function(t,e){l("map",this.solvedPath);var r=this._get().data,n=arguments.length;if(!a.default.array(r))throw Error("baobab.Cursor.map: cannot map a non-list type.");return r.map((function(i,a){return t.call(n>1?e:this,this.select(a),a,r)}),this)},s._get=function(t){if(void 0===t&&(t=[]),!a.default.path(t))throw(0,o.makeError)("Baobab.Cursor.getters: invalid path.",{path:t});return this.solvedPath?(0,o.getIn)(this.tree._data,this.solvedPath.concat(t)):{data:void 0,solvedPath:null,exists:!1}},s.exists=function(t){return t=(0,o.coercePath)(t),arguments.length>1&&(t=(0,o.arrayFrom)(arguments)),this._get(t).exists},s.get=function(t){t=(0,o.coercePath)(t),arguments.length>1&&(t=(0,o.arrayFrom)(arguments));var e=this._get(t),r=e.data,n=e.solvedPath;return this.tree.emit("get",{data:r,solvedPath:n,path:this.path.concat(t)}),r},s.clone=function(){var t=this.get.apply(this,arguments);return(0,o.shallowClone)(t)},s.deepClone=function(){var t=this.get.apply(this,arguments);return(0,o.deepClone)(t)},s.serialize=function(t){if(t=(0,o.coercePath)(t),arguments.length>1&&(t=(0,o.arrayFrom)(arguments)),!a.default.path(t))throw(0,o.makeError)("Baobab.Cursor.getters: invalid path.",{path:t});if(this.solvedPath){var e=this.solvedPath.concat(t),r=(0,o.deepClone)((0,o.getIn)(this.tree._data,e).data),n=(0,o.getIn)(this.tree._monkeys,e).data,s=function t(e,r){if(a.default.object(r)&&a.default.object(e))for(var n in r)r[n]instanceof i.Monkey?delete e[n]:t(e[n],r[n])};return s(r,n),r}},s.project=function(t){if(a.default.object(t)){var e={};for(var r in t)e[r]=this.get(t[r]);return e}if(a.default.array(t)){for(var n=[],i=0,s=t.length;i2)throw(0,o.makeError)("Baobab.Cursor."+t+": too many arguments.");if(1!==arguments.length||f[t]||(n=r,r=[]),r=(0,o.coercePath)(r),!a.default.path(r))throw(0,o.makeError)("Baobab.Cursor."+t+": invalid path.",{path:r});if(e&&!e(n))throw(0,o.makeError)("Baobab.Cursor."+t+": invalid value.",{path:r,value:n});if(!this.solvedPath)throw(0,o.makeError)("Baobab.Cursor."+t+": the dynamic path of the cursor cannot be solved.",{path:this.path});var i=this.solvedPath.concat(r);return this.tree.update(i,{type:t,value:n})}}d("set"),d("unset"),d("apply",a.default.function),d("push"),d("concat",a.default.array),d("unshift"),d("pop"),d("shift"),d("splice",a.default.splicer),d("merge",a.default.object),d("deepMerge",a.default.object)},{"./helpers":4,"./monkey":5,"./type":6,emmett:1}],4:[function(t,e,r){(function(e){(function(){"use strict";r.__esModule=!0,r.Archive=void 0,r.arrayFrom=function(t){return u(t)},r.before=function(t,e){return function(){t.apply(null,arguments),e.apply(null,arguments)}},r.coercePath=function(t){return t||0===t||""===t?t:[]},r.freeze=r.deepMerge=r.deepFreeze=r.deepClone=void 0,r.getIn=function(t,e){if(!e)return g;var r,n,i,o=[],u=!0,h=t;for(n=0,i=e.length;n3?n-3:0),o=3;o=0?t.slice(0,e).concat(i).concat(t.slice(e+r)):t.slice(0,t.length+e).concat(i).concat(t.slice(t.length+e+r))},r.uniqid=void 0;var n,i=t("./monkey"),a=(n=t("./type"))&&n.__esModule?n:{default:n};var o={}.hasOwnProperty;function s(t,e){var r,n;for(r=0,n=t.length;rthis.size&&(this.records.length=this.size),this},e.clear=function(){return this.records=[],this},e.back=function(t){var e=this.records[t-1];return e&&(this.records=this.records.slice(t)),e},t}();function l(t,r){if(!r||"object"!=typeof r||r instanceof Error||r instanceof i.MonkeyDefinition||r instanceof i.Monkey||"ArrayBuffer"in e&&r instanceof ArrayBuffer)return r;if(a.default.array(r)){if(t){for(var n=new Array(r.length),o=0,s=r.length;o1?e-1:0),n=1;n1&&isNaN(+t[1]))&&a(t[0],["number","function","object"]))};var o=["string","number","function","object"];i.path=function(t){return!(!t&&0!==t&&""!==t)&&[].concat(t).every((function(t){return a(t,o)}))},i.dynamicPath=function(t){return t.some((function(t){return i.function(t)||i.object(t)}))},i.monkeyPath=function(t,e){var r,i,a=[],o=t;for(r=0,i=e.length;r0&&v.push(h),s===u-1){if("set"===l){if(n.pure&&g[h]===c)return{node:g[h]};i.default.lazyGetter(g,h)?Object.defineProperty(g,h,{value:c,enumerable:!0,configurable:!0}):n.persistent&&!d.mutableLeaf?g[h]=(0,a.shallowClone)(c):g[h]=c}else if("monkey"===l)Object.defineProperty(g,h,{get:c,enumerable:!0,configurable:!0});else if("apply"===l){var m=c(g[h]);if(n.pure&&g[h]===m)return{node:g[h]};i.default.lazyGetter(g,h)?Object.defineProperty(g,h,{value:m,enumerable:!0,configurable:!0}):n.persistent?g[h]=(0,a.shallowClone)(m):g[h]=m}else if("push"===l){if(!i.default.array(g[h]))throw o("push","array",v);n.persistent?g[h]=g[h].concat([c]):g[h].push(c)}else if("unshift"===l){if(!i.default.array(g[h]))throw o("unshift","array",v);n.persistent?g[h]=[c].concat(g[h]):g[h].unshift(c)}else if("concat"===l){if(!i.default.array(g[h]))throw o("concat","array",v);n.persistent?g[h]=g[h].concat(c):g[h].push.apply(g[h],c)}else if("splice"===l){if(!i.default.array(g[h]))throw o("splice","array",v);n.persistent?g[h]=a.splice.apply(null,[g[h]].concat(c)):g[h].splice.apply(g[h],c)}else if("pop"===l){if(!i.default.array(g[h]))throw o("pop","array",v);n.persistent?g[h]=(0,a.splice)(g[h],-1,1):g[h].pop()}else if("shift"===l){if(!i.default.array(g[h]))throw o("shift","array",v);n.persistent?g[h]=(0,a.splice)(g[h],0,1):g[h].shift()}else if("unset"===l)i.default.object(g)?delete g[h]:i.default.array(g)&&g.splice(h,1);else if("merge"===l){if(!i.default.object(g[h]))throw o("merge","object",v);n.persistent?g[h]=(0,a.shallowMerge)({},g[h],c):g[h]=(0,a.shallowMerge)(g[h],c)}else if("deepMerge"===l){if(!i.default.object(g[h]))throw o("deepMerge","object",v);n.persistent?g[h]=(0,a.deepMerge)({},g[h],c):g[h]=(0,a.deepMerge)(g[h],c)}n.immutable&&!d.mutableLeaf&&(0,a.deepFreeze)(g);break}i.default.primitive(g[h])?g[h]={}:n.persistent&&(g[h]=(0,a.shallowClone)(g[h])),n.immutable&&u>0&&(0,a.freeze)(g),g=g[h]}return i.default.lazyGetter(g,h)?{data:p.root}:{data:p.root,node:g[h]}};var n,i=(n=t("./type"))&&n.__esModule?n:{default:n},a=t("./helpers");function o(t,e,r){return(0,a.makeError)('Baobab.update: cannot apply the "'+t+'" on a non '+e+" (path: /"+r.join("/")+").",{path:r})}},{"./helpers":4,"./type":6}],8:[function(t,e,r){"use strict";r.__esModule=!0,r.default=void 0;var n=s(t("emmett")),i=s(t("./cursor")),a=s(t("./type")),o=t("./helpers");function s(t){return t&&t.__esModule?t:{default:t}}function u(t,e){return(u=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}var h=function(t){var e,r;function n(e,r){var n;return(n=t.call(this)||this).tree=e,n.mapping=null,n.state={killed:!1},n.refresh(r),n.handler=function(t){if(!n.state.killed){var e=n.getWatchedPaths();return(0,o.solveUpdate)(t.data.paths,e)?n.emit("update"):void 0}},n.tree.on("update",n.handler),n}r=t,(e=n).prototype=Object.create(r.prototype),e.prototype.constructor=e,u(e,r);var s=n.prototype;return s.getWatchedPaths=function(){var t=this;return Object.keys(this.mapping).map((function(e){var r=t.mapping[e];return r instanceof i.default?r.solvedPath:t.mapping[e]})).reduce((function(e,r){if(r=[].concat(r),a.default.dynamicPath(r)&&(r=(0,o.getIn)(t.tree._data,r).solvedPath),!r)return e;var n=a.default.monkeyPath(t.tree._monkeys,r);return n?e.concat((0,o.getIn)(t.tree._monkeys,n).data.relatedPaths()):e.concat([r])}),[])},s.getCursors=function(){var t=this,e={};return Object.keys(this.mapping).forEach((function(r){var n=t.mapping[r];n instanceof i.default?e[r]=n:e[r]=t.tree.select(n)})),e},s.refresh=function(t){if(!a.default.watcherMapping(t))throw(0,o.makeError)("Baobab.watch: invalid mapping.",{mapping:t});this.mapping=t;var e={};for(var r in t)e[r]=t[r]instanceof i.default?t[r].path:t[r];this.get=this.tree.project.bind(this.tree,e)},s.release=function(){this.tree.off("update",this.handler),this.state.killed=!0,this.kill()},n}(n.default);r.default=h},{"./cursor":3,"./helpers":4,"./type":6,emmett:1}]},{},[2])(2)})); diff --git a/dist/baobab.d.ts b/dist/baobab.d.ts new file mode 100644 index 0000000..d7d7fce --- /dev/null +++ b/dist/baobab.d.ts @@ -0,0 +1,206 @@ +import Emitter from 'emmett'; +import {SBaobab} from '../dist/sbaobab'; + +interface PlainObject { + [key: string]: T; +} + +type Predicate = (data: any) => boolean; +type Constraints = PlainObject; +type PathKey = string | number; +type PathElement = PathKey | Predicate | Constraints; +export type Path = PathElement[] | PathKey; + +type Splicer = [number | PlainObject | ((...args: any[]) => any), ...any[]]; + +/** + * This class is empty purposely. Baobab must be able to identify in an initial + * state when it has to deal with Monkeys instanciation, and uses this dummy + * class in that purpose. + */ +export class MonkeyDefinition { + // Empty class intended +} + +export class Monkey { + // TODO +} + +export interface BaobabOptions { + autoCommit: boolean; + asynchronous: boolean; + immutable: boolean; + lazyMonkeys: boolean; + monkeyBusiness: boolean; + persistent: boolean; + pure: boolean; + validate: null | ((previousData: any, data: any, affectedPaths?: Path[]) => (Error | undefined)); + validationBehavior: string; +} + +export interface MonkeyOptions { + immutable: boolean; +} + +/** + * This class only exists to group methods that are common to the Baobab and + * Cursor classes. Since `Baobab.root` is a property while `Cursor#root` is a + * method, Baobab cannot extend Cursor. + */ +export abstract class CommonBaobabMethods extends Emitter { + apply(path: Path, value: (state: any) => any): any; + apply(value: (state: any) => any): any; + + clone(...args: PathElement[]): any; + clone(path?: Path): any; + + concat(path: Path, value: any[]): any; + concat(value: any[]): any; + + deepClone(...args: PathElement[]): any; + deepClone(path?: Path): any; + + deepMerge(path: Path, value: PlainObject): any; + deepMerge(value: PlainObject): any; + + exists(...args: PathElement[]): boolean; + exists(path?: Path): boolean; + + get(...args: PathElement[]): any; + get(path: Path): any; + + merge(path: Path, value: PlainObject): any; + merge(value: PlainObject): any; + + pop(path?: Path): any; + + project(projection: (Path)[]): any[]; + project(projection: PlainObject): PlainObject; + + push(path: Path, value: any): any; + push(value: any): any; + + release(): void; + + select(...args: PathElement[]): Cursor; + select(path: Path): Cursor; + + serialize(...args: PathElement[]): any; + serialize(path: Path): any; + + set(path: Path, value: any): any; + set(value: any): any; + + shift(path?: Path): any; + + splice(path: Path, value: Splicer): any; + splice(value: Splicer): any; + + unset(path?: Path): any; + + unshift(path: Path, value: any): any; + unshift(value: any): any; +} + +export class Watcher extends Emitter { + constructor(tree: Baobab, mapping: PlainObject); + + get(): PlainObject; + getWatchedPaths(): Path[]; + getCursors(): PlainObject; + refresh(mappings: PlainObject): void; + release(): void; +} + +export class Cursor extends CommonBaobabMethods implements Iterable { + path?: Path; + solvedPath?: PathKey[]; + state: { + killed: boolean; + recording: boolean; + undoing: boolean; + }; + + [Symbol.iterator](): IterableIterator; + + // Navigation: + up(): Cursor | null; + down(): Cursor; + left(): Cursor | null; + right(): Cursor | null; + leftmost(): Cursor | null; + rightmost(): Cursor | null; + root(): Cursor; + + // Predicates: + isLeaf(): boolean; + isRoot(): boolean; + isBranch(): boolean; + + // History: + hasHistory(): boolean; + getHistory(): any[]; + clearHistory(): this; + startRecording(maxRecords?: number): this; + stopRecording(): this; + undo(steps?: number): this; + + // Others: + toJSON(): string; + toString(): string; + + map(fn: (v: any, index?: number) => any, scope?: any): any[]; +} + +export class Baobab extends CommonBaobabMethods { + constructor(initialState?: PlainObject, options?: Partial); + + root: Cursor; + options: BaobabOptions; + + update( + path: Path, + operation: { + type: string, + value: any, + options?: { + mutableLeaf?: boolean; + }; + } + ): this; + + commit(): this; + + getMonkey(path: Path): Monkey; + + watch(mappings: PlainObject): Watcher; + + static monkey(definition: {cursors?: PlainObject; get(data: PlainObject): any; options?: MonkeyOptions;}): MonkeyDefinition; + + /* tslint:disable:unified-signatures */ + // Polymorphisms for: + // `.monkey(...paths: Path[], get: (v1: any) => any)` + static monkey(path1: Path, get: (value: any) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: Path, path2: Path, get: (...values: [any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: Path, path2: Path, path3: Path, get: (...values: [any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: Path, path2: Path, path3: Path, path4: Path, get: (...values: [any, any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: Path, path2: Path, path3: Path, path4: Path, path5: Path, get: (...values: [any, any, any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + // Fallback: + static monkey(...pathsEndingWithGetAndMaybeOptions: (Path | ((...values: any[]) => any) | MonkeyOptions)[]): MonkeyDefinition; + + // Polymorphisms for: + // `.monkey(definition: [...paths: Path[], get: (v1: any) => any])` + static monkey(args: [Path, (value: any) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [Path, Path, (...values: [any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [Path, Path, Path, (...values: [any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [Path, Path, Path, Path, (...values: [any, any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [Path, Path, Path, Path, Path, (...values: [any, any, any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + // Fallback: + static monkey(pathsEndingWithGet: (Path | ((...values: any[]) => any) | MonkeyOptions)[]): MonkeyDefinition; + /* tslint:enable:unified-signatures */ + + static dynamicNode: typeof Baobab.monkey; +} + +export default Baobab; +// export * from './sbaobab'; \ No newline at end of file diff --git a/dist/baobab.js b/dist/baobab.js new file mode 100644 index 0000000..d84f579 --- /dev/null +++ b/dist/baobab.js @@ -0,0 +1,592 @@ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "Cursor", { + enumerable: true, + get: function get() { + return _cursor["default"]; + } +}); +Object.defineProperty(exports, "Monkey", { + enumerable: true, + get: function get() { + return _monkey.Monkey; + } +}); +Object.defineProperty(exports, "MonkeyDefinition", { + enumerable: true, + get: function get() { + return _monkey.MonkeyDefinition; + } +}); +exports.monkey = exports.helpers = exports.dynamic = exports["default"] = exports.VERSION = void 0; +Object.defineProperty(exports, "type", { + enumerable: true, + get: function get() { + return _type["default"]; + } +}); + +var _emmett = _interopRequireDefault(require("emmett")); + +var _cursor = _interopRequireDefault(require("./cursor")); + +var _monkey = require("./monkey"); + +var _watcher = _interopRequireDefault(require("./watcher")); + +var _type = _interopRequireDefault(require("./type")); + +var _update2 = _interopRequireDefault(require("./update")); + +var helpers = _interopRequireWildcard(require("./helpers")); + +exports.helpers = helpers; + +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } + +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +var arrayFrom = helpers.arrayFrom, + coercePath = helpers.coercePath, + deepFreeze = helpers.deepFreeze, + getIn = helpers.getIn, + makeError = helpers.makeError, + deepClone = helpers.deepClone, + deepMerge = helpers.deepMerge, + shallowClone = helpers.shallowClone, + shallowMerge = helpers.shallowMerge, + hashPath = helpers.hashPath; +/** + * Baobab defaults + */ + +var DEFAULTS = { + // Should the tree handle its transactions on its own? + autoCommit: true, + // Should the transactions be handled asynchronously? + asynchronous: true, + // Should the tree's data be immutable? + immutable: true, + // Should the monkeys be lazy? + lazyMonkeys: true, + // Should we evaluate monkeys? + monkeyBusiness: true, + // Should the tree be persistent? + persistent: true, + // Should the tree's update be pure? + pure: true, + // Validation specifications + validate: null, + // Validation behavior 'rollback' or 'notify' + validationBehavior: 'rollback' +}; +/** + * Baobab class + * + * @constructor + * @param {object|array} [initialData={}] - Initial data passed to the tree. + * @param {object} [opts] - Optional options. + * @param {boolean} [opts.autoCommit] - Should the tree auto-commit? + * @param {boolean} [opts.asynchronous] - Should the tree's transactions + * handled asynchronously? + * @param {boolean} [opts.immutable] - Should the tree be immutable? + * @param {boolean} [opts.persistent] - Should the tree be persistent? + * @param {boolean} [opts.pure] - Should the tree be pure? + * @param {function} [opts.validate] - Validation function. + * @param {string} [opts.validationBehaviour] - "rollback" or "notify". + */ + +var Baobab = /*#__PURE__*/function (_Emitter) { + _inherits(Baobab, _Emitter); + + var _super = _createSuper(Baobab); + + function Baobab(initialData, opts) { + var _this; + + _classCallCheck(this, Baobab); + + _this = _super.call(this); // Setting initialData to an empty object if no data is provided by use + + if (arguments.length < 1) initialData = {}; // Checking whether given initial data is valid + + if (!_type["default"].object(initialData) && !_type["default"].array(initialData)) throw makeError('Baobab: invalid data.', { + data: initialData + }); // Merging given options with defaults + + _this.options = shallowMerge({}, DEFAULTS, opts); // Disabling immutability & persistence if persistence if disabled + + if (!_this.options.persistent) { + _this.options.immutable = false; + _this.options.pure = false; + } // Privates + + + _this._identity = '[object Baobab]'; + _this._cursors = {}; + _this._future = null; + _this._transaction = []; + _this._affectedPathsIndex = {}; + _this._monkeys = {}; + _this._previousData = null; + _this._data = initialData; // Properties + + _this.root = new _cursor["default"](_assertThisInitialized(_this), [], 'λ'); + delete _this.root.release; // Does the user want an immutable tree? + + if (_this.options.immutable) deepFreeze(_this._data); // Bootstrapping root cursor's getters and setters + + var bootstrap = function bootstrap(name) { + _this[name] = function () { + var r = this.root[name].apply(this.root, arguments); + return r instanceof _cursor["default"] ? this : r; + }; + }; + + ['apply', 'clone', 'concat', 'deepClone', 'deepMerge', 'exists', 'get', 'push', 'merge', 'pop', 'project', 'serialize', 'set', 'shift', 'splice', 'unset', 'unshift'].forEach(bootstrap); // Registering the initial monkeys + + if (_this.options.monkeyBusiness) { + _this._refreshMonkeys(); + } // Initial validation + + + var validationError = _this.validate(); + + if (validationError) throw Error('Baobab: invalid data.', { + error: validationError + }); + return _this; + } + /** + * Internal method used to refresh the tree's monkey register on every + * update. + * Note 1) For the time being, placing monkeys beneath array nodes is not + * allowed for performance reasons. + * + * @param {mixed} node - The starting node. + * @param {array} path - The starting node's path. + * @param {string} operation - The operation that lead to a refreshment. + * @return {Baobab} - The tree instance for chaining purposes. + */ + + + _createClass(Baobab, [{ + key: "_refreshMonkeys", + value: function _refreshMonkeys(node, path, operation) { + var _this2 = this; + + var clean = function clean(data) { + var p = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; + + if (data instanceof _monkey.Monkey) { + data.release(); + (0, _update2["default"])(_this2._monkeys, p, { + type: 'unset' + }, { + immutable: false, + persistent: false, + pure: false + }); + return; + } + + if (_type["default"].object(data)) { + for (var k in data) { + clean(data[k], p.concat(k)); + } + } + }; + + var walk = function walk(data) { + var p = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; + + // Should we sit a monkey in the tree? + if (data instanceof _monkey.MonkeyDefinition || data instanceof _monkey.Monkey) { + var monkeyInstance = new _monkey.Monkey(_this2, p, data instanceof _monkey.Monkey ? data.definition : data); + (0, _update2["default"])(_this2._monkeys, p, { + type: 'set', + value: monkeyInstance + }, { + immutable: false, + persistent: false, + pure: false + }); + return; + } // Object iteration + + + if (_type["default"].object(data)) { + for (var k in data) { + walk(data[k], p.concat(k)); + } + } + }; // Walking the whole tree + + + if (!arguments.length) { + walk(this._data); + } else { + var monkeysNode = getIn(this._monkeys, path).data; // Is this required that we clean some already existing monkeys? + + if (monkeysNode) clean(monkeysNode, path); // Let's walk the tree only from the updated point + + if (operation !== 'unset') { + walk(node, path); + } + } + + return this; + } + /** + * Method used to validate the tree's data. + * + * @return {boolean} - Is the tree valid? + */ + + }, { + key: "validate", + value: function validate(affectedPaths) { + var _this$options = this.options, + validate = _this$options.validate, + behavior = _this$options.validationBehavior; + if (typeof validate !== 'function') return null; + var error = validate.call(this, this._previousData, this._data, affectedPaths || [[]]); + + if (error instanceof Error) { + if (behavior === 'rollback') { + this._data = this._previousData; + this._affectedPathsIndex = {}; + this._transaction = []; + this._previousData = this._data; + } + + this.emit('invalid', { + error: error + }); + return error; + } + + return null; + } + /** + * Method used to select data within the tree by creating a cursor. Cursors + * are kept as singletons by the tree for performance and hygiene reasons. + * + * Arity (1): + * @param {path} path - Path to select in the tree. + * + * Arity (*): + * @param {...step} path - Path to select in the tree. + * + * @return {Cursor} - The resultant cursor. + */ + + }, { + key: "select", + value: function select(path) { + // If no path is given, we simply return the root + path = path || []; // Variadic + + if (arguments.length > 1) path = arrayFrom(arguments); // Checking that given path is valid + + if (!_type["default"].path(path)) throw makeError('Baobab.select: invalid path.', { + path: path + }); // Casting to array + + path = [].concat(path); // Computing hash (done here because it would be too late to do it in the + // cursor's constructor since we need to hit the cursors' index first). + + var hash = hashPath(path); // Creating a new cursor or returning the already existing one for the + // requested path. + + var cursor = this._cursors[hash]; + + if (!cursor) { + cursor = new _cursor["default"](this, path, hash); + this._cursors[hash] = cursor; + } // Emitting an event to notify that a part of the tree was selected + + + this.emit('select', { + path: path, + cursor: cursor + }); + return cursor; + } + /** + * Method used to update the tree. Updates are simply expressed by a path, + * dynamic or not, and an operation. + * + * This is where path solving should happen and not in the cursor. + * + * @param {path} path - The path where we'll apply the operation. + * @param {object} operation - The operation to apply. + * @return {mixed} - Return the result of the update. + */ + + }, { + key: "update", + value: function update(path, operation) { + var _this3 = this; + + // Coercing path + path = coercePath(path); + if (!_type["default"].operationType(operation.type)) throw makeError("Baobab.update: unknown operation type \"".concat(operation.type, "\"."), { + operation: operation + }); // Solving the given path + + var _getIn = getIn(this._data, path), + solvedPath = _getIn.solvedPath, + exists = _getIn.exists; // If we couldn't solve the path, we throw + + + if (!solvedPath) throw makeError('Baobab.update: could not solve the given path.', { + path: solvedPath + }); // Read-only path? + + var monkeyPath = _type["default"].monkeyPath(this._monkeys, solvedPath); + + if (monkeyPath && solvedPath.length > monkeyPath.length) throw makeError('Baobab.update: attempting to update a read-only path.', { + path: solvedPath + }); // We don't unset irrelevant paths + + if (operation.type === 'unset' && !exists) return; // If we merge data, we need to acknowledge monkeys + + var realOperation = operation; + + if (/merge/i.test(operation.type)) { + var monkeysNode = getIn(this._monkeys, solvedPath).data; + + if (_type["default"].object(monkeysNode)) { + // Cloning the operation not to create weird behavior for the user + realOperation = shallowClone(realOperation); // Fetching the existing node in the current data + + var currentNode = getIn(this._data, solvedPath).data; + if (/deep/i.test(realOperation.type)) realOperation.value = deepMerge({}, deepMerge({}, currentNode, deepClone(monkeysNode)), realOperation.value);else realOperation.value = shallowMerge({}, deepMerge({}, currentNode, deepClone(monkeysNode)), realOperation.value); + } + } // Stashing previous data if this is the frame's first update + + + if (!this._transaction.length) this._previousData = this._data; // Applying the operation + + var result = (0, _update2["default"])(this._data, solvedPath, realOperation, this.options); + var data = result.data, + node = result.node; // If because of purity, the update was moot, we stop here + + if (!('data' in result)) return node; // If the operation is push, the affected path is slightly different + + var affectedPath = solvedPath.concat(operation.type === 'push' ? node.length - 1 : []); + var hash = hashPath(affectedPath); // Updating data and transaction + + this._data = data; + this._affectedPathsIndex[hash] = true; + + this._transaction.push(shallowMerge({}, operation, { + path: affectedPath + })); // Updating the monkeys + + + if (this.options.monkeyBusiness) { + this._refreshMonkeys(node, solvedPath, operation.type); + } // Emitting a `write` event + + + this.emit('write', { + path: affectedPath + }); // Should we let the user commit? + + if (!this.options.autoCommit) return node; // Should we update asynchronously? + + if (!this.options.asynchronous) { + this.commit(); + return node; + } // Updating asynchronously + + + if (!this._future) this._future = setTimeout(function () { + return _this3.commit(); + }, 0); // Finally returning the affected node + + return node; + } + /** + * Method committing the updates of the tree and firing the tree's events. + * + * @return {Baobab} - The tree instance for chaining purposes. + */ + + }, { + key: "commit", + value: function commit() { + // Do not fire update if the transaction is empty + if (!this._transaction.length) return this; // Clearing timeout if one was defined + + if (this._future) this._future = clearTimeout(this._future); + var affectedPaths = Object.keys(this._affectedPathsIndex).map(function (h) { + return h !== 'λ' ? h.split('λ').slice(1) : []; + }); // Is the tree still valid? + + var validationError = this.validate(affectedPaths); + if (validationError) return this; // Caching to keep original references before we change them + + var transaction = this._transaction, + previousData = this._previousData; + this._affectedPathsIndex = {}; + this._transaction = []; + this._previousData = this._data; // Emitting update event + + this.emit('update', { + paths: affectedPaths, + currentData: this._data, + transaction: transaction, + previousData: previousData + }); + return this; + } + /** + * Method returning a monkey at the given path or else `null`. + * + * @param {path} path - Path of the monkey to retrieve. + * @return {Monkey|null} - The Monkey instance of `null`. + */ + + }, { + key: "getMonkey", + value: function getMonkey(path) { + path = coercePath(path); + var monkey = getIn(this._monkeys, [].concat(path)).data; + if (monkey instanceof _monkey.Monkey) return monkey; + return null; + } + /** + * Method used to watch a collection of paths within the tree. Very useful + * to bind UI components and such to the tree. + * + * @param {object} mapping - Mapping of paths to listen. + * @return {Cursor} - The created watcher. + */ + + }, { + key: "watch", + value: function watch(mapping) { + return new _watcher["default"](this, mapping); + } + /** + * Method releasing the tree and its attached data from memory. + */ + + }, { + key: "release", + value: function release() { + var k; + this.emit('release'); + delete this.root; + delete this._data; + delete this._previousData; + delete this._transaction; + delete this._affectedPathsIndex; + delete this._monkeys; // Releasing cursors + + for (k in this._cursors) { + this._cursors[k].release(); + } + + delete this._cursors; // Killing event emitter + + this.kill(); + } + /** + * Overriding the `toJSON` method for convenient use with JSON.stringify. + * + * @return {mixed} - Data at cursor. + */ + + }, { + key: "toJSON", + value: function toJSON() { + return this.serialize(); + } + /** + * Overriding the `toString` method for debugging purposes. + * + * @return {string} - The baobab's identity. + */ + + }, { + key: "toString", + value: function toString() { + return this._identity; + } + }]); + + return Baobab; +}(_emmett["default"]); +/** + * Monkey helper. + */ + + +Baobab.monkey = function () { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + if (!args.length) throw new Error('Baobab.monkey: missing definition.'); + if (args.length === 1 && typeof args[0] !== 'function') return new _monkey.MonkeyDefinition(args[0]); + return new _monkey.MonkeyDefinition(args); +}; + +Baobab.dynamicNode = Baobab.monkey; +var monkey = Baobab.monkey; +exports.monkey = monkey; +var dynamic = Baobab.dynamic; +/** + * Exposing some internals for convenience + */ + +exports.dynamic = dynamic; + +/** + * Version. + */ +Baobab.VERSION = '2.6.1'; +var VERSION = Baobab.VERSION; +/** + * Exporting. + */ + +exports.VERSION = VERSION; +var _default = Baobab; // export * from './sbaobab'; + +exports["default"] = _default; +for (var exportedName in exports) + Baobab[exportedName] = exports[exportedName]; + +module.exports = Baobab; diff --git a/dist/cursor.js b/dist/cursor.js new file mode 100644 index 0000000..4e79ddf --- /dev/null +++ b/dist/cursor.js @@ -0,0 +1,844 @@ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = void 0; + +var _emmett = _interopRequireDefault(require("emmett")); + +var _monkey = require("./monkey"); + +var _type = _interopRequireDefault(require("./type")); + +var _helpers = require("./helpers"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +/** + * Traversal helper function for dynamic cursors. Will throw a legible error + * if traversal is not possible. + * + * @param {string} method - The method name, to create a correct error msg. + * @param {array} solvedPath - The cursor's solved path. + */ +function checkPossibilityOfDynamicTraversal(method, solvedPath) { + if (!solvedPath) throw (0, _helpers.makeError)("Baobab.Cursor.".concat(method, ": ") + "cannot use ".concat(method, " on an unresolved dynamic path."), { + path: solvedPath + }); +} +/** + * Cursor class + * + * @constructor + * @param {Baobab} tree - The cursor's root. + * @param {array} path - The cursor's path in the tree. + * @param {string} hash - The path's hash computed ahead by the tree. + */ + + +var Cursor = /*#__PURE__*/function (_Emitter) { + _inherits(Cursor, _Emitter); + + var _super = _createSuper(Cursor); + + function Cursor(tree, path, hash) { + var _this; + + _classCallCheck(this, Cursor); + + _this = _super.call(this); // If no path were to be provided, we fallback to an empty path (root) + + path = path || []; // Privates + + _this._identity = '[object Cursor]'; + _this._archive = null; // Properties + + _this.tree = tree; + _this.path = path; + _this.hash = hash; // State + + _this.state = { + killed: false, + recording: false, + undoing: false + }; // Checking whether the given path is dynamic or not + + _this._dynamicPath = _type["default"].dynamicPath(_this.path); // Checking whether the given path will meet a monkey + + _this._monkeyPath = _type["default"].monkeyPath(_this.tree._monkeys, _this.path); + if (!_this._dynamicPath) _this.solvedPath = _this.path;else _this.solvedPath = (0, _helpers.getIn)(_this.tree._data, _this.path).solvedPath; + /** + * Listener bound to the tree's writes so that cursors with dynamic paths + * may update their solved path correctly. + * + * @param {object} event - The event fired by the tree. + */ + + _this._writeHandler = function (_ref) { + var data = _ref.data; + if (_this.state.killed || !(0, _helpers.solveUpdate)([data.path], _this._getComparedPaths())) return; + _this.solvedPath = (0, _helpers.getIn)(_this.tree._data, _this.path).solvedPath; + }; + /** + * Function in charge of actually trigger the cursor's updates and + * deal with the archived records. + * + * @note: probably should wrap the current solvedPath in closure to avoid + * for tricky cases where it would fail. + * + * @param {mixed} previousData - the tree's previous data. + */ + + + var fireUpdate = function fireUpdate(previousData) { + var self = _assertThisInitialized(_this); + + var eventData = { + get previousData() { + return (0, _helpers.getIn)(previousData, self.solvedPath).data; + }, + + get currentData() { + return self.get(); + } + + }; + if (_this.state.recording && !_this.state.undoing) _this.archive.add(eventData.previousData); + _this.state.undoing = false; + return _this.emit('update', eventData); + }; + /** + * Listener bound to the tree's updates and determining whether the + * cursor is affected and should react accordingly. + * + * Note that this listener is lazily bound to the tree to be sure + * one wouldn't leak listeners when only creating cursors for convenience + * and not to listen to updates specifically. + * + * @param {object} event - The event fired by the tree. + */ + + + _this._updateHandler = function (event) { + if (_this.state.killed) return; + + var _event$data = event.data, + paths = _event$data.paths, + previousData = _event$data.previousData, + update = fireUpdate.bind(_assertThisInitialized(_this), previousData), + comparedPaths = _this._getComparedPaths(); + + if ((0, _helpers.solveUpdate)(paths, comparedPaths)) return update(); + }; // Lazy binding + + + var bound = false; + + _this._lazyBind = function () { + if (bound) return; + bound = true; + if (_this._dynamicPath) _this.tree.on('write', _this._writeHandler); + return _this.tree.on('update', _this._updateHandler); + }; // If the path is dynamic, we actually need to listen to the tree + + + if (_this._dynamicPath) { + _this._lazyBind(); + } else { + // Overriding the emitter `on` and `once` methods + _this.on = (0, _helpers.before)(_this._lazyBind, _this.on.bind(_assertThisInitialized(_this))); + _this.once = (0, _helpers.before)(_this._lazyBind, _this.once.bind(_assertThisInitialized(_this))); + } + + return _this; + } + /** + * Internal helpers + * ----------------- + */ + + /** + * Method returning the paths of the tree watched over by the cursor and that + * should be taken into account when solving a potential update. + * + * @return {array} - Array of paths to compare with a given update. + */ + + + _createClass(Cursor, [{ + key: "_getComparedPaths", + value: function _getComparedPaths() { + // Checking whether we should keep track of some dependencies + var additionalPaths = this._monkeyPath ? (0, _helpers.getIn)(this.tree._monkeys, this._monkeyPath).data.relatedPaths() : []; + return [this.solvedPath].concat(additionalPaths); + } + /** + * Predicates + * ----------- + */ + + /** + * Method returning whether the cursor is at root level. + * + * @return {boolean} - Is the cursor the root? + */ + + }, { + key: "isRoot", + value: function isRoot() { + return !this.path.length; + } + /** + * Method returning whether the cursor is at leaf level. + * + * @return {boolean} - Is the cursor a leaf? + */ + + }, { + key: "isLeaf", + value: function isLeaf() { + return _type["default"].primitive(this._get().data); + } + /** + * Method returning whether the cursor is at branch level. + * + * @return {boolean} - Is the cursor a branch? + */ + + }, { + key: "isBranch", + value: function isBranch() { + return !this.isRoot() && !this.isLeaf(); + } + /** + * Traversal Methods + * ------------------ + */ + + /** + * Method returning the root cursor. + * + * @return {Baobab} - The root cursor. + */ + + }, { + key: "root", + value: function root() { + return this.tree.select(); + } + /** + * Method selecting a subpath as a new cursor. + * + * Arity (1): + * @param {path} path - The path to select. + * + * Arity (*): + * @param {...step} path - The path to select. + * + * @return {Cursor} - The created cursor. + */ + + }, { + key: "select", + value: function select(path) { + if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments); + return this.tree.select(this.path.concat(path)); + } + /** + * Method returning the parent node of the cursor or else `null` if the + * cursor is already at root level. + * + * @return {Baobab} - The parent cursor. + */ + + }, { + key: "up", + value: function up() { + if (!this.isRoot()) return this.tree.select(this.path.slice(0, -1)); + return null; + } + /** + * Method returning the child node of the cursor. + * + * @return {Baobab} - The child cursor. + */ + + }, { + key: "down", + value: function down() { + checkPossibilityOfDynamicTraversal('down', this.solvedPath); + if (!(this._get().data instanceof Array)) throw Error('Baobab.Cursor.down: cannot go down on a non-list type.'); + return this.tree.select(this.solvedPath.concat(0)); + } + /** + * Method returning the left sibling node of the cursor if this one is + * pointing at a list. Returns `null` if this cursor is already leftmost. + * + * @return {Baobab} - The left sibling cursor. + */ + + }, { + key: "left", + value: function left() { + checkPossibilityOfDynamicTraversal('left', this.solvedPath); + var last = +this.solvedPath[this.solvedPath.length - 1]; + if (isNaN(last)) throw Error('Baobab.Cursor.left: cannot go left on a non-list type.'); + return last ? this.tree.select(this.solvedPath.slice(0, -1).concat(last - 1)) : null; + } + /** + * Method returning the right sibling node of the cursor if this one is + * pointing at a list. Returns `null` if this cursor is already rightmost. + * + * @return {Baobab} - The right sibling cursor. + */ + + }, { + key: "right", + value: function right() { + checkPossibilityOfDynamicTraversal('right', this.solvedPath); + var last = +this.solvedPath[this.solvedPath.length - 1]; + if (isNaN(last)) throw Error('Baobab.Cursor.right: cannot go right on a non-list type.'); + if (last + 1 === this.up()._get().data.length) return null; + return this.tree.select(this.solvedPath.slice(0, -1).concat(last + 1)); + } + /** + * Method returning the leftmost sibling node of the cursor if this one is + * pointing at a list. + * + * @return {Baobab} - The leftmost sibling cursor. + */ + + }, { + key: "leftmost", + value: function leftmost() { + checkPossibilityOfDynamicTraversal('leftmost', this.solvedPath); + var last = +this.solvedPath[this.solvedPath.length - 1]; + if (isNaN(last)) throw Error('Baobab.Cursor.leftmost: cannot go left on a non-list type.'); + return this.tree.select(this.solvedPath.slice(0, -1).concat(0)); + } + /** + * Method returning the rightmost sibling node of the cursor if this one is + * pointing at a list. + * + * @return {Baobab} - The rightmost sibling cursor. + */ + + }, { + key: "rightmost", + value: function rightmost() { + checkPossibilityOfDynamicTraversal('rightmost', this.solvedPath); + var last = +this.solvedPath[this.solvedPath.length - 1]; + if (isNaN(last)) throw Error('Baobab.Cursor.rightmost: cannot go right on a non-list type.'); + + var list = this.up()._get().data; + + return this.tree.select(this.solvedPath.slice(0, -1).concat(list.length - 1)); + } + /** + * Method mapping the children nodes of the cursor. + * + * @param {function} fn - The function to map. + * @param {object} [scope] - An optional scope. + * @return {array} - The resultant array. + */ + + }, { + key: "map", + value: function map(fn, scope) { + checkPossibilityOfDynamicTraversal('map', this.solvedPath); + + var array = this._get().data, + l = arguments.length; + + if (!_type["default"].array(array)) throw Error('baobab.Cursor.map: cannot map a non-list type.'); + return array.map(function (item, i) { + return fn.call(l > 1 ? scope : this, this.select(i), i, array); + }, this); + } + /** + * Getter Methods + * --------------- + */ + + /** + * Internal get method. Basically contains the main body of the `get` method + * without the event emitting. This is sometimes needed not to fire useless + * events. + * + * @param {path} [path=[]] - Path to get in the tree. + * @return {object} info - The resultant information. + * @return {mixed} info.data - Data at path. + * @return {array} info.solvedPath - The path solved when getting. + */ + + }, { + key: "_get", + value: function _get() { + var path = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + if (!_type["default"].path(path)) throw (0, _helpers.makeError)('Baobab.Cursor.getters: invalid path.', { + path: path + }); + if (!this.solvedPath) return { + data: undefined, + solvedPath: null, + exists: false + }; + return (0, _helpers.getIn)(this.tree._data, this.solvedPath.concat(path)); + } + /** + * Method used to check whether a certain path exists in the tree starting + * from the current cursor. + * + * Arity (1): + * @param {path} path - Path to check in the tree. + * + * Arity (2): + * @param {..step} path - Path to check in the tree. + * + * @return {boolean} - Does the given path exists? + */ + + }, { + key: "exists", + value: function exists(path) { + path = (0, _helpers.coercePath)(path); + if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments); + return this._get(path).exists; + } + /** + * Method used to get data from the tree. Will fire a `get` event from the + * tree so that the user may sometimes react upon it to fetch data, for + * instance. + * + * Arity (1): + * @param {path} path - Path to get in the tree. + * + * Arity (2): + * @param {..step} path - Path to get in the tree. + * + * @return {mixed} - Data at path. + */ + + }, { + key: "get", + value: function get(path) { + path = (0, _helpers.coercePath)(path); + if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments); + + var _this$_get = this._get(path), + data = _this$_get.data, + solvedPath = _this$_get.solvedPath; // Emitting the event + + + this.tree.emit('get', { + data: data, + solvedPath: solvedPath, + path: this.path.concat(path) + }); + return data; + } + /** + * Method used to shallow clone data from the tree. + * + * Arity (1): + * @param {path} path - Path to get in the tree. + * + * Arity (2): + * @param {..step} path - Path to get in the tree. + * + * @return {mixed} - Cloned data at path. + */ + + }, { + key: "clone", + value: function clone() { + var data = this.get.apply(this, arguments); + return (0, _helpers.shallowClone)(data); + } + /** + * Method used to deep clone data from the tree. + * + * Arity (1): + * @param {path} path - Path to get in the tree. + * + * Arity (2): + * @param {..step} path - Path to get in the tree. + * + * @return {mixed} - Cloned data at path. + */ + + }, { + key: "deepClone", + value: function deepClone() { + var data = this.get.apply(this, arguments); + return (0, _helpers.deepClone)(data); + } + /** + * Method used to return raw data from the tree, by carefully avoiding + * computed one. + * + * @todo: should be more performant as the cloning should happen as well as + * when dropping computed data. + * + * Arity (1): + * @param {path} path - Path to serialize in the tree. + * + * Arity (2): + * @param {..step} path - Path to serialize in the tree. + * + * @return {mixed} - The retrieved raw data. + */ + + }, { + key: "serialize", + value: function serialize(path) { + path = (0, _helpers.coercePath)(path); + if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments); + if (!_type["default"].path(path)) throw (0, _helpers.makeError)('Baobab.Cursor.getters: invalid path.', { + path: path + }); + if (!this.solvedPath) return undefined; + var fullPath = this.solvedPath.concat(path); + var data = (0, _helpers.deepClone)((0, _helpers.getIn)(this.tree._data, fullPath).data), + monkeys = (0, _helpers.getIn)(this.tree._monkeys, fullPath).data; + + var dropComputedData = function dropComputedData(d, m) { + if (!_type["default"].object(m) || !_type["default"].object(d)) return; + + for (var k in m) { + if (m[k] instanceof _monkey.Monkey) delete d[k];else dropComputedData(d[k], m[k]); + } + }; + + dropComputedData(data, monkeys); + return data; + } + /** + * Method used to project some of the data at cursor onto a map or a list. + * + * @param {object|array} projection - The projection's formal definition. + * @return {object|array} - The resultant map/list. + */ + + }, { + key: "project", + value: function project(projection) { + if (_type["default"].object(projection)) { + var data = {}; + + for (var k in projection) { + data[k] = this.get(projection[k]); + } + + return data; + } else if (_type["default"].array(projection)) { + var _data = []; + + for (var i = 0, l = projection.length; i < l; i++) { + _data.push(this.get(projection[i])); + } + + return _data; + } + + throw (0, _helpers.makeError)('Baobab.Cursor.project: wrong projection.', { + projection: projection + }); + } + /** + * History Methods + * ---------------- + */ + + /** + * Methods starting to record the cursor's successive states. + * + * @param {integer} [maxRecords] - Maximum records to keep in memory. Note + * that if no number is provided, the cursor + * will keep everything. + * @return {Cursor} - The cursor instance for chaining purposes. + */ + + }, { + key: "startRecording", + value: function startRecording(maxRecords) { + maxRecords = maxRecords || Infinity; + if (maxRecords < 1) throw (0, _helpers.makeError)('Baobab.Cursor.startRecording: invalid max records.', { + value: maxRecords + }); + this.state.recording = true; + if (this.archive) return this; // Lazy binding + + this._lazyBind(); + + this.archive = new _helpers.Archive(maxRecords); + return this; + } + /** + * Methods stopping to record the cursor's successive states. + * + * @return {Cursor} - The cursor instance for chaining purposes. + */ + + }, { + key: "stopRecording", + value: function stopRecording() { + this.state.recording = false; + return this; + } + /** + * Methods undoing n steps of the cursor's recorded states. + * + * @param {integer} [steps=1] - The number of steps to rollback. + * @return {Cursor} - The cursor instance for chaining purposes. + */ + + }, { + key: "undo", + value: function undo() { + var steps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; + if (!this.state.recording) throw new Error('Baobab.Cursor.undo: cursor is not recording.'); + var record = this.archive.back(steps); + if (!record) throw Error('Baobab.Cursor.undo: cannot find a relevant record.'); + this.state.undoing = true; + this.set(record); + return this; + } + /** + * Methods returning whether the cursor has a recorded history. + * + * @return {boolean} - `true` if the cursor has a recorded history? + */ + + }, { + key: "hasHistory", + value: function hasHistory() { + return !!(this.archive && this.archive.get().length); + } + /** + * Methods returning the cursor's history. + * + * @return {array} - The cursor's history. + */ + + }, { + key: "getHistory", + value: function getHistory() { + return this.archive ? this.archive.get() : []; + } + /** + * Methods clearing the cursor's history. + * + * @return {Cursor} - The cursor instance for chaining purposes. + */ + + }, { + key: "clearHistory", + value: function clearHistory() { + if (this.archive) this.archive.clear(); + return this; + } + /** + * Releasing + * ---------- + */ + + /** + * Methods releasing the cursor from memory. + */ + + }, { + key: "release", + value: function release() { + // Removing listeners on parent + if (this._dynamicPath) this.tree.off('write', this._writeHandler); + this.tree.off('update', this._updateHandler); // Unsubscribe from the parent + + if (this.hash) delete this.tree._cursors[this.hash]; // Dereferencing + + delete this.tree; + delete this.path; + delete this.solvedPath; + delete this.archive; // Killing emitter + + this.kill(); + this.state.killed = true; + } + /** + * Output + * ------- + */ + + /** + * Overriding the `toJSON` method for convenient use with JSON.stringify. + * + * @return {mixed} - Data at cursor. + */ + + }, { + key: "toJSON", + value: function toJSON() { + return this.serialize(); + } + /** + * Overriding the `toString` method for debugging purposes. + * + * @return {string} - The cursor's identity. + */ + + }, { + key: "toString", + value: function toString() { + return this._identity; + } + }]); + + return Cursor; +}(_emmett["default"]); +/** + * Method used to allow iterating over cursors containing list-type data. + * + * e.g. for(let i of cursor) { ... } + * + * @returns {object} - Each item sequentially. + */ + + +exports["default"] = Cursor; + +if (typeof Symbol === 'function' && typeof Symbol.iterator !== 'undefined') { + Cursor.prototype[Symbol.iterator] = function () { + var array = this._get().data; + + if (!_type["default"].array(array)) throw Error('baobab.Cursor.@@iterate: cannot iterate a non-list type.'); + var i = 0; + var cursor = this, + length = array.length; + return { + next: function next() { + if (i < length) { + return { + value: cursor.select(i++) + }; + } + + return { + done: true + }; + } + }; + }; +} +/** + * Setter Methods + * --------------- + * + * Those methods are dynamically assigned to the class for DRY reasons. + */ +// Not using a Set so that ES5 consumers don't pay a bundle size price + + +var INTRANSITIVE_SETTERS = { + unset: true, + pop: true, + shift: true +}; +/** + * Function creating a setter method for the Cursor class. + * + * @param {string} name - the method's name. + * @param {function} [typeChecker] - a function checking that the given value is + * valid for the given operation. + */ + +function makeSetter(name, typeChecker) { + /** + * Binding a setter method to the Cursor class and having the following + * definition. + * + * Note: this is not really possible to make those setters variadic because + * it would create an impossible polymorphism with path. + * + * @todo: perform value validation elsewhere so that tree.update can + * beneficiate from it. + * + * Arity (1): + * @param {mixed} value - New value to set at cursor's path. + * + * Arity (2): + * @param {path} path - Subpath to update starting from cursor's. + * @param {mixed} value - New value to set. + * + * @return {mixed} - Data at path. + */ + Cursor.prototype[name] = function (path, value) { + // We should warn the user if he applies to many arguments to the function + if (arguments.length > 2) throw (0, _helpers.makeError)("Baobab.Cursor.".concat(name, ": too many arguments.")); // Handling arities + + if (arguments.length === 1 && !INTRANSITIVE_SETTERS[name]) { + value = path; + path = []; + } // Coerce path + + + path = (0, _helpers.coercePath)(path); // Checking the path's validity + + if (!_type["default"].path(path)) throw (0, _helpers.makeError)("Baobab.Cursor.".concat(name, ": invalid path."), { + path: path + }); // Checking the value's validity + + if (typeChecker && !typeChecker(value)) throw (0, _helpers.makeError)("Baobab.Cursor.".concat(name, ": invalid value."), { + path: path, + value: value + }); // Checking the solvability of the cursor's dynamic path + + if (!this.solvedPath) throw (0, _helpers.makeError)("Baobab.Cursor.".concat(name, ": the dynamic path of the cursor cannot be solved."), { + path: this.path + }); + var fullPath = this.solvedPath.concat(path); // Filing the update to the tree + + return this.tree.update(fullPath, { + type: name, + value: value + }); + }; +} +/** + * Making the necessary setters. + */ + + +makeSetter('set'); +makeSetter('unset'); +makeSetter('apply', _type["default"]["function"]); +makeSetter('push'); +makeSetter('concat', _type["default"].array); +makeSetter('unshift'); +makeSetter('pop'); +makeSetter('shift'); +makeSetter('splice', _type["default"].splicer); +makeSetter('merge', _type["default"].object); +makeSetter('deepMerge', _type["default"].object); \ No newline at end of file diff --git a/dist/helpers.js b/dist/helpers.js new file mode 100644 index 0000000..32d37b0 --- /dev/null +++ b/dist/helpers.js @@ -0,0 +1,598 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Archive = void 0; +exports.arrayFrom = arrayFrom; +exports.before = before; +exports.coercePath = coercePath; +exports.freeze = exports.deepMerge = exports.deepFreeze = exports.deepClone = void 0; +exports.getIn = getIn; +exports.hashPath = hashPath; +exports.makeError = makeError; +exports.shallowMerge = exports.shallowClone = void 0; +exports.solveRelativePath = solveRelativePath; +exports.solveUpdate = solveUpdate; +exports.splice = splice; +exports.uniqid = void 0; + +var _monkey = require("./monkey"); + +var _type = _interopRequireDefault(require("./type")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var hasOwnProp = {}.hasOwnProperty; +/** + * Function returning the index of the first element of a list matching the + * given predicate. + * + * @param {array} a - The target array. + * @param {function} fn - The predicate function. + * @return {mixed} - The index of the first matching item or -1. + */ + +function index(a, fn) { + var i, l; + + for (i = 0, l = a.length; i < l; i++) { + if (fn(a[i])) return i; + } + + return -1; +} +/** + * Efficient slice function used to clone arrays or parts of them. + * + * @param {array} array - The array to slice. + * @return {array} - The sliced array. + */ + + +function slice(array) { + var newArray = new Array(array.length); + var i, l; + + for (i = 0, l = array.length; i < l; i++) { + newArray[i] = array[i]; + } + + return newArray; +} +/** + * Archive abstraction + * + * @constructor + * @param {integer} size - Maximum number of records to store. + */ + + +var Archive = /*#__PURE__*/function () { + function Archive(size) { + _classCallCheck(this, Archive); + + this.size = size; + this.records = []; + } + /** + * Method retrieving the records. + * + * @return {array} - The records. + */ + + + _createClass(Archive, [{ + key: "get", + value: function get() { + return this.records; + } + /** + * Method adding a record to the archive + * + * @param {object} record - The record to store. + * @return {Archive} - The archive itself for chaining purposes. + */ + + }, { + key: "add", + value: function add(record) { + this.records.unshift(record); // If the number of records is exceeded, we truncate the records + + if (this.records.length > this.size) this.records.length = this.size; + return this; + } + /** + * Method clearing the records. + * + * @return {Archive} - The archive itself for chaining purposes. + */ + + }, { + key: "clear", + value: function clear() { + this.records = []; + return this; + } + /** + * Method to go back in time. + * + * @param {integer} steps - Number of steps we should go back by. + * @return {number} - The last record. + */ + + }, { + key: "back", + value: function back(steps) { + var record = this.records[steps - 1]; + if (record) this.records = this.records.slice(steps); + return record; + } + }]); + + return Archive; +}(); +/** + * Function creating a real array from what should be an array but is not. + * I'm looking at you nasty `arguments`... + * + * @param {mixed} culprit - The culprit to convert. + * @return {array} - The real array. + */ + + +exports.Archive = Archive; + +function arrayFrom(culprit) { + return slice(culprit); +} +/** + * Function decorating one function with another that will be called before the + * decorated one. + * + * @param {function} decorator - The decorating function. + * @param {function} fn - The function to decorate. + * @return {function} - The decorated function. + */ + + +function before(decorator, fn) { + return function () { + decorator.apply(null, arguments); + fn.apply(null, arguments); + }; +} +/** + * Function cloning the given regular expression. Supports `y` and `u` flags + * already. + * + * @param {RegExp} re - The target regular expression. + * @return {RegExp} - The cloned regular expression. + */ + + +function cloneRegexp(re) { + var pattern = re.source; + var flags = ''; + if (re.global) flags += 'g'; + if (re.multiline) flags += 'm'; + if (re.ignoreCase) flags += 'i'; + if (re.sticky) flags += 'y'; + if (re.unicode) flags += 'u'; + return new RegExp(pattern, flags); +} +/** + * Function cloning the given variable. + * + * @todo: implement a faster way to clone an array. + * + * @param {boolean} deep - Should we deep clone the variable. + * @param {mixed} item - The variable to clone + * @return {mixed} - The cloned variable. + */ + + +function cloner(deep, item) { + if (!item || _typeof(item) !== 'object' || item instanceof Error || item instanceof _monkey.MonkeyDefinition || item instanceof _monkey.Monkey || 'ArrayBuffer' in global && item instanceof ArrayBuffer) return item; // Array + + if (_type["default"].array(item)) { + if (deep) { + var a = new Array(item.length); + + for (var i = 0, l = item.length; i < l; i++) { + a[i] = cloner(true, item[i]); + } + + return a; + } + + return slice(item); + } // Date + + + if (item instanceof Date) return new Date(item.getTime()); // RegExp + + if (item instanceof RegExp) return cloneRegexp(item); // Object + + if (_type["default"].object(item)) { + var o = {}; // NOTE: could be possible to erase computed properties through `null`. + + var props = Object.getOwnPropertyNames(item); + + for (var _i = 0, _l = props.length; _i < _l; _i++) { + var name = props[_i]; + var k = Object.getOwnPropertyDescriptor(item, name); + + if (k.enumerable === true) { + if (k.get && k.get.isLazyGetter) { + Object.defineProperty(o, name, { + get: k.get, + enumerable: true, + configurable: true + }); + } else { + o[name] = deep ? cloner(true, item[name]) : item[name]; + } + } else if (k.enumerable === false) { + Object.defineProperty(o, name, { + value: deep ? cloner(true, k.value) : k.value, + enumerable: false, + writable: true, + configurable: true + }); + } + } + + return o; + } + + return item; +} +/** + * Exporting shallow and deep cloning functions. + */ + + +var shallowClone = cloner.bind(null, false), + deepClone = cloner.bind(null, true); +exports.deepClone = deepClone; +exports.shallowClone = shallowClone; + +/** + * Coerce the given variable into a full-fledged path. + * + * @param {mixed} target - The variable to coerce. + * @return {array} - The array path. + */ +function coercePath(target) { + if (target || target === 0 || target === '') return target; + return []; +} +/** + * Function comparing an object's properties to a given descriptive + * object. + * + * @param {object} object - The object to compare. + * @param {object} description - The description's mapping. + * @return {boolean} - Whether the object matches the description. + */ + + +function compare(object, description) { + var ok = true, + k; // If we reached here via a recursive call, object may be undefined because + // not all items in a collection will have the same deep nesting structure. + + if (!object) return false; + + for (k in description) { + if (_type["default"].object(description[k])) { + ok = ok && compare(object[k], description[k]); + } else if (_type["default"].array(description[k])) { + ok = ok && !!~description[k].indexOf(object[k]); + } else { + if (object[k] !== description[k]) return false; + } + } + + return ok; +} +/** + * Function freezing the given variable if possible. + * + * @param {boolean} deep - Should we recursively freeze the given objects? + * @param {object} o - The variable to freeze. + * @return {object} - The merged object. + */ + + +function freezer(deep, o) { + if (_typeof(o) !== 'object' || o === null || o instanceof _monkey.Monkey) return; + Object.freeze(o); + if (!deep) return; + + if (Array.isArray(o)) { + // Iterating through the elements + var i, l; + + for (i = 0, l = o.length; i < l; i++) { + deepFreeze(o[i]); + } + } else { + var p, k; + + for (k in o) { + if (_type["default"].lazyGetter(o, k)) continue; + p = o[k]; + if (!p || !hasOwnProp.call(o, k) || _typeof(p) !== 'object' || Object.isFrozen(p)) continue; + deepFreeze(p); + } + } +} + +var freeze = freezer.bind(null, false), + deepFreeze = freezer.bind(null, true); +exports.deepFreeze = deepFreeze; +exports.freeze = freeze; + +/** + * Function retrieving nested data within the given object and according to + * the given path. + * + * @todo: work if dynamic path hit objects also. + * @todo: memoized perfgetters. + * + * @param {object} object - The object we need to get data from. + * @param {array} path - The path to follow. + * @return {object} result - The result. + * @return {mixed} result.data - The data at path, or `undefined`. + * @return {array} result.solvedPath - The solved path or `null`. + * @return {boolean} result.exists - Does the path exists in the tree? + */ +var NOT_FOUND_OBJECT = { + data: undefined, + solvedPath: null, + exists: false +}; + +function getIn(object, path) { + if (!path) return NOT_FOUND_OBJECT; + var solvedPath = []; + var exists = true, + c = object, + idx, + i, + l; + + for (i = 0, l = path.length; i < l; i++) { + if (!c) return { + data: undefined, + solvedPath: solvedPath.concat(path.slice(i)), + exists: false + }; + + if (typeof path[i] === 'function') { + if (!_type["default"].array(c)) return NOT_FOUND_OBJECT; + idx = index(c, path[i]); + if (!~idx) return NOT_FOUND_OBJECT; + solvedPath.push(idx); + c = c[idx]; + } else if (_typeof(path[i]) === 'object') { + if (!_type["default"].array(c)) return NOT_FOUND_OBJECT; + idx = index(c, function (e) { + return compare(e, path[i]); + }); + if (!~idx) return NOT_FOUND_OBJECT; + solvedPath.push(idx); + c = c[idx]; + } else { + solvedPath.push(path[i]); + exists = _typeof(c) === 'object' && path[i] in c; + c = c[path[i]]; + } + } + + return { + data: c, + solvedPath: solvedPath, + exists: exists + }; +} +/** + * Little helper returning a JavaScript error carrying some data with it. + * + * @param {string} message - The error message. + * @param {object} [data] - Optional data to assign to the error. + * @return {Error} - The created error. + */ + + +function makeError(message, data) { + var err = new Error(message); + + for (var k in data) { + err[k] = data[k]; + } + + return err; +} +/** + * Function taking n objects to merge them together. + * Note 1): the latter object will take precedence over the first one. + * Note 2): the first object will be mutated to allow for perf scenarios. + * Note 3): this function will consider monkeys as leaves. + * + * @param {boolean} deep - Whether the merge should be deep or not. + * @param {...object} objects - Objects to merge. + * @return {object} - The merged object. + */ + + +function merger(deep) { + for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + objects[_key - 1] = arguments[_key]; + } + + var o = objects[0]; + var t, i, l, k; + + for (i = 1, l = objects.length; i < l; i++) { + t = objects[i]; + + for (k in t) { + if (deep && _type["default"].object(t[k]) && !(t[k] instanceof _monkey.Monkey) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') { + o[k] = merger(true, o[k] || {}, t[k]); + } else { + o[k] = t[k]; + } + } + } + + return o; +} +/** + * Exporting both `shallowMerge` and `deepMerge` functions. + */ + + +var shallowMerge = merger.bind(null, false), + deepMerge = merger.bind(null, true); +exports.deepMerge = deepMerge; +exports.shallowMerge = shallowMerge; + +/** + * Function returning a string hash from a non-dynamic path expressed as an + * array. + * + * @param {array} path - The path to hash. + * @return {string} string - The resultant hash. + */ +function hashPath(path) { + return 'λ' + path.map(function (step) { + if (_type["default"]["function"](step) || _type["default"].object(step)) return "#".concat(uniqid(), "#"); + return step; + }).join('λ'); +} +/** + * Solving a potentially relative path. + * + * @param {array} base - The base path from which to solve the path. + * @param {array} to - The subpath to reach. + * @param {array} - The solved absolute path. + */ + + +function solveRelativePath(base, to) { + var solvedPath = []; // Coercing to array + + to = [].concat(to); + + for (var i = 0, l = to.length; i < l; i++) { + var step = to[i]; + + if (step === '.') { + if (!i) solvedPath = base.slice(0); + } else if (step === '..') { + solvedPath = (!i ? base : solvedPath).slice(0, -1); + } else { + solvedPath.push(step); + } + } + + return solvedPath; +} +/** + * Function determining whether some paths in the tree were affected by some + * updates that occurred at the given paths. This helper is mainly used at + * cursor level to determine whether the cursor is concerned by the updates + * fired at tree level. + * + * NOTES: 1) If performance become an issue, the following threefold loop + * can be simplified to a complex twofold one. + * 2) A regex version could also work but I am not confident it would + * be faster. + * 3) Another solution would be to keep a register of cursors like with + * the monkeys and update along this tree. + * + * @param {array} affectedPaths - The paths that were updated. + * @param {array} comparedPaths - The paths that we are actually interested in. + * @return {boolean} - Is the update relevant to the compared + * paths? + */ + + +function solveUpdate(affectedPaths, comparedPaths) { + var i, j, k, l, m, n, p, c, s; // Looping through possible paths + + for (i = 0, l = affectedPaths.length; i < l; i++) { + p = affectedPaths[i]; + if (!p.length) return true; // Looping through logged paths + + for (j = 0, m = comparedPaths.length; j < m; j++) { + c = comparedPaths[j]; + if (!c || !c.length) return true; // Looping through steps + + for (k = 0, n = c.length; k < n; k++) { + s = c[k]; // If path is not relevant, we break + // NOTE: the '!=' instead of '!==' is required here! + + if (s != p[k]) break; // If we reached last item and we are relevant + + if (k + 1 === n || k + 1 === p.length) return true; + } + } + } + + return false; +} +/** + * Non-mutative version of the splice array method. + * + * @param {array} array - The array to splice. + * @param {integer} startIndex - The start index. + * @param {integer} nb - Number of elements to remove. + * @param {...mixed} elements - Elements to append after splicing. + * @return {array} - The spliced array. + */ + + +function splice(array, startIndex, nb) { + for (var _len2 = arguments.length, elements = new Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) { + elements[_key2 - 3] = arguments[_key2]; + } + + if (nb === undefined && arguments.length === 2) nb = array.length - startIndex;else if (nb === null || nb === undefined) nb = 0;else if (isNaN(+nb)) throw new Error("argument nb ".concat(nb, " can not be parsed into a number!")); + nb = Math.max(0, nb); // Solving startIndex + + if (_type["default"]["function"](startIndex)) startIndex = index(array, startIndex); + if (_type["default"].object(startIndex)) startIndex = index(array, function (e) { + return compare(e, startIndex); + }); // Positive index + + if (startIndex >= 0) return array.slice(0, startIndex).concat(elements).concat(array.slice(startIndex + nb)); // Negative index + + return array.slice(0, array.length + startIndex).concat(elements).concat(array.slice(array.length + startIndex + nb)); +} +/** + * Function returning a unique incremental id each time it is called. + * + * @return {integer} - The latest unique id. + */ + + +var uniqid = function () { + var i = 0; + return function () { + return i++; + }; +}(); + +exports.uniqid = uniqid; \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..b654207 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,4 @@ +export * from './baobab'; +export * from './sbaobab'; +import Baobab from './baobab'; +export default Baobab; \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..e1cf757 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,44 @@ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _exportNames = {}; +exports["default"] = void 0; + +var _baobab = _interopRequireWildcard(require("./baobab")); + +Object.keys(_baobab).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _baobab[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _baobab[key]; + } + }); +}); + +var _sbaobab = require("./sbaobab"); + +Object.keys(_sbaobab).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _sbaobab[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _sbaobab[key]; + } + }); +}); + +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } + +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +var _default = _baobab["default"]; +exports["default"] = _default; \ No newline at end of file diff --git a/dist/monkey.js b/dist/monkey.js new file mode 100644 index 0000000..48852dc --- /dev/null +++ b/dist/monkey.js @@ -0,0 +1,265 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MonkeyDefinition = exports.Monkey = void 0; + +var _type = _interopRequireDefault(require("./type")); + +var _update2 = _interopRequireDefault(require("./update")); + +var _helpers = require("./helpers"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/** + * Monkey Definition class + * Note: The only reason why this is a class is to be able to spot it within + * otherwise ordinary data. + * + * @constructor + * @param {array|object} definition - The formal definition of the monkey. + */ +var MonkeyDefinition = function MonkeyDefinition(definition) { + var _this = this; + + _classCallCheck(this, MonkeyDefinition); + + var monkeyType = _type["default"].monkeyDefinition(definition); + + if (!monkeyType) throw (0, _helpers.makeError)('Baobab.monkey: invalid definition.', { + definition: definition + }); + this.type = monkeyType; + + if (this.type === 'object') { + this.getter = definition.get; + this.projection = definition.cursors || {}; + this.paths = Object.keys(this.projection).map(function (k) { + return _this.projection[k]; + }); + this.options = definition.options || {}; + } else { + var offset = 1, + options = {}; + + if (_type["default"].object(definition[definition.length - 1])) { + offset++; + options = definition[definition.length - 1]; + } + + this.getter = definition[definition.length - offset]; + this.projection = definition.slice(0, -offset); + this.paths = this.projection; + this.options = options; + } // Coercing paths for convenience + + + this.paths = this.paths.map(function (p) { + return [].concat(p); + }); // Does the definition contain dynamic paths + + this.hasDynamicPaths = this.paths.some(_type["default"].dynamicPath); +}; +/** + * Monkey core class + * + * @constructor + * @param {Baobab} tree - The bound tree. + * @param {MonkeyDefinition} definition - A definition instance. + */ + + +exports.MonkeyDefinition = MonkeyDefinition; + +var Monkey = /*#__PURE__*/function () { + function Monkey(tree, pathInTree, definition) { + var _this2 = this; + + _classCallCheck(this, Monkey); + + // Properties + this.tree = tree; + this.path = pathInTree; + this.definition = definition; // Adapting the definition's paths & projection to this monkey's case + + var projection = definition.projection, + relative = _helpers.solveRelativePath.bind(null, pathInTree.slice(0, -1)); + + if (definition.type === 'object') { + this.projection = Object.keys(projection).reduce(function (acc, k) { + acc[k] = relative(projection[k]); + return acc; + }, {}); + this.depPaths = Object.keys(this.projection).map(function (k) { + return _this2.projection[k]; + }); + } else { + this.projection = projection.map(relative); + this.depPaths = this.projection; + } // Internal state + + + this.state = { + killed: false + }; + /** + * Listener on the tree's `write` event. + * + * When the tree writes, this listener will check whether the updated paths + * are of any use to the monkey and, if so, will update the tree's node + * where the monkey sits. + */ + + this.writeListener = function (_ref) { + var path = _ref.data.path; + if (_this2.state.killed) return; // Is the monkey affected by the current write event? + + var concerned = (0, _helpers.solveUpdate)([path], _this2.relatedPaths()); + if (concerned) _this2.update(); + }; + /** + * Listener on the tree's `monkey` event. + * + * When another monkey updates, this listener will check whether the + * updated paths are of any use to the monkey and, if so, will update the + * tree's node where the monkey sits. + */ + + + this.recursiveListener = function (_ref2) { + var _ref2$data = _ref2.data, + monkey = _ref2$data.monkey, + path = _ref2$data.path; + if (_this2.state.killed) return; // Breaking if this is the same monkey + + if (_this2 === monkey) return; // Is the monkey affected by the current monkey event? + + var concerned = (0, _helpers.solveUpdate)([path], _this2.relatedPaths(false)); + if (concerned) _this2.update(); + }; // Binding listeners + + + this.tree.on('write', this.writeListener); + this.tree.on('_monkey', this.recursiveListener); // Updating relevant node + + this.update(); + } + /** + * Method returning solved paths related to the monkey. + * + * @param {boolean} recursive - Should we compute recursive paths? + * @return {array} - An array of related paths. + */ + + + _createClass(Monkey, [{ + key: "relatedPaths", + value: function relatedPaths() { + var _this3 = this; + + var recursive = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + var paths; + if (this.definition.hasDynamicPaths) paths = this.depPaths.map(function (p) { + return (0, _helpers.getIn)(_this3.tree._data, p).solvedPath; + });else paths = this.depPaths; + var isRecursive = recursive && this.depPaths.some(function (p) { + return !!_type["default"].monkeyPath(_this3.tree._monkeys, p); + }); + if (!isRecursive) return paths; + return paths.reduce(function (accumulatedPaths, path) { + var monkeyPath = _type["default"].monkeyPath(_this3.tree._monkeys, path); + + if (!monkeyPath) return accumulatedPaths.concat([path]); // Solving recursive path + + var relatedMonkey = (0, _helpers.getIn)(_this3.tree._monkeys, monkeyPath).data; + return accumulatedPaths.concat(relatedMonkey.relatedPaths()); + }, []); + } + /** + * Method used to update the tree's internal data with a lazy getter holding + * the computed data. + * + * @return {Monkey} - Returns itself for chaining purposes. + */ + + }, { + key: "update", + value: function update() { + var _this4 = this; + + var deps = this.tree.project(this.projection); + + var lazyGetter = function (tree, def, data) { + var cache = null, + alreadyComputed = false; + return function () { + if (!alreadyComputed) { + cache = def.getter.apply(tree, def.type === 'object' ? [data] : data); + if (tree.options.immutable && def.options.immutable !== false) (0, _helpers.deepFreeze)(cache); // update tree affected paths + + var hash = (0, _helpers.hashPath)(_this4.path); + tree._affectedPathsIndex[hash] = true; + alreadyComputed = true; + } + + return cache; + }; + }(this.tree, this.definition, deps); + + lazyGetter.isLazyGetter = true; // Should we write the lazy getter in the tree or solve it right now? + + if (this.tree.options.lazyMonkeys) { + this.tree._data = (0, _update2["default"])(this.tree._data, this.path, { + type: 'monkey', + value: lazyGetter + }, this.tree.options).data; + } else { + var result = (0, _update2["default"])(this.tree._data, this.path, { + type: 'set', + value: lazyGetter(), + options: { + mutableLeaf: !this.definition.options.immutable + } + }, this.tree.options); + if ('data' in result) this.tree._data = result.data; + } // Notifying the monkey's update so we can handle recursivity + + + this.tree.emit('_monkey', { + monkey: this, + path: this.path + }); + return this; + } + /** + * Method releasing the monkey from memory. + */ + + }, { + key: "release", + value: function release() { + // Unbinding events + this.tree.off('write', this.writeListener); + this.tree.off('_monkey', this.recursiveListener); + this.state.killed = true; // Deleting properties + // NOTE: not deleting this.definition because some strange things happen + // in the _refreshMonkeys method. See #372. + + delete this.projection; + delete this.depPaths; + delete this.tree; + } + }]); + + return Monkey; +}(); + +exports.Monkey = Monkey; \ No newline at end of file diff --git a/dist/sbaobab.d.ts b/dist/sbaobab.d.ts new file mode 100644 index 0000000..0f5e2ec --- /dev/null +++ b/dist/sbaobab.d.ts @@ -0,0 +1,242 @@ +/** Stricter and more informative types for Baobab. Otherwise identical. + * TODO? Putting a couple more specific overloads of each function might improve client typechecking performance? +*/ +import Emitter from 'emmett'; +import {BaobabOptions, MonkeyDefinition, MonkeyOptions} from './baobab'; +import type {DeepPartial, DI, DP, Im, ImDI, HeadOf} from './util'; + +interface PlainObject { + [key: string]: T; +} + +type SimplePath = (string | number)[]; +type SP = SimplePath; + + +type Splicer = [number] | [number, number] | [number, number, (oldVals: T) => T] | [number, number, ...T]; +/** + * This class only exists to group methods that are common to the Baobab and + * Cursor classes. Since `Baobab.root` is a property while `Cursor#root` is a + * method, Baobab cannot extend Cursor. + */ +export abstract class SCommonBaobabMethods extends Emitter { + + + //TODO?: problematic overload? https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#use-union-types + apply(getNew: (state: T) => T): T; + apply(key: K, getNew: (state: Im) => Im): Im; + apply

>(path: P, getNew: (state: ImDI) => ImDI): ImDI; + + clone

>(...path: P): ImDI; + clone

>(path: DI): ImDI; + + // TODO?: could type guard for all array methods (concat, push, shift, splice, unshift) (not pop) + concat(value: T): Im; + concat

>(path: P, value: DI): ImDI; + + deepClone

>(...args: P): ImDI; + deepClone

>(path: P): ImDI; + + deepMerge(value: DeepPartial): Im; + deepMerge(path: K, value: DeepPartial): Im; + deepMerge

>(path: P, value: DeepPartial>): ImDI; + + exists

>(...args: P): ImDI; + exists

>(path: P): ImDI; + + get(): Im; + get(key: K): Im; + get

>(...path: P): ImDI; + get

>(path: P): ImDI; + + merge(value: Partial): Im; + merge(path: K, value: Partial): Im; + merge

>(path: P, value: Partial>): ImDI; + + // TODO?: could type guard only allow popping optional values + pop(): Im; + pop(key: K): Im; + pop

>(path: P): ImDI; + + project(projection: Record>): Record>; // TODO + project[]>(projection: Proj): unknown[]; + + push(value: T[number]): Im; + push(key: K, value: T[K][number]): Im; + push

>(path: P, value: DI[number]): ImDI; + + release(): void; + + serialize

>(...args: P): string; + serialize

>(path: P): string; + + set(value: T): Im; + set(key: K, value: T[K]): T[K]; + set

>(path: P, value: DI): ImDI; + + shift(): Im; + shift(key: K): T[K]; + shift

>(path: P, value: DI[number]): ImDI; + + splice(value: Splicer): Im; + splice

>(path: P, value: Splicer>): ImDI; + + unset(): void; + unset(key: K): void; + unset

>(path: P): void; + + unshift(value: T[number]): Im; + shift(key: K, value: T[K][number]): T[K]; + unshift

>(path: P, value: DI[number]): ImDI; + + +} + +export class SWatcher> | Record> extends Emitter { + // TODO?: initialized with cursors + constructor(tree: SBaobab, mapping: Mapping); + + get(): Mapping extends Record ? {[Name in keyof Mapping]: T[Mapping[Name]]} : {[Name in keyof Mapping]: ImDI}; + getWatchedPaths(): Mapping[keyof Mapping]; + getCursors(): unknown; // TODO + refresh(mappings: Mapping): void; + release(): void; +} + +/** + * @constructor + * @param {Baobab} tree - The bound tree. + * @param {MonkeyDefinition} definition - A definition instance. + */ +export class SMonkey { + constructor(tree: SBaobab, pathInTree: SimplePath, definition: MonkeyDefinition); + relatedPaths(recursive: boolean): SimplePath[]; + update(): SMonkey; + release(): void; +} + + +export class SCursor = unknown, Root = unknown> extends SCommonBaobabMethods implements Iterable { + constructor(tree: SBaobab, path: FullPath, hash: string); + path?: FullPath; + solvedPath?: SimplePath; + state: { + killed: boolean; + recording: boolean; + undoing: boolean; + }; + + [Symbol.iterator](): IterableIterator; + // Navigation: + up(): SCursor>, HeadOf, Root>; + down(): SCursor, [...FullPath, number], Root>; + left(): SCursor | null; + right(): SCursor | null; + leftmost(): SCursor | null; + rightmost(): SCursor | null; + root(): SCursor; + + // Predicates: + isLeaf(): boolean; + isRoot(): boolean; + isBranch(): boolean; + + // History: + hasHistory(): boolean; + getHistory(): any[]; + clearHistory(): this; + startRecording(maxRecords?: number): this; + stopRecording(): this; + undo(steps?: number): this; + + // Others: + toJSON(): string; + toString(): string; + + map(fn: (v: SCursor, index: number) => Return, scope?: any): Return[]; + on(name: 'update', handler: (e: {data: {currentData: T, previousData: T;};}) => void): this; + + + select(key: K): SCursor; + select

>(...path: P): SCursor, [...FullPath, ...P], Root>; // TODO: forbid empty path + select

>(path: P): SCursor, [...FullPath, ...P], Root>; + + +} + +/** Stricter and more informative types for Baobab. Otherwise identical. */ +export class SBaobab extends SCommonBaobabMethods { + constructor(initialState?: T, options?: Partial); + debugType: T; + + root: SCursor; + options: BaobabOptions; + + update( + path: SP, + operation: { + type: string, + value: any, + options?: { + mutableLeaf?: boolean; + }; + } + ): this; + + commit(): this; + + getMonkey(path: SP): SMonkey; + + watch> | Record>(mappings: Mappings): SWatcher; + + static monkey(definition: {cursors?: PlainObject; get(data: PlainObject): any; options?: MonkeyOptions;}): MonkeyDefinition; + + /* tslint:disable:unified-signatures */ + // Polymorphisms for: + // `.monkey(...paths: Path[], get: (v1: any) => any)` + static monkey(path1: SP, get: (value: any) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: SP, path2: SP, get: (...values: [any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: SP, path2: SP, path3: SP, get: (...values: [any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: SP, path2: SP, path3: SP, path4: SP, get: (...values: [any, any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: SP, path2: SP, path3: SP, path4: SP, path5: SP, get: (...values: [any, any, any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + // Fallback: + static monkey(...pathsEndingWithGetAndMaybeOptions: (SP | ((...values: any[]) => any) | MonkeyOptions)[]): MonkeyDefinition; + + // Polymorphisms for: + // `.monkey(definition: [...paths: SP[], get: (v1: any) => any])` + static monkey(args: [SP, (value: any) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [SP, SP, (...values: [any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [SP, SP, SP, (...values: [any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [SP, SP, SP, SP, (...values: [any, any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [SP, SP, SP, SP, SP, (...values: [any, any, any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + // Fallback: + static monkey(pathsEndingWithGet: (SP | ((...values: any[]) => any) | MonkeyOptions)[]): MonkeyDefinition; + /* tslint:enable:unified-signatures */ + + static dynamicNode: typeof SBaobab.monkey; + + on(name: 'write', handler: (e: {data: {path: SimplePath;};}) => void): this; + on(name: 'invalid', handler: (e: {data: {error: unknown;};}) => void): this; + on(name: 'get', handler: (e: {data: {path: unknown, solvedPath: SimplePath, data: unknown;};}) => void): this; + on(name: 'select', handler: (e: {data: {path: unknown, cursor: unknown;};}) => void): this; + on(name: 'update', handler: (e: {data: {currentData: T, previousData: T; transaction: unknown; paths: unknown;};}) => void): this; + + select(): SCursor; + select(key: K): SCursor; + select

>(...path: P): SCursor, [...FullPath, ...P], T>; + select

>(path: P): SCursor, [...FullPath, ...P], T>; + +} + +export type ROBaobab = Pick, 'clone' | 'deepClone' | 'exists' | 'watch' | 'serialize' | 'project' | 'options' | 'get' | 'getMonkey' | 'on'> & { + select(): ROCursor; + select(key: K): ROCursor; + select

>(...path: P): ROCursor, [...FullPath, ...P], T>; + select

>(path: P): ROCursor, [...FullPath, ...P], T>; +}; + +export type ROCursor = unknown, Root = unknown> = Pick, 'clone' | 'deepClone' | 'exists' | 'serialize' | 'project' | 'get' | 'on'> & { + select(key: K): ROCursor; + select

>(...path: P): ROCursor, [...FullPath, ...P], Root>; // TODO: forbid empty path + select

>(path: P): ROCursor, [...FullPath, ...P], Root>; +}; diff --git a/dist/sbaobab.js b/dist/sbaobab.js new file mode 100644 index 0000000..74e0c61 --- /dev/null +++ b/dist/sbaobab.js @@ -0,0 +1,70 @@ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SCursor = exports.SBaobab = void 0; + +var _baobab = _interopRequireDefault(require("./baobab")); + +var _cursor = _interopRequireDefault(require("./cursor")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +var SBaobab = /*#__PURE__*/function (_Baobab) { + _inherits(SBaobab, _Baobab); + + var _super = _createSuper(SBaobab); + + function SBaobab() { + _classCallCheck(this, SBaobab); + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return _super.call.apply(_super, [this].concat(args)); + } + + return SBaobab; +}(_baobab["default"]); + +exports.SBaobab = SBaobab; + +var SCursor = /*#__PURE__*/function (_Cursor) { + _inherits(SCursor, _Cursor); + + var _super2 = _createSuper(SCursor); + + function SCursor() { + _classCallCheck(this, SCursor); + + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + return _super2.call.apply(_super2, [this].concat(args)); + } + + return SCursor; +}(_cursor["default"]); + +exports.SCursor = SCursor; \ No newline at end of file diff --git a/dist/type.js b/dist/type.js new file mode 100644 index 0000000..1bee78d --- /dev/null +++ b/dist/type.js @@ -0,0 +1,246 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = void 0; + +var _monkey = require("./monkey"); + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +var type = {}; +/** + * Helpers + * -------- + */ + +/** + * Checking whether the given variable is of any of the given types. + * + * @todo Optimize this function by dropping `some`. + * + * @param {mixed} target - Variable to test. + * @param {array} allowed - Array of allowed types. + * @return {boolean} + */ + +function anyOf(target, allowed) { + return allowed.some(function (t) { + return type[t](target); + }); +} +/** + * Simple types + * ------------- + */ + +/** + * Checking whether the given variable is an array. + * + * @param {mixed} target - Variable to test. + * @return {boolean} + */ + + +type.array = function (target) { + return Array.isArray(target); +}; +/** + * Checking whether the given variable is an object. + * + * @param {mixed} target - Variable to test. + * @return {boolean} + */ + + +type.object = function (target) { + return target && _typeof(target) === 'object' && !Array.isArray(target) && !(target instanceof Date) && !(target instanceof RegExp) && !(typeof Map === 'function' && target instanceof Map) && !(typeof Set === 'function' && target instanceof Set); +}; +/** + * Checking whether the given variable is a string. + * + * @param {mixed} target - Variable to test. + * @return {boolean} + */ + + +type.string = function (target) { + return typeof target === 'string'; +}; +/** + * Checking whether the given variable is a number. + * + * @param {mixed} target - Variable to test. + * @return {boolean} + */ + + +type.number = function (target) { + return typeof target === 'number'; +}; +/** + * Checking whether the given variable is a function. + * + * @param {mixed} target - Variable to test. + * @return {boolean} + */ + + +type["function"] = function (target) { + return typeof target === 'function'; +}; +/** + * Checking whether the given variable is a JavaScript primitive. + * + * @param {mixed} target - Variable to test. + * @return {boolean} + */ + + +type.primitive = function (target) { + return target !== Object(target); +}; +/** + * Complex types + * -------------- + */ + +/** + * Checking whether the given variable is a valid splicer. + * + * @param {mixed} target - Variable to test. + * @param {array} [allowed] - Optional valid types in path. + * @return {boolean} + */ + + +type.splicer = function (target) { + if (!type.array(target) || target.length < 1) return false; + if (target.length > 1 && isNaN(+target[1])) return false; + return anyOf(target[0], ['number', 'function', 'object']); +}; +/** + * Checking whether the given variable is a valid cursor path. + * + * @param {mixed} target - Variable to test. + * @param {array} [allowed] - Optional valid types in path. + * @return {boolean} + */ +// Order is important for performance reasons + + +var ALLOWED_FOR_PATH = ['string', 'number', 'function', 'object']; + +type.path = function (target) { + if (!target && target !== 0 && target !== '') return false; + return [].concat(target).every(function (step) { + return anyOf(step, ALLOWED_FOR_PATH); + }); +}; +/** + * Checking whether the given path is a dynamic one. + * + * @param {mixed} path - The path to test. + * @return {boolean} + */ + + +type.dynamicPath = function (path) { + return path.some(function (step) { + return type["function"](step) || type.object(step); + }); +}; +/** + * Retrieve any monkey subpath in the given path or null if the path never comes + * across computed data. + * + * @param {mixed} data - The data to test. + * @param {array} path - The path to test. + * @return {boolean} + */ + + +type.monkeyPath = function (data, path) { + var subpath = []; + var c = data, + i, + l; + + for (i = 0, l = path.length; i < l; i++) { + subpath.push(path[i]); + if (_typeof(c) !== 'object') return null; + c = c[path[i]]; + if (c instanceof _monkey.Monkey) return subpath; + } + + return null; +}; +/** + * Check if the given object property is a lazy getter used by a monkey. + * + * @param {mixed} o - The target object. + * @param {string} propertyKey - The property to test. + * @return {boolean} + */ + + +type.lazyGetter = function (o, propertyKey) { + var descriptor = Object.getOwnPropertyDescriptor(o, propertyKey); + return descriptor && descriptor.get && descriptor.get.isLazyGetter === true; +}; +/** + * Returns the type of the given monkey definition or `null` if invalid. + * + * @param {mixed} definition - The definition to check. + * @return {string|null} + */ + + +type.monkeyDefinition = function (definition) { + if (type.object(definition)) { + if (!type["function"](definition.get) || definition.cursors && (!type.object(definition.cursors) || !Object.keys(definition.cursors).every(function (k) { + return type.path(definition.cursors[k]); + }))) return null; + return 'object'; + } else if (type.array(definition)) { + var offset = 1; + if (type.object(definition[definition.length - 1])) offset++; + if (!type["function"](definition[definition.length - offset]) || !definition.slice(0, -offset).every(function (p) { + return type.path(p); + })) return null; + return 'array'; + } + + return null; +}; +/** + * Checking whether the given watcher definition is valid. + * + * @param {mixed} definition - The definition to check. + * @return {boolean} + */ + + +type.watcherMapping = function (definition) { + return type.object(definition) && Object.keys(definition).every(function (k) { + return type.path(definition[k]); + }); +}; +/** + * Checking whether the given string is a valid operation type. + * + * @param {mixed} string - The string to test. + * @return {boolean} + */ +// Ordered by likeliness + + +var VALID_OPERATIONS = ['set', 'apply', 'push', 'unshift', 'concat', 'pop', 'shift', 'deepMerge', 'merge', 'splice', 'unset']; + +type.operationType = function (string) { + return typeof string === 'string' && !!~VALID_OPERATIONS.indexOf(string); +}; + +var _default = type; +exports["default"] = _default; \ No newline at end of file diff --git a/dist/update.js b/dist/update.js new file mode 100644 index 0000000..1d3ca93 --- /dev/null +++ b/dist/update.js @@ -0,0 +1,211 @@ +"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) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } + +function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } + +function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; 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] + }; +} \ No newline at end of file diff --git a/dist/util.d.ts b/dist/util.d.ts new file mode 100644 index 0000000..586c48a --- /dev/null +++ b/dist/util.d.ts @@ -0,0 +1,88 @@ +// https://stackoverflow.com/a/58993872/4941530 + +// eslint-disable-next-line @typescript-eslint/ban-types +type ImmutablePrimitive = undefined | null | boolean | string | number | Function; + +export type Immutable = + T extends ImmutablePrimitive ? T : + T extends Array ? ImmutableArray : + T extends Map ? ImmutableMap : + T extends Set ? ImmutableSet : ImmutableObject; + +type ImmutableArray = ReadonlyArray>; +type ImmutableMap = ReadonlyMap, Immutable>; +type ImmutableSet = ReadonlySet>; +type ImmutableObject = {readonly [K in keyof T]: Immutable}; + + +type Vals = T[keyof T]; +// https://stackoverflow.com/questions/58434389 +// type PathsOf = +// T extends object ? +// T extends Array ? +// [] | [number, ...PathsOf] : +// Vals<{[P in keyof T]-?: [] | [P, ...PathsOf]}> : +// []; + + +type Predicate = (data: T) => boolean; +type Constraint = Partial; + +type FullPathsOf = + T extends object ? + T extends Array ? + [] | [number, ...FullPathsOf] | [Predicate, ...FullPathsOf] | (Item extends object ? [Constraint, ...FullPathsOf] : []) : + Vals<{[P in keyof T]-?: [] | [P, ...FullPathsOf]}> : + []; + +// https://stackoverflow.com/a/61648690 +// type DeepIndex = +// KS extends [infer F, ...infer R] ? +// F extends keyof T ? +// R extends PropertyKey[] ? +// DeepIndex : +// Fail : +// Fail : +// T; + +interface Fail1 {} +interface Fail2 {} +interface Fail3 {} + +type Obj = Record; +export type FullKeys = (PropertyKey | Obj | Function)[]; +export type FullDeepIndex = + KS extends [infer Keyish, ...infer Rest] // A: have a key? + ? Rest extends FullKeys // B: rest is an array? + ? Keyish extends Obj | Function // C: key is special? + ? T extends Array // D: value is array? + ? FullDeepIndex // descend with special key. + : Fail1 // not D: special key on non-array value + : Keyish extends keyof T // (not C) E: key is valid + ? FullDeepIndex // descend with regular key + : Fail2 // not E: invalid key + : Fail3 // not B: not an array at all + : T; // not A: stop descending + +// TODO: immutable types causing tons problems in other packages using baobab +// export type Im = Immutable; +export type Im = T; +// export type ImDI = Immutable>; +export type ImDI = FullDeepIndex; +export type DP = FullPathsOf; // TODO? as FullKeys +export type DI = FullDeepIndex; + + +export type DeepPartial = { + [P in keyof T]?: DeepPartial; +}; + +interface HeadOfErr {} +export type HeadOf = T extends [...infer Head, infer Last] ? Head : HeadOfErr; + +export type IfEquals = + (() => G extends T ? 1 : 2) extends + (() => G extends U ? 1 : 2) ? Y : N; + +type Foo = 'blarg' | 'smarf'; +type IMFoo = Immutable; \ No newline at end of file diff --git a/dist/watcher.js b/dist/watcher.js new file mode 100644 index 0000000..abbe959 --- /dev/null +++ b/dist/watcher.js @@ -0,0 +1,168 @@ +"use strict"; + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = void 0; + +var _emmett = _interopRequireDefault(require("emmett")); + +var _cursor = _interopRequireDefault(require("./cursor")); + +var _type = _interopRequireDefault(require("./type")); + +var _helpers = require("./helpers"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +/** + * Watcher class. + * + * @constructor + * @param {Baobab} tree - The watched tree. + * @param {object} mapping - A mapping of the paths to watch in the tree. + */ +var Watcher = /*#__PURE__*/function (_Emitter) { + _inherits(Watcher, _Emitter); + + var _super = _createSuper(Watcher); + + function Watcher(tree, mapping) { + var _this; + + _classCallCheck(this, Watcher); + + _this = _super.call(this); // Properties + + _this.tree = tree; + _this.mapping = null; + _this.state = { + killed: false + }; // Initializing + + _this.refresh(mapping); // Listening + + + _this.handler = function (e) { + if (_this.state.killed) return; + + var watchedPaths = _this.getWatchedPaths(); + + if ((0, _helpers.solveUpdate)(e.data.paths, watchedPaths)) return _this.emit('update'); + }; + + _this.tree.on('update', _this.handler); + + return _this; + } + /** + * Method used to get the current watched paths. + * + * @return {array} - The array of watched paths. + */ + + + _createClass(Watcher, [{ + key: "getWatchedPaths", + value: function getWatchedPaths() { + var _this2 = this; + + var rawPaths = Object.keys(this.mapping).map(function (k) { + var v = _this2.mapping[k]; // Watcher mappings can accept a cursor + + if (v instanceof _cursor["default"]) return v.solvedPath; + return _this2.mapping[k]; + }); + return rawPaths.reduce(function (cp, p) { + // Handling path polymorphisms + p = [].concat(p); // Dynamic path? + + if (_type["default"].dynamicPath(p)) p = (0, _helpers.getIn)(_this2.tree._data, p).solvedPath; + if (!p) return cp; // Facet path? + + var monkeyPath = _type["default"].monkeyPath(_this2.tree._monkeys, p); + + if (monkeyPath) return cp.concat((0, _helpers.getIn)(_this2.tree._monkeys, monkeyPath).data.relatedPaths()); + return cp.concat([p]); + }, []); + } + /** + * Method used to return a map of the watcher's cursors. + * + * @return {object} - TMap of relevant cursors. + */ + + }, { + key: "getCursors", + value: function getCursors() { + var _this3 = this; + + var cursors = {}; + Object.keys(this.mapping).forEach(function (k) { + var path = _this3.mapping[k]; + if (path instanceof _cursor["default"]) cursors[k] = path;else cursors[k] = _this3.tree.select(path); + }); + return cursors; + } + /** + * Method used to refresh the watcher's mapping. + * + * @param {object} mapping - The new mapping to apply. + * @return {Watcher} - Itself for chaining purposes. + */ + + }, { + key: "refresh", + value: function refresh(mapping) { + if (!_type["default"].watcherMapping(mapping)) throw (0, _helpers.makeError)('Baobab.watch: invalid mapping.', { + mapping: mapping + }); + this.mapping = mapping; // Creating the get method + + var projection = {}; + + for (var k in mapping) { + projection[k] = mapping[k] instanceof _cursor["default"] ? mapping[k].path : mapping[k]; + } + + this.get = this.tree.project.bind(this.tree, projection); + } + /** + * Methods releasing the watcher from memory. + */ + + }, { + key: "release", + value: function release() { + this.tree.off('update', this.handler); + this.state.killed = true; + this.kill(); + } + }]); + + return Watcher; +}(_emmett["default"]); + +exports["default"] = Watcher; \ No newline at end of file diff --git a/package.json b/package.json index 74c5e98..93945b6 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "baobab", - "version": "2.6.1", + "version": "2.6.2", "description": "JavaScript persistent data tree with cursors.", - "main": "./dist/baobab.js", + "main": "./dist/index.js", "dependencies": { "emmett": "^3.2.0" }, @@ -37,8 +37,8 @@ "build": "node ./scripts/build.js", "check": "npm test && npm run lint && npm run build", "dist:addendum": "cat scripts/commonjs-addendum.js >> dist/baobab.js", - "dist": "babel ./src --out-dir dist --presets @babel/preset-env && cp src/baobab.d.ts dist/. && npm run dist:addendum", - "lint": "eslint -c eslint.config.js ./src ./test && tslint src/baobab.d.ts test/suites/*.ts", + "dist": "rm -rf dist && babel ./src --out-dir dist --presets @babel/preset-env && cp src/*.d.ts dist/. && npm run dist:addendum", + "lint": "eslint -c eslint.config.js ./src ./test && tslint src/*.d.ts test/suites/*.ts", "prepublish": "npm run check && npm run dist", "test:commonjs": "node scripts/test-commonjs.js", "test:es6-import": "babel --presets @babel/preset-env scripts/test-es6-import.js | node", diff --git a/src/baobab.d.ts b/src/baobab.d.ts index 8f310a6..d7d7fce 100644 --- a/src/baobab.d.ts +++ b/src/baobab.d.ts @@ -1,4 +1,5 @@ import Emitter from 'emmett'; +import {SBaobab} from '../dist/sbaobab'; interface PlainObject { [key: string]: T; @@ -163,8 +164,8 @@ export class Baobab extends CommonBaobabMethods { type: string, value: any, options?: { - mutableLeaf?: boolean - } + mutableLeaf?: boolean; + }; } ): this; @@ -174,7 +175,7 @@ export class Baobab extends CommonBaobabMethods { watch(mappings: PlainObject): Watcher; - static monkey(definition: { cursors?: PlainObject; get(data: PlainObject): any; options?: MonkeyOptions }): MonkeyDefinition; + static monkey(definition: {cursors?: PlainObject; get(data: PlainObject): any; options?: MonkeyOptions;}): MonkeyDefinition; /* tslint:disable:unified-signatures */ // Polymorphisms for: @@ -202,3 +203,4 @@ export class Baobab extends CommonBaobabMethods { } export default Baobab; +// export * from './sbaobab'; \ No newline at end of file diff --git a/src/baobab.js b/src/baobab.js index 0c16d99..a03a426 100644 --- a/src/baobab.js +++ b/src/baobab.js @@ -188,7 +188,7 @@ class Baobab extends Emitter { // Should we sit a monkey in the tree? if (data instanceof MonkeyDefinition || - data instanceof Monkey) { + data instanceof Monkey) { const monkeyInstance = new Monkey( this, p, @@ -467,7 +467,7 @@ class Baobab extends Emitter { // Caching to keep original references before we change them const transaction = this._transaction, - previousData = this._previousData; + previousData = this._previousData; this._affectedPathsIndex = {}; this._transaction = []; @@ -589,3 +589,4 @@ export const VERSION = Baobab.VERSION; * Exporting. */ export default Baobab; +// export * from './sbaobab'; \ No newline at end of file diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..b654207 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,4 @@ +export * from './baobab'; +export * from './sbaobab'; +import Baobab from './baobab'; +export default Baobab; \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..b654207 --- /dev/null +++ b/src/index.js @@ -0,0 +1,4 @@ +export * from './baobab'; +export * from './sbaobab'; +import Baobab from './baobab'; +export default Baobab; \ No newline at end of file diff --git a/src/sbaobab.d.ts b/src/sbaobab.d.ts new file mode 100644 index 0000000..0f5e2ec --- /dev/null +++ b/src/sbaobab.d.ts @@ -0,0 +1,242 @@ +/** Stricter and more informative types for Baobab. Otherwise identical. + * TODO? Putting a couple more specific overloads of each function might improve client typechecking performance? +*/ +import Emitter from 'emmett'; +import {BaobabOptions, MonkeyDefinition, MonkeyOptions} from './baobab'; +import type {DeepPartial, DI, DP, Im, ImDI, HeadOf} from './util'; + +interface PlainObject { + [key: string]: T; +} + +type SimplePath = (string | number)[]; +type SP = SimplePath; + + +type Splicer = [number] | [number, number] | [number, number, (oldVals: T) => T] | [number, number, ...T]; +/** + * This class only exists to group methods that are common to the Baobab and + * Cursor classes. Since `Baobab.root` is a property while `Cursor#root` is a + * method, Baobab cannot extend Cursor. + */ +export abstract class SCommonBaobabMethods extends Emitter { + + + //TODO?: problematic overload? https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#use-union-types + apply(getNew: (state: T) => T): T; + apply(key: K, getNew: (state: Im) => Im): Im; + apply

>(path: P, getNew: (state: ImDI) => ImDI): ImDI; + + clone

>(...path: P): ImDI; + clone

>(path: DI): ImDI; + + // TODO?: could type guard for all array methods (concat, push, shift, splice, unshift) (not pop) + concat(value: T): Im; + concat

>(path: P, value: DI): ImDI; + + deepClone

>(...args: P): ImDI; + deepClone

>(path: P): ImDI; + + deepMerge(value: DeepPartial): Im; + deepMerge(path: K, value: DeepPartial): Im; + deepMerge

>(path: P, value: DeepPartial>): ImDI; + + exists

>(...args: P): ImDI; + exists

>(path: P): ImDI; + + get(): Im; + get(key: K): Im; + get

>(...path: P): ImDI; + get

>(path: P): ImDI; + + merge(value: Partial): Im; + merge(path: K, value: Partial): Im; + merge

>(path: P, value: Partial>): ImDI; + + // TODO?: could type guard only allow popping optional values + pop(): Im; + pop(key: K): Im; + pop

>(path: P): ImDI; + + project(projection: Record>): Record>; // TODO + project[]>(projection: Proj): unknown[]; + + push(value: T[number]): Im; + push(key: K, value: T[K][number]): Im; + push

>(path: P, value: DI[number]): ImDI; + + release(): void; + + serialize

>(...args: P): string; + serialize

>(path: P): string; + + set(value: T): Im; + set(key: K, value: T[K]): T[K]; + set

>(path: P, value: DI): ImDI; + + shift(): Im; + shift(key: K): T[K]; + shift

>(path: P, value: DI[number]): ImDI; + + splice(value: Splicer): Im; + splice

>(path: P, value: Splicer>): ImDI; + + unset(): void; + unset(key: K): void; + unset

>(path: P): void; + + unshift(value: T[number]): Im; + shift(key: K, value: T[K][number]): T[K]; + unshift

>(path: P, value: DI[number]): ImDI; + + +} + +export class SWatcher> | Record> extends Emitter { + // TODO?: initialized with cursors + constructor(tree: SBaobab, mapping: Mapping); + + get(): Mapping extends Record ? {[Name in keyof Mapping]: T[Mapping[Name]]} : {[Name in keyof Mapping]: ImDI}; + getWatchedPaths(): Mapping[keyof Mapping]; + getCursors(): unknown; // TODO + refresh(mappings: Mapping): void; + release(): void; +} + +/** + * @constructor + * @param {Baobab} tree - The bound tree. + * @param {MonkeyDefinition} definition - A definition instance. + */ +export class SMonkey { + constructor(tree: SBaobab, pathInTree: SimplePath, definition: MonkeyDefinition); + relatedPaths(recursive: boolean): SimplePath[]; + update(): SMonkey; + release(): void; +} + + +export class SCursor = unknown, Root = unknown> extends SCommonBaobabMethods implements Iterable { + constructor(tree: SBaobab, path: FullPath, hash: string); + path?: FullPath; + solvedPath?: SimplePath; + state: { + killed: boolean; + recording: boolean; + undoing: boolean; + }; + + [Symbol.iterator](): IterableIterator; + // Navigation: + up(): SCursor>, HeadOf, Root>; + down(): SCursor, [...FullPath, number], Root>; + left(): SCursor | null; + right(): SCursor | null; + leftmost(): SCursor | null; + rightmost(): SCursor | null; + root(): SCursor; + + // Predicates: + isLeaf(): boolean; + isRoot(): boolean; + isBranch(): boolean; + + // History: + hasHistory(): boolean; + getHistory(): any[]; + clearHistory(): this; + startRecording(maxRecords?: number): this; + stopRecording(): this; + undo(steps?: number): this; + + // Others: + toJSON(): string; + toString(): string; + + map(fn: (v: SCursor, index: number) => Return, scope?: any): Return[]; + on(name: 'update', handler: (e: {data: {currentData: T, previousData: T;};}) => void): this; + + + select(key: K): SCursor; + select

>(...path: P): SCursor, [...FullPath, ...P], Root>; // TODO: forbid empty path + select

>(path: P): SCursor, [...FullPath, ...P], Root>; + + +} + +/** Stricter and more informative types for Baobab. Otherwise identical. */ +export class SBaobab extends SCommonBaobabMethods { + constructor(initialState?: T, options?: Partial); + debugType: T; + + root: SCursor; + options: BaobabOptions; + + update( + path: SP, + operation: { + type: string, + value: any, + options?: { + mutableLeaf?: boolean; + }; + } + ): this; + + commit(): this; + + getMonkey(path: SP): SMonkey; + + watch> | Record>(mappings: Mappings): SWatcher; + + static monkey(definition: {cursors?: PlainObject; get(data: PlainObject): any; options?: MonkeyOptions;}): MonkeyDefinition; + + /* tslint:disable:unified-signatures */ + // Polymorphisms for: + // `.monkey(...paths: Path[], get: (v1: any) => any)` + static monkey(path1: SP, get: (value: any) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: SP, path2: SP, get: (...values: [any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: SP, path2: SP, path3: SP, get: (...values: [any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: SP, path2: SP, path3: SP, path4: SP, get: (...values: [any, any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + static monkey(path1: SP, path2: SP, path3: SP, path4: SP, path5: SP, get: (...values: [any, any, any, any, any]) => any, options?: MonkeyOptions): MonkeyDefinition; + // Fallback: + static monkey(...pathsEndingWithGetAndMaybeOptions: (SP | ((...values: any[]) => any) | MonkeyOptions)[]): MonkeyDefinition; + + // Polymorphisms for: + // `.monkey(definition: [...paths: SP[], get: (v1: any) => any])` + static monkey(args: [SP, (value: any) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [SP, SP, (...values: [any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [SP, SP, SP, (...values: [any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [SP, SP, SP, SP, (...values: [any, any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + static monkey(args: [SP, SP, SP, SP, SP, (...values: [any, any, any, any, any]) => any], options?: MonkeyOptions): MonkeyDefinition; + // Fallback: + static monkey(pathsEndingWithGet: (SP | ((...values: any[]) => any) | MonkeyOptions)[]): MonkeyDefinition; + /* tslint:enable:unified-signatures */ + + static dynamicNode: typeof SBaobab.monkey; + + on(name: 'write', handler: (e: {data: {path: SimplePath;};}) => void): this; + on(name: 'invalid', handler: (e: {data: {error: unknown;};}) => void): this; + on(name: 'get', handler: (e: {data: {path: unknown, solvedPath: SimplePath, data: unknown;};}) => void): this; + on(name: 'select', handler: (e: {data: {path: unknown, cursor: unknown;};}) => void): this; + on(name: 'update', handler: (e: {data: {currentData: T, previousData: T; transaction: unknown; paths: unknown;};}) => void): this; + + select(): SCursor; + select(key: K): SCursor; + select

>(...path: P): SCursor, [...FullPath, ...P], T>; + select

>(path: P): SCursor, [...FullPath, ...P], T>; + +} + +export type ROBaobab = Pick, 'clone' | 'deepClone' | 'exists' | 'watch' | 'serialize' | 'project' | 'options' | 'get' | 'getMonkey' | 'on'> & { + select(): ROCursor; + select(key: K): ROCursor; + select

>(...path: P): ROCursor, [...FullPath, ...P], T>; + select

>(path: P): ROCursor, [...FullPath, ...P], T>; +}; + +export type ROCursor = unknown, Root = unknown> = Pick, 'clone' | 'deepClone' | 'exists' | 'serialize' | 'project' | 'get' | 'on'> & { + select(key: K): ROCursor; + select

>(...path: P): ROCursor, [...FullPath, ...P], Root>; // TODO: forbid empty path + select

>(path: P): ROCursor, [...FullPath, ...P], Root>; +}; diff --git a/src/sbaobab.js b/src/sbaobab.js new file mode 100644 index 0000000..866137b --- /dev/null +++ b/src/sbaobab.js @@ -0,0 +1,11 @@ +/** Identical to Baobab but uses stricter / more informative types */ + +import Baobab from './baobab'; +import Cursor from './cursor'; + +export class SBaobab extends Baobab { + constructor(...args) {super(...args);} +} +export class SCursor extends Cursor { + constructor(...args) {super(...args);} +} diff --git a/src/util.d.ts b/src/util.d.ts new file mode 100644 index 0000000..586c48a --- /dev/null +++ b/src/util.d.ts @@ -0,0 +1,88 @@ +// https://stackoverflow.com/a/58993872/4941530 + +// eslint-disable-next-line @typescript-eslint/ban-types +type ImmutablePrimitive = undefined | null | boolean | string | number | Function; + +export type Immutable = + T extends ImmutablePrimitive ? T : + T extends Array ? ImmutableArray : + T extends Map ? ImmutableMap : + T extends Set ? ImmutableSet : ImmutableObject; + +type ImmutableArray = ReadonlyArray>; +type ImmutableMap = ReadonlyMap, Immutable>; +type ImmutableSet = ReadonlySet>; +type ImmutableObject = {readonly [K in keyof T]: Immutable}; + + +type Vals = T[keyof T]; +// https://stackoverflow.com/questions/58434389 +// type PathsOf = +// T extends object ? +// T extends Array ? +// [] | [number, ...PathsOf] : +// Vals<{[P in keyof T]-?: [] | [P, ...PathsOf]}> : +// []; + + +type Predicate = (data: T) => boolean; +type Constraint = Partial; + +type FullPathsOf = + T extends object ? + T extends Array ? + [] | [number, ...FullPathsOf] | [Predicate, ...FullPathsOf] | (Item extends object ? [Constraint, ...FullPathsOf] : []) : + Vals<{[P in keyof T]-?: [] | [P, ...FullPathsOf]}> : + []; + +// https://stackoverflow.com/a/61648690 +// type DeepIndex = +// KS extends [infer F, ...infer R] ? +// F extends keyof T ? +// R extends PropertyKey[] ? +// DeepIndex : +// Fail : +// Fail : +// T; + +interface Fail1 {} +interface Fail2 {} +interface Fail3 {} + +type Obj = Record; +export type FullKeys = (PropertyKey | Obj | Function)[]; +export type FullDeepIndex = + KS extends [infer Keyish, ...infer Rest] // A: have a key? + ? Rest extends FullKeys // B: rest is an array? + ? Keyish extends Obj | Function // C: key is special? + ? T extends Array // D: value is array? + ? FullDeepIndex // descend with special key. + : Fail1 // not D: special key on non-array value + : Keyish extends keyof T // (not C) E: key is valid + ? FullDeepIndex // descend with regular key + : Fail2 // not E: invalid key + : Fail3 // not B: not an array at all + : T; // not A: stop descending + +// TODO: immutable types causing tons problems in other packages using baobab +// export type Im = Immutable; +export type Im = T; +// export type ImDI = Immutable>; +export type ImDI = FullDeepIndex; +export type DP = FullPathsOf; // TODO? as FullKeys +export type DI = FullDeepIndex; + + +export type DeepPartial = { + [P in keyof T]?: DeepPartial; +}; + +interface HeadOfErr {} +export type HeadOf = T extends [...infer Head, infer Last] ? Head : HeadOfErr; + +export type IfEquals = + (() => G extends T ? 1 : 2) extends + (() => G extends U ? 1 : 2) ? Y : N; + +type Foo = 'blarg' | 'smarf'; +type IMFoo = Immutable; \ No newline at end of file diff --git a/test/suites/types.ts b/test/suites/types.ts new file mode 100644 index 0000000..c6fcc60 --- /dev/null +++ b/test/suites/types.ts @@ -0,0 +1,201 @@ +import {SBaobab} from '../../src/sbaobab'; +import {strict as assert} from 'assert'; + +const initialState = { + hello: 'world', + palette: { + colors: ['yellow', 'purple', 'green'], + name: 'Glorious colors' + }, + numberIn: { + here: 5 + }, + very: { + deeply: { + nested: { + objects: { + are: 'okay' + } + } + } + }, + somewhat: { + nested: { + objects: 'are', + }, + also: 'okay' + }, + lists: { + 0: ['a', 'b', 'c'], + 1: [0, 10, 20, 30, 40, 50], + 2: [{key: 'obj1', val: 'wow'}, {key: 'obj2', val: 'okay'}] + } +}; +interface InitialState { + hello: string, + palette: { + colors: string[]; + name: string; + }, + numberIn: { + here: number; + }, + very: { + deeply: { + nested: { + objects: { + are: string; + }; + }; + }; + }, + somewhat: { + nested: { + objects: string, + }, + also: string; + }, + lists: { + 0: string[], + 1: number[], + 2: {key: string, val: string;}[]; + }; +}; + + + + +describe('Types', function() { + it('should have all the right types but this test doesn\'t actually test that', function() { + assert.strictEqual(2 + 2, 4); + }); + + const tree = new SBaobab(initialState); + const rnjt = tree.get(); + const ljda = rnjt.palette.name; + const yxwi = rnjt?.invalid?.key; // invalid + // const getName = tree.get(['palette', 'name']); + const getName2 = tree.get(['palette', 'name']); + const wtep = tree.get('palette', 'name'); + const foo = tree.get(); + const vrpf = tree.get(['somewhat', 'nested']); + const fxwo = tree.get('lists', 2, {key: 'obj1'}); + const seci = tree.apply(['lists', 2, {key: 'obj1'}], o => o); + // assert.strictEqual(getName, name); + const numbersList = tree.select('lists', 1); + const mgfy = numbersList.push(60); + numbersList.push('foo'); // should have error + numbersList.unshift(0); // should throw error + numbersList.exists(); + tree.serialize(); + tree.serialize(['somewhat', 'nested']); + const watcher = tree.watch({ + name: ['palette', 'name'], + nested: ['somewhat', 'nested'] + }); + watcher.get(); + const liox = numbersList.clone(); + const ampw = numbersList.deepClone(); + const sdtn = tree.pop(['lists', 0]); + const ncow = tree.concat(['lists', 0], ['d', 'e', 'f']); + const oczw = tree.concat(['lists', 0], [36]); // invalid + const nbto = tree.concat(['lists', 1], ['a']); // invalid + const xpmw = tree.apply('palette', p => ({...p, name: p.name + 'wow'})); + const kwba = tree.merge('palette', {name: 'newname'}); + const eipo = tree.deepMerge({somewhat: {nested: {objects: 'really are'}}}); + const complexCursor = tree.select('lists', 2, {key: 'obj1'}, 'val'); + + tree.on('update', e => { + const rjks = e.type; + const wxhj = e.target; + const vvst = e.data; + }); + + tree.on('write', function(e) { + const v1 = e.data.path; + }); + + tree.on('invalid', function(e) { + const v2 = e.data.error; + }); + tree.on('get', function(e) { + const v3 = e.data.path; + const v4 = e.data.solvedPath; + const v5 = e.data.data; + }); + + tree.on('select', function(e) { + const v6 = e.data.path; + const v7 = e.data.cursor; + }); + + tree.select('lists').on('update', e => { + const v1 = e.type; + const v2 = e.target; + const v3 = e.data; + }); + numbersList.splice([1, 1, 100]); + tree.select('numberIn', 'here').apply(x => x + 1); + tree.select('numberIn').apply('here', x => x + 1); + const zsxb = tree.select('palette', 'colors'); + const rfmy = tree.select('palette'); + const hzbk = tree.select('palette', 'colors'); + const ayco = tree.select('palette', 'colors', 2); + const xbzu = tree.set('hello', 'monde'); + // const opoc = tree.select('palette', 'colors', function(color) { return color === 'green'; }); + const opoc = tree.get(['palette', 'colors', (color: string) => color === 'green']); + + const fyjb = tree.get('palette', 'colors', function(color: string) { + return color === 'green'; + }); + + const stou = tree.set({hello: 'bonjour', palette: {colors: ['green', 'red'], name: 'lame colors', invalidKey: 'should error'}}); // invalid + const hecg = tree.unset('hello'); + const vtyy = tree.project({ + abmw: ['very', 'deeply', 'nested'], + ispn: ['lists'], + }); + const geza = tree.project([['very', 'deeply', 'nested'], ['lists']]); + const aweu = tree.project({some: ['unknownkey']}); // invalid + const qssn = tree.set({...initialState, invalidKey: 'foo'}); // invalid + const gzlk = tree.project([ + ['hello'], + ['lists'] + ]); + const dscz = hzbk.root(); + // tree.debugType + + const arqw = new SBaobab({ + list: [[1, 2], [3, 4]], + longList: ['one', 'two', 'three', 'four'] + }); + + const krfq = arqw.select('list'); + const ipfw = arqw.select('longList', 1); + const vmzg = ipfw.get(); + const yklt = krfq.up(); + const vppy = krfq.up(); // TODO: should error + const pelj = krfq?.down()?.right()?.get(); // [3, 4] + const rwpi = krfq?.select(1)?.down()?.right()?.get();// 4 + const ivxr = krfq?.select(1)?.down()?.right()?.left()?.get();// 3 + const mxgc = ipfw?.leftmost()?.get();// 'one' + const rfdi = new SBaobab({list: [1, 2, 3]}); + + rfdi.select('list').map(function(cursor, i) { + const nbuv = cursor.get(); + }); + const atke = rfdi.select('list'); + atke.isRoot(); + atke.isBranch(); + atke.isLeaf(); + + const dwuz = new SBaobab({x: 1, y: {z: 3}}, { + validate: (_previousState, _newState, _affectedPaths) => {return undefined;} + }); + + const bzqd = new SBaobab({list: [1, 2, 3]}); + const thcc = bzqd.select('list'); + const opwb = thcc.map(function(cursor, i) { + const dxyk = cursor.get(); + }); +}); \ No newline at end of file diff --git a/test/suites/watcher-types.patch b/test/suites/watcher-types.patch new file mode 100644 index 0000000..b6b011c --- /dev/null +++ b/test/suites/watcher-types.patch @@ -0,0 +1,71 @@ +diff --git a/test/suites/watcher.ts b/test/suites/watcher.ts +index 3ea3c67..8b5461b 100644 +--- a/test/suites/watcher.ts ++++ b/test/suites/watcher.ts +@@ -3,12 +3,20 @@ + * =========================== + */ + import {strict as assert} from 'assert'; +-import Baobab, {Cursor} from '../../src/baobab'; ++// import Baobab, {Cursor} from '../../src/baobab'; ++import {SBaobab as Baobab, SCursor as Cursor} from '../../src/sbaobab'; + + describe('Watchers', function() { + + it('should be possible to track some paths within the tree.', function() { +- const tree = new Baobab({ ++ type Tree = { ++ data: { ++ greeting?: string; ++ name?: string; ++ }; ++ hey?: string; ++ }; ++ const tree = new Baobab({ + data: { + greeting: 'Hello', + name: 'Jack' +@@ -38,16 +46,27 @@ describe('Watchers', function() { + }); + + it('should be possible to give cursors to a watcher.', function() { +- const tree = new Baobab({ ++ type Tree = { ++ data: { ++ greeting?: string; ++ name?: string; ++ }; ++ hey?: string; ++ }; ++ const tree = new Baobab({ + data: { + greeting: 'Hello', + name: 'Jack' + } + }, {asynchronous: false}); + ++ // const watcher = tree.watch({ ++ // greeting: tree.select(['data', 'greeting']), ++ // name: tree.select(['data', 'name']) ++ // }); + const watcher = tree.watch({ +- greeting: tree.select(['data', 'greeting']), +- name: tree.select(['data', 'name']) ++ greeting: ['data', 'greeting'], ++ name: ['data', 'name'] + }); + + let count = 0; +@@ -83,7 +102,11 @@ describe('Watchers', function() { + }); + + it('should be possible to use dynamic paths.', function() { +- const tree = new Baobab({ ++ type Tree = { ++ data?: {id: number, txt: string;}[] | {}; ++ hey?: string; ++ }; ++ const tree = new Baobab({ + data: [{id: 0, txt: 'Hello'}, {id: 1, txt: 'World'}] + }, {asynchronous: false}); +