mobx-state-tree
Version:
Opinionated, transactional, MobX powered state container
1,465 lines (1,448 loc) • 232 kB
JavaScript
import { isComputedProp, isObservableProp, createAtom, action, _allowStateChangesInsideComputed, reaction, computed, runInAction, observable, values, entries, isObservableArray, getAtom, $mobx, ObservableMap, _interceptReads, intercept, observe, _getAdministration, set } from '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 (isComputedProp(target, key))
reflected.views.push(key);
else
reflected.volatile.push(key);
return;
}
if (descriptor.value._isMSTAction === true)
reflected.actions.push(key);
else if (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 = 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 = 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([
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 (_allowStateChangesInsideComputed) {
_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 ()