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