@jbrowse/mobx-state-tree 6.2.0 → 6.4.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.
@@ -272,6 +272,8 @@ function unprotect(target) {
272
272
  * Returns true if the object is in protected mode, @see protect
273
273
  */
274
274
  function isProtected(target) {
275
+ // check all arguments
276
+ assertIsStateTreeNode(target, 1);
275
277
  return getStateTreeNode(target).isProtected;
276
278
  }
277
279
  /**
@@ -303,6 +305,27 @@ function getSnapshot(target, applyPostProcess = true) {
303
305
  }
304
306
  return freeze(node.type.getSnapshot(node, false));
305
307
  }
308
+ function findParentAtDepth(node, depth) {
309
+ let remaining = depth;
310
+ let parent = node.parent;
311
+ while (parent) {
312
+ if (--remaining === 0) {
313
+ return parent;
314
+ }
315
+ parent = parent.parent;
316
+ }
317
+ return null;
318
+ }
319
+ function findParentOfType(node, type) {
320
+ let parent = node.parent;
321
+ while (parent) {
322
+ if (type.is(parent.storedValue)) {
323
+ return parent;
324
+ }
325
+ parent = parent.parent;
326
+ }
327
+ return null;
328
+ }
306
329
  /**
307
330
  * Given a model instance, returns `true` if the object has a parent, that is, is part of another object, map or array.
308
331
  *
@@ -314,14 +337,7 @@ function hasParent(target, depth = 1) {
314
337
  // check all arguments
315
338
  assertIsStateTreeNode(target, 1);
316
339
  assertIsNumber(depth, 2, 0);
317
- let parent = getStateTreeNode(target).parent;
318
- while (parent) {
319
- if (--depth === 0) {
320
- return true;
321
- }
322
- parent = parent.parent;
323
- }
324
- return false;
340
+ return findParentAtDepth(getStateTreeNode(target), depth) !== null;
325
341
  }
326
342
  /**
327
343
  * Returns the immediate parent of this object, or throws.
@@ -340,15 +356,11 @@ function getParent(target, depth = 1) {
340
356
  // check all arguments
341
357
  assertIsStateTreeNode(target, 1);
342
358
  assertIsNumber(depth, 2, 0);
343
- let d = depth;
344
- let parent = getStateTreeNode(target).parent;
345
- while (parent) {
346
- if (--d === 0) {
347
- return parent.storedValue;
348
- }
349
- parent = parent.parent;
359
+ const parent = findParentAtDepth(getStateTreeNode(target), depth);
360
+ if (!parent) {
361
+ throw fail(`Failed to find the parent of ${getStateTreeNode(target)} at depth ${depth}`);
350
362
  }
351
- throw fail(`Failed to find the parent of ${getStateTreeNode(target)} at depth ${depth}`);
363
+ return parent.storedValue;
352
364
  }
353
365
  /**
354
366
  * Given a model instance, returns `true` if the object has a parent of given type, that is, is part of another object, map or array
@@ -361,14 +373,7 @@ function hasParentOfType(target, type) {
361
373
  // check all arguments
362
374
  assertIsStateTreeNode(target, 1);
363
375
  assertIsType(type, 2);
364
- let parent = getStateTreeNode(target).parent;
365
- while (parent) {
366
- if (type.is(parent.storedValue)) {
367
- return true;
368
- }
369
- parent = parent.parent;
370
- }
371
- return false;
376
+ return findParentOfType(getStateTreeNode(target), type) !== null;
372
377
  }
373
378
  /**
374
379
  * Returns the target's parent of a given type, or throws.
@@ -381,14 +386,11 @@ function getParentOfType(target, type) {
381
386
  // check all arguments
382
387
  assertIsStateTreeNode(target, 1);
383
388
  assertIsType(type, 2);
384
- let parent = getStateTreeNode(target).parent;
385
- while (parent) {
386
- if (type.is(parent.storedValue)) {
387
- return parent.storedValue;
388
- }
389
- parent = parent.parent;
389
+ const parent = findParentOfType(getStateTreeNode(target), type);
390
+ if (!parent) {
391
+ throw fail(`Failed to find the parent of ${getStateTreeNode(target)} of a given type`);
390
392
  }
391
- throw fail(`Failed to find the parent of ${getStateTreeNode(target)} of a given type`);
393
+ return parent.storedValue;
392
394
  }
393
395
  /**
394
396
  * Given an object in a model tree, returns the root object of that tree.
@@ -482,30 +484,20 @@ function getIdentifier(target) {
482
484
  return getStateTreeNode(target).identifier;
483
485
  }
484
486
  /**
485
- * Tests if a reference is valid (pointing to an existing node and optionally if alive) and returns such reference if the check passes,
486
- * else it returns undefined.
487
- *
488
- * @param getter Function to access the reference.
489
- * @param checkIfAlive true to also make sure the referenced node is alive (default), false to skip this check.
490
- * @returns
487
+ * Resolves a reference getter to the referenced node, or `undefined` when the
488
+ * reference is empty, dangling, or (with `checkIfAlive`) points at a dead node.
489
+ * Backs both {@link tryReference} and {@link isValidReference}.
491
490
  */
492
- function tryReference(getter, checkIfAlive = true) {
491
+ function resolveReference(getter, checkIfAlive) {
493
492
  try {
494
493
  const node = getter();
495
494
  if (node === undefined || node === null) {
496
495
  return undefined;
497
496
  }
498
- else if (isStateTreeNode(node)) {
499
- if (!checkIfAlive) {
500
- return node;
501
- }
502
- else {
503
- return isAlive(node) ? node : undefined;
504
- }
505
- }
506
- else {
497
+ if (!isStateTreeNode(node)) {
507
498
  throw fail("The reference to be checked is not one of node, null or undefined");
508
499
  }
500
+ return !checkIfAlive || isAlive(node) ? node : undefined;
509
501
  }
510
502
  catch (e) {
511
503
  if (e instanceof InvalidReferenceError) {
@@ -514,6 +506,17 @@ function tryReference(getter, checkIfAlive = true) {
514
506
  throw e;
515
507
  }
516
508
  }
509
+ /**
510
+ * Tests if a reference is valid (pointing to an existing node and optionally if alive) and returns such reference if the check passes,
511
+ * else it returns undefined.
512
+ *
513
+ * @param getter Function to access the reference.
514
+ * @param checkIfAlive true to also make sure the referenced node is alive (default), false to skip this check.
515
+ * @returns
516
+ */
517
+ function tryReference(getter, checkIfAlive = true) {
518
+ return resolveReference(getter, checkIfAlive);
519
+ }
517
520
  /**
518
521
  * Tests if a reference is valid (pointing to an existing node and optionally if alive) and returns if the check passes or not.
519
522
  *
@@ -522,24 +525,7 @@ function tryReference(getter, checkIfAlive = true) {
522
525
  * @returns
523
526
  */
524
527
  function isValidReference(getter, checkIfAlive = true) {
525
- try {
526
- const node = getter();
527
- if (node === undefined || node === null) {
528
- return false;
529
- }
530
- else if (isStateTreeNode(node)) {
531
- return checkIfAlive ? isAlive(node) : true;
532
- }
533
- else {
534
- throw fail("The reference to be checked is not one of node, null or undefined");
535
- }
536
- }
537
- catch (e) {
538
- if (e instanceof InvalidReferenceError) {
539
- return false;
540
- }
541
- throw e;
542
- }
528
+ return resolveReference(getter, checkIfAlive) !== undefined;
543
529
  }
544
530
  /**
545
531
  * Try to resolve a given path relative to a given node.
@@ -1213,11 +1199,11 @@ class ObjectNode extends BaseNode {
1213
1199
  this.identifier = normalizeIdentifier(id);
1214
1200
  this.unnormalizedIdentifier = id;
1215
1201
  }
1216
- if (!parent) {
1217
- this.identifierCache.addNodeToCache(this);
1218
- }
1219
- else {
1220
- parent.root.identifierCache.addNodeToCache(this);
1202
+ // addNodeToCache no-ops without an identifier attribute, and reaching the
1203
+ // root to ask it is a walk up the whole parent chain
1204
+ if (this.identifierAttribute) {
1205
+ const cache = parent ? parent.root.identifierCache : this.identifierCache;
1206
+ cache.addNodeToCache(this);
1221
1207
  }
1222
1208
  }
1223
1209
  createObservableInstanceIfNeeded(fireHooks = true) {
@@ -1365,15 +1351,9 @@ class ObjectNode extends BaseNode {
1365
1351
  typeof this.storedValue === "object" &&
1366
1352
  this.storedValue[name];
1367
1353
  if (typeof fn === "function") {
1368
- // we check for it to allow old mobx peer dependencies that don't have the method to work (even when still bugged)
1369
- if (mobx._allowStateChangesInsideComputed) {
1370
- mobx._allowStateChangesInsideComputed(() => {
1371
- fn.apply(this.storedValue);
1372
- });
1373
- }
1374
- else {
1354
+ mobx._allowStateChangesInsideComputed(() => {
1375
1355
  fn.apply(this.storedValue);
1376
- }
1356
+ });
1377
1357
  }
1378
1358
  }
1379
1359
  _snapshotUponDeath;
@@ -1753,8 +1733,8 @@ var TypeFlags;
1753
1733
  TypeFlags[TypeFlags["Float"] = 4194304] = "Float";
1754
1734
  })(TypeFlags || (TypeFlags = {}));
1755
1735
  /**
1756
- * @internal
1757
- * @hidden
1736
+ * The value `getSubTypes()` returns when a type cannot report its subtype,
1737
+ * which is distinct from reporting that it has none (`null`).
1758
1738
  */
1759
1739
  const cannotDetermineSubtype = "cannotDetermine";
1760
1740
  /**
@@ -1806,6 +1786,9 @@ class BaseType {
1806
1786
  // istanbul ignore next
1807
1787
  throw fail("unimplemented method");
1808
1788
  }
1789
+ describe() {
1790
+ return this.name;
1791
+ }
1809
1792
  isAssignableFrom(type) {
1810
1793
  return type === this;
1811
1794
  }
@@ -1951,7 +1934,7 @@ class SimpleType extends BaseType {
1951
1934
  * @returns `true` if the value is a type.
1952
1935
  */
1953
1936
  function isType(value) {
1954
- return typeof value === "object" && value?.isType === true;
1937
+ return (typeof value === "object" && value?.isType === true);
1955
1938
  }
1956
1939
  /**
1957
1940
  * @internal
@@ -1961,7 +1944,6 @@ function assertIsType(type, argNumber) {
1961
1944
  assertArg(type, isType, "mobx-state-tree type", argNumber);
1962
1945
  }
1963
1946
 
1964
- const runningActions = new Map();
1965
1947
  /**
1966
1948
  * Note: Consider migrating to `createActionTrackingMiddleware2`, it is easier to use.
1967
1949
  *
@@ -1977,6 +1959,12 @@ const runningActions = new Map();
1977
1959
  * @returns
1978
1960
  */
1979
1961
  function createActionTrackingMiddleware(hooks) {
1962
+ // per middleware, not per module: the entries hold the `context` this
1963
+ // middleware's own onStart returned, so two middlewares on one tree would
1964
+ // otherwise overwrite each other's context and then delete the shared entry —
1965
+ // whichever saw `flow_return` second dereferenced `undefined`.
1966
+ // createActionTrackingMiddleware2 scopes its map the same way.
1967
+ const runningActions = new Map();
1980
1968
  return function actionTrackingMiddleware(call, next, _abort) {
1981
1969
  switch (call.type) {
1982
1970
  case "action": {
@@ -2681,10 +2669,6 @@ function safeStringify(value) {
2681
2669
  return `<Unserializable: ${e}>`;
2682
2670
  }
2683
2671
  }
2684
- /**
2685
- * @internal
2686
- * @hidden
2687
- */
2688
2672
  function prettyPrintValue(value) {
2689
2673
  return typeof value === "function"
2690
2674
  ? `<function${value.name ? ` ${value.name}` : ""}>`
@@ -2699,6 +2683,8 @@ function shortenPrintValue(valueInString) {
2699
2683
  }
2700
2684
  function toErrorString(error) {
2701
2685
  const { value } = error;
2686
+ // every context entry carries a type: they are built by getContextForPath and
2687
+ // by typecheck's initial `[{ path: "", type }]`
2702
2688
  const type = error.context[error.context.length - 1].type;
2703
2689
  const fullPath = error.context
2704
2690
  .map(({ path }) => path)
@@ -2710,14 +2696,17 @@ function toErrorString(error) {
2710
2696
  : isPrimitive(value)
2711
2697
  ? "value"
2712
2698
  : "snapshot";
2713
- const isSnapshotCompatible = type && isStateTreeNode(value) && type.is(getStateTreeNode(value).snapshot);
2714
- return `${pathPrefix}${currentTypename} ${shortenPrintValue(prettyPrintValue(value))} is not assignable ${type ? `to type: \`${type.name}\`` : ``}${error.message ? ` (${error.message})` : ""}${type
2715
- ? isPrimitiveType(type) || isPrimitive(value)
2716
- ? `.`
2717
- : `, expected an instance of \`${type.name}\` or a snapshot like \`${shortenPrintValue(type.describe())}\` instead.${isSnapshotCompatible
2718
- ? " (Note that a snapshot of the provided value is compatible with the targeted type)"
2719
- : ""}`
2720
- : `.`}`;
2699
+ let expectation;
2700
+ if (isPrimitiveType(type) || isPrimitive(value)) {
2701
+ expectation = ".";
2702
+ }
2703
+ else {
2704
+ const isSnapshotCompatible = isStateTreeNode(value) && type.is(getStateTreeNode(value).snapshot);
2705
+ expectation = `, expected an instance of \`${type.name}\` or a snapshot like \`${shortenPrintValue(type.describe())}\` instead.${isSnapshotCompatible
2706
+ ? " (Note that a snapshot of the provided value is compatible with the targeted type)"
2707
+ : ""}`;
2708
+ }
2709
+ return `${pathPrefix}${currentTypename} ${shortenPrintValue(prettyPrintValue(value))} is not assignable to type: \`${type.name}\`${error.message ? ` (${error.message})` : ""}${expectation}`;
2721
2710
  }
2722
2711
  /**
2723
2712
  * @internal
@@ -2994,7 +2983,7 @@ function getStateTreeNode(value) {
2994
2983
  * @hidden
2995
2984
  */
2996
2985
  function getStateTreeNodeSafe(value) {
2997
- return value?.$treenode || null;
2986
+ return value?.["$treenode"] || null;
2998
2987
  }
2999
2988
  /**
3000
2989
  * @internal
@@ -3306,9 +3295,6 @@ class EventHandler {
3306
3295
  this.handlers.splice(index, 1);
3307
3296
  }
3308
3297
  }
3309
- clear() {
3310
- this.handlers.length = 0;
3311
- }
3312
3298
  emit(...args) {
3313
3299
  const handlers = this.handlers;
3314
3300
  // 0 and 1 handlers are by far the common cases (a patch/snapshot listener, a
@@ -3453,21 +3439,6 @@ function assertIsString(value, argNumber, canBeEmpty = true) {
3453
3439
  assertArg(value, s => s !== "", "not empty string", argNumber);
3454
3440
  }
3455
3441
  }
3456
- /**
3457
- * @internal
3458
- * @hidden
3459
- */
3460
- function setImmediateWithFallback(fn) {
3461
- if (typeof queueMicrotask === "function") {
3462
- queueMicrotask(fn);
3463
- }
3464
- else if (typeof setImmediate === "function") {
3465
- setImmediate(fn);
3466
- }
3467
- else {
3468
- setTimeout(fn, 1);
3469
- }
3470
- }
3471
3442
 
3472
3443
  /**
3473
3444
  * See [asynchronous actions](concepts/async-actions.md).
@@ -3538,14 +3509,12 @@ function toGeneratorFunction(p) {
3538
3509
  function* toGenerator(p) {
3539
3510
  return (yield p);
3540
3511
  }
3541
- /**
3542
- * @internal
3543
- * @hidden
3544
- */
3545
3512
  function createFlowSpawner(name, generator) {
3546
3513
  const spawner = function flowSpawner(...flowArgs) {
3547
3514
  // Implementation based on https://github.com/tj/co/blob/master/index.js
3548
3515
  const runId = getNextActionId();
3516
+ // no `!`: the guard below is the point, and asserting non-null first made it
3517
+ // dead to the checker while leaving it live at runtime
3549
3518
  const parentContext = getCurrentActionContext();
3550
3519
  if (!parentContext) {
3551
3520
  throw fail("a mst flow must always have a parent context");
@@ -3597,7 +3566,7 @@ function createFlowSpawner(name, generator) {
3597
3566
  }
3598
3567
  catch (e) {
3599
3568
  // prettier-ignore
3600
- setImmediateWithFallback(() => {
3569
+ queueMicrotask(() => {
3601
3570
  wrap((_r) => { reject(e); }, "flow_throw", e);
3602
3571
  });
3603
3572
  return;
@@ -3613,7 +3582,7 @@ function createFlowSpawner(name, generator) {
3613
3582
  }
3614
3583
  catch (e) {
3615
3584
  // prettier-ignore
3616
- setImmediateWithFallback(() => {
3585
+ queueMicrotask(() => {
3617
3586
  wrap((_r) => { reject(e); }, "flow_throw", e);
3618
3587
  });
3619
3588
  return;
@@ -3623,7 +3592,7 @@ function createFlowSpawner(name, generator) {
3623
3592
  function next(ret) {
3624
3593
  if (ret.done) {
3625
3594
  // prettier-ignore
3626
- setImmediateWithFallback(() => {
3595
+ queueMicrotask(() => {
3627
3596
  wrap((r) => { resolve(r); }, "flow_return", ret.value);
3628
3597
  });
3629
3598
  return;
@@ -3651,10 +3620,6 @@ function splitPatch(patch) {
3651
3620
  }
3652
3621
  return [stripPatch(patch), invertPatch(patch)];
3653
3622
  }
3654
- /**
3655
- * @internal
3656
- * @hidden
3657
- */
3658
3623
  function stripPatch(patch) {
3659
3624
  // strips `oldvalue` information from the patch, so that it becomes a patch conform the json-patch spec
3660
3625
  // this removes the ability to undo the patch
@@ -3814,8 +3779,10 @@ class SnapshotProcessor extends BaseType {
3814
3779
  return sn;
3815
3780
  }
3816
3781
  _fixNode(node) {
3817
- // the node has to use these methods rather than the original type ones
3818
- proxyNodeTypeMethods(node.type, this, "create");
3782
+ // the node's type is the *inner* type, so `getType(instance).create(...)`
3783
+ // would bypass the processors — point it at ours instead
3784
+ const nodeType = node.type;
3785
+ nodeType.create = this.create.bind(this);
3819
3786
  if (node instanceof ObjectNode) {
3820
3787
  node.hasSnapshotPostProcessor = !!this._processors.postProcessor;
3821
3788
  }
@@ -3884,11 +3851,6 @@ class SnapshotProcessor extends BaseType {
3884
3851
  return this._subtype.isMatchingSnapshotId(current, processedSn);
3885
3852
  }
3886
3853
  }
3887
- function proxyNodeTypeMethods(nodeType, snapshotProcessorType, ...methods) {
3888
- for (const method of methods) {
3889
- nodeType[method] = snapshotProcessorType[method].bind(snapshotProcessorType);
3890
- }
3891
- }
3892
3854
  /**
3893
3855
  * `types.snapshotProcessor` - Runs a pre/post snapshot processor before/after serializing a given type.
3894
3856
  *
@@ -3939,6 +3901,41 @@ function snapshotProcessor(type, processors, name) {
3939
3901
  return new SnapshotProcessor(type, processors, name);
3940
3902
  }
3941
3903
 
3904
+ /**
3905
+ * Append `hooks` to a collection type's initializer list. `EMPTY_ARRAY` is the
3906
+ * shared default, so a type that never calls `.hooks()` allocates nothing.
3907
+ *
3908
+ * @internal
3909
+ * @hidden
3910
+ */
3911
+ function appendHookInitializer(current, hooks) {
3912
+ return current.length > 0 ? [...current, hooks] : [hooks];
3913
+ }
3914
+ /**
3915
+ * Install every hook the type's initializers produce onto `instance` as an MST
3916
+ * action, mirroring how a model's `.actions()` members are attached.
3917
+ *
3918
+ * @internal
3919
+ * @hidden
3920
+ */
3921
+ function installHookInitializers(hookInitializers, instance) {
3922
+ const addProp = !devMode() ? addHiddenFinalProp : addHiddenWritableProp;
3923
+ for (const initializer of hookInitializers) {
3924
+ const hooks = initializer(instance);
3925
+ for (const name of Object.keys(hooks)) {
3926
+ const hook = hooks[name];
3927
+ addProp(instance, name, createActionInvoker(instance, name, hook));
3928
+ }
3929
+ }
3930
+ }
3931
+ /**
3932
+ * The empty initializer list every collection type starts with.
3933
+ *
3934
+ * @internal
3935
+ * @hidden
3936
+ */
3937
+ const NO_HOOK_INITIALIZERS = EMPTY_ARRAY;
3938
+
3942
3939
  const needsIdentifierError = `Map.put can only be used to store complex values that have an identifier type attribute`;
3943
3940
  function tryCollectModelTypes(type, modelTypes) {
3944
3941
  const subtypes = type.getSubTypes();
@@ -3969,13 +3966,6 @@ var MapIdentifierMode;
3969
3966
  MapIdentifierMode[MapIdentifierMode["NO"] = 2] = "NO";
3970
3967
  })(MapIdentifierMode || (MapIdentifierMode = {}));
3971
3968
  class MSTMap extends mobx.ObservableMap {
3972
- constructor(initialData, name) {
3973
- // mobx's default (deep) enhancer. The old `observable.ref.enhancer` arg
3974
- // read undefined ever since mobx 6 renamed the field to `options_.enhancer`,
3975
- // so deep has been the effective behavior all along; mobx 7 dropped
3976
- // `observable.ref` entirely.
3977
- super(initialData, undefined, name);
3978
- }
3979
3969
  get(key) {
3980
3970
  // maybe this is over-enthousiastic? normalize numeric keys to strings
3981
3971
  return super.get(`${key}`);
@@ -4035,24 +4025,21 @@ class MSTMap extends mobx.ObservableMap {
4035
4025
  */
4036
4026
  class MapType extends ComplexType {
4037
4027
  _subType;
4028
+ hookInitializers;
4038
4029
  identifierMode = MapIdentifierMode.UNKNOWN;
4039
4030
  mapIdentifierAttribute = undefined;
4040
4031
  flags = TypeFlags.Map;
4041
- hookInitializers = [];
4042
- constructor(_subType, hookInitializers = []) {
4032
+ constructor(_subType, hookInitializers = NO_HOOK_INITIALIZERS) {
4043
4033
  super();
4044
4034
  this._subType = _subType;
4045
- this._determineIdentifierMode();
4046
4035
  this.hookInitializers = hookInitializers;
4036
+ this._determineIdentifierMode();
4047
4037
  }
4048
4038
  computeName() {
4049
4039
  return `Map<string, ${this._subType.name}>`;
4050
4040
  }
4051
4041
  hooks(hooks) {
4052
- const hookInitializers = this.hookInitializers.length > 0
4053
- ? this.hookInitializers.concat(hooks)
4054
- : [hooks];
4055
- return new MapType(this._subType, hookInitializers);
4042
+ return new MapType(this._subType, appendHookInitializer(this.hookInitializers, hooks));
4056
4043
  }
4057
4044
  instantiate(parent, subpath, environment, initialValue) {
4058
4045
  this._determineIdentifierMode();
@@ -4091,25 +4078,15 @@ class MapType extends ComplexType {
4091
4078
  return result;
4092
4079
  }
4093
4080
  createNewInstance(childNodes) {
4094
- return new MSTMap(childNodes, this.name);
4081
+ // undefined is mobx's enhancer argument, left at its deep default
4082
+ return new MSTMap(childNodes, undefined, this.name);
4095
4083
  }
4096
4084
  finalizeNewInstance(node, instance) {
4097
4085
  mobx._interceptReads(instance, node.unbox);
4098
- const type = node.type;
4099
- type.hookInitializers.forEach(initializer => {
4100
- const hooks = initializer(instance);
4101
- Object.keys(hooks).forEach(name => {
4102
- const hook = hooks[name];
4103
- const actionInvoker = createActionInvoker(instance, name, hook);
4104
- (!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(instance, name, actionInvoker);
4105
- });
4106
- });
4086
+ installHookInitializers(node.type.hookInitializers, instance);
4107
4087
  mobx.intercept(instance, this.willChange);
4108
4088
  mobx.observe(instance, this.didChange);
4109
4089
  }
4110
- describe() {
4111
- return this.name;
4112
- }
4113
4090
  getChildren(node) {
4114
4091
  // return (node.storedValue as ObservableMap<any>).values()
4115
4092
  return mobx.values(node.storedValue);
@@ -4286,6 +4263,7 @@ MapType.prototype.applySnapshot = mobx.action(MapType.prototype.applySnapshot);
4286
4263
  * @returns
4287
4264
  */
4288
4265
  function map(subtype) {
4266
+ assertIsType(subtype, 1);
4289
4267
  return new MapType(subtype);
4290
4268
  }
4291
4269
  /**
@@ -4304,9 +4282,9 @@ function isMapType(type) {
4304
4282
  */
4305
4283
  class ArrayType extends ComplexType {
4306
4284
  _subType;
4285
+ hookInitializers;
4307
4286
  flags = TypeFlags.Array;
4308
- hookInitializers = [];
4309
- constructor(_subType, hookInitializers = []) {
4287
+ constructor(_subType, hookInitializers = NO_HOOK_INITIALIZERS) {
4310
4288
  super();
4311
4289
  this._subType = _subType;
4312
4290
  this.hookInitializers = hookInitializers;
@@ -4315,10 +4293,7 @@ class ArrayType extends ComplexType {
4315
4293
  return `${this._subType.name}[]`;
4316
4294
  }
4317
4295
  hooks(hooks) {
4318
- const hookInitializers = this.hookInitializers.length > 0
4319
- ? this.hookInitializers.concat(hooks)
4320
- : [hooks];
4321
- return new ArrayType(this._subType, hookInitializers);
4296
+ return new ArrayType(this._subType, appendHookInitializer(this.hookInitializers, hooks));
4322
4297
  }
4323
4298
  instantiate(parent, subpath, environment, initialValue) {
4324
4299
  return createObjectNode(this, parent, subpath, environment, initialValue);
@@ -4338,21 +4313,10 @@ class ArrayType extends ComplexType {
4338
4313
  }
4339
4314
  finalizeNewInstance(node, instance) {
4340
4315
  mobx._getAdministration(instance).dehancer = node.unbox;
4341
- const type = node.type;
4342
- type.hookInitializers.forEach(initializer => {
4343
- const hooks = initializer(instance);
4344
- Object.keys(hooks).forEach(name => {
4345
- const hook = hooks[name];
4346
- const actionInvoker = createActionInvoker(instance, name, hook);
4347
- (!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(instance, name, actionInvoker);
4348
- });
4349
- });
4316
+ installHookInitializers(node.type.hookInitializers, instance);
4350
4317
  mobx.intercept(instance, this.willChange);
4351
4318
  mobx.observe(instance, this.didChange);
4352
4319
  }
4353
- describe() {
4354
- return this.name;
4355
- }
4356
4320
  getChildren(node) {
4357
4321
  return node.storedValue.slice();
4358
4322
  }
@@ -4722,11 +4686,10 @@ function getPropObservables(storedValue) {
4722
4686
  function objectTypeToString() {
4723
4687
  return getStateTreeNode(this).toString();
4724
4688
  }
4725
- const defaultObjectOptions = {
4726
- name: "AnonymousModel"};
4689
+ const ANONYMOUS_MODEL_NAME = "AnonymousModel";
4727
4690
  /**
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
4691
+ * A plain loop rather than a callback per property, which allocated a closure
4692
+ * and made an indirect call. This runs once per `types.model()` over every
4730
4693
  * declared property, and jbrowse builds a ~40-slot schema per track.
4731
4694
  */
4732
4695
  function findIdentifierAttribute(properties, propertyNames) {
@@ -4809,7 +4772,7 @@ class ModelType extends ComplexType {
4809
4772
  // `??`, not `||`: `types.model("", {})` names the model "". The old
4810
4773
  // `Object.assign(this, defaults, opts)` below overwrote the `||` fallback
4811
4774
  // with opts.name afterwards, so that only worked by accident.
4812
- super(opts.name ?? defaultObjectOptions.name);
4775
+ super(opts.name ?? ANONYMOUS_MODEL_NAME);
4813
4776
  // Every field is assigned here, unconditionally and in a fixed order,
4814
4777
  // rather than by `Object.assign(this, defaultObjectOptions, opts)`. `opts`
4815
4778
  // carries a different key set at each of the three call sites — `model()`,
@@ -4924,12 +4887,6 @@ class ModelType extends ComplexType {
4924
4887
  (!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(self, name, actionInvoker);
4925
4888
  });
4926
4889
  }
4927
- named = name => {
4928
- return this.cloneAndEnhance({ name });
4929
- };
4930
- props = properties => {
4931
- return this.cloneAndEnhance({ properties });
4932
- };
4933
4890
  volatile(fn) {
4934
4891
  if (typeof fn !== "function") {
4935
4892
  throw fail(`You passed an ${typeof fn} to volatile state as an argument, when function is expected`);
@@ -4949,19 +4906,7 @@ class ModelType extends ComplexType {
4949
4906
  }
4950
4907
  extend(fn) {
4951
4908
  const initializer = (self) => {
4952
- const { actions, views, state, ...rest } = fn(self);
4953
- for (const key in rest) {
4954
- throw fail(`The \`extend\` function should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'`);
4955
- }
4956
- if (state) {
4957
- this.instantiateVolatileState(self, state);
4958
- }
4959
- if (views) {
4960
- this.instantiateViews(self, views);
4961
- }
4962
- if (actions) {
4963
- this.instantiateActions(self, actions);
4964
- }
4909
+ this.applyExtension(self, fn(self), "The `extend` function");
4965
4910
  return self;
4966
4911
  };
4967
4912
  return this.cloneAndEnhance({ initializers: [initializer] });
@@ -4982,9 +4927,17 @@ class ModelType extends ComplexType {
4982
4927
  * are permitted (see extendInstance, which runs this in an action context).
4983
4928
  */
4984
4929
  applyExtensionToInstance(self, extension) {
4930
+ this.applyExtension(self, extension, "extendInstance");
4931
+ }
4932
+ /**
4933
+ * Materializes an `{ actions, views, state }` bundle onto an instance. Shared
4934
+ * by `.extend()` (at creation time) and `applyExtensionToInstance` (on a live
4935
+ * instance); `subject` only names the caller in the invalid-key error.
4936
+ */
4937
+ applyExtension(self, extension, subject) {
4985
4938
  const { actions, views, state, ...rest } = extension;
4986
4939
  for (const key in rest) {
4987
- throw fail(`extendInstance should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'`);
4940
+ throw fail(`${subject} should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'`);
4988
4941
  }
4989
4942
  if (state) {
4990
4943
  this.instantiateVolatileState(self, state);
@@ -5016,28 +4969,6 @@ class ModelType extends ComplexType {
5016
4969
  }
5017
4970
  });
5018
4971
  }
5019
- preProcessSnapshot = preProcessor => {
5020
- const currentPreprocessor = this.preProcessor;
5021
- if (!currentPreprocessor) {
5022
- return this.cloneAndEnhance({ preProcessor });
5023
- }
5024
- else {
5025
- return this.cloneAndEnhance({
5026
- preProcessor: snapshot => currentPreprocessor(preProcessor(snapshot))
5027
- });
5028
- }
5029
- };
5030
- postProcessSnapshot = postProcessor => {
5031
- const currentPostprocessor = this.postProcessor;
5032
- if (!currentPostprocessor) {
5033
- return this.cloneAndEnhance({ postProcessor });
5034
- }
5035
- else {
5036
- return this.cloneAndEnhance({
5037
- postProcessor: snapshot => postProcessor(currentPostprocessor(snapshot))
5038
- });
5039
- }
5040
- };
5041
4972
  instantiate(parent, subpath, environment, initialValue) {
5042
4973
  const value = isStateTreeNode(initialValue)
5043
4974
  ? initialValue
@@ -5048,10 +4979,11 @@ class ModelType extends ComplexType {
5048
4979
  }
5049
4980
  initializeChildNodes(objNode, initialSnapshot = {}) {
5050
4981
  const type = objNode.type;
4982
+ const properties = type.properties;
5051
4983
  const result = {};
5052
- type.forAllProps((name, childType) => {
5053
- result[name] = childType.instantiate(objNode, name, undefined, initialSnapshot[name]);
5054
- });
4984
+ for (const name of type.propertyNames) {
4985
+ result[name] = properties[name].instantiate(objNode, name, undefined, initialSnapshot[name]);
4986
+ }
5055
4987
  return result;
5056
4988
  }
5057
4989
  createNewInstance(childNodes) {
@@ -5070,9 +5002,9 @@ class ModelType extends ComplexType {
5070
5002
  }
5071
5003
  }
5072
5004
  else {
5073
- this.forAllProps(name => {
5005
+ for (const name of this.propertyNames) {
5074
5006
  mobx._interceptReads(instance, name, node.unbox);
5075
- });
5007
+ }
5076
5008
  }
5077
5009
  this.initializers.reduce((self, fn) => fn(self), instance);
5078
5010
  // views, actions and volatile share the instance namespace with properties,
@@ -5083,12 +5015,12 @@ class ModelType extends ComplexType {
5083
5015
  // function/value stops being observable at all. The flag is only set on
5084
5016
  // success so a broken type keeps throwing on every create.
5085
5017
  if (!this.duplicateKeysChecked) {
5086
- this.forAllProps(name => {
5018
+ for (const name of this.propertyNames) {
5087
5019
  if (mobx.isComputedProp(instance, name) ||
5088
5020
  !mobx.isObservableProp(instance, name)) {
5089
5021
  throw fail(`${name} property is declared twice`);
5090
5022
  }
5091
- });
5023
+ }
5092
5024
  this.duplicateKeysChecked = true;
5093
5025
  }
5094
5026
  mobx.intercept(instance, this.willChange);
@@ -5141,24 +5073,16 @@ class ModelType extends ComplexType {
5141
5073
  }
5142
5074
  return res;
5143
5075
  }
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) {
5076
+ getChildNode(node, key) {
5077
+ if (!(key in this.properties)) {
5078
+ throw fail(`Not a value property: ${key}`);
5079
+ }
5150
5080
  const childNode = getPropObservable(node.storedValue, key).raw();
5151
5081
  if (!childNode) {
5152
5082
  throw fail(`Node not available for property ${key}`);
5153
5083
  }
5154
5084
  return childNode;
5155
5085
  }
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
- }
5162
5086
  getSnapshot(node, applyPostProcess = true) {
5163
5087
  const res = {};
5164
5088
  const storedValue = node.storedValue;
@@ -5210,9 +5134,9 @@ class ModelType extends ComplexType {
5210
5134
  applySnapshot(node, snapshot) {
5211
5135
  typecheckInternal(this, snapshot);
5212
5136
  const preProcessedSnapshot = this.applySnapshotPreProcessor(snapshot);
5213
- this.forAllProps(name => {
5137
+ for (const name of this.propertyNames) {
5214
5138
  node.storedValue[name] = preProcessedSnapshot[name];
5215
- });
5139
+ }
5216
5140
  }
5217
5141
  applySnapshotPreProcessor(snapshot) {
5218
5142
  const processor = this.preProcessor;
@@ -5245,12 +5169,6 @@ class ModelType extends ComplexType {
5245
5169
  }
5246
5170
  return typeCheckSuccess();
5247
5171
  }
5248
- forAllProps(fn) {
5249
- const properties = this.properties;
5250
- for (const key of this.propertyNames) {
5251
- fn(key, properties[key]);
5252
- }
5253
- }
5254
5172
  describe() {
5255
5173
  // optimization: cache
5256
5174
  return `{ ${this.propertyNames
@@ -5265,6 +5183,38 @@ class ModelType extends ComplexType {
5265
5183
  }
5266
5184
  }
5267
5185
  ModelType.prototype.applySnapshot = mobx.action(ModelType.prototype.applySnapshot);
5186
+ // Chain steps shared by every model type; see the field declarations in the
5187
+ // class for why they are not own slots.
5188
+ Object.assign(ModelType.prototype, {
5189
+ named(name) {
5190
+ return this.cloneAndEnhance({ name });
5191
+ },
5192
+ props(properties) {
5193
+ return this.cloneAndEnhance({ properties });
5194
+ },
5195
+ preProcessSnapshot(preProcessor) {
5196
+ const currentPreprocessor = this.preProcessor;
5197
+ if (!currentPreprocessor) {
5198
+ return this.cloneAndEnhance({ preProcessor });
5199
+ }
5200
+ else {
5201
+ return this.cloneAndEnhance({
5202
+ preProcessor: snapshot => currentPreprocessor(preProcessor(snapshot))
5203
+ });
5204
+ }
5205
+ },
5206
+ postProcessSnapshot(postProcessor) {
5207
+ const currentPostprocessor = this.postProcessor;
5208
+ if (!currentPostprocessor) {
5209
+ return this.cloneAndEnhance({ postProcessor });
5210
+ }
5211
+ else {
5212
+ return this.cloneAndEnhance({
5213
+ postProcessor: snapshot => postProcessor(currentPostprocessor(snapshot))
5214
+ });
5215
+ }
5216
+ }
5217
+ });
5268
5218
  /**
5269
5219
  * `types.model` - Creates a new model type by providing a name, properties, volatile state and actions.
5270
5220
  *
@@ -5274,7 +5224,7 @@ function model(...args) {
5274
5224
  if (devMode() && typeof args[0] !== "string" && args[1]) {
5275
5225
  throw fail("Model creation failed. First argument must be a string when two arguments are provided");
5276
5226
  }
5277
- const name = typeof args[0] === "string" ? args.shift() : "AnonymousModel";
5227
+ const name = typeof args[0] === "string" ? args.shift() : ANONYMOUS_MODEL_NAME;
5278
5228
  const properties = args.shift() || {};
5279
5229
  return new ModelType({ name, properties });
5280
5230
  }
@@ -5288,7 +5238,7 @@ function model(...args) {
5288
5238
  function compose(...args) {
5289
5239
  // TODO: just join the base type names if no name is provided
5290
5240
  const hasTypename = typeof args[0] === "string";
5291
- const typeName = hasTypename ? args[0] : "AnonymousModel";
5241
+ const typeName = hasTypename ? args[0] : ANONYMOUS_MODEL_NAME;
5292
5242
  if (hasTypename) {
5293
5243
  args.shift();
5294
5244
  }
@@ -5375,9 +5325,6 @@ class CoreType extends SimpleType {
5375
5325
  this.checker = checker;
5376
5326
  this.initializer = initializer;
5377
5327
  }
5378
- describe() {
5379
- return this.name;
5380
- }
5381
5328
  instantiate(parent, subpath, environment, initialValue) {
5382
5329
  return createScalarNode(this, parent, subpath, environment, initialValue);
5383
5330
  }
@@ -5514,6 +5461,12 @@ function getPrimitiveFactoryFromValue(value) {
5514
5461
  /**
5515
5462
  * Returns if a given value represents a primitive type.
5516
5463
  *
5464
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
5465
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
5466
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
5467
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
5468
+ * `isMapType`, `isModelType`) keep their predicate.
5469
+ *
5517
5470
  * @param type
5518
5471
  * @returns
5519
5472
  */
@@ -5538,15 +5491,15 @@ class Literal extends SimpleType {
5538
5491
  value;
5539
5492
  flags = TypeFlags.Literal;
5540
5493
  constructor(value) {
5541
- super(JSON.stringify(value));
5494
+ super();
5542
5495
  this.value = value;
5543
5496
  }
5497
+ computeName() {
5498
+ return JSON.stringify(this.value);
5499
+ }
5544
5500
  instantiate(parent, subpath, environment, initialValue) {
5545
5501
  return createScalarNode(this, parent, subpath, environment, initialValue);
5546
5502
  }
5547
- describe() {
5548
- return JSON.stringify(this.value);
5549
- }
5550
5503
  isValidSnapshot(value, context) {
5551
5504
  if (isPrimitive(value) && value === this.value) {
5552
5505
  return typeCheckSuccess();
@@ -5578,6 +5531,12 @@ function literal(value) {
5578
5531
  /**
5579
5532
  * Returns if a given value represents a literal type.
5580
5533
  *
5534
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
5535
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
5536
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
5537
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
5538
+ * `isMapType`, `isModelType`) keep their predicate.
5539
+ *
5581
5540
  * @param type
5582
5541
  * @returns
5583
5542
  */
@@ -5598,9 +5557,6 @@ class Refinement extends BaseType {
5598
5557
  this._predicate = _predicate;
5599
5558
  this._message = _message;
5600
5559
  }
5601
- describe() {
5602
- return this.name;
5603
- }
5604
5560
  instantiate(parent, subpath, environment, initialValue) {
5605
5561
  // create the child type
5606
5562
  return this._subtype.instantiate(parent, subpath, environment, initialValue);
@@ -5657,6 +5613,12 @@ function refinement(...args) {
5657
5613
  /**
5658
5614
  * Returns if a given value is a refinement type.
5659
5615
  *
5616
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
5617
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
5618
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
5619
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
5620
+ * `isMapType`, `isModelType`) keep their predicate.
5621
+ *
5660
5622
  * @param type
5661
5623
  * @returns
5662
5624
  */
@@ -5726,6 +5688,16 @@ function resolveModelType(type) {
5726
5688
  }
5727
5689
  return undefined;
5728
5690
  }
5691
+ // The quick-match paths below already know a type carries TypeFlags.Literal,
5692
+ // but `is()` still routes through BaseType.validate — a context array, an entry
5693
+ // object and a `$treenode` probe per property, per candidate member. A real
5694
+ // `Literal` stores its primitive, and its isValidSnapshot is exactly value
5695
+ // equality against it, so compare directly. The `instanceof` gate is load
5696
+ // bearing: wrappers (optional, refinement, snapshotProcessor, late, union)
5697
+ // inherit the Literal flag from what they wrap and must keep the full check.
5698
+ function matchesLiteral(type, value) {
5699
+ return type instanceof Literal ? type.value === value : type.is(value);
5700
+ }
5729
5701
  /**
5730
5702
  * @internal
5731
5703
  * @hidden
@@ -5843,10 +5815,10 @@ class Union extends BaseType {
5843
5815
  if (!model) {
5844
5816
  continue;
5845
5817
  }
5846
- const typeProp = model.properties.type;
5818
+ const typeProp = model.properties["type"];
5847
5819
  if (!typeProp ||
5848
5820
  !(typeProp.flags & TypeFlags.Literal) ||
5849
- !typeProp.is(discriminator)) {
5821
+ !matchesLiteral(typeProp, discriminator)) {
5850
5822
  continue;
5851
5823
  }
5852
5824
  if (found) {
@@ -5866,7 +5838,7 @@ class Union extends BaseType {
5866
5838
  this._allMembersDiscriminated = this._types.every(t => {
5867
5839
  const model = resolveModelType(t);
5868
5840
  const typeProp = model &&
5869
- model.properties.type;
5841
+ model.properties["type"];
5870
5842
  return !!typeProp && (typeProp.flags & TypeFlags.Literal) !== 0;
5871
5843
  });
5872
5844
  }
@@ -5940,7 +5912,7 @@ class Union extends BaseType {
5940
5912
  }
5941
5913
  // for literals, check exact value match
5942
5914
  if (flags & TypeFlags.Literal) {
5943
- if (type.is(value)) {
5915
+ if (matchesLiteral(type, value)) {
5944
5916
  return type;
5945
5917
  }
5946
5918
  }
@@ -5972,7 +5944,7 @@ class Union extends BaseType {
5972
5944
  // for literal types, verify the value matches exactly
5973
5945
  // this is critical for discriminated unions
5974
5946
  if (flags & TypeFlags.Literal) {
5975
- if (!propType.is(propValue)) {
5947
+ if (!matchesLiteral(propType, propValue)) {
5976
5948
  return false;
5977
5949
  }
5978
5950
  }
@@ -5994,7 +5966,8 @@ class Union extends BaseType {
5994
5966
  // - A failure is the definitive, scoped error only when every member is
5995
5967
  // discriminated; otherwise a catch-all could still accept the value, so
5996
5968
  // fall through to full validation.
5997
- if (isPlainObject(value) && !isStateTreeNode(value)) {
5969
+ const isSnapshotObject = isPlainObject(value) && !isStateTreeNode(value);
5970
+ if (isSnapshotObject) {
5998
5971
  const discriminator = value.type;
5999
5972
  if (typeof discriminator === "string") {
6000
5973
  const candidate = this._findCandidateByTypeDiscriminator(discriminator);
@@ -6010,7 +5983,7 @@ class Union extends BaseType {
6010
5983
  // for plain-object snapshots, prefer union members whose literal-typed
6011
5984
  // discriminator properties match the value (e.g. {type: "MsaView"})
6012
5985
  // so error output is scoped to the intended branch instead of every member
6013
- const candidates = isPlainObject(value) && !isStateTreeNode(value)
5986
+ const candidates = isSnapshotObject
6014
5987
  ? this._types.filter(t => this.snapshotLooksLikeType(value, t))
6015
5988
  : [];
6016
5989
  const typesToValidate = candidates.length > 0 ? candidates : this._types;
@@ -6047,6 +6020,22 @@ Object.assign(Union.prototype, {
6047
6020
  _discriminatorCache: undefined,
6048
6021
  _allMembersDiscriminated: undefined
6049
6022
  });
6023
+ const unionInternRoot = {};
6024
+ function internUnion(types) {
6025
+ let node = unionInternRoot;
6026
+ for (const type of types) {
6027
+ const level = (node.next ??= new WeakMap());
6028
+ const existing = level.get(type);
6029
+ if (existing) {
6030
+ node = existing;
6031
+ }
6032
+ else {
6033
+ node = {};
6034
+ level.set(type, node);
6035
+ }
6036
+ }
6037
+ return (node.union ??= new Union(types));
6038
+ }
6050
6039
  /**
6051
6040
  * `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`.
6052
6041
  *
@@ -6075,11 +6064,17 @@ function union(...args) {
6075
6064
  assertIsType(type, options ? i + 2 : i + 1);
6076
6065
  });
6077
6066
  }
6078
- return new Union(types, options);
6067
+ return options === undefined ? internUnion(types) : new Union(types, options);
6079
6068
  }
6080
6069
  /**
6081
6070
  * Returns if a given value represents a union type.
6082
6071
  *
6072
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
6073
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
6074
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
6075
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
6076
+ * `isMapType`, `isModelType`) keep their predicate.
6077
+ *
6083
6078
  * @param type
6084
6079
  * @returns
6085
6080
  */
@@ -6258,13 +6253,41 @@ const undefinedAsOptionalValues = [undefined];
6258
6253
  /**
6259
6254
  * Returns if a value represents an optional type.
6260
6255
  *
6261
- * @template IT
6256
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
6257
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
6258
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
6259
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
6260
+ * `isMapType`, `isModelType`) keep their predicate.
6261
+ *
6262
6262
  * @param type
6263
6263
  * @returns
6264
6264
  */
6265
6265
  function isOptionalType(type) {
6266
6266
  return isType(type) && (type.flags & TypeFlags.Optional) > 0;
6267
6267
  }
6268
+ function isRecord(value) {
6269
+ return typeof value === "object" && value !== null;
6270
+ }
6271
+ /**
6272
+ * The identifier attribute of the model a stripDefault ultimately wraps, or
6273
+ * `null` when there is none. Drills through the single-subtype wrappers the way
6274
+ * `resolveModelType` in union.ts does — that one is module-private there, and
6275
+ * this file may not reach into it.
6276
+ *
6277
+ * `null` is also the answer for an unresolved `types.late`, which only costs the
6278
+ * short-circuit, never correctness: the full structural walk still runs.
6279
+ */
6280
+ function resolveIdentifierAttribute(type) {
6281
+ let current = type;
6282
+ for (let depth = 0; current && depth < 20; depth++) {
6283
+ if (current instanceof ModelType) {
6284
+ return current.identifierAttribute ? current.identifierAttribute : null;
6285
+ }
6286
+ const wrapper = current;
6287
+ current = wrapper._subtype ?? wrapper.getSubType?.(false);
6288
+ }
6289
+ return null;
6290
+ }
6268
6291
  /**
6269
6292
  * Compare a child snapshot to a stripped-default's reference snapshot: identity
6270
6293
  * for primitives, structural for objects/arrays.
@@ -6343,6 +6366,22 @@ function defaultSnapshotEquals(a, b) {
6343
6366
  * @internal
6344
6367
  */
6345
6368
  class StripDefaultValue extends OptionalValue {
6369
+ equalsDefault(snapshot, defaultSnapshot) {
6370
+ let identifierAttribute = this._identifierAttribute;
6371
+ if (identifierAttribute === undefined) {
6372
+ identifierAttribute = resolveIdentifierAttribute(this.getSubTypes());
6373
+ this._identifierAttribute = identifierAttribute;
6374
+ }
6375
+ // an identified model's snapshot normally has the same shape as the default
6376
+ // and differs only here, so answering from one key beats walking every key
6377
+ // (including nested objects) until the walk reaches it
6378
+ return identifierAttribute !== null &&
6379
+ isRecord(snapshot) &&
6380
+ isRecord(defaultSnapshot) &&
6381
+ snapshot[identifierAttribute] !== defaultSnapshot[identifierAttribute]
6382
+ ? false
6383
+ : defaultSnapshotEquals(snapshot, defaultSnapshot);
6384
+ }
6346
6385
  shouldStripFromSnapshot(snapshot) {
6347
6386
  if (!this._defaultSnapshot) {
6348
6387
  // instantiate the subtype detached with the default and read the node's
@@ -6351,11 +6390,35 @@ class StripDefaultValue extends OptionalValue {
6351
6390
  const node = this.getSubTypes().instantiate(null, "", undefined, this.getDefaultInstanceOrSnapshot());
6352
6391
  this._defaultSnapshot = { value: node.snapshot };
6353
6392
  }
6354
- return defaultSnapshotEquals(snapshot, this._defaultSnapshot.value);
6393
+ const defaultSnapshot = this._defaultSnapshot.value;
6394
+ let result;
6395
+ if (isRecord(snapshot)) {
6396
+ // a child node's snapshot is a keepAlive computed, so a stable reference
6397
+ // means unchanged content: the same object always gets the same answer
6398
+ let cache = this._equalsDefaultCache;
6399
+ if (!cache) {
6400
+ cache = new WeakMap();
6401
+ this._equalsDefaultCache = cache;
6402
+ }
6403
+ const cached = cache.get(snapshot);
6404
+ if (cached === undefined) {
6405
+ result = this.equalsDefault(snapshot, defaultSnapshot);
6406
+ cache.set(snapshot, result);
6407
+ }
6408
+ else {
6409
+ result = cached;
6410
+ }
6411
+ }
6412
+ else {
6413
+ result = defaultSnapshotEquals(snapshot, defaultSnapshot);
6414
+ }
6415
+ return result;
6355
6416
  }
6356
6417
  }
6357
6418
  Object.assign(StripDefaultValue.prototype, {
6358
- _defaultSnapshot: undefined
6419
+ _defaultSnapshot: undefined,
6420
+ _identifierAttribute: undefined,
6421
+ _equalsDefaultCache: undefined
6359
6422
  });
6360
6423
  /**
6361
6424
  * Whether `type` is a strip-default optional whose current child `snapshot`
@@ -6504,6 +6567,12 @@ function late(nameOrType, maybeType) {
6504
6567
  /**
6505
6568
  * Returns if a given value represents a late type.
6506
6569
  *
6570
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
6571
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
6572
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
6573
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
6574
+ * `isMapType`, `isModelType`) keep their predicate.
6575
+ *
6507
6576
  * @param type
6508
6577
  * @returns
6509
6578
  */
@@ -6672,6 +6741,12 @@ function frozen(arg) {
6672
6741
  /**
6673
6742
  * Returns if a given value represents a frozen type.
6674
6743
  *
6744
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
6745
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
6746
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
6747
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
6748
+ * `isMapType`, `isModelType`) keep their predicate.
6749
+ *
6675
6750
  * @param type
6676
6751
  * @returns
6677
6752
  */
@@ -6762,9 +6837,6 @@ class BaseReferenceType extends SimpleType {
6762
6837
  computeName() {
6763
6838
  return `reference(${this.targetType.name})`;
6764
6839
  }
6765
- describe() {
6766
- return this.name;
6767
- }
6768
6840
  isAssignableFrom(type) {
6769
6841
  return this.targetType.isAssignableFrom(type);
6770
6842
  }
@@ -6902,9 +6974,6 @@ class BaseReferenceType extends SimpleType {
6902
6974
  * @hidden
6903
6975
  */
6904
6976
  class IdentifierReferenceType extends BaseReferenceType {
6905
- constructor(targetType, onInvalidated) {
6906
- super(targetType, onInvalidated);
6907
- }
6908
6977
  getValue(storedRefNode) {
6909
6978
  if (!storedRefNode.isAlive) {
6910
6979
  return undefined;
@@ -7020,6 +7089,12 @@ function reference(subType, options) {
7020
7089
  /**
7021
7090
  * Returns if a given value represents a reference type.
7022
7091
  *
7092
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
7093
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
7094
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
7095
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
7096
+ * `isMapType`, `isModelType`) keep their predicate.
7097
+ *
7023
7098
  * @param type
7024
7099
  * @returns
7025
7100
  */
@@ -7098,9 +7173,6 @@ class IdentifierType extends BaseIdentifierType {
7098
7173
  constructor() {
7099
7174
  super(`identifier`, "string");
7100
7175
  }
7101
- describe() {
7102
- return `identifier`;
7103
- }
7104
7176
  }
7105
7177
  /**
7106
7178
  * @internal
@@ -7110,12 +7182,6 @@ class IdentifierNumberType extends BaseIdentifierType {
7110
7182
  constructor() {
7111
7183
  super("identifierNumber", "number");
7112
7184
  }
7113
- getSnapshot(node) {
7114
- return node.storedValue;
7115
- }
7116
- describe() {
7117
- return `identifierNumber`;
7118
- }
7119
7185
  }
7120
7186
  /**
7121
7187
  * `types.identifier` - Identifiers are used to make references, lifecycle events and reconciling works.
@@ -7152,6 +7218,12 @@ const identifierNumber = new IdentifierNumberType();
7152
7218
  /**
7153
7219
  * Returns if a given value represents an identifier type.
7154
7220
  *
7221
+ * Returns a plain `boolean`, not a `type is IT` predicate: with the parameter
7222
+ * typed as `IT`, narrowing to `IT` was a no-op in the positive branch while
7223
+ * collapsing the negative one to `never`, so `if (!isX(t)) { t.name }` failed
7224
+ * to compile. Guards with a distinct narrowing target (`isArrayType`,
7225
+ * `isMapType`, `isModelType`) keep their predicate.
7226
+ *
7155
7227
  * @param type
7156
7228
  * @returns
7157
7229
  */
@@ -7240,9 +7312,6 @@ class CustomType extends SimpleType {
7240
7312
  super(options.name);
7241
7313
  this.options = options;
7242
7314
  }
7243
- describe() {
7244
- return this.name;
7245
- }
7246
7315
  isValidSnapshot(value, context) {
7247
7316
  if (this.options.isTargetType(value)) {
7248
7317
  return typeCheckSuccess();
@@ -7451,6 +7520,7 @@ exports.addMiddleware = addMiddleware;
7451
7520
  exports.applyAction = applyAction;
7452
7521
  exports.applyPatch = applyPatch;
7453
7522
  exports.applySnapshot = applySnapshot;
7523
+ exports.cannotDetermineSubtype = cannotDetermineSubtype;
7454
7524
  exports.cast = cast;
7455
7525
  exports.castFlowReturn = castFlowReturn;
7456
7526
  exports.castToReferenceSnapshot = castToReferenceSnapshot;