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