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