@jbrowse/mobx-state-tree 6.0.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 +176 -118
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +176 -118
- 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() {
|
|
@@ -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();
|
|
@@ -3277,10 +3255,19 @@ class EventHandler {
|
|
|
3277
3255
|
this.handlers.length = 0;
|
|
3278
3256
|
}
|
|
3279
3257
|
emit(...args) {
|
|
3280
|
-
|
|
3281
|
-
// and
|
|
3282
|
-
|
|
3283
|
-
|
|
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
|
+
}
|
|
3284
3271
|
}
|
|
3285
3272
|
}
|
|
3286
3273
|
/**
|
|
@@ -3661,6 +3648,11 @@ function escapeJsonPath(path) {
|
|
|
3661
3648
|
* Unescape slashes and backslashes.
|
|
3662
3649
|
*/
|
|
3663
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
|
+
}
|
|
3664
3656
|
return path.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3665
3657
|
}
|
|
3666
3658
|
/**
|
|
@@ -4151,22 +4143,21 @@ class MapType extends ComplexType {
|
|
|
4151
4143
|
applySnapshot(node, snapshot) {
|
|
4152
4144
|
typecheckInternal(this, snapshot);
|
|
4153
4145
|
const target = node.storedValue;
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
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());
|
|
4158
4151
|
if (snapshot) {
|
|
4159
4152
|
// Don't use target.replace, as it will throw away all existing items first
|
|
4160
4153
|
for (const key in snapshot) {
|
|
4161
4154
|
target.set(key, snapshot[key]);
|
|
4162
|
-
|
|
4155
|
+
staleKeys.delete(key);
|
|
4163
4156
|
}
|
|
4164
4157
|
}
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
}
|
|
4169
|
-
});
|
|
4158
|
+
for (const key of staleKeys) {
|
|
4159
|
+
target.delete(key);
|
|
4160
|
+
}
|
|
4170
4161
|
}
|
|
4171
4162
|
getChildType() {
|
|
4172
4163
|
return this._subType;
|
|
@@ -4306,7 +4297,7 @@ class ArrayType extends ComplexType {
|
|
|
4306
4297
|
if (change.newValue === change.object[change.index]) {
|
|
4307
4298
|
return null;
|
|
4308
4299
|
}
|
|
4309
|
-
const updatedNodes = reconcileArrayChildren(node, subType, [childNodes[change.index]], [change.newValue],
|
|
4300
|
+
const updatedNodes = reconcileArrayChildren(node, subType, [childNodes[change.index]], [change.newValue], change.index);
|
|
4310
4301
|
if (!updatedNodes) {
|
|
4311
4302
|
return null;
|
|
4312
4303
|
}
|
|
@@ -4316,7 +4307,7 @@ class ArrayType extends ComplexType {
|
|
|
4316
4307
|
case "splice":
|
|
4317
4308
|
{
|
|
4318
4309
|
const { index, removedCount, added } = change;
|
|
4319
|
-
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added,
|
|
4310
|
+
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added, index);
|
|
4320
4311
|
if (!addedNodes) {
|
|
4321
4312
|
return null;
|
|
4322
4313
|
}
|
|
@@ -4442,7 +4433,11 @@ function array(subtype) {
|
|
|
4442
4433
|
assertIsType(subtype, 1);
|
|
4443
4434
|
return new ArrayType(`${subtype.name}[]`, subtype);
|
|
4444
4435
|
}
|
|
4445
|
-
|
|
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) {
|
|
4446
4441
|
let nothingChanged = true;
|
|
4447
4442
|
// When the element type is a plain model with an identifier, index the old
|
|
4448
4443
|
// nodes by id so a moved / replaced child is matched in O(1) instead of the
|
|
@@ -4465,7 +4460,7 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
|
|
|
4465
4460
|
const hasNewNode = i <= newValues.length - 1;
|
|
4466
4461
|
const oldNode = oldNodes[i];
|
|
4467
4462
|
let newValue = hasNewNode ? newValues[i] : undefined;
|
|
4468
|
-
const newPath = `${
|
|
4463
|
+
const newPath = `${firstNewPath + i}`;
|
|
4469
4464
|
// for some reason, instead of newValue we got a node, fallback to the storedValue
|
|
4470
4465
|
// TODO: https://github.com/mobxjs/mobx-state-tree/issues/340#issuecomment-325581681
|
|
4471
4466
|
if (isNode(newValue)) {
|
|
@@ -4589,12 +4584,14 @@ function areSame(oldNode, newValue) {
|
|
|
4589
4584
|
if (!(oldNode instanceof ObjectNode)) {
|
|
4590
4585
|
return false;
|
|
4591
4586
|
}
|
|
4587
|
+
if (oldNode.identifier === null ||
|
|
4588
|
+
!oldNode.identifierAttribute ||
|
|
4589
|
+
!isPlainObject(newValue)) {
|
|
4590
|
+
return false;
|
|
4591
|
+
}
|
|
4592
4592
|
const oldNodeType = oldNode.getReconciliationType();
|
|
4593
4593
|
// new value is a snapshot with the correct identifier
|
|
4594
|
-
return (
|
|
4595
|
-
oldNode.identifierAttribute &&
|
|
4596
|
-
isPlainObject(newValue) &&
|
|
4597
|
-
oldNodeType.is(newValue) &&
|
|
4594
|
+
return (oldNodeType.is(newValue) &&
|
|
4598
4595
|
oldNodeType.isMatchingSnapshotId(oldNode, newValue));
|
|
4599
4596
|
}
|
|
4600
4597
|
/**
|
|
@@ -4609,6 +4606,9 @@ function isArrayType(type) {
|
|
|
4609
4606
|
|
|
4610
4607
|
const PRE_PROCESS_SNAPSHOT = "preProcessSnapshot";
|
|
4611
4608
|
const POST_PROCESS_SNAPSHOT = "postProcessSnapshot";
|
|
4609
|
+
function getPropObservable(storedValue, key) {
|
|
4610
|
+
return getAtom(storedValue, key);
|
|
4611
|
+
}
|
|
4612
4612
|
function objectTypeToString() {
|
|
4613
4613
|
return getStateTreeNode(this).toString();
|
|
4614
4614
|
}
|
|
@@ -4618,16 +4618,8 @@ const defaultObjectOptions = {
|
|
|
4618
4618
|
initializers: EMPTY_ARRAY
|
|
4619
4619
|
};
|
|
4620
4620
|
function toPropertiesObject(declaredProps) {
|
|
4621
|
-
const keysList = Object.keys(declaredProps);
|
|
4622
|
-
const alreadySeenKeys = new Set();
|
|
4623
|
-
keysList.forEach(key => {
|
|
4624
|
-
if (alreadySeenKeys.has(key)) {
|
|
4625
|
-
throw fail(`${key} is declared twice in the model. Model should not contain the same keys`);
|
|
4626
|
-
}
|
|
4627
|
-
alreadySeenKeys.add(key);
|
|
4628
|
-
});
|
|
4629
4621
|
// loop through properties and ensures that all items are types
|
|
4630
|
-
return
|
|
4622
|
+
return Object.keys(declaredProps).reduce((props, key) => {
|
|
4631
4623
|
// warn if user intended a HOOK
|
|
4632
4624
|
if (key in Hook) {
|
|
4633
4625
|
throw fail(`Hook '${key}' was defined as property. Hooks should be defined as part of the actions`);
|
|
@@ -4667,7 +4659,10 @@ function toPropertiesObject(declaredProps) {
|
|
|
4667
4659
|
throw fail(`Invalid type definition for property '${key}', cannot infer a type from a value like '${value}' (${typeof value})`);
|
|
4668
4660
|
}
|
|
4669
4661
|
return props;
|
|
4670
|
-
},
|
|
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 });
|
|
4671
4666
|
}
|
|
4672
4667
|
/**
|
|
4673
4668
|
* @internal
|
|
@@ -4949,7 +4944,8 @@ class ModelType extends ComplexType {
|
|
|
4949
4944
|
// success so a broken type keeps throwing on every create.
|
|
4950
4945
|
if (!this.duplicateKeysChecked) {
|
|
4951
4946
|
this.forAllProps(name => {
|
|
4952
|
-
if (isComputedProp(instance, name) ||
|
|
4947
|
+
if (isComputedProp(instance, name) ||
|
|
4948
|
+
!isObservableProp(instance, name)) {
|
|
4953
4949
|
throw fail(`${name} property is declared twice`);
|
|
4954
4950
|
}
|
|
4955
4951
|
});
|
|
@@ -4989,33 +4985,52 @@ class ModelType extends ComplexType {
|
|
|
4989
4985
|
}
|
|
4990
4986
|
}
|
|
4991
4987
|
getChildren(node) {
|
|
4992
|
-
const
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
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
|
+
}
|
|
4996
4993
|
return res;
|
|
4997
4994
|
}
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
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();
|
|
5004
5002
|
if (!childNode) {
|
|
5005
5003
|
throw fail(`Node not available for property ${key}`);
|
|
5006
5004
|
}
|
|
5007
5005
|
return childNode;
|
|
5008
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
|
+
}
|
|
5009
5013
|
getSnapshot(node, applyPostProcess = true) {
|
|
5010
5014
|
const res = {};
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
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;
|
|
5014
5029
|
// strip-default optionals omit their key when equal to the default
|
|
5015
|
-
if (!shouldStripChildFromSnapshot(
|
|
5030
|
+
if (!shouldStripChildFromSnapshot(properties[name], snapshot)) {
|
|
5016
5031
|
res[name] = snapshot;
|
|
5017
5032
|
}
|
|
5018
|
-
}
|
|
5033
|
+
}
|
|
5019
5034
|
if (applyPostProcess) {
|
|
5020
5035
|
return this.applySnapshotPostProcessor(res);
|
|
5021
5036
|
}
|
|
@@ -5079,7 +5094,10 @@ class ModelType extends ComplexType {
|
|
|
5079
5094
|
return typeCheckSuccess();
|
|
5080
5095
|
}
|
|
5081
5096
|
forAllProps(fn) {
|
|
5082
|
-
|
|
5097
|
+
const properties = this.properties;
|
|
5098
|
+
for (const key of this.propertyNames) {
|
|
5099
|
+
fn(key, properties[key]);
|
|
5100
|
+
}
|
|
5083
5101
|
}
|
|
5084
5102
|
describe() {
|
|
5085
5103
|
// optimization: cache
|
|
@@ -5549,11 +5567,16 @@ class Union extends BaseType {
|
|
|
5549
5567
|
_types;
|
|
5550
5568
|
_dispatcher;
|
|
5551
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.
|
|
5552
5575
|
get flags() {
|
|
5553
5576
|
let result = TypeFlags.Union;
|
|
5554
|
-
this._types
|
|
5577
|
+
for (const type of this._types) {
|
|
5555
5578
|
result |= type.flags;
|
|
5556
|
-
}
|
|
5579
|
+
}
|
|
5557
5580
|
return result;
|
|
5558
5581
|
}
|
|
5559
5582
|
constructor(name, _types, options) {
|
|
@@ -5695,13 +5718,9 @@ class Union extends BaseType {
|
|
|
5695
5718
|
if (reconcileCurrentType.is(value)) {
|
|
5696
5719
|
return reconcileCurrentType;
|
|
5697
5720
|
}
|
|
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));
|
|
5721
|
+
return this._types.find(type => type !== reconcileCurrentType && type.is(value));
|
|
5704
5722
|
}
|
|
5723
|
+
return this._types.find(type => type.is(value));
|
|
5705
5724
|
}
|
|
5706
5725
|
tryQuickMatch(value, reconcileCurrentType) {
|
|
5707
5726
|
// state tree nodes need full type compatibility checking
|
|
@@ -5713,15 +5732,17 @@ class Union extends BaseType {
|
|
|
5713
5732
|
if (!isPlainObject(value)) {
|
|
5714
5733
|
return this.tryMatchPrimitive(value);
|
|
5715
5734
|
}
|
|
5716
|
-
// for objects, try structural matching against model types
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
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)) {
|
|
5725
5746
|
return type;
|
|
5726
5747
|
}
|
|
5727
5748
|
}
|
|
@@ -5836,7 +5857,7 @@ class Union extends BaseType {
|
|
|
5836
5857
|
if (applicableTypes === 1) {
|
|
5837
5858
|
return typeCheckSuccess();
|
|
5838
5859
|
}
|
|
5839
|
-
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());
|
|
5840
5861
|
}
|
|
5841
5862
|
getSubTypes() {
|
|
5842
5863
|
return this._types;
|
|
@@ -5936,15 +5957,15 @@ class OptionalValue extends BaseType {
|
|
|
5936
5957
|
: this.getDefaultInstanceOrSnapshot(), parent, subpath);
|
|
5937
5958
|
}
|
|
5938
5959
|
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);
|
|
5960
|
+
const defaultValue = this._defaultValue;
|
|
5961
|
+
if (typeof defaultValue !== "function") {
|
|
5962
|
+
// static values are already snapshots, checked once by types.optional
|
|
5963
|
+
return defaultValue;
|
|
5946
5964
|
}
|
|
5947
|
-
|
|
5965
|
+
const generated = defaultValue();
|
|
5966
|
+
// generator functions must always be rechecked just in case
|
|
5967
|
+
typecheckInternal(this, generated);
|
|
5968
|
+
return generated;
|
|
5948
5969
|
}
|
|
5949
5970
|
isValidSnapshot(value, context) {
|
|
5950
5971
|
// defaulted values can be skipped
|
|
@@ -6036,33 +6057,70 @@ function isOptionalType(type) {
|
|
|
6036
6057
|
return isType(type) && (type.flags & TypeFlags.Optional) > 0;
|
|
6037
6058
|
}
|
|
6038
6059
|
/**
|
|
6039
|
-
* Compare a child snapshot to a stripped-default's reference snapshot
|
|
6040
|
-
*
|
|
6041
|
-
*
|
|
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.
|
|
6042
6081
|
*/
|
|
6043
6082
|
function defaultSnapshotEquals(a, b) {
|
|
6044
6083
|
if (a === b) {
|
|
6045
6084
|
return true;
|
|
6046
6085
|
}
|
|
6047
|
-
if (typeof a
|
|
6048
|
-
a
|
|
6049
|
-
typeof b
|
|
6050
|
-
b
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
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) {
|
|
6056
6100
|
return false;
|
|
6057
6101
|
}
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
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])) {
|
|
6061
6120
|
return false;
|
|
6062
6121
|
}
|
|
6063
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
6064
6122
|
}
|
|
6065
|
-
return
|
|
6123
|
+
return true;
|
|
6066
6124
|
}
|
|
6067
6125
|
/**
|
|
6068
6126
|
* An optional type that additionally omits its key from a parent model's
|