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