@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/index.d.ts
CHANGED
|
@@ -945,7 +945,6 @@ declare abstract class BaseType<C, S, T, N extends BaseNode<any, any, any> = Bas
|
|
|
945
945
|
*/
|
|
946
946
|
declare abstract class ComplexType<C, S, T> extends BaseType<C, S, T, ObjectNode<C, S, T>> {
|
|
947
947
|
identifierAttribute?: string;
|
|
948
|
-
constructor(name: string);
|
|
949
948
|
create(snapshot?: C, environment?: any): any;
|
|
950
949
|
getValue(node: this["N"]): T;
|
|
951
950
|
abstract getDefaultSnapshot(): C;
|
|
@@ -953,7 +952,7 @@ declare abstract class ComplexType<C, S, T> extends BaseType<C, S, T, ObjectNode
|
|
|
953
952
|
abstract finalizeNewInstance(node: this["N"], instance: any): void;
|
|
954
953
|
abstract applySnapshot(node: this["N"], snapshot: C): void;
|
|
955
954
|
abstract applyPatchLocally(node: this["N"], subpath: string, patch: IJsonPatch): void;
|
|
956
|
-
abstract processInitialSnapshot(childNodes: IChildNodesMap
|
|
955
|
+
abstract processInitialSnapshot(childNodes: IChildNodesMap): S;
|
|
957
956
|
abstract getChildren(node: this["N"]): ReadonlyArray<AnyNode>;
|
|
958
957
|
abstract getChildNode(node: this["N"], key: string): AnyNode;
|
|
959
958
|
abstract getChildType(propertyName?: string): IAnyType;
|
|
@@ -1245,7 +1244,6 @@ declare class IdentifierCache {
|
|
|
1245
1244
|
private cacheId;
|
|
1246
1245
|
private cache;
|
|
1247
1246
|
private lastCacheModificationPerId;
|
|
1248
|
-
constructor();
|
|
1249
1247
|
private updateLastCacheModificationPerId;
|
|
1250
1248
|
getLastCacheModificationPerId(identifier: string): string;
|
|
1251
1249
|
addNodeToCache(node: AnyObjectNode, lastCacheUpdate?: boolean): void;
|
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() {
|
|
@@ -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;
|
|
@@ -1810,9 +1806,6 @@ BaseType.prototype.create = mobx.action(BaseType.prototype.create);
|
|
|
1810
1806
|
*/
|
|
1811
1807
|
class ComplexType extends BaseType {
|
|
1812
1808
|
identifierAttribute;
|
|
1813
|
-
constructor(name) {
|
|
1814
|
-
super(name);
|
|
1815
|
-
}
|
|
1816
1809
|
create(snapshot = this.getDefaultSnapshot(), environment) {
|
|
1817
1810
|
return super.create(snapshot, environment);
|
|
1818
1811
|
}
|
|
@@ -2714,13 +2707,6 @@ function typeCheckFailure(context, value, message) {
|
|
|
2714
2707
|
// Clone context since it may be mutated after this error is created
|
|
2715
2708
|
return [{ context: context.slice(), value, message }];
|
|
2716
2709
|
}
|
|
2717
|
-
/**
|
|
2718
|
-
* @internal
|
|
2719
|
-
* @hidden
|
|
2720
|
-
*/
|
|
2721
|
-
function flattenTypeErrors(errors) {
|
|
2722
|
-
return errors.flat();
|
|
2723
|
-
}
|
|
2724
2710
|
// TODO; doublecheck: typecheck should only needed to be invoked from: type.create and array / map / value.property will change
|
|
2725
2711
|
/**
|
|
2726
2712
|
* @internal
|
|
@@ -2782,7 +2768,6 @@ class IdentifierCache {
|
|
|
2782
2768
|
// last time the cache (array) for a given time changed
|
|
2783
2769
|
// n.b. it is not really the time, but just an integer that gets increased after each modification to the array
|
|
2784
2770
|
lastCacheModificationPerId = mobx.observable.map();
|
|
2785
|
-
constructor() { }
|
|
2786
2771
|
updateLastCacheModificationPerId(identifier) {
|
|
2787
2772
|
const lcm = this.lastCacheModificationPerId.get(identifier);
|
|
2788
2773
|
// we start at 1 since 0 means no update since cache creation
|
|
@@ -2823,7 +2808,7 @@ class IdentifierCache {
|
|
|
2823
2808
|
if (!set.length) {
|
|
2824
2809
|
this.cache.delete(id);
|
|
2825
2810
|
}
|
|
2826
|
-
this.updateLastCacheModificationPerId(
|
|
2811
|
+
this.updateLastCacheModificationPerId(id);
|
|
2827
2812
|
}
|
|
2828
2813
|
}
|
|
2829
2814
|
}
|
|
@@ -3055,15 +3040,8 @@ function convertChildNodesToArray(childNodes) {
|
|
|
3055
3040
|
if (!childNodes) {
|
|
3056
3041
|
return EMPTY_ARRAY;
|
|
3057
3042
|
}
|
|
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;
|
|
3043
|
+
const result = Object.values(childNodes);
|
|
3044
|
+
return result.length ? result : EMPTY_ARRAY;
|
|
3067
3045
|
}
|
|
3068
3046
|
|
|
3069
3047
|
const plainObjectString = Object.toString();
|
|
@@ -3279,10 +3257,19 @@ class EventHandler {
|
|
|
3279
3257
|
this.handlers.length = 0;
|
|
3280
3258
|
}
|
|
3281
3259
|
emit(...args) {
|
|
3282
|
-
|
|
3283
|
-
// and
|
|
3284
|
-
|
|
3285
|
-
|
|
3260
|
+
const handlers = this.handlers;
|
|
3261
|
+
// 0 and 1 handlers are by far the common cases (a patch/snapshot listener, a
|
|
3262
|
+
// disposer) and need no copy: with a single handler, calling the value we
|
|
3263
|
+
// already read is exactly what iterating a copy of it would do.
|
|
3264
|
+
if (handlers.length <= 1) {
|
|
3265
|
+
handlers[0]?.(...args);
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
// otherwise iterate a copy so (un)registrations during emit don't disturb
|
|
3269
|
+
// this pass and reentrant emits stay correct
|
|
3270
|
+
for (const f of handlers.slice()) {
|
|
3271
|
+
f(...args);
|
|
3272
|
+
}
|
|
3286
3273
|
}
|
|
3287
3274
|
}
|
|
3288
3275
|
/**
|
|
@@ -3663,6 +3650,11 @@ function escapeJsonPath(path) {
|
|
|
3663
3650
|
* Unescape slashes and backslashes.
|
|
3664
3651
|
*/
|
|
3665
3652
|
function unescapeJsonPath(path) {
|
|
3653
|
+
// every escape sequence starts with `~`, so bail before the two regex passes
|
|
3654
|
+
// for the overwhelmingly common unescaped part (mirrors escapeJsonPath above)
|
|
3655
|
+
if (!path.includes("~")) {
|
|
3656
|
+
return path;
|
|
3657
|
+
}
|
|
3666
3658
|
return path.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3667
3659
|
}
|
|
3668
3660
|
/**
|
|
@@ -4153,22 +4145,21 @@ class MapType extends ComplexType {
|
|
|
4153
4145
|
applySnapshot(node, snapshot) {
|
|
4154
4146
|
typecheckInternal(this, snapshot);
|
|
4155
4147
|
const target = node.storedValue;
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4148
|
+
// keys present before the apply, minus the ones the snapshot (re)sets — what
|
|
4149
|
+
// remains has to be deleted. A Set rather than a plain object: it needs no
|
|
4150
|
+
// sentinel values, no second pass to find the survivors, and it can hold keys
|
|
4151
|
+
// like "__proto__", which an object literal silently swallows.
|
|
4152
|
+
const staleKeys = new Set(target.keys());
|
|
4160
4153
|
if (snapshot) {
|
|
4161
4154
|
// Don't use target.replace, as it will throw away all existing items first
|
|
4162
4155
|
for (const key in snapshot) {
|
|
4163
4156
|
target.set(key, snapshot[key]);
|
|
4164
|
-
|
|
4157
|
+
staleKeys.delete(key);
|
|
4165
4158
|
}
|
|
4166
4159
|
}
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
}
|
|
4171
|
-
});
|
|
4160
|
+
for (const key of staleKeys) {
|
|
4161
|
+
target.delete(key);
|
|
4162
|
+
}
|
|
4172
4163
|
}
|
|
4173
4164
|
getChildType() {
|
|
4174
4165
|
return this._subType;
|
|
@@ -4308,7 +4299,7 @@ class ArrayType extends ComplexType {
|
|
|
4308
4299
|
if (change.newValue === change.object[change.index]) {
|
|
4309
4300
|
return null;
|
|
4310
4301
|
}
|
|
4311
|
-
const updatedNodes = reconcileArrayChildren(node, subType, [childNodes[change.index]], [change.newValue],
|
|
4302
|
+
const updatedNodes = reconcileArrayChildren(node, subType, [childNodes[change.index]], [change.newValue], change.index);
|
|
4312
4303
|
if (!updatedNodes) {
|
|
4313
4304
|
return null;
|
|
4314
4305
|
}
|
|
@@ -4318,7 +4309,7 @@ class ArrayType extends ComplexType {
|
|
|
4318
4309
|
case "splice":
|
|
4319
4310
|
{
|
|
4320
4311
|
const { index, removedCount, added } = change;
|
|
4321
|
-
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added,
|
|
4312
|
+
const addedNodes = reconcileArrayChildren(node, subType, childNodes.slice(index, index + removedCount), added, index);
|
|
4322
4313
|
if (!addedNodes) {
|
|
4323
4314
|
return null;
|
|
4324
4315
|
}
|
|
@@ -4444,7 +4435,11 @@ function array(subtype) {
|
|
|
4444
4435
|
assertIsType(subtype, 1);
|
|
4445
4436
|
return new ArrayType(`${subtype.name}[]`, subtype);
|
|
4446
4437
|
}
|
|
4447
|
-
|
|
4438
|
+
/**
|
|
4439
|
+
* @param firstNewPath index the reconciled slice starts at; both call sites
|
|
4440
|
+
* hand over a contiguous run, so subpath `i` is simply `firstNewPath + i`
|
|
4441
|
+
*/
|
|
4442
|
+
function reconcileArrayChildren(parent, childType, oldNodes, newValues, firstNewPath) {
|
|
4448
4443
|
let nothingChanged = true;
|
|
4449
4444
|
// When the element type is a plain model with an identifier, index the old
|
|
4450
4445
|
// nodes by id so a moved / replaced child is matched in O(1) instead of the
|
|
@@ -4467,7 +4462,7 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
|
|
|
4467
4462
|
const hasNewNode = i <= newValues.length - 1;
|
|
4468
4463
|
const oldNode = oldNodes[i];
|
|
4469
4464
|
let newValue = hasNewNode ? newValues[i] : undefined;
|
|
4470
|
-
const newPath = `${
|
|
4465
|
+
const newPath = `${firstNewPath + i}`;
|
|
4471
4466
|
// for some reason, instead of newValue we got a node, fallback to the storedValue
|
|
4472
4467
|
// TODO: https://github.com/mobxjs/mobx-state-tree/issues/340#issuecomment-325581681
|
|
4473
4468
|
if (isNode(newValue)) {
|
|
@@ -4591,12 +4586,14 @@ function areSame(oldNode, newValue) {
|
|
|
4591
4586
|
if (!(oldNode instanceof ObjectNode)) {
|
|
4592
4587
|
return false;
|
|
4593
4588
|
}
|
|
4589
|
+
if (oldNode.identifier === null ||
|
|
4590
|
+
!oldNode.identifierAttribute ||
|
|
4591
|
+
!isPlainObject(newValue)) {
|
|
4592
|
+
return false;
|
|
4593
|
+
}
|
|
4594
4594
|
const oldNodeType = oldNode.getReconciliationType();
|
|
4595
4595
|
// new value is a snapshot with the correct identifier
|
|
4596
|
-
return (
|
|
4597
|
-
oldNode.identifierAttribute &&
|
|
4598
|
-
isPlainObject(newValue) &&
|
|
4599
|
-
oldNodeType.is(newValue) &&
|
|
4596
|
+
return (oldNodeType.is(newValue) &&
|
|
4600
4597
|
oldNodeType.isMatchingSnapshotId(oldNode, newValue));
|
|
4601
4598
|
}
|
|
4602
4599
|
/**
|
|
@@ -4611,6 +4608,9 @@ function isArrayType(type) {
|
|
|
4611
4608
|
|
|
4612
4609
|
const PRE_PROCESS_SNAPSHOT = "preProcessSnapshot";
|
|
4613
4610
|
const POST_PROCESS_SNAPSHOT = "postProcessSnapshot";
|
|
4611
|
+
function getPropObservable(storedValue, key) {
|
|
4612
|
+
return mobx.getAtom(storedValue, key);
|
|
4613
|
+
}
|
|
4614
4614
|
function objectTypeToString() {
|
|
4615
4615
|
return getStateTreeNode(this).toString();
|
|
4616
4616
|
}
|
|
@@ -4620,16 +4620,8 @@ const defaultObjectOptions = {
|
|
|
4620
4620
|
initializers: EMPTY_ARRAY
|
|
4621
4621
|
};
|
|
4622
4622
|
function toPropertiesObject(declaredProps) {
|
|
4623
|
-
const keysList = Object.keys(declaredProps);
|
|
4624
|
-
const alreadySeenKeys = new Set();
|
|
4625
|
-
keysList.forEach(key => {
|
|
4626
|
-
if (alreadySeenKeys.has(key)) {
|
|
4627
|
-
throw fail(`${key} is declared twice in the model. Model should not contain the same keys`);
|
|
4628
|
-
}
|
|
4629
|
-
alreadySeenKeys.add(key);
|
|
4630
|
-
});
|
|
4631
4623
|
// loop through properties and ensures that all items are types
|
|
4632
|
-
return
|
|
4624
|
+
return Object.keys(declaredProps).reduce((props, key) => {
|
|
4633
4625
|
// warn if user intended a HOOK
|
|
4634
4626
|
if (key in Hook) {
|
|
4635
4627
|
throw fail(`Hook '${key}' was defined as property. Hooks should be defined as part of the actions`);
|
|
@@ -4669,7 +4661,10 @@ function toPropertiesObject(declaredProps) {
|
|
|
4669
4661
|
throw fail(`Invalid type definition for property '${key}', cannot infer a type from a value like '${value}' (${typeof value})`);
|
|
4670
4662
|
}
|
|
4671
4663
|
return props;
|
|
4672
|
-
},
|
|
4664
|
+
},
|
|
4665
|
+
// seeded with the raw declaration; every key is either replaced by its
|
|
4666
|
+
// converted type below, left alone because it already is one, or throws
|
|
4667
|
+
{ ...declaredProps });
|
|
4673
4668
|
}
|
|
4674
4669
|
/**
|
|
4675
4670
|
* @internal
|
|
@@ -4951,7 +4946,8 @@ class ModelType extends ComplexType {
|
|
|
4951
4946
|
// success so a broken type keeps throwing on every create.
|
|
4952
4947
|
if (!this.duplicateKeysChecked) {
|
|
4953
4948
|
this.forAllProps(name => {
|
|
4954
|
-
if (mobx.isComputedProp(instance, name) ||
|
|
4949
|
+
if (mobx.isComputedProp(instance, name) ||
|
|
4950
|
+
!mobx.isObservableProp(instance, name)) {
|
|
4955
4951
|
throw fail(`${name} property is declared twice`);
|
|
4956
4952
|
}
|
|
4957
4953
|
});
|
|
@@ -4991,33 +4987,52 @@ class ModelType extends ComplexType {
|
|
|
4991
4987
|
}
|
|
4992
4988
|
}
|
|
4993
4989
|
getChildren(node) {
|
|
4994
|
-
const
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4990
|
+
const names = this.propertyNames;
|
|
4991
|
+
const res = new Array(names.length);
|
|
4992
|
+
for (let i = 0; i < names.length; i++) {
|
|
4993
|
+
res[i] = this.getPropertyNode(node, names[i]);
|
|
4994
|
+
}
|
|
4998
4995
|
return res;
|
|
4999
4996
|
}
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
4997
|
+
/**
|
|
4998
|
+
* Same as {@link getChildNode} but for a key already known to be a declared
|
|
4999
|
+
* property (callers iterating `propertyNames`), so the membership guard is
|
|
5000
|
+
* skipped.
|
|
5001
|
+
*/
|
|
5002
|
+
getPropertyNode(node, key) {
|
|
5003
|
+
const childNode = getPropObservable(node.storedValue, key).raw();
|
|
5006
5004
|
if (!childNode) {
|
|
5007
5005
|
throw fail(`Node not available for property ${key}`);
|
|
5008
5006
|
}
|
|
5009
5007
|
return childNode;
|
|
5010
5008
|
}
|
|
5009
|
+
getChildNode(node, key) {
|
|
5010
|
+
if (!(key in this.properties)) {
|
|
5011
|
+
throw fail(`Not a value property: ${key}`);
|
|
5012
|
+
}
|
|
5013
|
+
return this.getPropertyNode(node, key);
|
|
5014
|
+
}
|
|
5011
5015
|
getSnapshot(node, applyPostProcess = true) {
|
|
5012
5016
|
const res = {};
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5017
|
+
const storedValue = node.storedValue;
|
|
5018
|
+
const properties = this.properties;
|
|
5019
|
+
for (const name of this.propertyNames) {
|
|
5020
|
+
// One mobx lookup serves both purposes: reportObserved so the snapshot
|
|
5021
|
+
// computed recomputes when the child is reassigned (raw() below does not
|
|
5022
|
+
// track), and raw() to read the child node. Going through getChildNode
|
|
5023
|
+
// would repeat the same lookup for every property.
|
|
5024
|
+
const observable = getPropObservable(storedValue, name);
|
|
5025
|
+
observable.reportObserved();
|
|
5026
|
+
const childNode = observable.raw();
|
|
5027
|
+
if (!childNode) {
|
|
5028
|
+
throw fail(`Node not available for property ${name}`);
|
|
5029
|
+
}
|
|
5030
|
+
const snapshot = childNode.snapshot;
|
|
5016
5031
|
// strip-default optionals omit their key when equal to the default
|
|
5017
|
-
if (!shouldStripChildFromSnapshot(
|
|
5032
|
+
if (!shouldStripChildFromSnapshot(properties[name], snapshot)) {
|
|
5018
5033
|
res[name] = snapshot;
|
|
5019
5034
|
}
|
|
5020
|
-
}
|
|
5035
|
+
}
|
|
5021
5036
|
if (applyPostProcess) {
|
|
5022
5037
|
return this.applySnapshotPostProcessor(res);
|
|
5023
5038
|
}
|
|
@@ -5081,7 +5096,10 @@ class ModelType extends ComplexType {
|
|
|
5081
5096
|
return typeCheckSuccess();
|
|
5082
5097
|
}
|
|
5083
5098
|
forAllProps(fn) {
|
|
5084
|
-
|
|
5099
|
+
const properties = this.properties;
|
|
5100
|
+
for (const key of this.propertyNames) {
|
|
5101
|
+
fn(key, properties[key]);
|
|
5102
|
+
}
|
|
5085
5103
|
}
|
|
5086
5104
|
describe() {
|
|
5087
5105
|
// optimization: cache
|
|
@@ -5551,11 +5569,16 @@ class Union extends BaseType {
|
|
|
5551
5569
|
_types;
|
|
5552
5570
|
_dispatcher;
|
|
5553
5571
|
_eager = true;
|
|
5572
|
+
// Deliberately recomputed rather than memoized: a `types.late` member reports
|
|
5573
|
+
// 0 for its subtype until the definition resolves, so the fold is not stable
|
|
5574
|
+
// over the type's lifetime and a memo would need a carve-out for exactly the
|
|
5575
|
+
// case that motivates it. Measured at ~4% of union creation, which is not
|
|
5576
|
+
// worth another cache.
|
|
5554
5577
|
get flags() {
|
|
5555
5578
|
let result = TypeFlags.Union;
|
|
5556
|
-
this._types
|
|
5579
|
+
for (const type of this._types) {
|
|
5557
5580
|
result |= type.flags;
|
|
5558
|
-
}
|
|
5581
|
+
}
|
|
5559
5582
|
return result;
|
|
5560
5583
|
}
|
|
5561
5584
|
constructor(name, _types, options) {
|
|
@@ -5697,13 +5720,9 @@ class Union extends BaseType {
|
|
|
5697
5720
|
if (reconcileCurrentType.is(value)) {
|
|
5698
5721
|
return reconcileCurrentType;
|
|
5699
5722
|
}
|
|
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));
|
|
5723
|
+
return this._types.find(type => type !== reconcileCurrentType && type.is(value));
|
|
5706
5724
|
}
|
|
5725
|
+
return this._types.find(type => type.is(value));
|
|
5707
5726
|
}
|
|
5708
5727
|
tryQuickMatch(value, reconcileCurrentType) {
|
|
5709
5728
|
// state tree nodes need full type compatibility checking
|
|
@@ -5715,15 +5734,17 @@ class Union extends BaseType {
|
|
|
5715
5734
|
if (!isPlainObject(value)) {
|
|
5716
5735
|
return this.tryMatchPrimitive(value);
|
|
5717
5736
|
}
|
|
5718
|
-
// for objects, try structural matching against model types
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5737
|
+
// for objects, try structural matching against model types, preferring the
|
|
5738
|
+
// reconciliation type (checked first, then skipped in the main pass) so a
|
|
5739
|
+
// reconcile keeps its current member when it still fits — without building
|
|
5740
|
+
// the reordered candidate list this used to allocate on every call
|
|
5741
|
+
if (reconcileCurrentType &&
|
|
5742
|
+
this.snapshotLooksLikeType(value, reconcileCurrentType)) {
|
|
5743
|
+
return reconcileCurrentType;
|
|
5744
|
+
}
|
|
5745
|
+
for (const type of this._types) {
|
|
5746
|
+
if (type !== reconcileCurrentType &&
|
|
5747
|
+
this.snapshotLooksLikeType(value, type)) {
|
|
5727
5748
|
return type;
|
|
5728
5749
|
}
|
|
5729
5750
|
}
|
|
@@ -5838,7 +5859,7 @@ class Union extends BaseType {
|
|
|
5838
5859
|
if (applicableTypes === 1) {
|
|
5839
5860
|
return typeCheckSuccess();
|
|
5840
5861
|
}
|
|
5841
|
-
return typeCheckFailure(context, value, "No type is applicable for the union").concat(
|
|
5862
|
+
return typeCheckFailure(context, value, "No type is applicable for the union").concat(allErrors.flat());
|
|
5842
5863
|
}
|
|
5843
5864
|
getSubTypes() {
|
|
5844
5865
|
return this._types;
|
|
@@ -5938,15 +5959,15 @@ class OptionalValue extends BaseType {
|
|
|
5938
5959
|
: this.getDefaultInstanceOrSnapshot(), parent, subpath);
|
|
5939
5960
|
}
|
|
5940
5961
|
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);
|
|
5962
|
+
const defaultValue = this._defaultValue;
|
|
5963
|
+
if (typeof defaultValue !== "function") {
|
|
5964
|
+
// static values are already snapshots, checked once by types.optional
|
|
5965
|
+
return defaultValue;
|
|
5948
5966
|
}
|
|
5949
|
-
|
|
5967
|
+
const generated = defaultValue();
|
|
5968
|
+
// generator functions must always be rechecked just in case
|
|
5969
|
+
typecheckInternal(this, generated);
|
|
5970
|
+
return generated;
|
|
5950
5971
|
}
|
|
5951
5972
|
isValidSnapshot(value, context) {
|
|
5952
5973
|
// defaulted values can be skipped
|
|
@@ -6038,33 +6059,70 @@ function isOptionalType(type) {
|
|
|
6038
6059
|
return isType(type) && (type.flags & TypeFlags.Optional) > 0;
|
|
6039
6060
|
}
|
|
6040
6061
|
/**
|
|
6041
|
-
* Compare a child snapshot to a stripped-default's reference snapshot
|
|
6042
|
-
*
|
|
6043
|
-
*
|
|
6062
|
+
* Compare a child snapshot to a stripped-default's reference snapshot: identity
|
|
6063
|
+
* for primitives, structural for objects/arrays.
|
|
6064
|
+
*
|
|
6065
|
+
* Walks the two snapshots in parallel and bails at the first difference. The
|
|
6066
|
+
* previous implementation compared `JSON.stringify(a) === JSON.stringify(b)`
|
|
6067
|
+
* behind a size guard, which is O(whole snapshot) on both sides — plus two
|
|
6068
|
+
* string allocations — even when the very first key differs. That is the normal
|
|
6069
|
+
* case for an identified sub-model: its snapshot has exactly the same shape as
|
|
6070
|
+
* the default and differs only in the identifier, so the size guard never fires
|
|
6071
|
+
* and every `getSnapshot` of the parent serialized both trees just to answer
|
|
6072
|
+
* "no".
|
|
6073
|
+
*
|
|
6074
|
+
* Snapshots are frozen, acyclic, JSON-ish plain data, so a plain recursive walk
|
|
6075
|
+
* is safe. Key order is compared too, matching what stringify comparison did:
|
|
6076
|
+
* the point is to answer "is this still the default", and the only unsafe answer
|
|
6077
|
+
* is a false positive (a key gets dropped from the snapshot that shouldn't be),
|
|
6078
|
+
* so where this can't reproduce stringify exactly — `{a: undefined}` vs
|
|
6079
|
+
* `{b: undefined}`, or `NaN` nested in a frozen value, both of which stringify
|
|
6080
|
+
* flattened into equal text — it errs toward "not equal" and simply keeps the
|
|
6081
|
+
* key. Neither shape is reachable from a model snapshot, whose key set and order
|
|
6082
|
+
* are fixed by its type.
|
|
6044
6083
|
*/
|
|
6045
6084
|
function defaultSnapshotEquals(a, b) {
|
|
6046
6085
|
if (a === b) {
|
|
6047
6086
|
return true;
|
|
6048
6087
|
}
|
|
6049
|
-
if (typeof a
|
|
6050
|
-
a
|
|
6051
|
-
typeof b
|
|
6052
|
-
b
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6088
|
+
if (typeof a !== "object" ||
|
|
6089
|
+
a === null ||
|
|
6090
|
+
typeof b !== "object" ||
|
|
6091
|
+
b === null) {
|
|
6092
|
+
return false;
|
|
6093
|
+
}
|
|
6094
|
+
const aIsArray = Array.isArray(a);
|
|
6095
|
+
if (aIsArray !== Array.isArray(b)) {
|
|
6096
|
+
return false;
|
|
6097
|
+
}
|
|
6098
|
+
if (aIsArray) {
|
|
6099
|
+
const arrayA = a;
|
|
6100
|
+
const arrayB = b;
|
|
6101
|
+
if (arrayA.length !== arrayB.length) {
|
|
6058
6102
|
return false;
|
|
6059
6103
|
}
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6104
|
+
for (let i = 0; i < arrayA.length; i++) {
|
|
6105
|
+
if (!defaultSnapshotEquals(arrayA[i], arrayB[i])) {
|
|
6106
|
+
return false;
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
return true;
|
|
6110
|
+
}
|
|
6111
|
+
const objectA = a;
|
|
6112
|
+
const objectB = b;
|
|
6113
|
+
const keysA = Object.keys(objectA);
|
|
6114
|
+
const keysB = Object.keys(objectB);
|
|
6115
|
+
if (keysA.length !== keysB.length) {
|
|
6116
|
+
return false;
|
|
6117
|
+
}
|
|
6118
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
6119
|
+
const key = keysA[i];
|
|
6120
|
+
if (key !== keysB[i] ||
|
|
6121
|
+
!defaultSnapshotEquals(objectA[key], objectB[key])) {
|
|
6063
6122
|
return false;
|
|
6064
6123
|
}
|
|
6065
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
6066
6124
|
}
|
|
6067
|
-
return
|
|
6125
|
+
return true;
|
|
6068
6126
|
}
|
|
6069
6127
|
/**
|
|
6070
6128
|
* An optional type that additionally omits its key from a parent model's
|