UNPKG

mobx-state-tree

Version:

Opinionated, transactional, MobX powered state container

1,496 lines (1,475 loc) 326 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); } /** * @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. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ 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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); 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 __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } 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 __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; /** * 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 deeply 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 a 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: [], inversePatches: [] }; // 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.inversePatches.slice().reverse(); } return publicData.reversedInversePatches; }, get inversePatches() { if (!publicData.inversePatches) { publicData.inversePatches = data.inversePatches.slice(); } 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.inversePatches.push(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.inversePatches.slice().reverse()); } }; 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 new MstError("`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 new MstError("`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 new MstError("Failed to find the parent of ".concat(getStateTreeNode(target), " at depth ").concat(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 new MstError("Failed to find the parent of ".concat(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 === null || node === void 0 ? void 0 : node.value; } /** * 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 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 new MstError("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 new MstError("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. * Shorthand 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, or throws. For more info on environments, * see [Dependency injection](/concepts/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) throw new MstError("Failed to find the environment of ".concat(node, " ").concat(node.path)); return env; } /** * Returns whether the current state tree has environment or not. * * @export * @param {IStateTreeNode} target * @return {boolean} */ function hasEnv(target) { // check all arguments if (process.env.NODE_ENV !== "production") { if (!isStateTreeNode(target)) throw new MstError("expected first argument to be a mobx-state-tree node, got " + target + " instead"); } var node = getStateTreeNode(target); var env = node.root.environment; 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 state, * and actions. `flowActions` is also provided as a separate array of names for any action that * came from a flow generator as well. * * In the case where a model has two actions: `doSomething` and `doSomethingWithFlow`, where * `doSomethingWithFlow` is a flow generator, the `actions` array will contain both actions, * i.e. ["doSomething", "doSomethingWithFlow"], and the `flowActions` array will contain only * the flow action, i.e. ["doSomethingWithFlow"]. * * @param target * @returns */ function getMembers(target) { var type = getStateTreeNode(target).type; var reflected = __assign(__assign({}, getPropertyMembers(type)), { actions: [], volatile: [], views: [], flowActions: [] }); 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._isFlowAction === true) { reflected.flowActions.push(key); } 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 cast as an instance */ function cast(snapshotOrInstance) { return snapshotOrInstance; } /** * Casts a node instance type to a 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 cast 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 reference 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 reference 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: castToReferenceSnapshot(a)}) * ``` * * @param instance Instance * @returns The same object cast as a 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) { Object.defineProperty(this, "type", { enumerable: true, configurable: true, writable: true, value: type }); Object.defineProperty(this, "environment", { enumerable: true, configurable: true, writable: true, value: environment }); Object.defineProperty(this, "_escapedSubpath", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_subpath", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_subpathUponDeath", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_pathUponDeath", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "storedValue", { enumerable: true, configurable: true, writable: true, value: void 0 }); // usually the same type as the value, but not always (such as with references) Object.defineProperty(this, "aliveAtom", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_state", { enumerable: true, configurable: true, writable: true, value: NodeLifeCycle.INITIALIZING }); Object.defineProperty(this, "_hookSubscribers", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_parent", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "pathAtom", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.environment = environment; this.baseSetParent(parent, subpath); } Object.defineProperty(BaseNode.prototype, "subpath", { get: function () { return this._subpath; }, enumerable: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "subpathUponDeath", { get: function () { return this._subpathUponDeath; }, enumerable: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "pathUponDeath", { get: function () { return this._pathUponDeath; }, enumerable: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "value", { get: function () { return this.type.getValue(this); }, enumerable: false, 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: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "fireInternalHook", { enumerable: false, configurable: true, writable: true, value: function (name) { if (this._hookSubscribers) { this._hookSubscribers.emit(name, this, name); } } }); Object.defineProperty(BaseNode.prototype, "registerHook", { enumerable: false, configurable: true, writable: true, value: 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: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "getReconciliationType", { enumerable: false, configurable: true, writable: true, value: function () { return this.type; } }); Object.defineProperty(BaseNode.prototype, "baseSetParent", { enumerable: false, configurable: true, writable: true, value: 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: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "getEscapedPath", { enumerable: false, configurable: true, writable: true, value: 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: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "isAlive", { get: function () { return this.state !== NodeLifeCycle.DEAD; }, enumerable: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "isDetaching", { get: function () { return this.state === NodeLifeCycle.DETACHING; }, enumerable: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "observableIsAlive", { get: function () { if (!this.aliveAtom) { this.aliveAtom = mobx.createAtom("alive"); } this.aliveAtom.reportObserved(); return this.isAlive; }, enumerable: false, configurable: true }); Object.defineProperty(BaseNode.prototype, "baseFinalizeCreation", { enumerable: false, configurable: true, writable: true, value: function (whenFinalized) { if (devMode()) { if (!this.isAlive) { // istanbul ignore next throw new MstError("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(); } } } }); Object.defineProperty(BaseNode.prototype, "baseFinalizeDeath", { enumerable: false, configurable: true, writable: true, value: function () { if (this._hookSubscribers) { this._hookSubscribers.clearAll(); } this._subpathUponDeath = this._subpath; this._pathUponDeath = this.getEscapedPath(false); this.baseSetParent(null, ""); this.state = NodeLifeCycle.DEAD; } }); Object.defineProperty(BaseNode.prototype, "baseAboutToDie", { enumerable: false, configurable: true, writable: true, value: 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 new MstError("This scalar node is not part of a tree"); return this.parent.root; }, enumerable: false, configurable: true }); Object.defineProperty(ScalarNode.prototype, "setParent", { enumerable: false, configurable: true, writable: true, value: 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 new MstError("assertion failed: subpath expected"); } if (!newParent) { // istanbul ignore next throw new MstError("assertion failed: parent expected"); } if (parentChanged) { // istanbul ignore next throw new MstError("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: false, configurable: true }); Object.defineProperty(ScalarNode.prototype, "getSnapshot", { enumerable: false, configurable: true, writable: true, value: function () { return this.type.getSnapshot(this); } }); Object.defineProperty(ScalarNode.prototype, "toString", { enumerable: false, configurable: true, writable: true, value: function () { var path = (this.isAlive ? this.path : this.pathUponDeath) || "<root>"; return "".concat(this.type.name, "@").concat(path).concat(this.isAlive ? "" : " [dead]"); } }); Object.defineProperty(ScalarNode.prototype, "die", { enumerable: false, configurable: true, writable: true, value: function () { if (!this.isAlive || this.state === NodeLifeCycle.DETACHING) return; this.aboutToDie(); this.finalizeDeath(); } }); Object.defineProperty(ScalarNode.prototype, "finalizeCreation", { enumerable: false, configurable: true, writable: true, value: function () { this.baseFinalizeCreation(); } }); Object.defineProperty(ScalarNode.prototype, "aboutToDie", { enumerable: false, configurable: true, writable: true, value: function () { this.baseAboutToDie(); } }); Object.defineProperty(ScalarNode.prototype, "finalizeDeath", { enumerable: false, configurable: true, writable: true, value: function () { this.baseFinalizeDeath(); } }); Object.defineProperty(ScalarNode.prototype, "fireHook", { enumerable: false, configurable: true, writable: true, value: function (name) { this.fireInternalHook(name); } }); return ScalarNode; }(BaseNode)); ScalarNode.prototype.die = mobx.action(ScalarNode.prototype.die); var nextNodeId = 1; var ObservableInstanceLifecycle; (function (ObservableInstanceLifecycle) { // the actual observable instance has not been created yet ObservableInstanceLifecycle[ObservableInstanceLifecycle["UNINITIALIZED"] = 0] = "UNINITIALIZED"; // the actual observable instance is being created ObservableInstanceLifecycle[ObservableInstanceLifecycle["CREATING"] = 1] = "CREATING"; // the actual observable instance has been created ObservableInstanceLifecycle[ObservableInstanceLifecycle["CREATED"] = 2] = "CREATED"; })(ObservableInstanceLifecycle || (ObservableInstanceLifecycle = {})); var InternalEvents; (function (InternalEvents) { InternalEvents["Dispose"] = "dispose"; InternalEvents["Patch"] = "patch"; InternalEvents["Snapshot"] = "snapshot"; })(InternalEvents || (InternalEvents = {})); 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; Object.defineProperty(_this, "nodeId", { enumerable: true, configurable: true, writable: true, value: ++nextNodeId }); Object.defineProperty(_this, "identifierAttribute", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(_this, "identifier", { enumerable: true, configurable: true, writable: true, value: void 0 }); // Identifier is always normalized to string, even if the identifier property isn't Object.defineProperty(_this, "unnormalizedIdentifier", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(_this, "identifierCache", { enumerable: true, configurable: true, writable: true, value: void 0