UNPKG

mobx-keystone-mindreframer

Version:

A MobX powered state management solution based on data trees with first class support for Typescript, snapshots, patches and much more

2,236 lines (1,860 loc) 316 kB
import * as mobx from 'mobx'; import { isObservableMap, isObservableSet, isObservableArray, isObservableObject, createAtom, runInAction, computed, action, observable, remove, reaction, set, untracked, isObservable, isAction, intercept, observe, when, toJS, values, get, has, keys, entries } from 'mobx'; import { v4 } from 'uuid'; import { __decorate } from 'tslib'; import fastDeepEqual from 'fast-deep-equal/es6'; var dataModelActionRegistry = new Map(); /** * @ignore * @internal */ function getDataModelAction(fullActionName) { return dataModelActionRegistry.get(fullActionName); } /** * @ignore * @internal */ function setDataModelAction(fullActionName, modelClass, fnName) { dataModelActionRegistry.set(fullActionName, { modelClass: modelClass, fnName: fnName }); } /** * A built-in action. */ var BuiltInAction; (function (BuiltInAction) { /** * applyPatches */ BuiltInAction["ApplyPatches"] = "$$applyPatches"; /** * applySnapshot */ BuiltInAction["ApplySnapshot"] = "$$applySnapshot"; /** * detach */ BuiltInAction["Detach"] = "$$detach"; /** * applySet */ BuiltInAction["ApplySet"] = "$$applySet"; /** * applyDelete */ BuiltInAction["ApplyDelete"] = "$$applyDelete"; /** * applyMethodCall */ BuiltInAction["ApplyMethodCall"] = "$$applyMethodCall"; })(BuiltInAction || (BuiltInAction = {})); var builtInActionValues = new Set(Object.values(BuiltInAction)); /** * Returns if a given action name is a built-in action, this is, one of: * - applyPatches() * - applySnapshot() * - detach() * * @param actionName Action name to check. * @returns true if it is a built-in action, false otherwise. */ function isBuiltInAction(actionName) { return builtInActionValues.has(actionName); } /** * Action type, sync or async. */ var ActionContextActionType; (function (ActionContextActionType) { ActionContextActionType["Sync"] = "sync"; ActionContextActionType["Async"] = "async"; })(ActionContextActionType || (ActionContextActionType = {})); /** * An async step type. */ var ActionContextAsyncStepType; (function (ActionContextAsyncStepType) { /** * The flow is about to start. */ ActionContextAsyncStepType["Spawn"] = "spawn"; /** * The flow is about to return (finish). */ ActionContextAsyncStepType["Return"] = "return"; /** * The flow is about to continue. */ ActionContextAsyncStepType["Resume"] = "resume"; /** * The flow yield just threw, which might be recovered (caught) or not. */ ActionContextAsyncStepType["ResumeError"] = "resumeError"; /** * The flow is about to throw an error to the flow caller. */ ActionContextAsyncStepType["Throw"] = "throw"; })(ActionContextAsyncStepType || (ActionContextAsyncStepType = {})); var currentActionContext; /** * Gets the currently running action context, or undefined if none. * * @returns */ function getCurrentActionContext() { return currentActionContext; } /** * @ignore * @internal * * Sets the current action context. * * @param ctx Current action context. */ function setCurrentActionContext(ctx) { currentActionContext = ctx; } /** * @ignore * @internal */ var modelActionSymbol = Symbol("modelAction"); /** * Returns if the given function is a model action or not. * * @param fn Function to check. * @returns */ function isModelAction(fn) { return typeof fn === "function" && !!fn[modelActionSymbol]; } /** * Key where model snapshots will store model type metadata. */ var modelTypeKey = "$modelType"; /** * Key where model snapshots usually will store model internal IDs metadata (unless overridden). */ var modelIdKey = "$modelId"; /** * @internal * Returns if a given key is a reserved key in model snapshots. * * @param key * @returns */ function isReservedModelKey(key) { // note $modelId is NOT a reserved key, since it will eventually end up in the data // and can actually be changed return key === modelTypeKey; } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; } function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var id = 0; function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; } function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; } /** * A mobx-keystone error. */ var MobxKeystoneError = /*#__PURE__*/function (_Error) { _inheritsLoose(MobxKeystoneError, _Error); function MobxKeystoneError(msg) { var _this; _this = _Error.call(this, msg) || this; // Set the prototype explicitly. Object.setPrototypeOf(_assertThisInitialized(_this), MobxKeystoneError.prototype); return _this; } return MobxKeystoneError; }( /*#__PURE__*/_wrapNativeSuper(Error)); /** * @ignore * @internal */ function failure(msg) { return new MobxKeystoneError(msg); } var writableHiddenPropDescriptor = { enumerable: false, writable: true, configurable: false, value: undefined }; /** * @ignore * @internal */ function addHiddenProp(object, propName, value, writable) { if (writable === void 0) { writable = true; } if (writable) { Object.defineProperty(object, propName, writableHiddenPropDescriptor); object[propName] = value; } else { Object.defineProperty(object, propName, { enumerable: false, writable: writable, configurable: true, value: value }); } } /** * @ignore * @internal */ function makePropReadonly(object, propName, enumerable) { var propDesc = Object.getOwnPropertyDescriptor(object, propName); if (propDesc) { propDesc.enumerable = enumerable; if (propDesc.get) { delete propDesc.set; } else { propDesc.writable = false; } Object.defineProperty(object, propName, propDesc); } } /** * @ignore * @internal */ function isPlainObject(value) { if (!isObject(value)) return false; var proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; } /** * @ignore * @internal */ function isObject(value) { return value !== null && typeof value === "object"; } /** * @ignore * @internal */ function isPrimitive(value) { switch (typeof value) { case "number": case "string": case "boolean": case "undefined": case "bigint": return true; } return value === null; } /** * @ignore * @internal */ function debugFreeze(value) { if (inDevMode()) { Object.freeze(value); } } /** * @ignore * @internal */ function deleteFromArray(array, value) { var index = array.indexOf(value); if (index >= 0) { array.splice(index, 1); return true; } return false; } /** * @ignore * @internal */ function isMap(val) { return val instanceof Map || isObservableMap(val); } /** * @ignore * @internal */ function isSet(val) { return val instanceof Set || isObservableSet(val); } /** * @ignore * @internal */ function isArray(val) { return Array.isArray(val) || isObservableArray(val); } /** * @ignore * @internal */ function inDevMode() { return process.env.NODE_ENV !== "production"; } /** * @ignore * @internal */ function assertIsObject(value, argName) { if (!isObject(value)) { throw failure(argName + " must be an object"); } } /** * @ignore * @internal */ function assertIsPlainObject(value, argName) { if (!isPlainObject(value)) { throw failure(argName + " must be a plain object"); } } /** * @ignore * @internal */ function assertIsObservableObject(value, argName) { if (!isObservableObject(value)) { throw failure(argName + " must be an observable object"); } } /** * @ignore * @internal */ function assertIsObservableArray(value, argName) { if (!isObservableArray(value)) { throw failure(argName + " must be an observable array"); } } /** * @ignore * @internal */ function assertIsMap(value, argName) { if (!isMap(value)) { throw failure(argName + " must be a map"); } } /** * @ignore * @internal */ function assertIsSet(value, argName) { if (!isSet(value)) { throw failure(argName + " must be a set"); } } /** * @ignore * @internal */ function assertIsFunction(value, argName) { if (typeof value !== "function") { throw failure(argName + " must be a function"); } } /** * @ignore * @internal */ function assertIsPrimitive(value, argName) { if (!isPrimitive(value)) { throw failure(argName + " must be a primitive"); } } /** * @ignore * @internal */ function assertIsString(value, argName) { if (typeof value !== "string") { throw failure(argName + " must be a string"); } } /** * @ignore * @internal */ var runAfterNewSymbol = Symbol("runAfterNew"); /** * @ignore * @internal */ function addLateInitializationFunction(target, symbol, fn) { var array = target[symbol]; if (!array) { array = []; addHiddenProp(target, symbol, array); } array.push(fn); } /** * @ignore * @internal */ function decorateWrapMethodOrField(decoratorName, data, wrap) { var target = data.target, propertyKey = data.propertyKey, baseDescriptor = data.baseDescriptor; var addFieldDecorator = function addFieldDecorator() { addLateInitializationFunction(target, runAfterNewSymbol, function (instance) { instance[propertyKey] = wrap(data, instance[propertyKey]); }); }; if (baseDescriptor) { if (baseDescriptor.get !== undefined) { throw failure("@" + decoratorName + " cannot be used with getters"); } if (baseDescriptor.value) { // babel / typescript - method decorator // @action method() { } return { enumerable: false, writable: true, configurable: true, value: wrap(data, baseDescriptor.value) }; } else { // babel - field decorator: @action method = () => {} addFieldDecorator(); } } else { // typescript - field decorator addFieldDecorator(); } } /** * @ignore * @internal */ function runLateInitializationFunctions(target, symbol) { var fns = target[symbol]; if (fns) { for (var _iterator = _createForOfIteratorHelperLoose(fns), _step; !(_step = _iterator()).done;) { var fn = _step.value; fn(target); } } } var warningsAlreadyDisplayed = new Set(); /** * @ignore * @internal */ function logWarning(type, msg, uniqueKey) { if (uniqueKey) { if (warningsAlreadyDisplayed.has(uniqueKey)) { return; } warningsAlreadyDisplayed.add(uniqueKey); } msg = "[mobx-keystone] " + msg; switch (type) { case "warn": console.warn(msg); break; case "error": console.error(msg); break; default: throw failure("unknown log type - " + type); } } var notMemoized = Symbol("notMemoized"); /** * @ignore * @internal */ function lateVal(getter) { var memoized = notMemoized; var fn = function fn() { if (memoized === notMemoized) { memoized = getter.apply(void 0, [].slice.call(arguments)); } return memoized; }; return fn; } /** * @ignore * @internal */ function lazy(valueGen) { var inited = false; var val; return function () { if (!inited) { val = valueGen(); inited = true; } return val; }; } /** * @ignore * @internal */ var mobx6 = { // eslint-disable-next-line no-useless-concat makeObservable: mobx[// just to ensure import * is kept properly String.fromCharCode("l".charCodeAt(0) + 1) + "akeObservable"] }; /** * @ignore * @internal */ function propNameToSetterName(propName) { return "set" + propName[0].toUpperCase() + propName.slice(1); } /** * @ignore * @internal */ function getMobxVersion() { if (mobx6.makeObservable) { return 6; } else { return 5; } } // this file only purpose is to break cyclic dependencies /** * @ignore * @internal */ var _BaseModel; /** * @ignore * @internal */ function setBaseModel(baseModelClass) { _BaseModel = baseModelClass; } /** * Checks if an object is a model instance. * * @param model * @returns */ function isModel(model) { return model instanceof _BaseModel; } /** * @ignore * @internal * * Asserts something is actually a model. * * @param model * @param argName */ function assertIsModel(model, argName, customErrMsg) { if (customErrMsg === void 0) { customErrMsg = "must be a model instance"; } if (!isModel(model)) { throw failure(argName + " " + customErrMsg); } } /** * @ignore * @internal */ function isModelClass(modelClass) { if (typeof modelClass !== "function") { return false; } if (modelClass !== _BaseModel && !(modelClass.prototype instanceof _BaseModel)) { return false; } return true; } /** * @ignore * @internal */ function assertIsModelClass(modelClass, argName) { if (typeof modelClass !== "function") { throw failure(argName + " must be a class"); } if (modelClass !== _BaseModel && !(modelClass.prototype instanceof _BaseModel)) { throw failure(argName + " must extend Model"); } } /** * @ignore * @internal */ function isModelSnapshot(sn) { return isPlainObject(sn) && !!sn[modelTypeKey]; } function getOrCreate(map, key, create) { var value = map.get(key); if (value === undefined) { value = create(); map.set(key, value); } return value; } /** * @ignore */ var objectParents = new WeakMap(); /** * @ignore */ var objectParentsAtoms = new WeakMap(); /** * @ignore */ function parentPathEquals(parentPath1, parentPath2, comparePath) { if (comparePath === void 0) { comparePath = true; } if (!parentPath1 && !parentPath2) return true; if (!parentPath1 || !parentPath2) return false; var parentEquals = parentPath1.parent === parentPath2.parent; if (!parentEquals) return false; return comparePath ? parentPath1.path === parentPath2.path : true; } function createParentPathAtom(obj) { return getOrCreate(objectParentsAtoms, obj, function () { return createAtom("parentAtom"); }); } /** * @ignore */ function reportParentPathObserved(node) { createParentPathAtom(node).reportObserved(); } /** * @ignore */ function reportParentPathChanged(node) { createParentPathAtom(node).reportChanged(); } /** * @ignore */ var dataObjectParent = new WeakMap(); /** * @ignore */ function dataToModelNode(node) { var modelNode = dataObjectParent.get(node); return modelNode != null ? modelNode : node; } /** * @ignore */ function modelToDataNode(node) { return isModel(node) ? node.$ : node; } /** * @ignore * @internal */ var tweakedObjects = new WeakMap(); /** * @ignore * @internal */ function isTweakedObject(value, canBeDataObject) { if (!canBeDataObject && dataObjectParent.has(value)) { return false; } return tweakedObjects.has(value); } /** * Checks if a given object is now a tree node. * * @param value Value to check. * @returns true if it is a tree node, false otherwise. */ function isTreeNode(value) { return !isPrimitive(value) && isTweakedObject(value, false); } /** * @ignore * @internal */ function assertTweakedObject(treeNode, argName, canBeDataObject) { if (canBeDataObject === void 0) { canBeDataObject = false; } if (!canBeDataObject && dataObjectParent.has(treeNode)) { throw failure(argName + " must be the model object instance instead of the '$' sub-object"); } if (isPrimitive(treeNode) || !isTweakedObject(treeNode, true)) { throw failure(argName + " must be a tree node (usually a model or a shallow / deep child part of a model 'data' object)"); } } /** * Asserts a given object is now a tree node, or throws otherwise. * * @param value Value to check. * @param argName Argument name, part of the thrown error description. */ function assertIsTreeNode(value, argName) { if (argName === void 0) { argName = "argument"; } assertTweakedObject(value, argName, false); } /** * @ignore * @internal */ var runningWithoutSnapshotOrPatches = false; /** * @ignore * @internal */ function runWithoutSnapshotOrPatches(fn) { var old = runningWithoutSnapshotOrPatches; runningWithoutSnapshotOrPatches = true; try { runInAction(function () { fn(); }); } finally { runningWithoutSnapshotOrPatches = old; } } /** * Returns the parent of the target plus the path from the parent to the target, or undefined if it has no parent. * * @typeparam T Parent object type. * @param value Target object. * @returns */ function getParentPath(value) { assertTweakedObject(value, "value"); return fastGetParentPath(value); } /** * @ignore * @internal */ function fastGetParentPath(value) { reportParentPathObserved(value); return objectParents.get(value); } /** * @ignore * @internal */ function fastGetParentPathIncludingDataObjects(value) { var parentModel = dataObjectParent.get(value); if (parentModel) { return { parent: parentModel, path: "$" }; } var parentPath = fastGetParentPath(value); if (parentPath && isModel(parentPath.parent)) { return { parent: parentPath.parent.$, path: parentPath.path }; } return parentPath; } /** * Returns the parent object of the target object, or undefined if there's no parent. * * @typeparam T Parent object type. * @param value Target object. * @returns */ function getParent(value) { assertTweakedObject(value, "value"); return fastGetParent(value); } /** * @ignore * @internal */ function fastGetParent(value) { var parentPath = fastGetParentPath(value); return parentPath ? parentPath.parent : undefined; } /** * @ignore * @internal */ function fastGetParentIncludingDataObjects(value) { var parentPath = fastGetParentPathIncludingDataObjects(value); return parentPath ? parentPath.parent : undefined; } /** * Returns if a given object is a model interim data object (`$`). * * @param value Object to check. * @returns true if it is, false otherwise. */ function isModelDataObject(value) { assertTweakedObject(value, "value", true); return fastIsModelDataObject(value); } /** * @ignore * @internal */ function fastIsModelDataObject(value) { return dataObjectParent.has(value); } /** * Returns the root of the target plus the path from the root to get to the target. * * @typeparam T Root object type. * @param value Target object. * @returns */ function getRootPath(value) { assertTweakedObject(value, "value"); return fastGetRootPath(value); } // we use computeds so they are cached whenever possible var computedsGetRootPath = new WeakMap(); function internalGetRootPath(value) { var rootPath = { root: value, path: [], pathObjects: [value] }; var parentPath; while (parentPath = fastGetParentPath(rootPath.root)) { rootPath.root = parentPath.parent; rootPath.path.unshift(parentPath.path); rootPath.pathObjects.unshift(parentPath.parent); } return rootPath; } /** * @ignore * @internal */ function fastGetRootPath(value) { return getOrCreate(computedsGetRootPath, value, function () { return computed(function () { return internalGetRootPath(value); }); }).get(); } /** * Returns the root of the target object, or itself if the target is a root. * * @typeparam T Root object type. * @param value Target object. * @returns */ function getRoot(value) { assertTweakedObject(value, "value"); return fastGetRoot(value); } /** * @ignore * @internal */ function fastGetRoot(value) { return fastGetRootPath(value).root; } /** * Returns if a given object is a root object. * * @param value Target object. * @returns */ function isRoot(value) { assertTweakedObject(value, "value"); return !fastGetParent(value); } var unresolved = { resolved: false }; /** * Tries to resolve a path from an object. * * @typeparam T Returned value type. * @param pathRootObject Object that serves as path root. * @param path Path as an string or number array. * @returns An object with `{ resolved: true, value: T }` or `{ resolved: false }`. */ function resolvePath(pathRootObject, path) { // unit tests rely on this to work with any object // assertTweakedObject(pathRootObject, "pathRootObject") var current = modelToDataNode(pathRootObject); var len = path.length; for (var i = 0; i < len; i++) { if (!isObject(current)) { return unresolved; } var p = path[i]; // check just to avoid mobx warnings about trying to access out of bounds index if (isArray(current) && +p >= current.length) { return unresolved; } current = modelToDataNode(current[p]); } return { resolved: true, value: dataToModelNode(current) }; } /** * @ignore */ var skipIdChecking = Symbol("skipIdChecking"); /** * @ignore * * Tries to resolve a path from an object while checking ids. * * @typeparam T Returned value type. * @param pathRootObject Object that serves as path root. * @param path Path as an string or number array. * @param pathIds An array of ids of the models that must be checked, null if not a model or `skipIdChecking` to skip it. * @returns An object with `{ resolved: true, value: T }` or `{ resolved: false }`. */ function resolvePathCheckingIds(pathRootObject, path, pathIds) { // unit tests rely on this to work with any object // assertTweakedObject(pathRootObject, "pathRootObject") var current = modelToDataNode(pathRootObject); // root id is never checked var len = path.length; for (var i = 0; i < len; i++) { if (!isObject(current)) { return { resolved: false }; } var p = path[i]; // check just to avoid mobx warnings about trying to access out of bounds index if (isArray(current) && +p >= current.length) { return { resolved: false }; } var currentMaybeModel = current[p]; current = modelToDataNode(currentMaybeModel); var expectedId = pathIds[i]; if (expectedId !== skipIdChecking) { var currentId = isModel(currentMaybeModel) ? currentMaybeModel[modelIdKey] : null; if (expectedId !== currentId) { return { resolved: false }; } } } return { resolved: true, value: dataToModelNode(current) }; } /** * Gets the path to get from a parent to a given child. * Returns an empty array if the child is actually the given parent or undefined if the child is not a child of the parent. * * @param fromParent * @param toChild * @returns */ function getParentToChildPath(fromParent, toChild) { assertTweakedObject(fromParent, "fromParent"); assertTweakedObject(toChild, "toChild"); if (fromParent === toChild) { return []; } var path = []; var current = toChild; var parentPath; while (parentPath = fastGetParentPath(current)) { path.unshift(parentPath.path); current = parentPath.parent; if (current === fromParent) { return path; } } return undefined; } var defaultObservableSetOptions = { deep: false }; var objectChildren = new WeakMap(); /** * @ignore * @internal */ function initializeObjectChildren(node) { if (objectChildren.has(node)) { return; } objectChildren.set(node, { shallow: observable.set(undefined, defaultObservableSetOptions), deep: new Set(), extensionsData: initExtensionsData(), deepDirty: true, deepAtom: createAtom("deepChildrenAtom") }); } /** * @ignore * @internal */ function getObjectChildren(node) { return objectChildren.get(node).shallow; } /** * @ignore * @internal */ function getDeepObjectChildren(node) { var obj = objectChildren.get(node); if (obj.deepDirty) { updateDeepObjectChildren(node); } obj.deepAtom.reportObserved(); return obj; } function addNodeToDeepLists(node, data) { data.deep.add(node); extensions.forEach(function (extension, dataSymbol) { extension.addNode(node, data.extensionsData.get(dataSymbol)); }); } var updateDeepObjectChildren = action(function (node) { var obj = objectChildren.get(node); if (!obj.deepDirty) { return obj; } var data = { deep: new Set(), extensionsData: initExtensionsData() }; var childrenIter = getObjectChildren(node).values(); var ch = childrenIter.next(); while (!ch.done) { addNodeToDeepLists(ch.value, data); var ret = updateDeepObjectChildren(ch.value).deep; var retIter = ret.values(); var retCur = retIter.next(); while (!retCur.done) { addNodeToDeepLists(retCur.value, data); retCur = retIter.next(); } ch = childrenIter.next(); } Object.assign(obj, data); obj.deepDirty = false; obj.deepAtom.reportChanged(); return obj; }); /** * @ignore * @internal */ var addObjectChild = action(function (node, child) { var obj = objectChildren.get(node); obj.shallow.add(child); invalidateDeepChildren(node); }); /** * @ignore * @internal */ var removeObjectChild = action(function (node, child) { var obj = objectChildren.get(node); obj.shallow["delete"](child); invalidateDeepChildren(node); }); function invalidateDeepChildren(node) { var obj = objectChildren.get(node); if (!obj.deepDirty) { obj.deepDirty = true; obj.deepAtom.reportChanged(); } var parent = fastGetParent(node); if (parent) { invalidateDeepChildren(parent); } } /** * @ignore * @internal */ function byModelTypeAndIdKey(modelType, modelId) { return modelType + " " + modelId; } var extensions = new Map(); /** * @ignore * @internal */ function registerDeepObjectChildrenExtension(extension) { var dataSymbol = Symbol("deepObjectChildrenExtension"); extensions.set(dataSymbol, extension); return function (data) { return data.extensionsData.get(dataSymbol); }; } function initExtensionsData() { var extensionsData = new Map(); extensions.forEach(function (extension, dataSymbol) { extensionsData.set(dataSymbol, extension.initData()); }); return extensionsData; } /** * Returns if the target is a "child" of the tree of the given "parent" object. * * @param child Target object. * @param parent Parent object. * @returns */ function isChildOfParent(child, parent) { assertTweakedObject(child, "child"); assertTweakedObject(parent, "parent"); return getDeepObjectChildren(parent).deep.has(child); } /** * Returns if the target is a "parent" that has in its tree the given "child" object. * * @param parent Target object. * @param child Child object. * @returns */ function isParentOfChild(parent, child) { return isChildOfParent(child, parent); } var perObjectActionMiddlewares = new WeakMap(); var perObjectActionMiddlewaresIterator = new WeakMap(); /** * @ignore * @internal * * Gets the current action middlewares to be run over a given object as an iterable object. * * @returns */ function getActionMiddlewares(obj) { // when we call a middleware we will call the middlewares of that object plus all parent objects // the parent object middlewares are run last // since an array like [a, b, c] will be called like c(b(a())) this means that we need to put // the parent object ones at the end of the array var iterable = perObjectActionMiddlewaresIterator.get(obj); if (!iterable) { var _iterable; iterable = (_iterable = {}, _iterable[Symbol.iterator] = function () { var current = obj; function getCurrentIterator() { var objMwares = current ? perObjectActionMiddlewares.get(current) : undefined; if (!objMwares || objMwares.length <= 0) { return undefined; } return objMwares[Symbol.iterator](); } function findNextIterator() { var nextIter; while (current && !nextIter) { current = fastGetParent(current); nextIter = getCurrentIterator(); } return nextIter; } var iter = getCurrentIterator(); if (!iter) { iter = findNextIterator(); } var iterator = { next: function next() { if (!iter) { return { value: undefined, done: true }; } var result = iter.next(); if (!result.done) { return result; } iter = findNextIterator(); return this.next(); } }; return iterator; }, _iterable); perObjectActionMiddlewaresIterator.set(obj, iterable); } return iterable; } /** * Adds a global action middleware to be run when an action is performed. * It is usually preferable to use `onActionMiddleware` instead to limit it to a given tree and only to topmost level actions * or `actionTrackingMiddleware` for a simplified middleware. * * @param mware Action middleware to be run. * @returns A disposer to cancel the middleware. Note that if you don't plan to do an early disposal of the middleware * calling this function becomes optional. */ function addActionMiddleware(mware) { assertIsObject(mware, "middleware"); var middleware = mware.middleware, filter = mware.filter, subtreeRoot = mware.subtreeRoot; assertTweakedObject(subtreeRoot, "middleware.subtreeRoot"); assertIsFunction(middleware, "middleware.middleware"); if (filter && typeof filter !== "function") { throw failure("middleware.filter must be a function or undefined"); } // reminder: never turn middlewares into actions or else // reactions will not be picked up by the undo manager if (subtreeRoot) { var targetFilter = function targetFilter(ctx) { return ctx.target === subtreeRoot || isChildOfParent(ctx.target, subtreeRoot); }; if (!filter) { filter = targetFilter; } else { var customFilter = filter; filter = function filter(ctx) { return targetFilter(ctx) && customFilter(ctx); }; } } var actualMware = { middleware: middleware, filter: filter }; var objMwares = perObjectActionMiddlewares.get(subtreeRoot); if (!objMwares) { objMwares = [actualMware]; perObjectActionMiddlewares.set(subtreeRoot, objMwares); } else { objMwares.push(actualMware); } return function () { deleteFromArray(objMwares, actualMware); }; } /** * @ignore * @internal */ function canWrite() { return !getActionProtection() || !!getCurrentActionContext(); } /** * @ignore * @internal */ function assertCanWrite() { if (!canWrite()) { throw failure("data changes must be performed inside model actions"); } } var actionProtection = true; /** * @ignore * @internal * * Gets if the action protection is currently enabled or not. * * @returns */ function getActionProtection() { return actionProtection; } /** * @ignore * @internal * * Sets if the action protection is currently enabled or not. */ function setActionProtection(protection) { actionProtection = protection; } var pendingActions = []; function isActionRunning() { return !getActionProtection() || getCurrentActionContext(); } /** * @ignore */ function enqueuePendingAction(action) { // delay action until all current actions are finished if (isActionRunning()) { pendingActions.push(action); } else { action(); } } /** * @ignore */ function tryRunPendingActions() { if (isActionRunning()) { return false; } while (pendingActions.length > 0) { var nextAction = pendingActions.shift(); nextAction(); } return true; } /** * @ignore */ function wrapInAction(_ref) { var nameOrNameFn = _ref.nameOrNameFn, fn = _ref.fn, actionType = _ref.actionType, overrideContext = _ref.overrideContext, _ref$isFlowFinisher = _ref.isFlowFinisher, isFlowFinisher = _ref$isFlowFinisher === void 0 ? false : _ref$isFlowFinisher; var fnInAction = false; var wrappedAction = function wrappedAction() { var _fn; var name = typeof nameOrNameFn === "function" ? nameOrNameFn() : nameOrNameFn; if (!fnInAction) { fnInAction = true; // we need to make only inner actions actual mobx actions // so reactions (e.g. reference detaching) are picked up in the // right context fn = action(name, fn); } var target = this; var parentContext = getCurrentActionContext(); var context = { actionName: name, type: actionType, target: target, args: Array.from(arguments), parentContext: parentContext, data: {}, rootContext: undefined // will be set after the override }; if (overrideContext) { overrideContext(context, this); } if (!context.rootContext) { if (context.previousAsyncStepContext) { context.rootContext = context.previousAsyncStepContext.rootContext; } else if (context.parentContext) { context.rootContext = context.parentContext.rootContext; } else { context.rootContext = context; } } setCurrentActionContext(context); var mwareFn = (_fn = fn).bind.apply(_fn, [target].concat(Array.prototype.slice.call(arguments))); var mwareIter = getActionMiddlewares(context.target)[Symbol.iterator](); var mwareCur = mwareIter.next(); while (!mwareCur.done) { var mware = mwareCur.value; var filterPassed = mware.filter ? mware.filter(context) : true; if (filterPassed) { mwareFn = mware.middleware.bind(undefined, context, mwareFn); } mwareCur = mwareIter.next(); } try { var ret = mwareFn(); if (isFlowFinisher) { var flowFinisher = ret; var value = flowFinisher.value; if (flowFinisher.resolution === "accept") { flowFinisher.accepter(value); } else { flowFinisher.rejecter(value); } return value; // not sure if this is even needed } else { return ret; } } finally { setCurrentActionContext(context.parentContext); tryRunPendingActions(); } }; wrappedAction[modelActionSymbol] = true; return wrappedAction; } /** * @ignore */ function wrapModelMethodInActionIfNeeded(model, propertyKey, name) { var fn = model[propertyKey]; if (isModelAction(fn)) { return; } var wrappedFn = wrapInAction({ nameOrNameFn: name, fn: fn, actionType: ActionContextActionType.Sync }); var proto = Object.getPrototypeOf(model); var protoFn = proto[propertyKey]; if (protoFn === fn) { proto[propertyKey] = wrappedFn; } else { model[propertyKey] = wrappedFn; } } /** * Detaches a given object from a tree. * If the parent is an object / model, detaching will delete the property. * If the parent is an array detaching will remove the node by splicing it. * If there's no parent it will throw. * * @param node Object to be detached. */ function detach(node) { assertTweakedObject(node, "node"); wrappedInternalDetach().call(node); } var wrappedInternalDetach = lazy(function () { return wrapInAction({ nameOrNameFn: BuiltInAction.Detach, fn: internalDetach, actionType: ActionContextActionType.Sync }); }); function internalDetach() { var node = this; var parentPath = fastGetParentPathIncludingDataObjects(node); if (!parentPath) return; var parent = parentPath.parent, path = parentPath.path; if (isObservableArray(parent)) { parent.splice(+path, 1); } else if (isObservableObject(parent)) { remove(parent, "" + path); } else { throw failure("parent must be an observable object or an observable array"); } } /** * Returns all the children objects (this is, excluding primitives) of an object. * * @param node Object to get the list of children from. * @param [options] An optional object with the `deep` option (defaults to false) to true to get * the children deeply or false to get them shallowly. * @returns A readonly observable set with the children. */ function getChildrenObjects(node, options) { assertTweakedObject(node, "node"); if (!options || !options.deep) { return getObjectChildren(node); } else { return getDeepObjectChildren(node).deep; } } /** * Iterates through all children and collects them in a set if the * given predicate matches. * * @param root Root object to get the matching children from. * @param predicate Function that will be run for every child of the root object. * @param [options] An optional object with the `deep` option (defaults to `false`) set to `true` to * get the children deeply or `false` to get them shallowly. * @returns A readonly observable set with the matching children. */ function findChildren(root, predicate, options) { var children = getChildrenObjects(root, options); var set = new Set(); var iter = children.values(); var cur = iter.next(); while (!cur.done) { if (predicate(cur.value)) { set.add(cur.value); } cur = iter.next(); } return set; } /** * Iterates through all the parents (from the nearest until the root) * until one of them matches the given predicate. * If the predicate is matched it will return the found node. * If none is found it will return undefined. * * @typeparam T Parent object type. * @param child Target object. * @param predicate Function that will be run for every parent of the target object, from immediate parent to the root. * @param maxDepth Max depth, or 0 for infinite. * @returns */ function findParent(child, predicate, maxDepth) { if (maxDepth === void 0) { maxDepth = 0; } var foundParentPath = findParentPath(child, predicate, maxDepth); return foundParentPath ? foundParentPath.parent : undefined; } /** * Iterates through all the parents (from the nearest until the root) * until one of them matches the given predicate. * If the predicate is matched it will return the found node plus the * path to get from the parent to the child. * If none is found it will return undefined. * * @typeparam T Parent object type. * @param child Target object. * @param predicate Function that will be run for every parent of the target object, from immediate parent to the root. * @param maxDepth Max depth, or 0 for infinite. * @returns */ function findParentPath(child, predicate, maxDepth) { if (maxDepth === void 0) { maxDepth = 0; } assertTweakedObject(child, "child"); var path = []; var current = child; var depth = 0; var parentPath; while (parentPath = fastGetParentPath(current)) { path.unshift(parentPath.path); current = parentPath.parent; if (predicate(current)) { return { parent: current, path: path }; } depth++; if (maxDepth > 0 && depth === maxDepth) { break; } } return undefined; } /** * Runs a callback everytime a new object is attached to a given node. * The callback can optionally return a disposer which will be run when the child is detached. * * The optional options parameter accepts and object with the following options: * - `deep: boolean` (default: `false`) - true if the callback should be run for all children deeply * or false if it it should only run for shallow children. * - `fireForCurrentChildren: boolean` (default: `true`) - true if the callback should be immediately * called for currently attached children, false if only for future attachments. * * Returns a disposer, which has a boolean parameter which should be true if pending detachment * callbacks should be run or false otherwise. * * @param target Function that returns the object whose children should be tracked. * @param fn Callback called when a child is attached to the target object. * @param [options] * @returns */ function onChildAttachedTo(target, fn, options) { assertIsFunction(target, "target"); assertIsFunction(fn, "fn"); var opts = _extends({ deep: false, runForCurrentChildren: true }, options); var detachDisposers = new WeakMap(); var runDetachDisposer = function runDetachDisposer(n) { var detachDisposer = detachDisposers.get(n); if (detachDisposer) { detachDisposers["delete"](n); detachDisposer(); } }; var addDetachDisposer = function addDetachDisposer(n, disposer) { if (disposer) { detachDisposers.set(n, disposer); } }; var getChildrenObjectOpts = { deep: opts.deep }; var getCurrentChildren = function getCurrentChildren() { var t = target(); assertTweakedObject(t, "target()"); var children = getChildrenObjects(t, getChildrenObjectOpts); var set = new Set(); var iter = children.values(); var cur = iter.next(); while (!cur.done) { set.add(cur.value); cur = iter.next(); } return set; }; var currentChildren = opts.runForCurrentChildren ? new Set() : getCurrentChildren(); var disposer = reaction(function () { return getCurrentChildren(); }, function (newChildren) { var disposersToRun = []; // find dead var currentChildrenIter = currentChildren.values(); var currentChildrenCur = currentChildrenIter.next(); while (!currentChildrenCur.done) { var n = currentChildrenCur.value; if (!newChildren.has(n)) { currentChildren["delete"](n); // we should run it in inverse order disposersToRun.push(n); } currentChildrenCur = currentChildrenIter.next(); } if (disposersToRun.length > 0) { for (var i = disposersToRun.length - 1; i >= 0; i--) { runDetachDisposer(disposersToRun[i]); } } // find new var newChildrenIter = newChildren.values(); var newChildrenCur = newChildrenIter.next(); while (!newChildrenCur.done) { var _n = newChildrenCur.value; if (!currentChildren.has(_n)) { currentChildren.add(_n); addDetachDisposer(_n, fn(_n)); } newChildrenCur = newChildrenIter.next(); } }, { fireImmediately: true }); return function (runDetachDisposers) { disposer(); if (runDetachDisposers) { var currentChildrenIter = currentChildren.values(); var currentChildrenCur = currentChildrenIter.next(); while (!currentChildrenCur.done) { var n = currentChildrenCur.value; runDetachDisposer(n); currentChildrenCur = currentChildrenIter.next(); } } currentChildren.clear(); }; } /** * Mode for the `walkTree` method. */ var WalkTreeMode; (function (WalkTreeMode) { /** * The walk will be done parent (roots) first, then children. */ WalkTreeMode["ParentFirst"] = "parentFirst"; /** * The walk will be done children (leafs) first, then parents. */ WalkTreeMode["ChildrenFirst"] = "childrenFirst"; })(WalkTreeMode || (WalkTreeMode = {})); /** * Walks a tree, running the predicate function for each node. * If the predicate function returns something other than undefined, * then the walk will be stopped and the function will return the returned value. * * @typeparam T Returned object type, defaults to void. * @param target Subtree root object. * @param predicate Function that will be run for each node of the tree. * @param mode Mode to walk the tree, as defined in `WalkTreeMode`. * @returns */ function walkTree(target, predicate, mode) { assertTweakedObject(target, "target"); if (mode === WalkTreeMode.ParentFirst) { var recurse = function recurse(child) { return walkTreeParentFirst(child, predicate, recurse); }; return walkTreeParentFirst(target, predicate, recurse); } else { var _recurse = function _recurse(child) { return walkTreeChildrenFirst(child, predicate, _recurse); }; return walkTreeChildrenFirst(target, predicate, _recurse); } } function walkTreeParentFirst(target, rootPredicate, recurse) { var ret = rootPredicate(target); if (ret !== undefined) { return ret; } var childrenIter = getObjectChildren(target).values(); var ch = childrenIter.next(); while (!ch.done) { var _ret = recurse(ch.value); if (_ret !== undefined) { return _ret; } ch = childrenIter.next(); } return undefined; } function walkTreeChildrenFirst(target, rootPredicate, recurse) { var childrenIter = getObjectChildren(target).values(); var ch = childrenIter.next(); while (!ch.done) { var _ret2 = recurse(ch.value); if (_ret2 !== undefined) { return _ret2; } ch = childrenIter.next(); } var ret = rootPredicate(target); if (ret !== undefined) { return ret; } return undefined; } /** * @ignore * @internal */ function computedWalkTreeAggregate(predicate) { var computedFns = new WeakMap(); var getComputedTreeResult = function getComputedTreeResult(tre