@jbrowse/mobx-state-tree 5.9.2 → 5.10.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/README.md +4 -2
- package/dist/mobx-state-tree.cjs +132 -13
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +132 -13
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
package/dist/mobx-state-tree.mjs
CHANGED
|
@@ -2699,16 +2699,27 @@ function typecheck(type, value) {
|
|
|
2699
2699
|
}
|
|
2700
2700
|
}
|
|
2701
2701
|
const MAX_ERRORS_REPORTED = 10;
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2702
|
+
/**
|
|
2703
|
+
* @internal
|
|
2704
|
+
* @hidden
|
|
2705
|
+
* Format a list of validation errors as bullet-style lines, capped at
|
|
2706
|
+
* MAX_ERRORS_REPORTED entries with an overflow suffix. Shared by typecheck()
|
|
2707
|
+
* and union's `noMatchMessage` so prod-build union failures can include
|
|
2708
|
+
* candidate validation errors using the same formatting.
|
|
2709
|
+
*/
|
|
2710
|
+
function formatValidationErrorLines(errors) {
|
|
2706
2711
|
const shown = errors.slice(0, MAX_ERRORS_REPORTED).map(toErrorString);
|
|
2707
2712
|
const overflow = errors.length - shown.length;
|
|
2708
2713
|
if (overflow > 0) {
|
|
2709
2714
|
shown.push(`(… and ${overflow} more error${overflow === 1 ? "" : "s"})`);
|
|
2710
2715
|
}
|
|
2711
|
-
return
|
|
2716
|
+
return shown;
|
|
2717
|
+
}
|
|
2718
|
+
function validationErrorsToString(type, value, errors) {
|
|
2719
|
+
if (errors.length === 0) {
|
|
2720
|
+
return undefined;
|
|
2721
|
+
}
|
|
2722
|
+
return (`Error while converting ${shortenPrintValue(prettyPrintValue(value))} to \`${type.name}\`:\n\n ` + formatValidationErrorLines(errors).join("\n "));
|
|
2712
2723
|
}
|
|
2713
2724
|
|
|
2714
2725
|
let identifierCacheId = 0;
|
|
@@ -5389,6 +5400,25 @@ function enumeration(name, options) {
|
|
|
5389
5400
|
return type;
|
|
5390
5401
|
}
|
|
5391
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
|
+
}
|
|
5392
5422
|
/**
|
|
5393
5423
|
* @internal
|
|
5394
5424
|
* @hidden
|
|
@@ -5438,10 +5468,74 @@ class Union extends BaseType {
|
|
|
5438
5468
|
return type.reconcile(current, newValue, parent, subpath);
|
|
5439
5469
|
}
|
|
5440
5470
|
noMatchMessage(value) {
|
|
5441
|
-
const
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5471
|
+
const base = `No matching type for union ${this.name}`;
|
|
5472
|
+
if (!isPlainObject(value)) {
|
|
5473
|
+
return base;
|
|
5474
|
+
}
|
|
5475
|
+
const discriminator = value.type;
|
|
5476
|
+
if (typeof discriminator !== "string") {
|
|
5477
|
+
return base;
|
|
5478
|
+
}
|
|
5479
|
+
const baseWithDiscriminator = `${base} for snapshot with type "${discriminator}"`;
|
|
5480
|
+
// If exactly one union member has a literal `type` property matching the
|
|
5481
|
+
// snapshot's discriminator, run its validate() so we can append the
|
|
5482
|
+
// property-level reasons it didn't match. This converts the bare prod-build
|
|
5483
|
+
// "no matching type" into something diagnosable (e.g. which field was the
|
|
5484
|
+
// wrong type, which required field was missing) without re-bloating the
|
|
5485
|
+
// message back to every-member's full describe() output.
|
|
5486
|
+
const candidate = this._findCandidateByTypeDiscriminator(discriminator);
|
|
5487
|
+
if (!candidate) {
|
|
5488
|
+
return baseWithDiscriminator;
|
|
5489
|
+
}
|
|
5490
|
+
const errors = candidate.validate(value, [
|
|
5491
|
+
{ path: "", type: candidate }
|
|
5492
|
+
]);
|
|
5493
|
+
if (errors.length === 0) {
|
|
5494
|
+
return baseWithDiscriminator;
|
|
5495
|
+
}
|
|
5496
|
+
return (`${baseWithDiscriminator}:\n ` +
|
|
5497
|
+
formatValidationErrorLines(errors).join("\n "));
|
|
5498
|
+
}
|
|
5499
|
+
_findCandidateByTypeDiscriminator(discriminator) {
|
|
5500
|
+
let found;
|
|
5501
|
+
for (const t of this._types) {
|
|
5502
|
+
const model = resolveModelType(t);
|
|
5503
|
+
if (!model) {
|
|
5504
|
+
continue;
|
|
5505
|
+
}
|
|
5506
|
+
const typeProp = model.properties.type;
|
|
5507
|
+
if (!typeProp ||
|
|
5508
|
+
!(typeProp.flags & TypeFlags.Literal) ||
|
|
5509
|
+
!typeProp.is(discriminator)) {
|
|
5510
|
+
continue;
|
|
5511
|
+
}
|
|
5512
|
+
if (found) {
|
|
5513
|
+
// Ambiguous (two members declare the same `type` literal). Fall back
|
|
5514
|
+
// to the short message rather than picking arbitrarily.
|
|
5515
|
+
return undefined;
|
|
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.
|
|
5520
|
+
found = t;
|
|
5521
|
+
}
|
|
5522
|
+
return found;
|
|
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;
|
|
5445
5539
|
}
|
|
5446
5540
|
determineType(value, reconcileCurrentType) {
|
|
5447
5541
|
// try the dispatcher, if defined
|
|
@@ -5530,11 +5624,14 @@ class Union extends BaseType {
|
|
|
5530
5624
|
}
|
|
5531
5625
|
snapshotLooksLikeType(value, type) {
|
|
5532
5626
|
// for model types, check if snapshot has all the required property keys
|
|
5533
|
-
// and that any literal-typed properties match exactly
|
|
5534
|
-
|
|
5535
|
-
|
|
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;
|
|
5536
5633
|
// use cached propertyNames from ModelType instead of Object.keys()
|
|
5537
|
-
for (const key of
|
|
5634
|
+
for (const key of model.propertyNames) {
|
|
5538
5635
|
const propType = props[key];
|
|
5539
5636
|
const isOptional = propType.flags & TypeFlags.Optional;
|
|
5540
5637
|
const propValue = value[key];
|
|
@@ -5561,6 +5658,28 @@ class Union extends BaseType {
|
|
|
5561
5658
|
if (this._dispatcher) {
|
|
5562
5659
|
return this._dispatcher(value).validate(value, context);
|
|
5563
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
|
+
}
|
|
5564
5683
|
// for plain-object snapshots, prefer union members whose literal-typed
|
|
5565
5684
|
// discriminator properties match the value (e.g. {type: "MsaView"})
|
|
5566
5685
|
// so error output is scoped to the intended branch instead of every member
|