@jbrowse/mobx-state-tree 6.4.0 → 6.5.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.
@@ -5723,7 +5723,7 @@ class Union extends BaseType {
5723
5723
  return cached;
5724
5724
  }
5725
5725
  let result = TypeFlags.Union;
5726
- for (const type of this._types) {
5726
+ for (const type of this.members()) {
5727
5727
  result |= type.flags;
5728
5728
  }
5729
5729
  if (!(result & TypeFlags.Late)) {
@@ -5732,12 +5732,14 @@ class Union extends BaseType {
5732
5732
  return result;
5733
5733
  }
5734
5734
  computeName() {
5735
- return `(${this._types.map(type => type.name).join(" | ")})`;
5735
+ return `(${this.members()
5736
+ .map(type => type.name)
5737
+ .join(" | ")})`;
5736
5738
  }
5737
5739
  constructor(_types, options) {
5738
5740
  super();
5739
5741
  this._types = _types;
5740
- // read the two options directly rather than spreading defaults into a fresh
5742
+ // read the options directly rather than spreading defaults into a fresh
5741
5743
  // object: this constructor runs once per config slot in jbrowse, and the
5742
5744
  // merged object was allocated only to be read twice and dropped
5743
5745
  if (options !== undefined) {
@@ -5747,13 +5749,23 @@ class Union extends BaseType {
5747
5749
  if (options.eager === false) {
5748
5750
  this._eager = false;
5749
5751
  }
5752
+ if (options.name !== undefined) {
5753
+ this.name = options.name;
5754
+ }
5750
5755
  }
5751
5756
  }
5757
+ // The one point membership is read through, so DynamicUnion can substitute
5758
+ // its thunk without duplicating any of the scanning logic below.
5759
+ members() {
5760
+ return this._types;
5761
+ }
5752
5762
  isAssignableFrom(type) {
5753
- return this._types.some(subType => subType.isAssignableFrom(type));
5763
+ return this.members().some(subType => subType.isAssignableFrom(type));
5754
5764
  }
5755
5765
  describe() {
5756
- return `(${this._types.map(factory => factory.describe()).join(" | ")})`;
5766
+ return `(${this.members()
5767
+ .map(factory => factory.describe())
5768
+ .join(" | ")})`;
5757
5769
  }
5758
5770
  instantiate(parent, subpath, environment, initialValue) {
5759
5771
  const type = this.determineType(initialValue, undefined);
@@ -5808,7 +5820,7 @@ class Union extends BaseType {
5808
5820
  }
5809
5821
  _scanForTypeDiscriminator(discriminator) {
5810
5822
  let found;
5811
- for (const t of this._types) {
5823
+ for (const t of this.members()) {
5812
5824
  const model = resolveModelType(t);
5813
5825
  if (!model) {
5814
5826
  continue;
@@ -5833,15 +5845,18 @@ class Union extends BaseType {
5833
5845
  }
5834
5846
  allMembersDiscriminated() {
5835
5847
  if (this._allMembersDiscriminated === undefined) {
5836
- this._allMembersDiscriminated = this._types.every(t => {
5837
- const model = resolveModelType(t);
5838
- const typeProp = model &&
5839
- model.properties["type"];
5840
- return !!typeProp && (typeProp.flags & TypeFlags.Literal) !== 0;
5841
- });
5848
+ this._allMembersDiscriminated = this.computeAllMembersDiscriminated();
5842
5849
  }
5843
5850
  return this._allMembersDiscriminated;
5844
5851
  }
5852
+ computeAllMembersDiscriminated() {
5853
+ return this.members().every(t => {
5854
+ const model = resolveModelType(t);
5855
+ const typeProp = model &&
5856
+ model.properties["type"];
5857
+ return !!typeProp && (typeProp.flags & TypeFlags.Literal) !== 0;
5858
+ });
5859
+ }
5845
5860
  determineType(value, reconcileCurrentType) {
5846
5861
  // try the dispatcher, if defined
5847
5862
  if (this._dispatcher) {
@@ -5862,9 +5877,9 @@ class Union extends BaseType {
5862
5877
  if (reconcileCurrentType.is(value)) {
5863
5878
  return reconcileCurrentType;
5864
5879
  }
5865
- return this._types.find(type => type !== reconcileCurrentType && type.is(value));
5880
+ return this.members().find(type => type !== reconcileCurrentType && type.is(value));
5866
5881
  }
5867
- return this._types.find(type => type.is(value));
5882
+ return this.members().find(type => type.is(value));
5868
5883
  }
5869
5884
  tryQuickMatch(value, reconcileCurrentType) {
5870
5885
  // state tree nodes need full type compatibility checking
@@ -5884,7 +5899,7 @@ class Union extends BaseType {
5884
5899
  this.snapshotLooksLikeType(value, reconcileCurrentType)) {
5885
5900
  return reconcileCurrentType;
5886
5901
  }
5887
- for (const type of this._types) {
5902
+ for (const type of this.members()) {
5888
5903
  if (type !== reconcileCurrentType &&
5889
5904
  this.snapshotLooksLikeType(value, type)) {
5890
5905
  return type;
@@ -5894,7 +5909,7 @@ class Union extends BaseType {
5894
5909
  }
5895
5910
  tryMatchPrimitive(value) {
5896
5911
  const valueType = typeof value;
5897
- for (const type of this._types) {
5912
+ for (const type of this.members()) {
5898
5913
  const flags = type.flags;
5899
5914
  if ((valueType === "string" && flags & TypeFlags.String) ||
5900
5915
  (valueType === "number" &&
@@ -5981,10 +5996,11 @@ class Union extends BaseType {
5981
5996
  // for plain-object snapshots, prefer union members whose literal-typed
5982
5997
  // discriminator properties match the value (e.g. {type: "MsaView"})
5983
5998
  // so error output is scoped to the intended branch instead of every member
5999
+ const members = this.members();
5984
6000
  const candidates = isSnapshotObject
5985
- ? this._types.filter(t => this.snapshotLooksLikeType(value, t))
6001
+ ? members.filter(t => this.snapshotLooksLikeType(value, t))
5986
6002
  : [];
5987
- const typesToValidate = candidates.length > 0 ? candidates : this._types;
6003
+ const typesToValidate = candidates.length > 0 ? candidates : members;
5988
6004
  const allErrors = [];
5989
6005
  let applicableTypes = 0;
5990
6006
  for (const type of typesToValidate) {
@@ -6007,7 +6023,7 @@ class Union extends BaseType {
6007
6023
  return typeCheckFailure(context, value, "No type is applicable for the union").concat(allErrors.flat());
6008
6024
  }
6009
6025
  getSubTypes() {
6010
- return this._types;
6026
+ return this.members();
6011
6027
  }
6012
6028
  }
6013
6029
  // Defaults shared by every union; see the field declarations at the top of the
@@ -6018,6 +6034,56 @@ Object.assign(Union.prototype, {
6018
6034
  _discriminatorCache: undefined,
6019
6035
  _allMembersDiscriminated: undefined
6020
6036
  });
6037
+ /**
6038
+ * @internal
6039
+ * @hidden
6040
+ *
6041
+ * A union whose membership is read fresh from a thunk on every operation, so
6042
+ * members that come into existence after the union was built — a registry of
6043
+ * pluggable types whose modules load on demand — still dispatch, validate, and
6044
+ * satisfy references. That last one is the reason this exists: reference
6045
+ * resolution asks the target type `isAssignableFrom(node.type)` through the
6046
+ * identifier cache, and a membership list frozen at construction would reject
6047
+ * every late-loaded member's instances.
6048
+ *
6049
+ * Every membership-derived cache the base class keeps is bypassed here: flags
6050
+ * and the folded name are recomputed per read, and the discriminator scan is
6051
+ * uncached so a miss stays retryable once the missing member registers. A hit
6052
+ * for a given discriminator is assumed stable — registering two members with
6053
+ * the same `type` literal at different times can leave a stale unambiguous
6054
+ * answer where the base class would have reported ambiguity.
6055
+ */
6056
+ class DynamicUnion extends Union {
6057
+ _members;
6058
+ constructor(members, options) {
6059
+ super([], options);
6060
+ this._members = members;
6061
+ }
6062
+ members() {
6063
+ return this._members();
6064
+ }
6065
+ get flags() {
6066
+ let result = TypeFlags.Union;
6067
+ for (const type of this.members()) {
6068
+ result |= type.flags;
6069
+ }
6070
+ return result;
6071
+ }
6072
+ get name() {
6073
+ return this._explicitName !== undefined
6074
+ ? this._explicitName
6075
+ : this.computeName();
6076
+ }
6077
+ set name(value) {
6078
+ this._explicitName = value;
6079
+ }
6080
+ _findCandidateByTypeDiscriminator(discriminator) {
6081
+ return this._scanForTypeDiscriminator(discriminator);
6082
+ }
6083
+ allMembersDiscriminated() {
6084
+ return this.computeAllMembersDiscriminated();
6085
+ }
6086
+ }
6021
6087
  const unionInternRoot = {};
6022
6088
  function internUnion(types) {
6023
6089
  let node = unionInternRoot;
@@ -6037,6 +6103,13 @@ function internUnion(types) {
6037
6103
  /**
6038
6104
  * `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`.
6039
6105
  *
6106
+ * Passing `members: () => Type[]` in the options (with no positional member
6107
+ * types) creates a union whose membership is re-read from the thunk on every
6108
+ * operation, so types registered after construction — e.g. from a lazily
6109
+ * loaded module — participate in dispatch, validation and reference
6110
+ * resolution. Pair it with a `dispatcher` for snapshot dispatch and a `name`
6111
+ * for stable error messages.
6112
+ *
6040
6113
  * @param optionsOrType
6041
6114
  * @param otherTypes
6042
6115
  * @returns
@@ -6056,12 +6129,18 @@ function union(...args) {
6056
6129
  // check all options
6057
6130
  if (devMode()) {
6058
6131
  if (options) {
6059
- assertArg(options, o => isPlainObject(o), "object { eager?: boolean, dispatcher?: Function }", 1);
6132
+ assertArg(options, o => isPlainObject(o), "object { eager?: boolean, dispatcher?: Function, members?: Function, name?: string }", 1);
6060
6133
  }
6061
6134
  types.forEach((type, i) => {
6062
6135
  assertIsType(type, options ? i + 2 : i + 1);
6063
6136
  });
6064
6137
  }
6138
+ if (options?.members !== undefined) {
6139
+ if (types.length > 0) {
6140
+ throw fail("union(): the members option cannot be combined with positional member types");
6141
+ }
6142
+ return new DynamicUnion(options.members, options);
6143
+ }
6065
6144
  return options === undefined ? internUnion(types) : new Union(types, options);
6066
6145
  }
6067
6146
  /**
@@ -6646,6 +6725,13 @@ class Lazy extends SimpleType {
6646
6725
  }
6647
6726
  reconcile(current, value, parent, subpath) {
6648
6727
  if (this.loadedType) {
6728
+ // a node created after load carries the loaded type: let it reconcile so
6729
+ // the instance survives applySnapshot instead of dying on every write
6730
+ const loaded = this.loadedType;
6731
+ const currentType = current.type;
6732
+ if (currentType === loaded) {
6733
+ return this.loadedType.reconcile(current, value, parent, subpath);
6734
+ }
6649
6735
  current.die();
6650
6736
  return this.loadedType.instantiate(parent, subpath, parent.environment, value);
6651
6737
  }