@jbrowse/mobx-state-tree 6.0.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 +29 -8
- package/dist/mobx-state-tree.cjs +518 -229
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +518 -229
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
package/dist/mobx-state-tree.mjs
CHANGED
|
@@ -944,7 +944,6 @@ class BaseNode {
|
|
|
944
944
|
constructor(type, parent, subpath, environment) {
|
|
945
945
|
this.type = type;
|
|
946
946
|
this.environment = environment;
|
|
947
|
-
this.environment = environment;
|
|
948
947
|
this.baseSetParent(parent, subpath);
|
|
949
948
|
}
|
|
950
949
|
getReconciliationType() {
|
|
@@ -1166,7 +1165,7 @@ class ObjectNode extends BaseNode {
|
|
|
1166
1165
|
}
|
|
1167
1166
|
_autoUnbox = true; // unboxing is disabled when reading child nodes
|
|
1168
1167
|
_isRunningAction = false; // only relevant for root
|
|
1169
|
-
|
|
1168
|
+
_snapshotReactionDisposer;
|
|
1170
1169
|
_observableInstanceState = ObservableInstanceLifecycle.UNINITIALIZED;
|
|
1171
1170
|
_childNodes;
|
|
1172
1171
|
_initialSnapshot;
|
|
@@ -1412,10 +1411,7 @@ class ObjectNode extends BaseNode {
|
|
|
1412
1411
|
}
|
|
1413
1412
|
_getCachedInitialSnapshot() {
|
|
1414
1413
|
if (!this._cachedInitialSnapshotCreated) {
|
|
1415
|
-
|
|
1416
|
-
const childNodes = this._childNodes;
|
|
1417
|
-
const snapshot = this._initialSnapshot;
|
|
1418
|
-
this._cachedInitialSnapshot = type.processInitialSnapshot(childNodes, snapshot);
|
|
1414
|
+
this._cachedInitialSnapshot = this.type.processInitialSnapshot(this._childNodes);
|
|
1419
1415
|
this._cachedInitialSnapshotCreated = true;
|
|
1420
1416
|
}
|
|
1421
1417
|
return this._cachedInitialSnapshot;
|
|
@@ -1591,7 +1587,17 @@ class ObjectNode extends BaseNode {
|
|
|
1591
1587
|
}
|
|
1592
1588
|
onSnapshot(onChange) {
|
|
1593
1589
|
this._addSnapshotReaction();
|
|
1594
|
-
|
|
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
|
+
};
|
|
1595
1601
|
}
|
|
1596
1602
|
emitSnapshot(snapshot) {
|
|
1597
1603
|
this._internalEventsEmit(InternalEvents.Snapshot, snapshot);
|
|
@@ -1656,12 +1662,24 @@ class ObjectNode extends BaseNode {
|
|
|
1656
1662
|
this.type.applyPatchLocally(this, subpath, patch);
|
|
1657
1663
|
}
|
|
1658
1664
|
_addSnapshotReaction() {
|
|
1659
|
-
if (!this.
|
|
1665
|
+
if (!this._snapshotReactionDisposer) {
|
|
1660
1666
|
const snapshotDisposer = reaction(() => this.snapshot, snapshot => this.emitSnapshot(snapshot), snapshotReactionOptions);
|
|
1661
1667
|
this.addDisposer(snapshotDisposer);
|
|
1662
|
-
this.
|
|
1668
|
+
this._snapshotReactionDisposer = snapshotDisposer;
|
|
1663
1669
|
}
|
|
1664
1670
|
}
|
|
1671
|
+
_removeSnapshotReaction() {
|
|
1672
|
+
const disposer = this._snapshotReactionDisposer;
|
|
1673
|
+
if (!disposer) {
|
|
1674
|
+
return;
|
|
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();
|
|
1682
|
+
}
|
|
1665
1683
|
// #region internal event handling
|
|
1666
1684
|
_internalEvents;
|
|
1667
1685
|
// we proxy the methods to avoid creating an EventHandlers instance when it is not needed
|
|
@@ -1737,8 +1755,6 @@ var TypeFlags;
|
|
|
1737
1755
|
* @hidden
|
|
1738
1756
|
*/
|
|
1739
1757
|
const cannotDetermineSubtype = "cannotDetermine";
|
|
1740
|
-
/** @hidden */
|
|
1741
|
-
const $type = Symbol("$type");
|
|
1742
1758
|
/**
|
|
1743
1759
|
* A base type produces a MST node (Node in the state tree)
|
|
1744
1760
|
*
|
|
@@ -1746,16 +1762,39 @@ const $type = Symbol("$type");
|
|
|
1746
1762
|
* @hidden
|
|
1747
1763
|
*/
|
|
1748
1764
|
class BaseType {
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
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
|
+
}
|
|
1757
1794
|
constructor(name) {
|
|
1758
|
-
|
|
1795
|
+
if (name !== undefined) {
|
|
1796
|
+
this._name = name;
|
|
1797
|
+
}
|
|
1759
1798
|
}
|
|
1760
1799
|
create(snapshot, environment) {
|
|
1761
1800
|
typecheckInternal(this, snapshot);
|
|
@@ -1800,6 +1839,9 @@ class BaseType {
|
|
|
1800
1839
|
}
|
|
1801
1840
|
}
|
|
1802
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 });
|
|
1803
1845
|
/**
|
|
1804
1846
|
* A complex type produces a MST node (Node in the state tree)
|
|
1805
1847
|
*
|
|
@@ -1808,9 +1850,6 @@ BaseType.prototype.create = action(BaseType.prototype.create);
|
|
|
1808
1850
|
*/
|
|
1809
1851
|
class ComplexType extends BaseType {
|
|
1810
1852
|
identifierAttribute;
|
|
1811
|
-
constructor(name) {
|
|
1812
|
-
super(name);
|
|
1813
|
-
}
|
|
1814
1853
|
create(snapshot = this.getDefaultSnapshot(), environment) {
|
|
1815
1854
|
return super.create(snapshot, environment);
|
|
1816
1855
|
}
|
|
@@ -2128,7 +2167,7 @@ function createActionTrackingMiddleware2(middlewareHooks) {
|
|
|
2128
2167
|
};
|
|
2129
2168
|
}
|
|
2130
2169
|
|
|
2131
|
-
function serializeArgument(
|
|
2170
|
+
function serializeArgument(arg) {
|
|
2132
2171
|
if (arg instanceof Date) {
|
|
2133
2172
|
return { $MST_DATE: arg.getTime() };
|
|
2134
2173
|
}
|
|
@@ -2156,7 +2195,7 @@ function serializeArgument(node, actionName, index, arg) {
|
|
|
2156
2195
|
return serializeTheUnserializable(`${e}`);
|
|
2157
2196
|
}
|
|
2158
2197
|
}
|
|
2159
|
-
function deserializeArgument(
|
|
2198
|
+
function deserializeArgument(value) {
|
|
2160
2199
|
if (value && typeof value === "object" && "$MST_DATE" in value) {
|
|
2161
2200
|
return new Date(value["$MST_DATE"]);
|
|
2162
2201
|
}
|
|
@@ -2200,7 +2239,7 @@ function baseApplyAction(target, action) {
|
|
|
2200
2239
|
if (!(typeof resolvedTarget[action.name] === "function")) {
|
|
2201
2240
|
throw fail(`Action '${action.name}' does not exist in '${node.path}'`);
|
|
2202
2241
|
}
|
|
2203
|
-
return resolvedTarget[action.name](...(action.args ? action.args.map(
|
|
2242
|
+
return resolvedTarget[action.name](...(action.args ? action.args.map(deserializeArgument) : []));
|
|
2204
2243
|
}
|
|
2205
2244
|
/**
|
|
2206
2245
|
* Small abstraction around `onAction` and `applyAction`, attaches an action listener to a tree and records all the actions emitted.
|
|
@@ -2317,7 +2356,7 @@ function onAction(target, listener, attachAfter = false) {
|
|
|
2317
2356
|
const info = {
|
|
2318
2357
|
name: rawCall.name,
|
|
2319
2358
|
path: getRelativePathBetweenNodes(getStateTreeNode(target), sourceNode),
|
|
2320
|
-
args: rawCall.args.map(
|
|
2359
|
+
args: rawCall.args.map(serializeArgument)
|
|
2321
2360
|
};
|
|
2322
2361
|
if (attachAfter) {
|
|
2323
2362
|
const res = next(rawCall);
|
|
@@ -2712,13 +2751,6 @@ function typeCheckFailure(context, value, message) {
|
|
|
2712
2751
|
// Clone context since it may be mutated after this error is created
|
|
2713
2752
|
return [{ context: context.slice(), value, message }];
|
|
2714
2753
|
}
|
|
2715
|
-
/**
|
|
2716
|
-
* @internal
|
|
2717
|
-
* @hidden
|
|
2718
|
-
*/
|
|
2719
|
-
function flattenTypeErrors(errors) {
|
|
2720
|
-
return errors.flat();
|
|
2721
|
-
}
|
|
2722
2754
|
// TODO; doublecheck: typecheck should only needed to be invoked from: type.create and array / map / value.property will change
|
|
2723
2755
|
/**
|
|
2724
2756
|
* @internal
|
|
@@ -2780,7 +2812,6 @@ class IdentifierCache {
|
|
|
2780
2812
|
// last time the cache (array) for a given time changed
|
|
2781
2813
|
// n.b. it is not really the time, but just an integer that gets increased after each modification to the array
|
|
2782
2814
|
lastCacheModificationPerId = observable.map();
|
|
2783
|
-
constructor() { }
|
|
2784
2815
|
updateLastCacheModificationPerId(identifier) {
|
|
2785
2816
|
const lcm = this.lastCacheModificationPerId.get(identifier);
|
|
2786
2817
|
// we start at 1 since 0 means no update since cache creation
|
|
@@ -2793,10 +2824,13 @@ class IdentifierCache {
|
|
|
2793
2824
|
addNodeToCache(node, lastCacheUpdate = true) {
|
|
2794
2825
|
if (node.identifierAttribute) {
|
|
2795
2826
|
const identifier = node.identifier;
|
|
2796
|
-
|
|
2797
|
-
|
|
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);
|
|
2798
2833
|
}
|
|
2799
|
-
const set = this.cache.get(identifier);
|
|
2800
2834
|
if (set.includes(node)) {
|
|
2801
2835
|
throw fail(`Already registered`);
|
|
2802
2836
|
}
|
|
@@ -2821,7 +2855,7 @@ class IdentifierCache {
|
|
|
2821
2855
|
if (!set.length) {
|
|
2822
2856
|
this.cache.delete(id);
|
|
2823
2857
|
}
|
|
2824
|
-
this.updateLastCacheModificationPerId(
|
|
2858
|
+
this.updateLastCacheModificationPerId(id);
|
|
2825
2859
|
}
|
|
2826
2860
|
}
|
|
2827
2861
|
}
|
|
@@ -3053,15 +3087,8 @@ function convertChildNodesToArray(childNodes) {
|
|
|
3053
3087
|
if (!childNodes) {
|
|
3054
3088
|
return EMPTY_ARRAY;
|
|
3055
3089
|
}
|
|
3056
|
-
const
|
|
3057
|
-
|
|
3058
|
-
return EMPTY_ARRAY;
|
|
3059
|
-
}
|
|
3060
|
-
const result = new Array(keys.length);
|
|
3061
|
-
keys.forEach((key, index) => {
|
|
3062
|
-
result[index] = childNodes[key];
|
|
3063
|
-
});
|
|
3064
|
-
return result;
|
|
3090
|
+
const result = Object.values(childNodes);
|
|
3091
|
+
return result.length ? result : EMPTY_ARRAY;
|
|
3065
3092
|
}
|
|
3066
3093
|
|
|
3067
3094
|
const plainObjectString = Object.toString();
|
|
@@ -3143,7 +3170,11 @@ function isPlainObject(value) {
|
|
|
3143
3170
|
return false;
|
|
3144
3171
|
}
|
|
3145
3172
|
const proto = Object.getPrototypeOf(value);
|
|
3146
|
-
|
|
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) {
|
|
3147
3178
|
return true;
|
|
3148
3179
|
}
|
|
3149
3180
|
return proto.constructor?.toString() === plainObjectString;
|
|
@@ -3277,10 +3308,19 @@ class EventHandler {
|
|
|
3277
3308
|
this.handlers.length = 0;
|
|
3278
3309
|
}
|
|
3279
3310
|
emit(...args) {
|
|
3280
|
-
|
|
3281
|
-
// and
|
|
3282
|
-
|
|
3283
|
-
|
|
3311
|
+
const handlers = this.handlers;
|
|
3312
|
+
// 0 and 1 handlers are by far the common cases (a patch/snapshot listener, a
|
|
3313
|
+
// disposer) and need no copy: with a single handler, calling the value we
|
|
3314
|
+
// already read is exactly what iterating a copy of it would do.
|
|
3315
|
+
if (handlers.length <= 1) {
|
|
3316
|
+
handlers[0]?.(...args);
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
// otherwise iterate a copy so (un)registrations during emit don't disturb
|
|
3320
|
+
// this pass and reentrant emits stay correct
|
|
3321
|
+
for (const f of handlers.slice()) {
|
|
3322
|
+
f(...args);
|
|
3323
|
+
}
|
|
3284
3324
|
}
|
|
3285
3325
|
}
|
|
3286
3326
|
/**
|
|
@@ -3661,6 +3701,11 @@ function escapeJsonPath(path) {
|
|
|
3661
3701
|
* Unescape slashes and backslashes.
|
|
3662
3702
|
*/
|
|
3663
3703
|
function unescapeJsonPath(path) {
|
|
3704
|
+
// every escape sequence starts with `~`, so bail before the two regex passes
|
|
3705
|
+
// for the overwhelmingly common unescaped part (mirrors escapeJsonPath above)
|
|
3706
|
+
if (!path.includes("~")) {
|
|
3707
|
+
return path;
|
|
3708
|
+
}
|
|
3664
3709
|
return path.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3665
3710
|
}
|
|
3666
3711
|
/**
|
|
@@ -3719,14 +3764,30 @@ const $preProcessorFailed = Symbol("$preProcessorFailed");
|
|
|
3719
3764
|
class SnapshotProcessor extends BaseType {
|
|
3720
3765
|
_subtype;
|
|
3721
3766
|
_processors;
|
|
3767
|
+
_flags;
|
|
3768
|
+
// memoized once stable; see the guard on Union.flags
|
|
3722
3769
|
get flags() {
|
|
3723
|
-
|
|
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;
|
|
3724
3779
|
}
|
|
3725
3780
|
constructor(_subtype, _processors, name) {
|
|
3726
|
-
|
|
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);
|
|
3727
3785
|
this._subtype = _subtype;
|
|
3728
3786
|
this._processors = _processors;
|
|
3729
3787
|
}
|
|
3788
|
+
computeName() {
|
|
3789
|
+
return this._subtype.name;
|
|
3790
|
+
}
|
|
3730
3791
|
describe() {
|
|
3731
3792
|
return `snapshotProcessor(${this._subtype.describe()})`;
|
|
3732
3793
|
}
|
|
@@ -3976,17 +4037,20 @@ class MapType extends ComplexType {
|
|
|
3976
4037
|
mapIdentifierAttribute = undefined;
|
|
3977
4038
|
flags = TypeFlags.Map;
|
|
3978
4039
|
hookInitializers = [];
|
|
3979
|
-
constructor(
|
|
3980
|
-
super(
|
|
4040
|
+
constructor(_subType, hookInitializers = []) {
|
|
4041
|
+
super();
|
|
3981
4042
|
this._subType = _subType;
|
|
3982
4043
|
this._determineIdentifierMode();
|
|
3983
4044
|
this.hookInitializers = hookInitializers;
|
|
3984
4045
|
}
|
|
4046
|
+
computeName() {
|
|
4047
|
+
return `Map<string, ${this._subType.name}>`;
|
|
4048
|
+
}
|
|
3985
4049
|
hooks(hooks) {
|
|
3986
4050
|
const hookInitializers = this.hookInitializers.length > 0
|
|
3987
4051
|
? this.hookInitializers.concat(hooks)
|
|
3988
4052
|
: [hooks];
|
|
3989
|
-
return new MapType(this.
|
|
4053
|
+
return new MapType(this._subType, hookInitializers);
|
|
3990
4054
|
}
|
|
3991
4055
|
instantiate(parent, subpath, environment, initialValue) {
|
|
3992
4056
|
this._determineIdentifierMode();
|
|
@@ -4151,22 +4215,21 @@ class MapType extends ComplexType {
|
|
|
4151
4215
|
applySnapshot(node, snapshot) {
|
|
4152
4216
|
typecheckInternal(this, snapshot);
|
|
4153
4217
|
const target = node.storedValue;
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4218
|
+
// keys present before the apply, minus the ones the snapshot (re)sets — what
|
|
4219
|
+
// remains has to be deleted. A Set rather than a plain object: it needs no
|
|
4220
|
+
// sentinel values, no second pass to find the survivors, and it can hold keys
|
|
4221
|
+
// like "__proto__", which an object literal silently swallows.
|
|
4222
|
+
const staleKeys = new Set(target.keys());
|
|
4158
4223
|
if (snapshot) {
|
|
4159
4224
|
// Don't use target.replace, as it will throw away all existing items first
|
|
4160
4225
|
for (const key in snapshot) {
|
|
4161
4226
|
target.set(key, snapshot[key]);
|
|
4162
|
-
|
|
4227
|
+
staleKeys.delete(key);
|
|
4163
4228
|
}
|
|
4164
4229
|
}
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
}
|
|
4169
|
-
});
|
|
4230
|
+
for (const key of staleKeys) {
|
|
4231
|
+
target.delete(key);
|
|
4232
|
+
}
|
|
4170
4233
|
}
|
|
4171
4234
|
getChildType() {
|
|
4172
4235
|
return this._subType;
|
|
@@ -4221,7 +4284,7 @@ MapType.prototype.applySnapshot = action(MapType.prototype.applySnapshot);
|
|
|
4221
4284
|
* @returns
|
|
4222
4285
|
*/
|
|
4223
4286
|
function map(subtype) {
|
|
4224
|
-
return new MapType(
|
|
4287
|
+
return new MapType(subtype);
|
|
4225
4288
|
}
|
|
4226
4289
|
/**
|
|
4227
4290
|
* Returns if a given value represents a map type.
|
|
@@ -4241,16 +4304,19 @@ class ArrayType extends ComplexType {
|
|
|
4241
4304
|
_subType;
|
|
4242
4305
|
flags = TypeFlags.Array;
|
|
4243
4306
|
hookInitializers = [];
|
|
4244
|
-
constructor(
|
|
4245
|
-
super(
|
|
4307
|
+
constructor(_subType, hookInitializers = []) {
|
|
4308
|
+
super();
|
|
4246
4309
|
this._subType = _subType;
|
|
4247
4310
|
this.hookInitializers = hookInitializers;
|
|
4248
4311
|
}
|
|
4312
|
+
computeName() {
|
|
4313
|
+
return `${this._subType.name}[]`;
|
|
4314
|
+
}
|
|
4249
4315
|
hooks(hooks) {
|
|
4250
4316
|
const hookInitializers = this.hookInitializers.length > 0
|
|
4251
4317
|
? this.hookInitializers.concat(hooks)
|
|
4252
4318
|
: [hooks];
|
|
4253
|
-
return new ArrayType(this.
|
|
4319
|
+
return new ArrayType(this._subType, hookInitializers);
|
|
4254
4320
|
}
|
|
4255
4321
|
instantiate(parent, subpath, environment, initialValue) {
|
|
4256
4322
|
return createObjectNode(this, parent, subpath, environment, initialValue);
|
|
@@ -4299,14 +4365,17 @@ class ArrayType extends ComplexType {
|
|
|
4299
4365
|
const node = getStateTreeNode(change.object);
|
|
4300
4366
|
node.assertWritable({ subpath: `${change.index}` });
|
|
4301
4367
|
const subType = node.type._subType;
|
|
4302
|
-
const childNodes = node.getChildren();
|
|
4303
4368
|
switch (change.type) {
|
|
4304
4369
|
case "update":
|
|
4305
4370
|
{
|
|
4306
4371
|
if (change.newValue === change.object[change.index]) {
|
|
4307
4372
|
return null;
|
|
4308
4373
|
}
|
|
4309
|
-
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);
|
|
4310
4379
|
if (!updatedNodes) {
|
|
4311
4380
|
return null;
|
|
4312
4381
|
}
|
|
@@ -4316,7 +4385,8 @@ class ArrayType extends ComplexType {
|
|
|
4316
4385
|
case "splice":
|
|
4317
4386
|
{
|
|
4318
4387
|
const { index, removedCount, added } = change;
|
|
4319
|
-
const
|
|
4388
|
+
const childNodes = node.getChildren();
|
|
4389
|
+
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added, index);
|
|
4320
4390
|
if (!addedNodes) {
|
|
4321
4391
|
return null;
|
|
4322
4392
|
}
|
|
@@ -4440,9 +4510,13 @@ ArrayType.prototype.applySnapshot = action(ArrayType.prototype.applySnapshot);
|
|
|
4440
4510
|
*/
|
|
4441
4511
|
function array(subtype) {
|
|
4442
4512
|
assertIsType(subtype, 1);
|
|
4443
|
-
return new ArrayType(
|
|
4513
|
+
return new ArrayType(subtype);
|
|
4444
4514
|
}
|
|
4445
|
-
|
|
4515
|
+
/**
|
|
4516
|
+
* @param firstNewPath index the reconciled slice starts at; both call sites
|
|
4517
|
+
* hand over a contiguous run, so subpath `i` is simply `firstNewPath + i`
|
|
4518
|
+
*/
|
|
4519
|
+
function reconcileArrayChildren(parent, childType, oldNodes, newValues, firstNewPath) {
|
|
4446
4520
|
let nothingChanged = true;
|
|
4447
4521
|
// When the element type is a plain model with an identifier, index the old
|
|
4448
4522
|
// nodes by id so a moved / replaced child is matched in O(1) instead of the
|
|
@@ -4451,8 +4525,12 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
|
|
|
4451
4525
|
// whose id extraction needs type-specific preprocessing (union,
|
|
4452
4526
|
// snapshotProcessor, late, ...) are intentionally excluded: areSame must run
|
|
4453
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.
|
|
4454
4530
|
let idIndex;
|
|
4455
|
-
if (
|
|
4531
|
+
if (oldNodes.length > 1 &&
|
|
4532
|
+
childType instanceof ModelType &&
|
|
4533
|
+
childType.identifierAttribute) {
|
|
4456
4534
|
const byId = new Map();
|
|
4457
4535
|
for (const n of oldNodes) {
|
|
4458
4536
|
if (n instanceof ObjectNode && n.identifier !== null) {
|
|
@@ -4465,7 +4543,7 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
|
|
|
4465
4543
|
const hasNewNode = i <= newValues.length - 1;
|
|
4466
4544
|
const oldNode = oldNodes[i];
|
|
4467
4545
|
let newValue = hasNewNode ? newValues[i] : undefined;
|
|
4468
|
-
const newPath = `${
|
|
4546
|
+
const newPath = `${firstNewPath + i}`;
|
|
4469
4547
|
// for some reason, instead of newValue we got a node, fallback to the storedValue
|
|
4470
4548
|
// TODO: https://github.com/mobxjs/mobx-state-tree/issues/340#issuecomment-325581681
|
|
4471
4549
|
if (isNode(newValue)) {
|
|
@@ -4589,12 +4667,14 @@ function areSame(oldNode, newValue) {
|
|
|
4589
4667
|
if (!(oldNode instanceof ObjectNode)) {
|
|
4590
4668
|
return false;
|
|
4591
4669
|
}
|
|
4670
|
+
if (oldNode.identifier === null ||
|
|
4671
|
+
!oldNode.identifierAttribute ||
|
|
4672
|
+
!isPlainObject(newValue)) {
|
|
4673
|
+
return false;
|
|
4674
|
+
}
|
|
4592
4675
|
const oldNodeType = oldNode.getReconciliationType();
|
|
4593
4676
|
// new value is a snapshot with the correct identifier
|
|
4594
|
-
return (
|
|
4595
|
-
oldNode.identifierAttribute &&
|
|
4596
|
-
isPlainObject(newValue) &&
|
|
4597
|
-
oldNodeType.is(newValue) &&
|
|
4677
|
+
return (oldNodeType.is(newValue) &&
|
|
4598
4678
|
oldNodeType.isMatchingSnapshotId(oldNode, newValue));
|
|
4599
4679
|
}
|
|
4600
4680
|
/**
|
|
@@ -4609,25 +4689,59 @@ function isArrayType(type) {
|
|
|
4609
4689
|
|
|
4610
4690
|
const PRE_PROCESS_SNAPSHOT = "preProcessSnapshot";
|
|
4611
4691
|
const POST_PROCESS_SNAPSHOT = "postProcessSnapshot";
|
|
4692
|
+
function getPropObservable(storedValue, key) {
|
|
4693
|
+
return getAtom(storedValue, key);
|
|
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
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
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;
|
|
4626
4738
|
}
|
|
4627
|
-
|
|
4628
|
-
|
|
4739
|
+
}
|
|
4740
|
+
return identifierAttribute;
|
|
4741
|
+
}
|
|
4742
|
+
function toPropertiesObject(declaredProps) {
|
|
4629
4743
|
// loop through properties and ensures that all items are types
|
|
4630
|
-
return
|
|
4744
|
+
return Object.keys(declaredProps).reduce((props, key) => {
|
|
4631
4745
|
// warn if user intended a HOOK
|
|
4632
4746
|
if (key in Hook) {
|
|
4633
4747
|
throw fail(`Hook '${key}' was defined as property. Hooks should be defined as part of the actions`);
|
|
@@ -4667,7 +4781,10 @@ function toPropertiesObject(declaredProps) {
|
|
|
4667
4781
|
throw fail(`Invalid type definition for property '${key}', cannot infer a type from a value like '${value}' (${typeof value})`);
|
|
4668
4782
|
}
|
|
4669
4783
|
return props;
|
|
4670
|
-
},
|
|
4784
|
+
},
|
|
4785
|
+
// seeded with the raw declaration; every key is either replaced by its
|
|
4786
|
+
// converted type below, left alone because it already is one, or throws
|
|
4787
|
+
{ ...declaredProps });
|
|
4671
4788
|
}
|
|
4672
4789
|
/**
|
|
4673
4790
|
* @internal
|
|
@@ -4687,14 +4804,29 @@ class ModelType extends ComplexType {
|
|
|
4687
4804
|
// to check the first instance we finalize (see finalizeNewInstance)
|
|
4688
4805
|
duplicateKeysChecked = false;
|
|
4689
4806
|
constructor(opts) {
|
|
4690
|
-
|
|
4691
|
-
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);
|
|
4692
4823
|
if (opts.propertiesArePreProcessed) {
|
|
4693
4824
|
// `properties` is a parent type's already-converted + frozen output
|
|
4694
4825
|
// (chain step with no new props), so its derived propertyNames and
|
|
4695
4826
|
// identifierAttribute are identical to the parent's — reuse them verbatim
|
|
4696
4827
|
// (cloneAndEnhance passed them in) instead of re-running Object.keys and
|
|
4697
4828
|
// the per-prop identifier scan, both O(props), on every step.
|
|
4829
|
+
this.properties = declaredProperties;
|
|
4698
4830
|
this.propertyNames = opts.propertyNames;
|
|
4699
4831
|
this.identifierAttribute = opts.identifierAttribute;
|
|
4700
4832
|
}
|
|
@@ -4703,26 +4835,16 @@ class ModelType extends ComplexType {
|
|
|
4703
4835
|
// means every value is already a type — the parent's converted bag merged
|
|
4704
4836
|
// with a freshly converted delta — so skip re-converting. Only raw entry
|
|
4705
4837
|
// points (`model()`) still need the full toPropertiesObject pass.
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
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);
|
|
4712
4846
|
}
|
|
4713
4847
|
}
|
|
4714
|
-
_getIdentifierAttribute() {
|
|
4715
|
-
let identifierAttribute = undefined;
|
|
4716
|
-
this.forAllProps((propName, propType) => {
|
|
4717
|
-
if (propType.flags & TypeFlags.Identifier) {
|
|
4718
|
-
if (identifierAttribute) {
|
|
4719
|
-
throw fail(`Cannot define property '${propName}' as object identifier, property '${identifierAttribute}' is already defined as identifier property`);
|
|
4720
|
-
}
|
|
4721
|
-
identifierAttribute = propName;
|
|
4722
|
-
}
|
|
4723
|
-
});
|
|
4724
|
-
return identifierAttribute;
|
|
4725
|
-
}
|
|
4726
4848
|
cloneAndEnhance(opts) {
|
|
4727
4849
|
// Fast path: a chain step that adds no new properties (.actions/.views/
|
|
4728
4850
|
// .volatile/.named/pre-postProcessor) reuses this type's already-converted +
|
|
@@ -4936,9 +5058,20 @@ class ModelType extends ComplexType {
|
|
|
4936
5058
|
}
|
|
4937
5059
|
finalizeNewInstance(node, instance) {
|
|
4938
5060
|
addHiddenFinalProp(instance, "toString", objectTypeToString);
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
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
|
+
}
|
|
4942
5075
|
this.initializers.reduce((self, fn) => fn(self), instance);
|
|
4943
5076
|
// views, actions and volatile share the instance namespace with properties,
|
|
4944
5077
|
// so a view/action reusing a property name silently clobbers that property's
|
|
@@ -4949,7 +5082,8 @@ class ModelType extends ComplexType {
|
|
|
4949
5082
|
// success so a broken type keeps throwing on every create.
|
|
4950
5083
|
if (!this.duplicateKeysChecked) {
|
|
4951
5084
|
this.forAllProps(name => {
|
|
4952
|
-
if (isComputedProp(instance, name) ||
|
|
5085
|
+
if (isComputedProp(instance, name) ||
|
|
5086
|
+
!isObservableProp(instance, name)) {
|
|
4953
5087
|
throw fail(`${name} property is declared twice`);
|
|
4954
5088
|
}
|
|
4955
5089
|
});
|
|
@@ -4989,33 +5123,64 @@ class ModelType extends ComplexType {
|
|
|
4989
5123
|
}
|
|
4990
5124
|
}
|
|
4991
5125
|
getChildren(node) {
|
|
4992
|
-
const
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
5126
|
+
const names = this.propertyNames;
|
|
5127
|
+
const storedValue = node.storedValue;
|
|
5128
|
+
const observables = getPropObservables(storedValue);
|
|
5129
|
+
const res = new Array(names.length);
|
|
5130
|
+
for (let i = 0; i < names.length; i++) {
|
|
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;
|
|
5139
|
+
}
|
|
4996
5140
|
return res;
|
|
4997
5141
|
}
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5142
|
+
/**
|
|
5143
|
+
* Same as {@link getChildNode} but for a key already known to be a declared
|
|
5144
|
+
* property (callers iterating `propertyNames`), so the membership guard is
|
|
5145
|
+
* skipped.
|
|
5146
|
+
*/
|
|
5147
|
+
getPropertyNode(node, key) {
|
|
5148
|
+
const childNode = getPropObservable(node.storedValue, key).raw();
|
|
5004
5149
|
if (!childNode) {
|
|
5005
5150
|
throw fail(`Node not available for property ${key}`);
|
|
5006
5151
|
}
|
|
5007
5152
|
return childNode;
|
|
5008
5153
|
}
|
|
5154
|
+
getChildNode(node, key) {
|
|
5155
|
+
if (!(key in this.properties)) {
|
|
5156
|
+
throw fail(`Not a value property: ${key}`);
|
|
5157
|
+
}
|
|
5158
|
+
return this.getPropertyNode(node, key);
|
|
5159
|
+
}
|
|
5009
5160
|
getSnapshot(node, applyPostProcess = true) {
|
|
5010
5161
|
const res = {};
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5162
|
+
const storedValue = node.storedValue;
|
|
5163
|
+
const properties = this.properties;
|
|
5164
|
+
const observables = getPropObservables(storedValue);
|
|
5165
|
+
for (const name of this.propertyNames) {
|
|
5166
|
+
// One mobx lookup serves both purposes: reportObserved so the snapshot
|
|
5167
|
+
// computed recomputes when the child is reassigned (raw() below does not
|
|
5168
|
+
// track), and raw() to read the child node. Going through getChildNode
|
|
5169
|
+
// would repeat the same lookup for every property.
|
|
5170
|
+
const observable = observables
|
|
5171
|
+
? observables.get(name)
|
|
5172
|
+
: getPropObservable(storedValue, name);
|
|
5173
|
+
observable?.reportObserved();
|
|
5174
|
+
const childNode = observable?.raw();
|
|
5175
|
+
if (!childNode) {
|
|
5176
|
+
throw fail(`Node not available for property ${name}`);
|
|
5177
|
+
}
|
|
5178
|
+
const snapshot = childNode.snapshot;
|
|
5014
5179
|
// strip-default optionals omit their key when equal to the default
|
|
5015
|
-
if (!shouldStripChildFromSnapshot(
|
|
5180
|
+
if (!shouldStripChildFromSnapshot(properties[name], snapshot)) {
|
|
5016
5181
|
res[name] = snapshot;
|
|
5017
5182
|
}
|
|
5018
|
-
}
|
|
5183
|
+
}
|
|
5019
5184
|
if (applyPostProcess) {
|
|
5020
5185
|
return this.applySnapshotPostProcessor(res);
|
|
5021
5186
|
}
|
|
@@ -5079,7 +5244,10 @@ class ModelType extends ComplexType {
|
|
|
5079
5244
|
return typeCheckSuccess();
|
|
5080
5245
|
}
|
|
5081
5246
|
forAllProps(fn) {
|
|
5082
|
-
|
|
5247
|
+
const properties = this.properties;
|
|
5248
|
+
for (const key of this.propertyNames) {
|
|
5249
|
+
fn(key, properties[key]);
|
|
5250
|
+
}
|
|
5083
5251
|
}
|
|
5084
5252
|
describe() {
|
|
5085
5253
|
// optimization: cache
|
|
@@ -5353,6 +5521,8 @@ function isPrimitiveType(type) {
|
|
|
5353
5521
|
(TypeFlags.String |
|
|
5354
5522
|
TypeFlags.Number |
|
|
5355
5523
|
TypeFlags.Integer |
|
|
5524
|
+
TypeFlags.Float |
|
|
5525
|
+
TypeFlags.Finite |
|
|
5356
5526
|
TypeFlags.Boolean |
|
|
5357
5527
|
TypeFlags.Date)) >
|
|
5358
5528
|
0);
|
|
@@ -5489,7 +5659,7 @@ function refinement(...args) {
|
|
|
5489
5659
|
* @returns
|
|
5490
5660
|
*/
|
|
5491
5661
|
function isRefinementType(type) {
|
|
5492
|
-
return (type.flags & TypeFlags.Refinement) > 0;
|
|
5662
|
+
return isType(type) && (type.flags & TypeFlags.Refinement) > 0;
|
|
5493
5663
|
}
|
|
5494
5664
|
|
|
5495
5665
|
/**
|
|
@@ -5530,10 +5700,23 @@ function enumeration(name, options) {
|
|
|
5530
5700
|
// the scoping never engages and every failure prints every member's full
|
|
5531
5701
|
// structure. Wrappers expose their child as `_subtype` (optional/refinement/
|
|
5532
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();
|
|
5533
5708
|
function resolveModelType(type) {
|
|
5709
|
+
if (!type) {
|
|
5710
|
+
return undefined;
|
|
5711
|
+
}
|
|
5712
|
+
const cached = resolvedModelTypes.get(type);
|
|
5713
|
+
if (cached) {
|
|
5714
|
+
return cached;
|
|
5715
|
+
}
|
|
5534
5716
|
let current = type;
|
|
5535
5717
|
for (let depth = 0; current && depth < 20; depth++) {
|
|
5536
5718
|
if (current instanceof ModelType) {
|
|
5719
|
+
resolvedModelTypes.set(type, current);
|
|
5537
5720
|
return current;
|
|
5538
5721
|
}
|
|
5539
5722
|
const wrapper = current;
|
|
@@ -5547,26 +5730,51 @@ function resolveModelType(type) {
|
|
|
5547
5730
|
*/
|
|
5548
5731
|
class Union extends BaseType {
|
|
5549
5732
|
_types;
|
|
5550
|
-
|
|
5551
|
-
|
|
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.
|
|
5552
5748
|
get flags() {
|
|
5749
|
+
const cached = this._flags;
|
|
5750
|
+
if (cached !== undefined) {
|
|
5751
|
+
return cached;
|
|
5752
|
+
}
|
|
5553
5753
|
let result = TypeFlags.Union;
|
|
5554
|
-
this._types
|
|
5754
|
+
for (const type of this._types) {
|
|
5555
5755
|
result |= type.flags;
|
|
5556
|
-
}
|
|
5756
|
+
}
|
|
5757
|
+
if (!(result & TypeFlags.Late)) {
|
|
5758
|
+
this._flags = result;
|
|
5759
|
+
}
|
|
5557
5760
|
return result;
|
|
5558
5761
|
}
|
|
5559
|
-
|
|
5560
|
-
|
|
5762
|
+
computeName() {
|
|
5763
|
+
return `(${this._types.map(type => type.name).join(" | ")})`;
|
|
5764
|
+
}
|
|
5765
|
+
constructor(_types, options) {
|
|
5766
|
+
super();
|
|
5561
5767
|
this._types = _types;
|
|
5562
|
-
options
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
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
|
+
}
|
|
5570
5778
|
}
|
|
5571
5779
|
}
|
|
5572
5780
|
isAssignableFrom(type) {
|
|
@@ -5617,14 +5825,6 @@ class Union extends BaseType {
|
|
|
5617
5825
|
}
|
|
5618
5826
|
return `${baseWithDiscriminator}:\n ${formatValidationErrorLines(errors).join("\n ")}`;
|
|
5619
5827
|
}
|
|
5620
|
-
// Memoizes the discriminator -> member scan below. Union membership is fixed
|
|
5621
|
-
// at construction, so the result for a given `type` string never changes.
|
|
5622
|
-
// Without this, validating a config with many elements drawn from a wide
|
|
5623
|
-
// pluggable union (e.g. jbrowse's 30+ track/adapter types) re-scans every
|
|
5624
|
-
// member — and calls resolveModelType + literal.is() on each — once per
|
|
5625
|
-
// element. With it, each distinct discriminator scans once; the rest are
|
|
5626
|
-
// O(1) map hits. `undefined` (no match OR ambiguous) is cached too.
|
|
5627
|
-
_discriminatorCache;
|
|
5628
5828
|
_findCandidateByTypeDiscriminator(discriminator) {
|
|
5629
5829
|
const cache = (this._discriminatorCache ??= new Map());
|
|
5630
5830
|
if (cache.has(discriminator)) {
|
|
@@ -5659,11 +5859,6 @@ class Union extends BaseType {
|
|
|
5659
5859
|
}
|
|
5660
5860
|
return found;
|
|
5661
5861
|
}
|
|
5662
|
-
// True when every member resolves to a model carrying a literal `type`
|
|
5663
|
-
// discriminator — i.e. a fully discriminated union, where a snapshot's `type`
|
|
5664
|
-
// uniquely identifies the intended member and no untagged catch-all member
|
|
5665
|
-
// could also accept it. Cached: membership is fixed at construction.
|
|
5666
|
-
_allMembersDiscriminated;
|
|
5667
5862
|
allMembersDiscriminated() {
|
|
5668
5863
|
if (this._allMembersDiscriminated === undefined) {
|
|
5669
5864
|
this._allMembersDiscriminated = this._types.every(t => {
|
|
@@ -5695,13 +5890,9 @@ class Union extends BaseType {
|
|
|
5695
5890
|
if (reconcileCurrentType.is(value)) {
|
|
5696
5891
|
return reconcileCurrentType;
|
|
5697
5892
|
}
|
|
5698
|
-
return this._types
|
|
5699
|
-
.filter(t => t !== reconcileCurrentType)
|
|
5700
|
-
.find(type => type.is(value));
|
|
5701
|
-
}
|
|
5702
|
-
else {
|
|
5703
|
-
return this._types.find(type => type.is(value));
|
|
5893
|
+
return this._types.find(type => type !== reconcileCurrentType && type.is(value));
|
|
5704
5894
|
}
|
|
5895
|
+
return this._types.find(type => type.is(value));
|
|
5705
5896
|
}
|
|
5706
5897
|
tryQuickMatch(value, reconcileCurrentType) {
|
|
5707
5898
|
// state tree nodes need full type compatibility checking
|
|
@@ -5713,15 +5904,17 @@ class Union extends BaseType {
|
|
|
5713
5904
|
if (!isPlainObject(value)) {
|
|
5714
5905
|
return this.tryMatchPrimitive(value);
|
|
5715
5906
|
}
|
|
5716
|
-
// for objects, try structural matching against model types
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5907
|
+
// for objects, try structural matching against model types, preferring the
|
|
5908
|
+
// reconciliation type (checked first, then skipped in the main pass) so a
|
|
5909
|
+
// reconcile keeps its current member when it still fits — without building
|
|
5910
|
+
// the reordered candidate list this used to allocate on every call
|
|
5911
|
+
if (reconcileCurrentType &&
|
|
5912
|
+
this.snapshotLooksLikeType(value, reconcileCurrentType)) {
|
|
5913
|
+
return reconcileCurrentType;
|
|
5914
|
+
}
|
|
5915
|
+
for (const type of this._types) {
|
|
5916
|
+
if (type !== reconcileCurrentType &&
|
|
5917
|
+
this.snapshotLooksLikeType(value, type)) {
|
|
5725
5918
|
return type;
|
|
5726
5919
|
}
|
|
5727
5920
|
}
|
|
@@ -5763,18 +5956,20 @@ class Union extends BaseType {
|
|
|
5763
5956
|
// use cached propertyNames from ModelType instead of Object.keys()
|
|
5764
5957
|
for (const key of model.propertyNames) {
|
|
5765
5958
|
const propType = props[key];
|
|
5766
|
-
|
|
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;
|
|
5767
5962
|
const propValue = value[key];
|
|
5768
5963
|
// check required properties exist and are not undefined
|
|
5769
5964
|
// (unless the type accepts undefined, which Optional types do)
|
|
5770
|
-
if (!
|
|
5965
|
+
if (!(flags & TypeFlags.Optional)) {
|
|
5771
5966
|
if (!(key in value) || propValue === undefined) {
|
|
5772
5967
|
return false;
|
|
5773
5968
|
}
|
|
5774
5969
|
}
|
|
5775
5970
|
// for literal types, verify the value matches exactly
|
|
5776
5971
|
// this is critical for discriminated unions
|
|
5777
|
-
if (
|
|
5972
|
+
if (flags & TypeFlags.Literal) {
|
|
5778
5973
|
if (!propType.is(propValue)) {
|
|
5779
5974
|
return false;
|
|
5780
5975
|
}
|
|
@@ -5836,12 +6031,20 @@ class Union extends BaseType {
|
|
|
5836
6031
|
if (applicableTypes === 1) {
|
|
5837
6032
|
return typeCheckSuccess();
|
|
5838
6033
|
}
|
|
5839
|
-
return typeCheckFailure(context, value, "No type is applicable for the union").concat(
|
|
6034
|
+
return typeCheckFailure(context, value, "No type is applicable for the union").concat(allErrors.flat());
|
|
5840
6035
|
}
|
|
5841
6036
|
getSubTypes() {
|
|
5842
6037
|
return this._types;
|
|
5843
6038
|
}
|
|
5844
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
|
+
});
|
|
5845
6048
|
/**
|
|
5846
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`.
|
|
5847
6050
|
*
|
|
@@ -5849,12 +6052,18 @@ class Union extends BaseType {
|
|
|
5849
6052
|
* @param otherTypes
|
|
5850
6053
|
* @returns
|
|
5851
6054
|
*/
|
|
5852
|
-
function union(
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
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
|
|
5858
6067
|
// check all options
|
|
5859
6068
|
if (devMode()) {
|
|
5860
6069
|
if (options) {
|
|
@@ -5864,7 +6073,7 @@ function union(optionsOrType, ...otherTypes) {
|
|
|
5864
6073
|
assertIsType(type, options ? i + 2 : i + 1);
|
|
5865
6074
|
});
|
|
5866
6075
|
}
|
|
5867
|
-
return new Union(
|
|
6076
|
+
return new Union(types, options);
|
|
5868
6077
|
}
|
|
5869
6078
|
/**
|
|
5870
6079
|
* Returns if a given value represents a union type.
|
|
@@ -5873,7 +6082,7 @@ function union(optionsOrType, ...otherTypes) {
|
|
|
5873
6082
|
* @returns
|
|
5874
6083
|
*/
|
|
5875
6084
|
function isUnionType(type) {
|
|
5876
|
-
return (type.flags & TypeFlags.Union) > 0;
|
|
6085
|
+
return isType(type) && (type.flags & TypeFlags.Union) > 0;
|
|
5877
6086
|
}
|
|
5878
6087
|
/**
|
|
5879
6088
|
* Returns the member types of a union.
|
|
@@ -5911,15 +6120,31 @@ class OptionalValue extends BaseType {
|
|
|
5911
6120
|
_subtype;
|
|
5912
6121
|
_defaultValue;
|
|
5913
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"
|
|
5914
6126
|
get flags() {
|
|
5915
|
-
|
|
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;
|
|
5916
6136
|
}
|
|
5917
6137
|
constructor(_subtype, _defaultValue, optionalValues) {
|
|
5918
|
-
|
|
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();
|
|
5919
6141
|
this._subtype = _subtype;
|
|
5920
6142
|
this._defaultValue = _defaultValue;
|
|
5921
6143
|
this.optionalValues = optionalValues;
|
|
5922
6144
|
}
|
|
6145
|
+
computeName() {
|
|
6146
|
+
return this._subtype.name;
|
|
6147
|
+
}
|
|
5923
6148
|
describe() {
|
|
5924
6149
|
return `${this._subtype.describe()}?`;
|
|
5925
6150
|
}
|
|
@@ -5936,15 +6161,15 @@ class OptionalValue extends BaseType {
|
|
|
5936
6161
|
: this.getDefaultInstanceOrSnapshot(), parent, subpath);
|
|
5937
6162
|
}
|
|
5938
6163
|
getDefaultInstanceOrSnapshot() {
|
|
5939
|
-
const
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
// generator functions must always be rechecked just in case
|
|
5944
|
-
if (typeof this._defaultValue === "function") {
|
|
5945
|
-
typecheckInternal(this, defaultInstanceOrSnapshot);
|
|
6164
|
+
const defaultValue = this._defaultValue;
|
|
6165
|
+
if (typeof defaultValue !== "function") {
|
|
6166
|
+
// static values are already snapshots, checked once by types.optional
|
|
6167
|
+
return defaultValue;
|
|
5946
6168
|
}
|
|
5947
|
-
|
|
6169
|
+
const generated = defaultValue();
|
|
6170
|
+
// generator functions must always be rechecked just in case
|
|
6171
|
+
typecheckInternal(this, generated);
|
|
6172
|
+
return generated;
|
|
5948
6173
|
}
|
|
5949
6174
|
isValidSnapshot(value, context) {
|
|
5950
6175
|
// defaulted values can be skipped
|
|
@@ -5962,8 +6187,11 @@ class OptionalValue extends BaseType {
|
|
|
5962
6187
|
}
|
|
5963
6188
|
}
|
|
5964
6189
|
function checkOptionalPreconditions(type, defaultValueOrFunction) {
|
|
5965
|
-
// make sure we never pass direct instances
|
|
5966
|
-
|
|
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" &&
|
|
5967
6195
|
isStateTreeNode(defaultValueOrFunction)) {
|
|
5968
6196
|
throw fail("default value cannot be an instance, pass a snapshot or a function that creates an instance/snapshot instead");
|
|
5969
6197
|
}
|
|
@@ -6036,33 +6264,70 @@ function isOptionalType(type) {
|
|
|
6036
6264
|
return isType(type) && (type.flags & TypeFlags.Optional) > 0;
|
|
6037
6265
|
}
|
|
6038
6266
|
/**
|
|
6039
|
-
* Compare a child snapshot to a stripped-default's reference snapshot
|
|
6040
|
-
*
|
|
6041
|
-
*
|
|
6267
|
+
* Compare a child snapshot to a stripped-default's reference snapshot: identity
|
|
6268
|
+
* for primitives, structural for objects/arrays.
|
|
6269
|
+
*
|
|
6270
|
+
* Walks the two snapshots in parallel and bails at the first difference. The
|
|
6271
|
+
* previous implementation compared `JSON.stringify(a) === JSON.stringify(b)`
|
|
6272
|
+
* behind a size guard, which is O(whole snapshot) on both sides — plus two
|
|
6273
|
+
* string allocations — even when the very first key differs. That is the normal
|
|
6274
|
+
* case for an identified sub-model: its snapshot has exactly the same shape as
|
|
6275
|
+
* the default and differs only in the identifier, so the size guard never fires
|
|
6276
|
+
* and every `getSnapshot` of the parent serialized both trees just to answer
|
|
6277
|
+
* "no".
|
|
6278
|
+
*
|
|
6279
|
+
* Snapshots are frozen, acyclic, JSON-ish plain data, so a plain recursive walk
|
|
6280
|
+
* is safe. Key order is compared too, matching what stringify comparison did:
|
|
6281
|
+
* the point is to answer "is this still the default", and the only unsafe answer
|
|
6282
|
+
* is a false positive (a key gets dropped from the snapshot that shouldn't be),
|
|
6283
|
+
* so where this can't reproduce stringify exactly — `{a: undefined}` vs
|
|
6284
|
+
* `{b: undefined}`, or `NaN` nested in a frozen value, both of which stringify
|
|
6285
|
+
* flattened into equal text — it errs toward "not equal" and simply keeps the
|
|
6286
|
+
* key. Neither shape is reachable from a model snapshot, whose key set and order
|
|
6287
|
+
* are fixed by its type.
|
|
6042
6288
|
*/
|
|
6043
6289
|
function defaultSnapshotEquals(a, b) {
|
|
6044
6290
|
if (a === b) {
|
|
6045
6291
|
return true;
|
|
6046
6292
|
}
|
|
6047
|
-
if (typeof a
|
|
6048
|
-
a
|
|
6049
|
-
typeof b
|
|
6050
|
-
b
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6293
|
+
if (typeof a !== "object" ||
|
|
6294
|
+
a === null ||
|
|
6295
|
+
typeof b !== "object" ||
|
|
6296
|
+
b === null) {
|
|
6297
|
+
return false;
|
|
6298
|
+
}
|
|
6299
|
+
const aIsArray = Array.isArray(a);
|
|
6300
|
+
if (aIsArray !== Array.isArray(b)) {
|
|
6301
|
+
return false;
|
|
6302
|
+
}
|
|
6303
|
+
if (aIsArray) {
|
|
6304
|
+
const arrayA = a;
|
|
6305
|
+
const arrayB = b;
|
|
6306
|
+
if (arrayA.length !== arrayB.length) {
|
|
6056
6307
|
return false;
|
|
6057
6308
|
}
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6309
|
+
for (let i = 0; i < arrayA.length; i++) {
|
|
6310
|
+
if (!defaultSnapshotEquals(arrayA[i], arrayB[i])) {
|
|
6311
|
+
return false;
|
|
6312
|
+
}
|
|
6313
|
+
}
|
|
6314
|
+
return true;
|
|
6315
|
+
}
|
|
6316
|
+
const objectA = a;
|
|
6317
|
+
const objectB = b;
|
|
6318
|
+
const keysA = Object.keys(objectA);
|
|
6319
|
+
const keysB = Object.keys(objectB);
|
|
6320
|
+
if (keysA.length !== keysB.length) {
|
|
6321
|
+
return false;
|
|
6322
|
+
}
|
|
6323
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
6324
|
+
const key = keysA[i];
|
|
6325
|
+
if (key !== keysB[i] ||
|
|
6326
|
+
!defaultSnapshotEquals(objectA[key], objectB[key])) {
|
|
6061
6327
|
return false;
|
|
6062
6328
|
}
|
|
6063
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
6064
6329
|
}
|
|
6065
|
-
return
|
|
6330
|
+
return true;
|
|
6066
6331
|
}
|
|
6067
6332
|
/**
|
|
6068
6333
|
* An optional type that additionally omits its key from a parent model's
|
|
@@ -6076,7 +6341,6 @@ function defaultSnapshotEquals(a, b) {
|
|
|
6076
6341
|
* @internal
|
|
6077
6342
|
*/
|
|
6078
6343
|
class StripDefaultValue extends OptionalValue {
|
|
6079
|
-
_defaultSnapshot;
|
|
6080
6344
|
shouldStripFromSnapshot(snapshot) {
|
|
6081
6345
|
if (!this._defaultSnapshot) {
|
|
6082
6346
|
// instantiate the subtype detached with the default and read the node's
|
|
@@ -6088,6 +6352,9 @@ class StripDefaultValue extends OptionalValue {
|
|
|
6088
6352
|
return defaultSnapshotEquals(snapshot, this._defaultSnapshot.value);
|
|
6089
6353
|
}
|
|
6090
6354
|
}
|
|
6355
|
+
Object.assign(StripDefaultValue.prototype, {
|
|
6356
|
+
_defaultSnapshot: undefined
|
|
6357
|
+
});
|
|
6091
6358
|
/**
|
|
6092
6359
|
* Whether `type` is a strip-default optional whose current child `snapshot`
|
|
6093
6360
|
* equals its default and should therefore be omitted from the parent model's
|
|
@@ -6290,7 +6557,13 @@ class Lazy extends SimpleType {
|
|
|
6290
6557
|
}
|
|
6291
6558
|
const node = createScalarNode(this, parent, subpath, environment, deepFreeze(value));
|
|
6292
6559
|
this.pendingNodeList.push(node);
|
|
6293
|
-
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
|
+
});
|
|
6294
6567
|
return node;
|
|
6295
6568
|
}
|
|
6296
6569
|
isValidSnapshot(value, context) {
|
|
@@ -6319,9 +6592,12 @@ class Frozen extends SimpleType {
|
|
|
6319
6592
|
subType;
|
|
6320
6593
|
flags = TypeFlags.Frozen;
|
|
6321
6594
|
constructor(subType) {
|
|
6322
|
-
super(subType ?
|
|
6595
|
+
super(subType ? undefined : "frozen");
|
|
6323
6596
|
this.subType = subType;
|
|
6324
6597
|
}
|
|
6598
|
+
computeName() {
|
|
6599
|
+
return `frozen(${this.subType.name})`;
|
|
6600
|
+
}
|
|
6325
6601
|
describe() {
|
|
6326
6602
|
return "<any immutable value>";
|
|
6327
6603
|
}
|
|
@@ -6477,10 +6753,13 @@ class BaseReferenceType extends SimpleType {
|
|
|
6477
6753
|
onInvalidated;
|
|
6478
6754
|
flags = TypeFlags.Reference;
|
|
6479
6755
|
constructor(targetType, onInvalidated) {
|
|
6480
|
-
super(
|
|
6756
|
+
super();
|
|
6481
6757
|
this.targetType = targetType;
|
|
6482
6758
|
this.onInvalidated = onInvalidated;
|
|
6483
6759
|
}
|
|
6760
|
+
computeName() {
|
|
6761
|
+
return `reference(${this.targetType.name})`;
|
|
6762
|
+
}
|
|
6484
6763
|
describe() {
|
|
6485
6764
|
return this.name;
|
|
6486
6765
|
}
|
|
@@ -6743,7 +7022,7 @@ function reference(subType, options) {
|
|
|
6743
7022
|
* @returns
|
|
6744
7023
|
*/
|
|
6745
7024
|
function isReferenceType(type) {
|
|
6746
|
-
return (type.flags & TypeFlags.Reference) > 0;
|
|
7025
|
+
return isType(type) && (type.flags & TypeFlags.Reference) > 0;
|
|
6747
7026
|
}
|
|
6748
7027
|
/**
|
|
6749
7028
|
* `types.safeReference` - A safe reference is like a standard reference, except that it accepts the undefined value by default
|
|
@@ -6814,7 +7093,6 @@ class BaseIdentifierType extends SimpleType {
|
|
|
6814
7093
|
* @hidden
|
|
6815
7094
|
*/
|
|
6816
7095
|
class IdentifierType extends BaseIdentifierType {
|
|
6817
|
-
flags = TypeFlags.Identifier;
|
|
6818
7096
|
constructor() {
|
|
6819
7097
|
super(`identifier`, "string");
|
|
6820
7098
|
}
|
|
@@ -7012,11 +7290,14 @@ class Resilient extends BaseType {
|
|
|
7012
7290
|
return this._subtype.flags;
|
|
7013
7291
|
}
|
|
7014
7292
|
constructor(_subtype, _fallbackType, _createFallbackSnapshot) {
|
|
7015
|
-
super(
|
|
7293
|
+
super();
|
|
7016
7294
|
this._subtype = _subtype;
|
|
7017
7295
|
this._fallbackType = _fallbackType;
|
|
7018
7296
|
this._createFallbackSnapshot = _createFallbackSnapshot;
|
|
7019
7297
|
}
|
|
7298
|
+
computeName() {
|
|
7299
|
+
return `resilient(${this._subtype.name})`;
|
|
7300
|
+
}
|
|
7020
7301
|
describe() {
|
|
7021
7302
|
return `resilient(${this._subtype.describe()})`;
|
|
7022
7303
|
}
|
|
@@ -7051,12 +7332,20 @@ class Resilient extends BaseType {
|
|
|
7051
7332
|
return this._subtype.reconcile(current, newValue, parent, subpath);
|
|
7052
7333
|
}
|
|
7053
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;
|
|
7054
7338
|
try {
|
|
7055
|
-
|
|
7339
|
+
recovered = this._subtype.instantiate(parent, subpath, undefined, newValue);
|
|
7056
7340
|
}
|
|
7057
7341
|
catch (e) {
|
|
7058
7342
|
return this._fallbackType.reconcile(current, this._createFallbackSnapshot(e, newValue), parent, subpath);
|
|
7059
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;
|
|
7060
7349
|
}
|
|
7061
7350
|
try {
|
|
7062
7351
|
return this._subtype.reconcile(current, newValue, parent, subpath);
|