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