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,070 lines (1,737 loc) • 346 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('mobx'), require('uuid'), require('tslib'), require('fast-deep-equal/es6')) :
typeof define === 'function' && define.amd ? define(['exports', 'mobx', 'uuid', 'tslib', 'fast-deep-equal/es6'], factory) :
(global = global || self, factory(global.mobxKeystone = {}, global.mobx, global.uuid, global.tslib, global['fast-deep-equal/es6']));
}(this, (function (exports, mobx, uuid, tslib, fastDeepEqual) {
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () {
return e[k];
}
});
}
});
}
n['default'] = e;
return n;
}
var mobx__namespace = /*#__PURE__*/_interopNamespace(mobx);
var fastDeepEqual__default = /*#__PURE__*/_interopDefaultLegacy(fastDeepEqual);
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.
*/
exports.BuiltInAction = void 0;
(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";
})(exports.BuiltInAction || (exports.BuiltInAction = {}));
var builtInActionValues = new Set(Object.values(exports.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.
*/
exports.ActionContextActionType = void 0;
(function (ActionContextActionType) {
ActionContextActionType["Sync"] = "sync";
ActionContextActionType["Async"] = "async";
})(exports.ActionContextActionType || (exports.ActionContextActionType = {}));
/**
* An async step type.
*/
exports.ActionContextAsyncStepType = void 0;
(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";
})(exports.ActionContextAsyncStepType || (exports.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 || mobx.isObservableMap(val);
}
/**
* @ignore
* @internal
*/
function isSet(val) {
return val instanceof Set || mobx.isObservableSet(val);
}
/**
* @ignore
* @internal
*/
function isArray(val) {
return Array.isArray(val) || mobx.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 (!mobx.isObservableObject(value)) {
throw failure(argName + " must be an observable object");
}
}
/**
* @ignore
* @internal
*/
function assertIsObservableArray(value, argName) {
if (!mobx.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__namespace[// 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 mobx.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
*/
exports.runningWithoutSnapshotOrPatches = false;
/**
* @ignore
* @internal
*/
function runWithoutSnapshotOrPatches(fn) {
var old = exports.runningWithoutSnapshotOrPatches;
exports.runningWithoutSnapshotOrPatches = true;
try {
mobx.runInAction(function () {
fn();
});
} finally {
exports.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 mobx.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: mobx.observable.set(undefined, defaultObservableSetOptions),
deep: new Set(),
extensionsData: initExtensionsData(),
deepDirty: true,
deepAtom: mobx.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 = mobx.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 = mobx.action(function (node, child) {
var obj = objectChildren.get(node);
obj.shallow.add(child);
invalidateDeepChildren(node);
});
/**
* @ignore
* @internal
*/
var removeObjectChild = mobx.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 = mobx.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: exports.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: exports.BuiltInAction.Detach,
fn: internalDetach,
actionType: exports.ActionContextActionType.Sync
});
});
function internalDetach() {
var node = this;
var parentPath = fastGetParentPathIncludingDataObjects(node);
if (!parentPath) return;
var parent = parentPath.parent,
path = parentPath.path;
if (mobx.isObservableArray(parent)) {
parent.splice(+path, 1);
} else if (mobx.isObservableObject(parent)) {
mobx.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, di