@jbrowse/mobx-state-tree 6.1.0 → 6.3.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 +91 -14
- package/dist/mobx-state-tree.cjs +508 -220
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +508 -220
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
package/dist/mobx-state-tree.cjs
CHANGED
|
@@ -482,30 +482,20 @@ function getIdentifier(target) {
|
|
|
482
482
|
return getStateTreeNode(target).identifier;
|
|
483
483
|
}
|
|
484
484
|
/**
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
* @param getter Function to access the reference.
|
|
489
|
-
* @param checkIfAlive true to also make sure the referenced node is alive (default), false to skip this check.
|
|
490
|
-
* @returns
|
|
485
|
+
* Resolves a reference getter to the referenced node, or `undefined` when the
|
|
486
|
+
* reference is empty, dangling, or (with `checkIfAlive`) points at a dead node.
|
|
487
|
+
* Backs both {@link tryReference} and {@link isValidReference}.
|
|
491
488
|
*/
|
|
492
|
-
function
|
|
489
|
+
function resolveReference(getter, checkIfAlive) {
|
|
493
490
|
try {
|
|
494
491
|
const node = getter();
|
|
495
492
|
if (node === undefined || node === null) {
|
|
496
493
|
return undefined;
|
|
497
494
|
}
|
|
498
|
-
|
|
499
|
-
if (!checkIfAlive) {
|
|
500
|
-
return node;
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
return isAlive(node) ? node : undefined;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
495
|
+
if (!isStateTreeNode(node)) {
|
|
507
496
|
throw fail("The reference to be checked is not one of node, null or undefined");
|
|
508
497
|
}
|
|
498
|
+
return !checkIfAlive || isAlive(node) ? node : undefined;
|
|
509
499
|
}
|
|
510
500
|
catch (e) {
|
|
511
501
|
if (e instanceof InvalidReferenceError) {
|
|
@@ -514,6 +504,17 @@ function tryReference(getter, checkIfAlive = true) {
|
|
|
514
504
|
throw e;
|
|
515
505
|
}
|
|
516
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Tests if a reference is valid (pointing to an existing node and optionally if alive) and returns such reference if the check passes,
|
|
509
|
+
* else it returns undefined.
|
|
510
|
+
*
|
|
511
|
+
* @param getter Function to access the reference.
|
|
512
|
+
* @param checkIfAlive true to also make sure the referenced node is alive (default), false to skip this check.
|
|
513
|
+
* @returns
|
|
514
|
+
*/
|
|
515
|
+
function tryReference(getter, checkIfAlive = true) {
|
|
516
|
+
return resolveReference(getter, checkIfAlive);
|
|
517
|
+
}
|
|
517
518
|
/**
|
|
518
519
|
* Tests if a reference is valid (pointing to an existing node and optionally if alive) and returns if the check passes or not.
|
|
519
520
|
*
|
|
@@ -522,24 +523,7 @@ function tryReference(getter, checkIfAlive = true) {
|
|
|
522
523
|
* @returns
|
|
523
524
|
*/
|
|
524
525
|
function isValidReference(getter, checkIfAlive = true) {
|
|
525
|
-
|
|
526
|
-
const node = getter();
|
|
527
|
-
if (node === undefined || node === null) {
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
else if (isStateTreeNode(node)) {
|
|
531
|
-
return checkIfAlive ? isAlive(node) : true;
|
|
532
|
-
}
|
|
533
|
-
else {
|
|
534
|
-
throw fail("The reference to be checked is not one of node, null or undefined");
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
catch (e) {
|
|
538
|
-
if (e instanceof InvalidReferenceError) {
|
|
539
|
-
return false;
|
|
540
|
-
}
|
|
541
|
-
throw e;
|
|
542
|
-
}
|
|
526
|
+
return resolveReference(getter, checkIfAlive) !== undefined;
|
|
543
527
|
}
|
|
544
528
|
/**
|
|
545
529
|
* Try to resolve a given path relative to a given node.
|
|
@@ -1167,7 +1151,7 @@ class ObjectNode extends BaseNode {
|
|
|
1167
1151
|
}
|
|
1168
1152
|
_autoUnbox = true; // unboxing is disabled when reading child nodes
|
|
1169
1153
|
_isRunningAction = false; // only relevant for root
|
|
1170
|
-
|
|
1154
|
+
_snapshotReactionDisposer;
|
|
1171
1155
|
_observableInstanceState = ObservableInstanceLifecycle.UNINITIALIZED;
|
|
1172
1156
|
_childNodes;
|
|
1173
1157
|
_initialSnapshot;
|
|
@@ -1589,7 +1573,17 @@ class ObjectNode extends BaseNode {
|
|
|
1589
1573
|
}
|
|
1590
1574
|
onSnapshot(onChange) {
|
|
1591
1575
|
this._addSnapshotReaction();
|
|
1592
|
-
|
|
1576
|
+
const unregister = this._internalEventsRegister(InternalEvents.Snapshot, onChange);
|
|
1577
|
+
return () => {
|
|
1578
|
+
unregister();
|
|
1579
|
+
// The reaction re-serializes the whole subtree on every change, so leaving
|
|
1580
|
+
// it running once the last listener is gone would keep charging the node
|
|
1581
|
+
// for a snapshot nobody receives. Referential stability of getSnapshot()
|
|
1582
|
+
// does not depend on it — that comes from keepAlive on _snapshotComputed.
|
|
1583
|
+
if (!this._internalEventsHasSubscribers(InternalEvents.Snapshot)) {
|
|
1584
|
+
this._removeSnapshotReaction();
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1593
1587
|
}
|
|
1594
1588
|
emitSnapshot(snapshot) {
|
|
1595
1589
|
this._internalEventsEmit(InternalEvents.Snapshot, snapshot);
|
|
@@ -1654,11 +1648,23 @@ class ObjectNode extends BaseNode {
|
|
|
1654
1648
|
this.type.applyPatchLocally(this, subpath, patch);
|
|
1655
1649
|
}
|
|
1656
1650
|
_addSnapshotReaction() {
|
|
1657
|
-
if (!this.
|
|
1651
|
+
if (!this._snapshotReactionDisposer) {
|
|
1658
1652
|
const snapshotDisposer = mobx.reaction(() => this.snapshot, snapshot => this.emitSnapshot(snapshot), snapshotReactionOptions);
|
|
1659
1653
|
this.addDisposer(snapshotDisposer);
|
|
1660
|
-
this.
|
|
1654
|
+
this._snapshotReactionDisposer = snapshotDisposer;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
_removeSnapshotReaction() {
|
|
1658
|
+
const disposer = this._snapshotReactionDisposer;
|
|
1659
|
+
if (!disposer) {
|
|
1660
|
+
return;
|
|
1661
1661
|
}
|
|
1662
|
+
this._snapshotReactionDisposer = undefined;
|
|
1663
|
+
// not removeDisposer(), which throws when the registration is already gone —
|
|
1664
|
+
// the last listener can be disposed from within a disposer, i.e. after
|
|
1665
|
+
// aboutToDie has cleared them
|
|
1666
|
+
this._internalEventsUnregister(InternalEvents.Dispose, disposer);
|
|
1667
|
+
disposer();
|
|
1662
1668
|
}
|
|
1663
1669
|
// #region internal event handling
|
|
1664
1670
|
_internalEvents;
|
|
@@ -1735,8 +1741,6 @@ var TypeFlags;
|
|
|
1735
1741
|
* @hidden
|
|
1736
1742
|
*/
|
|
1737
1743
|
const cannotDetermineSubtype = "cannotDetermine";
|
|
1738
|
-
/** @hidden */
|
|
1739
|
-
const $type = Symbol("$type");
|
|
1740
1744
|
/**
|
|
1741
1745
|
* A base type produces a MST node (Node in the state tree)
|
|
1742
1746
|
*
|
|
@@ -1744,16 +1748,39 @@ const $type = Symbol("$type");
|
|
|
1744
1748
|
* @hidden
|
|
1745
1749
|
*/
|
|
1746
1750
|
class BaseType {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1751
|
+
/**
|
|
1752
|
+
* Builds the name of a type that does not get one handed to it. Composite
|
|
1753
|
+
* types (`union`, `array`, `map`, `reference`, ...) override this to fold
|
|
1754
|
+
* their members' names; see {@link name}. It runs after construction, so
|
|
1755
|
+
* unlike a `super(...)` argument it can read the subclass's own fields.
|
|
1756
|
+
*/
|
|
1757
|
+
computeName() {
|
|
1758
|
+
// istanbul ignore next
|
|
1759
|
+
throw fail(`${this.constructor.name} has neither a name nor a computeName`);
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Friendly type name.
|
|
1763
|
+
*
|
|
1764
|
+
* A composite type folds its members' names, which for a union is a map +
|
|
1765
|
+
* join over every member — and the result is read only by error messages and
|
|
1766
|
+
* `describe()`. So those types leave it unset and it is built on first read
|
|
1767
|
+
* and cached: a union that never fails a typecheck never builds one, and
|
|
1768
|
+
* jbrowse builds a union per config slot, tens of thousands per session load.
|
|
1769
|
+
*
|
|
1770
|
+
* Deliberately not a lambda passed to the constructor: that allocated a
|
|
1771
|
+
* closure per type (the very cost being avoided) and made two separately
|
|
1772
|
+
* built but equivalent types compare unequal, since the closures differ.
|
|
1773
|
+
*/
|
|
1774
|
+
get name() {
|
|
1775
|
+
return (this._name ??= this.computeName());
|
|
1776
|
+
}
|
|
1777
|
+
set name(value) {
|
|
1778
|
+
this._name = value;
|
|
1779
|
+
}
|
|
1755
1780
|
constructor(name) {
|
|
1756
|
-
|
|
1781
|
+
if (name !== undefined) {
|
|
1782
|
+
this._name = name;
|
|
1783
|
+
}
|
|
1757
1784
|
}
|
|
1758
1785
|
create(snapshot, environment) {
|
|
1759
1786
|
typecheckInternal(this, snapshot);
|
|
@@ -1798,6 +1825,9 @@ class BaseType {
|
|
|
1798
1825
|
}
|
|
1799
1826
|
}
|
|
1800
1827
|
BaseType.prototype.create = mobx.action(BaseType.prototype.create);
|
|
1828
|
+
// Defaults that every type shares, kept off the individual type objects. See
|
|
1829
|
+
// `isType` and `_name` in the class body.
|
|
1830
|
+
Object.assign(BaseType.prototype, { isType: true, _name: undefined });
|
|
1801
1831
|
/**
|
|
1802
1832
|
* A complex type produces a MST node (Node in the state tree)
|
|
1803
1833
|
*
|
|
@@ -1915,7 +1945,6 @@ function assertIsType(type, argNumber) {
|
|
|
1915
1945
|
assertArg(type, isType, "mobx-state-tree type", argNumber);
|
|
1916
1946
|
}
|
|
1917
1947
|
|
|
1918
|
-
const runningActions = new Map();
|
|
1919
1948
|
/**
|
|
1920
1949
|
* Note: Consider migrating to `createActionTrackingMiddleware2`, it is easier to use.
|
|
1921
1950
|
*
|
|
@@ -1931,6 +1960,12 @@ const runningActions = new Map();
|
|
|
1931
1960
|
* @returns
|
|
1932
1961
|
*/
|
|
1933
1962
|
function createActionTrackingMiddleware(hooks) {
|
|
1963
|
+
// per middleware, not per module: the entries hold the `context` this
|
|
1964
|
+
// middleware's own onStart returned, so two middlewares on one tree would
|
|
1965
|
+
// otherwise overwrite each other's context and then delete the shared entry —
|
|
1966
|
+
// whichever saw `flow_return` second dereferenced `undefined`.
|
|
1967
|
+
// createActionTrackingMiddleware2 scopes its map the same way.
|
|
1968
|
+
const runningActions = new Map();
|
|
1934
1969
|
return function actionTrackingMiddleware(call, next, _abort) {
|
|
1935
1970
|
switch (call.type) {
|
|
1936
1971
|
case "action": {
|
|
@@ -2123,7 +2158,7 @@ function createActionTrackingMiddleware2(middlewareHooks) {
|
|
|
2123
2158
|
};
|
|
2124
2159
|
}
|
|
2125
2160
|
|
|
2126
|
-
function serializeArgument(
|
|
2161
|
+
function serializeArgument(arg) {
|
|
2127
2162
|
if (arg instanceof Date) {
|
|
2128
2163
|
return { $MST_DATE: arg.getTime() };
|
|
2129
2164
|
}
|
|
@@ -2151,7 +2186,7 @@ function serializeArgument(node, actionName, index, arg) {
|
|
|
2151
2186
|
return serializeTheUnserializable(`${e}`);
|
|
2152
2187
|
}
|
|
2153
2188
|
}
|
|
2154
|
-
function deserializeArgument(
|
|
2189
|
+
function deserializeArgument(value) {
|
|
2155
2190
|
if (value && typeof value === "object" && "$MST_DATE" in value) {
|
|
2156
2191
|
return new Date(value["$MST_DATE"]);
|
|
2157
2192
|
}
|
|
@@ -2195,7 +2230,7 @@ function baseApplyAction(target, action) {
|
|
|
2195
2230
|
if (!(typeof resolvedTarget[action.name] === "function")) {
|
|
2196
2231
|
throw fail(`Action '${action.name}' does not exist in '${node.path}'`);
|
|
2197
2232
|
}
|
|
2198
|
-
return resolvedTarget[action.name](...(action.args ? action.args.map(
|
|
2233
|
+
return resolvedTarget[action.name](...(action.args ? action.args.map(deserializeArgument) : []));
|
|
2199
2234
|
}
|
|
2200
2235
|
/**
|
|
2201
2236
|
* Small abstraction around `onAction` and `applyAction`, attaches an action listener to a tree and records all the actions emitted.
|
|
@@ -2312,7 +2347,7 @@ function onAction(target, listener, attachAfter = false) {
|
|
|
2312
2347
|
const info = {
|
|
2313
2348
|
name: rawCall.name,
|
|
2314
2349
|
path: getRelativePathBetweenNodes(getStateTreeNode(target), sourceNode),
|
|
2315
|
-
args: rawCall.args.map(
|
|
2350
|
+
args: rawCall.args.map(serializeArgument)
|
|
2316
2351
|
};
|
|
2317
2352
|
if (attachAfter) {
|
|
2318
2353
|
const res = next(rawCall);
|
|
@@ -2653,6 +2688,8 @@ function shortenPrintValue(valueInString) {
|
|
|
2653
2688
|
}
|
|
2654
2689
|
function toErrorString(error) {
|
|
2655
2690
|
const { value } = error;
|
|
2691
|
+
// every context entry carries a type: they are built by getContextForPath and
|
|
2692
|
+
// by typecheck's initial `[{ path: "", type }]`
|
|
2656
2693
|
const type = error.context[error.context.length - 1].type;
|
|
2657
2694
|
const fullPath = error.context
|
|
2658
2695
|
.map(({ path }) => path)
|
|
@@ -2664,14 +2701,21 @@ function toErrorString(error) {
|
|
|
2664
2701
|
: isPrimitive(value)
|
|
2665
2702
|
? "value"
|
|
2666
2703
|
: "snapshot";
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2704
|
+
let expectation;
|
|
2705
|
+
if (isPrimitiveType(type) || isPrimitive(value)) {
|
|
2706
|
+
expectation = ".";
|
|
2707
|
+
}
|
|
2708
|
+
else {
|
|
2709
|
+
// `isPrimitiveType` is declared `(type: IT) => type is IT`, so its negative
|
|
2710
|
+
// branch narrows to `never` rather than to "some non-primitive type"; the
|
|
2711
|
+
// annotation restores the type it actually has here.
|
|
2712
|
+
const complexType = type;
|
|
2713
|
+
const isSnapshotCompatible = isStateTreeNode(value) && complexType.is(getStateTreeNode(value).snapshot);
|
|
2714
|
+
expectation = `, expected an instance of \`${complexType.name}\` or a snapshot like \`${shortenPrintValue(complexType.describe())}\` instead.${isSnapshotCompatible
|
|
2715
|
+
? " (Note that a snapshot of the provided value is compatible with the targeted type)"
|
|
2716
|
+
: ""}`;
|
|
2717
|
+
}
|
|
2718
|
+
return `${pathPrefix}${currentTypename} ${shortenPrintValue(prettyPrintValue(value))} is not assignable to type: \`${type.name}\`${error.message ? ` (${error.message})` : ""}${expectation}`;
|
|
2675
2719
|
}
|
|
2676
2720
|
/**
|
|
2677
2721
|
* @internal
|
|
@@ -2780,10 +2824,13 @@ class IdentifierCache {
|
|
|
2780
2824
|
addNodeToCache(node, lastCacheUpdate = true) {
|
|
2781
2825
|
if (node.identifierAttribute) {
|
|
2782
2826
|
const identifier = node.identifier;
|
|
2783
|
-
|
|
2784
|
-
|
|
2827
|
+
// one observable-map read, not a has() plus a get(): this runs for every
|
|
2828
|
+
// identified node created
|
|
2829
|
+
let set = this.cache.get(identifier);
|
|
2830
|
+
if (!set) {
|
|
2831
|
+
set = mobx.observable.array([], mobxShallow);
|
|
2832
|
+
this.cache.set(identifier, set);
|
|
2785
2833
|
}
|
|
2786
|
-
const set = this.cache.get(identifier);
|
|
2787
2834
|
if (set.includes(node)) {
|
|
2788
2835
|
throw fail(`Already registered`);
|
|
2789
2836
|
}
|
|
@@ -3123,7 +3170,11 @@ function isPlainObject(value) {
|
|
|
3123
3170
|
return false;
|
|
3124
3171
|
}
|
|
3125
3172
|
const proto = Object.getPrototypeOf(value);
|
|
3126
|
-
|
|
3173
|
+
// Fast path for an object literal from this realm — nearly every snapshot MST
|
|
3174
|
+
// inspects. The fallback compares the constructor's *source text* so that an
|
|
3175
|
+
// `Object` from another realm (iframe, vm context) still counts as plain; it
|
|
3176
|
+
// is ~6x slower, so only cross-realm and null-prototype values pay for it.
|
|
3177
|
+
if (proto === Object.prototype || proto == null) {
|
|
3127
3178
|
return true;
|
|
3128
3179
|
}
|
|
3129
3180
|
return proto.constructor?.toString() === plainObjectString;
|
|
@@ -3493,6 +3544,8 @@ function createFlowSpawner(name, generator) {
|
|
|
3493
3544
|
const spawner = function flowSpawner(...flowArgs) {
|
|
3494
3545
|
// Implementation based on https://github.com/tj/co/blob/master/index.js
|
|
3495
3546
|
const runId = getNextActionId();
|
|
3547
|
+
// no `!`: the guard below is the point, and asserting non-null first made it
|
|
3548
|
+
// dead to the checker while leaving it live at runtime
|
|
3496
3549
|
const parentContext = getCurrentActionContext();
|
|
3497
3550
|
if (!parentContext) {
|
|
3498
3551
|
throw fail("a mst flow must always have a parent context");
|
|
@@ -3713,14 +3766,30 @@ const $preProcessorFailed = Symbol("$preProcessorFailed");
|
|
|
3713
3766
|
class SnapshotProcessor extends BaseType {
|
|
3714
3767
|
_subtype;
|
|
3715
3768
|
_processors;
|
|
3769
|
+
_flags;
|
|
3770
|
+
// memoized once stable; see the guard on Union.flags
|
|
3716
3771
|
get flags() {
|
|
3717
|
-
|
|
3772
|
+
const cached = this._flags;
|
|
3773
|
+
if (cached !== undefined) {
|
|
3774
|
+
return cached;
|
|
3775
|
+
}
|
|
3776
|
+
const result = this._subtype.flags | TypeFlags.SnapshotProcessor;
|
|
3777
|
+
if (!(result & TypeFlags.Late)) {
|
|
3778
|
+
this._flags = result;
|
|
3779
|
+
}
|
|
3780
|
+
return result;
|
|
3718
3781
|
}
|
|
3719
3782
|
constructor(_subtype, _processors, name) {
|
|
3720
|
-
|
|
3783
|
+
// `|| undefined`, so an empty name still falls through to the subtype's, as
|
|
3784
|
+
// it did when this read `name || _subtype.name`. Passing `""` straight to
|
|
3785
|
+
// `super` would keep it: `_name ??= computeName()` only fills a nullish one.
|
|
3786
|
+
super(name || undefined);
|
|
3721
3787
|
this._subtype = _subtype;
|
|
3722
3788
|
this._processors = _processors;
|
|
3723
3789
|
}
|
|
3790
|
+
computeName() {
|
|
3791
|
+
return this._subtype.name;
|
|
3792
|
+
}
|
|
3724
3793
|
describe() {
|
|
3725
3794
|
return `snapshotProcessor(${this._subtype.describe()})`;
|
|
3726
3795
|
}
|
|
@@ -3745,8 +3814,10 @@ class SnapshotProcessor extends BaseType {
|
|
|
3745
3814
|
return sn;
|
|
3746
3815
|
}
|
|
3747
3816
|
_fixNode(node) {
|
|
3748
|
-
// the node
|
|
3749
|
-
|
|
3817
|
+
// the node's type is the *inner* type, so `getType(instance).create(...)`
|
|
3818
|
+
// would bypass the processors — point it at ours instead
|
|
3819
|
+
const nodeType = node.type;
|
|
3820
|
+
nodeType.create = this.create.bind(this);
|
|
3750
3821
|
if (node instanceof ObjectNode) {
|
|
3751
3822
|
node.hasSnapshotPostProcessor = !!this._processors.postProcessor;
|
|
3752
3823
|
}
|
|
@@ -3815,11 +3886,6 @@ class SnapshotProcessor extends BaseType {
|
|
|
3815
3886
|
return this._subtype.isMatchingSnapshotId(current, processedSn);
|
|
3816
3887
|
}
|
|
3817
3888
|
}
|
|
3818
|
-
function proxyNodeTypeMethods(nodeType, snapshotProcessorType, ...methods) {
|
|
3819
|
-
for (const method of methods) {
|
|
3820
|
-
nodeType[method] = snapshotProcessorType[method].bind(snapshotProcessorType);
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
3889
|
/**
|
|
3824
3890
|
* `types.snapshotProcessor` - Runs a pre/post snapshot processor before/after serializing a given type.
|
|
3825
3891
|
*
|
|
@@ -3870,6 +3936,41 @@ function snapshotProcessor(type, processors, name) {
|
|
|
3870
3936
|
return new SnapshotProcessor(type, processors, name);
|
|
3871
3937
|
}
|
|
3872
3938
|
|
|
3939
|
+
/**
|
|
3940
|
+
* Append `hooks` to a collection type's initializer list. `EMPTY_ARRAY` is the
|
|
3941
|
+
* shared default, so a type that never calls `.hooks()` allocates nothing.
|
|
3942
|
+
*
|
|
3943
|
+
* @internal
|
|
3944
|
+
* @hidden
|
|
3945
|
+
*/
|
|
3946
|
+
function appendHookInitializer(current, hooks) {
|
|
3947
|
+
return current.length > 0 ? [...current, hooks] : [hooks];
|
|
3948
|
+
}
|
|
3949
|
+
/**
|
|
3950
|
+
* Install every hook the type's initializers produce onto `instance` as an MST
|
|
3951
|
+
* action, mirroring how a model's `.actions()` members are attached.
|
|
3952
|
+
*
|
|
3953
|
+
* @internal
|
|
3954
|
+
* @hidden
|
|
3955
|
+
*/
|
|
3956
|
+
function installHookInitializers(hookInitializers, instance) {
|
|
3957
|
+
const addProp = !devMode() ? addHiddenFinalProp : addHiddenWritableProp;
|
|
3958
|
+
for (const initializer of hookInitializers) {
|
|
3959
|
+
const hooks = initializer(instance);
|
|
3960
|
+
for (const name of Object.keys(hooks)) {
|
|
3961
|
+
const hook = hooks[name];
|
|
3962
|
+
addProp(instance, name, createActionInvoker(instance, name, hook));
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
/**
|
|
3967
|
+
* The empty initializer list every collection type starts with.
|
|
3968
|
+
*
|
|
3969
|
+
* @internal
|
|
3970
|
+
* @hidden
|
|
3971
|
+
*/
|
|
3972
|
+
const NO_HOOK_INITIALIZERS = EMPTY_ARRAY;
|
|
3973
|
+
|
|
3873
3974
|
const needsIdentifierError = `Map.put can only be used to store complex values that have an identifier type attribute`;
|
|
3874
3975
|
function tryCollectModelTypes(type, modelTypes) {
|
|
3875
3976
|
const subtypes = type.getSubTypes();
|
|
@@ -3966,21 +4067,21 @@ class MSTMap extends mobx.ObservableMap {
|
|
|
3966
4067
|
*/
|
|
3967
4068
|
class MapType extends ComplexType {
|
|
3968
4069
|
_subType;
|
|
4070
|
+
hookInitializers;
|
|
3969
4071
|
identifierMode = MapIdentifierMode.UNKNOWN;
|
|
3970
4072
|
mapIdentifierAttribute = undefined;
|
|
3971
4073
|
flags = TypeFlags.Map;
|
|
3972
|
-
hookInitializers =
|
|
3973
|
-
|
|
3974
|
-
super(name);
|
|
4074
|
+
constructor(_subType, hookInitializers = NO_HOOK_INITIALIZERS) {
|
|
4075
|
+
super();
|
|
3975
4076
|
this._subType = _subType;
|
|
3976
|
-
this._determineIdentifierMode();
|
|
3977
4077
|
this.hookInitializers = hookInitializers;
|
|
4078
|
+
this._determineIdentifierMode();
|
|
4079
|
+
}
|
|
4080
|
+
computeName() {
|
|
4081
|
+
return `Map<string, ${this._subType.name}>`;
|
|
3978
4082
|
}
|
|
3979
4083
|
hooks(hooks) {
|
|
3980
|
-
|
|
3981
|
-
? this.hookInitializers.concat(hooks)
|
|
3982
|
-
: [hooks];
|
|
3983
|
-
return new MapType(this.name, this._subType, hookInitializers);
|
|
4084
|
+
return new MapType(this._subType, appendHookInitializer(this.hookInitializers, hooks));
|
|
3984
4085
|
}
|
|
3985
4086
|
instantiate(parent, subpath, environment, initialValue) {
|
|
3986
4087
|
this._determineIdentifierMode();
|
|
@@ -4023,15 +4124,7 @@ class MapType extends ComplexType {
|
|
|
4023
4124
|
}
|
|
4024
4125
|
finalizeNewInstance(node, instance) {
|
|
4025
4126
|
mobx._interceptReads(instance, node.unbox);
|
|
4026
|
-
|
|
4027
|
-
type.hookInitializers.forEach(initializer => {
|
|
4028
|
-
const hooks = initializer(instance);
|
|
4029
|
-
Object.keys(hooks).forEach(name => {
|
|
4030
|
-
const hook = hooks[name];
|
|
4031
|
-
const actionInvoker = createActionInvoker(instance, name, hook);
|
|
4032
|
-
(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(instance, name, actionInvoker);
|
|
4033
|
-
});
|
|
4034
|
-
});
|
|
4127
|
+
installHookInitializers(node.type.hookInitializers, instance);
|
|
4035
4128
|
mobx.intercept(instance, this.willChange);
|
|
4036
4129
|
mobx.observe(instance, this.didChange);
|
|
4037
4130
|
}
|
|
@@ -4214,7 +4307,7 @@ MapType.prototype.applySnapshot = mobx.action(MapType.prototype.applySnapshot);
|
|
|
4214
4307
|
* @returns
|
|
4215
4308
|
*/
|
|
4216
4309
|
function map(subtype) {
|
|
4217
|
-
return new MapType(
|
|
4310
|
+
return new MapType(subtype);
|
|
4218
4311
|
}
|
|
4219
4312
|
/**
|
|
4220
4313
|
* Returns if a given value represents a map type.
|
|
@@ -4232,18 +4325,18 @@ function isMapType(type) {
|
|
|
4232
4325
|
*/
|
|
4233
4326
|
class ArrayType extends ComplexType {
|
|
4234
4327
|
_subType;
|
|
4328
|
+
hookInitializers;
|
|
4235
4329
|
flags = TypeFlags.Array;
|
|
4236
|
-
hookInitializers =
|
|
4237
|
-
|
|
4238
|
-
super(name);
|
|
4330
|
+
constructor(_subType, hookInitializers = NO_HOOK_INITIALIZERS) {
|
|
4331
|
+
super();
|
|
4239
4332
|
this._subType = _subType;
|
|
4240
4333
|
this.hookInitializers = hookInitializers;
|
|
4241
4334
|
}
|
|
4335
|
+
computeName() {
|
|
4336
|
+
return `${this._subType.name}[]`;
|
|
4337
|
+
}
|
|
4242
4338
|
hooks(hooks) {
|
|
4243
|
-
|
|
4244
|
-
? this.hookInitializers.concat(hooks)
|
|
4245
|
-
: [hooks];
|
|
4246
|
-
return new ArrayType(this.name, this._subType, hookInitializers);
|
|
4339
|
+
return new ArrayType(this._subType, appendHookInitializer(this.hookInitializers, hooks));
|
|
4247
4340
|
}
|
|
4248
4341
|
instantiate(parent, subpath, environment, initialValue) {
|
|
4249
4342
|
return createObjectNode(this, parent, subpath, environment, initialValue);
|
|
@@ -4263,15 +4356,7 @@ class ArrayType extends ComplexType {
|
|
|
4263
4356
|
}
|
|
4264
4357
|
finalizeNewInstance(node, instance) {
|
|
4265
4358
|
mobx._getAdministration(instance).dehancer = node.unbox;
|
|
4266
|
-
|
|
4267
|
-
type.hookInitializers.forEach(initializer => {
|
|
4268
|
-
const hooks = initializer(instance);
|
|
4269
|
-
Object.keys(hooks).forEach(name => {
|
|
4270
|
-
const hook = hooks[name];
|
|
4271
|
-
const actionInvoker = createActionInvoker(instance, name, hook);
|
|
4272
|
-
(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(instance, name, actionInvoker);
|
|
4273
|
-
});
|
|
4274
|
-
});
|
|
4359
|
+
installHookInitializers(node.type.hookInitializers, instance);
|
|
4275
4360
|
mobx.intercept(instance, this.willChange);
|
|
4276
4361
|
mobx.observe(instance, this.didChange);
|
|
4277
4362
|
}
|
|
@@ -4292,14 +4377,17 @@ class ArrayType extends ComplexType {
|
|
|
4292
4377
|
const node = getStateTreeNode(change.object);
|
|
4293
4378
|
node.assertWritable({ subpath: `${change.index}` });
|
|
4294
4379
|
const subType = node.type._subType;
|
|
4295
|
-
const childNodes = node.getChildren();
|
|
4296
4380
|
switch (change.type) {
|
|
4297
4381
|
case "update":
|
|
4298
4382
|
{
|
|
4299
4383
|
if (change.newValue === change.object[change.index]) {
|
|
4300
4384
|
return null;
|
|
4301
4385
|
}
|
|
4302
|
-
const updatedNodes = reconcileArrayChildren(node, subType,
|
|
4386
|
+
const updatedNodes = reconcileArrayChildren(node, subType,
|
|
4387
|
+
// only the replaced child is reconciled, so read that one node
|
|
4388
|
+
// directly — `node.getChildren()` copies the whole backing array,
|
|
4389
|
+
// which made a single-element assignment cost O(array length)
|
|
4390
|
+
[node.getChildNode(`${change.index}`)], [change.newValue], change.index);
|
|
4303
4391
|
if (!updatedNodes) {
|
|
4304
4392
|
return null;
|
|
4305
4393
|
}
|
|
@@ -4309,6 +4397,7 @@ class ArrayType extends ComplexType {
|
|
|
4309
4397
|
case "splice":
|
|
4310
4398
|
{
|
|
4311
4399
|
const { index, removedCount, added } = change;
|
|
4400
|
+
const childNodes = node.getChildren();
|
|
4312
4401
|
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added, index);
|
|
4313
4402
|
if (!addedNodes) {
|
|
4314
4403
|
return null;
|
|
@@ -4433,7 +4522,7 @@ ArrayType.prototype.applySnapshot = mobx.action(ArrayType.prototype.applySnapsho
|
|
|
4433
4522
|
*/
|
|
4434
4523
|
function array(subtype) {
|
|
4435
4524
|
assertIsType(subtype, 1);
|
|
4436
|
-
return new ArrayType(
|
|
4525
|
+
return new ArrayType(subtype);
|
|
4437
4526
|
}
|
|
4438
4527
|
/**
|
|
4439
4528
|
* @param firstNewPath index the reconciled slice starts at; both call sites
|
|
@@ -4448,8 +4537,12 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, firstNew
|
|
|
4448
4537
|
// whose id extraction needs type-specific preprocessing (union,
|
|
4449
4538
|
// snapshotProcessor, late, ...) are intentionally excluded: areSame must run
|
|
4450
4539
|
// `is()` before their id check, so they stay on the scan path.
|
|
4540
|
+
// With at most one old node the scan below is already O(1), so building the
|
|
4541
|
+
// index would only add a Map allocation to every single-element write.
|
|
4451
4542
|
let idIndex;
|
|
4452
|
-
if (
|
|
4543
|
+
if (oldNodes.length > 1 &&
|
|
4544
|
+
childType instanceof ModelType &&
|
|
4545
|
+
childType.identifierAttribute) {
|
|
4453
4546
|
const byId = new Map();
|
|
4454
4547
|
for (const n of oldNodes) {
|
|
4455
4548
|
if (n instanceof ObjectNode && n.identifier !== null) {
|
|
@@ -4611,14 +4704,52 @@ const POST_PROCESS_SNAPSHOT = "postProcessSnapshot";
|
|
|
4611
4704
|
function getPropObservable(storedValue, key) {
|
|
4612
4705
|
return mobx.getAtom(storedValue, key);
|
|
4613
4706
|
}
|
|
4707
|
+
/**
|
|
4708
|
+
* All of an instance's per-property `ObservableValue`s in one map, or
|
|
4709
|
+
* `undefined` if this mobx does not expose them.
|
|
4710
|
+
*
|
|
4711
|
+
* `observable.object` returns a **Proxy**, and `getAtom(storedValue, key)`
|
|
4712
|
+
* probes it with four `isObservableArray/Set/Map/Object` guards plus a `$mobx`
|
|
4713
|
+
* read — each one a marker-property read that goes through the proxy's `get`
|
|
4714
|
+
* trap. Paying that per property made the trap machinery the bulk of a wide
|
|
4715
|
+
* model's `getSnapshot`; resolving the administration once and reading its
|
|
4716
|
+
* property map directly reduces the per-property cost to a `Map.get`.
|
|
4717
|
+
*
|
|
4718
|
+
* `values_` is mobx-internal, hence the undefined result: every caller keeps a
|
|
4719
|
+
* path built on supported API, so a mobx that drops it degrades to the old
|
|
4720
|
+
* speed rather than breaking.
|
|
4721
|
+
*
|
|
4722
|
+
* The map is complete for our instances. `getAtom` falls back to
|
|
4723
|
+
* `materializeLazy{Computed,Observable}_` because mobx defers constructing an
|
|
4724
|
+
* `ObservableValue` for *decorator* annotations; `createNewInstance` builds
|
|
4725
|
+
* these through `observable.object`, which populates `values_` eagerly for
|
|
4726
|
+
* every declared property.
|
|
4727
|
+
*/
|
|
4728
|
+
function getPropObservables(storedValue) {
|
|
4729
|
+
const adm = mobx._getAdministration(storedValue);
|
|
4730
|
+
return adm.values_;
|
|
4731
|
+
}
|
|
4614
4732
|
function objectTypeToString() {
|
|
4615
4733
|
return getStateTreeNode(this).toString();
|
|
4616
4734
|
}
|
|
4617
|
-
const
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4735
|
+
const ANONYMOUS_MODEL_NAME = "AnonymousModel";
|
|
4736
|
+
/**
|
|
4737
|
+
* A plain loop rather than `forAllProps`, which allocated a closure and made an
|
|
4738
|
+
* indirect call per property. This runs once per `types.model()` over every
|
|
4739
|
+
* declared property, and jbrowse builds a ~40-slot schema per track.
|
|
4740
|
+
*/
|
|
4741
|
+
function findIdentifierAttribute(properties, propertyNames) {
|
|
4742
|
+
let identifierAttribute = undefined;
|
|
4743
|
+
for (const propName of propertyNames) {
|
|
4744
|
+
if (properties[propName].flags & TypeFlags.Identifier) {
|
|
4745
|
+
if (identifierAttribute) {
|
|
4746
|
+
throw fail(`Cannot define property '${propName}' as object identifier, property '${identifierAttribute}' is already defined as identifier property`);
|
|
4747
|
+
}
|
|
4748
|
+
identifierAttribute = propName;
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
return identifierAttribute;
|
|
4752
|
+
}
|
|
4622
4753
|
function toPropertiesObject(declaredProps) {
|
|
4623
4754
|
// loop through properties and ensures that all items are types
|
|
4624
4755
|
return Object.keys(declaredProps).reduce((props, key) => {
|
|
@@ -4684,14 +4815,29 @@ class ModelType extends ComplexType {
|
|
|
4684
4815
|
// to check the first instance we finalize (see finalizeNewInstance)
|
|
4685
4816
|
duplicateKeysChecked = false;
|
|
4686
4817
|
constructor(opts) {
|
|
4687
|
-
|
|
4688
|
-
Object.assign(this,
|
|
4818
|
+
// `??`, not `||`: `types.model("", {})` names the model "". The old
|
|
4819
|
+
// `Object.assign(this, defaults, opts)` below overwrote the `||` fallback
|
|
4820
|
+
// with opts.name afterwards, so that only worked by accident.
|
|
4821
|
+
super(opts.name ?? ANONYMOUS_MODEL_NAME);
|
|
4822
|
+
// Every field is assigned here, unconditionally and in a fixed order,
|
|
4823
|
+
// rather than by `Object.assign(this, defaultObjectOptions, opts)`. `opts`
|
|
4824
|
+
// carries a different key set at each of the three call sites — `model()`,
|
|
4825
|
+
// and cloneAndEnhance's preprocessed / converted paths — so copying it
|
|
4826
|
+
// wholesale gave ModelType three hidden classes, making every later read of
|
|
4827
|
+
// `type.properties` / `type.propertyNames` (getSnapshot's inner loop, among
|
|
4828
|
+
// others) polymorphic. It also left the internal `propertiesArePreProcessed`
|
|
4829
|
+
// / `propertiesAreConverted` plumbing on the type for its whole lifetime.
|
|
4830
|
+
this.initializers = opts.initializers ?? EMPTY_ARRAY;
|
|
4831
|
+
this.preProcessor = opts.preProcessor;
|
|
4832
|
+
this.postProcessor = opts.postProcessor;
|
|
4833
|
+
const declaredProperties = (opts.properties ?? EMPTY_OBJECT);
|
|
4689
4834
|
if (opts.propertiesArePreProcessed) {
|
|
4690
4835
|
// `properties` is a parent type's already-converted + frozen output
|
|
4691
4836
|
// (chain step with no new props), so its derived propertyNames and
|
|
4692
4837
|
// identifierAttribute are identical to the parent's — reuse them verbatim
|
|
4693
4838
|
// (cloneAndEnhance passed them in) instead of re-running Object.keys and
|
|
4694
4839
|
// the per-prop identifier scan, both O(props), on every step.
|
|
4840
|
+
this.properties = declaredProperties;
|
|
4695
4841
|
this.propertyNames = opts.propertyNames;
|
|
4696
4842
|
this.identifierAttribute = opts.identifierAttribute;
|
|
4697
4843
|
}
|
|
@@ -4700,26 +4846,16 @@ class ModelType extends ComplexType {
|
|
|
4700
4846
|
// means every value is already a type — the parent's converted bag merged
|
|
4701
4847
|
// with a freshly converted delta — so skip re-converting. Only raw entry
|
|
4702
4848
|
// points (`model()`) still need the full toPropertiesObject pass.
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4849
|
+
const properties = (opts.propertiesAreConverted
|
|
4850
|
+
? declaredProperties
|
|
4851
|
+
: toPropertiesObject(declaredProperties));
|
|
4852
|
+
this.properties = properties;
|
|
4853
|
+
freeze(properties); // make sure nobody messes with it
|
|
4854
|
+
const propertyNames = Object.keys(properties);
|
|
4855
|
+
this.propertyNames = propertyNames;
|
|
4856
|
+
this.identifierAttribute = findIdentifierAttribute(properties, propertyNames);
|
|
4709
4857
|
}
|
|
4710
4858
|
}
|
|
4711
|
-
_getIdentifierAttribute() {
|
|
4712
|
-
let identifierAttribute = undefined;
|
|
4713
|
-
this.forAllProps((propName, propType) => {
|
|
4714
|
-
if (propType.flags & TypeFlags.Identifier) {
|
|
4715
|
-
if (identifierAttribute) {
|
|
4716
|
-
throw fail(`Cannot define property '${propName}' as object identifier, property '${identifierAttribute}' is already defined as identifier property`);
|
|
4717
|
-
}
|
|
4718
|
-
identifierAttribute = propName;
|
|
4719
|
-
}
|
|
4720
|
-
});
|
|
4721
|
-
return identifierAttribute;
|
|
4722
|
-
}
|
|
4723
4859
|
cloneAndEnhance(opts) {
|
|
4724
4860
|
// Fast path: a chain step that adds no new properties (.actions/.views/
|
|
4725
4861
|
// .volatile/.named/pre-postProcessor) reuses this type's already-converted +
|
|
@@ -4822,19 +4958,7 @@ class ModelType extends ComplexType {
|
|
|
4822
4958
|
}
|
|
4823
4959
|
extend(fn) {
|
|
4824
4960
|
const initializer = (self) => {
|
|
4825
|
-
|
|
4826
|
-
for (const key in rest) {
|
|
4827
|
-
throw fail(`The \`extend\` function should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'`);
|
|
4828
|
-
}
|
|
4829
|
-
if (state) {
|
|
4830
|
-
this.instantiateVolatileState(self, state);
|
|
4831
|
-
}
|
|
4832
|
-
if (views) {
|
|
4833
|
-
this.instantiateViews(self, views);
|
|
4834
|
-
}
|
|
4835
|
-
if (actions) {
|
|
4836
|
-
this.instantiateActions(self, actions);
|
|
4837
|
-
}
|
|
4961
|
+
this.applyExtension(self, fn(self), "The `extend` function");
|
|
4838
4962
|
return self;
|
|
4839
4963
|
};
|
|
4840
4964
|
return this.cloneAndEnhance({ initializers: [initializer] });
|
|
@@ -4855,9 +4979,17 @@ class ModelType extends ComplexType {
|
|
|
4855
4979
|
* are permitted (see extendInstance, which runs this in an action context).
|
|
4856
4980
|
*/
|
|
4857
4981
|
applyExtensionToInstance(self, extension) {
|
|
4982
|
+
this.applyExtension(self, extension, "extendInstance");
|
|
4983
|
+
}
|
|
4984
|
+
/**
|
|
4985
|
+
* Materializes an `{ actions, views, state }` bundle onto an instance. Shared
|
|
4986
|
+
* by `.extend()` (at creation time) and `applyExtensionToInstance` (on a live
|
|
4987
|
+
* instance); `subject` only names the caller in the invalid-key error.
|
|
4988
|
+
*/
|
|
4989
|
+
applyExtension(self, extension, subject) {
|
|
4858
4990
|
const { actions, views, state, ...rest } = extension;
|
|
4859
4991
|
for (const key in rest) {
|
|
4860
|
-
throw fail(
|
|
4992
|
+
throw fail(`${subject} should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'`);
|
|
4861
4993
|
}
|
|
4862
4994
|
if (state) {
|
|
4863
4995
|
this.instantiateVolatileState(self, state);
|
|
@@ -4933,9 +5065,20 @@ class ModelType extends ComplexType {
|
|
|
4933
5065
|
}
|
|
4934
5066
|
finalizeNewInstance(node, instance) {
|
|
4935
5067
|
addHiddenFinalProp(instance, "toString", objectTypeToString);
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
5068
|
+
// `_interceptReads(instance, name, ...)` assigns exactly this `dehancer`,
|
|
5069
|
+
// but reaches the ObservableValue via getAtom — several proxy-trap reads
|
|
5070
|
+
// per property (see getPropObservables). Resolve the map once instead.
|
|
5071
|
+
const observables = getPropObservables(instance);
|
|
5072
|
+
if (observables) {
|
|
5073
|
+
for (const name of this.propertyNames) {
|
|
5074
|
+
observables.get(name).dehancer = node.unbox;
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
else {
|
|
5078
|
+
this.forAllProps(name => {
|
|
5079
|
+
mobx._interceptReads(instance, name, node.unbox);
|
|
5080
|
+
});
|
|
5081
|
+
}
|
|
4939
5082
|
this.initializers.reduce((self, fn) => fn(self), instance);
|
|
4940
5083
|
// views, actions and volatile share the instance namespace with properties,
|
|
4941
5084
|
// so a view/action reusing a property name silently clobbers that property's
|
|
@@ -4988,9 +5131,18 @@ class ModelType extends ComplexType {
|
|
|
4988
5131
|
}
|
|
4989
5132
|
getChildren(node) {
|
|
4990
5133
|
const names = this.propertyNames;
|
|
5134
|
+
const storedValue = node.storedValue;
|
|
5135
|
+
const observables = getPropObservables(storedValue);
|
|
4991
5136
|
const res = new Array(names.length);
|
|
4992
5137
|
for (let i = 0; i < names.length; i++) {
|
|
4993
|
-
|
|
5138
|
+
const name = names[i];
|
|
5139
|
+
const childNode = (observables
|
|
5140
|
+
? observables.get(name)
|
|
5141
|
+
: getPropObservable(storedValue, name))?.raw();
|
|
5142
|
+
if (!childNode) {
|
|
5143
|
+
throw fail(`Node not available for property ${name}`);
|
|
5144
|
+
}
|
|
5145
|
+
res[i] = childNode;
|
|
4994
5146
|
}
|
|
4995
5147
|
return res;
|
|
4996
5148
|
}
|
|
@@ -5016,14 +5168,17 @@ class ModelType extends ComplexType {
|
|
|
5016
5168
|
const res = {};
|
|
5017
5169
|
const storedValue = node.storedValue;
|
|
5018
5170
|
const properties = this.properties;
|
|
5171
|
+
const observables = getPropObservables(storedValue);
|
|
5019
5172
|
for (const name of this.propertyNames) {
|
|
5020
5173
|
// One mobx lookup serves both purposes: reportObserved so the snapshot
|
|
5021
5174
|
// computed recomputes when the child is reassigned (raw() below does not
|
|
5022
5175
|
// track), and raw() to read the child node. Going through getChildNode
|
|
5023
5176
|
// would repeat the same lookup for every property.
|
|
5024
|
-
const observable =
|
|
5025
|
-
|
|
5026
|
-
|
|
5177
|
+
const observable = observables
|
|
5178
|
+
? observables.get(name)
|
|
5179
|
+
: getPropObservable(storedValue, name);
|
|
5180
|
+
observable?.reportObserved();
|
|
5181
|
+
const childNode = observable?.raw();
|
|
5027
5182
|
if (!childNode) {
|
|
5028
5183
|
throw fail(`Node not available for property ${name}`);
|
|
5029
5184
|
}
|
|
@@ -5124,7 +5279,7 @@ function model(...args) {
|
|
|
5124
5279
|
if (devMode() && typeof args[0] !== "string" && args[1]) {
|
|
5125
5280
|
throw fail("Model creation failed. First argument must be a string when two arguments are provided");
|
|
5126
5281
|
}
|
|
5127
|
-
const name = typeof args[0] === "string" ? args.shift() :
|
|
5282
|
+
const name = typeof args[0] === "string" ? args.shift() : ANONYMOUS_MODEL_NAME;
|
|
5128
5283
|
const properties = args.shift() || {};
|
|
5129
5284
|
return new ModelType({ name, properties });
|
|
5130
5285
|
}
|
|
@@ -5138,7 +5293,7 @@ function model(...args) {
|
|
|
5138
5293
|
function compose(...args) {
|
|
5139
5294
|
// TODO: just join the base type names if no name is provided
|
|
5140
5295
|
const hasTypename = typeof args[0] === "string";
|
|
5141
|
-
const typeName = hasTypename ? args[0] :
|
|
5296
|
+
const typeName = hasTypename ? args[0] : ANONYMOUS_MODEL_NAME;
|
|
5142
5297
|
if (hasTypename) {
|
|
5143
5298
|
args.shift();
|
|
5144
5299
|
}
|
|
@@ -5364,6 +5519,12 @@ function getPrimitiveFactoryFromValue(value) {
|
|
|
5364
5519
|
/**
|
|
5365
5520
|
* Returns if a given value represents a primitive type.
|
|
5366
5521
|
*
|
|
5522
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
5523
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
5524
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
5525
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
5526
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
5527
|
+
*
|
|
5367
5528
|
* @param type
|
|
5368
5529
|
* @returns
|
|
5369
5530
|
*/
|
|
@@ -5373,6 +5534,8 @@ function isPrimitiveType(type) {
|
|
|
5373
5534
|
(TypeFlags.String |
|
|
5374
5535
|
TypeFlags.Number |
|
|
5375
5536
|
TypeFlags.Integer |
|
|
5537
|
+
TypeFlags.Float |
|
|
5538
|
+
TypeFlags.Finite |
|
|
5376
5539
|
TypeFlags.Boolean |
|
|
5377
5540
|
TypeFlags.Date)) >
|
|
5378
5541
|
0);
|
|
@@ -5426,6 +5589,12 @@ function literal(value) {
|
|
|
5426
5589
|
/**
|
|
5427
5590
|
* Returns if a given value represents a literal type.
|
|
5428
5591
|
*
|
|
5592
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
5593
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
5594
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
5595
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
5596
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
5597
|
+
*
|
|
5429
5598
|
* @param type
|
|
5430
5599
|
* @returns
|
|
5431
5600
|
*/
|
|
@@ -5505,11 +5674,17 @@ function refinement(...args) {
|
|
|
5505
5674
|
/**
|
|
5506
5675
|
* Returns if a given value is a refinement type.
|
|
5507
5676
|
*
|
|
5677
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
5678
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
5679
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
5680
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
5681
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
5682
|
+
*
|
|
5508
5683
|
* @param type
|
|
5509
5684
|
* @returns
|
|
5510
5685
|
*/
|
|
5511
5686
|
function isRefinementType(type) {
|
|
5512
|
-
return (type.flags & TypeFlags.Refinement) > 0;
|
|
5687
|
+
return isType(type) && (type.flags & TypeFlags.Refinement) > 0;
|
|
5513
5688
|
}
|
|
5514
5689
|
|
|
5515
5690
|
/**
|
|
@@ -5550,10 +5725,23 @@ function enumeration(name, options) {
|
|
|
5550
5725
|
// the scoping never engages and every failure prints every member's full
|
|
5551
5726
|
// structure. Wrappers expose their child as `_subtype` (optional/refinement/
|
|
5552
5727
|
// snapshotProcessor) or via `getSubType()` (late); bounded to avoid cycles.
|
|
5728
|
+
// Only *successful* resolutions are cached. A wrapper chain's shape is fixed at
|
|
5729
|
+
// construction, so once a member resolves to a ModelType it always will; but a
|
|
5730
|
+
// `late` member reports no subtype until its definition evaluates, and that
|
|
5731
|
+
// miss must stay retryable.
|
|
5732
|
+
const resolvedModelTypes = new WeakMap();
|
|
5553
5733
|
function resolveModelType(type) {
|
|
5734
|
+
if (!type) {
|
|
5735
|
+
return undefined;
|
|
5736
|
+
}
|
|
5737
|
+
const cached = resolvedModelTypes.get(type);
|
|
5738
|
+
if (cached) {
|
|
5739
|
+
return cached;
|
|
5740
|
+
}
|
|
5554
5741
|
let current = type;
|
|
5555
5742
|
for (let depth = 0; current && depth < 20; depth++) {
|
|
5556
5743
|
if (current instanceof ModelType) {
|
|
5744
|
+
resolvedModelTypes.set(type, current);
|
|
5557
5745
|
return current;
|
|
5558
5746
|
}
|
|
5559
5747
|
const wrapper = current;
|
|
@@ -5567,31 +5755,51 @@ function resolveModelType(type) {
|
|
|
5567
5755
|
*/
|
|
5568
5756
|
class Union extends BaseType {
|
|
5569
5757
|
_types;
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
//
|
|
5573
|
-
//
|
|
5574
|
-
//
|
|
5575
|
-
//
|
|
5576
|
-
//
|
|
5758
|
+
_flags;
|
|
5759
|
+
// Memoized, but only once the fold is known to be stable. A `types.late`
|
|
5760
|
+
// member reports 0 for its subtype until its definition resolves, so a union
|
|
5761
|
+
// containing one must keep recomputing — and every wrapper ORs its subtype's
|
|
5762
|
+
// flags upward, so `Late` in the *result* is an exact test for "some member
|
|
5763
|
+
// may still change" however deeply it is nested. A resolved late still
|
|
5764
|
+
// reports Late, so such a union simply never caches; that is conservative in
|
|
5765
|
+
// the safe direction and no real-world union is built out of late members.
|
|
5766
|
+
//
|
|
5767
|
+
// This reverses an earlier decision to leave it uncached, which was sized at
|
|
5768
|
+
// ~4% "on union creation alone". That understated it: the reads are what
|
|
5769
|
+
// cost, not the creation. `ModelType._getIdentifierAttribute` folds every
|
|
5770
|
+
// property's flags on each `types.model()`, and jbrowse's config slots are
|
|
5771
|
+
// unions under a stripDefault, so building one schema re-folded every slot's
|
|
5772
|
+
// union. See agent-docs/adr/0003.
|
|
5577
5773
|
get flags() {
|
|
5774
|
+
const cached = this._flags;
|
|
5775
|
+
if (cached !== undefined) {
|
|
5776
|
+
return cached;
|
|
5777
|
+
}
|
|
5578
5778
|
let result = TypeFlags.Union;
|
|
5579
5779
|
for (const type of this._types) {
|
|
5580
5780
|
result |= type.flags;
|
|
5581
5781
|
}
|
|
5782
|
+
if (!(result & TypeFlags.Late)) {
|
|
5783
|
+
this._flags = result;
|
|
5784
|
+
}
|
|
5582
5785
|
return result;
|
|
5583
5786
|
}
|
|
5584
|
-
|
|
5585
|
-
|
|
5787
|
+
computeName() {
|
|
5788
|
+
return `(${this._types.map(type => type.name).join(" | ")})`;
|
|
5789
|
+
}
|
|
5790
|
+
constructor(_types, options) {
|
|
5791
|
+
super();
|
|
5586
5792
|
this._types = _types;
|
|
5587
|
-
options
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5793
|
+
// read the two options directly rather than spreading defaults into a fresh
|
|
5794
|
+
// object: this constructor runs once per config slot in jbrowse, and the
|
|
5795
|
+
// merged object was allocated only to be read twice and dropped
|
|
5796
|
+
if (options !== undefined) {
|
|
5797
|
+
if (options.dispatcher !== undefined) {
|
|
5798
|
+
this._dispatcher = options.dispatcher;
|
|
5799
|
+
}
|
|
5800
|
+
if (options.eager === false) {
|
|
5801
|
+
this._eager = false;
|
|
5802
|
+
}
|
|
5595
5803
|
}
|
|
5596
5804
|
}
|
|
5597
5805
|
isAssignableFrom(type) {
|
|
@@ -5642,14 +5850,6 @@ class Union extends BaseType {
|
|
|
5642
5850
|
}
|
|
5643
5851
|
return `${baseWithDiscriminator}:\n ${formatValidationErrorLines(errors).join("\n ")}`;
|
|
5644
5852
|
}
|
|
5645
|
-
// Memoizes the discriminator -> member scan below. Union membership is fixed
|
|
5646
|
-
// at construction, so the result for a given `type` string never changes.
|
|
5647
|
-
// Without this, validating a config with many elements drawn from a wide
|
|
5648
|
-
// pluggable union (e.g. jbrowse's 30+ track/adapter types) re-scans every
|
|
5649
|
-
// member — and calls resolveModelType + literal.is() on each — once per
|
|
5650
|
-
// element. With it, each distinct discriminator scans once; the rest are
|
|
5651
|
-
// O(1) map hits. `undefined` (no match OR ambiguous) is cached too.
|
|
5652
|
-
_discriminatorCache;
|
|
5653
5853
|
_findCandidateByTypeDiscriminator(discriminator) {
|
|
5654
5854
|
const cache = (this._discriminatorCache ??= new Map());
|
|
5655
5855
|
if (cache.has(discriminator)) {
|
|
@@ -5684,11 +5884,6 @@ class Union extends BaseType {
|
|
|
5684
5884
|
}
|
|
5685
5885
|
return found;
|
|
5686
5886
|
}
|
|
5687
|
-
// True when every member resolves to a model carrying a literal `type`
|
|
5688
|
-
// discriminator — i.e. a fully discriminated union, where a snapshot's `type`
|
|
5689
|
-
// uniquely identifies the intended member and no untagged catch-all member
|
|
5690
|
-
// could also accept it. Cached: membership is fixed at construction.
|
|
5691
|
-
_allMembersDiscriminated;
|
|
5692
5887
|
allMembersDiscriminated() {
|
|
5693
5888
|
if (this._allMembersDiscriminated === undefined) {
|
|
5694
5889
|
this._allMembersDiscriminated = this._types.every(t => {
|
|
@@ -5786,18 +5981,20 @@ class Union extends BaseType {
|
|
|
5786
5981
|
// use cached propertyNames from ModelType instead of Object.keys()
|
|
5787
5982
|
for (const key of model.propertyNames) {
|
|
5788
5983
|
const propType = props[key];
|
|
5789
|
-
|
|
5984
|
+
// `flags` is a recomputed getter on the wrapper types (optional,
|
|
5985
|
+
// snapshotProcessor, late, union), so read it once per property
|
|
5986
|
+
const flags = propType.flags;
|
|
5790
5987
|
const propValue = value[key];
|
|
5791
5988
|
// check required properties exist and are not undefined
|
|
5792
5989
|
// (unless the type accepts undefined, which Optional types do)
|
|
5793
|
-
if (!
|
|
5990
|
+
if (!(flags & TypeFlags.Optional)) {
|
|
5794
5991
|
if (!(key in value) || propValue === undefined) {
|
|
5795
5992
|
return false;
|
|
5796
5993
|
}
|
|
5797
5994
|
}
|
|
5798
5995
|
// for literal types, verify the value matches exactly
|
|
5799
5996
|
// this is critical for discriminated unions
|
|
5800
|
-
if (
|
|
5997
|
+
if (flags & TypeFlags.Literal) {
|
|
5801
5998
|
if (!propType.is(propValue)) {
|
|
5802
5999
|
return false;
|
|
5803
6000
|
}
|
|
@@ -5820,7 +6017,8 @@ class Union extends BaseType {
|
|
|
5820
6017
|
// - A failure is the definitive, scoped error only when every member is
|
|
5821
6018
|
// discriminated; otherwise a catch-all could still accept the value, so
|
|
5822
6019
|
// fall through to full validation.
|
|
5823
|
-
|
|
6020
|
+
const isSnapshotObject = isPlainObject(value) && !isStateTreeNode(value);
|
|
6021
|
+
if (isSnapshotObject) {
|
|
5824
6022
|
const discriminator = value.type;
|
|
5825
6023
|
if (typeof discriminator === "string") {
|
|
5826
6024
|
const candidate = this._findCandidateByTypeDiscriminator(discriminator);
|
|
@@ -5836,7 +6034,7 @@ class Union extends BaseType {
|
|
|
5836
6034
|
// for plain-object snapshots, prefer union members whose literal-typed
|
|
5837
6035
|
// discriminator properties match the value (e.g. {type: "MsaView"})
|
|
5838
6036
|
// so error output is scoped to the intended branch instead of every member
|
|
5839
|
-
const candidates =
|
|
6037
|
+
const candidates = isSnapshotObject
|
|
5840
6038
|
? this._types.filter(t => this.snapshotLooksLikeType(value, t))
|
|
5841
6039
|
: [];
|
|
5842
6040
|
const typesToValidate = candidates.length > 0 ? candidates : this._types;
|
|
@@ -5865,6 +6063,14 @@ class Union extends BaseType {
|
|
|
5865
6063
|
return this._types;
|
|
5866
6064
|
}
|
|
5867
6065
|
}
|
|
6066
|
+
// Defaults shared by every union; see the field declarations at the top of the
|
|
6067
|
+
// class for why they are not own slots.
|
|
6068
|
+
Object.assign(Union.prototype, {
|
|
6069
|
+
_dispatcher: undefined,
|
|
6070
|
+
_eager: true,
|
|
6071
|
+
_discriminatorCache: undefined,
|
|
6072
|
+
_allMembersDiscriminated: undefined
|
|
6073
|
+
});
|
|
5868
6074
|
/**
|
|
5869
6075
|
* `types.union` - Create a union of multiple types. If the correct type cannot be inferred unambiguously from a snapshot, provide a dispatcher function of the form `(snapshot) => Type`.
|
|
5870
6076
|
*
|
|
@@ -5872,12 +6078,18 @@ class Union extends BaseType {
|
|
|
5872
6078
|
* @param otherTypes
|
|
5873
6079
|
* @returns
|
|
5874
6080
|
*/
|
|
5875
|
-
function union(
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
6081
|
+
function union(...args) {
|
|
6082
|
+
// One rest array, handed straight to the Union in the common case. Splitting
|
|
6083
|
+
// the leading argument out — whether by a `(first, ...rest)` signature that
|
|
6084
|
+
// rebuilds `[first, ...rest]`, or by `rest.unshift(first)` — allocates a
|
|
6085
|
+
// second array per union, and jbrowse builds one union per config slot. (The
|
|
6086
|
+
// `unshift` form is worse still: V8 inlines the spread and calls out to the
|
|
6087
|
+
// builtin, so it measured 3x the spread on a two-member union.) Only the
|
|
6088
|
+
// options overload, which nothing hot uses, pays for a copy.
|
|
6089
|
+
const firstIsType = isType(args[0]);
|
|
6090
|
+
const options = firstIsType ? undefined : args[0];
|
|
6091
|
+
const types = (firstIsType ? args : args.slice(1));
|
|
6092
|
+
// the name is folded from the members on demand — see Union.computeName
|
|
5881
6093
|
// check all options
|
|
5882
6094
|
if (devMode()) {
|
|
5883
6095
|
if (options) {
|
|
@@ -5887,16 +6099,22 @@ function union(optionsOrType, ...otherTypes) {
|
|
|
5887
6099
|
assertIsType(type, options ? i + 2 : i + 1);
|
|
5888
6100
|
});
|
|
5889
6101
|
}
|
|
5890
|
-
return new Union(
|
|
6102
|
+
return new Union(types, options);
|
|
5891
6103
|
}
|
|
5892
6104
|
/**
|
|
5893
6105
|
* Returns if a given value represents a union type.
|
|
5894
6106
|
*
|
|
6107
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
6108
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
6109
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
6110
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
6111
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
6112
|
+
*
|
|
5895
6113
|
* @param type
|
|
5896
6114
|
* @returns
|
|
5897
6115
|
*/
|
|
5898
6116
|
function isUnionType(type) {
|
|
5899
|
-
return (type.flags & TypeFlags.Union) > 0;
|
|
6117
|
+
return isType(type) && (type.flags & TypeFlags.Union) > 0;
|
|
5900
6118
|
}
|
|
5901
6119
|
/**
|
|
5902
6120
|
* Returns the member types of a union.
|
|
@@ -5934,15 +6152,31 @@ class OptionalValue extends BaseType {
|
|
|
5934
6152
|
_subtype;
|
|
5935
6153
|
_defaultValue;
|
|
5936
6154
|
optionalValues;
|
|
6155
|
+
_flags;
|
|
6156
|
+
// memoized once stable; see the same guard on Union.flags for why `Late` in
|
|
6157
|
+
// the folded result is the exact test for "may still change"
|
|
5937
6158
|
get flags() {
|
|
5938
|
-
|
|
6159
|
+
const cached = this._flags;
|
|
6160
|
+
if (cached !== undefined) {
|
|
6161
|
+
return cached;
|
|
6162
|
+
}
|
|
6163
|
+
const result = this._subtype.flags | TypeFlags.Optional;
|
|
6164
|
+
if (!(result & TypeFlags.Late)) {
|
|
6165
|
+
this._flags = result;
|
|
6166
|
+
}
|
|
6167
|
+
return result;
|
|
5939
6168
|
}
|
|
5940
6169
|
constructor(_subtype, _defaultValue, optionalValues) {
|
|
5941
|
-
|
|
6170
|
+
// no name argument: reading `_subtype.name` here would force the name of
|
|
6171
|
+
// whatever is wrapped, and jbrowse wraps a union per config slot
|
|
6172
|
+
super();
|
|
5942
6173
|
this._subtype = _subtype;
|
|
5943
6174
|
this._defaultValue = _defaultValue;
|
|
5944
6175
|
this.optionalValues = optionalValues;
|
|
5945
6176
|
}
|
|
6177
|
+
computeName() {
|
|
6178
|
+
return this._subtype.name;
|
|
6179
|
+
}
|
|
5946
6180
|
describe() {
|
|
5947
6181
|
return `${this._subtype.describe()}?`;
|
|
5948
6182
|
}
|
|
@@ -5985,8 +6219,11 @@ class OptionalValue extends BaseType {
|
|
|
5985
6219
|
}
|
|
5986
6220
|
}
|
|
5987
6221
|
function checkOptionalPreconditions(type, defaultValueOrFunction) {
|
|
5988
|
-
// make sure we never pass direct instances
|
|
5989
|
-
|
|
6222
|
+
// make sure we never pass direct instances. A node is always an object, so
|
|
6223
|
+
// the typeof narrows first: most defaults are primitives, and reading
|
|
6224
|
+
// `$treenode` off a string or a number is a megamorphic miss that this runs
|
|
6225
|
+
// once per config slot.
|
|
6226
|
+
if (typeof defaultValueOrFunction === "object" &&
|
|
5990
6227
|
isStateTreeNode(defaultValueOrFunction)) {
|
|
5991
6228
|
throw fail("default value cannot be an instance, pass a snapshot or a function that creates an instance/snapshot instead");
|
|
5992
6229
|
}
|
|
@@ -6051,6 +6288,12 @@ const undefinedAsOptionalValues = [undefined];
|
|
|
6051
6288
|
/**
|
|
6052
6289
|
* Returns if a value represents an optional type.
|
|
6053
6290
|
*
|
|
6291
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
6292
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
6293
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
6294
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
6295
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
6296
|
+
*
|
|
6054
6297
|
* @template IT
|
|
6055
6298
|
* @param type
|
|
6056
6299
|
* @returns
|
|
@@ -6136,7 +6379,6 @@ function defaultSnapshotEquals(a, b) {
|
|
|
6136
6379
|
* @internal
|
|
6137
6380
|
*/
|
|
6138
6381
|
class StripDefaultValue extends OptionalValue {
|
|
6139
|
-
_defaultSnapshot;
|
|
6140
6382
|
shouldStripFromSnapshot(snapshot) {
|
|
6141
6383
|
if (!this._defaultSnapshot) {
|
|
6142
6384
|
// instantiate the subtype detached with the default and read the node's
|
|
@@ -6148,6 +6390,9 @@ class StripDefaultValue extends OptionalValue {
|
|
|
6148
6390
|
return defaultSnapshotEquals(snapshot, this._defaultSnapshot.value);
|
|
6149
6391
|
}
|
|
6150
6392
|
}
|
|
6393
|
+
Object.assign(StripDefaultValue.prototype, {
|
|
6394
|
+
_defaultSnapshot: undefined
|
|
6395
|
+
});
|
|
6151
6396
|
/**
|
|
6152
6397
|
* Whether `type` is a strip-default optional whose current child `snapshot`
|
|
6153
6398
|
* equals its default and should therefore be omitted from the parent model's
|
|
@@ -6295,6 +6540,12 @@ function late(nameOrType, maybeType) {
|
|
|
6295
6540
|
/**
|
|
6296
6541
|
* Returns if a given value represents a late type.
|
|
6297
6542
|
*
|
|
6543
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
6544
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
6545
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
6546
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
6547
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
6548
|
+
*
|
|
6298
6549
|
* @param type
|
|
6299
6550
|
* @returns
|
|
6300
6551
|
*/
|
|
@@ -6350,7 +6601,13 @@ class Lazy extends SimpleType {
|
|
|
6350
6601
|
}
|
|
6351
6602
|
const node = createScalarNode(this, parent, subpath, environment, deepFreeze(value));
|
|
6352
6603
|
this.pendingNodeList.push(node);
|
|
6353
|
-
mobx.when(() => !node.isAlive, () =>
|
|
6604
|
+
mobx.when(() => !node.isAlive, () => {
|
|
6605
|
+
// guard the index: splice(-1, 1) would drop an unrelated pending node
|
|
6606
|
+
const index = this.pendingNodeList.indexOf(node);
|
|
6607
|
+
if (index >= 0) {
|
|
6608
|
+
this.pendingNodeList.splice(index, 1);
|
|
6609
|
+
}
|
|
6610
|
+
});
|
|
6354
6611
|
return node;
|
|
6355
6612
|
}
|
|
6356
6613
|
isValidSnapshot(value, context) {
|
|
@@ -6379,9 +6636,12 @@ class Frozen extends SimpleType {
|
|
|
6379
6636
|
subType;
|
|
6380
6637
|
flags = TypeFlags.Frozen;
|
|
6381
6638
|
constructor(subType) {
|
|
6382
|
-
super(subType ?
|
|
6639
|
+
super(subType ? undefined : "frozen");
|
|
6383
6640
|
this.subType = subType;
|
|
6384
6641
|
}
|
|
6642
|
+
computeName() {
|
|
6643
|
+
return `frozen(${this.subType.name})`;
|
|
6644
|
+
}
|
|
6385
6645
|
describe() {
|
|
6386
6646
|
return "<any immutable value>";
|
|
6387
6647
|
}
|
|
@@ -6454,6 +6714,12 @@ function frozen(arg) {
|
|
|
6454
6714
|
/**
|
|
6455
6715
|
* Returns if a given value represents a frozen type.
|
|
6456
6716
|
*
|
|
6717
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
6718
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
6719
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
6720
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
6721
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
6722
|
+
*
|
|
6457
6723
|
* @param type
|
|
6458
6724
|
* @returns
|
|
6459
6725
|
*/
|
|
@@ -6537,10 +6803,13 @@ class BaseReferenceType extends SimpleType {
|
|
|
6537
6803
|
onInvalidated;
|
|
6538
6804
|
flags = TypeFlags.Reference;
|
|
6539
6805
|
constructor(targetType, onInvalidated) {
|
|
6540
|
-
super(
|
|
6806
|
+
super();
|
|
6541
6807
|
this.targetType = targetType;
|
|
6542
6808
|
this.onInvalidated = onInvalidated;
|
|
6543
6809
|
}
|
|
6810
|
+
computeName() {
|
|
6811
|
+
return `reference(${this.targetType.name})`;
|
|
6812
|
+
}
|
|
6544
6813
|
describe() {
|
|
6545
6814
|
return this.name;
|
|
6546
6815
|
}
|
|
@@ -6681,9 +6950,6 @@ class BaseReferenceType extends SimpleType {
|
|
|
6681
6950
|
* @hidden
|
|
6682
6951
|
*/
|
|
6683
6952
|
class IdentifierReferenceType extends BaseReferenceType {
|
|
6684
|
-
constructor(targetType, onInvalidated) {
|
|
6685
|
-
super(targetType, onInvalidated);
|
|
6686
|
-
}
|
|
6687
6953
|
getValue(storedRefNode) {
|
|
6688
6954
|
if (!storedRefNode.isAlive) {
|
|
6689
6955
|
return undefined;
|
|
@@ -6799,11 +7065,17 @@ function reference(subType, options) {
|
|
|
6799
7065
|
/**
|
|
6800
7066
|
* Returns if a given value represents a reference type.
|
|
6801
7067
|
*
|
|
7068
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
7069
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
7070
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
7071
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
7072
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
7073
|
+
*
|
|
6802
7074
|
* @param type
|
|
6803
7075
|
* @returns
|
|
6804
7076
|
*/
|
|
6805
7077
|
function isReferenceType(type) {
|
|
6806
|
-
return (type.flags & TypeFlags.Reference) > 0;
|
|
7078
|
+
return isType(type) && (type.flags & TypeFlags.Reference) > 0;
|
|
6807
7079
|
}
|
|
6808
7080
|
/**
|
|
6809
7081
|
* `types.safeReference` - A safe reference is like a standard reference, except that it accepts the undefined value by default
|
|
@@ -6874,7 +7146,6 @@ class BaseIdentifierType extends SimpleType {
|
|
|
6874
7146
|
* @hidden
|
|
6875
7147
|
*/
|
|
6876
7148
|
class IdentifierType extends BaseIdentifierType {
|
|
6877
|
-
flags = TypeFlags.Identifier;
|
|
6878
7149
|
constructor() {
|
|
6879
7150
|
super(`identifier`, "string");
|
|
6880
7151
|
}
|
|
@@ -6932,6 +7203,12 @@ const identifierNumber = new IdentifierNumberType();
|
|
|
6932
7203
|
/**
|
|
6933
7204
|
* Returns if a given value represents an identifier type.
|
|
6934
7205
|
*
|
|
7206
|
+
* Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
|
|
7207
|
+
* typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
|
|
7208
|
+
* collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
|
|
7209
|
+
* to compile. Guards with a distinct narrowing target (`isArrayType`,
|
|
7210
|
+
* `isMapType`, `isModelType`) keep their predicate.
|
|
7211
|
+
*
|
|
6935
7212
|
* @param type
|
|
6936
7213
|
* @returns
|
|
6937
7214
|
*/
|
|
@@ -7072,11 +7349,14 @@ class Resilient extends BaseType {
|
|
|
7072
7349
|
return this._subtype.flags;
|
|
7073
7350
|
}
|
|
7074
7351
|
constructor(_subtype, _fallbackType, _createFallbackSnapshot) {
|
|
7075
|
-
super(
|
|
7352
|
+
super();
|
|
7076
7353
|
this._subtype = _subtype;
|
|
7077
7354
|
this._fallbackType = _fallbackType;
|
|
7078
7355
|
this._createFallbackSnapshot = _createFallbackSnapshot;
|
|
7079
7356
|
}
|
|
7357
|
+
computeName() {
|
|
7358
|
+
return `resilient(${this._subtype.name})`;
|
|
7359
|
+
}
|
|
7080
7360
|
describe() {
|
|
7081
7361
|
return `resilient(${this._subtype.describe()})`;
|
|
7082
7362
|
}
|
|
@@ -7111,12 +7391,20 @@ class Resilient extends BaseType {
|
|
|
7111
7391
|
return this._subtype.reconcile(current, newValue, parent, subpath);
|
|
7112
7392
|
}
|
|
7113
7393
|
if (this._fallbackType.isAssignableFrom(current.type)) {
|
|
7394
|
+
// `current` holds the fallback. Try the real type again, but keep the
|
|
7395
|
+
// fallback node around while doing so — it is what the catch reconciles.
|
|
7396
|
+
let recovered;
|
|
7114
7397
|
try {
|
|
7115
|
-
|
|
7398
|
+
recovered = this._subtype.instantiate(parent, subpath, undefined, newValue);
|
|
7116
7399
|
}
|
|
7117
7400
|
catch (e) {
|
|
7118
7401
|
return this._fallbackType.reconcile(current, this._createFallbackSnapshot(e, newValue), parent, subpath);
|
|
7119
7402
|
}
|
|
7403
|
+
// the fallback node has been replaced, so it has to die: otherwise it
|
|
7404
|
+
// stays alive in the tree and its identifier stays in the root's cache,
|
|
7405
|
+
// where it collides with the recovered node's
|
|
7406
|
+
current.die();
|
|
7407
|
+
return recovered;
|
|
7120
7408
|
}
|
|
7121
7409
|
try {
|
|
7122
7410
|
return this._subtype.reconcile(current, newValue, parent, subpath);
|