@jbrowse/mobx-state-tree 5.12.0 → 6.0.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 +38 -1
- package/dist/mobx-state-tree.cjs +188 -19
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +189 -21
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -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
|
@@ -1139,6 +1139,10 @@ const snapshotReactionOptions = {
|
|
|
1139
1139
|
throw e;
|
|
1140
1140
|
}
|
|
1141
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 };
|
|
1142
1146
|
/**
|
|
1143
1147
|
* @internal
|
|
1144
1148
|
* @hidden
|
|
@@ -1171,9 +1175,18 @@ class ObjectNode extends BaseNode {
|
|
|
1171
1175
|
_cachedInitialSnapshot;
|
|
1172
1176
|
_cachedInitialSnapshotCreated = false;
|
|
1173
1177
|
_snapshotComputed;
|
|
1178
|
+
// whether _snapshotComputed has ever run — see createObservableInstance
|
|
1179
|
+
_snapshotComputedEvaluated = false;
|
|
1174
1180
|
constructor(complexType, parent, subpath, environment, initialValue) {
|
|
1175
1181
|
super(complexType, parent, subpath, environment);
|
|
1176
|
-
|
|
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);
|
|
1177
1190
|
this.unbox = this.unbox.bind(this);
|
|
1178
1191
|
this._initialSnapshot = freeze(initialValue);
|
|
1179
1192
|
this.identifierAttribute = complexType.identifierAttribute;
|
|
@@ -1257,10 +1270,17 @@ class ObjectNode extends BaseNode {
|
|
|
1257
1270
|
this._isRunningAction = false;
|
|
1258
1271
|
}
|
|
1259
1272
|
this._observableInstanceState = ObservableInstanceLifecycle.CREATED;
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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.
|
|
1264
1284
|
this._childNodes = EMPTY_OBJECT;
|
|
1265
1285
|
this.state = NodeLifeCycle.CREATED;
|
|
1266
1286
|
if (fireHooks) {
|
|
@@ -1365,6 +1385,21 @@ class ObjectNode extends BaseNode {
|
|
|
1365
1385
|
}
|
|
1366
1386
|
return this._snapshotComputed.get();
|
|
1367
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
|
+
}
|
|
1368
1403
|
// NOTE: we use this method to get snapshot without creating @computed overhead
|
|
1369
1404
|
getSnapshot() {
|
|
1370
1405
|
if (!this.isAlive) {
|
|
@@ -3046,9 +3081,7 @@ const EMPTY_OBJECT = Object.freeze({});
|
|
|
3046
3081
|
* @internal
|
|
3047
3082
|
* @hidden
|
|
3048
3083
|
*/
|
|
3049
|
-
const mobxShallow =
|
|
3050
|
-
? { deep: false }
|
|
3051
|
-
: { deep: false, proxy: false };
|
|
3084
|
+
const mobxShallow = { deep: false };
|
|
3052
3085
|
Object.freeze(mobxShallow);
|
|
3053
3086
|
/**
|
|
3054
3087
|
* @internal
|
|
@@ -3312,6 +3345,11 @@ let _typeChecking;
|
|
|
3312
3345
|
function setTypeChecking(enabled) {
|
|
3313
3346
|
_typeChecking = enabled;
|
|
3314
3347
|
}
|
|
3348
|
+
// the ENABLE_TYPE_CHECK env var is fixed at process start, so read it once:
|
|
3349
|
+
// process.env access is a comparatively expensive lookup and isTypeCheckingEnabled
|
|
3350
|
+
// runs on every create() / typed write. The setTypeChecking() override and
|
|
3351
|
+
// dev-mode default stay live below.
|
|
3352
|
+
const _envTypeCheck = typeof process !== "undefined" && process.env?.ENABLE_TYPE_CHECK === "true";
|
|
3315
3353
|
/**
|
|
3316
3354
|
* @internal
|
|
3317
3355
|
* @hidden
|
|
@@ -3319,10 +3357,7 @@ function setTypeChecking(enabled) {
|
|
|
3319
3357
|
function isTypeCheckingEnabled() {
|
|
3320
3358
|
// an explicit setTypeChecking() override (incl. `false`) wins over the
|
|
3321
3359
|
// dev-mode / env-var default, hence ?? rather than ||
|
|
3322
|
-
return
|
|
3323
|
-
(devMode() ||
|
|
3324
|
-
(typeof process !== "undefined" &&
|
|
3325
|
-
process.env?.ENABLE_TYPE_CHECK === "true")));
|
|
3360
|
+
return _typeChecking ?? (devMode() || _envTypeCheck);
|
|
3326
3361
|
}
|
|
3327
3362
|
let _devMode = process.env.NODE_ENV !== "production";
|
|
3328
3363
|
/**
|
|
@@ -3725,6 +3760,11 @@ class SnapshotProcessor extends BaseType {
|
|
|
3725
3760
|
}
|
|
3726
3761
|
const oldGetSnapshot = node.getSnapshot;
|
|
3727
3762
|
node.getSnapshot = () => this.postProcessSnapshot(oldGetSnapshot.call(node), node);
|
|
3763
|
+
// getSnapshot was just rewritten; refresh any snapshot already memoized for
|
|
3764
|
+
// this node (e.g. it was serialized before being moved under this processor)
|
|
3765
|
+
if (node instanceof ObjectNode) {
|
|
3766
|
+
node.refreshMemoizedSnapshot();
|
|
3767
|
+
}
|
|
3728
3768
|
if (!isUnionType(this._subtype)) {
|
|
3729
3769
|
node.getReconciliationType = () => {
|
|
3730
3770
|
return this;
|
|
@@ -3869,7 +3909,11 @@ var MapIdentifierMode;
|
|
|
3869
3909
|
})(MapIdentifierMode || (MapIdentifierMode = {}));
|
|
3870
3910
|
class MSTMap extends mobx.ObservableMap {
|
|
3871
3911
|
constructor(initialData, name) {
|
|
3872
|
-
|
|
3912
|
+
// mobx's default (deep) enhancer. The old `observable.ref.enhancer` arg
|
|
3913
|
+
// read undefined ever since mobx 6 renamed the field to `options_.enhancer`,
|
|
3914
|
+
// so deep has been the effective behavior all along; mobx 7 dropped
|
|
3915
|
+
// `observable.ref` entirely.
|
|
3916
|
+
super(initialData, undefined, name);
|
|
3873
3917
|
}
|
|
3874
3918
|
get(key) {
|
|
3875
3919
|
// maybe this is over-enthousiastic? normalize numeric keys to strings
|
|
@@ -4647,11 +4691,27 @@ class ModelType extends ComplexType {
|
|
|
4647
4691
|
constructor(opts) {
|
|
4648
4692
|
super(opts.name || defaultObjectOptions.name);
|
|
4649
4693
|
Object.assign(this, defaultObjectOptions, opts);
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4694
|
+
if (opts.propertiesArePreProcessed) {
|
|
4695
|
+
// `properties` is a parent type's already-converted + frozen output
|
|
4696
|
+
// (chain step with no new props), so its derived propertyNames and
|
|
4697
|
+
// identifierAttribute are identical to the parent's — reuse them verbatim
|
|
4698
|
+
// (cloneAndEnhance passed them in) instead of re-running Object.keys and
|
|
4699
|
+
// the per-prop identifier scan, both O(props), on every step.
|
|
4700
|
+
this.propertyNames = opts.propertyNames;
|
|
4701
|
+
this.identifierAttribute = opts.identifierAttribute;
|
|
4702
|
+
}
|
|
4703
|
+
else {
|
|
4704
|
+
// `propertiesAreConverted` (set by cloneAndEnhance when props are added)
|
|
4705
|
+
// means every value is already a type — the parent's converted bag merged
|
|
4706
|
+
// with a freshly converted delta — so skip re-converting. Only raw entry
|
|
4707
|
+
// points (`model()`) still need the full toPropertiesObject pass.
|
|
4708
|
+
if (!opts.propertiesAreConverted) {
|
|
4709
|
+
this.properties = toPropertiesObject(this.properties);
|
|
4710
|
+
}
|
|
4711
|
+
freeze(this.properties); // make sure nobody messes with it
|
|
4712
|
+
this.propertyNames = Object.keys(this.properties);
|
|
4713
|
+
this.identifierAttribute = this._getIdentifierAttribute();
|
|
4714
|
+
}
|
|
4655
4715
|
}
|
|
4656
4716
|
_getIdentifierAttribute() {
|
|
4657
4717
|
let identifierAttribute = undefined;
|
|
@@ -4666,9 +4726,35 @@ class ModelType extends ComplexType {
|
|
|
4666
4726
|
return identifierAttribute;
|
|
4667
4727
|
}
|
|
4668
4728
|
cloneAndEnhance(opts) {
|
|
4729
|
+
// Fast path: a chain step that adds no new properties (.actions/.views/
|
|
4730
|
+
// .volatile/.named/pre-postProcessor) reuses this type's already-converted +
|
|
4731
|
+
// frozen properties (and their derived names/identifier) verbatim.
|
|
4732
|
+
const hasNewProps = opts.properties !== undefined && Object.keys(opts.properties).length > 0;
|
|
4733
|
+
if (!hasNewProps) {
|
|
4734
|
+
return new ModelType({
|
|
4735
|
+
name: opts.name || this.name,
|
|
4736
|
+
properties: this.properties,
|
|
4737
|
+
propertiesArePreProcessed: true,
|
|
4738
|
+
// safe to share: propertyNames is never mutated after construction
|
|
4739
|
+
propertyNames: this.propertyNames,
|
|
4740
|
+
identifierAttribute: this.identifierAttribute,
|
|
4741
|
+
initializers: this.initializers.concat(opts.initializers || []),
|
|
4742
|
+
preProcessor: opts.preProcessor || this.preProcessor,
|
|
4743
|
+
postProcessor: opts.postProcessor || this.postProcessor
|
|
4744
|
+
});
|
|
4745
|
+
}
|
|
4746
|
+
// Adding props: this type's `properties` is already converted+frozen, so only
|
|
4747
|
+
// the delta needs conversion. compose passes already-converted props
|
|
4748
|
+
// (cur.properties) and flags them; `.props()` passes a raw declaration. Either
|
|
4749
|
+
// way the merged bag is fully converted, so the constructor skips re-converting
|
|
4750
|
+
// the (potentially large) inherited set — the previous hot spot in compose.
|
|
4751
|
+
const convertedNewProps = opts.propertiesAreConverted
|
|
4752
|
+
? opts.properties
|
|
4753
|
+
: toPropertiesObject(opts.properties);
|
|
4669
4754
|
return new ModelType({
|
|
4670
4755
|
name: opts.name || this.name,
|
|
4671
|
-
properties: Object.assign({}, this.properties,
|
|
4756
|
+
properties: Object.assign({}, this.properties, convertedNewProps),
|
|
4757
|
+
propertiesAreConverted: true,
|
|
4672
4758
|
initializers: this.initializers.concat(opts.initializers || []),
|
|
4673
4759
|
preProcessor: opts.preProcessor || this.preProcessor,
|
|
4674
4760
|
postProcessor: opts.postProcessor || this.postProcessor
|
|
@@ -4765,6 +4851,29 @@ class ModelType extends ComplexType {
|
|
|
4765
4851
|
};
|
|
4766
4852
|
return this.cloneAndEnhance({ initializers: [viewInitializer] });
|
|
4767
4853
|
}
|
|
4854
|
+
/**
|
|
4855
|
+
* @internal
|
|
4856
|
+
* @hidden
|
|
4857
|
+
* Materializes an additional actions/views/state extension onto an
|
|
4858
|
+
* already-created instance, using the same machinery as `.extend()` runs at
|
|
4859
|
+
* creation time. Powers {@link extendInstance}; callers must ensure the writes
|
|
4860
|
+
* are permitted (see extendInstance, which runs this in an action context).
|
|
4861
|
+
*/
|
|
4862
|
+
applyExtensionToInstance(self, extension) {
|
|
4863
|
+
const { actions, views, state, ...rest } = extension;
|
|
4864
|
+
for (const key in rest) {
|
|
4865
|
+
throw fail(`extendInstance should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'`);
|
|
4866
|
+
}
|
|
4867
|
+
if (state) {
|
|
4868
|
+
this.instantiateVolatileState(self, state);
|
|
4869
|
+
}
|
|
4870
|
+
if (views) {
|
|
4871
|
+
this.instantiateViews(self, views);
|
|
4872
|
+
}
|
|
4873
|
+
if (actions) {
|
|
4874
|
+
this.instantiateActions(self, actions);
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4768
4877
|
instantiateViews(self, views) {
|
|
4769
4878
|
// check views return
|
|
4770
4879
|
if (!isPlainObject(views)) {
|
|
@@ -5025,6 +5134,8 @@ function compose(...args) {
|
|
|
5025
5134
|
.reduce((prev, cur) => prev.cloneAndEnhance({
|
|
5026
5135
|
name: `${prev.name}_${cur.name}`,
|
|
5027
5136
|
properties: cur.properties,
|
|
5137
|
+
// cur.properties is another ModelType's already-converted+frozen bag
|
|
5138
|
+
propertiesAreConverted: true,
|
|
5028
5139
|
initializers: cur.initializers,
|
|
5029
5140
|
preProcessor: (snapshot) => cur.applySnapshotPreProcessor(prev.applySnapshotPreProcessor(snapshot)),
|
|
5030
5141
|
postProcessor: (snapshot) => cur.applySnapshotPostProcessor(prev.applySnapshotPostProcessor(snapshot))
|
|
@@ -5040,6 +5151,46 @@ function compose(...args) {
|
|
|
5040
5151
|
function isModelType(type) {
|
|
5041
5152
|
return isType(type) && (type.flags & TypeFlags.Object) > 0;
|
|
5042
5153
|
}
|
|
5154
|
+
/**
|
|
5155
|
+
* `extendInstance` - attaches additional actions, views and volatile state to an
|
|
5156
|
+
* already-created model instance, using the same instantiation machinery as
|
|
5157
|
+
* `.extend()`. This enables lazily loading a model's views/actions chain: create
|
|
5158
|
+
* the model with only its base props (plus any critical-path members), then attach
|
|
5159
|
+
* the rest at runtime — e.g. from a dynamically imported module — without rebuilding
|
|
5160
|
+
* the type or re-hydrating the tree.
|
|
5161
|
+
*
|
|
5162
|
+
* The extension is applied inside an action context, so it works on protected trees.
|
|
5163
|
+
* Attached views are computeds (reactive, memoized) and attached actions get a full
|
|
5164
|
+
* MST action context, exactly like members declared in the original chain.
|
|
5165
|
+
*
|
|
5166
|
+
* Note: the attached members are NOT part of the instance's *static* type. If you
|
|
5167
|
+
* need them typed, declare the augmented shape separately (an `import type` of the
|
|
5168
|
+
* chain's return type is erased at build time, so it costs nothing in the bundle).
|
|
5169
|
+
*
|
|
5170
|
+
* @param instance a live model instance
|
|
5171
|
+
* @param fn receives the instance and returns `{ actions?, views?, state? }`
|
|
5172
|
+
* @returns the same instance
|
|
5173
|
+
*/
|
|
5174
|
+
function extendInstance(instance, fn) {
|
|
5175
|
+
if (!isStateTreeNode(instance)) {
|
|
5176
|
+
throw fail("extendInstance expects a mobx-state-tree node");
|
|
5177
|
+
}
|
|
5178
|
+
const type = getStateTreeNode(instance).type;
|
|
5179
|
+
if (!isModelType(type)) {
|
|
5180
|
+
throw fail("extendInstance can only be used on model instances");
|
|
5181
|
+
}
|
|
5182
|
+
// Views/actions/volatile are installed via defineProperty + makeObservable — the
|
|
5183
|
+
// same "add" operations the initializers perform at creation, before the
|
|
5184
|
+
// write-protection interceptor is attached (see finalizeNewInstance). On a live
|
|
5185
|
+
// instance that interceptor is already active, so run the attach inside an action
|
|
5186
|
+
// context: isRunningAction() then short-circuits assertWritable.
|
|
5187
|
+
const modelType = type;
|
|
5188
|
+
const attach = createActionInvoker(instance, "@@extendInstance", (() => {
|
|
5189
|
+
modelType.applyExtensionToInstance(instance, fn(instance));
|
|
5190
|
+
}));
|
|
5191
|
+
attach();
|
|
5192
|
+
return instance;
|
|
5193
|
+
}
|
|
5043
5194
|
|
|
5044
5195
|
// TODO: implement CoreType using types.custom ?
|
|
5045
5196
|
/**
|
|
@@ -5468,7 +5619,24 @@ class Union extends BaseType {
|
|
|
5468
5619
|
}
|
|
5469
5620
|
return `${baseWithDiscriminator}:\n ${formatValidationErrorLines(errors).join("\n ")}`;
|
|
5470
5621
|
}
|
|
5622
|
+
// Memoizes the discriminator -> member scan below. Union membership is fixed
|
|
5623
|
+
// at construction, so the result for a given `type` string never changes.
|
|
5624
|
+
// Without this, validating a config with many elements drawn from a wide
|
|
5625
|
+
// pluggable union (e.g. jbrowse's 30+ track/adapter types) re-scans every
|
|
5626
|
+
// member — and calls resolveModelType + literal.is() on each — once per
|
|
5627
|
+
// element. With it, each distinct discriminator scans once; the rest are
|
|
5628
|
+
// O(1) map hits. `undefined` (no match OR ambiguous) is cached too.
|
|
5629
|
+
_discriminatorCache;
|
|
5471
5630
|
_findCandidateByTypeDiscriminator(discriminator) {
|
|
5631
|
+
const cache = (this._discriminatorCache ??= new Map());
|
|
5632
|
+
if (cache.has(discriminator)) {
|
|
5633
|
+
return cache.get(discriminator);
|
|
5634
|
+
}
|
|
5635
|
+
const found = this._scanForTypeDiscriminator(discriminator);
|
|
5636
|
+
cache.set(discriminator, found);
|
|
5637
|
+
return found;
|
|
5638
|
+
}
|
|
5639
|
+
_scanForTypeDiscriminator(discriminator) {
|
|
5472
5640
|
let found;
|
|
5473
5641
|
for (const t of this._types) {
|
|
5474
5642
|
const model = resolveModelType(t);
|
|
@@ -7005,6 +7173,7 @@ exports.decorate = decorate;
|
|
|
7005
7173
|
exports.destroy = destroy;
|
|
7006
7174
|
exports.detach = detach;
|
|
7007
7175
|
exports.escapeJsonPath = escapeJsonPath;
|
|
7176
|
+
exports.extendInstance = extendInstance;
|
|
7008
7177
|
exports.flow = flow;
|
|
7009
7178
|
exports.getChildType = getChildType;
|
|
7010
7179
|
exports.getEnv = getEnv;
|