mobx-keystone 1.22.0 → 1.23.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/CHANGELOG.md +7 -0
- package/dist/mobx-keystone.esm.js +6702 -5797
- package/dist/mobx-keystone.esm.mjs +6702 -5797
- package/dist/mobx-keystone.umd.js +6718 -5798
- package/dist/types/action/runUnprotected.d.ts +8 -4
- package/dist/types/model/Model.d.ts +5 -0
- package/dist/types/modelShared/prop.d.ts +8 -4
- package/dist/types/snapshot/modelInitialData.d.ts +1 -0
- package/dist/types/tweaker/treeNodeMetadata.d.ts +1 -0
- package/dist/types/types/createCachedTypeCheck.d.ts +1 -0
- package/package.json +11 -10
- package/src/action/middleware.ts +5 -18
- package/src/action/mutationBatch.ts +72 -0
- package/src/action/runUnprotected.ts +21 -8
- package/src/action/wrapInAction.ts +45 -15
- package/src/dataModel/BaseDataModel.ts +4 -8
- package/src/deepChange/onDeepChange.ts +246 -16
- package/src/model/BaseModel.ts +4 -12
- package/src/model/Model.ts +5 -0
- package/src/model/newModel.ts +68 -36
- package/src/modelShared/prop.ts +105 -63
- package/src/modelShared/sharedInternalModel.ts +91 -60
- package/src/parent/core.ts +16 -10
- package/src/parent/coreObjectChildren.ts +219 -21
- package/src/parent/path.ts +6 -5
- package/src/parent/setParent.ts +5 -5
- package/src/patch/emitPatch.ts +102 -20
- package/src/snapshot/fromArraySnapshot.ts +11 -2
- package/src/snapshot/fromModelSnapshot.ts +10 -3
- package/src/snapshot/fromPlainObjectSnapshot.ts +18 -7
- package/src/snapshot/fromSnapshot.ts +31 -9
- package/src/snapshot/getSnapshot.ts +8 -3
- package/src/snapshot/internal.ts +243 -82
- package/src/snapshot/modelInitialData.ts +41 -0
- package/src/snapshot/reconcileArraySnapshot.ts +9 -3
- package/src/snapshot/reconcileModelSnapshot.ts +97 -17
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +9 -3
- package/src/tweaker/core.ts +30 -5
- package/src/tweaker/treeNodeMetadata.ts +38 -0
- package/src/tweaker/tweak.ts +15 -4
- package/src/tweaker/tweakArray.ts +159 -121
- package/src/tweaker/tweakFrozen.ts +2 -2
- package/src/tweaker/tweakModel.ts +2 -2
- package/src/tweaker/tweakPlainObject.ts +114 -122
- package/src/tweaker/typeChecking.ts +10 -1
- package/src/types/TypeChecker.ts +261 -15
- package/src/types/arrayBased/typesArray.ts +38 -40
- package/src/types/arrayBased/typesTuple.ts +51 -34
- package/src/types/createCachedTypeCheck.ts +181 -0
- package/src/types/objectBased/typesArraySet.ts +42 -32
- package/src/types/objectBased/typesDataModelData.ts +18 -11
- package/src/types/objectBased/typesModel.ts +1 -1
- package/src/types/objectBased/typesObject.ts +57 -28
- package/src/types/objectBased/typesObjectMap.ts +51 -40
- package/src/types/objectBased/typesRecord.ts +59 -39
- package/src/types/objectBased/typesRef.ts +1 -1
- package/src/types/primitiveBased/typesPrimitive.ts +9 -9
- package/src/types/tProp.ts +80 -44
- package/src/types/utility/typesOr.ts +35 -24
- package/src/types/utility/typesRefinement.ts +21 -8
- package/src/types/utility/typesSkipCheck.ts +21 -8
- package/src/types/utility/typesTag.ts +21 -9
- package/src/types/utility/typesUnchecked.ts +2 -3
- package/src/utils/ModelPool.ts +34 -19
- package/src/utils/errorDiagnostics.ts +60 -18
- package/src/utils/index.ts +9 -7
- package/src/types/createPerEntryCachedCheck.ts +0 -166
- /package/dist/types/{types/createPerEntryCachedCheck.d.ts → action/mutationBatch.d.ts} +0 -0
|
@@ -7,29 +7,35 @@ import {
|
|
|
7
7
|
observe,
|
|
8
8
|
} from "mobx"
|
|
9
9
|
import { assertCanWrite } from "../action/protection"
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
emitObjectAddDeepChange,
|
|
12
|
+
emitObjectRemoveDeepChange,
|
|
13
|
+
emitObjectUpdateDeepChange,
|
|
14
|
+
} from "../deepChange/onDeepChange"
|
|
11
15
|
import type { AnyModel } from "../model/BaseModel"
|
|
12
16
|
import { modelTypeKey } from "../model/metadata"
|
|
13
17
|
import type { ModelClass } from "../modelShared/BaseModelShared"
|
|
14
18
|
import { getModelInfoForName, getModelNotRegisteredErrorMessage } from "../modelShared/modelInfo"
|
|
15
19
|
import { dataToModelNode } from "../parent/core"
|
|
16
20
|
import type { ParentPath } from "../parent/path"
|
|
17
|
-
import type { Path } from "../parent/pathTypes"
|
|
18
21
|
import { setParent } from "../parent/setParent"
|
|
19
|
-
import { InternalPatchRecorder } from "../patch/emitPatch"
|
|
22
|
+
import { hasPatchListenersFor, InternalPatchRecorder } from "../patch/emitPatch"
|
|
20
23
|
import {
|
|
24
|
+
flushInternalSnapshot,
|
|
21
25
|
freezeInternalSnapshot,
|
|
22
26
|
getInternalSnapshot,
|
|
23
27
|
type SnapshotTransformFn,
|
|
24
28
|
setNewInternalSnapshot,
|
|
25
29
|
updateInternalSnapshot,
|
|
26
30
|
} from "../snapshot/internal"
|
|
27
|
-
import {
|
|
31
|
+
import { takeModelInitialDataSnapshot } from "../snapshot/modelInitialData"
|
|
32
|
+
import { failure, isPlainObject, isPrimitive, setProtoProp } from "../utils"
|
|
28
33
|
import { setIfDifferent } from "../utils/setIfDifferent"
|
|
29
|
-
import { runningWithoutSnapshotOrPatches,
|
|
34
|
+
import { runningWithoutSnapshotOrPatches, setTweakedObjectUntweakers } from "./core"
|
|
30
35
|
import { TweakerPriority } from "./TweakerPriority"
|
|
36
|
+
import { markAsTweakedObject } from "./treeNodeMetadata"
|
|
31
37
|
import { registerTweaker, tweak } from "./tweak"
|
|
32
|
-
import { runTypeCheckingAfterChange } from "./typeChecking"
|
|
38
|
+
import { isTypeCheckingAfterChangeEnabled, runTypeCheckingAfterChange } from "./typeChecking"
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* @internal
|
|
@@ -46,15 +52,9 @@ export function tweakPlainObject<T extends Record<string, any>>(
|
|
|
46
52
|
? originalObj
|
|
47
53
|
: observable.object({}, undefined, observableOptions)
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const untweak = () => {
|
|
53
|
-
interceptDisposer()
|
|
54
|
-
observeDisposer()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
tweakedObjects.set(tweakedObj, untweak)
|
|
55
|
+
// Mark it as tweaked before processing children. The cleanup functions are
|
|
56
|
+
// installed after the MobX handlers have been registered.
|
|
57
|
+
markAsTweakedObject(tweakedObj)
|
|
58
58
|
setParent(
|
|
59
59
|
tweakedObj, // value
|
|
60
60
|
parentPath,
|
|
@@ -64,10 +64,14 @@ export function tweakPlainObject<T extends Record<string, any>>(
|
|
|
64
64
|
false // cloneIfApplicable
|
|
65
65
|
)
|
|
66
66
|
|
|
67
|
-
const
|
|
67
|
+
const initialDataSnapshot =
|
|
68
|
+
isDataObject && snapshotModelType ? takeModelInitialDataSnapshot(tweakedObj) : undefined
|
|
69
|
+
const reuseInitialDataSnapshot =
|
|
70
|
+
!!initialDataSnapshot?.reusable && initialDataSnapshot.allValuesPrimitive
|
|
71
|
+
const untransformedSn: any = reuseInitialDataSnapshot ? initialDataSnapshot.snapshot : {}
|
|
68
72
|
|
|
69
73
|
// substitute initial values by tweaked values
|
|
70
|
-
const originalObjKeys = Object.keys(originalObj)
|
|
74
|
+
const originalObjKeys = reuseInitialDataSnapshot ? [] : Object.keys(originalObj)
|
|
71
75
|
const originalObjKeysLen = originalObjKeys.length
|
|
72
76
|
for (let i = 0; i < originalObjKeysLen; i++) {
|
|
73
77
|
const k = originalObjKeys[i]
|
|
@@ -77,7 +81,11 @@ export function tweakPlainObject<T extends Record<string, any>>(
|
|
|
77
81
|
if (!doNotTweakChildren) {
|
|
78
82
|
setIfDifferent(tweakedObj, k, v)
|
|
79
83
|
}
|
|
80
|
-
|
|
84
|
+
if (k === "__proto__") {
|
|
85
|
+
setProtoProp(untransformedSn, v)
|
|
86
|
+
} else {
|
|
87
|
+
untransformedSn[k] = v
|
|
88
|
+
}
|
|
81
89
|
} else {
|
|
82
90
|
const path = { parent: tweakedObj, path: k }
|
|
83
91
|
|
|
@@ -98,13 +106,17 @@ export function tweakPlainObject<T extends Record<string, any>>(
|
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
const valueSn = getInternalSnapshot(tweakedValue)!
|
|
101
|
-
|
|
109
|
+
if (k === "__proto__") {
|
|
110
|
+
setProtoProp(untransformedSn, valueSn.transformed)
|
|
111
|
+
} else {
|
|
112
|
+
untransformedSn[k] = valueSn.transformed
|
|
113
|
+
}
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
let transformFn: SnapshotTransformFn | undefined
|
|
106
118
|
if (snapshotModelType) {
|
|
107
|
-
|
|
119
|
+
untransformedSn[modelTypeKey] = snapshotModelType
|
|
108
120
|
|
|
109
121
|
const modelInfo = getModelInfoForName(snapshotModelType)
|
|
110
122
|
if (!modelInfo) {
|
|
@@ -123,8 +135,9 @@ export function tweakPlainObject<T extends Record<string, any>>(
|
|
|
123
135
|
transformFn
|
|
124
136
|
)
|
|
125
137
|
|
|
126
|
-
interceptDisposer = intercept(tweakedObj, interceptObjectMutation)
|
|
127
|
-
observeDisposer = observe(tweakedObj, objectDidChange)
|
|
138
|
+
const interceptDisposer = intercept(tweakedObj, interceptObjectMutation)
|
|
139
|
+
const observeDisposer = observe(tweakedObj, objectDidChange)
|
|
140
|
+
setTweakedObjectUntweakers(tweakedObj, interceptDisposer, observeDisposer)
|
|
128
141
|
|
|
129
142
|
return tweakedObj as any
|
|
130
143
|
}
|
|
@@ -134,7 +147,11 @@ const observableOptions = {
|
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
function mutateSet(k: PropertyKey, v: unknown, sn: Record<PropertyKey, unknown>) {
|
|
137
|
-
|
|
150
|
+
if (k === "__proto__") {
|
|
151
|
+
setProtoProp(sn, v)
|
|
152
|
+
} else {
|
|
153
|
+
sn[k] = v
|
|
154
|
+
}
|
|
138
155
|
}
|
|
139
156
|
|
|
140
157
|
function mutateDelete(k: PropertyKey, sn: Record<PropertyKey, unknown>) {
|
|
@@ -143,8 +160,6 @@ function mutateDelete(k: PropertyKey, sn: Record<PropertyKey, unknown>) {
|
|
|
143
160
|
|
|
144
161
|
const patchRecorder = new InternalPatchRecorder()
|
|
145
162
|
|
|
146
|
-
const emptyPath: Path = Object.freeze([])
|
|
147
|
-
|
|
148
163
|
function objectDidChange(change: IObjectDidChange): void {
|
|
149
164
|
if (typeof change.name !== "string") {
|
|
150
165
|
throw failure("change.name is not a string")
|
|
@@ -152,99 +167,88 @@ function objectDidChange(change: IObjectDidChange): void {
|
|
|
152
167
|
|
|
153
168
|
const obj = change.object
|
|
154
169
|
const actualNode = dataToModelNode(obj)
|
|
155
|
-
const oldUntransformedSn = getInternalSnapshot(actualNode)!.untransformed
|
|
156
170
|
|
|
157
171
|
patchRecorder.reset()
|
|
158
172
|
|
|
159
|
-
let mutate: ((sn: any) => void) | undefined
|
|
160
|
-
|
|
161
173
|
switch (change.type) {
|
|
162
|
-
case "add":
|
|
163
|
-
|
|
164
|
-
// Emit deep change for add
|
|
165
|
-
emitDeepChange(actualNode, {
|
|
166
|
-
type: DeepChangeType.ObjectAdd,
|
|
167
|
-
target: obj as object,
|
|
168
|
-
path: emptyPath,
|
|
169
|
-
key: change.name,
|
|
170
|
-
newValue: change.newValue,
|
|
171
|
-
isInit: getIsInInitPhase(),
|
|
172
|
-
})
|
|
174
|
+
case "add":
|
|
175
|
+
emitObjectAddDeepChange(actualNode, obj, change.name, change.newValue)
|
|
173
176
|
break
|
|
174
|
-
}
|
|
175
177
|
|
|
176
|
-
case "update":
|
|
177
|
-
|
|
178
|
-
// Emit deep change for update
|
|
179
|
-
emitDeepChange(actualNode, {
|
|
180
|
-
type: DeepChangeType.ObjectUpdate,
|
|
181
|
-
target: obj as object,
|
|
182
|
-
path: emptyPath,
|
|
183
|
-
key: change.name,
|
|
184
|
-
newValue: change.newValue,
|
|
185
|
-
oldValue: change.oldValue,
|
|
186
|
-
isInit: getIsInInitPhase(),
|
|
187
|
-
})
|
|
178
|
+
case "update":
|
|
179
|
+
emitObjectUpdateDeepChange(actualNode, obj, change.name, change.newValue, change.oldValue)
|
|
188
180
|
break
|
|
189
|
-
}
|
|
190
181
|
|
|
191
|
-
case "remove":
|
|
192
|
-
|
|
193
|
-
// Emit deep change for remove
|
|
194
|
-
emitDeepChange(actualNode, {
|
|
195
|
-
type: DeepChangeType.ObjectRemove,
|
|
196
|
-
target: obj as object,
|
|
197
|
-
path: emptyPath,
|
|
198
|
-
key: change.name,
|
|
199
|
-
oldValue: change.oldValue,
|
|
200
|
-
isInit: getIsInInitPhase(),
|
|
201
|
-
})
|
|
182
|
+
case "remove":
|
|
183
|
+
emitObjectRemoveDeepChange(actualNode, obj, change.name, change.oldValue)
|
|
202
184
|
break
|
|
203
|
-
}
|
|
204
185
|
|
|
205
186
|
default:
|
|
206
187
|
throw failure("assertion error: unsupported object change type")
|
|
207
188
|
}
|
|
208
189
|
|
|
209
|
-
|
|
190
|
+
if (runningWithoutSnapshotOrPatches) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const oldUntransformedSn = getInternalSnapshot(actualNode)!.untransformed
|
|
195
|
+
const shouldEmitPatches = hasPatchListenersFor(actualNode)
|
|
196
|
+
const shouldBuildInversePatches = shouldEmitPatches || isTypeCheckingAfterChangeEnabled()
|
|
210
197
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
198
|
+
let mutate: ((sn: any) => void) | undefined
|
|
199
|
+
switch (change.type) {
|
|
200
|
+
case "add":
|
|
201
|
+
case "update":
|
|
202
|
+
mutate = objectDidChangeAddOrUpdate(
|
|
203
|
+
change,
|
|
204
|
+
oldUntransformedSn,
|
|
205
|
+
shouldEmitPatches,
|
|
206
|
+
shouldBuildInversePatches
|
|
207
|
+
)
|
|
208
|
+
break
|
|
209
|
+
case "remove":
|
|
210
|
+
mutate = objectDidChangeRemove(
|
|
211
|
+
change,
|
|
212
|
+
oldUntransformedSn,
|
|
213
|
+
shouldEmitPatches,
|
|
214
|
+
shouldBuildInversePatches
|
|
215
|
+
)
|
|
216
|
+
break
|
|
217
|
+
default:
|
|
218
|
+
throw failure("assertion error: unsupported object change type")
|
|
214
219
|
}
|
|
220
|
+
|
|
221
|
+
runTypeCheckingAfterChange(obj, patchRecorder)
|
|
222
|
+
updateInternalSnapshot(actualNode, mutate)
|
|
223
|
+
patchRecorder.emit(actualNode)
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
function objectDidChangeRemove(
|
|
218
227
|
change: IObjectDidChange & { type: "remove" },
|
|
219
|
-
oldUntransformedSn: any
|
|
228
|
+
oldUntransformedSn: any,
|
|
229
|
+
shouldEmitPatches: boolean,
|
|
230
|
+
shouldBuildInversePatches: boolean
|
|
220
231
|
) {
|
|
221
232
|
const k = change.name
|
|
222
233
|
const oldVal = oldUntransformedSn[k]
|
|
223
234
|
const mutate = mutateDelete.bind(undefined, k)
|
|
224
235
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
{
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
],
|
|
234
|
-
[
|
|
235
|
-
{
|
|
236
|
-
op: "add",
|
|
237
|
-
path,
|
|
238
|
-
value: freezeInternalSnapshot(oldVal),
|
|
239
|
-
},
|
|
240
|
-
]
|
|
241
|
-
)
|
|
236
|
+
if (shouldEmitPatches || shouldBuildInversePatches) {
|
|
237
|
+
const path = [k as string]
|
|
238
|
+
const patches = shouldEmitPatches ? [{ op: "remove" as const, path }] : undefined
|
|
239
|
+
const invPatches = shouldBuildInversePatches
|
|
240
|
+
? [{ op: "add" as const, path, value: freezeInternalSnapshot(oldVal) }]
|
|
241
|
+
: undefined
|
|
242
|
+
patchRecorder.record(patches, invPatches)
|
|
243
|
+
}
|
|
242
244
|
return mutate
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
function objectDidChangeAddOrUpdate(
|
|
246
248
|
change: IObjectWillChange & { type: "add" | "update" },
|
|
247
|
-
oldUntransformedSn: any
|
|
249
|
+
oldUntransformedSn: any,
|
|
250
|
+
shouldEmitPatches: boolean,
|
|
251
|
+
shouldBuildInversePatches: boolean
|
|
248
252
|
) {
|
|
249
253
|
const k = change.name
|
|
250
254
|
const val = change.newValue
|
|
@@ -261,40 +265,23 @@ function objectDidChangeAddOrUpdate(
|
|
|
261
265
|
|
|
262
266
|
const mutate = mutateSet.bind(undefined, k, newVal)
|
|
263
267
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
} else {
|
|
282
|
-
patchRecorder.record(
|
|
283
|
-
[
|
|
284
|
-
{
|
|
285
|
-
op: "replace",
|
|
286
|
-
path,
|
|
287
|
-
value: freezeInternalSnapshot(newVal),
|
|
288
|
-
},
|
|
289
|
-
],
|
|
290
|
-
[
|
|
291
|
-
{
|
|
292
|
-
op: "replace",
|
|
293
|
-
path,
|
|
294
|
-
value: freezeInternalSnapshot(oldVal),
|
|
295
|
-
},
|
|
296
|
-
]
|
|
297
|
-
)
|
|
268
|
+
if (shouldEmitPatches || shouldBuildInversePatches) {
|
|
269
|
+
const path = [k as string]
|
|
270
|
+
if (change.type === "add") {
|
|
271
|
+
const patches = shouldEmitPatches
|
|
272
|
+
? [{ op: "add" as const, path, value: freezeInternalSnapshot(newVal) }]
|
|
273
|
+
: undefined
|
|
274
|
+
const invPatches = shouldBuildInversePatches ? [{ op: "remove" as const, path }] : undefined
|
|
275
|
+
patchRecorder.record(patches, invPatches)
|
|
276
|
+
} else {
|
|
277
|
+
const patches = shouldEmitPatches
|
|
278
|
+
? [{ op: "replace" as const, path, value: freezeInternalSnapshot(newVal) }]
|
|
279
|
+
: undefined
|
|
280
|
+
const invPatches = shouldBuildInversePatches
|
|
281
|
+
? [{ op: "replace" as const, path, value: freezeInternalSnapshot(oldVal) }]
|
|
282
|
+
: undefined
|
|
283
|
+
patchRecorder.record(patches, invPatches)
|
|
284
|
+
}
|
|
298
285
|
}
|
|
299
286
|
|
|
300
287
|
return mutate
|
|
@@ -307,6 +294,11 @@ function interceptObjectMutation(change: IObjectWillChange) {
|
|
|
307
294
|
throw failure("symbol properties are not supported")
|
|
308
295
|
}
|
|
309
296
|
|
|
297
|
+
// This must happen before detach/reparent work below. It keeps the old value
|
|
298
|
+
// available for inverse patches and folds a departing dirty child into its
|
|
299
|
+
// parent snapshot while its parent path still exists.
|
|
300
|
+
flushInternalSnapshot(dataToModelNode(change.object), false)
|
|
301
|
+
|
|
310
302
|
switch (change.type) {
|
|
311
303
|
case "add":
|
|
312
304
|
change.newValue = tweak(change.newValue, {
|
|
@@ -18,6 +18,15 @@ import { isTypeCheckingAllowed } from "./withoutTypeChecking"
|
|
|
18
18
|
// still be invalid, causing another rollback attempt).
|
|
19
19
|
let isRollingBackTypeCheckFailure = false
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export function isTypeCheckingAfterChangeEnabled(): boolean {
|
|
25
|
+
return (
|
|
26
|
+
isTypeCheckingAllowed() && isModelAutoTypeCheckingEnabled() && !isRollingBackTypeCheckFailure
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
function isModelWithTypeChecker(obj: object): obj is AnyModel {
|
|
22
31
|
return isModel(obj) && !!getModelMetadata(obj).dataType
|
|
23
32
|
}
|
|
@@ -68,7 +77,7 @@ export function runTypeCheckingAfterChange(
|
|
|
68
77
|
return
|
|
69
78
|
}
|
|
70
79
|
|
|
71
|
-
if (patchRecorder && patchRecorder.
|
|
80
|
+
if (patchRecorder && !patchRecorder.hasChanges) {
|
|
72
81
|
// No patches means no effective change.
|
|
73
82
|
return
|
|
74
83
|
}
|