@jbrowse/mobx-state-tree 5.10.8 → 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 +152 -24
- package/dist/index.d.ts +12 -0
- package/dist/mobx-state-tree.cjs +92 -3
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +92 -3
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
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
|
|
27
|
-
|
|
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
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1921,6 +1921,17 @@ declare function optional<IT extends IAnyType, OptionalVals extends ValidOptiona
|
|
|
1921
1921
|
* @returns
|
|
1922
1922
|
*/
|
|
1923
1923
|
declare function isOptionalType<IT extends IAnyType>(type: IT): type is IT;
|
|
1924
|
+
/**
|
|
1925
|
+
* `types.stripDefault` - Like `types.optional`, but the property is omitted from
|
|
1926
|
+
* a parent model's snapshot entirely when its value equals the default (instead
|
|
1927
|
+
* of being serialized with the default value). Lets a model produce minimal
|
|
1928
|
+
* snapshots without a bespoke `postProcessSnapshot`.
|
|
1929
|
+
*
|
|
1930
|
+
* @param type
|
|
1931
|
+
* @param defaultValueOrFunction
|
|
1932
|
+
* @returns
|
|
1933
|
+
*/
|
|
1934
|
+
declare function stripDefault<IT extends IAnyType>(type: IT, defaultValueOrFunction: OptionalDefaultValueOrFunction<IT>): IOptionalIType<IT, [undefined]>;
|
|
1924
1935
|
|
|
1925
1936
|
/** @hidden */
|
|
1926
1937
|
interface IMaybeIType<IT extends IAnyType, C, O> extends IType<IT["CreationType"] | C, IT["SnapshotType"] | O, IT["TypeWithoutSTN"] | O> {
|
|
@@ -2149,6 +2160,7 @@ declare const types: {
|
|
|
2149
2160
|
safeReference: typeof safeReference;
|
|
2150
2161
|
union: typeof union;
|
|
2151
2162
|
optional: typeof optional;
|
|
2163
|
+
stripDefault: typeof stripDefault;
|
|
2152
2164
|
literal: typeof literal;
|
|
2153
2165
|
maybe: typeof maybe;
|
|
2154
2166
|
maybeNull: typeof maybeNull;
|
package/dist/mobx-state-tree.cjs
CHANGED
|
@@ -4905,7 +4905,7 @@ class ModelType extends ComplexType {
|
|
|
4905
4905
|
}
|
|
4906
4906
|
getSnapshot(node, applyPostProcess = true) {
|
|
4907
4907
|
const res = {};
|
|
4908
|
-
this.forAllProps((name,
|
|
4908
|
+
this.forAllProps((name, type) => {
|
|
4909
4909
|
try {
|
|
4910
4910
|
// TODO: FIXME, make sure the observable ref is used!
|
|
4911
4911
|
const atom = mobx.getAtom(node.storedValue, name);
|
|
@@ -4914,7 +4914,11 @@ class ModelType extends ComplexType {
|
|
|
4914
4914
|
catch (_e) {
|
|
4915
4915
|
throw fail(`${name} property is declared twice`);
|
|
4916
4916
|
}
|
|
4917
|
-
|
|
4917
|
+
const snapshot = this.getChildNode(node, name).snapshot;
|
|
4918
|
+
// strip-default optionals omit their key when equal to the default
|
|
4919
|
+
if (!shouldStripChildFromSnapshot(type, snapshot)) {
|
|
4920
|
+
res[name] = snapshot;
|
|
4921
|
+
}
|
|
4918
4922
|
});
|
|
4919
4923
|
if (applyPostProcess) {
|
|
4920
4924
|
return this.applySnapshotPostProcessor(res);
|
|
@@ -4924,7 +4928,13 @@ class ModelType extends ComplexType {
|
|
|
4924
4928
|
processInitialSnapshot(childNodes) {
|
|
4925
4929
|
const processed = {};
|
|
4926
4930
|
Object.keys(childNodes).forEach(key => {
|
|
4927
|
-
|
|
4931
|
+
const snapshot = childNodes[key].getSnapshot();
|
|
4932
|
+
// strip-default optionals omit their key when equal to the default; this
|
|
4933
|
+
// mirrors getSnapshot for nodes serialized before they become observable
|
|
4934
|
+
// instances (e.g. array/map children that were never accessed)
|
|
4935
|
+
if (!shouldStripChildFromSnapshot(this.properties[key], snapshot)) {
|
|
4936
|
+
processed[key] = snapshot;
|
|
4937
|
+
}
|
|
4928
4938
|
});
|
|
4929
4939
|
return this.applySnapshotPostProcessor(processed);
|
|
4930
4940
|
}
|
|
@@ -5878,6 +5888,84 @@ const undefinedAsOptionalValues = [undefined];
|
|
|
5878
5888
|
function isOptionalType(type) {
|
|
5879
5889
|
return isType(type) && (type.flags & TypeFlags.Optional) > 0;
|
|
5880
5890
|
}
|
|
5891
|
+
/**
|
|
5892
|
+
* Compare a child snapshot to a stripped-default's reference snapshot. Mirrors
|
|
5893
|
+
* the legacy hand-rolled comparison: identity for primitives, structural for
|
|
5894
|
+
* objects/arrays.
|
|
5895
|
+
*/
|
|
5896
|
+
function defaultSnapshotEquals(a, b) {
|
|
5897
|
+
if (a === b) {
|
|
5898
|
+
return true;
|
|
5899
|
+
}
|
|
5900
|
+
if (typeof a === "object" &&
|
|
5901
|
+
a !== null &&
|
|
5902
|
+
typeof b === "object" &&
|
|
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
|
+
}
|
|
5916
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
5917
|
+
}
|
|
5918
|
+
return false;
|
|
5919
|
+
}
|
|
5920
|
+
/**
|
|
5921
|
+
* An optional type that additionally omits its key from a parent model's
|
|
5922
|
+
* snapshot when the value equals the (snapshotted) default. The comparison is
|
|
5923
|
+
* against the default *snapshot* — i.e. the subtype is instantiated with the
|
|
5924
|
+
* default once and its post-processed snapshot is cached — so a default whose
|
|
5925
|
+
* normalized form gains fields (e.g. a fileLocation gaining `locationType`)
|
|
5926
|
+
* still strips correctly.
|
|
5927
|
+
*
|
|
5928
|
+
* @hidden
|
|
5929
|
+
* @internal
|
|
5930
|
+
*/
|
|
5931
|
+
class StripDefaultValue extends OptionalValue {
|
|
5932
|
+
_defaultSnapshot;
|
|
5933
|
+
shouldStripFromSnapshot(snapshot) {
|
|
5934
|
+
if (!this._defaultSnapshot) {
|
|
5935
|
+
// instantiate the subtype detached with the default and read the node's
|
|
5936
|
+
// snapshot, which normalizes (fills model defaults, applies the subtype's
|
|
5937
|
+
// own postProcess). Cached on the (singleton) type after first use.
|
|
5938
|
+
const node = this.getSubTypes().instantiate(null, "", undefined, this.getDefaultInstanceOrSnapshot());
|
|
5939
|
+
this._defaultSnapshot = { value: node.snapshot };
|
|
5940
|
+
}
|
|
5941
|
+
return defaultSnapshotEquals(snapshot, this._defaultSnapshot.value);
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5944
|
+
/**
|
|
5945
|
+
* Whether `type` is a strip-default optional whose current child `snapshot`
|
|
5946
|
+
* equals its default and should therefore be omitted from the parent model's
|
|
5947
|
+
* snapshot. Used by `ModelType.getSnapshot`.
|
|
5948
|
+
*
|
|
5949
|
+
* @hidden
|
|
5950
|
+
* @internal
|
|
5951
|
+
*/
|
|
5952
|
+
function shouldStripChildFromSnapshot(type, snapshot) {
|
|
5953
|
+
return (type instanceof StripDefaultValue && type.shouldStripFromSnapshot(snapshot));
|
|
5954
|
+
}
|
|
5955
|
+
/**
|
|
5956
|
+
* `types.stripDefault` - Like `types.optional`, but the property is omitted from
|
|
5957
|
+
* a parent model's snapshot entirely when its value equals the default (instead
|
|
5958
|
+
* of being serialized with the default value). Lets a model produce minimal
|
|
5959
|
+
* snapshots without a bespoke `postProcessSnapshot`.
|
|
5960
|
+
*
|
|
5961
|
+
* @param type
|
|
5962
|
+
* @param defaultValueOrFunction
|
|
5963
|
+
* @returns
|
|
5964
|
+
*/
|
|
5965
|
+
function stripDefault(type, defaultValueOrFunction) {
|
|
5966
|
+
checkOptionalPreconditions(type, defaultValueOrFunction);
|
|
5967
|
+
return new StripDefaultValue(type, defaultValueOrFunction, undefinedAsOptionalValues);
|
|
5968
|
+
}
|
|
5881
5969
|
|
|
5882
5970
|
const optionalUndefinedType = optional(undefinedType, undefined);
|
|
5883
5971
|
const optionalNullType = optional(nullType, null);
|
|
@@ -6895,6 +6983,7 @@ const types = {
|
|
|
6895
6983
|
safeReference,
|
|
6896
6984
|
union,
|
|
6897
6985
|
optional,
|
|
6986
|
+
stripDefault,
|
|
6898
6987
|
literal,
|
|
6899
6988
|
maybe,
|
|
6900
6989
|
maybeNull,
|