UNPKG

mobx-state-tree

Version:

Opinionated, transactional, MobX powered state container

1,474 lines (1,455 loc) 234 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var mobx = require('mobx'); var livelinessChecking = "warn"; /** * Defines what MST should do when running into reads / writes to objects that have died. * By default it will print a warning. * Use the `"error"` option to easy debugging to see where the error was thrown and when the offending read / write took place * * @param mode `"warn"`, `"error"` or `"ignore"` */ function setLivelinessChecking(mode) { livelinessChecking = mode; } /** * Returns the current liveliness checking mode. * * @returns `"warn"`, `"error"` or `"ignore"` */ function getLivelinessChecking() { return livelinessChecking; } /** * @deprecated use setLivelinessChecking instead * @hidden * * Defines what MST should do when running into reads / writes to objects that have died. * By default it will print a warning. * Use the `"error"` option to easy debugging to see where the error was thrown and when the offending read / write took place * * @param mode `"warn"`, `"error"` or `"ignore"` */ function setLivelynessChecking(mode) { setLivelinessChecking(mode); } /** * @internal * @hidden */ var Hook; (function (Hook) { Hook["afterCreate"] = "afterCreate"; Hook["afterAttach"] = "afterAttach"; Hook["afterCreationFinalization"] = "afterCreationFinalization"; Hook["beforeDetach"] = "beforeDetach"; Hook["beforeDestroy"] = "beforeDestroy"; })(Hook || (Hook = {})); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function __values(o) { var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; if (m) return m.call(o); return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; } function __read(o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; } function __spread() { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; } /** * Returns the _actual_ type of the given tree node. (Or throws) * * @param object * @returns */ function getType(object) { assertIsStateTreeNode(object, 1); return getStateTreeNode(object).type; } /** * Returns the _declared_ type of the given sub property of an object, array or map. * In the case of arrays and maps the property name is optional and will be ignored. * * Example: * ```ts * const Box = types.model({ x: 0, y: 0 }) * const box = Box.create() * * console.log(getChildType(box, "x").name) // 'number' * ``` * * @param object * @param propertyName * @returns */ function getChildType(object, propertyName) { assertIsStateTreeNode(object, 1); return getStateTreeNode(object).getChildType(propertyName); } /** * Registers a function that will be invoked for each mutation that is applied to the provided model instance, or to any of its children. * See [patches](https://github.com/mobxjs/mobx-state-tree#patches) for more details. onPatch events are emitted immediately and will not await the end of a transaction. * Patches can be used to deep observe a model tree. * * @param target the model instance from which to receive patches * @param callback the callback that is invoked for each patch. The reversePatch is a patch that would actually undo the emitted patch * @returns function to remove the listener */ function onPatch(target, callback) { // check all arguments assertIsStateTreeNode(target, 1); assertIsFunction(callback, 2); return getStateTreeNode(target).onPatch(callback); } /** * Registers a function that is invoked whenever a new snapshot for the given model instance is available. * The listener will only be fire at the end of the current MobX (trans)action. * See [snapshots](https://github.com/mobxjs/mobx-state-tree#snapshots) for more details. * * @param target * @param callback * @returns */ function onSnapshot(target, callback) { // check all arguments assertIsStateTreeNode(target, 1); assertIsFunction(callback, 2); return getStateTreeNode(target).onSnapshot(callback); } /** * Applies a JSON-patch to the given model instance or bails out if the patch couldn't be applied * See [patches](https://github.com/mobxjs/mobx-state-tree#patches) for more details. * * Can apply a single past, or an array of patches. * * @param target * @param patch * @returns */ function applyPatch(target, patch) { // check all arguments assertIsStateTreeNode(target, 1); assertArg(patch, function (p) { return typeof p === "object"; }, "object or array", 2); getStateTreeNode(target).applyPatches(asArray(patch)); } /** * Small abstraction around `onPatch` and `applyPatch`, attaches a patch listener to a tree and records all the patches. * Returns an recorder object with the following signature: * * Example: * ```ts * export interface IPatchRecorder { * // the recorded patches * patches: IJsonPatch[] * // the inverse of the recorded patches * inversePatches: IJsonPatch[] * // true if currently recording * recording: boolean * // stop recording patches * stop(): void * // resume recording patches * resume(): void * // apply all the recorded patches on the given target (the original subject if omitted) * replay(target?: IAnyStateTreeNode): void * // reverse apply the recorded patches on the given target (the original subject if omitted) * // stops the recorder if not already stopped * undo(): void * } * ``` * * The optional filter function allows to skip recording certain patches. * * @param subject * @param filter * @returns */ function recordPatches(subject, filter) { // check all arguments assertIsStateTreeNode(subject, 1); var data = { patches: [], reversedInversePatches: [] }; // we will generate the immutable copy of patches on demand for public consumption var publicData = {}; var disposer; var recorder = { get recording() { return !!disposer; }, get patches() { if (!publicData.patches) { publicData.patches = data.patches.slice(); } return publicData.patches; }, get reversedInversePatches() { if (!publicData.reversedInversePatches) { publicData.reversedInversePatches = data.reversedInversePatches.slice(); } return publicData.reversedInversePatches; }, get inversePatches() { if (!publicData.inversePatches) { publicData.inversePatches = data.reversedInversePatches.slice().reverse(); } return publicData.inversePatches; }, stop: function () { if (disposer) { disposer(); disposer = undefined; } }, resume: function () { if (disposer) return; disposer = onPatch(subject, function (patch, inversePatch) { // skip patches that are asked to be filtered if there's a filter in place if (filter && !filter(patch, inversePatch, getRunningActionContext())) { return; } data.patches.push(patch); data.reversedInversePatches.unshift(inversePatch); // mark immutable public patches as dirty publicData.patches = undefined; publicData.inversePatches = undefined; publicData.reversedInversePatches = undefined; }); }, replay: function (target) { applyPatch(target || subject, data.patches); }, undo: function (target) { applyPatch(target || subject, data.reversedInversePatches); } }; recorder.resume(); return recorder; } /** * The inverse of `unprotect`. * * @param target */ function protect(target) { // check all arguments assertIsStateTreeNode(target, 1); var node = getStateTreeNode(target); if (!node.isRoot) throw fail$1("`protect` can only be invoked on root nodes"); node.isProtectionEnabled = true; } /** * By default it is not allowed to directly modify a model. Models can only be modified through actions. * However, in some cases you don't care about the advantages (like replayability, traceability, etc) this yields. * For example because you are building a PoC or don't have any middleware attached to your tree. * * In that case you can disable this protection by calling `unprotect` on the root of your tree. * * Example: * ```ts * const Todo = types.model({ * done: false * }).actions(self => ({ * toggle() { * self.done = !self.done * } * })) * * const todo = Todo.create() * todo.done = true // throws! * todo.toggle() // OK * unprotect(todo) * todo.done = false // OK * ``` */ function unprotect(target) { // check all arguments assertIsStateTreeNode(target, 1); var node = getStateTreeNode(target); if (!node.isRoot) throw fail$1("`unprotect` can only be invoked on root nodes"); node.isProtectionEnabled = false; } /** * Returns true if the object is in protected mode, @see protect */ function isProtected(target) { return getStateTreeNode(target).isProtected; } /** * Applies a snapshot to a given model instances. Patch and snapshot listeners will be invoked as usual. * * @param target * @param snapshot * @returns */ function applySnapshot(target, snapshot) { // check all arguments assertIsStateTreeNode(target, 1); return getStateTreeNode(target).applySnapshot(snapshot); } /** * Calculates a snapshot from the given model instance. The snapshot will always reflect the latest state but use * structural sharing where possible. Doesn't require MobX transactions to be completed. * * @param target * @param applyPostProcess If true (the default) then postProcessSnapshot gets applied. * @returns */ function getSnapshot(target, applyPostProcess) { if (applyPostProcess === void 0) { applyPostProcess = true; } // check all arguments assertIsStateTreeNode(target, 1); var node = getStateTreeNode(target); if (applyPostProcess) return node.snapshot; return freeze(node.type.getSnapshot(node, false)); } /** * Given a model instance, returns `true` if the object has a parent, that is, is part of another object, map or array. * * @param target * @param depth How far should we look upward? 1 by default. * @returns */ function hasParent(target, depth) { if (depth === void 0) { depth = 1; } // check all arguments assertIsStateTreeNode(target, 1); assertIsNumber(depth, 2, 0); var parent = getStateTreeNode(target).parent; while (parent) { if (--depth === 0) return true; parent = parent.parent; } return false; } /** * Returns the immediate parent of this object, or throws. * * Note that the immediate parent can be either an object, map or array, and * doesn't necessarily refer to the parent model. * * Please note that in child nodes access to the root is only possible * once the `afterAttach` hook has fired. * * @param target * @param depth How far should we look upward? 1 by default. * @returns */ function getParent(target, depth) { if (depth === void 0) { depth = 1; } // check all arguments assertIsStateTreeNode(target, 1); assertIsNumber(depth, 2, 0); var d = depth; var parent = getStateTreeNode(target).parent; while (parent) { if (--d === 0) return parent.storedValue; parent = parent.parent; } throw fail$1("Failed to find the parent of " + getStateTreeNode(target) + " at depth " + depth); } /** * Given a model instance, returns `true` if the object has a parent of given type, that is, is part of another object, map or array * * @param target * @param type * @returns */ function hasParentOfType(target, type) { // check all arguments assertIsStateTreeNode(target, 1); assertIsType(type, 2); var parent = getStateTreeNode(target).parent; while (parent) { if (type.is(parent.storedValue)) return true; parent = parent.parent; } return false; } /** * Returns the target's parent of a given type, or throws. * * @param target * @param type * @returns */ function getParentOfType(target, type) { // check all arguments assertIsStateTreeNode(target, 1); assertIsType(type, 2); var parent = getStateTreeNode(target).parent; while (parent) { if (type.is(parent.storedValue)) return parent.storedValue; parent = parent.parent; } throw fail$1("Failed to find the parent of " + getStateTreeNode(target) + " of a given type"); } /** * Given an object in a model tree, returns the root object of that tree. * * Please note that in child nodes access to the root is only possible * once the `afterAttach` hook has fired. * * @param target * @returns */ function getRoot(target) { // check all arguments assertIsStateTreeNode(target, 1); return getStateTreeNode(target).root.storedValue; } /** * Returns the path of the given object in the model tree * * @param target * @returns */ function getPath(target) { // check all arguments assertIsStateTreeNode(target, 1); return getStateTreeNode(target).path; } /** * Returns the path of the given object as unescaped string array. * * @param target * @returns */ function getPathParts(target) { // check all arguments assertIsStateTreeNode(target, 1); return splitJsonPath(getStateTreeNode(target).path); } /** * Returns true if the given object is the root of a model tree. * * @param target * @returns */ function isRoot(target) { // check all arguments assertIsStateTreeNode(target, 1); return getStateTreeNode(target).isRoot; } /** * Resolves a path relatively to a given object. * Returns undefined if no value can be found. * * @param target * @param path escaped json path * @returns */ function resolvePath(target, path) { // check all arguments assertIsStateTreeNode(target, 1); assertIsString(path, 2); var node = resolveNodeByPath(getStateTreeNode(target), path); return node ? node.value : undefined; } /** * Resolves a model instance given a root target, the type and the identifier you are searching for. * Returns undefined if no value can be found. * * @param type * @param target * @param identifier * @returns */ function resolveIdentifier(type, target, identifier) { // check all arguments assertIsType(type, 1); assertIsStateTreeNode(target, 2); assertIsValidIdentifier(identifier, 3); var node = getStateTreeNode(target).root.identifierCache.resolve(type, normalizeIdentifier(identifier)); return node ? node.value : undefined; } /** * Returns the identifier of the target node. * This is the *string normalized* identifier, which might not match the type of the identifier attribute * * @param target * @returns */ function getIdentifier(target) { // check all arguments assertIsStateTreeNode(target, 1); return getStateTreeNode(target).identifier; } /** * Tests if a reference is valid (pointing to an existing node and optionally if alive) and returns such reference if it the check passes, * else it returns undefined. * * @param getter Function to access the reference. * @param checkIfAlive true to also make sure the referenced node is alive (default), false to skip this check. * @returns */ function tryReference(getter, checkIfAlive) { if (checkIfAlive === void 0) { checkIfAlive = true; } try { var node = getter(); if (node === undefined || node === null) { return undefined; } else if (isStateTreeNode(node)) { if (!checkIfAlive) { return node; } else { return isAlive(node) ? node : undefined; } } else { throw fail$1("The reference to be checked is not one of node, null or undefined"); } } catch (e) { if (e instanceof InvalidReferenceError) { return undefined; } throw e; } } /** * Tests if a reference is valid (pointing to an existing node and optionally if alive) and returns if the check passes or not. * * @param getter Function to access the reference. * @param checkIfAlive true to also make sure the referenced node is alive (default), false to skip this check. * @returns */ function isValidReference(getter, checkIfAlive) { if (checkIfAlive === void 0) { checkIfAlive = true; } try { var node = getter(); if (node === undefined || node === null) { return false; } else if (isStateTreeNode(node)) { return checkIfAlive ? isAlive(node) : true; } else { throw fail$1("The reference to be checked is not one of node, null or undefined"); } } catch (e) { if (e instanceof InvalidReferenceError) { return false; } throw e; } } /** * Try to resolve a given path relative to a given node. * * @param target * @param path * @returns */ function tryResolve(target, path) { // check all arguments assertIsStateTreeNode(target, 1); assertIsString(path, 2); var node = resolveNodeByPath(getStateTreeNode(target), path, false); if (node === undefined) return undefined; try { return node.value; } catch (e) { // For what ever reason not resolvable (e.g. totally not existing path, or value that cannot be fetched) // see test / issue: 'try resolve doesn't work #686' return undefined; } } /** * Given two state tree nodes that are part of the same tree, * returns the shortest jsonpath needed to navigate from the one to the other * * @param base * @param target * @returns */ function getRelativePath(base, target) { // check all arguments assertIsStateTreeNode(base, 1); assertIsStateTreeNode(target, 2); return getRelativePathBetweenNodes(getStateTreeNode(base), getStateTreeNode(target)); } /** * Returns a deep copy of the given state tree node as new tree. * Short hand for `snapshot(x) = getType(x).create(getSnapshot(x))` * * _Tip: clone will create a literal copy, including the same identifiers. To modify identifiers etc during cloning, don't use clone but take a snapshot of the tree, modify it, and create new instance_ * * @param source * @param keepEnvironment indicates whether the clone should inherit the same environment (`true`, the default), or not have an environment (`false`). If an object is passed in as second argument, that will act as the environment for the cloned tree. * @returns */ function clone(source, keepEnvironment) { if (keepEnvironment === void 0) { keepEnvironment = true; } // check all arguments assertIsStateTreeNode(source, 1); var node = getStateTreeNode(source); return node.type.create(node.snapshot, keepEnvironment === true ? node.root.environment : keepEnvironment === false ? undefined : keepEnvironment); // it's an object or something else } /** * Removes a model element from the state tree, and let it live on as a new state tree */ function detach(target) { // check all arguments assertIsStateTreeNode(target, 1); getStateTreeNode(target).detach(); return target; } /** * Removes a model element from the state tree, and mark it as end-of-life; the element should not be used anymore */ function destroy(target) { // check all arguments assertIsStateTreeNode(target, 1); var node = getStateTreeNode(target); if (node.isRoot) node.die(); else node.parent.removeChild(node.subpath); } /** * Returns true if the given state tree node is not killed yet. * This means that the node is still a part of a tree, and that `destroy` * has not been called. If a node is not alive anymore, the only thing one can do with it * is requesting it's last path and snapshot * * @param target * @returns */ function isAlive(target) { // check all arguments assertIsStateTreeNode(target, 1); return getStateTreeNode(target).observableIsAlive; } /** * Use this utility to register a function that should be called whenever the * targeted state tree node is destroyed. This is a useful alternative to managing * cleanup methods yourself using the `beforeDestroy` hook. * * This methods returns the same disposer that was passed as argument. * * Example: * ```ts * const Todo = types.model({ * title: types.string * }).actions(self => ({ * afterCreate() { * const autoSaveDisposer = reaction( * () => getSnapshot(self), * snapshot => sendSnapshotToServerSomehow(snapshot) * ) * // stop sending updates to server if this * // instance is destroyed * addDisposer(self, autoSaveDisposer) * } * })) * ``` * * @param target * @param disposer * @returns The same disposer that was passed as argument */ function addDisposer(target, disposer) { // check all arguments assertIsStateTreeNode(target, 1); assertIsFunction(disposer, 2); var node = getStateTreeNode(target); node.addDisposer(disposer); return disposer; } /** * Returns the environment of the current state tree. For more info on environments, * see [Dependency injection](https://github.com/mobxjs/mobx-state-tree#dependency-injection) * * Please note that in child nodes access to the root is only possible * once the `afterAttach` hook has fired * * Returns an empty environment if the tree wasn't initialized with an environment * * @param target * @returns */ function getEnv(target) { // check all arguments assertIsStateTreeNode(target, 1); var node = getStateTreeNode(target); var env = node.root.environment; if (!env) return EMPTY_OBJECT; return env; } /** * Performs a depth first walk through a tree. */ function walk(target, processor) { // check all arguments assertIsStateTreeNode(target, 1); assertIsFunction(processor, 2); var node = getStateTreeNode(target); // tslint:disable-next-line:no_unused-variable node.getChildren().forEach(function (child) { if (isStateTreeNode(child.storedValue)) walk(child.storedValue, processor); }); processor(node.storedValue); } /** * Returns a reflection of the model type properties and name for either a model type or model node. * * @param typeOrNode * @returns */ function getPropertyMembers(typeOrNode) { var type; if (isStateTreeNode(typeOrNode)) { type = getType(typeOrNode); } else { type = typeOrNode; } assertArg(type, function (t) { return isModelType(t); }, "model type or model instance", 1); return { name: type.name, properties: __assign({}, type.properties) }; } /** * Returns a reflection of the model node, including name, properties, views, volatile and actions. * * @param target * @returns */ function getMembers(target) { var type = getStateTreeNode(target).type; var reflected = __assign({}, getPropertyMembers(type), { actions: [], volatile: [], views: [] }); var props = Object.getOwnPropertyNames(target); props.forEach(function (key) { if (key in reflected.properties) return; var descriptor = Object.getOwnPropertyDescriptor(target, key); if (descriptor.get) { if (mobx.isComputedProp(target, key)) reflected.views.push(key); else reflected.volatile.push(key); return; } if (descriptor.value._isMSTAction === true) reflected.actions.push(key); else if (mobx.isObservableProp(target, key)) reflected.volatile.push(key); else reflected.views.push(key); }); return reflected; } /** * Casts a node snapshot or instance type to an instance type so it can be assigned to a type instance. * Note that this is just a cast for the type system, this is, it won't actually convert a snapshot to an instance, * but just fool typescript into thinking so. * Either way, casting when outside an assignation operation won't compile. * * Example: * ```ts * const ModelA = types.model({ * n: types.number * }).actions(self => ({ * setN(aNumber: number) { * self.n = aNumber * } * })) * * const ModelB = types.model({ * innerModel: ModelA * }).actions(self => ({ * someAction() { * // this will allow the compiler to assign a snapshot to the property * self.innerModel = cast({ a: 5 }) * } * })) * ``` * * @param snapshotOrInstance Snapshot or instance * @returns The same object casted as an instance */ function cast(snapshotOrInstance) { return snapshotOrInstance; } /** * Casts a node instance type to an snapshot type so it can be assigned to a type snapshot (e.g. to be used inside a create call). * Note that this is just a cast for the type system, this is, it won't actually convert an instance to a snapshot, * but just fool typescript into thinking so. * * Example: * ```ts * const ModelA = types.model({ * n: types.number * }).actions(self => ({ * setN(aNumber: number) { * self.n = aNumber * } * })) * * const ModelB = types.model({ * innerModel: ModelA * }) * * const a = ModelA.create({ n: 5 }); * // this will allow the compiler to use a model as if it were a snapshot * const b = ModelB.create({ innerModel: castToSnapshot(a)}) * ``` * * @param snapshotOrInstance Snapshot or instance * @returns The same object casted as an input (creation) snapshot */ function castToSnapshot(snapshotOrInstance) { return snapshotOrInstance; } /** * Casts a node instance type to a reference snapshot type so it can be assigned to a refernence snapshot (e.g. to be used inside a create call). * Note that this is just a cast for the type system, this is, it won't actually convert an instance to a refererence snapshot, * but just fool typescript into thinking so. * * Example: * ```ts * const ModelA = types.model({ * id: types.identifier, * n: types.number * }).actions(self => ({ * setN(aNumber: number) { * self.n = aNumber * } * })) * * const ModelB = types.model({ * refA: types.reference(ModelA) * }) * * const a = ModelA.create({ id: 'someId', n: 5 }); * // this will allow the compiler to use a model as if it were a reference snapshot * const b = ModelB.create({ refA: castToReference(a)}) * ``` * * @param instance Instance * @returns The same object casted as an reference snapshot (string or number) */ function castToReferenceSnapshot(instance) { return instance; } /** * Returns the unique node id (not to be confused with the instance identifier) for a * given instance. * This id is a number that is unique for each instance. * * @export * @param target * @returns */ function getNodeId(target) { assertIsStateTreeNode(target, 1); return getStateTreeNode(target).nodeId; } /** * @internal * @hidden */ var BaseNode = /** @class */ (function () { function BaseNode(type, parent, subpath, environment) { this.type = type; this.environment = environment; this._state = NodeLifeCycle.INITIALIZING; this.environment = environment; this.baseSetParent(parent, subpath); } Object.defineProperty(BaseNode.prototype, "subpath", { get: function () { return this._subpath; }, enumerable: true, configurable: true }); Object.defineProperty(BaseNode.prototype, "subpathUponDeath", { get: function () { return this._subpathUponDeath; }, enumerable: true, configurable: true }); Object.defineProperty(BaseNode.prototype, "pathUponDeath", { get: function () { return this._pathUponDeath; }, enumerable: true, configurable: true }); Object.defineProperty(BaseNode.prototype, "value", { get: function () { return this.type.getValue(this); }, enumerable: true, configurable: true }); Object.defineProperty(BaseNode.prototype, "state", { get: function () { return this._state; }, set: function (val) { var wasAlive = this.isAlive; this._state = val; var isAlive = this.isAlive; if (this.aliveAtom && wasAlive !== isAlive) { this.aliveAtom.reportChanged(); } }, enumerable: true, configurable: true }); BaseNode.prototype.fireInternalHook = function (name) { if (this._hookSubscribers) { this._hookSubscribers.emit(name, this, name); } }; BaseNode.prototype.registerHook = function (hook, hookHandler) { if (!this._hookSubscribers) { this._hookSubscribers = new EventHandlers(); } return this._hookSubscribers.register(hook, hookHandler); }; Object.defineProperty(BaseNode.prototype, "parent", { get: function () { return this._parent; }, enumerable: true, configurable: true }); BaseNode.prototype.baseSetParent = function (parent, subpath) { this._parent = parent; this._subpath = subpath; this._escapedSubpath = undefined; // regenerate when needed if (this.pathAtom) { this.pathAtom.reportChanged(); } }; Object.defineProperty(BaseNode.prototype, "path", { /* * Returns (escaped) path representation as string */ get: function () { return this.getEscapedPath(true); }, enumerable: true, configurable: true }); BaseNode.prototype.getEscapedPath = function (reportObserved) { if (reportObserved) { if (!this.pathAtom) { this.pathAtom = mobx.createAtom("path"); } this.pathAtom.reportObserved(); } if (!this.parent) return ""; // regenerate escaped subpath if needed if (this._escapedSubpath === undefined) { this._escapedSubpath = !this._subpath ? "" : escapeJsonPath(this._subpath); } return this.parent.getEscapedPath(reportObserved) + "/" + this._escapedSubpath; }; Object.defineProperty(BaseNode.prototype, "isRoot", { get: function () { return this.parent === null; }, enumerable: true, configurable: true }); Object.defineProperty(BaseNode.prototype, "isAlive", { get: function () { return this.state !== NodeLifeCycle.DEAD; }, enumerable: true, configurable: true }); Object.defineProperty(BaseNode.prototype, "isDetaching", { get: function () { return this.state === NodeLifeCycle.DETACHING; }, enumerable: true, configurable: true }); Object.defineProperty(BaseNode.prototype, "observableIsAlive", { get: function () { if (!this.aliveAtom) { this.aliveAtom = mobx.createAtom("alive"); } this.aliveAtom.reportObserved(); return this.isAlive; }, enumerable: true, configurable: true }); BaseNode.prototype.baseFinalizeCreation = function (whenFinalized) { if (devMode()) { if (!this.isAlive) { // istanbul ignore next throw fail("assertion failed: cannot finalize the creation of a node that is already dead"); } } // goal: afterCreate hooks runs depth-first. After attach runs parent first, so on afterAttach the parent has completed already if (this.state === NodeLifeCycle.CREATED) { if (this.parent) { if (this.parent.state !== NodeLifeCycle.FINALIZED) { // parent not ready yet, postpone return; } this.fireHook(Hook.afterAttach); } this.state = NodeLifeCycle.FINALIZED; if (whenFinalized) { whenFinalized(); } } }; BaseNode.prototype.baseFinalizeDeath = function () { if (this._hookSubscribers) { this._hookSubscribers.clearAll(); } this._subpathUponDeath = this._subpath; this._pathUponDeath = this.getEscapedPath(false); this.baseSetParent(null, ""); this.state = NodeLifeCycle.DEAD; }; BaseNode.prototype.baseAboutToDie = function () { this.fireHook(Hook.beforeDestroy); }; return BaseNode; }()); /** * @internal * @hidden */ var ScalarNode = /** @class */ (function (_super) { __extends(ScalarNode, _super); function ScalarNode(simpleType, parent, subpath, environment, initialSnapshot) { var _this = _super.call(this, simpleType, parent, subpath, environment) || this; try { _this.storedValue = simpleType.createNewInstance(initialSnapshot); } catch (e) { // short-cut to die the instance, to avoid the snapshot computed starting to throw... _this.state = NodeLifeCycle.DEAD; throw e; } _this.state = NodeLifeCycle.CREATED; // for scalar nodes there's no point in firing this event since it would fire on the constructor, before // anybody can actually register for/listen to it // this.fireHook(Hook.AfterCreate) _this.finalizeCreation(); return _this; } Object.defineProperty(ScalarNode.prototype, "root", { get: function () { // future optimization: store root ref in the node and maintain it if (!this.parent) throw fail$1("This scalar node is not part of a tree"); return this.parent.root; }, enumerable: true, configurable: true }); ScalarNode.prototype.setParent = function (newParent, subpath) { var parentChanged = this.parent !== newParent; var subpathChanged = this.subpath !== subpath; if (!parentChanged && !subpathChanged) { return; } if (devMode()) { if (!subpath) { // istanbul ignore next throw fail$1("assertion failed: subpath expected"); } if (!newParent) { // istanbul ignore next throw fail$1("assertion failed: parent expected"); } if (parentChanged) { // istanbul ignore next throw fail$1("assertion failed: scalar nodes cannot change their parent"); } } this.environment = undefined; // use parent's this.baseSetParent(this.parent, subpath); }; Object.defineProperty(ScalarNode.prototype, "snapshot", { get: function () { return freeze(this.getSnapshot()); }, enumerable: true, configurable: true }); ScalarNode.prototype.getSnapshot = function () { return this.type.getSnapshot(this); }; ScalarNode.prototype.toString = function () { var path = (this.isAlive ? this.path : this.pathUponDeath) || "<root>"; return this.type.name + "@" + path + (this.isAlive ? "" : " [dead]"); }; ScalarNode.prototype.die = function () { if (!this.isAlive || this.state === NodeLifeCycle.DETACHING) return; this.aboutToDie(); this.finalizeDeath(); }; ScalarNode.prototype.finalizeCreation = function () { this.baseFinalizeCreation(); }; ScalarNode.prototype.aboutToDie = function () { this.baseAboutToDie(); }; ScalarNode.prototype.finalizeDeath = function () { this.baseFinalizeDeath(); }; ScalarNode.prototype.fireHook = function (name) { this.fireInternalHook(name); }; __decorate([ mobx.action ], ScalarNode.prototype, "die", null); return ScalarNode; }(BaseNode)); var nextNodeId = 1; var snapshotReactionOptions = { onError: function (e) { throw e; } }; /** * @internal * @hidden */ var ObjectNode = /** @class */ (function (_super) { __extends(ObjectNode, _super); function ObjectNode(complexType, parent, subpath, environment, initialValue) { var _this = _super.call(this, complexType, parent, subpath, environment) || this; _this.nodeId = ++nextNodeId; _this.isProtectionEnabled = true; _this._autoUnbox = true; // unboxing is disabled when reading child nodes _this._isRunningAction = false; // only relevant for root _this._hasSnapshotReaction = false; _this._observableInstanceState = 0 /* UNINITIALIZED */; _this._cachedInitialSnapshotCreated = false; _this.unbox = _this.unbox.bind(_this); _this._initialSnapshot = freeze(initialValue); _this.identifierAttribute = complexType.identifierAttribute; if (!parent) { _this.identifierCache = new IdentifierCache(); } _this._childNodes = complexType.initializeChildNodes(_this, _this._initialSnapshot); // identifier can not be changed during lifecycle of a node // so we safely can read it from initial snapshot _this.identifier = null; _this.unnormalizedIdentifier = null; if (_this.identifierAttribute && _this._initialSnapshot) { var id = _this._initialSnapshot[_this.identifierAttribute]; if (id === undefined) { // try with the actual node if not (for optional identifiers) var childNode = _this._childNodes[_this.identifierAttribute]; if (childNode) { id = childNode.value; } } if (typeof id !== "string" && typeof id !== "number") { throw fail$1("Instance identifier '" + _this.identifierAttribute + "' for type '" + _this.type.name + "' must be a string or a number"); } // normalize internal identifier to string _this.identifier = normalizeIdentifier(id); _this.unnormalizedIdentifier = id; } if (!parent) { _this.identifierCache.addNodeToCache(_this); } else { parent.root.identifierCache.addNodeToCache(_this); } return _this; } ObjectNode.prototype.applyPatches = function (patches) { this.createObservableInstanceIfNeeded(); this._applyPatches(patches); }; ObjectNode.prototype.applySnapshot = function (snapshot) { this.createObservableInstanceIfNeeded(); this._applySnapshot(snapshot); }; ObjectNode.prototype.createObservableInstanceIfNeeded = function () { var e_1, _a; if (this._observableInstanceState !== 0 /* UNINITIALIZED */) { return; } if (devMode()) { if (this.state !== NodeLifeCycle.INITIALIZING) { // istanbul ignore next throw fail$1("assertion failed: the creation of the observable instance must be done on the initializing phase"); } } this._observableInstanceState = 1 /* CREATING */; // make sure the parent chain is created as well // array with parent chain from parent to child var parentChain = []; var parent = this.parent; // for performance reasons we never go back further than the most direct // uninitialized parent // this is done to avoid traversing the whole tree to the root when using // the same reference again while (parent && parent._observableInstanceState === 0 /* UNINITIALIZED */) { parentChain.unshift(parent); parent = parent.parent; } try { // initialize the uninitialized parent chain from parent to child for (var parentChain_1 = __values(parentChain), parentChain_1_1 = parentChain_1.next(); !parentChain_1_1.done; parentChain_1_1 = parentChain_1.next()) { var p = parentChain_1_1.value; p.createObservableInstanceIfNeeded(); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (parentChain_1_1 && !parentChain_1_1.done && (_a = parentChain_1.return)) _a.call(parentChain_1); } finally { if (e_1) throw e_1.error; } } var type = this.type; try { this.storedValue = type.createNewInstance(this._childNodes); this.preboot(); this._isRunningAction = true; type.finalizeNewInstance(this, this.storedValue); } catch (e) { // short-cut to die the instance, to avoid the snapshot computed starting to throw... this.state = NodeLifeCycle.DEAD; throw e; } finally { this._isRunningAction = false; } this._observableInstanceState = 2 /* CREATED */; // NOTE: we need to touch snapshot, because non-observable // "_observableInstanceState" field was touched invalidateComputed(this, "snapshot"); if (this.isRoot) this._addSnapshotReaction(); this._childNodes = EMPTY_OBJECT; this.state = NodeLifeCycle.CREATED; this.fireHook(Hook.afterCreate); this.finalizeCreation(); }; Object.defineProperty(ObjectNode.prototype, "root", { get: function () { var parent = this.parent; return parent ? parent.root : this; }, enumerable: true, configurable: true }); ObjectNode.prototype.clearParent = function () { if (!this.parent) return; // detach if attached this.fireHook(Hook.beforeDetach); var previousState = this.state; this.state = NodeLifeCycle.DETACHING; var root = this.root; var newEnv = root.environment; var newIdCache = root.identifierCache.splitCache(this); try { this.parent.removeChild(this.subpath); this.baseSetParent(null, ""); this.environment = newEnv; this.identifierCache = newIdCache; } finally { this.state = previousState; } }; ObjectNode.prototype.setParent = function (newParent, subpath) { var parentChanged = newParent !== this.parent; var subpathChanged = subpath !== this.subpath; if (!parentChanged && !subpathChanged) { return; } if (devMode()) { if (!subpath) { // istanbul ignore next throw fail$1("assertion failed: subpath expected"); } if (!newParent) { // istanbul ignore next throw fail$1("assertion failed: new parent expected"); } if (this.parent && parentChanged) { throw fail$1("A node cannot exists twice in the state tree. Failed to add " + this + " to path '" + newParent.path + "/" + subpath + "'."); } if (!this.parent && newParent.root === this) { throw fail$1("A state tree is not allowed to contain itself. Cannot assign " + this + " to path '" + newParent.path + "/" + subpath + "'"); } if (!this.parent && !!this.environment && this.environment !== newParent.root.environment) { throw fail$1("A state tree cannot be made part of another state tree as long as their environments are different."); } } if (parentChanged) { // attach to new parent this.environment = undefined; // will use root's newParent.root.identifierCache.mergeCache(this); this.baseSetParent(newParent, subpath); this.fireHook(Hook.afterAttach); } else if (subpathChanged) { // moving to a new subpath on the same parent this.baseSetParent(this.parent, subpath); } }; ObjectNode.prototype.fireHook = function (name) { var _this = this; this.fireInternalHook(name); var fn = this.storedValue && typeof this.storedValue === "object" && this.storedValue[name]; if (typeof fn === "function") { // we check for it to allow old mobx peer dependencies that don't have the method to work (even when still bugged) if (mobx._allowStateChangesInsideComputed) { mobx._allowStateChangesInsideComputed(function () { fn.apply(_this.storedValue); }); } else { fn.apply(this.storedValue); } } }; Object.defineProperty(ObjectNode.prototype, "snapshot", { // advantage of using computed for a snapshot is that nicely respects transactions etc. get: function () { return freeze(this.getSnapshot()); }, enumerable: true, configurable: true }); // NOTE: we use this method to get snapshot without creating @computed overhead ObjectNode.prototype.getSnapshot = function () { if (!this.isAlive) return this._snapshotUponDeath; return this._observableInstanceState === 2 /* CREATED */ ? this._getActualSnapshot() : this._getCachedInitialSnapshot(); }; ObjectNode.prototype._getActualSnapshot = function () { return this.type.getSnapshot(this); }; ObjectNode.prototype._getCachedInitialSnapshot = function () { if (!this._cachedInitialSnapshotCreated) { var type = this.type; var childNodes = this._childNodes; var snapshot = this._initialSnapshot; this._cachedInitialSnapshot = type.processInitialSnapshot(childNodes, snapshot); this._cachedInitialSnapshotCreated = true; } return this._cachedInitialSnapshot; }; ObjectNode.prototype.isRunningAction = function () { if (this._isRunningAction) return true; if (this.isRoot) return false; return this.parent.isR