@jbrowse/mobx-state-tree 5.13.0 → 6.1.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 +1 -3
- package/dist/mobx-state-tree.cjs +182 -122
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +183 -123
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +3 -3
package/dist/mobx-state-tree.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isComputedProp, isObservableProp, createAtom, action, computed, _allowStateChangesInsideComputed, reaction, runInAction, observable, values, entries, isObservableArray,
|
|
1
|
+
import { isComputedProp, isObservableProp, createAtom, action, computed, _allowStateChangesInsideComputed, reaction, runInAction, observable, values, entries, isObservableArray, isObservableObject, defineProperty as defineProperty$1, _interceptReads, intercept, observe, ObservableMap, _getAdministration, set, makeObservable, getAtom, when } from 'mobx';
|
|
2
2
|
|
|
3
3
|
let livelinessChecking = "warn";
|
|
4
4
|
/**
|
|
@@ -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() {
|
|
@@ -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;
|
|
@@ -1808,9 +1804,6 @@ BaseType.prototype.create = action(BaseType.prototype.create);
|
|
|
1808
1804
|
*/
|
|
1809
1805
|
class ComplexType extends BaseType {
|
|
1810
1806
|
identifierAttribute;
|
|
1811
|
-
constructor(name) {
|
|
1812
|
-
super(name);
|
|
1813
|
-
}
|
|
1814
1807
|
create(snapshot = this.getDefaultSnapshot(), environment) {
|
|
1815
1808
|
return super.create(snapshot, environment);
|
|
1816
1809
|
}
|
|
@@ -2712,13 +2705,6 @@ function typeCheckFailure(context, value, message) {
|
|
|
2712
2705
|
// Clone context since it may be mutated after this error is created
|
|
2713
2706
|
return [{ context: context.slice(), value, message }];
|
|
2714
2707
|
}
|
|
2715
|
-
/**
|
|
2716
|
-
* @internal
|
|
2717
|
-
* @hidden
|
|
2718
|
-
*/
|
|
2719
|
-
function flattenTypeErrors(errors) {
|
|
2720
|
-
return errors.flat();
|
|
2721
|
-
}
|
|
2722
2708
|
// TODO; doublecheck: typecheck should only needed to be invoked from: type.create and array / map / value.property will change
|
|
2723
2709
|
/**
|
|
2724
2710
|
* @internal
|
|
@@ -2780,7 +2766,6 @@ class IdentifierCache {
|
|
|
2780
2766
|
// last time the cache (array) for a given time changed
|
|
2781
2767
|
// n.b. it is not really the time, but just an integer that gets increased after each modification to the array
|
|
2782
2768
|
lastCacheModificationPerId = observable.map();
|
|
2783
|
-
constructor() { }
|
|
2784
2769
|
updateLastCacheModificationPerId(identifier) {
|
|
2785
2770
|
const lcm = this.lastCacheModificationPerId.get(identifier);
|
|
2786
2771
|
// we start at 1 since 0 means no update since cache creation
|
|
@@ -2821,7 +2806,7 @@ class IdentifierCache {
|
|
|
2821
2806
|
if (!set.length) {
|
|
2822
2807
|
this.cache.delete(id);
|
|
2823
2808
|
}
|
|
2824
|
-
this.updateLastCacheModificationPerId(
|
|
2809
|
+
this.updateLastCacheModificationPerId(id);
|
|
2825
2810
|
}
|
|
2826
2811
|
}
|
|
2827
2812
|
}
|
|
@@ -3053,15 +3038,8 @@ function convertChildNodesToArray(childNodes) {
|
|
|
3053
3038
|
if (!childNodes) {
|
|
3054
3039
|
return EMPTY_ARRAY;
|
|
3055
3040
|
}
|
|
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;
|
|
3041
|
+
const result = Object.values(childNodes);
|
|
3042
|
+
return result.length ? result : EMPTY_ARRAY;
|
|
3065
3043
|
}
|
|
3066
3044
|
|
|
3067
3045
|
const plainObjectString = Object.toString();
|
|
@@ -3079,9 +3057,7 @@ const EMPTY_OBJECT = Object.freeze({});
|
|
|
3079
3057
|
* @internal
|
|
3080
3058
|
* @hidden
|
|
3081
3059
|
*/
|
|
3082
|
-
const mobxShallow =
|
|
3083
|
-
? { deep: false }
|
|
3084
|
-
: { deep: false, proxy: false };
|
|
3060
|
+
const mobxShallow = { deep: false };
|
|
3085
3061
|
Object.freeze(mobxShallow);
|
|
3086
3062
|
/**
|
|
3087
3063
|
* @internal
|
|
@@ -3279,10 +3255,19 @@ class EventHandler {
|
|
|
3279
3255
|
this.handlers.length = 0;
|
|
3280
3256
|
}
|
|
3281
3257
|
emit(...args) {
|
|
3282
|
-
|
|
3283
|
-
// and
|
|
3284
|
-
|
|
3285
|
-
|
|
3258
|
+
const handlers = this.handlers;
|
|
3259
|
+
// 0 and 1 handlers are by far the common cases (a patch/snapshot listener, a
|
|
3260
|
+
// disposer) and need no copy: with a single handler, calling the value we
|
|
3261
|
+
// already read is exactly what iterating a copy of it would do.
|
|
3262
|
+
if (handlers.length <= 1) {
|
|
3263
|
+
handlers[0]?.(...args);
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
// otherwise iterate a copy so (un)registrations during emit don't disturb
|
|
3267
|
+
// this pass and reentrant emits stay correct
|
|
3268
|
+
for (const f of handlers.slice()) {
|
|
3269
|
+
f(...args);
|
|
3270
|
+
}
|
|
3286
3271
|
}
|
|
3287
3272
|
}
|
|
3288
3273
|
/**
|
|
@@ -3663,6 +3648,11 @@ function escapeJsonPath(path) {
|
|
|
3663
3648
|
* Unescape slashes and backslashes.
|
|
3664
3649
|
*/
|
|
3665
3650
|
function unescapeJsonPath(path) {
|
|
3651
|
+
// every escape sequence starts with `~`, so bail before the two regex passes
|
|
3652
|
+
// for the overwhelmingly common unescaped part (mirrors escapeJsonPath above)
|
|
3653
|
+
if (!path.includes("~")) {
|
|
3654
|
+
return path;
|
|
3655
|
+
}
|
|
3666
3656
|
return path.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3667
3657
|
}
|
|
3668
3658
|
/**
|
|
@@ -3909,7 +3899,11 @@ var MapIdentifierMode;
|
|
|
3909
3899
|
})(MapIdentifierMode || (MapIdentifierMode = {}));
|
|
3910
3900
|
class MSTMap extends ObservableMap {
|
|
3911
3901
|
constructor(initialData, name) {
|
|
3912
|
-
|
|
3902
|
+
// mobx's default (deep) enhancer. The old `observable.ref.enhancer` arg
|
|
3903
|
+
// read undefined ever since mobx 6 renamed the field to `options_.enhancer`,
|
|
3904
|
+
// so deep has been the effective behavior all along; mobx 7 dropped
|
|
3905
|
+
// `observable.ref` entirely.
|
|
3906
|
+
super(initialData, undefined, name);
|
|
3913
3907
|
}
|
|
3914
3908
|
get(key) {
|
|
3915
3909
|
// maybe this is over-enthousiastic? normalize numeric keys to strings
|
|
@@ -4149,22 +4143,21 @@ class MapType extends ComplexType {
|
|
|
4149
4143
|
applySnapshot(node, snapshot) {
|
|
4150
4144
|
typecheckInternal(this, snapshot);
|
|
4151
4145
|
const target = node.storedValue;
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4146
|
+
// keys present before the apply, minus the ones the snapshot (re)sets — what
|
|
4147
|
+
// remains has to be deleted. A Set rather than a plain object: it needs no
|
|
4148
|
+
// sentinel values, no second pass to find the survivors, and it can hold keys
|
|
4149
|
+
// like "__proto__", which an object literal silently swallows.
|
|
4150
|
+
const staleKeys = new Set(target.keys());
|
|
4156
4151
|
if (snapshot) {
|
|
4157
4152
|
// Don't use target.replace, as it will throw away all existing items first
|
|
4158
4153
|
for (const key in snapshot) {
|
|
4159
4154
|
target.set(key, snapshot[key]);
|
|
4160
|
-
|
|
4155
|
+
staleKeys.delete(key);
|
|
4161
4156
|
}
|
|
4162
4157
|
}
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
}
|
|
4167
|
-
});
|
|
4158
|
+
for (const key of staleKeys) {
|
|
4159
|
+
target.delete(key);
|
|
4160
|
+
}
|
|
4168
4161
|
}
|
|
4169
4162
|
getChildType() {
|
|
4170
4163
|
return this._subType;
|
|
@@ -4304,7 +4297,7 @@ class ArrayType extends ComplexType {
|
|
|
4304
4297
|
if (change.newValue === change.object[change.index]) {
|
|
4305
4298
|
return null;
|
|
4306
4299
|
}
|
|
4307
|
-
const updatedNodes = reconcileArrayChildren(node, subType, [childNodes[change.index]], [change.newValue],
|
|
4300
|
+
const updatedNodes = reconcileArrayChildren(node, subType, [childNodes[change.index]], [change.newValue], change.index);
|
|
4308
4301
|
if (!updatedNodes) {
|
|
4309
4302
|
return null;
|
|
4310
4303
|
}
|
|
@@ -4314,7 +4307,7 @@ class ArrayType extends ComplexType {
|
|
|
4314
4307
|
case "splice":
|
|
4315
4308
|
{
|
|
4316
4309
|
const { index, removedCount, added } = change;
|
|
4317
|
-
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added,
|
|
4310
|
+
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added, index);
|
|
4318
4311
|
if (!addedNodes) {
|
|
4319
4312
|
return null;
|
|
4320
4313
|
}
|
|
@@ -4440,7 +4433,11 @@ function array(subtype) {
|
|
|
4440
4433
|
assertIsType(subtype, 1);
|
|
4441
4434
|
return new ArrayType(`${subtype.name}[]`, subtype);
|
|
4442
4435
|
}
|
|
4443
|
-
|
|
4436
|
+
/**
|
|
4437
|
+
* @param firstNewPath index the reconciled slice starts at; both call sites
|
|
4438
|
+
* hand over a contiguous run, so subpath `i` is simply `firstNewPath + i`
|
|
4439
|
+
*/
|
|
4440
|
+
function reconcileArrayChildren(parent, childType, oldNodes, newValues, firstNewPath) {
|
|
4444
4441
|
let nothingChanged = true;
|
|
4445
4442
|
// When the element type is a plain model with an identifier, index the old
|
|
4446
4443
|
// nodes by id so a moved / replaced child is matched in O(1) instead of the
|
|
@@ -4463,7 +4460,7 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
|
|
|
4463
4460
|
const hasNewNode = i <= newValues.length - 1;
|
|
4464
4461
|
const oldNode = oldNodes[i];
|
|
4465
4462
|
let newValue = hasNewNode ? newValues[i] : undefined;
|
|
4466
|
-
const newPath = `${
|
|
4463
|
+
const newPath = `${firstNewPath + i}`;
|
|
4467
4464
|
// for some reason, instead of newValue we got a node, fallback to the storedValue
|
|
4468
4465
|
// TODO: https://github.com/mobxjs/mobx-state-tree/issues/340#issuecomment-325581681
|
|
4469
4466
|
if (isNode(newValue)) {
|
|
@@ -4587,12 +4584,14 @@ function areSame(oldNode, newValue) {
|
|
|
4587
4584
|
if (!(oldNode instanceof ObjectNode)) {
|
|
4588
4585
|
return false;
|
|
4589
4586
|
}
|
|
4587
|
+
if (oldNode.identifier === null ||
|
|
4588
|
+
!oldNode.identifierAttribute ||
|
|
4589
|
+
!isPlainObject(newValue)) {
|
|
4590
|
+
return false;
|
|
4591
|
+
}
|
|
4590
4592
|
const oldNodeType = oldNode.getReconciliationType();
|
|
4591
4593
|
// new value is a snapshot with the correct identifier
|
|
4592
|
-
return (
|
|
4593
|
-
oldNode.identifierAttribute &&
|
|
4594
|
-
isPlainObject(newValue) &&
|
|
4595
|
-
oldNodeType.is(newValue) &&
|
|
4594
|
+
return (oldNodeType.is(newValue) &&
|
|
4596
4595
|
oldNodeType.isMatchingSnapshotId(oldNode, newValue));
|
|
4597
4596
|
}
|
|
4598
4597
|
/**
|
|
@@ -4607,6 +4606,9 @@ function isArrayType(type) {
|
|
|
4607
4606
|
|
|
4608
4607
|
const PRE_PROCESS_SNAPSHOT = "preProcessSnapshot";
|
|
4609
4608
|
const POST_PROCESS_SNAPSHOT = "postProcessSnapshot";
|
|
4609
|
+
function getPropObservable(storedValue, key) {
|
|
4610
|
+
return getAtom(storedValue, key);
|
|
4611
|
+
}
|
|
4610
4612
|
function objectTypeToString() {
|
|
4611
4613
|
return getStateTreeNode(this).toString();
|
|
4612
4614
|
}
|
|
@@ -4616,16 +4618,8 @@ const defaultObjectOptions = {
|
|
|
4616
4618
|
initializers: EMPTY_ARRAY
|
|
4617
4619
|
};
|
|
4618
4620
|
function toPropertiesObject(declaredProps) {
|
|
4619
|
-
const keysList = Object.keys(declaredProps);
|
|
4620
|
-
const alreadySeenKeys = new Set();
|
|
4621
|
-
keysList.forEach(key => {
|
|
4622
|
-
if (alreadySeenKeys.has(key)) {
|
|
4623
|
-
throw fail(`${key} is declared twice in the model. Model should not contain the same keys`);
|
|
4624
|
-
}
|
|
4625
|
-
alreadySeenKeys.add(key);
|
|
4626
|
-
});
|
|
4627
4621
|
// loop through properties and ensures that all items are types
|
|
4628
|
-
return
|
|
4622
|
+
return Object.keys(declaredProps).reduce((props, key) => {
|
|
4629
4623
|
// warn if user intended a HOOK
|
|
4630
4624
|
if (key in Hook) {
|
|
4631
4625
|
throw fail(`Hook '${key}' was defined as property. Hooks should be defined as part of the actions`);
|
|
@@ -4665,7 +4659,10 @@ function toPropertiesObject(declaredProps) {
|
|
|
4665
4659
|
throw fail(`Invalid type definition for property '${key}', cannot infer a type from a value like '${value}' (${typeof value})`);
|
|
4666
4660
|
}
|
|
4667
4661
|
return props;
|
|
4668
|
-
},
|
|
4662
|
+
},
|
|
4663
|
+
// seeded with the raw declaration; every key is either replaced by its
|
|
4664
|
+
// converted type below, left alone because it already is one, or throws
|
|
4665
|
+
{ ...declaredProps });
|
|
4669
4666
|
}
|
|
4670
4667
|
/**
|
|
4671
4668
|
* @internal
|
|
@@ -4947,7 +4944,8 @@ class ModelType extends ComplexType {
|
|
|
4947
4944
|
// success so a broken type keeps throwing on every create.
|
|
4948
4945
|
if (!this.duplicateKeysChecked) {
|
|
4949
4946
|
this.forAllProps(name => {
|
|
4950
|
-
if (isComputedProp(instance, name) ||
|
|
4947
|
+
if (isComputedProp(instance, name) ||
|
|
4948
|
+
!isObservableProp(instance, name)) {
|
|
4951
4949
|
throw fail(`${name} property is declared twice`);
|
|
4952
4950
|
}
|
|
4953
4951
|
});
|
|
@@ -4987,33 +4985,52 @@ class ModelType extends ComplexType {
|
|
|
4987
4985
|
}
|
|
4988
4986
|
}
|
|
4989
4987
|
getChildren(node) {
|
|
4990
|
-
const
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4988
|
+
const names = this.propertyNames;
|
|
4989
|
+
const res = new Array(names.length);
|
|
4990
|
+
for (let i = 0; i < names.length; i++) {
|
|
4991
|
+
res[i] = this.getPropertyNode(node, names[i]);
|
|
4992
|
+
}
|
|
4994
4993
|
return res;
|
|
4995
4994
|
}
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
4995
|
+
/**
|
|
4996
|
+
* Same as {@link getChildNode} but for a key already known to be a declared
|
|
4997
|
+
* property (callers iterating `propertyNames`), so the membership guard is
|
|
4998
|
+
* skipped.
|
|
4999
|
+
*/
|
|
5000
|
+
getPropertyNode(node, key) {
|
|
5001
|
+
const childNode = getPropObservable(node.storedValue, key).raw();
|
|
5002
5002
|
if (!childNode) {
|
|
5003
5003
|
throw fail(`Node not available for property ${key}`);
|
|
5004
5004
|
}
|
|
5005
5005
|
return childNode;
|
|
5006
5006
|
}
|
|
5007
|
+
getChildNode(node, key) {
|
|
5008
|
+
if (!(key in this.properties)) {
|
|
5009
|
+
throw fail(`Not a value property: ${key}`);
|
|
5010
|
+
}
|
|
5011
|
+
return this.getPropertyNode(node, key);
|
|
5012
|
+
}
|
|
5007
5013
|
getSnapshot(node, applyPostProcess = true) {
|
|
5008
5014
|
const res = {};
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5015
|
+
const storedValue = node.storedValue;
|
|
5016
|
+
const properties = this.properties;
|
|
5017
|
+
for (const name of this.propertyNames) {
|
|
5018
|
+
// One mobx lookup serves both purposes: reportObserved so the snapshot
|
|
5019
|
+
// computed recomputes when the child is reassigned (raw() below does not
|
|
5020
|
+
// track), and raw() to read the child node. Going through getChildNode
|
|
5021
|
+
// would repeat the same lookup for every property.
|
|
5022
|
+
const observable = getPropObservable(storedValue, name);
|
|
5023
|
+
observable.reportObserved();
|
|
5024
|
+
const childNode = observable.raw();
|
|
5025
|
+
if (!childNode) {
|
|
5026
|
+
throw fail(`Node not available for property ${name}`);
|
|
5027
|
+
}
|
|
5028
|
+
const snapshot = childNode.snapshot;
|
|
5012
5029
|
// strip-default optionals omit their key when equal to the default
|
|
5013
|
-
if (!shouldStripChildFromSnapshot(
|
|
5030
|
+
if (!shouldStripChildFromSnapshot(properties[name], snapshot)) {
|
|
5014
5031
|
res[name] = snapshot;
|
|
5015
5032
|
}
|
|
5016
|
-
}
|
|
5033
|
+
}
|
|
5017
5034
|
if (applyPostProcess) {
|
|
5018
5035
|
return this.applySnapshotPostProcessor(res);
|
|
5019
5036
|
}
|
|
@@ -5077,7 +5094,10 @@ class ModelType extends ComplexType {
|
|
|
5077
5094
|
return typeCheckSuccess();
|
|
5078
5095
|
}
|
|
5079
5096
|
forAllProps(fn) {
|
|
5080
|
-
|
|
5097
|
+
const properties = this.properties;
|
|
5098
|
+
for (const key of this.propertyNames) {
|
|
5099
|
+
fn(key, properties[key]);
|
|
5100
|
+
}
|
|
5081
5101
|
}
|
|
5082
5102
|
describe() {
|
|
5083
5103
|
// optimization: cache
|
|
@@ -5547,11 +5567,16 @@ class Union extends BaseType {
|
|
|
5547
5567
|
_types;
|
|
5548
5568
|
_dispatcher;
|
|
5549
5569
|
_eager = true;
|
|
5570
|
+
// Deliberately recomputed rather than memoized: a `types.late` member reports
|
|
5571
|
+
// 0 for its subtype until the definition resolves, so the fold is not stable
|
|
5572
|
+
// over the type's lifetime and a memo would need a carve-out for exactly the
|
|
5573
|
+
// case that motivates it. Measured at ~4% of union creation, which is not
|
|
5574
|
+
// worth another cache.
|
|
5550
5575
|
get flags() {
|
|
5551
5576
|
let result = TypeFlags.Union;
|
|
5552
|
-
this._types
|
|
5577
|
+
for (const type of this._types) {
|
|
5553
5578
|
result |= type.flags;
|
|
5554
|
-
}
|
|
5579
|
+
}
|
|
5555
5580
|
return result;
|
|
5556
5581
|
}
|
|
5557
5582
|
constructor(name, _types, options) {
|
|
@@ -5693,13 +5718,9 @@ class Union extends BaseType {
|
|
|
5693
5718
|
if (reconcileCurrentType.is(value)) {
|
|
5694
5719
|
return reconcileCurrentType;
|
|
5695
5720
|
}
|
|
5696
|
-
return this._types
|
|
5697
|
-
.filter(t => t !== reconcileCurrentType)
|
|
5698
|
-
.find(type => type.is(value));
|
|
5699
|
-
}
|
|
5700
|
-
else {
|
|
5701
|
-
return this._types.find(type => type.is(value));
|
|
5721
|
+
return this._types.find(type => type !== reconcileCurrentType && type.is(value));
|
|
5702
5722
|
}
|
|
5723
|
+
return this._types.find(type => type.is(value));
|
|
5703
5724
|
}
|
|
5704
5725
|
tryQuickMatch(value, reconcileCurrentType) {
|
|
5705
5726
|
// state tree nodes need full type compatibility checking
|
|
@@ -5711,15 +5732,17 @@ class Union extends BaseType {
|
|
|
5711
5732
|
if (!isPlainObject(value)) {
|
|
5712
5733
|
return this.tryMatchPrimitive(value);
|
|
5713
5734
|
}
|
|
5714
|
-
// for objects, try structural matching against model types
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5735
|
+
// for objects, try structural matching against model types, preferring the
|
|
5736
|
+
// reconciliation type (checked first, then skipped in the main pass) so a
|
|
5737
|
+
// reconcile keeps its current member when it still fits — without building
|
|
5738
|
+
// the reordered candidate list this used to allocate on every call
|
|
5739
|
+
if (reconcileCurrentType &&
|
|
5740
|
+
this.snapshotLooksLikeType(value, reconcileCurrentType)) {
|
|
5741
|
+
return reconcileCurrentType;
|
|
5742
|
+
}
|
|
5743
|
+
for (const type of this._types) {
|
|
5744
|
+
if (type !== reconcileCurrentType &&
|
|
5745
|
+
this.snapshotLooksLikeType(value, type)) {
|
|
5723
5746
|
return type;
|
|
5724
5747
|
}
|
|
5725
5748
|
}
|
|
@@ -5834,7 +5857,7 @@ class Union extends BaseType {
|
|
|
5834
5857
|
if (applicableTypes === 1) {
|
|
5835
5858
|
return typeCheckSuccess();
|
|
5836
5859
|
}
|
|
5837
|
-
return typeCheckFailure(context, value, "No type is applicable for the union").concat(
|
|
5860
|
+
return typeCheckFailure(context, value, "No type is applicable for the union").concat(allErrors.flat());
|
|
5838
5861
|
}
|
|
5839
5862
|
getSubTypes() {
|
|
5840
5863
|
return this._types;
|
|
@@ -5934,15 +5957,15 @@ class OptionalValue extends BaseType {
|
|
|
5934
5957
|
: this.getDefaultInstanceOrSnapshot(), parent, subpath);
|
|
5935
5958
|
}
|
|
5936
5959
|
getDefaultInstanceOrSnapshot() {
|
|
5937
|
-
const
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
// generator functions must always be rechecked just in case
|
|
5942
|
-
if (typeof this._defaultValue === "function") {
|
|
5943
|
-
typecheckInternal(this, defaultInstanceOrSnapshot);
|
|
5960
|
+
const defaultValue = this._defaultValue;
|
|
5961
|
+
if (typeof defaultValue !== "function") {
|
|
5962
|
+
// static values are already snapshots, checked once by types.optional
|
|
5963
|
+
return defaultValue;
|
|
5944
5964
|
}
|
|
5945
|
-
|
|
5965
|
+
const generated = defaultValue();
|
|
5966
|
+
// generator functions must always be rechecked just in case
|
|
5967
|
+
typecheckInternal(this, generated);
|
|
5968
|
+
return generated;
|
|
5946
5969
|
}
|
|
5947
5970
|
isValidSnapshot(value, context) {
|
|
5948
5971
|
// defaulted values can be skipped
|
|
@@ -6034,33 +6057,70 @@ function isOptionalType(type) {
|
|
|
6034
6057
|
return isType(type) && (type.flags & TypeFlags.Optional) > 0;
|
|
6035
6058
|
}
|
|
6036
6059
|
/**
|
|
6037
|
-
* Compare a child snapshot to a stripped-default's reference snapshot
|
|
6038
|
-
*
|
|
6039
|
-
*
|
|
6060
|
+
* Compare a child snapshot to a stripped-default's reference snapshot: identity
|
|
6061
|
+
* for primitives, structural for objects/arrays.
|
|
6062
|
+
*
|
|
6063
|
+
* Walks the two snapshots in parallel and bails at the first difference. The
|
|
6064
|
+
* previous implementation compared `JSON.stringify(a) === JSON.stringify(b)`
|
|
6065
|
+
* behind a size guard, which is O(whole snapshot) on both sides — plus two
|
|
6066
|
+
* string allocations — even when the very first key differs. That is the normal
|
|
6067
|
+
* case for an identified sub-model: its snapshot has exactly the same shape as
|
|
6068
|
+
* the default and differs only in the identifier, so the size guard never fires
|
|
6069
|
+
* and every `getSnapshot` of the parent serialized both trees just to answer
|
|
6070
|
+
* "no".
|
|
6071
|
+
*
|
|
6072
|
+
* Snapshots are frozen, acyclic, JSON-ish plain data, so a plain recursive walk
|
|
6073
|
+
* is safe. Key order is compared too, matching what stringify comparison did:
|
|
6074
|
+
* the point is to answer "is this still the default", and the only unsafe answer
|
|
6075
|
+
* is a false positive (a key gets dropped from the snapshot that shouldn't be),
|
|
6076
|
+
* so where this can't reproduce stringify exactly — `{a: undefined}` vs
|
|
6077
|
+
* `{b: undefined}`, or `NaN` nested in a frozen value, both of which stringify
|
|
6078
|
+
* flattened into equal text — it errs toward "not equal" and simply keeps the
|
|
6079
|
+
* key. Neither shape is reachable from a model snapshot, whose key set and order
|
|
6080
|
+
* are fixed by its type.
|
|
6040
6081
|
*/
|
|
6041
6082
|
function defaultSnapshotEquals(a, b) {
|
|
6042
6083
|
if (a === b) {
|
|
6043
6084
|
return true;
|
|
6044
6085
|
}
|
|
6045
|
-
if (typeof a
|
|
6046
|
-
a
|
|
6047
|
-
typeof b
|
|
6048
|
-
b
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6086
|
+
if (typeof a !== "object" ||
|
|
6087
|
+
a === null ||
|
|
6088
|
+
typeof b !== "object" ||
|
|
6089
|
+
b === null) {
|
|
6090
|
+
return false;
|
|
6091
|
+
}
|
|
6092
|
+
const aIsArray = Array.isArray(a);
|
|
6093
|
+
if (aIsArray !== Array.isArray(b)) {
|
|
6094
|
+
return false;
|
|
6095
|
+
}
|
|
6096
|
+
if (aIsArray) {
|
|
6097
|
+
const arrayA = a;
|
|
6098
|
+
const arrayB = b;
|
|
6099
|
+
if (arrayA.length !== arrayB.length) {
|
|
6054
6100
|
return false;
|
|
6055
6101
|
}
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6102
|
+
for (let i = 0; i < arrayA.length; i++) {
|
|
6103
|
+
if (!defaultSnapshotEquals(arrayA[i], arrayB[i])) {
|
|
6104
|
+
return false;
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
return true;
|
|
6108
|
+
}
|
|
6109
|
+
const objectA = a;
|
|
6110
|
+
const objectB = b;
|
|
6111
|
+
const keysA = Object.keys(objectA);
|
|
6112
|
+
const keysB = Object.keys(objectB);
|
|
6113
|
+
if (keysA.length !== keysB.length) {
|
|
6114
|
+
return false;
|
|
6115
|
+
}
|
|
6116
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
6117
|
+
const key = keysA[i];
|
|
6118
|
+
if (key !== keysB[i] ||
|
|
6119
|
+
!defaultSnapshotEquals(objectA[key], objectB[key])) {
|
|
6059
6120
|
return false;
|
|
6060
6121
|
}
|
|
6061
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
6062
6122
|
}
|
|
6063
|
-
return
|
|
6123
|
+
return true;
|
|
6064
6124
|
}
|
|
6065
6125
|
/**
|
|
6066
6126
|
* An optional type that additionally omits its key from a parent model's
|