@jbrowse/mobx-state-tree 5.11.0 → 5.11.2

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 CHANGED
@@ -1,27 +1,228 @@
1
1
  # @jbrowse/mobx-state-tree
2
2
 
3
- Fork of mobx-state-tree v5.4.2 for use in jbrowse
4
-
5
- ## Current list of changes
6
-
7
- - updated typescript
8
- - updated to import actual filepaths instead of node module resolution
9
- - reduced rollup config
10
- - removed process.env.NODE_ENV from code, instead exporting a setDevMode function
11
- - converted tests to vitest
12
- - remove unused dependencies
13
- - updated tslint to eslint and typescript-eslint
14
- - dual CJS/ESM build using main and module fields (pure ESM causes trouble when both are imported in vite)
15
- - added `types.resilient` - wraps a type so that instantiation errors are caught
16
- and a fallback type is used instead of crashing the entire state tree. This is
17
- useful for arrays or maps that may contain unknown or invalid entries (e.g.
18
- plugin types that are not installed). It takes a primary type, a fallback type,
19
- and a `createFallbackSnapshot` callback that receives the error and original
20
- snapshot. Note: `isValidSnapshot` always returns success for resilient types,
21
- meaning any value is considered a valid snapshot. Validation is deferred to
22
- instantiation time, where failures are caught and routed to the fallback
23
- - removed NonEmptyObject annotation (also at upstream in post v5 versions)
24
- - replaced `unique symbol` phantom properties (`$emptyObject`, `$stateTreeNodeType`) with
3
+ Fork of [mobx-state-tree](https://mobx-state-tree.js.org) v5.4.2 maintained for use in
4
+ [JBrowse 2](https://jbrowse.org). All upstream types and APIs are available — this page
5
+ documents only the additions and changes made in this fork.
6
+
7
+ ## Infrastructure changes
8
+
9
+ - Updated TypeScript and import paths to use explicit file extensions
10
+ - Replaced `process.env.NODE_ENV` with an exported `setDevMode()` function
11
+ - Converted tests from Jest to Vitest
12
+ - Replaced tslint with eslint + typescript-eslint
13
+ - Dual CJS/ESM build (avoids issues when both are imported in the same Vite build)
14
+ - Removed unused dependencies and the `NonEmptyObject` annotation
15
+ - Replaced `unique symbol` phantom properties (`$emptyObject`, `$stateTreeNodeType`) with
25
16
  exported string-keyed brand interfaces (`$EmptyObjectBrand`, `$__mstStateTreeNodeType__`),
26
- fixing tsgo TS4058/TS4023 errors when MST types propagate into exported function return types
27
- - add reflection api getChildType and getUnionSubtypes and getDefaultInstanceOrSnapshot
17
+ fixing tsgo TS4058/TS4023 errors when MST types appear in exported function return types
18
+
19
+ ## Error message changes
20
+
21
+ Validation and conversion errors have been reworked to stay readable on large state
22
+ trees. Upstream MST could throw error messages hundreds of kilobytes long because it
23
+ expanded every union member's full structure and stringified entire snapshots unbounded.
24
+ This fork keeps the errors short while making them more diagnosable.
25
+
26
+ ### Bounded conversion errors
27
+
28
+ `typecheck()` / instantiation errors no longer dump unbounded output:
29
+
30
+ - Stringified snapshots are depth-bound (nested objects past 3 levels collapse to `…`)
31
+ - Each printed value is capped in length
32
+ - At most 10 individual errors are reported, with a `(… and N more errors)` suffix
33
+
34
+ ### Scoped discriminated-union errors
35
+
36
+ Take a union of view types discriminated by a literal `type` property:
37
+
38
+ ```ts
39
+ const LinearGenomeView = types.model("LinearGenomeView", {
40
+ type: types.literal("LinearGenomeView"),
41
+ height: types.number,
42
+ assembly: types.string
43
+ })
44
+ const DotplotView = types.model("DotplotView", {
45
+ type: types.literal("DotplotView"),
46
+ height: types.number
47
+ })
48
+ const AnyView = types.union(LinearGenomeView, DotplotView)
49
+ ```
50
+
51
+ Upstream MST listed every member's full structure on any failure. A single mistyped field
52
+ produced output like this — each member's entire schema expanded inline, repeated for every
53
+ member, often hundreds of kilobytes on a real config union:
54
+
55
+ ```
56
+ No matching type for union(LinearGenomeView | DotplotView)
57
+ (snapshot `{"type":"LinearGenomeView","height":"tall","assembly":"hg38"}` is not assignable
58
+ to type: `({ type: ("LinearGenomeView" | snapshot like `"LinearGenomeView"`); height: number;
59
+ assembly: string } | { type: ("DotplotView" | snapshot like `"DotplotView"`); height: number })`,
60
+ expected an instance of `LinearGenomeView` or a snapshot like `{ type: ("LinearGenomeView" |
61
+ snapshot like `"LinearGenomeView"`); height: number; assembly: string }` instead. … )
62
+ ```
63
+
64
+ Now the error is scoped to the single member whose literal `type` matches the snapshot's
65
+ discriminator, and reports only the property-level reasons it failed.
66
+
67
+ `AnyView.create({ type: "LinearGenomeView", height: "tall", assembly: "hg38" })` throws:
68
+
69
+ ```
70
+ [mobx-state-tree] Error while converting `{"type":"LinearGenomeView","height":"tall","assembly":"hg38"}` to `(LinearGenomeView | DotplotView)`:
71
+
72
+ at path "/height" value `"tall"` is not assignable to type: `number` (Value is not a number).
73
+ ```
74
+
75
+ And in production builds (where full type-checking is skipped) a missing required field on a
76
+ recognized member still names the discriminator and the offending path —
77
+ `AnyView.create({ type: "LinearGenomeView", height: 100 })` throws:
78
+
79
+ ```
80
+ [mobx-state-tree] No matching type for union (LinearGenomeView | DotplotView) for snapshot with type "LinearGenomeView":
81
+ at path "/assembly" value `undefined` is not assignable to type: `string` (Value is not a string).
82
+ ```
83
+
84
+ Instead of a wall of text covering every union member, you get the discriminator that was
85
+ seen plus the property-level reasons that one intended member rejected the snapshot (wrong
86
+ field type, missing required field, etc.). The scoping drills through wrapped members
87
+ (`optional`, `refinement`, `snapshotProcessor`, `late`) to find the underlying model, so
88
+ real-world members like an `optional(model)` config schema are still matched correctly.
89
+ When two members declare the same `type` literal (ambiguous), it falls back to the short
90
+ message rather than guessing.
91
+
92
+ ## Added APIs
93
+
94
+ ### `types.resilient`
95
+
96
+ Wraps a type so that if instantiation fails (e.g. because the snapshot is from an unknown
97
+ plugin that is not installed), the error is caught and a fallback type is used instead of
98
+ crashing the entire state tree.
99
+
100
+ ```ts
101
+ const PluginWidget = types.resilient(
102
+ KnownWidget,
103
+ UnknownWidget,
104
+ (error, originalSnapshot) => ({ kind: "unknown", raw: originalSnapshot })
105
+ )
106
+
107
+ // Arrays of plugins stay alive even if one entry is unrecognized
108
+ const Store = types.model({
109
+ widgets: types.array(PluginWidget)
110
+ })
111
+ ```
112
+
113
+ `types.resilient` takes three arguments:
114
+
115
+ - `type` — the primary type to try first
116
+ - `fallbackType` — the type to use when the primary fails
117
+ - `createFallbackSnapshot(error, originalSnapshot)` — a callback that receives the caught
118
+ error and the original snapshot, and returns a valid snapshot for the fallback type
119
+
120
+ **Important caveat:** `isValidSnapshot` always returns success for a resilient type. Any
121
+ value is considered a valid snapshot — validation is deferred to instantiation time, where
122
+ failures are caught and routed to the fallback.
123
+
124
+ ---
125
+
126
+ ### `types.stripDefault`
127
+
128
+ Like `types.optional`, but omits the property key from the parent model's snapshot entirely
129
+ when the value equals the default.
130
+
131
+ #### Background: snapshots and `types.optional`
132
+
133
+ In MST, a **snapshot** is the plain JSON representation of your model — what you'd get from
134
+ `getSnapshot(model)` and what you'd pass to `Model.create(...)`. Snapshots are used for
135
+ serialization, persistence, and undo/redo.
136
+
137
+ `types.optional` lets you define a property with a default value so it can be omitted when
138
+ creating a model:
139
+
140
+ ```ts
141
+ const Settings = types.model({
142
+ theme: types.optional(types.string, "light")
143
+ })
144
+
145
+ const s = Settings.create() // theme is "light"
146
+ getSnapshot(s) // { theme: "light" }
147
+ ```
148
+
149
+ Even though `"light"` is just the default, it always appears in the snapshot. If you store
150
+ or diff these snapshots, every instance carries that redundant data.
151
+
152
+ #### What `types.stripDefault` does
153
+
154
+ `types.stripDefault` works exactly like `types.optional` at runtime — the property gets its
155
+ default when missing — but the key is **left out of the snapshot entirely** when the value
156
+ equals the default:
157
+
158
+ ```ts
159
+ const Settings = types.model({
160
+ theme: types.stripDefault(types.string, "light")
161
+ })
162
+
163
+ const s = Settings.create()
164
+ getSnapshot(s) // {} ← key omitted because value equals default
165
+
166
+ s.theme = "dark"
167
+ getSnapshot(s) // { theme: "dark" }
168
+
169
+ s.theme = "light"
170
+ getSnapshot(s) // {} ← omitted again
171
+ ```
172
+
173
+ #### Why this matters
174
+
175
+ Without `stripDefault`, keeping defaults out of snapshots requires a manual
176
+ `postProcessSnapshot` on every model. `stripDefault` handles this automatically per-property.
177
+
178
+ This is especially useful when:
179
+
180
+ - Snapshots are stored or synced and you want to minimize their size
181
+ - You diff snapshots (e.g. for undo/redo or change detection) and want default values to
182
+ appear as "no change" rather than an explicit write
183
+ - You have many optional fields and don't want boilerplate `postProcessSnapshot`
184
+
185
+ #### How the default comparison works
186
+
187
+ The comparison is done against the **normalized** default snapshot, not the raw value you
188
+ passed in. MST instantiates the subtype with the default once, reads back its snapshot
189
+ (which applies any nested model defaults and post-processing), and caches that as the
190
+ reference. This means a complex nested default that gains extra fields at instantiation time
191
+ will still compare correctly.
192
+
193
+ ---
194
+
195
+ ### Reflection API additions
196
+
197
+ These utilities expose type metadata that is useful for building generic tooling (e.g.
198
+ editors, inspectors, plugin systems) that need to introspect an MST type at runtime.
199
+
200
+ **`getChildType(node, propertyName?)`** — returns the declared MST type of a child
201
+ property on a model, array, or map instance. For arrays and maps the property name is
202
+ ignored (all children share one element type).
203
+
204
+ ```ts
205
+ const Box = types.model({ x: types.number, y: types.number })
206
+ const box = Box.create({ x: 1, y: 2 })
207
+
208
+ getChildType(box, "x").name // "number"
209
+ ```
210
+
211
+ **`getUnionSubtypes(type)`** — given a `types.union` type, returns the array of its member
212
+ types. Useful when you need to enumerate what a union can hold.
213
+
214
+ ```ts
215
+ const Shape = types.union(Circle, Square, Triangle)
216
+
217
+ getUnionSubtypes(Shape) // [Circle, Square, Triangle]
218
+ ```
219
+
220
+ **`getDefaultInstanceOrSnapshot(optionalType)`** — returns the default value (or snapshot)
221
+ declared on a `types.optional` or `types.stripDefault` type. Lets generic tooling read the
222
+ default without having to create a model instance.
223
+
224
+ ```ts
225
+ const t = types.optional(types.string, "hello")
226
+
227
+ t.getDefaultInstanceOrSnapshot() // "hello"
228
+ ```
package/dist/index.d.ts CHANGED
@@ -1284,52 +1284,6 @@ interface IAnyStateTreeNode extends STNValue<any, IAnyType> {
1284
1284
  */
1285
1285
  declare function isStateTreeNode<IT extends IAnyComplexType = IAnyComplexType>(value: any): value is STNValue<Instance<IT>, IT>;
1286
1286
 
1287
- /**
1288
- * @deprecated has been renamed to `flow()`.
1289
- * @hidden
1290
- */
1291
- declare function process<R>(generator: () => IterableIterator<any>): () => Promise<R>;
1292
- /**
1293
- * @deprecated has been renamed to `flow()`.
1294
- * @hidden
1295
- */
1296
- declare function process<A1>(generator: (a1: A1) => IterableIterator<any>): (a1: A1) => Promise<any>;
1297
- /**
1298
- * @deprecated has been renamed to `flow()`.
1299
- * @hidden
1300
- */
1301
- declare function process<A1, A2>(generator: (a1: A1, a2: A2) => IterableIterator<any>): (a1: A1, a2: A2) => Promise<any>;
1302
- /**
1303
- * @deprecated has been renamed to `flow()`.
1304
- * @hidden
1305
- */
1306
- declare function process<A1, A2, A3>(generator: (a1: A1, a2: A2, a3: A3) => IterableIterator<any>): (a1: A1, a2: A2, a3: A3) => Promise<any>;
1307
- /**
1308
- * @deprecated has been renamed to `flow()`.
1309
- * @hidden
1310
- */
1311
- declare function process<A1, A2, A3, A4>(generator: (a1: A1, a2: A2, a3: A3, a4: A4) => IterableIterator<any>): (a1: A1, a2: A2, a3: A3, a4: A4) => Promise<any>;
1312
- /**
1313
- * @deprecated has been renamed to `flow()`.
1314
- * @hidden
1315
- */
1316
- declare function process<A1, A2, A3, A4, A5>(generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => IterableIterator<any>): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => Promise<any>;
1317
- /**
1318
- * @deprecated has been renamed to `flow()`.
1319
- * @hidden
1320
- */
1321
- declare function process<A1, A2, A3, A4, A5, A6>(generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => IterableIterator<any>): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => Promise<any>;
1322
- /**
1323
- * @deprecated has been renamed to `flow()`.
1324
- * @hidden
1325
- */
1326
- declare function process<A1, A2, A3, A4, A5, A6, A7>(generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7) => IterableIterator<any>): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7) => Promise<any>;
1327
- /**
1328
- * @deprecated has been renamed to `flow()`.
1329
- * @hidden
1330
- */
1331
- declare function process<A1, A2, A3, A4, A5, A6, A7, A8>(generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8) => IterableIterator<any>): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8) => Promise<any>;
1332
-
1333
1287
  /**
1334
1288
  * @hidden
1335
1289
  */
@@ -2185,5 +2139,5 @@ declare const types: {
2185
2139
  resilient: typeof resilient;
2186
2140
  };
2187
2141
 
2188
- export { addDisposer, addMiddleware, applyAction, applyPatch, applySnapshot, cast, castFlowReturn, castToReferenceSnapshot, castToSnapshot, clone, createActionTrackingMiddleware, createActionTrackingMiddleware2, decorate, destroy, detach, escapeJsonPath, flow, getChildType, getEnv, getIdentifier, getLivelinessChecking, getMembers, getNodeId, getParent, getParentOfType, getPath, getPathParts, getPropertyMembers, getRelativePath, getRoot, getRunningActionContext, getSnapshot, getType, getUnionSubtypes, hasParent, hasParentOfType, isActionContextChildOf, isActionContextThisOrChildOf, isAlive, isArrayType, isFrozenType, isIdentifierType, isLateType, isLiteralType, isMapType, isModelType, isOptionalType, isPrimitiveType, isProtected, isReferenceType, isRefinementType, isRoot, isStateTreeNode, isType, isUnionType, isValidReference, joinJsonPath, onAction, onPatch, onSnapshot, process, protect, recordActions, recordPatches, resolveIdentifier, resolvePath, setDevMode, setLivelinessChecking, setLivelynessChecking, setTypeChecking, splitJsonPath, types as t, toGenerator, toGeneratorFunction, tryReference, tryResolve, typecheck, types, unescapeJsonPath, unprotect, walk };
2142
+ export { addDisposer, addMiddleware, applyAction, applyPatch, applySnapshot, cast, castFlowReturn, castToReferenceSnapshot, castToSnapshot, clone, createActionTrackingMiddleware, createActionTrackingMiddleware2, decorate, destroy, detach, escapeJsonPath, flow, getChildType, getEnv, getIdentifier, getLivelinessChecking, getMembers, getNodeId, getParent, getParentOfType, getPath, getPathParts, getPropertyMembers, getRelativePath, getRoot, getRunningActionContext, getSnapshot, getType, getUnionSubtypes, hasParent, hasParentOfType, isActionContextChildOf, isActionContextThisOrChildOf, isAlive, isArrayType, isFrozenType, isIdentifierType, isLateType, isLiteralType, isMapType, isModelType, isOptionalType, isPrimitiveType, isProtected, isReferenceType, isRefinementType, isRoot, isStateTreeNode, isType, isUnionType, isValidReference, joinJsonPath, onAction, onPatch, onSnapshot, protect, recordActions, recordPatches, resolveIdentifier, resolvePath, setDevMode, setLivelinessChecking, setLivelynessChecking, setTypeChecking, splitJsonPath, types as t, toGenerator, toGeneratorFunction, tryReference, tryResolve, typecheck, types, unescapeJsonPath, unprotect, walk };
2189
2143
  export type { $EmptyObjectBrand, CustomTypeOptions, IActionContext, IActionRecorder, IActionTrackingMiddleware2Call, IActionTrackingMiddleware2Hooks, IActionTrackingMiddlewareHooks, IAnyComplexType, IAnyModelType, IAnyStateTreeNode, IAnyType, IArrayType, IComplexType, IDisposer, IJsonPatch, IMSTArray, IMSTMap, IMapType, IMaybe, IMaybeIType, IMaybeNull, IMiddlewareEvent, IMiddlewareEventType, IMiddlewareHandler, IModelReflectionData, IModelReflectionPropertiesData, IModelType, IOptionalIType, IPatchRecorder, IReferenceType, IReversibleJsonPatch, ISerializedActionCall, ISimpleType, ISnapshotProcessor, ISnapshotProcessors, IStateTreeNode, IType, ITypeUnion, Instance, LivelinessMode, LivelynessMode, ModelActions, ModelCreationType, ModelCreationType2, ModelInstanceType, ModelInstanceTypeProps, ModelPrimitive, ModelProperties, ModelPropertiesDeclaration, ModelPropertiesDeclarationToProperties, ModelSnapshotType, ModelSnapshotType2, OnReferenceInvalidated, OnReferenceInvalidatedEvent, OptionalDefaultValueOrFunction, ReferenceIdentifier, ReferenceOptions, ReferenceOptionsGetSet, ReferenceOptionsOnInvalidated, SnapshotIn, SnapshotOrInstance, SnapshotOut, TypeOfValue, TypeOrStateTreeNodeToStateTreeNode, UnionOptions, UnionStringArray, ValidOptionalValue, ValidOptionalValues, _CustomCSProcessor, _CustomJoin, _CustomOrOther, _NotCustomized };
@@ -1118,6 +1118,21 @@ class ScalarNode extends BaseNode {
1118
1118
  ScalarNode.prototype.die = mobx.action(ScalarNode.prototype.die);
1119
1119
 
1120
1120
  let nextNodeId = 1;
1121
+ var ObservableInstanceLifecycle;
1122
+ (function (ObservableInstanceLifecycle) {
1123
+ // the actual observable instance has not been created yet
1124
+ ObservableInstanceLifecycle[ObservableInstanceLifecycle["UNINITIALIZED"] = 0] = "UNINITIALIZED";
1125
+ // the actual observable instance is being created
1126
+ ObservableInstanceLifecycle[ObservableInstanceLifecycle["CREATING"] = 1] = "CREATING";
1127
+ // the actual observable instance has been created
1128
+ ObservableInstanceLifecycle[ObservableInstanceLifecycle["CREATED"] = 2] = "CREATED";
1129
+ })(ObservableInstanceLifecycle || (ObservableInstanceLifecycle = {}));
1130
+ var InternalEvents;
1131
+ (function (InternalEvents) {
1132
+ InternalEvents["Dispose"] = "dispose";
1133
+ InternalEvents["Patch"] = "patch";
1134
+ InternalEvents["Snapshot"] = "snapshot";
1135
+ })(InternalEvents || (InternalEvents = {}));
1121
1136
  const snapshotReactionOptions = {
1122
1137
  onError(e) {
1123
1138
  throw e;
@@ -1149,7 +1164,7 @@ class ObjectNode extends BaseNode {
1149
1164
  _autoUnbox = true; // unboxing is disabled when reading child nodes
1150
1165
  _isRunningAction = false; // only relevant for root
1151
1166
  _hasSnapshotReaction = false;
1152
- _observableInstanceState = 0 /* ObservableInstanceLifecycle.UNINITIALIZED */;
1167
+ _observableInstanceState = ObservableInstanceLifecycle.UNINITIALIZED;
1153
1168
  _childNodes;
1154
1169
  _initialSnapshot;
1155
1170
  _cachedInitialSnapshot;
@@ -1194,7 +1209,7 @@ class ObjectNode extends BaseNode {
1194
1209
  }
1195
1210
  createObservableInstanceIfNeeded(fireHooks = true) {
1196
1211
  if (this._observableInstanceState ===
1197
- 0 /* ObservableInstanceLifecycle.UNINITIALIZED */) {
1212
+ ObservableInstanceLifecycle.UNINITIALIZED) {
1198
1213
  this.createObservableInstance(fireHooks);
1199
1214
  }
1200
1215
  }
@@ -1205,7 +1220,7 @@ class ObjectNode extends BaseNode {
1205
1220
  throw fail("assertion failed: the creation of the observable instance must be done on the initializing phase");
1206
1221
  }
1207
1222
  }
1208
- this._observableInstanceState = 1 /* ObservableInstanceLifecycle.CREATING */;
1223
+ this._observableInstanceState = ObservableInstanceLifecycle.CREATING;
1209
1224
  // make sure the parent chain is created as well
1210
1225
  // array with parent chain from parent to child
1211
1226
  const parentChain = [];
@@ -1215,7 +1230,7 @@ class ObjectNode extends BaseNode {
1215
1230
  // this is done to avoid traversing the whole tree to the root when using
1216
1231
  // the same reference again
1217
1232
  while (parent?._observableInstanceState ===
1218
- 0 /* ObservableInstanceLifecycle.UNINITIALIZED */) {
1233
+ ObservableInstanceLifecycle.UNINITIALIZED) {
1219
1234
  parentChain.unshift(parent);
1220
1235
  parent = parent.parent;
1221
1236
  }
@@ -1240,7 +1255,7 @@ class ObjectNode extends BaseNode {
1240
1255
  finally {
1241
1256
  this._isRunningAction = false;
1242
1257
  }
1243
- this._observableInstanceState = 2 /* ObservableInstanceLifecycle.CREATED */;
1258
+ this._observableInstanceState = ObservableInstanceLifecycle.CREATED;
1244
1259
  this._snapshotComputed.trackAndCompute();
1245
1260
  if (this.isRoot) {
1246
1261
  this._addSnapshotReaction();
@@ -1354,7 +1369,7 @@ class ObjectNode extends BaseNode {
1354
1369
  if (!this.isAlive) {
1355
1370
  return this._snapshotUponDeath;
1356
1371
  }
1357
- return this._observableInstanceState === 2 /* ObservableInstanceLifecycle.CREATED */
1372
+ return this._observableInstanceState === ObservableInstanceLifecycle.CREATED
1358
1373
  ? this._getActualSnapshot()
1359
1374
  : this._getCachedInitialSnapshot();
1360
1375
  }
@@ -1417,7 +1432,7 @@ class ObjectNode extends BaseNode {
1417
1432
  this._autoUnbox = false;
1418
1433
  try {
1419
1434
  return this._observableInstanceState ===
1420
- 2 /* ObservableInstanceLifecycle.CREATED */
1435
+ ObservableInstanceLifecycle.CREATED
1421
1436
  ? this.type.getChildNode(this, subpath)
1422
1437
  : this._childNodes[subpath];
1423
1438
  }
@@ -1430,7 +1445,7 @@ class ObjectNode extends BaseNode {
1430
1445
  this._autoUnbox = false;
1431
1446
  try {
1432
1447
  return this._observableInstanceState ===
1433
- 2 /* ObservableInstanceLifecycle.CREATED */
1448
+ ObservableInstanceLifecycle.CREATED
1434
1449
  ? this.type.getChildren(this)
1435
1450
  : convertChildNodesToArray(this._childNodes);
1436
1451
  }
@@ -1516,7 +1531,7 @@ class ObjectNode extends BaseNode {
1516
1531
  }
1517
1532
  aboutToDie() {
1518
1533
  if (this._observableInstanceState ===
1519
- 0 /* ObservableInstanceLifecycle.UNINITIALIZED */) {
1534
+ ObservableInstanceLifecycle.UNINITIALIZED) {
1520
1535
  return;
1521
1536
  }
1522
1537
  this.getChildren().forEach(node => {
@@ -1525,8 +1540,8 @@ class ObjectNode extends BaseNode {
1525
1540
  // beforeDestroy should run before the disposers since else we could end up in a situation where
1526
1541
  // a disposer added with addDisposer at this stage (beforeDestroy) is actually never released
1527
1542
  this.baseAboutToDie();
1528
- this._internalEventsEmit("dispose" /* InternalEvents.Dispose */);
1529
- this._internalEventsClear("dispose" /* InternalEvents.Dispose */);
1543
+ this._internalEventsEmit(InternalEvents.Dispose);
1544
+ this._internalEventsClear(InternalEvents.Dispose);
1530
1545
  }
1531
1546
  finalizeDeath() {
1532
1547
  // invariant: not called directly but from "die"
@@ -1542,42 +1557,42 @@ class ObjectNode extends BaseNode {
1542
1557
  }
1543
1558
  onSnapshot(onChange) {
1544
1559
  this._addSnapshotReaction();
1545
- return this._internalEventsRegister("snapshot" /* InternalEvents.Snapshot */, onChange);
1560
+ return this._internalEventsRegister(InternalEvents.Snapshot, onChange);
1546
1561
  }
1547
1562
  emitSnapshot(snapshot) {
1548
- this._internalEventsEmit("snapshot" /* InternalEvents.Snapshot */, snapshot);
1563
+ this._internalEventsEmit(InternalEvents.Snapshot, snapshot);
1549
1564
  }
1550
1565
  onPatch(handler) {
1551
- return this._internalEventsRegister("patch" /* InternalEvents.Patch */, handler);
1566
+ return this._internalEventsRegister(InternalEvents.Patch, handler);
1552
1567
  }
1553
1568
  emitPatch(basePatch, source) {
1554
- if (this._internalEventsHasSubscribers("patch" /* InternalEvents.Patch */)) {
1569
+ if (this._internalEventsHasSubscribers(InternalEvents.Patch)) {
1555
1570
  const localizedPatch = {
1556
1571
  ...basePatch,
1557
- path: `${source.path.substr(this.path.length)}/${basePatch.path}` // calculate the relative path of the patch
1572
+ path: `${source.path.slice(this.path.length)}/${basePatch.path}` // calculate the relative path of the patch
1558
1573
  };
1559
1574
  const [patch, reversePatch] = splitPatch(localizedPatch);
1560
- this._internalEventsEmit("patch" /* InternalEvents.Patch */, patch, reversePatch);
1575
+ this._internalEventsEmit(InternalEvents.Patch, patch, reversePatch);
1561
1576
  }
1562
1577
  if (this.parent) {
1563
1578
  this.parent.emitPatch(basePatch, source);
1564
1579
  }
1565
1580
  }
1566
1581
  hasDisposer(disposer) {
1567
- return this._internalEventsHas("dispose" /* InternalEvents.Dispose */, disposer);
1582
+ return this._internalEventsHas(InternalEvents.Dispose, disposer);
1568
1583
  }
1569
1584
  addDisposer(disposer) {
1570
1585
  if (!this.hasDisposer(disposer)) {
1571
- this._internalEventsRegister("dispose" /* InternalEvents.Dispose */, disposer, true);
1586
+ this._internalEventsRegister(InternalEvents.Dispose, disposer, true);
1572
1587
  return;
1573
1588
  }
1574
1589
  throw fail("cannot add a disposer when it is already registered for execution");
1575
1590
  }
1576
1591
  removeDisposer(disposer) {
1577
- if (!this._internalEventsHas("dispose" /* InternalEvents.Dispose */, disposer)) {
1592
+ if (!this._internalEventsHas(InternalEvents.Dispose, disposer)) {
1578
1593
  throw fail("cannot remove a disposer which was never registered for execution");
1579
1594
  }
1580
- this._internalEventsUnregister("dispose" /* InternalEvents.Dispose */, disposer);
1595
+ this._internalEventsUnregister(InternalEvents.Dispose, disposer);
1581
1596
  }
1582
1597
  removeMiddleware(middleware) {
1583
1598
  if (this.middlewares) {
@@ -2567,22 +2582,47 @@ function isActionContextThisOrChildOf(actionContext, parentOrThis) {
2567
2582
  }
2568
2583
 
2569
2584
  const MAX_STRINGIFY_DEPTH = 3;
2585
+ const MAX_STRINGIFY_ARRAY_ITEMS = 10;
2586
+ const MAX_STRINGIFY_OBJECT_KEYS = 20;
2587
+ const MAX_STRINGIFY_STRING_LENGTH = 100;
2588
+ // Recursively cap depth, array length, object key count, and string length so a
2589
+ // huge offending snapshot degrades into readable, bounded context instead of a
2590
+ // wall of text that then gets sliced mid-structure. The depth cap also bounds
2591
+ // recursion on circular structures.
2592
+ function truncateForStringify(value, depth) {
2593
+ if (typeof value === "string") {
2594
+ return value.length > MAX_STRINGIFY_STRING_LENGTH
2595
+ ? `${value.substring(0, MAX_STRINGIFY_STRING_LENGTH)}… (${value.length - MAX_STRINGIFY_STRING_LENGTH} more characters)`
2596
+ : value;
2597
+ }
2598
+ if (value === null || typeof value !== "object") {
2599
+ return value;
2600
+ }
2601
+ if (depth >= MAX_STRINGIFY_DEPTH) {
2602
+ return Array.isArray(value) ? "[…]" : "{…}";
2603
+ }
2604
+ if (Array.isArray(value)) {
2605
+ const items = value
2606
+ .slice(0, MAX_STRINGIFY_ARRAY_ITEMS)
2607
+ .map(item => truncateForStringify(item, depth + 1));
2608
+ if (value.length > MAX_STRINGIFY_ARRAY_ITEMS) {
2609
+ items.push(`… ${value.length - MAX_STRINGIFY_ARRAY_ITEMS} more items`);
2610
+ }
2611
+ return items;
2612
+ }
2613
+ const keys = Object.keys(value);
2614
+ const result = {};
2615
+ for (const key of keys.slice(0, MAX_STRINGIFY_OBJECT_KEYS)) {
2616
+ result[key] = truncateForStringify(value[key], depth + 1);
2617
+ }
2618
+ if (keys.length > MAX_STRINGIFY_OBJECT_KEYS) {
2619
+ result["…"] = `${keys.length - MAX_STRINGIFY_OBJECT_KEYS} more keys`;
2620
+ }
2621
+ return result;
2622
+ }
2570
2623
  function safeStringify(value) {
2571
2624
  try {
2572
- const ancestors = [];
2573
- return JSON.stringify(value, function (_key, val) {
2574
- if (val !== null && typeof val === "object") {
2575
- while (ancestors.length > 0 &&
2576
- ancestors[ancestors.length - 1] !== this) {
2577
- ancestors.pop();
2578
- }
2579
- if (ancestors.length >= MAX_STRINGIFY_DEPTH) {
2580
- return Array.isArray(val) ? "[…]" : "{…}";
2581
- }
2582
- ancestors.push(val);
2583
- }
2584
- return val;
2585
- });
2625
+ return JSON.stringify(truncateForStringify(value, 0));
2586
2626
  }
2587
2627
  catch (e) {
2588
2628
  // istanbul ignore next
@@ -3013,31 +3053,6 @@ function convertChildNodesToArray(childNodes) {
3013
3053
  return result;
3014
3054
  }
3015
3055
 
3016
- // based on: https://github.com/mobxjs/mobx-utils/blob/master/src/async-action.ts
3017
- /*
3018
- All contents of this file are deprecated.
3019
-
3020
- The term `process` has been replaced with `flow` to avoid conflicts with the
3021
- global `process` object.
3022
-
3023
- Refer to `flow.ts` for any further changes to this implementation.
3024
- */
3025
- const DEPRECATION_MESSAGE = "See https://github.com/mobxjs/mobx-state-tree/issues/399 for more information. " +
3026
- "Note that the middleware event types starting with `process` now start with `flow`.";
3027
- /**
3028
- * @hidden
3029
- *
3030
- * @deprecated has been renamed to `flow()`.
3031
- * See https://github.com/mobxjs/mobx-state-tree/issues/399 for more information.
3032
- * Note that the middleware event types starting with `process` now start with `flow`.
3033
- *
3034
- * @returns {Promise}
3035
- */
3036
- function process$1(asyncAction) {
3037
- deprecated("process", `\`process()\` has been renamed to \`flow()\`. ${DEPRECATION_MESSAGE}`);
3038
- return flow(asyncAction);
3039
- }
3040
-
3041
3056
  const plainObjectString = Object.toString();
3042
3057
  /**
3043
3058
  * @internal
@@ -3328,25 +3343,6 @@ class EventHandlers {
3328
3343
  }
3329
3344
  }
3330
3345
  }
3331
- /**
3332
- * @internal
3333
- * @hidden
3334
- */
3335
- const deprecated = function (id, message) {
3336
- // skip if running production
3337
- if (!devMode()) {
3338
- return;
3339
- }
3340
- // warn if hasn't been warned before
3341
- if (deprecated.ids && !deprecated.ids.hasOwnProperty(id)) {
3342
- warnError(`Deprecation warning: ${message}`);
3343
- }
3344
- // mark as warned to avoid duplicate warn message
3345
- if (deprecated.ids) {
3346
- deprecated.ids[id] = true;
3347
- }
3348
- };
3349
- deprecated.ids = {};
3350
3346
  /**
3351
3347
  * @internal
3352
3348
  * @hidden
@@ -4299,9 +4295,9 @@ class ArrayType extends ComplexType {
4299
4295
  return node.storedValue.slice();
4300
4296
  }
4301
4297
  getChildNode(node, key) {
4302
- const index = Number(key);
4303
- if (index < node.storedValue.length) {
4304
- return node.storedValue[index];
4298
+ const child = node.storedValue[Number(key)];
4299
+ if (child) {
4300
+ return child;
4305
4301
  }
4306
4302
  throw fail(`Not a child: ${key}`);
4307
4303
  }
@@ -4468,7 +4464,7 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
4468
4464
  // both are empty, end
4469
4465
  break;
4470
4466
  }
4471
- else if (!hasNewNode) {
4467
+ else if (oldNode && !hasNewNode) {
4472
4468
  // new one does not exists
4473
4469
  nothingChanged = false;
4474
4470
  oldNodes.splice(i, 1);
@@ -5901,6 +5897,18 @@ function defaultSnapshotEquals(a, b) {
5901
5897
  a !== null &&
5902
5898
  typeof b === "object" &&
5903
5899
  b !== null) {
5900
+ // Cheap structural short-circuit before the full stringify compare: a
5901
+ // value of a different size can't equal the default, so the common strip
5902
+ // case (non-empty value vs an empty `[]`/`{}` default) avoids stringifying
5903
+ // a potentially large snapshot on every getSnapshot.
5904
+ if (Array.isArray(a) !== Array.isArray(b)) {
5905
+ return false;
5906
+ }
5907
+ const aSize = Array.isArray(a) ? a.length : Object.keys(a).length;
5908
+ const bSize = Array.isArray(b) ? b.length : Object.keys(b).length;
5909
+ if (aSize !== bSize) {
5910
+ return false;
5911
+ }
5904
5912
  return JSON.stringify(a) === JSON.stringify(b);
5905
5913
  }
5906
5914
  return false;
@@ -7056,7 +7064,6 @@ exports.joinJsonPath = joinJsonPath;
7056
7064
  exports.onAction = onAction;
7057
7065
  exports.onPatch = onPatch;
7058
7066
  exports.onSnapshot = onSnapshot;
7059
- exports.process = process$1;
7060
7067
  exports.protect = protect;
7061
7068
  exports.recordActions = recordActions;
7062
7069
  exports.recordPatches = recordPatches;