@jbrowse/mobx-state-tree 5.11.0 → 5.11.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.
package/README.md CHANGED
@@ -1,27 +1,155 @@
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
+ ## Added APIs
20
+
21
+ ### `types.resilient`
22
+
23
+ Wraps a type so that if instantiation fails (e.g. because the snapshot is from an unknown
24
+ plugin that is not installed), the error is caught and a fallback type is used instead of
25
+ crashing the entire state tree.
26
+
27
+ ```ts
28
+ const PluginWidget = types.resilient(
29
+ KnownWidget,
30
+ UnknownWidget,
31
+ (error, originalSnapshot) => ({ kind: "unknown", raw: originalSnapshot }),
32
+ )
33
+
34
+ // Arrays of plugins stay alive even if one entry is unrecognized
35
+ const Store = types.model({
36
+ widgets: types.array(PluginWidget),
37
+ })
38
+ ```
39
+
40
+ `types.resilient` takes three arguments:
41
+
42
+ - `type` — the primary type to try first
43
+ - `fallbackType` — the type to use when the primary fails
44
+ - `createFallbackSnapshot(error, originalSnapshot)` — a callback that receives the caught
45
+ error and the original snapshot, and returns a valid snapshot for the fallback type
46
+
47
+ **Important caveat:** `isValidSnapshot` always returns success for a resilient type. Any
48
+ value is considered a valid snapshot — validation is deferred to instantiation time, where
49
+ failures are caught and routed to the fallback.
50
+
51
+ ---
52
+
53
+ ### `types.stripDefault`
54
+
55
+ Like `types.optional`, but omits the property key from the parent model's snapshot entirely
56
+ when the value equals the default.
57
+
58
+ #### Background: snapshots and `types.optional`
59
+
60
+ In MST, a **snapshot** is the plain JSON representation of your model — what you'd get from
61
+ `getSnapshot(model)` and what you'd pass to `Model.create(...)`. Snapshots are used for
62
+ serialization, persistence, and undo/redo.
63
+
64
+ `types.optional` lets you define a property with a default value so it can be omitted when
65
+ creating a model:
66
+
67
+ ```ts
68
+ const Settings = types.model({
69
+ theme: types.optional(types.string, "light"),
70
+ })
71
+
72
+ const s = Settings.create() // theme is "light"
73
+ getSnapshot(s) // { theme: "light" }
74
+ ```
75
+
76
+ Even though `"light"` is just the default, it always appears in the snapshot. If you store
77
+ or diff these snapshots, every instance carries that redundant data.
78
+
79
+ #### What `types.stripDefault` does
80
+
81
+ `types.stripDefault` works exactly like `types.optional` at runtime — the property gets its
82
+ default when missing — but the key is **left out of the snapshot entirely** when the value
83
+ equals the default:
84
+
85
+ ```ts
86
+ const Settings = types.model({
87
+ theme: types.stripDefault(types.string, "light"),
88
+ })
89
+
90
+ const s = Settings.create()
91
+ getSnapshot(s) // {} ← key omitted because value equals default
92
+
93
+ s.theme = "dark"
94
+ getSnapshot(s) // { theme: "dark" }
95
+
96
+ s.theme = "light"
97
+ getSnapshot(s) // {} ← omitted again
98
+ ```
99
+
100
+ #### Why this matters
101
+
102
+ Without `stripDefault`, keeping defaults out of snapshots requires a manual
103
+ `postProcessSnapshot` on every model. `stripDefault` handles this automatically per-property.
104
+
105
+ This is especially useful when:
106
+
107
+ - Snapshots are stored or synced and you want to minimize their size
108
+ - You diff snapshots (e.g. for undo/redo or change detection) and want default values to
109
+ appear as "no change" rather than an explicit write
110
+ - You have many optional fields and don't want boilerplate `postProcessSnapshot`
111
+
112
+ #### How the default comparison works
113
+
114
+ The comparison is done against the **normalized** default snapshot, not the raw value you
115
+ passed in. MST instantiates the subtype with the default once, reads back its snapshot
116
+ (which applies any nested model defaults and post-processing), and caches that as the
117
+ reference. This means a complex nested default that gains extra fields at instantiation time
118
+ will still compare correctly.
119
+
120
+ ---
121
+
122
+ ### Reflection API additions
123
+
124
+ These utilities expose type metadata that is useful for building generic tooling (e.g.
125
+ editors, inspectors, plugin systems) that need to introspect an MST type at runtime.
126
+
127
+ **`getChildType(node, propertyName?)`** — returns the declared MST type of a child
128
+ property on a model, array, or map instance. For arrays and maps the property name is
129
+ ignored (all children share one element type).
130
+
131
+ ```ts
132
+ const Box = types.model({ x: types.number, y: types.number })
133
+ const box = Box.create({ x: 1, y: 2 })
134
+
135
+ getChildType(box, "x").name // "number"
136
+ ```
137
+
138
+ **`getUnionSubtypes(type)`** — given a `types.union` type, returns the array of its member
139
+ types. Useful when you need to enumerate what a union can hold.
140
+
141
+ ```ts
142
+ const Shape = types.union(Circle, Square, Triangle)
143
+
144
+ getUnionSubtypes(Shape) // [Circle, Square, Triangle]
145
+ ```
146
+
147
+ **`getDefaultInstanceOrSnapshot(optionalType)`** — returns the default value (or snapshot)
148
+ declared on a `types.optional` or `types.stripDefault` type. Lets generic tooling read the
149
+ default without having to create a model instance.
150
+
151
+ ```ts
152
+ const t = types.optional(types.string, "hello")
153
+
154
+ t.getDefaultInstanceOrSnapshot() // "hello"
155
+ ```
@@ -5901,6 +5901,18 @@ function defaultSnapshotEquals(a, b) {
5901
5901
  a !== null &&
5902
5902
  typeof b === "object" &&
5903
5903
  b !== null) {
5904
+ // Cheap structural short-circuit before the full stringify compare: a
5905
+ // value of a different size can't equal the default, so the common strip
5906
+ // case (non-empty value vs an empty `[]`/`{}` default) avoids stringifying
5907
+ // a potentially large snapshot on every getSnapshot.
5908
+ if (Array.isArray(a) !== Array.isArray(b)) {
5909
+ return false;
5910
+ }
5911
+ const aSize = Array.isArray(a) ? a.length : Object.keys(a).length;
5912
+ const bSize = Array.isArray(b) ? b.length : Object.keys(b).length;
5913
+ if (aSize !== bSize) {
5914
+ return false;
5915
+ }
5904
5916
  return JSON.stringify(a) === JSON.stringify(b);
5905
5917
  }
5906
5918
  return false;