@jbrowse/mobx-state-tree 6.1.0 → 6.2.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 +28 -5
- package/dist/mobx-state-tree.cjs +352 -121
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +352 -121
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
package/dist/mobx-state-tree.mjs
CHANGED
|
@@ -1165,7 +1165,7 @@ class ObjectNode extends BaseNode {
|
|
|
1165
1165
|
}
|
|
1166
1166
|
_autoUnbox = true; // unboxing is disabled when reading child nodes
|
|
1167
1167
|
_isRunningAction = false; // only relevant for root
|
|
1168
|
-
|
|
1168
|
+
_snapshotReactionDisposer;
|
|
1169
1169
|
_observableInstanceState = ObservableInstanceLifecycle.UNINITIALIZED;
|
|
1170
1170
|
_childNodes;
|
|
1171
1171
|
_initialSnapshot;
|
|
@@ -1587,7 +1587,17 @@ class ObjectNode extends BaseNode {
|
|
|
1587
1587
|
}
|
|
1588
1588
|
onSnapshot(onChange) {
|
|
1589
1589
|
this._addSnapshotReaction();
|
|
1590
|
-
|
|
1590
|
+
const unregister = this._internalEventsRegister(InternalEvents.Snapshot, onChange);
|
|
1591
|
+
return () => {
|
|
1592
|
+
unregister();
|
|
1593
|
+
// The reaction re-serializes the whole subtree on every change, so leaving
|
|
1594
|
+
// it running once the last listener is gone would keep charging the node
|
|
1595
|
+
// for a snapshot nobody receives. Referential stability of getSnapshot()
|
|
1596
|
+
// does not depend on it — that comes from keepAlive on _snapshotComputed.
|
|
1597
|
+
if (!this._internalEventsHasSubscribers(InternalEvents.Snapshot)) {
|
|
1598
|
+
this._removeSnapshotReaction();
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1591
1601
|
}
|
|
1592
1602
|
emitSnapshot(snapshot) {
|
|
1593
1603
|
this._internalEventsEmit(InternalEvents.Snapshot, snapshot);
|
|
@@ -1652,11 +1662,23 @@ class ObjectNode extends BaseNode {
|
|
|
1652
1662
|
this.type.applyPatchLocally(this, subpath, patch);
|
|
1653
1663
|
}
|
|
1654
1664
|
_addSnapshotReaction() {
|
|
1655
|
-
if (!this.
|
|
1665
|
+
if (!this._snapshotReactionDisposer) {
|
|
1656
1666
|
const snapshotDisposer = reaction(() => this.snapshot, snapshot => this.emitSnapshot(snapshot), snapshotReactionOptions);
|
|
1657
1667
|
this.addDisposer(snapshotDisposer);
|
|
1658
|
-
this.
|
|
1668
|
+
this._snapshotReactionDisposer = snapshotDisposer;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
_removeSnapshotReaction() {
|
|
1672
|
+
const disposer = this._snapshotReactionDisposer;
|
|
1673
|
+
if (!disposer) {
|
|
1674
|
+
return;
|
|
1659
1675
|
}
|
|
1676
|
+
this._snapshotReactionDisposer = undefined;
|
|
1677
|
+
// not removeDisposer(), which throws when the registration is already gone —
|
|
1678
|
+
// the last listener can be disposed from within a disposer, i.e. after
|
|
1679
|
+
// aboutToDie has cleared them
|
|
1680
|
+
this._internalEventsUnregister(InternalEvents.Dispose, disposer);
|
|
1681
|
+
disposer();
|
|
1660
1682
|
}
|
|
1661
1683
|
// #region internal event handling
|
|
1662
1684
|
_internalEvents;
|
|
@@ -1733,8 +1755,6 @@ var TypeFlags;
|
|
|
1733
1755
|
* @hidden
|
|
1734
1756
|
*/
|
|
1735
1757
|
const cannotDetermineSubtype = "cannotDetermine";
|
|
1736
|
-
/** @hidden */
|
|
1737
|
-
const $type = Symbol("$type");
|
|
1738
1758
|
/**
|
|
1739
1759
|
* A base type produces a MST node (Node in the state tree)
|
|
1740
1760
|
*
|
|
@@ -1742,16 +1762,39 @@ const $type = Symbol("$type");
|
|
|
1742
1762
|
* @hidden
|
|
1743
1763
|
*/
|
|
1744
1764
|
class BaseType {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1765
|
+
/**
|
|
1766
|
+
* Builds the name of a type that does not get one handed to it. Composite
|
|
1767
|
+
* types (`union`, `array`, `map`, `reference`, ...) override this to fold
|
|
1768
|
+
* their members' names; see {@link name}. It runs after construction, so
|
|
1769
|
+
* unlike a `super(...)` argument it can read the subclass's own fields.
|
|
1770
|
+
*/
|
|
1771
|
+
computeName() {
|
|
1772
|
+
// istanbul ignore next
|
|
1773
|
+
throw fail(`${this.constructor.name} has neither a name nor a computeName`);
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Friendly type name.
|
|
1777
|
+
*
|
|
1778
|
+
* A composite type folds its members' names, which for a union is a map +
|
|
1779
|
+
* join over every member — and the result is read only by error messages and
|
|
1780
|
+
* `describe()`. So those types leave it unset and it is built on first read
|
|
1781
|
+
* and cached: a union that never fails a typecheck never builds one, and
|
|
1782
|
+
* jbrowse builds a union per config slot, tens of thousands per session load.
|
|
1783
|
+
*
|
|
1784
|
+
* Deliberately not a lambda passed to the constructor: that allocated a
|
|
1785
|
+
* closure per type (the very cost being avoided) and made two separately
|
|
1786
|
+
* built but equivalent types compare unequal, since the closures differ.
|
|
1787
|
+
*/
|
|
1788
|
+
get name() {
|
|
1789
|
+
return (this._name ??= this.computeName());
|
|
1790
|
+
}
|
|
1791
|
+
set name(value) {
|
|
1792
|
+
this._name = value;
|
|
1793
|
+
}
|
|
1753
1794
|
constructor(name) {
|
|
1754
|
-
|
|
1795
|
+
if (name !== undefined) {
|
|
1796
|
+
this._name = name;
|
|
1797
|
+
}
|
|
1755
1798
|
}
|
|
1756
1799
|
create(snapshot, environment) {
|
|
1757
1800
|
typecheckInternal(this, snapshot);
|
|
@@ -1796,6 +1839,9 @@ class BaseType {
|
|
|
1796
1839
|
}
|
|
1797
1840
|
}
|
|
1798
1841
|
BaseType.prototype.create = action(BaseType.prototype.create);
|
|
1842
|
+
// Defaults that every type shares, kept off the individual type objects. See
|
|
1843
|
+
// `isType` and `_name` in the class body.
|
|
1844
|
+
Object.assign(BaseType.prototype, { isType: true, _name: undefined });
|
|
1799
1845
|
/**
|
|
1800
1846
|
* A complex type produces a MST node (Node in the state tree)
|
|
1801
1847
|
*
|
|
@@ -2121,7 +2167,7 @@ function createActionTrackingMiddleware2(middlewareHooks) {
|
|
|
2121
2167
|
};
|
|
2122
2168
|
}
|
|
2123
2169
|
|
|
2124
|
-
function serializeArgument(
|
|
2170
|
+
function serializeArgument(arg) {
|
|
2125
2171
|
if (arg instanceof Date) {
|
|
2126
2172
|
return { $MST_DATE: arg.getTime() };
|
|
2127
2173
|
}
|
|
@@ -2149,7 +2195,7 @@ function serializeArgument(node, actionName, index, arg) {
|
|
|
2149
2195
|
return serializeTheUnserializable(`${e}`);
|
|
2150
2196
|
}
|
|
2151
2197
|
}
|
|
2152
|
-
function deserializeArgument(
|
|
2198
|
+
function deserializeArgument(value) {
|
|
2153
2199
|
if (value && typeof value === "object" && "$MST_DATE" in value) {
|
|
2154
2200
|
return new Date(value["$MST_DATE"]);
|
|
2155
2201
|
}
|
|
@@ -2193,7 +2239,7 @@ function baseApplyAction(target, action) {
|
|
|
2193
2239
|
if (!(typeof resolvedTarget[action.name] === "function")) {
|
|
2194
2240
|
throw fail(`Action '${action.name}' does not exist in '${node.path}'`);
|
|
2195
2241
|
}
|
|
2196
|
-
return resolvedTarget[action.name](...(action.args ? action.args.map(
|
|
2242
|
+
return resolvedTarget[action.name](...(action.args ? action.args.map(deserializeArgument) : []));
|
|
2197
2243
|
}
|
|
2198
2244
|
/**
|
|
2199
2245
|
* Small abstraction around `onAction` and `applyAction`, attaches an action listener to a tree and records all the actions emitted.
|
|
@@ -2310,7 +2356,7 @@ function onAction(target, listener, attachAfter = false) {
|
|
|
2310
2356
|
const info = {
|
|
2311
2357
|
name: rawCall.name,
|
|
2312
2358
|
path: getRelativePathBetweenNodes(getStateTreeNode(target), sourceNode),
|
|
2313
|
-
args: rawCall.args.map(
|
|
2359
|
+
args: rawCall.args.map(serializeArgument)
|
|
2314
2360
|
};
|
|
2315
2361
|
if (attachAfter) {
|
|
2316
2362
|
const res = next(rawCall);
|
|
@@ -2778,10 +2824,13 @@ class IdentifierCache {
|
|
|
2778
2824
|
addNodeToCache(node, lastCacheUpdate = true) {
|
|
2779
2825
|
if (node.identifierAttribute) {
|
|
2780
2826
|
const identifier = node.identifier;
|
|
2781
|
-
|
|
2782
|
-
|
|
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 = observable.array([], mobxShallow);
|
|
2832
|
+
this.cache.set(identifier, set);
|
|
2783
2833
|
}
|
|
2784
|
-
const set = this.cache.get(identifier);
|
|
2785
2834
|
if (set.includes(node)) {
|
|
2786
2835
|
throw fail(`Already registered`);
|
|
2787
2836
|
}
|
|
@@ -3121,7 +3170,11 @@ function isPlainObject(value) {
|
|
|
3121
3170
|
return false;
|
|
3122
3171
|
}
|
|
3123
3172
|
const proto = Object.getPrototypeOf(value);
|
|
3124
|
-
|
|
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) {
|
|
3125
3178
|
return true;
|
|
3126
3179
|
}
|
|
3127
3180
|
return proto.constructor?.toString() === plainObjectString;
|
|
@@ -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
|
}
|
|
@@ -3968,17 +4037,20 @@ class MapType extends ComplexType {
|
|
|
3968
4037
|
mapIdentifierAttribute = undefined;
|
|
3969
4038
|
flags = TypeFlags.Map;
|
|
3970
4039
|
hookInitializers = [];
|
|
3971
|
-
constructor(
|
|
3972
|
-
super(
|
|
4040
|
+
constructor(_subType, hookInitializers = []) {
|
|
4041
|
+
super();
|
|
3973
4042
|
this._subType = _subType;
|
|
3974
4043
|
this._determineIdentifierMode();
|
|
3975
4044
|
this.hookInitializers = hookInitializers;
|
|
3976
4045
|
}
|
|
4046
|
+
computeName() {
|
|
4047
|
+
return `Map<string, ${this._subType.name}>`;
|
|
4048
|
+
}
|
|
3977
4049
|
hooks(hooks) {
|
|
3978
4050
|
const hookInitializers = this.hookInitializers.length > 0
|
|
3979
4051
|
? this.hookInitializers.concat(hooks)
|
|
3980
4052
|
: [hooks];
|
|
3981
|
-
return new MapType(this.
|
|
4053
|
+
return new MapType(this._subType, hookInitializers);
|
|
3982
4054
|
}
|
|
3983
4055
|
instantiate(parent, subpath, environment, initialValue) {
|
|
3984
4056
|
this._determineIdentifierMode();
|
|
@@ -4212,7 +4284,7 @@ MapType.prototype.applySnapshot = action(MapType.prototype.applySnapshot);
|
|
|
4212
4284
|
* @returns
|
|
4213
4285
|
*/
|
|
4214
4286
|
function map(subtype) {
|
|
4215
|
-
return new MapType(
|
|
4287
|
+
return new MapType(subtype);
|
|
4216
4288
|
}
|
|
4217
4289
|
/**
|
|
4218
4290
|
* Returns if a given value represents a map type.
|
|
@@ -4232,16 +4304,19 @@ class ArrayType extends ComplexType {
|
|
|
4232
4304
|
_subType;
|
|
4233
4305
|
flags = TypeFlags.Array;
|
|
4234
4306
|
hookInitializers = [];
|
|
4235
|
-
constructor(
|
|
4236
|
-
super(
|
|
4307
|
+
constructor(_subType, hookInitializers = []) {
|
|
4308
|
+
super();
|
|
4237
4309
|
this._subType = _subType;
|
|
4238
4310
|
this.hookInitializers = hookInitializers;
|
|
4239
4311
|
}
|
|
4312
|
+
computeName() {
|
|
4313
|
+
return `${this._subType.name}[]`;
|
|
4314
|
+
}
|
|
4240
4315
|
hooks(hooks) {
|
|
4241
4316
|
const hookInitializers = this.hookInitializers.length > 0
|
|
4242
4317
|
? this.hookInitializers.concat(hooks)
|
|
4243
4318
|
: [hooks];
|
|
4244
|
-
return new ArrayType(this.
|
|
4319
|
+
return new ArrayType(this._subType, hookInitializers);
|
|
4245
4320
|
}
|
|
4246
4321
|
instantiate(parent, subpath, environment, initialValue) {
|
|
4247
4322
|
return createObjectNode(this, parent, subpath, environment, initialValue);
|
|
@@ -4290,14 +4365,17 @@ class ArrayType extends ComplexType {
|
|
|
4290
4365
|
const node = getStateTreeNode(change.object);
|
|
4291
4366
|
node.assertWritable({ subpath: `${change.index}` });
|
|
4292
4367
|
const subType = node.type._subType;
|
|
4293
|
-
const childNodes = node.getChildren();
|
|
4294
4368
|
switch (change.type) {
|
|
4295
4369
|
case "update":
|
|
4296
4370
|
{
|
|
4297
4371
|
if (change.newValue === change.object[change.index]) {
|
|
4298
4372
|
return null;
|
|
4299
4373
|
}
|
|
4300
|
-
const updatedNodes = reconcileArrayChildren(node, subType,
|
|
4374
|
+
const updatedNodes = reconcileArrayChildren(node, subType,
|
|
4375
|
+
// only the replaced child is reconciled, so read that one node
|
|
4376
|
+
// directly — `node.getChildren()` copies the whole backing array,
|
|
4377
|
+
// which made a single-element assignment cost O(array length)
|
|
4378
|
+
[node.getChildNode(`${change.index}`)], [change.newValue], change.index);
|
|
4301
4379
|
if (!updatedNodes) {
|
|
4302
4380
|
return null;
|
|
4303
4381
|
}
|
|
@@ -4307,6 +4385,7 @@ class ArrayType extends ComplexType {
|
|
|
4307
4385
|
case "splice":
|
|
4308
4386
|
{
|
|
4309
4387
|
const { index, removedCount, added } = change;
|
|
4388
|
+
const childNodes = node.getChildren();
|
|
4310
4389
|
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added, index);
|
|
4311
4390
|
if (!addedNodes) {
|
|
4312
4391
|
return null;
|
|
@@ -4431,7 +4510,7 @@ ArrayType.prototype.applySnapshot = action(ArrayType.prototype.applySnapshot);
|
|
|
4431
4510
|
*/
|
|
4432
4511
|
function array(subtype) {
|
|
4433
4512
|
assertIsType(subtype, 1);
|
|
4434
|
-
return new ArrayType(
|
|
4513
|
+
return new ArrayType(subtype);
|
|
4435
4514
|
}
|
|
4436
4515
|
/**
|
|
4437
4516
|
* @param firstNewPath index the reconciled slice starts at; both call sites
|
|
@@ -4446,8 +4525,12 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, firstNew
|
|
|
4446
4525
|
// whose id extraction needs type-specific preprocessing (union,
|
|
4447
4526
|
// snapshotProcessor, late, ...) are intentionally excluded: areSame must run
|
|
4448
4527
|
// `is()` before their id check, so they stay on the scan path.
|
|
4528
|
+
// With at most one old node the scan below is already O(1), so building the
|
|
4529
|
+
// index would only add a Map allocation to every single-element write.
|
|
4449
4530
|
let idIndex;
|
|
4450
|
-
if (
|
|
4531
|
+
if (oldNodes.length > 1 &&
|
|
4532
|
+
childType instanceof ModelType &&
|
|
4533
|
+
childType.identifierAttribute) {
|
|
4451
4534
|
const byId = new Map();
|
|
4452
4535
|
for (const n of oldNodes) {
|
|
4453
4536
|
if (n instanceof ObjectNode && n.identifier !== null) {
|
|
@@ -4609,14 +4692,53 @@ const POST_PROCESS_SNAPSHOT = "postProcessSnapshot";
|
|
|
4609
4692
|
function getPropObservable(storedValue, key) {
|
|
4610
4693
|
return getAtom(storedValue, key);
|
|
4611
4694
|
}
|
|
4695
|
+
/**
|
|
4696
|
+
* All of an instance's per-property `ObservableValue`s in one map, or
|
|
4697
|
+
* `undefined` if this mobx does not expose them.
|
|
4698
|
+
*
|
|
4699
|
+
* `observable.object` returns a **Proxy**, and `getAtom(storedValue, key)`
|
|
4700
|
+
* probes it with four `isObservableArray/Set/Map/Object` guards plus a `$mobx`
|
|
4701
|
+
* read — each one a marker-property read that goes through the proxy's `get`
|
|
4702
|
+
* trap. Paying that per property made the trap machinery the bulk of a wide
|
|
4703
|
+
* model's `getSnapshot`; resolving the administration once and reading its
|
|
4704
|
+
* property map directly reduces the per-property cost to a `Map.get`.
|
|
4705
|
+
*
|
|
4706
|
+
* `values_` is mobx-internal, hence the undefined result: every caller keeps a
|
|
4707
|
+
* path built on supported API, so a mobx that drops it degrades to the old
|
|
4708
|
+
* speed rather than breaking.
|
|
4709
|
+
*
|
|
4710
|
+
* The map is complete for our instances. `getAtom` falls back to
|
|
4711
|
+
* `materializeLazy{Computed,Observable}_` because mobx defers constructing an
|
|
4712
|
+
* `ObservableValue` for *decorator* annotations; `createNewInstance` builds
|
|
4713
|
+
* these through `observable.object`, which populates `values_` eagerly for
|
|
4714
|
+
* every declared property.
|
|
4715
|
+
*/
|
|
4716
|
+
function getPropObservables(storedValue) {
|
|
4717
|
+
const adm = _getAdministration(storedValue);
|
|
4718
|
+
return adm.values_;
|
|
4719
|
+
}
|
|
4612
4720
|
function objectTypeToString() {
|
|
4613
4721
|
return getStateTreeNode(this).toString();
|
|
4614
4722
|
}
|
|
4615
4723
|
const defaultObjectOptions = {
|
|
4616
|
-
name: "AnonymousModel"
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4724
|
+
name: "AnonymousModel"};
|
|
4725
|
+
/**
|
|
4726
|
+
* A plain loop rather than `forAllProps`, which allocated a closure and made an
|
|
4727
|
+
* indirect call per property. This runs once per `types.model()` over every
|
|
4728
|
+
* declared property, and jbrowse builds a ~40-slot schema per track.
|
|
4729
|
+
*/
|
|
4730
|
+
function findIdentifierAttribute(properties, propertyNames) {
|
|
4731
|
+
let identifierAttribute = undefined;
|
|
4732
|
+
for (const propName of propertyNames) {
|
|
4733
|
+
if (properties[propName].flags & TypeFlags.Identifier) {
|
|
4734
|
+
if (identifierAttribute) {
|
|
4735
|
+
throw fail(`Cannot define property '${propName}' as object identifier, property '${identifierAttribute}' is already defined as identifier property`);
|
|
4736
|
+
}
|
|
4737
|
+
identifierAttribute = propName;
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
return identifierAttribute;
|
|
4741
|
+
}
|
|
4620
4742
|
function toPropertiesObject(declaredProps) {
|
|
4621
4743
|
// loop through properties and ensures that all items are types
|
|
4622
4744
|
return Object.keys(declaredProps).reduce((props, key) => {
|
|
@@ -4682,14 +4804,29 @@ class ModelType extends ComplexType {
|
|
|
4682
4804
|
// to check the first instance we finalize (see finalizeNewInstance)
|
|
4683
4805
|
duplicateKeysChecked = false;
|
|
4684
4806
|
constructor(opts) {
|
|
4685
|
-
|
|
4686
|
-
Object.assign(this,
|
|
4807
|
+
// `??`, not `||`: `types.model("", {})` names the model "". The old
|
|
4808
|
+
// `Object.assign(this, defaults, opts)` below overwrote the `||` fallback
|
|
4809
|
+
// with opts.name afterwards, so that only worked by accident.
|
|
4810
|
+
super(opts.name ?? defaultObjectOptions.name);
|
|
4811
|
+
// Every field is assigned here, unconditionally and in a fixed order,
|
|
4812
|
+
// rather than by `Object.assign(this, defaultObjectOptions, opts)`. `opts`
|
|
4813
|
+
// carries a different key set at each of the three call sites — `model()`,
|
|
4814
|
+
// and cloneAndEnhance's preprocessed / converted paths — so copying it
|
|
4815
|
+
// wholesale gave ModelType three hidden classes, making every later read of
|
|
4816
|
+
// `type.properties` / `type.propertyNames` (getSnapshot's inner loop, among
|
|
4817
|
+
// others) polymorphic. It also left the internal `propertiesArePreProcessed`
|
|
4818
|
+
// / `propertiesAreConverted` plumbing on the type for its whole lifetime.
|
|
4819
|
+
this.initializers = opts.initializers ?? EMPTY_ARRAY;
|
|
4820
|
+
this.preProcessor = opts.preProcessor;
|
|
4821
|
+
this.postProcessor = opts.postProcessor;
|
|
4822
|
+
const declaredProperties = (opts.properties ?? EMPTY_OBJECT);
|
|
4687
4823
|
if (opts.propertiesArePreProcessed) {
|
|
4688
4824
|
// `properties` is a parent type's already-converted + frozen output
|
|
4689
4825
|
// (chain step with no new props), so its derived propertyNames and
|
|
4690
4826
|
// identifierAttribute are identical to the parent's — reuse them verbatim
|
|
4691
4827
|
// (cloneAndEnhance passed them in) instead of re-running Object.keys and
|
|
4692
4828
|
// the per-prop identifier scan, both O(props), on every step.
|
|
4829
|
+
this.properties = declaredProperties;
|
|
4693
4830
|
this.propertyNames = opts.propertyNames;
|
|
4694
4831
|
this.identifierAttribute = opts.identifierAttribute;
|
|
4695
4832
|
}
|
|
@@ -4698,26 +4835,16 @@ class ModelType extends ComplexType {
|
|
|
4698
4835
|
// means every value is already a type — the parent's converted bag merged
|
|
4699
4836
|
// with a freshly converted delta — so skip re-converting. Only raw entry
|
|
4700
4837
|
// points (`model()`) still need the full toPropertiesObject pass.
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4838
|
+
const properties = (opts.propertiesAreConverted
|
|
4839
|
+
? declaredProperties
|
|
4840
|
+
: toPropertiesObject(declaredProperties));
|
|
4841
|
+
this.properties = properties;
|
|
4842
|
+
freeze(properties); // make sure nobody messes with it
|
|
4843
|
+
const propertyNames = Object.keys(properties);
|
|
4844
|
+
this.propertyNames = propertyNames;
|
|
4845
|
+
this.identifierAttribute = findIdentifierAttribute(properties, propertyNames);
|
|
4707
4846
|
}
|
|
4708
4847
|
}
|
|
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
4848
|
cloneAndEnhance(opts) {
|
|
4722
4849
|
// Fast path: a chain step that adds no new properties (.actions/.views/
|
|
4723
4850
|
// .volatile/.named/pre-postProcessor) reuses this type's already-converted +
|
|
@@ -4931,9 +5058,20 @@ class ModelType extends ComplexType {
|
|
|
4931
5058
|
}
|
|
4932
5059
|
finalizeNewInstance(node, instance) {
|
|
4933
5060
|
addHiddenFinalProp(instance, "toString", objectTypeToString);
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
5061
|
+
// `_interceptReads(instance, name, ...)` assigns exactly this `dehancer`,
|
|
5062
|
+
// but reaches the ObservableValue via getAtom — several proxy-trap reads
|
|
5063
|
+
// per property (see getPropObservables). Resolve the map once instead.
|
|
5064
|
+
const observables = getPropObservables(instance);
|
|
5065
|
+
if (observables) {
|
|
5066
|
+
for (const name of this.propertyNames) {
|
|
5067
|
+
observables.get(name).dehancer = node.unbox;
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
else {
|
|
5071
|
+
this.forAllProps(name => {
|
|
5072
|
+
_interceptReads(instance, name, node.unbox);
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
4937
5075
|
this.initializers.reduce((self, fn) => fn(self), instance);
|
|
4938
5076
|
// views, actions and volatile share the instance namespace with properties,
|
|
4939
5077
|
// so a view/action reusing a property name silently clobbers that property's
|
|
@@ -4986,9 +5124,18 @@ class ModelType extends ComplexType {
|
|
|
4986
5124
|
}
|
|
4987
5125
|
getChildren(node) {
|
|
4988
5126
|
const names = this.propertyNames;
|
|
5127
|
+
const storedValue = node.storedValue;
|
|
5128
|
+
const observables = getPropObservables(storedValue);
|
|
4989
5129
|
const res = new Array(names.length);
|
|
4990
5130
|
for (let i = 0; i < names.length; i++) {
|
|
4991
|
-
|
|
5131
|
+
const name = names[i];
|
|
5132
|
+
const childNode = (observables
|
|
5133
|
+
? observables.get(name)
|
|
5134
|
+
: getPropObservable(storedValue, name))?.raw();
|
|
5135
|
+
if (!childNode) {
|
|
5136
|
+
throw fail(`Node not available for property ${name}`);
|
|
5137
|
+
}
|
|
5138
|
+
res[i] = childNode;
|
|
4992
5139
|
}
|
|
4993
5140
|
return res;
|
|
4994
5141
|
}
|
|
@@ -5014,14 +5161,17 @@ class ModelType extends ComplexType {
|
|
|
5014
5161
|
const res = {};
|
|
5015
5162
|
const storedValue = node.storedValue;
|
|
5016
5163
|
const properties = this.properties;
|
|
5164
|
+
const observables = getPropObservables(storedValue);
|
|
5017
5165
|
for (const name of this.propertyNames) {
|
|
5018
5166
|
// One mobx lookup serves both purposes: reportObserved so the snapshot
|
|
5019
5167
|
// computed recomputes when the child is reassigned (raw() below does not
|
|
5020
5168
|
// track), and raw() to read the child node. Going through getChildNode
|
|
5021
5169
|
// would repeat the same lookup for every property.
|
|
5022
|
-
const observable =
|
|
5023
|
-
|
|
5024
|
-
|
|
5170
|
+
const observable = observables
|
|
5171
|
+
? observables.get(name)
|
|
5172
|
+
: getPropObservable(storedValue, name);
|
|
5173
|
+
observable?.reportObserved();
|
|
5174
|
+
const childNode = observable?.raw();
|
|
5025
5175
|
if (!childNode) {
|
|
5026
5176
|
throw fail(`Node not available for property ${name}`);
|
|
5027
5177
|
}
|
|
@@ -5371,6 +5521,8 @@ function isPrimitiveType(type) {
|
|
|
5371
5521
|
(TypeFlags.String |
|
|
5372
5522
|
TypeFlags.Number |
|
|
5373
5523
|
TypeFlags.Integer |
|
|
5524
|
+
TypeFlags.Float |
|
|
5525
|
+
TypeFlags.Finite |
|
|
5374
5526
|
TypeFlags.Boolean |
|
|
5375
5527
|
TypeFlags.Date)) >
|
|
5376
5528
|
0);
|
|
@@ -5507,7 +5659,7 @@ function refinement(...args) {
|
|
|
5507
5659
|
* @returns
|
|
5508
5660
|
*/
|
|
5509
5661
|
function isRefinementType(type) {
|
|
5510
|
-
return (type.flags & TypeFlags.Refinement) > 0;
|
|
5662
|
+
return isType(type) && (type.flags & TypeFlags.Refinement) > 0;
|
|
5511
5663
|
}
|
|
5512
5664
|
|
|
5513
5665
|
/**
|
|
@@ -5548,10 +5700,23 @@ function enumeration(name, options) {
|
|
|
5548
5700
|
// the scoping never engages and every failure prints every member's full
|
|
5549
5701
|
// structure. Wrappers expose their child as `_subtype` (optional/refinement/
|
|
5550
5702
|
// snapshotProcessor) or via `getSubType()` (late); bounded to avoid cycles.
|
|
5703
|
+
// Only *successful* resolutions are cached. A wrapper chain's shape is fixed at
|
|
5704
|
+
// construction, so once a member resolves to a ModelType it always will; but a
|
|
5705
|
+
// `late` member reports no subtype until its definition evaluates, and that
|
|
5706
|
+
// miss must stay retryable.
|
|
5707
|
+
const resolvedModelTypes = new WeakMap();
|
|
5551
5708
|
function resolveModelType(type) {
|
|
5709
|
+
if (!type) {
|
|
5710
|
+
return undefined;
|
|
5711
|
+
}
|
|
5712
|
+
const cached = resolvedModelTypes.get(type);
|
|
5713
|
+
if (cached) {
|
|
5714
|
+
return cached;
|
|
5715
|
+
}
|
|
5552
5716
|
let current = type;
|
|
5553
5717
|
for (let depth = 0; current && depth < 20; depth++) {
|
|
5554
5718
|
if (current instanceof ModelType) {
|
|
5719
|
+
resolvedModelTypes.set(type, current);
|
|
5555
5720
|
return current;
|
|
5556
5721
|
}
|
|
5557
5722
|
const wrapper = current;
|
|
@@ -5565,31 +5730,51 @@ function resolveModelType(type) {
|
|
|
5565
5730
|
*/
|
|
5566
5731
|
class Union extends BaseType {
|
|
5567
5732
|
_types;
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
//
|
|
5571
|
-
//
|
|
5572
|
-
//
|
|
5573
|
-
//
|
|
5574
|
-
//
|
|
5733
|
+
_flags;
|
|
5734
|
+
// Memoized, but only once the fold is known to be stable. A `types.late`
|
|
5735
|
+
// member reports 0 for its subtype until its definition resolves, so a union
|
|
5736
|
+
// containing one must keep recomputing — and every wrapper ORs its subtype's
|
|
5737
|
+
// flags upward, so `Late` in the *result* is an exact test for "some member
|
|
5738
|
+
// may still change" however deeply it is nested. A resolved late still
|
|
5739
|
+
// reports Late, so such a union simply never caches; that is conservative in
|
|
5740
|
+
// the safe direction and no real-world union is built out of late members.
|
|
5741
|
+
//
|
|
5742
|
+
// This reverses an earlier decision to leave it uncached, which was sized at
|
|
5743
|
+
// ~4% "on union creation alone". That understated it: the reads are what
|
|
5744
|
+
// cost, not the creation. `ModelType._getIdentifierAttribute` folds every
|
|
5745
|
+
// property's flags on each `types.model()`, and jbrowse's config slots are
|
|
5746
|
+
// unions under a stripDefault, so building one schema re-folded every slot's
|
|
5747
|
+
// union. See agent-docs/adr/0003.
|
|
5575
5748
|
get flags() {
|
|
5749
|
+
const cached = this._flags;
|
|
5750
|
+
if (cached !== undefined) {
|
|
5751
|
+
return cached;
|
|
5752
|
+
}
|
|
5576
5753
|
let result = TypeFlags.Union;
|
|
5577
5754
|
for (const type of this._types) {
|
|
5578
5755
|
result |= type.flags;
|
|
5579
5756
|
}
|
|
5757
|
+
if (!(result & TypeFlags.Late)) {
|
|
5758
|
+
this._flags = result;
|
|
5759
|
+
}
|
|
5580
5760
|
return result;
|
|
5581
5761
|
}
|
|
5582
|
-
|
|
5583
|
-
|
|
5762
|
+
computeName() {
|
|
5763
|
+
return `(${this._types.map(type => type.name).join(" | ")})`;
|
|
5764
|
+
}
|
|
5765
|
+
constructor(_types, options) {
|
|
5766
|
+
super();
|
|
5584
5767
|
this._types = _types;
|
|
5585
|
-
options
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5768
|
+
// read the two options directly rather than spreading defaults into a fresh
|
|
5769
|
+
// object: this constructor runs once per config slot in jbrowse, and the
|
|
5770
|
+
// merged object was allocated only to be read twice and dropped
|
|
5771
|
+
if (options !== undefined) {
|
|
5772
|
+
if (options.dispatcher !== undefined) {
|
|
5773
|
+
this._dispatcher = options.dispatcher;
|
|
5774
|
+
}
|
|
5775
|
+
if (options.eager === false) {
|
|
5776
|
+
this._eager = false;
|
|
5777
|
+
}
|
|
5593
5778
|
}
|
|
5594
5779
|
}
|
|
5595
5780
|
isAssignableFrom(type) {
|
|
@@ -5640,14 +5825,6 @@ class Union extends BaseType {
|
|
|
5640
5825
|
}
|
|
5641
5826
|
return `${baseWithDiscriminator}:\n ${formatValidationErrorLines(errors).join("\n ")}`;
|
|
5642
5827
|
}
|
|
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
5828
|
_findCandidateByTypeDiscriminator(discriminator) {
|
|
5652
5829
|
const cache = (this._discriminatorCache ??= new Map());
|
|
5653
5830
|
if (cache.has(discriminator)) {
|
|
@@ -5682,11 +5859,6 @@ class Union extends BaseType {
|
|
|
5682
5859
|
}
|
|
5683
5860
|
return found;
|
|
5684
5861
|
}
|
|
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
5862
|
allMembersDiscriminated() {
|
|
5691
5863
|
if (this._allMembersDiscriminated === undefined) {
|
|
5692
5864
|
this._allMembersDiscriminated = this._types.every(t => {
|
|
@@ -5784,18 +5956,20 @@ class Union extends BaseType {
|
|
|
5784
5956
|
// use cached propertyNames from ModelType instead of Object.keys()
|
|
5785
5957
|
for (const key of model.propertyNames) {
|
|
5786
5958
|
const propType = props[key];
|
|
5787
|
-
|
|
5959
|
+
// `flags` is a recomputed getter on the wrapper types (optional,
|
|
5960
|
+
// snapshotProcessor, late, union), so read it once per property
|
|
5961
|
+
const flags = propType.flags;
|
|
5788
5962
|
const propValue = value[key];
|
|
5789
5963
|
// check required properties exist and are not undefined
|
|
5790
5964
|
// (unless the type accepts undefined, which Optional types do)
|
|
5791
|
-
if (!
|
|
5965
|
+
if (!(flags & TypeFlags.Optional)) {
|
|
5792
5966
|
if (!(key in value) || propValue === undefined) {
|
|
5793
5967
|
return false;
|
|
5794
5968
|
}
|
|
5795
5969
|
}
|
|
5796
5970
|
// for literal types, verify the value matches exactly
|
|
5797
5971
|
// this is critical for discriminated unions
|
|
5798
|
-
if (
|
|
5972
|
+
if (flags & TypeFlags.Literal) {
|
|
5799
5973
|
if (!propType.is(propValue)) {
|
|
5800
5974
|
return false;
|
|
5801
5975
|
}
|
|
@@ -5863,6 +6037,14 @@ class Union extends BaseType {
|
|
|
5863
6037
|
return this._types;
|
|
5864
6038
|
}
|
|
5865
6039
|
}
|
|
6040
|
+
// Defaults shared by every union; see the field declarations at the top of the
|
|
6041
|
+
// class for why they are not own slots.
|
|
6042
|
+
Object.assign(Union.prototype, {
|
|
6043
|
+
_dispatcher: undefined,
|
|
6044
|
+
_eager: true,
|
|
6045
|
+
_discriminatorCache: undefined,
|
|
6046
|
+
_allMembersDiscriminated: undefined
|
|
6047
|
+
});
|
|
5866
6048
|
/**
|
|
5867
6049
|
* `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
6050
|
*
|
|
@@ -5870,12 +6052,18 @@ class Union extends BaseType {
|
|
|
5870
6052
|
* @param otherTypes
|
|
5871
6053
|
* @returns
|
|
5872
6054
|
*/
|
|
5873
|
-
function union(
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
6055
|
+
function union(...args) {
|
|
6056
|
+
// One rest array, handed straight to the Union in the common case. Splitting
|
|
6057
|
+
// the leading argument out — whether by a `(first, ...rest)` signature that
|
|
6058
|
+
// rebuilds `[first, ...rest]`, or by `rest.unshift(first)` — allocates a
|
|
6059
|
+
// second array per union, and jbrowse builds one union per config slot. (The
|
|
6060
|
+
// `unshift` form is worse still: V8 inlines the spread and calls out to the
|
|
6061
|
+
// builtin, so it measured 3x the spread on a two-member union.) Only the
|
|
6062
|
+
// options overload, which nothing hot uses, pays for a copy.
|
|
6063
|
+
const firstIsType = isType(args[0]);
|
|
6064
|
+
const options = firstIsType ? undefined : args[0];
|
|
6065
|
+
const types = (firstIsType ? args : args.slice(1));
|
|
6066
|
+
// the name is folded from the members on demand — see Union.computeName
|
|
5879
6067
|
// check all options
|
|
5880
6068
|
if (devMode()) {
|
|
5881
6069
|
if (options) {
|
|
@@ -5885,7 +6073,7 @@ function union(optionsOrType, ...otherTypes) {
|
|
|
5885
6073
|
assertIsType(type, options ? i + 2 : i + 1);
|
|
5886
6074
|
});
|
|
5887
6075
|
}
|
|
5888
|
-
return new Union(
|
|
6076
|
+
return new Union(types, options);
|
|
5889
6077
|
}
|
|
5890
6078
|
/**
|
|
5891
6079
|
* Returns if a given value represents a union type.
|
|
@@ -5894,7 +6082,7 @@ function union(optionsOrType, ...otherTypes) {
|
|
|
5894
6082
|
* @returns
|
|
5895
6083
|
*/
|
|
5896
6084
|
function isUnionType(type) {
|
|
5897
|
-
return (type.flags & TypeFlags.Union) > 0;
|
|
6085
|
+
return isType(type) && (type.flags & TypeFlags.Union) > 0;
|
|
5898
6086
|
}
|
|
5899
6087
|
/**
|
|
5900
6088
|
* Returns the member types of a union.
|
|
@@ -5932,15 +6120,31 @@ class OptionalValue extends BaseType {
|
|
|
5932
6120
|
_subtype;
|
|
5933
6121
|
_defaultValue;
|
|
5934
6122
|
optionalValues;
|
|
6123
|
+
_flags;
|
|
6124
|
+
// memoized once stable; see the same guard on Union.flags for why `Late` in
|
|
6125
|
+
// the folded result is the exact test for "may still change"
|
|
5935
6126
|
get flags() {
|
|
5936
|
-
|
|
6127
|
+
const cached = this._flags;
|
|
6128
|
+
if (cached !== undefined) {
|
|
6129
|
+
return cached;
|
|
6130
|
+
}
|
|
6131
|
+
const result = this._subtype.flags | TypeFlags.Optional;
|
|
6132
|
+
if (!(result & TypeFlags.Late)) {
|
|
6133
|
+
this._flags = result;
|
|
6134
|
+
}
|
|
6135
|
+
return result;
|
|
5937
6136
|
}
|
|
5938
6137
|
constructor(_subtype, _defaultValue, optionalValues) {
|
|
5939
|
-
|
|
6138
|
+
// no name argument: reading `_subtype.name` here would force the name of
|
|
6139
|
+
// whatever is wrapped, and jbrowse wraps a union per config slot
|
|
6140
|
+
super();
|
|
5940
6141
|
this._subtype = _subtype;
|
|
5941
6142
|
this._defaultValue = _defaultValue;
|
|
5942
6143
|
this.optionalValues = optionalValues;
|
|
5943
6144
|
}
|
|
6145
|
+
computeName() {
|
|
6146
|
+
return this._subtype.name;
|
|
6147
|
+
}
|
|
5944
6148
|
describe() {
|
|
5945
6149
|
return `${this._subtype.describe()}?`;
|
|
5946
6150
|
}
|
|
@@ -5983,8 +6187,11 @@ class OptionalValue extends BaseType {
|
|
|
5983
6187
|
}
|
|
5984
6188
|
}
|
|
5985
6189
|
function checkOptionalPreconditions(type, defaultValueOrFunction) {
|
|
5986
|
-
// make sure we never pass direct instances
|
|
5987
|
-
|
|
6190
|
+
// make sure we never pass direct instances. A node is always an object, so
|
|
6191
|
+
// the typeof narrows first: most defaults are primitives, and reading
|
|
6192
|
+
// `$treenode` off a string or a number is a megamorphic miss that this runs
|
|
6193
|
+
// once per config slot.
|
|
6194
|
+
if (typeof defaultValueOrFunction === "object" &&
|
|
5988
6195
|
isStateTreeNode(defaultValueOrFunction)) {
|
|
5989
6196
|
throw fail("default value cannot be an instance, pass a snapshot or a function that creates an instance/snapshot instead");
|
|
5990
6197
|
}
|
|
@@ -6134,7 +6341,6 @@ function defaultSnapshotEquals(a, b) {
|
|
|
6134
6341
|
* @internal
|
|
6135
6342
|
*/
|
|
6136
6343
|
class StripDefaultValue extends OptionalValue {
|
|
6137
|
-
_defaultSnapshot;
|
|
6138
6344
|
shouldStripFromSnapshot(snapshot) {
|
|
6139
6345
|
if (!this._defaultSnapshot) {
|
|
6140
6346
|
// instantiate the subtype detached with the default and read the node's
|
|
@@ -6146,6 +6352,9 @@ class StripDefaultValue extends OptionalValue {
|
|
|
6146
6352
|
return defaultSnapshotEquals(snapshot, this._defaultSnapshot.value);
|
|
6147
6353
|
}
|
|
6148
6354
|
}
|
|
6355
|
+
Object.assign(StripDefaultValue.prototype, {
|
|
6356
|
+
_defaultSnapshot: undefined
|
|
6357
|
+
});
|
|
6149
6358
|
/**
|
|
6150
6359
|
* Whether `type` is a strip-default optional whose current child `snapshot`
|
|
6151
6360
|
* equals its default and should therefore be omitted from the parent model's
|
|
@@ -6348,7 +6557,13 @@ class Lazy extends SimpleType {
|
|
|
6348
6557
|
}
|
|
6349
6558
|
const node = createScalarNode(this, parent, subpath, environment, deepFreeze(value));
|
|
6350
6559
|
this.pendingNodeList.push(node);
|
|
6351
|
-
when(() => !node.isAlive, () =>
|
|
6560
|
+
when(() => !node.isAlive, () => {
|
|
6561
|
+
// guard the index: splice(-1, 1) would drop an unrelated pending node
|
|
6562
|
+
const index = this.pendingNodeList.indexOf(node);
|
|
6563
|
+
if (index >= 0) {
|
|
6564
|
+
this.pendingNodeList.splice(index, 1);
|
|
6565
|
+
}
|
|
6566
|
+
});
|
|
6352
6567
|
return node;
|
|
6353
6568
|
}
|
|
6354
6569
|
isValidSnapshot(value, context) {
|
|
@@ -6377,9 +6592,12 @@ class Frozen extends SimpleType {
|
|
|
6377
6592
|
subType;
|
|
6378
6593
|
flags = TypeFlags.Frozen;
|
|
6379
6594
|
constructor(subType) {
|
|
6380
|
-
super(subType ?
|
|
6595
|
+
super(subType ? undefined : "frozen");
|
|
6381
6596
|
this.subType = subType;
|
|
6382
6597
|
}
|
|
6598
|
+
computeName() {
|
|
6599
|
+
return `frozen(${this.subType.name})`;
|
|
6600
|
+
}
|
|
6383
6601
|
describe() {
|
|
6384
6602
|
return "<any immutable value>";
|
|
6385
6603
|
}
|
|
@@ -6535,10 +6753,13 @@ class BaseReferenceType extends SimpleType {
|
|
|
6535
6753
|
onInvalidated;
|
|
6536
6754
|
flags = TypeFlags.Reference;
|
|
6537
6755
|
constructor(targetType, onInvalidated) {
|
|
6538
|
-
super(
|
|
6756
|
+
super();
|
|
6539
6757
|
this.targetType = targetType;
|
|
6540
6758
|
this.onInvalidated = onInvalidated;
|
|
6541
6759
|
}
|
|
6760
|
+
computeName() {
|
|
6761
|
+
return `reference(${this.targetType.name})`;
|
|
6762
|
+
}
|
|
6542
6763
|
describe() {
|
|
6543
6764
|
return this.name;
|
|
6544
6765
|
}
|
|
@@ -6801,7 +7022,7 @@ function reference(subType, options) {
|
|
|
6801
7022
|
* @returns
|
|
6802
7023
|
*/
|
|
6803
7024
|
function isReferenceType(type) {
|
|
6804
|
-
return (type.flags & TypeFlags.Reference) > 0;
|
|
7025
|
+
return isType(type) && (type.flags & TypeFlags.Reference) > 0;
|
|
6805
7026
|
}
|
|
6806
7027
|
/**
|
|
6807
7028
|
* `types.safeReference` - A safe reference is like a standard reference, except that it accepts the undefined value by default
|
|
@@ -6872,7 +7093,6 @@ class BaseIdentifierType extends SimpleType {
|
|
|
6872
7093
|
* @hidden
|
|
6873
7094
|
*/
|
|
6874
7095
|
class IdentifierType extends BaseIdentifierType {
|
|
6875
|
-
flags = TypeFlags.Identifier;
|
|
6876
7096
|
constructor() {
|
|
6877
7097
|
super(`identifier`, "string");
|
|
6878
7098
|
}
|
|
@@ -7070,11 +7290,14 @@ class Resilient extends BaseType {
|
|
|
7070
7290
|
return this._subtype.flags;
|
|
7071
7291
|
}
|
|
7072
7292
|
constructor(_subtype, _fallbackType, _createFallbackSnapshot) {
|
|
7073
|
-
super(
|
|
7293
|
+
super();
|
|
7074
7294
|
this._subtype = _subtype;
|
|
7075
7295
|
this._fallbackType = _fallbackType;
|
|
7076
7296
|
this._createFallbackSnapshot = _createFallbackSnapshot;
|
|
7077
7297
|
}
|
|
7298
|
+
computeName() {
|
|
7299
|
+
return `resilient(${this._subtype.name})`;
|
|
7300
|
+
}
|
|
7078
7301
|
describe() {
|
|
7079
7302
|
return `resilient(${this._subtype.describe()})`;
|
|
7080
7303
|
}
|
|
@@ -7109,12 +7332,20 @@ class Resilient extends BaseType {
|
|
|
7109
7332
|
return this._subtype.reconcile(current, newValue, parent, subpath);
|
|
7110
7333
|
}
|
|
7111
7334
|
if (this._fallbackType.isAssignableFrom(current.type)) {
|
|
7335
|
+
// `current` holds the fallback. Try the real type again, but keep the
|
|
7336
|
+
// fallback node around while doing so — it is what the catch reconciles.
|
|
7337
|
+
let recovered;
|
|
7112
7338
|
try {
|
|
7113
|
-
|
|
7339
|
+
recovered = this._subtype.instantiate(parent, subpath, undefined, newValue);
|
|
7114
7340
|
}
|
|
7115
7341
|
catch (e) {
|
|
7116
7342
|
return this._fallbackType.reconcile(current, this._createFallbackSnapshot(e, newValue), parent, subpath);
|
|
7117
7343
|
}
|
|
7344
|
+
// the fallback node has been replaced, so it has to die: otherwise it
|
|
7345
|
+
// stays alive in the tree and its identifier stays in the root's cache,
|
|
7346
|
+
// where it collides with the recovered node's
|
|
7347
|
+
current.die();
|
|
7348
|
+
return recovered;
|
|
7118
7349
|
}
|
|
7119
7350
|
try {
|
|
7120
7351
|
return this._subtype.reconcile(current, newValue, parent, subpath);
|