@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.
@@ -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
- const type = this.type;
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(node.identifier);
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 keys = Object.keys(childNodes);
3057
- if (!keys.length) {
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
- // iterate a copy so (un)registrations during emit don't disturb this pass
3281
- // and reentrant emits stay correct
3282
- const handlers = this.handlers.slice();
3283
- handlers.forEach(f => f(...args));
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
- const currentKeys = {};
4155
- Array.from(target.keys()).forEach(key => {
4156
- currentKeys[key] = false;
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
- currentKeys[`${key}`] = true;
4155
+ staleKeys.delete(key);
4163
4156
  }
4164
4157
  }
4165
- Object.keys(currentKeys).forEach(key => {
4166
- if (currentKeys[key] === false) {
4167
- target.delete(key);
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], [change.index]);
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, added.map((_, i) => index + i));
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
- function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths) {
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 = `${newPaths[i]}`;
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 (oldNode.identifier !== null &&
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 keysList.reduce((props, key) => {
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
- }, { ...declaredProps });
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) || !isObservableProp(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 res = [];
4993
- this.forAllProps(name => {
4994
- res.push(this.getChildNode(node, name));
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
- getChildNode(node, key) {
4999
- if (!(key in this.properties)) {
5000
- throw fail(`Not a value property: ${key}`);
5001
- }
5002
- const adm = _getAdministration(node.storedValue, key);
5003
- const childNode = adm.raw?.();
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
- this.forAllProps((name, type) => {
5012
- getAtom(node.storedValue, name).reportObserved();
5013
- const snapshot = this.getChildNode(node, name).snapshot;
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(type, snapshot)) {
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
- this.propertyNames.forEach(key => fn(key, this.properties[key]));
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.forEach(type => {
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
- const typesToCheck = reconcileCurrentType
5718
- ? [
5719
- reconcileCurrentType,
5720
- ...this._types.filter(t => t !== reconcileCurrentType)
5721
- ]
5722
- : this._types;
5723
- for (const type of typesToCheck) {
5724
- if (this.snapshotLooksLikeType(value, type)) {
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(flattenTypeErrors(allErrors));
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 defaultInstanceOrSnapshot = typeof this._defaultValue === "function"
5940
- ? this._defaultValue()
5941
- : this._defaultValue;
5942
- // while static values are already snapshots and checked on types.optional
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
- return defaultInstanceOrSnapshot;
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. Mirrors
6040
- * the legacy hand-rolled comparison: identity for primitives, structural for
6041
- * objects/arrays.
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 === "object" &&
6048
- a !== null &&
6049
- typeof b === "object" &&
6050
- b !== null) {
6051
- // Cheap structural short-circuit before the full stringify compare: a
6052
- // value of a different size can't equal the default, so the common strip
6053
- // case (non-empty value vs an empty `[]`/`{}` default) avoids stringifying
6054
- // a potentially large snapshot on every getSnapshot.
6055
- if (Array.isArray(a) !== Array.isArray(b)) {
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
- const aSize = Array.isArray(a) ? a.length : Object.keys(a).length;
6059
- const bSize = Array.isArray(b) ? b.length : Object.keys(b).length;
6060
- if (aSize !== bSize) {
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 false;
6123
+ return true;
6066
6124
  }
6067
6125
  /**
6068
6126
  * An optional type that additionally omits its key from a parent model's