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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { action, isAction } from "mobx"
|
|
2
|
-
import { fastGetParentPath } from "../parent/path"
|
|
3
|
-
import type { Path, WritablePath } from "../parent/pathTypes"
|
|
2
|
+
import { fastGetParentPath, type ParentPath } from "../parent/path"
|
|
3
|
+
import type { Path, PathElement, WritablePath } from "../parent/pathTypes"
|
|
4
4
|
import { assertTweakedObject } from "../tweaker/core"
|
|
5
|
-
import { assertIsFunction, deleteFromArray } from "../utils"
|
|
5
|
+
import { assertIsFunction, deleteFromArray, failure } from "../utils"
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Disposer function to stop listening to deep changes.
|
|
@@ -99,11 +99,26 @@ export type OnDeepChangeListener = (change: DeepChange) => void
|
|
|
99
99
|
|
|
100
100
|
const deepChangeListeners = new WeakMap<object, OnDeepChangeListener[]>()
|
|
101
101
|
const globalDeepChangeListeners: ((target: object, change: DeepChange) => void)[] = []
|
|
102
|
+
const emptyPath: Path = Object.freeze([])
|
|
102
103
|
|
|
103
104
|
let deepChangeListenerCount = 0
|
|
104
105
|
|
|
105
106
|
let initPhaseDepth = 0
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
export function hasAnyDeepChangeListeners(): boolean {
|
|
112
|
+
return deepChangeListenerCount > 0 || hasGlobalDeepChangeListeners()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
export function hasGlobalDeepChangeListeners(): boolean {
|
|
119
|
+
return globalDeepChangeListeners.length > 0
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
/**
|
|
108
123
|
* @internal
|
|
109
124
|
* Returns whether we're currently in an initialization phase.
|
|
@@ -132,9 +147,7 @@ export function exitInitPhase(): void {
|
|
|
132
147
|
* @internal
|
|
133
148
|
*/
|
|
134
149
|
export function emitDeepChange(obj: object, change: DeepChange): void {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (!hasAnyListeners) {
|
|
150
|
+
if (!hasAnyDeepChangeListeners()) {
|
|
138
151
|
return
|
|
139
152
|
}
|
|
140
153
|
|
|
@@ -144,6 +157,200 @@ export function emitDeepChange(obj: object, change: DeepChange): void {
|
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
|
|
160
|
+
/**
|
|
161
|
+
* @internal
|
|
162
|
+
*/
|
|
163
|
+
export function emitObjectAddDeepChange(
|
|
164
|
+
node: object,
|
|
165
|
+
target: object,
|
|
166
|
+
key: string,
|
|
167
|
+
newValue: unknown
|
|
168
|
+
): void {
|
|
169
|
+
emitDeepChangeFromValues(node, DeepChangeType.ObjectAdd, target, key, newValue, undefined)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
export function emitObjectUpdateDeepChange(
|
|
176
|
+
node: object,
|
|
177
|
+
target: object,
|
|
178
|
+
key: string,
|
|
179
|
+
newValue: unknown,
|
|
180
|
+
oldValue: unknown
|
|
181
|
+
): void {
|
|
182
|
+
emitDeepChangeFromValues(node, DeepChangeType.ObjectUpdate, target, key, newValue, oldValue)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @internal
|
|
187
|
+
*/
|
|
188
|
+
export function emitObjectRemoveDeepChange(
|
|
189
|
+
node: object,
|
|
190
|
+
target: object,
|
|
191
|
+
key: string,
|
|
192
|
+
oldValue: unknown
|
|
193
|
+
): void {
|
|
194
|
+
emitDeepChangeFromValues(node, DeepChangeType.ObjectRemove, target, key, undefined, oldValue)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @internal
|
|
199
|
+
*/
|
|
200
|
+
export function emitArraySpliceDeepChange(
|
|
201
|
+
node: object,
|
|
202
|
+
target: unknown[],
|
|
203
|
+
index: number,
|
|
204
|
+
addedValues: unknown[],
|
|
205
|
+
removedValues: unknown[]
|
|
206
|
+
): void {
|
|
207
|
+
emitDeepChangeFromValues(
|
|
208
|
+
node,
|
|
209
|
+
DeepChangeType.ArraySplice,
|
|
210
|
+
target,
|
|
211
|
+
index,
|
|
212
|
+
undefined,
|
|
213
|
+
undefined,
|
|
214
|
+
addedValues,
|
|
215
|
+
removedValues
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @internal
|
|
221
|
+
*/
|
|
222
|
+
export function emitArrayUpdateDeepChange(
|
|
223
|
+
node: object,
|
|
224
|
+
target: unknown[],
|
|
225
|
+
index: number,
|
|
226
|
+
newValue: unknown,
|
|
227
|
+
oldValue: unknown
|
|
228
|
+
): void {
|
|
229
|
+
emitDeepChangeFromValues(node, DeepChangeType.ArrayUpdate, target, index, newValue, oldValue)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function emitDeepChangeFromValues(
|
|
233
|
+
node: object,
|
|
234
|
+
type: DeepChangeType,
|
|
235
|
+
target: object | unknown[],
|
|
236
|
+
keyOrIndex: string | number,
|
|
237
|
+
newValue: unknown,
|
|
238
|
+
oldValue: unknown,
|
|
239
|
+
addedValues?: unknown[],
|
|
240
|
+
removedValues?: unknown[]
|
|
241
|
+
): void {
|
|
242
|
+
let change: DeepChange | undefined
|
|
243
|
+
|
|
244
|
+
if (globalDeepChangeListeners.length > 0) {
|
|
245
|
+
change = createDeepChange(
|
|
246
|
+
type,
|
|
247
|
+
target,
|
|
248
|
+
keyOrIndex,
|
|
249
|
+
newValue,
|
|
250
|
+
oldValue,
|
|
251
|
+
addedValues,
|
|
252
|
+
removedValues
|
|
253
|
+
)
|
|
254
|
+
emitGlobalDeepChange(node, change)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (deepChangeListenerCount === 0) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Segments are pushed in child-to-root order (O(1) each) rather than unshifted
|
|
262
|
+
// at the front (O(depth) each), keeping the whole walk O(depth) instead of
|
|
263
|
+
// O(depth^2) on deep trees.
|
|
264
|
+
const reversedPrefix: WritablePath = []
|
|
265
|
+
let current: object | undefined = node
|
|
266
|
+
while (current) {
|
|
267
|
+
const listenersForObject = deepChangeListeners.get(current)
|
|
268
|
+
if (listenersForObject && listenersForObject.length > 0) {
|
|
269
|
+
change ??= createDeepChange(
|
|
270
|
+
type,
|
|
271
|
+
target,
|
|
272
|
+
keyOrIndex,
|
|
273
|
+
newValue,
|
|
274
|
+
oldValue,
|
|
275
|
+
addedValues,
|
|
276
|
+
removedValues
|
|
277
|
+
)
|
|
278
|
+
emitDeepChangeForTarget(current, change, reversedPrefix, reversedPrefix.length)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const parentPath: ParentPath<object> | undefined = fastGetParentPath(current, false)
|
|
282
|
+
if (!parentPath) {
|
|
283
|
+
break
|
|
284
|
+
}
|
|
285
|
+
reversedPrefix.push(parentPath.path)
|
|
286
|
+
current = parentPath.parent
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function createDeepChange(
|
|
291
|
+
type: DeepChangeType,
|
|
292
|
+
target: object | unknown[],
|
|
293
|
+
keyOrIndex: string | number,
|
|
294
|
+
newValue: unknown,
|
|
295
|
+
oldValue: unknown,
|
|
296
|
+
addedValues: unknown[] | undefined,
|
|
297
|
+
removedValues: unknown[] | undefined
|
|
298
|
+
): DeepChange {
|
|
299
|
+
const isInit = getIsInInitPhase()
|
|
300
|
+
switch (type) {
|
|
301
|
+
case DeepChangeType.ObjectAdd:
|
|
302
|
+
return {
|
|
303
|
+
type,
|
|
304
|
+
target: target as object,
|
|
305
|
+
path: emptyPath,
|
|
306
|
+
key: keyOrIndex as string,
|
|
307
|
+
newValue,
|
|
308
|
+
isInit,
|
|
309
|
+
}
|
|
310
|
+
case DeepChangeType.ObjectUpdate:
|
|
311
|
+
return {
|
|
312
|
+
type,
|
|
313
|
+
target: target as object,
|
|
314
|
+
path: emptyPath,
|
|
315
|
+
key: keyOrIndex as string,
|
|
316
|
+
newValue,
|
|
317
|
+
oldValue,
|
|
318
|
+
isInit,
|
|
319
|
+
}
|
|
320
|
+
case DeepChangeType.ObjectRemove:
|
|
321
|
+
return {
|
|
322
|
+
type,
|
|
323
|
+
target: target as object,
|
|
324
|
+
path: emptyPath,
|
|
325
|
+
key: keyOrIndex as string,
|
|
326
|
+
oldValue,
|
|
327
|
+
isInit,
|
|
328
|
+
}
|
|
329
|
+
case DeepChangeType.ArraySplice:
|
|
330
|
+
return {
|
|
331
|
+
type,
|
|
332
|
+
target: target as unknown[],
|
|
333
|
+
path: emptyPath,
|
|
334
|
+
index: keyOrIndex as number,
|
|
335
|
+
addedValues: addedValues!,
|
|
336
|
+
removedValues: removedValues!,
|
|
337
|
+
isInit,
|
|
338
|
+
}
|
|
339
|
+
case DeepChangeType.ArrayUpdate:
|
|
340
|
+
return {
|
|
341
|
+
type,
|
|
342
|
+
target: target as unknown[],
|
|
343
|
+
path: emptyPath,
|
|
344
|
+
index: keyOrIndex as number,
|
|
345
|
+
newValue,
|
|
346
|
+
oldValue,
|
|
347
|
+
isInit,
|
|
348
|
+
}
|
|
349
|
+
default:
|
|
350
|
+
throw failure("unsupported deep change type")
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
147
354
|
function emitGlobalDeepChange(obj: object, change: DeepChange): void {
|
|
148
355
|
for (let i = 0; i < globalDeepChangeListeners.length; i++) {
|
|
149
356
|
const listener = globalDeepChangeListeners[i]
|
|
@@ -152,21 +359,32 @@ function emitGlobalDeepChange(obj: object, change: DeepChange): void {
|
|
|
152
359
|
}
|
|
153
360
|
|
|
154
361
|
function emitDeepChangeToListeners(obj: object, change: DeepChange): void {
|
|
155
|
-
|
|
362
|
+
// Segments are pushed in child-to-root order (O(1) each) rather than unshifted
|
|
363
|
+
// at the front (O(depth) each), keeping the whole walk O(depth) instead of
|
|
364
|
+
// O(depth^2) on deep trees.
|
|
365
|
+
const reversedPrefix: WritablePath = []
|
|
156
366
|
|
|
157
|
-
emitDeepChangeForTarget(obj, change,
|
|
367
|
+
emitDeepChangeForTarget(obj, change, reversedPrefix, 0)
|
|
158
368
|
|
|
159
369
|
// and also emit subtree listeners all the way to the root
|
|
160
370
|
let parentPath = fastGetParentPath(obj, false)
|
|
161
371
|
while (parentPath) {
|
|
162
|
-
|
|
163
|
-
emitDeepChangeForTarget(parentPath.parent, change,
|
|
372
|
+
reversedPrefix.push(parentPath.path)
|
|
373
|
+
emitDeepChangeForTarget(parentPath.parent, change, reversedPrefix, reversedPrefix.length)
|
|
164
374
|
|
|
165
375
|
parentPath = fastGetParentPath(parentPath.parent, false)
|
|
166
376
|
}
|
|
167
377
|
}
|
|
168
378
|
|
|
169
|
-
|
|
379
|
+
// `reversedPrefix` holds the path segments from the changed node up to (but not
|
|
380
|
+
// including) `obj`, in child-to-root order; the emitted path prepends that slice
|
|
381
|
+
// reversed (root-to-child). `prefixCount` is how many leading entries apply.
|
|
382
|
+
function emitDeepChangeForTarget(
|
|
383
|
+
obj: object,
|
|
384
|
+
change: DeepChange,
|
|
385
|
+
reversedPrefix: readonly PathElement[],
|
|
386
|
+
prefixCount: number
|
|
387
|
+
): void {
|
|
170
388
|
const listenersForObject = deepChangeListeners.get(obj)
|
|
171
389
|
|
|
172
390
|
if (!listenersForObject || listenersForObject.length === 0) {
|
|
@@ -174,11 +392,8 @@ function emitDeepChangeForTarget(obj: object, change: DeepChange, pathPrefix: Pa
|
|
|
174
392
|
}
|
|
175
393
|
|
|
176
394
|
const changeWithPath =
|
|
177
|
-
|
|
178
|
-
? {
|
|
179
|
-
...change,
|
|
180
|
-
path: [...pathPrefix, ...change.path],
|
|
181
|
-
}
|
|
395
|
+
prefixCount > 0
|
|
396
|
+
? { ...change, path: buildPrefixedPath(reversedPrefix, prefixCount, change.path) }
|
|
182
397
|
: change
|
|
183
398
|
|
|
184
399
|
for (let i = 0; i < listenersForObject.length; i++) {
|
|
@@ -187,6 +402,21 @@ function emitDeepChangeForTarget(obj: object, change: DeepChange, pathPrefix: Pa
|
|
|
187
402
|
}
|
|
188
403
|
}
|
|
189
404
|
|
|
405
|
+
function buildPrefixedPath(
|
|
406
|
+
reversedPrefix: readonly PathElement[],
|
|
407
|
+
prefixCount: number,
|
|
408
|
+
suffix: Path
|
|
409
|
+
): WritablePath {
|
|
410
|
+
const newPath: WritablePath = new Array(prefixCount + suffix.length)
|
|
411
|
+
for (let i = 0; i < prefixCount; i++) {
|
|
412
|
+
newPath[i] = reversedPrefix[prefixCount - 1 - i]
|
|
413
|
+
}
|
|
414
|
+
for (let j = 0; j < suffix.length; j++) {
|
|
415
|
+
newPath[prefixCount + j] = suffix[j]
|
|
416
|
+
}
|
|
417
|
+
return newPath
|
|
418
|
+
}
|
|
419
|
+
|
|
190
420
|
/**
|
|
191
421
|
* Adds a listener that will be called every time a deep change is generated for the tree of the given target object.
|
|
192
422
|
* Unlike `onPatches`, this provides raw MobX change information including proper splice detection for arrays.
|
package/src/model/BaseModel.ts
CHANGED
|
@@ -53,10 +53,10 @@ export abstract class BaseModel<
|
|
|
53
53
|
ModelIdPropertyName extends string = never,
|
|
54
54
|
> {
|
|
55
55
|
// just to make typing work properly
|
|
56
|
-
[propsTypeSymbol]
|
|
57
|
-
[fromSnapshotOverrideTypeSymbol]
|
|
58
|
-
[toSnapshotOverrideTypeSymbol]
|
|
59
|
-
[modelIdPropertyNameSymbol]
|
|
56
|
+
declare [propsTypeSymbol]: TProps
|
|
57
|
+
declare [fromSnapshotOverrideTypeSymbol]: FromSnapshotOverride
|
|
58
|
+
declare [toSnapshotOverrideTypeSymbol]: ToSnapshotOverride
|
|
59
|
+
declare [modelIdPropertyNameSymbol]: ModelIdPropertyName
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Model type name.
|
|
@@ -139,14 +139,6 @@ export abstract class BaseModel<
|
|
|
139
139
|
|
|
140
140
|
Object.setPrototypeOf(this, modelClass!.prototype)
|
|
141
141
|
|
|
142
|
-
const self = this as any
|
|
143
|
-
|
|
144
|
-
// delete unnecessary props
|
|
145
|
-
delete self[propsTypeSymbol]
|
|
146
|
-
delete self[fromSnapshotOverrideTypeSymbol]
|
|
147
|
-
delete self[toSnapshotOverrideTypeSymbol]
|
|
148
|
-
delete self[modelIdPropertyNameSymbol]
|
|
149
|
-
|
|
150
142
|
if (snapshotInitialData) {
|
|
151
143
|
// from snapshot
|
|
152
144
|
internalFromSnapshotModel(this, snapshotInitialData, modelClass!, !!generateNewIds)
|
package/src/model/Model.ts
CHANGED
|
@@ -217,6 +217,9 @@ export interface ModelOptions<TProps extends ModelProps, FS, TS> {
|
|
|
217
217
|
/**
|
|
218
218
|
* Optional transformation that will be run when converting from a snapshot to the data part of the model.
|
|
219
219
|
* Useful for example to do versioning and keep the data part up to date with the latest version of the model.
|
|
220
|
+
* Snapshot processors must be pure and deterministic. The model's canonical output snapshot must round-trip
|
|
221
|
+
* through this processor to the same model data. Reconciliation may skip invoking it when the input already
|
|
222
|
+
* equals the model's current canonical output snapshot.
|
|
220
223
|
*
|
|
221
224
|
* @param sn The custom input snapshot.
|
|
222
225
|
* @returns An input snapshot that must match the expected model input snapshot.
|
|
@@ -225,6 +228,8 @@ export interface ModelOptions<TProps extends ModelProps, FS, TS> {
|
|
|
225
228
|
|
|
226
229
|
/**
|
|
227
230
|
* Optional transformation that will be run when converting the data part of the model into a snapshot.
|
|
231
|
+
* Snapshot processors must be pure and deterministic, and their canonical output must round-trip through the
|
|
232
|
+
* corresponding input processor to the same model data. Snapshot generation may cache processor results.
|
|
228
233
|
*
|
|
229
234
|
* @param sn The output snapshot.
|
|
230
235
|
* @param modelInstance The model instance.
|
package/src/model/newModel.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { O } from "ts-toolbelt"
|
|
|
3
3
|
import {
|
|
4
4
|
DeepChangeType,
|
|
5
5
|
emitDeepChange,
|
|
6
|
+
hasGlobalDeepChangeListeners,
|
|
6
7
|
type ObjectAddChange,
|
|
7
8
|
type ObjectUpdateChange,
|
|
8
9
|
} from "../deepChange/onDeepChange"
|
|
@@ -12,8 +13,13 @@ import { modelInfoByClass } from "../modelShared/modelInfo"
|
|
|
12
13
|
import { getInternalModelClassPropsInfo } from "../modelShared/modelPropsInfo"
|
|
13
14
|
import { applyModelInitializers } from "../modelShared/newModel"
|
|
14
15
|
import { getModelPropStoredDefaultValue, noDefaultValue } from "../modelShared/prop"
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
createPatchForObjectValueChange,
|
|
18
|
+
emitPatches,
|
|
19
|
+
hasGlobalPatchListeners,
|
|
20
|
+
} from "../patch/emitPatch"
|
|
16
21
|
import type { Patch } from "../patch/Patch"
|
|
22
|
+
import { invalidateModelInitialDataSnapshot } from "../snapshot/modelInitialData"
|
|
17
23
|
import { tweakModel } from "../tweaker/tweakModel"
|
|
18
24
|
import { tweakPlainObject } from "../tweaker/tweakPlainObject"
|
|
19
25
|
import { failure, inDevMode, makePropReadonly } from "../utils"
|
|
@@ -72,9 +78,10 @@ export const internalNewModel = action(
|
|
|
72
78
|
let changed = false
|
|
73
79
|
|
|
74
80
|
// apply untransform (if any) if not in snapshot mode
|
|
75
|
-
|
|
81
|
+
const transform = propData._getTransform ? propData._getTransform() : propData._transform
|
|
82
|
+
if (transform) {
|
|
76
83
|
changed = true
|
|
77
|
-
newValue =
|
|
84
|
+
newValue = transform.untransform(newValue, modelObj, k)
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
// apply default value (if needed)
|
|
@@ -145,17 +152,28 @@ export const internalFromSnapshotModel = action(
|
|
|
145
152
|
const modelObj = origModelObj as O.Writable<M>
|
|
146
153
|
modelObj[modelTypeKey] = modelInfo.name
|
|
147
154
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
// At this point the model is not attached to a tree yet, so initialization
|
|
156
|
+
// corrections can only be observed by global listeners. Tree-local listeners
|
|
157
|
+
// will observe the eventual insertion of the fully initialized model instead.
|
|
158
|
+
const shouldEmitPatches = hasGlobalPatchListeners()
|
|
159
|
+
const shouldEmitDeepChanges = hasGlobalDeepChangeListeners()
|
|
160
|
+
let patches: Patch[] | undefined
|
|
161
|
+
let inversePatches: Patch[] | undefined
|
|
162
|
+
let defaultsApplied: { key: string; oldValue: unknown; newValue: unknown }[] | undefined
|
|
151
163
|
|
|
152
164
|
if (modelIdPropertyName) {
|
|
153
165
|
const initialValue = initialData[modelIdPropertyName]
|
|
154
166
|
const valueChanged = setIfDifferentWithReturn(initialData, modelIdPropertyName, id)
|
|
155
167
|
|
|
156
168
|
if (valueChanged) {
|
|
169
|
+
invalidateModelInitialDataSnapshot(initialData)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (valueChanged && shouldEmitPatches) {
|
|
157
173
|
const modelIdPath = [modelIdPropertyName]
|
|
158
174
|
|
|
175
|
+
patches ??= []
|
|
176
|
+
inversePatches ??= []
|
|
159
177
|
patches.push(createPatchForObjectValueChange(modelIdPath, initialValue, id))
|
|
160
178
|
inversePatches.push(createPatchForObjectValueChange(modelIdPath, id, initialValue))
|
|
161
179
|
}
|
|
@@ -192,15 +210,23 @@ export const internalFromSnapshotModel = action(
|
|
|
192
210
|
if (changed) {
|
|
193
211
|
// setIfDifferent not required
|
|
194
212
|
set(initialData, k, newValue)
|
|
213
|
+
invalidateModelInitialDataSnapshot(initialData)
|
|
195
214
|
|
|
196
215
|
if (newValue !== initialValue) {
|
|
197
|
-
|
|
216
|
+
if (shouldEmitPatches) {
|
|
217
|
+
const propPath = [k]
|
|
198
218
|
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
patches ??= []
|
|
220
|
+
inversePatches ??= []
|
|
221
|
+
patches.push(createPatchForObjectValueChange(propPath, initialValue, newValue))
|
|
222
|
+
inversePatches.push(createPatchForObjectValueChange(propPath, newValue, initialValue))
|
|
223
|
+
}
|
|
201
224
|
|
|
202
225
|
// Track defaults applied for deep change emission
|
|
203
|
-
|
|
226
|
+
if (shouldEmitDeepChanges) {
|
|
227
|
+
defaultsApplied ??= []
|
|
228
|
+
defaultsApplied.push({ key: k, oldValue: initialValue, newValue })
|
|
229
|
+
}
|
|
204
230
|
}
|
|
205
231
|
}
|
|
206
232
|
}
|
|
@@ -208,9 +234,11 @@ export const internalFromSnapshotModel = action(
|
|
|
208
234
|
// also emit a patch for modelType, since it will get included in the snapshot
|
|
209
235
|
const initialModelType = snapshotInitialData?.unprocessedModelType
|
|
210
236
|
const newModelType = modelInfo.name
|
|
211
|
-
if (initialModelType !== newModelType) {
|
|
237
|
+
if (initialModelType !== newModelType && shouldEmitPatches) {
|
|
212
238
|
const modelTypePath = [modelTypeKey]
|
|
213
239
|
|
|
240
|
+
patches ??= []
|
|
241
|
+
inversePatches ??= []
|
|
214
242
|
patches.push(createPatchForObjectValueChange(modelTypePath, initialModelType, newModelType))
|
|
215
243
|
inversePatches.push(
|
|
216
244
|
createPatchForObjectValueChange(modelTypePath, newModelType, initialModelType)
|
|
@@ -219,35 +247,39 @@ export const internalFromSnapshotModel = action(
|
|
|
219
247
|
|
|
220
248
|
finalizeNewModel(modelObj, initialData, modelClass)
|
|
221
249
|
|
|
222
|
-
|
|
250
|
+
if (patches && inversePatches) {
|
|
251
|
+
emitPatches(modelObj, patches, inversePatches)
|
|
252
|
+
}
|
|
223
253
|
|
|
224
254
|
// Emit deep changes for defaults applied during fromSnapshot
|
|
225
255
|
// These are emitted with isInit: true so bindings can sync them to CRDTs
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
if (defaultsApplied) {
|
|
257
|
+
for (const { key, oldValue } of defaultsApplied) {
|
|
258
|
+
const target = modelObj.$
|
|
259
|
+
if (oldValue === undefined) {
|
|
260
|
+
// Key was added (didn't exist in snapshot)
|
|
261
|
+
const change: ObjectAddChange = {
|
|
262
|
+
type: DeepChangeType.ObjectAdd,
|
|
263
|
+
path: [],
|
|
264
|
+
target,
|
|
265
|
+
key,
|
|
266
|
+
newValue: target[key], // Use the tweaked value from $
|
|
267
|
+
isInit: true,
|
|
268
|
+
}
|
|
269
|
+
emitDeepChange(modelObj, change)
|
|
270
|
+
} else {
|
|
271
|
+
// Key was updated (had a different value in snapshot, e.g., null -> default)
|
|
272
|
+
const change: ObjectUpdateChange = {
|
|
273
|
+
type: DeepChangeType.ObjectUpdate,
|
|
274
|
+
path: [],
|
|
275
|
+
target,
|
|
276
|
+
key,
|
|
277
|
+
newValue: target[key], // Use the tweaked value from $
|
|
278
|
+
oldValue,
|
|
279
|
+
isInit: true,
|
|
280
|
+
}
|
|
281
|
+
emitDeepChange(modelObj, change)
|
|
249
282
|
}
|
|
250
|
-
emitDeepChange(modelObj, change)
|
|
251
283
|
}
|
|
252
284
|
}
|
|
253
285
|
|