@jbrowse/mobx-state-tree 5.11.2 → 5.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +39 -2
- package/dist/mobx-state-tree.cjs +247 -95
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +247 -96
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -347,7 +347,7 @@ declare function getRelativePath(base: IAnyStateTreeNode, target: IAnyStateTreeN
|
|
|
347
347
|
* @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.
|
|
348
348
|
* @returns
|
|
349
349
|
*/
|
|
350
|
-
declare function clone<T extends IAnyStateTreeNode>(source: T, keepEnvironment?: boolean |
|
|
350
|
+
declare function clone<T extends IAnyStateTreeNode>(source: T, keepEnvironment?: boolean | object): T;
|
|
351
351
|
/**
|
|
352
352
|
* Removes a model element from the state tree, and let it live on as a new state tree
|
|
353
353
|
*/
|
|
@@ -611,6 +611,7 @@ declare class ObjectNode<C, S, T> extends BaseNode<C, S, T> {
|
|
|
611
611
|
private _cachedInitialSnapshot?;
|
|
612
612
|
private _cachedInitialSnapshotCreated;
|
|
613
613
|
private _snapshotComputed;
|
|
614
|
+
private _snapshotComputedEvaluated;
|
|
614
615
|
constructor(complexType: ComplexType<C, S, T>, parent: AnyObjectNode | null, subpath: string, environment: any, initialValue: C);
|
|
615
616
|
createObservableInstanceIfNeeded(fireHooks?: boolean): void;
|
|
616
617
|
createObservableInstance(fireHooks?: boolean): void;
|
|
@@ -620,6 +621,17 @@ declare class ObjectNode<C, S, T> extends BaseNode<C, S, T> {
|
|
|
620
621
|
protected fireHook(name: Hook): void;
|
|
621
622
|
private _snapshotUponDeath?;
|
|
622
623
|
get snapshot(): S;
|
|
624
|
+
/**
|
|
625
|
+
* @internal
|
|
626
|
+
* @hidden
|
|
627
|
+
* Recompute+retrack the keepAlive snapshot computed if it has already run.
|
|
628
|
+
* Needed whenever something changes what the snapshot would produce without
|
|
629
|
+
* an observable mobx would track: flipping the (non-observable) observable-
|
|
630
|
+
* instance state, or SnapshotProcessor rewriting getSnapshot after
|
|
631
|
+
* construction (see snapshotProcessor._fixNode). If it never ran, there is
|
|
632
|
+
* nothing cached to go stale, so this is a no-op.
|
|
633
|
+
*/
|
|
634
|
+
refreshMemoizedSnapshot(): void;
|
|
623
635
|
getSnapshot(): S;
|
|
624
636
|
private _getActualSnapshot;
|
|
625
637
|
private _getCachedInitialSnapshot;
|
|
@@ -1725,6 +1737,31 @@ declare function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends Mo
|
|
|
1725
1737
|
* @returns
|
|
1726
1738
|
*/
|
|
1727
1739
|
declare function isModelType<IT extends IAnyModelType = IAnyModelType>(type: IAnyType): type is IT;
|
|
1740
|
+
/**
|
|
1741
|
+
* `extendInstance` - attaches additional actions, views and volatile state to an
|
|
1742
|
+
* already-created model instance, using the same instantiation machinery as
|
|
1743
|
+
* `.extend()`. This enables lazily loading a model's views/actions chain: create
|
|
1744
|
+
* the model with only its base props (plus any critical-path members), then attach
|
|
1745
|
+
* the rest at runtime — e.g. from a dynamically imported module — without rebuilding
|
|
1746
|
+
* the type or re-hydrating the tree.
|
|
1747
|
+
*
|
|
1748
|
+
* The extension is applied inside an action context, so it works on protected trees.
|
|
1749
|
+
* Attached views are computeds (reactive, memoized) and attached actions get a full
|
|
1750
|
+
* MST action context, exactly like members declared in the original chain.
|
|
1751
|
+
*
|
|
1752
|
+
* Note: the attached members are NOT part of the instance's *static* type. If you
|
|
1753
|
+
* need them typed, declare the augmented shape separately (an `import type` of the
|
|
1754
|
+
* chain's return type is erased at build time, so it costs nothing in the bundle).
|
|
1755
|
+
*
|
|
1756
|
+
* @param instance a live model instance
|
|
1757
|
+
* @param fn receives the instance and returns `{ actions?, views?, state? }`
|
|
1758
|
+
* @returns the same instance
|
|
1759
|
+
*/
|
|
1760
|
+
declare function extendInstance<T extends IAnyStateTreeNode>(instance: T, fn: (self: T) => {
|
|
1761
|
+
actions?: ModelActions;
|
|
1762
|
+
views?: object;
|
|
1763
|
+
state?: object;
|
|
1764
|
+
}): T;
|
|
1728
1765
|
|
|
1729
1766
|
/**
|
|
1730
1767
|
* `types.Date` - Creates a type that can only contain a javascript Date value.
|
|
@@ -2139,5 +2176,5 @@ declare const types: {
|
|
|
2139
2176
|
resilient: typeof resilient;
|
|
2140
2177
|
};
|
|
2141
2178
|
|
|
2142
|
-
export { addDisposer, addMiddleware, applyAction, applyPatch, applySnapshot, cast, castFlowReturn, castToReferenceSnapshot, castToSnapshot, clone, createActionTrackingMiddleware, createActionTrackingMiddleware2, decorate, destroy, detach, escapeJsonPath, flow, getChildType, getEnv, getIdentifier, getLivelinessChecking, getMembers, getNodeId, getParent, getParentOfType, getPath, getPathParts, getPropertyMembers, getRelativePath, getRoot, getRunningActionContext, getSnapshot, getType, getUnionSubtypes, hasParent, hasParentOfType, isActionContextChildOf, isActionContextThisOrChildOf, isAlive, isArrayType, isFrozenType, isIdentifierType, isLateType, isLiteralType, isMapType, isModelType, isOptionalType, isPrimitiveType, isProtected, isReferenceType, isRefinementType, isRoot, isStateTreeNode, isType, isUnionType, isValidReference, joinJsonPath, onAction, onPatch, onSnapshot, protect, recordActions, recordPatches, resolveIdentifier, resolvePath, setDevMode, setLivelinessChecking, setLivelynessChecking, setTypeChecking, splitJsonPath, types as t, toGenerator, toGeneratorFunction, tryReference, tryResolve, typecheck, types, unescapeJsonPath, unprotect, walk };
|
|
2179
|
+
export { addDisposer, addMiddleware, applyAction, applyPatch, applySnapshot, cast, castFlowReturn, castToReferenceSnapshot, castToSnapshot, clone, createActionTrackingMiddleware, createActionTrackingMiddleware2, decorate, destroy, detach, escapeJsonPath, extendInstance, flow, getChildType, getEnv, getIdentifier, getLivelinessChecking, getMembers, getNodeId, getParent, getParentOfType, getPath, getPathParts, getPropertyMembers, getRelativePath, getRoot, getRunningActionContext, getSnapshot, getType, getUnionSubtypes, hasParent, hasParentOfType, isActionContextChildOf, isActionContextThisOrChildOf, isAlive, isArrayType, isFrozenType, isIdentifierType, isLateType, isLiteralType, isMapType, isModelType, isOptionalType, isPrimitiveType, isProtected, isReferenceType, isRefinementType, isRoot, isStateTreeNode, isType, isUnionType, isValidReference, joinJsonPath, onAction, onPatch, onSnapshot, protect, recordActions, recordPatches, resolveIdentifier, resolvePath, setDevMode, setLivelinessChecking, setLivelynessChecking, setTypeChecking, splitJsonPath, types as t, toGenerator, toGeneratorFunction, tryReference, tryResolve, typecheck, types, unescapeJsonPath, unprotect, walk };
|
|
2143
2180
|
export type { $EmptyObjectBrand, CustomTypeOptions, IActionContext, IActionRecorder, IActionTrackingMiddleware2Call, IActionTrackingMiddleware2Hooks, IActionTrackingMiddlewareHooks, IAnyComplexType, IAnyModelType, IAnyStateTreeNode, IAnyType, IArrayType, IComplexType, IDisposer, IJsonPatch, IMSTArray, IMSTMap, IMapType, IMaybe, IMaybeIType, IMaybeNull, IMiddlewareEvent, IMiddlewareEventType, IMiddlewareHandler, IModelReflectionData, IModelReflectionPropertiesData, IModelType, IOptionalIType, IPatchRecorder, IReferenceType, IReversibleJsonPatch, ISerializedActionCall, ISimpleType, ISnapshotProcessor, ISnapshotProcessors, IStateTreeNode, IType, ITypeUnion, Instance, LivelinessMode, LivelynessMode, ModelActions, ModelCreationType, ModelCreationType2, ModelInstanceType, ModelInstanceTypeProps, ModelPrimitive, ModelProperties, ModelPropertiesDeclaration, ModelPropertiesDeclarationToProperties, ModelSnapshotType, ModelSnapshotType2, OnReferenceInvalidated, OnReferenceInvalidatedEvent, OptionalDefaultValueOrFunction, ReferenceIdentifier, ReferenceOptions, ReferenceOptionsGetSet, ReferenceOptionsOnInvalidated, SnapshotIn, SnapshotOrInstance, SnapshotOut, TypeOfValue, TypeOrStateTreeNodeToStateTreeNode, UnionOptions, UnionStringArray, ValidOptionalValue, ValidOptionalValues, _CustomCSProcessor, _CustomJoin, _CustomOrOther, _NotCustomized };
|
package/dist/mobx-state-tree.cjs
CHANGED
|
@@ -766,10 +766,11 @@ function getMembers(target) {
|
|
|
766
766
|
}
|
|
767
767
|
return;
|
|
768
768
|
}
|
|
769
|
-
|
|
769
|
+
const value = descriptor.value;
|
|
770
|
+
if (value?._isFlowAction === true) {
|
|
770
771
|
reflected.flowActions.push(key);
|
|
771
772
|
}
|
|
772
|
-
if (
|
|
773
|
+
if (value?._isMSTAction === true) {
|
|
773
774
|
reflected.actions.push(key);
|
|
774
775
|
}
|
|
775
776
|
else if (mobx.isObservableProp(target, key)) {
|
|
@@ -1138,6 +1139,10 @@ const snapshotReactionOptions = {
|
|
|
1138
1139
|
throw e;
|
|
1139
1140
|
}
|
|
1140
1141
|
};
|
|
1142
|
+
// keepAlive lets the snapshot computed memoize its value even with no observer,
|
|
1143
|
+
// so getSnapshot() stays referentially stable without an eager snapshot
|
|
1144
|
+
// reaction serializing the whole tree at create time. Shared: mobx only reads it.
|
|
1145
|
+
const snapshotComputedOptions = { keepAlive: true };
|
|
1141
1146
|
/**
|
|
1142
1147
|
* @internal
|
|
1143
1148
|
* @hidden
|
|
@@ -1170,9 +1175,18 @@ class ObjectNode extends BaseNode {
|
|
|
1170
1175
|
_cachedInitialSnapshot;
|
|
1171
1176
|
_cachedInitialSnapshotCreated = false;
|
|
1172
1177
|
_snapshotComputed;
|
|
1178
|
+
// whether _snapshotComputed has ever run — see createObservableInstance
|
|
1179
|
+
_snapshotComputedEvaluated = false;
|
|
1173
1180
|
constructor(complexType, parent, subpath, environment, initialValue) {
|
|
1174
1181
|
super(complexType, parent, subpath, environment);
|
|
1175
|
-
|
|
1182
|
+
// keepAlive: memoize the snapshot after the first read even with no
|
|
1183
|
+
// observer, so getSnapshot() is referentially stable (and reconcile-by-
|
|
1184
|
+
// snapshot works) without eagerly serializing the whole tree at create
|
|
1185
|
+
// time — the previous cost, driven by the eager root reaction below.
|
|
1186
|
+
this._snapshotComputed = mobx.computed(() => {
|
|
1187
|
+
this._snapshotComputedEvaluated = true;
|
|
1188
|
+
return freeze(this.getSnapshot());
|
|
1189
|
+
}, snapshotComputedOptions);
|
|
1176
1190
|
this.unbox = this.unbox.bind(this);
|
|
1177
1191
|
this._initialSnapshot = freeze(initialValue);
|
|
1178
1192
|
this.identifierAttribute = complexType.identifierAttribute;
|
|
@@ -1256,10 +1270,17 @@ class ObjectNode extends BaseNode {
|
|
|
1256
1270
|
this._isRunningAction = false;
|
|
1257
1271
|
}
|
|
1258
1272
|
this._observableInstanceState = ObservableInstanceLifecycle.CREATED;
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1273
|
+
// The snapshot computed branches on the non-observable
|
|
1274
|
+
// "_observableInstanceState" flag we just flipped, so a value already
|
|
1275
|
+
// memoized from the initial snapshot is now stale (mobx can't see the
|
|
1276
|
+
// change) — refresh it. When it was never read there is nothing to refresh,
|
|
1277
|
+
// and forcing it here would serialize the whole subtree (the dominant cost
|
|
1278
|
+
// of creating a root) for a value nobody has asked for yet, so we skip it.
|
|
1279
|
+
this.refreshMemoizedSnapshot();
|
|
1280
|
+
// The root's snapshot reaction only drives onSnapshot; set it up lazily
|
|
1281
|
+
// (onSnapshot already does) rather than eagerly here, so a root nobody
|
|
1282
|
+
// subscribes to never pays for a full-tree snapshot at creation. keepAlive
|
|
1283
|
+
// on _snapshotComputed preserves snapshot referential stability without it.
|
|
1263
1284
|
this._childNodes = EMPTY_OBJECT;
|
|
1264
1285
|
this.state = NodeLifeCycle.CREATED;
|
|
1265
1286
|
if (fireHooks) {
|
|
@@ -1364,6 +1385,21 @@ class ObjectNode extends BaseNode {
|
|
|
1364
1385
|
}
|
|
1365
1386
|
return this._snapshotComputed.get();
|
|
1366
1387
|
}
|
|
1388
|
+
/**
|
|
1389
|
+
* @internal
|
|
1390
|
+
* @hidden
|
|
1391
|
+
* Recompute+retrack the keepAlive snapshot computed if it has already run.
|
|
1392
|
+
* Needed whenever something changes what the snapshot would produce without
|
|
1393
|
+
* an observable mobx would track: flipping the (non-observable) observable-
|
|
1394
|
+
* instance state, or SnapshotProcessor rewriting getSnapshot after
|
|
1395
|
+
* construction (see snapshotProcessor._fixNode). If it never ran, there is
|
|
1396
|
+
* nothing cached to go stale, so this is a no-op.
|
|
1397
|
+
*/
|
|
1398
|
+
refreshMemoizedSnapshot() {
|
|
1399
|
+
if (this._snapshotComputedEvaluated) {
|
|
1400
|
+
this._snapshotComputed.trackAndCompute();
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1367
1403
|
// NOTE: we use this method to get snapshot without creating @computed overhead
|
|
1368
1404
|
getSnapshot() {
|
|
1369
1405
|
if (!this.isAlive) {
|
|
@@ -1668,9 +1704,6 @@ ObjectNode.prototype.createObservableInstance = mobx.action(ObjectNode.prototype
|
|
|
1668
1704
|
ObjectNode.prototype.detach = mobx.action(ObjectNode.prototype.detach);
|
|
1669
1705
|
ObjectNode.prototype.die = mobx.action(ObjectNode.prototype.die);
|
|
1670
1706
|
|
|
1671
|
-
// Cache for validation results to avoid re-validating the same object against the same type
|
|
1672
|
-
// Uses WeakMap so cached objects can be garbage collected
|
|
1673
|
-
const validationCache = new WeakMap();
|
|
1674
1707
|
/**
|
|
1675
1708
|
* @internal
|
|
1676
1709
|
* @hidden
|
|
@@ -1746,27 +1779,7 @@ class BaseType {
|
|
|
1746
1779
|
: typeCheckFailure(context, value);
|
|
1747
1780
|
// it is tempting to compare snapshots, but in that case we should always clone on assignments...
|
|
1748
1781
|
}
|
|
1749
|
-
|
|
1750
|
-
if (typeof value === "object" && value !== null && context.length === 1) {
|
|
1751
|
-
const typeCache = validationCache.get(value);
|
|
1752
|
-
if (typeCache) {
|
|
1753
|
-
const cached = typeCache.get(this);
|
|
1754
|
-
if (cached !== undefined) {
|
|
1755
|
-
return cached;
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
const result = this.isValidSnapshot(value, context);
|
|
1760
|
-
// cache result for object values (only at root level)
|
|
1761
|
-
if (typeof value === "object" && value !== null && context.length === 1) {
|
|
1762
|
-
let typeCache = validationCache.get(value);
|
|
1763
|
-
if (!typeCache) {
|
|
1764
|
-
typeCache = new WeakMap();
|
|
1765
|
-
validationCache.set(value, typeCache);
|
|
1766
|
-
}
|
|
1767
|
-
typeCache.set(this, result);
|
|
1768
|
-
}
|
|
1769
|
-
return result;
|
|
1782
|
+
return this.isValidSnapshot(value, context);
|
|
1770
1783
|
}
|
|
1771
1784
|
is(thing) {
|
|
1772
1785
|
return this.validate(thing, [{ path: "", type: this }]).length === 0;
|
|
@@ -3241,8 +3254,6 @@ function addHiddenWritableProp(object, propName, value) {
|
|
|
3241
3254
|
*/
|
|
3242
3255
|
class EventHandler {
|
|
3243
3256
|
handlers = [];
|
|
3244
|
-
emitting = false;
|
|
3245
|
-
pendingUnregisters = null;
|
|
3246
3257
|
get hasSubscribers() {
|
|
3247
3258
|
return this.handlers.length > 0;
|
|
3248
3259
|
}
|
|
@@ -3261,14 +3272,6 @@ class EventHandler {
|
|
|
3261
3272
|
return this.handlers.includes(fn);
|
|
3262
3273
|
}
|
|
3263
3274
|
unregister(fn) {
|
|
3264
|
-
if (this.emitting) {
|
|
3265
|
-
// defer unregistration until emit is done
|
|
3266
|
-
if (!this.pendingUnregisters) {
|
|
3267
|
-
this.pendingUnregisters = [];
|
|
3268
|
-
}
|
|
3269
|
-
this.pendingUnregisters.push(fn);
|
|
3270
|
-
return;
|
|
3271
|
-
}
|
|
3272
3275
|
const index = this.handlers.indexOf(fn);
|
|
3273
3276
|
if (index >= 0) {
|
|
3274
3277
|
this.handlers.splice(index, 1);
|
|
@@ -3278,26 +3281,10 @@ class EventHandler {
|
|
|
3278
3281
|
this.handlers.length = 0;
|
|
3279
3282
|
}
|
|
3280
3283
|
emit(...args) {
|
|
3281
|
-
//
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
f(...args);
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
finally {
|
|
3289
|
-
this.emitting = false;
|
|
3290
|
-
// process any deferred unregistrations
|
|
3291
|
-
if (this.pendingUnregisters) {
|
|
3292
|
-
for (const fn of this.pendingUnregisters) {
|
|
3293
|
-
const index = this.handlers.indexOf(fn);
|
|
3294
|
-
if (index >= 0) {
|
|
3295
|
-
this.handlers.splice(index, 1);
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
this.pendingUnregisters = null;
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3284
|
+
// iterate a copy so (un)registrations during emit don't disturb this pass
|
|
3285
|
+
// and reentrant emits stay correct
|
|
3286
|
+
const handlers = this.handlers.slice();
|
|
3287
|
+
handlers.forEach(f => f(...args));
|
|
3301
3288
|
}
|
|
3302
3289
|
}
|
|
3303
3290
|
/**
|
|
@@ -3360,6 +3347,11 @@ let _typeChecking;
|
|
|
3360
3347
|
function setTypeChecking(enabled) {
|
|
3361
3348
|
_typeChecking = enabled;
|
|
3362
3349
|
}
|
|
3350
|
+
// the ENABLE_TYPE_CHECK env var is fixed at process start, so read it once:
|
|
3351
|
+
// process.env access is a comparatively expensive lookup and isTypeCheckingEnabled
|
|
3352
|
+
// runs on every create() / typed write. The setTypeChecking() override and
|
|
3353
|
+
// dev-mode default stay live below.
|
|
3354
|
+
const _envTypeCheck = typeof process !== "undefined" && process.env?.ENABLE_TYPE_CHECK === "true";
|
|
3363
3355
|
/**
|
|
3364
3356
|
* @internal
|
|
3365
3357
|
* @hidden
|
|
@@ -3367,10 +3359,7 @@ function setTypeChecking(enabled) {
|
|
|
3367
3359
|
function isTypeCheckingEnabled() {
|
|
3368
3360
|
// an explicit setTypeChecking() override (incl. `false`) wins over the
|
|
3369
3361
|
// dev-mode / env-var default, hence ?? rather than ||
|
|
3370
|
-
return
|
|
3371
|
-
(devMode() ||
|
|
3372
|
-
(typeof process !== "undefined" &&
|
|
3373
|
-
process.env?.ENABLE_TYPE_CHECK === "true")));
|
|
3362
|
+
return _typeChecking ?? (devMode() || _envTypeCheck);
|
|
3374
3363
|
}
|
|
3375
3364
|
let _devMode = process.env.NODE_ENV !== "production";
|
|
3376
3365
|
/**
|
|
@@ -3773,6 +3762,11 @@ class SnapshotProcessor extends BaseType {
|
|
|
3773
3762
|
}
|
|
3774
3763
|
const oldGetSnapshot = node.getSnapshot;
|
|
3775
3764
|
node.getSnapshot = () => this.postProcessSnapshot(oldGetSnapshot.call(node), node);
|
|
3765
|
+
// getSnapshot was just rewritten; refresh any snapshot already memoized for
|
|
3766
|
+
// this node (e.g. it was serialized before being moved under this processor)
|
|
3767
|
+
if (node instanceof ObjectNode) {
|
|
3768
|
+
node.refreshMemoizedSnapshot();
|
|
3769
|
+
}
|
|
3776
3770
|
if (!isUnionType(this._subtype)) {
|
|
3777
3771
|
node.getReconciliationType = () => {
|
|
3778
3772
|
return this;
|
|
@@ -4450,6 +4444,23 @@ function array(subtype) {
|
|
|
4450
4444
|
}
|
|
4451
4445
|
function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths) {
|
|
4452
4446
|
let nothingChanged = true;
|
|
4447
|
+
// When the element type is a plain model with an identifier, index the old
|
|
4448
|
+
// nodes by id so a moved / replaced child is matched in O(1) instead of the
|
|
4449
|
+
// linear scan in the reorder branch below. This turns a full array
|
|
4450
|
+
// replacement (the common "load new data" case) from O(n^2) into O(n). Types
|
|
4451
|
+
// whose id extraction needs type-specific preprocessing (union,
|
|
4452
|
+
// snapshotProcessor, late, ...) are intentionally excluded: areSame must run
|
|
4453
|
+
// `is()` before their id check, so they stay on the scan path.
|
|
4454
|
+
let idIndex;
|
|
4455
|
+
if (childType instanceof ModelType && childType.identifierAttribute) {
|
|
4456
|
+
const byId = new Map();
|
|
4457
|
+
for (const n of oldNodes) {
|
|
4458
|
+
if (n instanceof ObjectNode && n.identifier !== null) {
|
|
4459
|
+
byId.set(n.identifier, n);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
idIndex = { attr: childType.identifierAttribute, byId };
|
|
4463
|
+
}
|
|
4453
4464
|
for (let i = 0;; i++) {
|
|
4454
4465
|
const hasNewNode = i <= newValues.length - 1;
|
|
4455
4466
|
const oldNode = oldNodes[i];
|
|
@@ -4493,13 +4504,26 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
|
|
|
4493
4504
|
oldNodes[i] = valueAsNode(childType, parent, newPath, newValue, oldNode);
|
|
4494
4505
|
}
|
|
4495
4506
|
else {
|
|
4496
|
-
// nothing to do, try to reorder
|
|
4507
|
+
// nothing to do, try to reorder: find a candidate old node to reuse
|
|
4497
4508
|
let oldMatch = undefined;
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4509
|
+
if (idIndex && isPlainObject(newValue)) {
|
|
4510
|
+
// for an identified element type a plain-object snapshot can only match
|
|
4511
|
+
// by id (it is never a live node, and snapshot-reference equality still
|
|
4512
|
+
// implies the same id), so a miss means "nothing to reuse" without
|
|
4513
|
+
// scanning. areSame still verifies the single candidate.
|
|
4514
|
+
const candidate = idIndex.byId.get(normalizeIdentifier(newValue[idIndex.attr]));
|
|
4515
|
+
const j = candidate ? oldNodes.indexOf(candidate, i) : -1;
|
|
4516
|
+
if (j >= i && areSame(oldNodes[j], newValue)) {
|
|
4501
4517
|
oldMatch = oldNodes.splice(j, 1)[0];
|
|
4502
|
-
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
else {
|
|
4521
|
+
// find a possible candidate to reuse
|
|
4522
|
+
for (let j = i; j < oldNodes.length; j++) {
|
|
4523
|
+
if (areSame(oldNodes[j], newValue)) {
|
|
4524
|
+
oldMatch = oldNodes.splice(j, 1)[0];
|
|
4525
|
+
break;
|
|
4526
|
+
}
|
|
4503
4527
|
}
|
|
4504
4528
|
}
|
|
4505
4529
|
nothingChanged = false;
|
|
@@ -4659,14 +4683,33 @@ class ModelType extends ComplexType {
|
|
|
4659
4683
|
preProcessor;
|
|
4660
4684
|
postProcessor;
|
|
4661
4685
|
propertyNames;
|
|
4686
|
+
// member/property name collisions are a property of the type, so we only need
|
|
4687
|
+
// to check the first instance we finalize (see finalizeNewInstance)
|
|
4688
|
+
duplicateKeysChecked = false;
|
|
4662
4689
|
constructor(opts) {
|
|
4663
4690
|
super(opts.name || defaultObjectOptions.name);
|
|
4664
4691
|
Object.assign(this, defaultObjectOptions, opts);
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4692
|
+
if (opts.propertiesArePreProcessed) {
|
|
4693
|
+
// `properties` is a parent type's already-converted + frozen output
|
|
4694
|
+
// (chain step with no new props), so its derived propertyNames and
|
|
4695
|
+
// identifierAttribute are identical to the parent's — reuse them verbatim
|
|
4696
|
+
// (cloneAndEnhance passed them in) instead of re-running Object.keys and
|
|
4697
|
+
// the per-prop identifier scan, both O(props), on every step.
|
|
4698
|
+
this.propertyNames = opts.propertyNames;
|
|
4699
|
+
this.identifierAttribute = opts.identifierAttribute;
|
|
4700
|
+
}
|
|
4701
|
+
else {
|
|
4702
|
+
// `propertiesAreConverted` (set by cloneAndEnhance when props are added)
|
|
4703
|
+
// means every value is already a type — the parent's converted bag merged
|
|
4704
|
+
// with a freshly converted delta — so skip re-converting. Only raw entry
|
|
4705
|
+
// points (`model()`) still need the full toPropertiesObject pass.
|
|
4706
|
+
if (!opts.propertiesAreConverted) {
|
|
4707
|
+
this.properties = toPropertiesObject(this.properties);
|
|
4708
|
+
}
|
|
4709
|
+
freeze(this.properties); // make sure nobody messes with it
|
|
4710
|
+
this.propertyNames = Object.keys(this.properties);
|
|
4711
|
+
this.identifierAttribute = this._getIdentifierAttribute();
|
|
4712
|
+
}
|
|
4670
4713
|
}
|
|
4671
4714
|
_getIdentifierAttribute() {
|
|
4672
4715
|
let identifierAttribute = undefined;
|
|
@@ -4681,9 +4724,35 @@ class ModelType extends ComplexType {
|
|
|
4681
4724
|
return identifierAttribute;
|
|
4682
4725
|
}
|
|
4683
4726
|
cloneAndEnhance(opts) {
|
|
4727
|
+
// Fast path: a chain step that adds no new properties (.actions/.views/
|
|
4728
|
+
// .volatile/.named/pre-postProcessor) reuses this type's already-converted +
|
|
4729
|
+
// frozen properties (and their derived names/identifier) verbatim.
|
|
4730
|
+
const hasNewProps = opts.properties !== undefined && Object.keys(opts.properties).length > 0;
|
|
4731
|
+
if (!hasNewProps) {
|
|
4732
|
+
return new ModelType({
|
|
4733
|
+
name: opts.name || this.name,
|
|
4734
|
+
properties: this.properties,
|
|
4735
|
+
propertiesArePreProcessed: true,
|
|
4736
|
+
// safe to share: propertyNames is never mutated after construction
|
|
4737
|
+
propertyNames: this.propertyNames,
|
|
4738
|
+
identifierAttribute: this.identifierAttribute,
|
|
4739
|
+
initializers: this.initializers.concat(opts.initializers || []),
|
|
4740
|
+
preProcessor: opts.preProcessor || this.preProcessor,
|
|
4741
|
+
postProcessor: opts.postProcessor || this.postProcessor
|
|
4742
|
+
});
|
|
4743
|
+
}
|
|
4744
|
+
// Adding props: this type's `properties` is already converted+frozen, so only
|
|
4745
|
+
// the delta needs conversion. compose passes already-converted props
|
|
4746
|
+
// (cur.properties) and flags them; `.props()` passes a raw declaration. Either
|
|
4747
|
+
// way the merged bag is fully converted, so the constructor skips re-converting
|
|
4748
|
+
// the (potentially large) inherited set — the previous hot spot in compose.
|
|
4749
|
+
const convertedNewProps = opts.propertiesAreConverted
|
|
4750
|
+
? opts.properties
|
|
4751
|
+
: toPropertiesObject(opts.properties);
|
|
4684
4752
|
return new ModelType({
|
|
4685
4753
|
name: opts.name || this.name,
|
|
4686
|
-
properties: Object.assign({}, this.properties,
|
|
4754
|
+
properties: Object.assign({}, this.properties, convertedNewProps),
|
|
4755
|
+
propertiesAreConverted: true,
|
|
4687
4756
|
initializers: this.initializers.concat(opts.initializers || []),
|
|
4688
4757
|
preProcessor: opts.preProcessor || this.preProcessor,
|
|
4689
4758
|
postProcessor: opts.postProcessor || this.postProcessor
|
|
@@ -4780,6 +4849,29 @@ class ModelType extends ComplexType {
|
|
|
4780
4849
|
};
|
|
4781
4850
|
return this.cloneAndEnhance({ initializers: [viewInitializer] });
|
|
4782
4851
|
}
|
|
4852
|
+
/**
|
|
4853
|
+
* @internal
|
|
4854
|
+
* @hidden
|
|
4855
|
+
* Materializes an additional actions/views/state extension onto an
|
|
4856
|
+
* already-created instance, using the same machinery as `.extend()` runs at
|
|
4857
|
+
* creation time. Powers {@link extendInstance}; callers must ensure the writes
|
|
4858
|
+
* are permitted (see extendInstance, which runs this in an action context).
|
|
4859
|
+
*/
|
|
4860
|
+
applyExtensionToInstance(self, extension) {
|
|
4861
|
+
const { actions, views, state, ...rest } = extension;
|
|
4862
|
+
for (const key in rest) {
|
|
4863
|
+
throw fail(`extendInstance should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'`);
|
|
4864
|
+
}
|
|
4865
|
+
if (state) {
|
|
4866
|
+
this.instantiateVolatileState(self, state);
|
|
4867
|
+
}
|
|
4868
|
+
if (views) {
|
|
4869
|
+
this.instantiateViews(self, views);
|
|
4870
|
+
}
|
|
4871
|
+
if (actions) {
|
|
4872
|
+
this.instantiateActions(self, actions);
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4783
4875
|
instantiateViews(self, views) {
|
|
4784
4876
|
// check views return
|
|
4785
4877
|
if (!isPlainObject(views)) {
|
|
@@ -4848,6 +4940,21 @@ class ModelType extends ComplexType {
|
|
|
4848
4940
|
mobx._interceptReads(instance, name, node.unbox);
|
|
4849
4941
|
});
|
|
4850
4942
|
this.initializers.reduce((self, fn) => fn(self), instance);
|
|
4943
|
+
// views, actions and volatile share the instance namespace with properties,
|
|
4944
|
+
// so a view/action reusing a property name silently clobbers that property's
|
|
4945
|
+
// observable value. It's a type-level mistake (identical for every instance),
|
|
4946
|
+
// so we check just the first one we finalize: a healthy property stays a
|
|
4947
|
+
// plain observable, a shadowing getter becomes a computed, and a shadowing
|
|
4948
|
+
// function/value stops being observable at all. The flag is only set on
|
|
4949
|
+
// success so a broken type keeps throwing on every create.
|
|
4950
|
+
if (!this.duplicateKeysChecked) {
|
|
4951
|
+
this.forAllProps(name => {
|
|
4952
|
+
if (mobx.isComputedProp(instance, name) || !mobx.isObservableProp(instance, name)) {
|
|
4953
|
+
throw fail(`${name} property is declared twice`);
|
|
4954
|
+
}
|
|
4955
|
+
});
|
|
4956
|
+
this.duplicateKeysChecked = true;
|
|
4957
|
+
}
|
|
4851
4958
|
mobx.intercept(instance, this.willChange);
|
|
4852
4959
|
mobx.observe(instance, this.didChange);
|
|
4853
4960
|
}
|
|
@@ -4902,14 +5009,7 @@ class ModelType extends ComplexType {
|
|
|
4902
5009
|
getSnapshot(node, applyPostProcess = true) {
|
|
4903
5010
|
const res = {};
|
|
4904
5011
|
this.forAllProps((name, type) => {
|
|
4905
|
-
|
|
4906
|
-
// TODO: FIXME, make sure the observable ref is used!
|
|
4907
|
-
const atom = mobx.getAtom(node.storedValue, name);
|
|
4908
|
-
atom.reportObserved();
|
|
4909
|
-
}
|
|
4910
|
-
catch (_e) {
|
|
4911
|
-
throw fail(`${name} property is declared twice`);
|
|
4912
|
-
}
|
|
5012
|
+
mobx.getAtom(node.storedValue, name).reportObserved();
|
|
4913
5013
|
const snapshot = this.getChildNode(node, name).snapshot;
|
|
4914
5014
|
// strip-default optionals omit their key when equal to the default
|
|
4915
5015
|
if (!shouldStripChildFromSnapshot(type, snapshot)) {
|
|
@@ -5032,6 +5132,8 @@ function compose(...args) {
|
|
|
5032
5132
|
.reduce((prev, cur) => prev.cloneAndEnhance({
|
|
5033
5133
|
name: `${prev.name}_${cur.name}`,
|
|
5034
5134
|
properties: cur.properties,
|
|
5135
|
+
// cur.properties is another ModelType's already-converted+frozen bag
|
|
5136
|
+
propertiesAreConverted: true,
|
|
5035
5137
|
initializers: cur.initializers,
|
|
5036
5138
|
preProcessor: (snapshot) => cur.applySnapshotPreProcessor(prev.applySnapshotPreProcessor(snapshot)),
|
|
5037
5139
|
postProcessor: (snapshot) => cur.applySnapshotPostProcessor(prev.applySnapshotPostProcessor(snapshot))
|
|
@@ -5047,6 +5149,46 @@ function compose(...args) {
|
|
|
5047
5149
|
function isModelType(type) {
|
|
5048
5150
|
return isType(type) && (type.flags & TypeFlags.Object) > 0;
|
|
5049
5151
|
}
|
|
5152
|
+
/**
|
|
5153
|
+
* `extendInstance` - attaches additional actions, views and volatile state to an
|
|
5154
|
+
* already-created model instance, using the same instantiation machinery as
|
|
5155
|
+
* `.extend()`. This enables lazily loading a model's views/actions chain: create
|
|
5156
|
+
* the model with only its base props (plus any critical-path members), then attach
|
|
5157
|
+
* the rest at runtime — e.g. from a dynamically imported module — without rebuilding
|
|
5158
|
+
* the type or re-hydrating the tree.
|
|
5159
|
+
*
|
|
5160
|
+
* The extension is applied inside an action context, so it works on protected trees.
|
|
5161
|
+
* Attached views are computeds (reactive, memoized) and attached actions get a full
|
|
5162
|
+
* MST action context, exactly like members declared in the original chain.
|
|
5163
|
+
*
|
|
5164
|
+
* Note: the attached members are NOT part of the instance's *static* type. If you
|
|
5165
|
+
* need them typed, declare the augmented shape separately (an `import type` of the
|
|
5166
|
+
* chain's return type is erased at build time, so it costs nothing in the bundle).
|
|
5167
|
+
*
|
|
5168
|
+
* @param instance a live model instance
|
|
5169
|
+
* @param fn receives the instance and returns `{ actions?, views?, state? }`
|
|
5170
|
+
* @returns the same instance
|
|
5171
|
+
*/
|
|
5172
|
+
function extendInstance(instance, fn) {
|
|
5173
|
+
if (!isStateTreeNode(instance)) {
|
|
5174
|
+
throw fail("extendInstance expects a mobx-state-tree node");
|
|
5175
|
+
}
|
|
5176
|
+
const type = getStateTreeNode(instance).type;
|
|
5177
|
+
if (!isModelType(type)) {
|
|
5178
|
+
throw fail("extendInstance can only be used on model instances");
|
|
5179
|
+
}
|
|
5180
|
+
// Views/actions/volatile are installed via defineProperty + makeObservable — the
|
|
5181
|
+
// same "add" operations the initializers perform at creation, before the
|
|
5182
|
+
// write-protection interceptor is attached (see finalizeNewInstance). On a live
|
|
5183
|
+
// instance that interceptor is already active, so run the attach inside an action
|
|
5184
|
+
// context: isRunningAction() then short-circuits assertWritable.
|
|
5185
|
+
const modelType = type;
|
|
5186
|
+
const attach = createActionInvoker(instance, "@@extendInstance", (() => {
|
|
5187
|
+
modelType.applyExtensionToInstance(instance, fn(instance));
|
|
5188
|
+
}));
|
|
5189
|
+
attach();
|
|
5190
|
+
return instance;
|
|
5191
|
+
}
|
|
5050
5192
|
|
|
5051
5193
|
// TODO: implement CoreType using types.custom ?
|
|
5052
5194
|
/**
|
|
@@ -5475,7 +5617,24 @@ class Union extends BaseType {
|
|
|
5475
5617
|
}
|
|
5476
5618
|
return `${baseWithDiscriminator}:\n ${formatValidationErrorLines(errors).join("\n ")}`;
|
|
5477
5619
|
}
|
|
5620
|
+
// Memoizes the discriminator -> member scan below. Union membership is fixed
|
|
5621
|
+
// at construction, so the result for a given `type` string never changes.
|
|
5622
|
+
// Without this, validating a config with many elements drawn from a wide
|
|
5623
|
+
// pluggable union (e.g. jbrowse's 30+ track/adapter types) re-scans every
|
|
5624
|
+
// member — and calls resolveModelType + literal.is() on each — once per
|
|
5625
|
+
// element. With it, each distinct discriminator scans once; the rest are
|
|
5626
|
+
// O(1) map hits. `undefined` (no match OR ambiguous) is cached too.
|
|
5627
|
+
_discriminatorCache;
|
|
5478
5628
|
_findCandidateByTypeDiscriminator(discriminator) {
|
|
5629
|
+
const cache = (this._discriminatorCache ??= new Map());
|
|
5630
|
+
if (cache.has(discriminator)) {
|
|
5631
|
+
return cache.get(discriminator);
|
|
5632
|
+
}
|
|
5633
|
+
const found = this._scanForTypeDiscriminator(discriminator);
|
|
5634
|
+
cache.set(discriminator, found);
|
|
5635
|
+
return found;
|
|
5636
|
+
}
|
|
5637
|
+
_scanForTypeDiscriminator(discriminator) {
|
|
5479
5638
|
let found;
|
|
5480
5639
|
for (const t of this._types) {
|
|
5481
5640
|
const model = resolveModelType(t);
|
|
@@ -5521,22 +5680,14 @@ class Union extends BaseType {
|
|
|
5521
5680
|
if (this._dispatcher) {
|
|
5522
5681
|
return this._dispatcher(value);
|
|
5523
5682
|
}
|
|
5524
|
-
// fast path: when type checking is disabled, try quick structural matching
|
|
5683
|
+
// fast path: when type checking is disabled, try quick structural matching
|
|
5684
|
+
// first. This skips full recursive validation of every property value, a
|
|
5685
|
+
// meaningful win for wide model members (e.g. jbrowse config schemas).
|
|
5525
5686
|
if (!isTypeCheckingEnabled()) {
|
|
5526
5687
|
const quickMatch = this.tryQuickMatch(value, reconcileCurrentType);
|
|
5527
5688
|
if (quickMatch) {
|
|
5528
5689
|
return quickMatch;
|
|
5529
5690
|
}
|
|
5530
|
-
// for plain object snapshots that didn't match via quick path, try all types
|
|
5531
|
-
// with quick matching before falling back to full validation
|
|
5532
|
-
// (state tree nodes must go through full validation for type identity checks)
|
|
5533
|
-
if (isPlainObject(value) && !isStateTreeNode(value)) {
|
|
5534
|
-
for (const type of this._types) {
|
|
5535
|
-
if (this.snapshotLooksLikeType(value, type)) {
|
|
5536
|
-
return type;
|
|
5537
|
-
}
|
|
5538
|
-
}
|
|
5539
|
-
}
|
|
5540
5691
|
}
|
|
5541
5692
|
// find the most accomodating type
|
|
5542
5693
|
// if we are using reconciliation try the current node type first (fix for #1045)
|
|
@@ -7020,6 +7171,7 @@ exports.decorate = decorate;
|
|
|
7020
7171
|
exports.destroy = destroy;
|
|
7021
7172
|
exports.detach = detach;
|
|
7022
7173
|
exports.escapeJsonPath = escapeJsonPath;
|
|
7174
|
+
exports.extendInstance = extendInstance;
|
|
7023
7175
|
exports.flow = flow;
|
|
7024
7176
|
exports.getChildType = getChildType;
|
|
7025
7177
|
exports.getEnv = getEnv;
|