@jbrowse/mobx-state-tree 5.9.3 → 5.10.1

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 CHANGED
@@ -1501,6 +1501,8 @@ declare function snapshotProcessor<IT extends IAnyType, CustomC = _NotCustomized
1501
1501
  /** @hidden */
1502
1502
  interface IMapType<IT extends IAnyType> extends IType<IKeyValueMap<IT["CreationType"]> | undefined, IKeyValueMap<IT["SnapshotType"]>, IMSTMap<IT>> {
1503
1503
  hooks(hooks: IHooksGetter<IMSTMap<IT>>): IMapType<IT>;
1504
+ /** the value type of the map */
1505
+ getChildType(): IAnyType;
1504
1506
  }
1505
1507
  /** @hidden */
1506
1508
  interface IMSTMap<IT extends IAnyType> {
@@ -1583,6 +1585,8 @@ interface IMSTArray<IT extends IAnyType> extends IObservableArray<IT["Type"]> {
1583
1585
  /** @hidden */
1584
1586
  interface IArrayType<IT extends IAnyType> extends IType<readonly IT["CreationType"][] | undefined, IT["SnapshotType"][], IMSTArray<IT>> {
1585
1587
  hooks(hooks: IHooksGetter<IMSTArray<IAnyType>>): IArrayType<IT>;
1588
+ /** the element type of the array */
1589
+ getChildType(): IAnyType;
1586
1590
  }
1587
1591
  /**
1588
1592
  * `types.array` - Creates an index based collection type who's children are all of a uniform declared type.
@@ -5402,6 +5402,25 @@ function enumeration(name, options) {
5402
5402
  return type;
5403
5403
  }
5404
5404
 
5405
+ // Drill through single-subtype wrappers — optional(), refinement(),
5406
+ // snapshotProcessor(), late() — to the underlying ModelType. Discriminated-
5407
+ // union scoping keys on a member's literal `type` property, but real-world
5408
+ // members are rarely bare models (jbrowse config schemas, for instance, are
5409
+ // always optional(model) or optional(snapshotProcessor(model))). Without this
5410
+ // the scoping never engages and every failure prints every member's full
5411
+ // structure. Wrappers expose their child as `_subtype` (optional/refinement/
5412
+ // snapshotProcessor) or via `getSubType()` (late); bounded to avoid cycles.
5413
+ function resolveModelType(type) {
5414
+ let current = type;
5415
+ for (let depth = 0; current && depth < 20; depth++) {
5416
+ if (current instanceof ModelType) {
5417
+ return current;
5418
+ }
5419
+ const wrapper = current;
5420
+ current = wrapper._subtype ?? wrapper.getSubType?.(false);
5421
+ }
5422
+ return undefined;
5423
+ }
5405
5424
  /**
5406
5425
  * @internal
5407
5426
  * @hidden
@@ -5482,11 +5501,11 @@ class Union extends BaseType {
5482
5501
  _findCandidateByTypeDiscriminator(discriminator) {
5483
5502
  let found;
5484
5503
  for (const t of this._types) {
5485
- if (!(t instanceof ModelType)) {
5504
+ const model = resolveModelType(t);
5505
+ if (!model) {
5486
5506
  continue;
5487
5507
  }
5488
- const typeProp = t.properties
5489
- .type;
5508
+ const typeProp = model.properties.type;
5490
5509
  if (!typeProp ||
5491
5510
  !(typeProp.flags & TypeFlags.Literal) ||
5492
5511
  !typeProp.is(discriminator)) {
@@ -5497,10 +5516,29 @@ class Union extends BaseType {
5497
5516
  // to the short message rather than picking arbitrarily.
5498
5517
  return undefined;
5499
5518
  }
5519
+ // Return the original (possibly wrapped) member, not the unwrapped
5520
+ // model, so validate()/is() still apply optional defaults and any
5521
+ // snapshotProcessor pre-processing.
5500
5522
  found = t;
5501
5523
  }
5502
5524
  return found;
5503
5525
  }
5526
+ // True when every member resolves to a model carrying a literal `type`
5527
+ // discriminator — i.e. a fully discriminated union, where a snapshot's `type`
5528
+ // uniquely identifies the intended member and no untagged catch-all member
5529
+ // could also accept it. Cached: membership is fixed at construction.
5530
+ _allMembersDiscriminated;
5531
+ allMembersDiscriminated() {
5532
+ if (this._allMembersDiscriminated === undefined) {
5533
+ this._allMembersDiscriminated = this._types.every(t => {
5534
+ const model = resolveModelType(t);
5535
+ const typeProp = model &&
5536
+ model.properties.type;
5537
+ return !!typeProp && (typeProp.flags & TypeFlags.Literal) !== 0;
5538
+ });
5539
+ }
5540
+ return this._allMembersDiscriminated;
5541
+ }
5504
5542
  determineType(value, reconcileCurrentType) {
5505
5543
  // try the dispatcher, if defined
5506
5544
  if (this._dispatcher) {
@@ -5588,11 +5626,14 @@ class Union extends BaseType {
5588
5626
  }
5589
5627
  snapshotLooksLikeType(value, type) {
5590
5628
  // for model types, check if snapshot has all the required property keys
5591
- // and that any literal-typed properties match exactly
5592
- if (type instanceof ModelType) {
5593
- const props = type.properties;
5629
+ // and that any literal-typed properties match exactly. Unwrap optional() /
5630
+ // snapshotProcessor() / refinement() / late() so wrapped members (e.g.
5631
+ // jbrowse config schemas, which are always optional(model)) still match.
5632
+ const model = resolveModelType(type);
5633
+ if (model) {
5634
+ const props = model.properties;
5594
5635
  // use cached propertyNames from ModelType instead of Object.keys()
5595
- for (const key of type.propertyNames) {
5636
+ for (const key of model.propertyNames) {
5596
5637
  const propType = props[key];
5597
5638
  const isOptional = propType.flags & TypeFlags.Optional;
5598
5639
  const propValue = value[key];
@@ -5619,6 +5660,28 @@ class Union extends BaseType {
5619
5660
  if (this._dispatcher) {
5620
5661
  return this._dispatcher(value).validate(value, context);
5621
5662
  }
5663
+ // For plain-object snapshots carrying a `type` discriminator, validate only
5664
+ // the single member whose literal `type` matches.
5665
+ // - Clean validation short-circuits to success only when it is sound to skip
5666
+ // the other members: an eager union (first match wins) or a fully
5667
+ // discriminated one (no untagged catch-all could also match). A non-eager
5668
+ // union with a catch-all must still fall through so ambiguity is counted.
5669
+ // - A failure is the definitive, scoped error only when every member is
5670
+ // discriminated; otherwise a catch-all could still accept the value, so
5671
+ // fall through to full validation.
5672
+ if (isPlainObject(value) && !isStateTreeNode(value)) {
5673
+ const discriminator = value.type;
5674
+ if (typeof discriminator === "string") {
5675
+ const candidate = this._findCandidateByTypeDiscriminator(discriminator);
5676
+ if (candidate) {
5677
+ const errors = candidate.validate(value, context);
5678
+ const cleanAndUnique = errors.length === 0 && this._eager;
5679
+ if (cleanAndUnique || this.allMembersDiscriminated()) {
5680
+ return errors;
5681
+ }
5682
+ }
5683
+ }
5684
+ }
5622
5685
  // for plain-object snapshots, prefer union members whose literal-typed
5623
5686
  // discriminator properties match the value (e.g. {type: "MsaView"})
5624
5687
  // so error output is scoped to the intended branch instead of every member