mobx-keystone 1.24.1 → 2.0.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 +137 -0
- package/README.md +5 -1
- package/dist/mobx-keystone.esm.js +1608 -1072
- package/dist/mobx-keystone.esm.mjs +1608 -1072
- package/dist/mobx-keystone.umd.js +1613 -1072
- package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
- package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
- package/dist/types/deepChange/onDeepChange.d.ts +9 -0
- package/dist/types/parent/detach.d.ts +1 -1
- package/dist/types/parent/findChildren.d.ts +1 -1
- package/dist/types/patch/applyPatches.d.ts +2 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
- package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
- package/dist/types/standardActions/arrayActions.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +0 -4
- package/dist/types/types/TypeCheckError.d.ts +1 -0
- package/dist/types/types/utility/typesRefinement.d.ts +2 -2
- package/dist/types/utils/forEachWithDelayedThrow.d.ts +1 -0
- package/dist/types/utils/index.d.ts +13 -0
- package/dist/types/utils/mapUtils.d.ts +2 -2
- package/dist/types/utils/setLike.d.ts +1 -0
- package/package.json +7 -7
- package/src/action/applyAction.ts +1 -1
- package/src/action/middleware.ts +17 -36
- package/src/action/modelFlow.ts +4 -5
- package/src/action/mutationBatch.ts +10 -10
- package/src/action/pendingActions.ts +3 -4
- package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +19 -14
- package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +29 -20
- package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +1 -1
- package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +4 -3
- package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +4 -12
- package/src/actionMiddlewares/actionSerialization/setSerializer.ts +4 -13
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +11 -13
- package/src/actionMiddlewares/transactionMiddleware.ts +42 -11
- package/src/actionMiddlewares/undoMiddleware.ts +76 -60
- package/src/actionMiddlewares/utils.ts +2 -1
- package/src/computedTree/computedTree.ts +14 -3
- package/src/context/context.ts +17 -45
- package/src/dataModel/BaseDataModel.ts +20 -16
- package/src/dataModel/dataModelInstanceCache.ts +8 -0
- package/src/deepChange/onDeepChange.ts +179 -24
- package/src/frozen/Frozen.ts +50 -33
- package/src/model/BaseModel.ts +15 -14
- package/src/model/newModel.ts +32 -28
- package/src/model/utils.ts +2 -2
- package/src/modelShared/modelClassInitializer.ts +3 -2
- package/src/modelShared/modelDecorator.ts +34 -14
- package/src/modelShared/modelInfo.ts +1 -1
- package/src/modelShared/prop.ts +6 -9
- package/src/modelShared/sharedInternalModel.ts +16 -3
- package/src/parent/detach.ts +1 -1
- package/src/parent/findChildren.ts +4 -7
- package/src/parent/findParent.ts +14 -9
- package/src/parent/onChildAttachedTo.ts +45 -40
- package/src/parent/path.ts +11 -7
- package/src/parent/setParent.ts +33 -14
- package/src/parent/walkTree.ts +21 -37
- package/src/patch/applyPatches.ts +75 -60
- package/src/patch/emitPatch.ts +142 -26
- package/src/patch/jsonPatch.ts +5 -0
- package/src/patch/patchRecorder.ts +18 -14
- package/src/redux/connectReduxDevTools.ts +24 -18
- package/src/redux/redux.ts +5 -21
- package/src/ref/core.ts +18 -2
- package/src/rootStore/attachDetach.ts +27 -16
- package/src/rootStore/rootStore.ts +10 -6
- package/src/snapshot/SnapshotOf.ts +2 -2
- package/src/snapshot/applySnapshot.ts +10 -1
- package/src/snapshot/clone.ts +1 -2
- package/src/snapshot/cloneTreeValue.ts +1 -2
- package/src/snapshot/fromModelSnapshot.ts +6 -2
- package/src/snapshot/fromSnapshot.ts +3 -1
- package/src/snapshot/internal.ts +22 -9
- package/src/snapshot/modelInitialData.ts +14 -19
- package/src/snapshot/onSnapshot.ts +12 -1
- package/src/snapshot/reconcileModelSnapshot.ts +16 -8
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -4
- package/src/snapshot/reconcileSnapshot.ts +13 -4
- package/src/standardActions/arrayActions.ts +12 -2
- package/src/standardActions/objectActions.ts +3 -2
- package/src/transforms/ImmutableDate.ts +4 -0
- package/src/transforms/asMap.ts +3 -1
- package/src/treeUtils/deepEquals.ts +15 -13
- package/src/treeUtils/draft.ts +8 -0
- package/src/treeUtils/sandbox.ts +31 -27
- package/src/tweaker/core.ts +2 -4
- package/src/tweaker/tweak.ts +8 -8
- package/src/tweaker/tweakArray.ts +86 -37
- package/src/tweaker/tweakFrozen.ts +0 -1
- package/src/tweaker/tweakModel.ts +0 -1
- package/src/tweaker/tweakPlainObject.ts +78 -29
- package/src/tweaker/typeChecking.ts +132 -33
- package/src/types/TypeCheckError.ts +17 -19
- package/src/types/createCachedTypeCheck.ts +27 -2
- package/src/types/objectBased/typesDataModelData.ts +4 -2
- package/src/types/objectBased/typesModel.ts +4 -2
- package/src/types/objectBased/typesObject.ts +20 -16
- package/src/types/objectBased/typesRecord.ts +6 -13
- package/src/types/objectBased/typesRef.ts +2 -2
- package/src/types/primitiveBased/typesEnum.ts +4 -2
- package/src/types/primitiveBased/typesPrimitive.ts +9 -4
- package/src/types/typeCheckErrorUtils.ts +1 -0
- package/src/types/utility/typesCodecSupport.ts +97 -56
- package/src/types/utility/typesMap.ts +24 -41
- package/src/types/utility/typesRefinement.ts +3 -2
- package/src/types/utility/typesSet.ts +24 -0
- package/src/utils/ModelPool.ts +3 -3
- package/src/utils/chainFns.ts +4 -0
- package/src/utils/decorators.ts +1 -1
- package/src/utils/errorDiagnostics.ts +6 -2
- package/src/utils/forEachWithDelayedThrow.ts +83 -0
- package/src/utils/index.ts +34 -2
- package/src/utils/mapUtils.ts +3 -7
- package/src/utils/setIfDifferent.ts +3 -2
- package/src/utils/setLike.ts +134 -0
- package/src/wrappers/ArraySet.ts +5 -9
- package/src/wrappers/ObjectMap.ts +15 -11
- package/src/wrappers/asMap.ts +30 -18
- package/src/wrappers/asSet.ts +36 -12
- package/dist/types/action/modelFlowPromiseGenerator.d.ts +0 -1
- package/src/action/modelFlowPromiseGenerator.ts +0 -127
|
@@ -29,21 +29,23 @@ export function deepEquals(a: any, b: any): boolean {
|
|
|
29
29
|
return true
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
a
|
|
37
|
-
} else if (isObservable(a)) {
|
|
38
|
-
a = toJS(a, toJSOptions)
|
|
39
|
-
}
|
|
40
|
-
if (isTreeNode(b)) {
|
|
41
|
-
b = getSnapshot(b)
|
|
42
|
-
} else if (isObservable(b)) {
|
|
43
|
-
b = toJS(b, toJSOptions)
|
|
32
|
+
const aIsObject = a !== null && typeof a === "object"
|
|
33
|
+
const bIsObject = b !== null && typeof b === "object"
|
|
34
|
+
if (!aIsObject && !bIsObject) {
|
|
35
|
+
// Strict equality already handled all equal primitives except NaN.
|
|
36
|
+
return Number.isNaN(a) && Number.isNaN(b)
|
|
44
37
|
}
|
|
45
38
|
|
|
46
|
-
return fastDeepEqual(a, b)
|
|
39
|
+
return fastDeepEqual(aIsObject ? comparisonValue(a) : a, bIsObject ? comparisonValue(b) : b)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function comparisonValue(value: object): unknown {
|
|
43
|
+
if (isTreeNode(value)) {
|
|
44
|
+
// Snapshots preserve structural sharing. Model $ objects are deliberately
|
|
45
|
+
// handled as observable objects rather than standalone tree nodes.
|
|
46
|
+
return getSnapshot(value)
|
|
47
|
+
}
|
|
48
|
+
return isObservable(value) ? toJS(value, toJSOptions) : value
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
const toJSOptions =
|
package/src/treeUtils/draft.ts
CHANGED
|
@@ -38,6 +38,10 @@ export class Draft<T extends object> {
|
|
|
38
38
|
*/
|
|
39
39
|
@mobxAction
|
|
40
40
|
commitByPath(path: Path): void {
|
|
41
|
+
if (path.length === 0) {
|
|
42
|
+
this.commit()
|
|
43
|
+
return
|
|
44
|
+
}
|
|
41
45
|
const draftTarget = resolvePath(this.data, path)
|
|
42
46
|
if (!draftTarget.resolved) {
|
|
43
47
|
throw failure(`path ${JSON.stringify(path)} could not be resolved in draft object`)
|
|
@@ -76,6 +80,10 @@ export class Draft<T extends object> {
|
|
|
76
80
|
*/
|
|
77
81
|
@mobxAction
|
|
78
82
|
resetByPath(path: Path): void {
|
|
83
|
+
if (path.length === 0) {
|
|
84
|
+
this.reset()
|
|
85
|
+
return
|
|
86
|
+
}
|
|
79
87
|
const originalTarget = resolvePath(this.originalData, path)
|
|
80
88
|
if (!originalTarget.resolved) {
|
|
81
89
|
throw failure(`path ${JSON.stringify(path)} could not be resolved in original object`)
|
package/src/treeUtils/sandbox.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { createContext } from "../context"
|
|
|
7
7
|
import { getParentToChildPath, resolvePath } from "../parent/path"
|
|
8
8
|
import { applyPatches } from "../patch/applyPatches"
|
|
9
9
|
import { onPatches } from "../patch/emitPatch"
|
|
10
|
-
import type { Patch } from "../patch/Patch"
|
|
11
10
|
import { type PatchRecorder, patchRecorder } from "../patch/patchRecorder"
|
|
12
11
|
import {
|
|
13
12
|
fastIsRootStoreNoAtom,
|
|
@@ -78,11 +77,6 @@ export class SandboxManager {
|
|
|
78
77
|
*/
|
|
79
78
|
private allowWrite: ReadonlyMiddlewareReturn["allowWrite"]
|
|
80
79
|
|
|
81
|
-
/**
|
|
82
|
-
* Whether changes made in the sandbox are currently being committed to the original subtree.
|
|
83
|
-
*/
|
|
84
|
-
private isCommitting = false
|
|
85
|
-
|
|
86
80
|
private readonly subtreeRoot: object
|
|
87
81
|
|
|
88
82
|
/**
|
|
@@ -127,11 +121,9 @@ export class SandboxManager {
|
|
|
127
121
|
if (this.withSandboxPatchRecorder) {
|
|
128
122
|
throw failure("original subtree must not change while 'withSandbox' executes")
|
|
129
123
|
}
|
|
130
|
-
|
|
131
|
-
this.
|
|
132
|
-
|
|
133
|
-
})
|
|
134
|
-
}
|
|
124
|
+
this.allowWrite(() => {
|
|
125
|
+
applyPatches(this.subtreeRootClone, patches)
|
|
126
|
+
})
|
|
135
127
|
})
|
|
136
128
|
|
|
137
129
|
const { allowWrite, dispose: disposeReadonlyMW } = readonlyMiddleware(this.subtreeRootClone)
|
|
@@ -232,26 +224,38 @@ export class SandboxManager {
|
|
|
232
224
|
}
|
|
233
225
|
if (commit) {
|
|
234
226
|
if (!isNestedWithSandboxCall) {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
227
|
+
const events = recorder.events
|
|
228
|
+
// The clone already holds these changes, so revert them and let the regular
|
|
229
|
+
// patch sync mirror the original's final state back in. That way anything a
|
|
230
|
+
// listener changes on the original during the commit reaches the clone too,
|
|
231
|
+
// at a cost proportional to the changes rather than to the tree size.
|
|
232
|
+
// Both halves run in a single action so that observers of the sandbox never
|
|
233
|
+
// see the reverted intermediate state.
|
|
234
|
+
runInAction(() => {
|
|
235
|
+
this.allowWrite(() => {
|
|
236
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
237
|
+
applyPatches(this.subtreeRootClone, events[i].inversePatches, true)
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
applyPatches(
|
|
241
|
+
this.subtreeRoot,
|
|
242
|
+
events.map((event) => event.patches)
|
|
243
|
+
)
|
|
244
|
+
})
|
|
248
245
|
}
|
|
249
246
|
} else {
|
|
250
247
|
this.allowWrite(() => {
|
|
251
248
|
runInAction(() => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
249
|
+
const wasRecording = recorder.recording
|
|
250
|
+
recorder.recording = false
|
|
251
|
+
try {
|
|
252
|
+
let i = recorder.events.length
|
|
253
|
+
while (i-- > numRecorderEvents) {
|
|
254
|
+
applyPatches(this.subtreeRootClone, recorder.events[i].inversePatches, true)
|
|
255
|
+
}
|
|
256
|
+
recorder.events.splice(numRecorderEvents)
|
|
257
|
+
} finally {
|
|
258
|
+
recorder.recording = wasRecording
|
|
255
259
|
}
|
|
256
260
|
})
|
|
257
261
|
})
|
package/src/tweaker/core.ts
CHANGED
|
@@ -28,10 +28,8 @@ export function setTweakedObjectUntweakers(
|
|
|
28
28
|
* @internal
|
|
29
29
|
*/
|
|
30
30
|
export function isTweakedObject(value: unknown, canBeDataObject: boolean): value is object {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
return treeNodeMetadata.get(value as object)?.tweaked === true
|
|
31
|
+
const metadata = treeNodeMetadata.get(value as object)
|
|
32
|
+
return metadata?.tweaked === true && (canBeDataObject || metadata.dataObjectParent === undefined)
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
/**
|
package/src/tweaker/tweak.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { unsetInternalSnapshot } from "../snapshot/internal"
|
|
|
8
8
|
import type { AnyStandardType, TypeToData } from "../types/schemas"
|
|
9
9
|
import { typeCheck } from "../types/typeCheck"
|
|
10
10
|
import { failure, inDevMode, isMap, isObject, isPrimitive, isSet } from "../utils"
|
|
11
|
+
import { getSafeErrorValuePreview } from "../utils/errorDiagnostics"
|
|
11
12
|
import { isTweakedObject, runTweakedObjectUntweakers } from "./core"
|
|
12
13
|
import { registerDefaultTweakers } from "./registerDefaultTweakers"
|
|
13
14
|
import { treeNodeMetadata } from "./treeNodeMetadata"
|
|
@@ -86,16 +87,11 @@ export function registerTweaker<T>(priority: number, tweaker: Tweaker<T>): void
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
function internalTweak<T>(value: T, parentPath: ParentPath<any> | undefined): T {
|
|
89
|
-
if (isPrimitive(value)) {
|
|
90
|
-
return value
|
|
91
|
-
}
|
|
92
|
-
|
|
93
90
|
// already tweaked
|
|
94
91
|
if (isTweakedObject(value, true)) {
|
|
95
92
|
value = setParent(
|
|
96
93
|
value,
|
|
97
94
|
parentPath,
|
|
98
|
-
false, // indexChangeAllowed
|
|
99
95
|
false, // isDataObject
|
|
100
96
|
true // cloneIfApplicable
|
|
101
97
|
)
|
|
@@ -131,14 +127,19 @@ function internalTweak<T>(value: T, parentPath: ParentPath<any> | undefined): T
|
|
|
131
127
|
}
|
|
132
128
|
|
|
133
129
|
throw failure(
|
|
134
|
-
`tweak can only work over models, observable objects/arrays, or primitives, but got ${value} instead`
|
|
130
|
+
`tweak can only work over models, observable objects/arrays, or primitives, but got ${getSafeErrorValuePreview(value)} instead`
|
|
135
131
|
)
|
|
136
132
|
}
|
|
137
133
|
|
|
134
|
+
const tweakNonPrimitive = action("tweak", internalTweak)
|
|
135
|
+
|
|
138
136
|
/**
|
|
139
137
|
* @internal
|
|
140
138
|
*/
|
|
141
|
-
export
|
|
139
|
+
export function tweak<T>(value: T, parentPath: ParentPath<any> | undefined): T {
|
|
140
|
+
// Primitives need no tree bookkeeping or MobX action boundary.
|
|
141
|
+
return isPrimitive(value) ? value : tweakNonPrimitive(value, parentPath)
|
|
142
|
+
}
|
|
142
143
|
|
|
143
144
|
/**
|
|
144
145
|
* @internal
|
|
@@ -169,7 +170,6 @@ export function tryUntweak(value: any): (() => void) | undefined {
|
|
|
169
170
|
setParent(
|
|
170
171
|
children[i], // value
|
|
171
172
|
undefined, // parentPath
|
|
172
|
-
false, // indexChangeAllowed
|
|
173
173
|
false, // isDataObject
|
|
174
174
|
// no need to clone if unsetting the parent
|
|
175
175
|
false // cloneIfApplicable
|
|
@@ -12,8 +12,14 @@ import { assertCanWrite } from "../action/protection"
|
|
|
12
12
|
import { emitArraySpliceDeepChange, emitArrayUpdateDeepChange } from "../deepChange/onDeepChange"
|
|
13
13
|
import { getGlobalConfig } from "../globalConfig"
|
|
14
14
|
import type { ParentPath } from "../parent/path"
|
|
15
|
-
import { setParent } from "../parent/setParent"
|
|
16
|
-
import {
|
|
15
|
+
import { reindexArrayChildren, setParent } from "../parent/setParent"
|
|
16
|
+
import {
|
|
17
|
+
beginPatchEmission,
|
|
18
|
+
emitPatches,
|
|
19
|
+
endPatchEmission,
|
|
20
|
+
hasPatchListenersFor,
|
|
21
|
+
InternalPatchRecorder,
|
|
22
|
+
} from "../patch/emitPatch"
|
|
17
23
|
import type { Patch } from "../patch/Patch"
|
|
18
24
|
import {
|
|
19
25
|
flushInternalSnapshot,
|
|
@@ -23,11 +29,20 @@ import {
|
|
|
23
29
|
updateInternalSnapshot,
|
|
24
30
|
} from "../snapshot/internal"
|
|
25
31
|
import { failure, inDevMode, isArray, isPrimitive } from "../utils"
|
|
32
|
+
import {
|
|
33
|
+
addDelayedError,
|
|
34
|
+
type DelayedError,
|
|
35
|
+
throwDelayedError,
|
|
36
|
+
} from "../utils/forEachWithDelayedThrow"
|
|
26
37
|
import { runningWithoutSnapshotOrPatches, setTweakedObjectUntweakers } from "./core"
|
|
27
38
|
import { TweakerPriority } from "./TweakerPriority"
|
|
28
39
|
import { markAsTweakedObject } from "./treeNodeMetadata"
|
|
29
40
|
import { registerTweaker, tweak } from "./tweak"
|
|
30
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
isTypeCheckingAfterChangeEnabled,
|
|
43
|
+
recordTypeCheckingBatchChange,
|
|
44
|
+
runTypeCheckingAfterChange,
|
|
45
|
+
} from "./typeChecking"
|
|
31
46
|
|
|
32
47
|
/**
|
|
33
48
|
* @internal
|
|
@@ -62,7 +77,6 @@ export function tweakArray<T extends any[]>(
|
|
|
62
77
|
setParent(
|
|
63
78
|
tweakedArr, // value
|
|
64
79
|
parentPath,
|
|
65
|
-
false, // indexChangeAllowed
|
|
66
80
|
false, // isDataObject
|
|
67
81
|
// arrays shouldn't be cloned anyway
|
|
68
82
|
false // cloneIfApplicable
|
|
@@ -97,7 +111,6 @@ export function tweakArray<T extends any[]>(
|
|
|
97
111
|
setParent(
|
|
98
112
|
tweakedValue, // value
|
|
99
113
|
path, // parentPath
|
|
100
|
-
false, // indexChangeAllowed
|
|
101
114
|
false, // isDataObject
|
|
102
115
|
// the value is already a new value (the result of a fromSnapshot)
|
|
103
116
|
false // cloneIfApplicable
|
|
@@ -140,8 +153,23 @@ function mutateSet(k: number, v: unknown, sn: unknown[]) {
|
|
|
140
153
|
sn[k] = v
|
|
141
154
|
}
|
|
142
155
|
|
|
156
|
+
// Conservative bound below every engine's argument-count limit for spread calls.
|
|
157
|
+
const maxSpreadableItems = 8192
|
|
158
|
+
|
|
143
159
|
function mutateSplice(index: number, removedCount: number, addedItems: any[], sn: any[]) {
|
|
144
|
-
|
|
160
|
+
if (addedItems.length < maxSpreadableItems) {
|
|
161
|
+
sn.splice(index, removedCount, ...addedItems)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Splice the tail in a single pass instead of spreading the added items as
|
|
166
|
+
// arguments, which would overflow the engine's limit. copyWithin handles overlap.
|
|
167
|
+
const oldLength = sn.length
|
|
168
|
+
const newLength = oldLength - removedCount + addedItems.length
|
|
169
|
+
if (newLength > oldLength) sn.length = newLength
|
|
170
|
+
sn.copyWithin(index + addedItems.length, index + removedCount, oldLength)
|
|
171
|
+
for (let i = 0; i < addedItems.length; i++) sn[index + i] = addedItems[i]
|
|
172
|
+
sn.length = newLength
|
|
145
173
|
}
|
|
146
174
|
|
|
147
175
|
const patchRecorder = new InternalPatchRecorder()
|
|
@@ -151,24 +179,12 @@ function arrayDidChange(change: IArrayDidChange) {
|
|
|
151
179
|
|
|
152
180
|
patchRecorder.reset()
|
|
153
181
|
|
|
154
|
-
switch (change.type) {
|
|
155
|
-
case "splice":
|
|
156
|
-
emitArraySpliceDeepChange(arr, arr, change.index as number, change.added, change.removed)
|
|
157
|
-
break
|
|
158
|
-
|
|
159
|
-
case "update":
|
|
160
|
-
emitArrayUpdateDeepChange(arr, arr, change.index as number, change.newValue, change.oldValue)
|
|
161
|
-
|
|
162
|
-
break
|
|
163
|
-
|
|
164
|
-
default:
|
|
165
|
-
break
|
|
166
|
-
}
|
|
167
|
-
|
|
168
182
|
if (runningWithoutSnapshotOrPatches) {
|
|
169
183
|
return
|
|
170
184
|
}
|
|
171
185
|
|
|
186
|
+
recordTypeCheckingBatchChange(change)
|
|
187
|
+
|
|
172
188
|
const oldSnapshot = getInternalSnapshot(arr as Array<unknown>)!.untransformed
|
|
173
189
|
const shouldEmitPatches = hasPatchListenersFor(arr)
|
|
174
190
|
const shouldBuildInversePatches = shouldEmitPatches || isTypeCheckingAfterChangeEnabled()
|
|
@@ -198,8 +214,53 @@ function arrayDidChange(change: IArrayDidChange) {
|
|
|
198
214
|
runTypeCheckingAfterChange(arr, patchRecorder)
|
|
199
215
|
if (mutate) {
|
|
200
216
|
updateInternalSnapshot(arr, mutate)
|
|
201
|
-
patchRecorder.emit(arr)
|
|
202
217
|
}
|
|
218
|
+
// Listeners may mutate another node and reuse the shared recorder.
|
|
219
|
+
// Keep this change's patches before publishing the validated deep change.
|
|
220
|
+
const { patches, invPatches } = patchRecorder
|
|
221
|
+
patchRecorder.reset()
|
|
222
|
+
|
|
223
|
+
// Reserve chronological patch notifications, then finish deep-change delivery
|
|
224
|
+
// before publishing them. Reentrant edits append behind this mutation.
|
|
225
|
+
// Every step below runs even when an earlier one throws, so a failing patch recorder
|
|
226
|
+
// cannot keep the mutation from reaching the deep change and public patch listeners,
|
|
227
|
+
// and no failure is lost to a later one.
|
|
228
|
+
beginPatchEmission()
|
|
229
|
+
let delayedError: DelayedError
|
|
230
|
+
try {
|
|
231
|
+
emitPatches(arr, patches, invPatches)
|
|
232
|
+
} catch (error) {
|
|
233
|
+
delayedError = addDelayedError(delayedError, error)
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
switch (change.type) {
|
|
237
|
+
case "splice":
|
|
238
|
+
emitArraySpliceDeepChange(arr, arr, change.index as number, change.added, change.removed)
|
|
239
|
+
break
|
|
240
|
+
|
|
241
|
+
case "update":
|
|
242
|
+
emitArrayUpdateDeepChange(
|
|
243
|
+
arr,
|
|
244
|
+
arr,
|
|
245
|
+
change.index as number,
|
|
246
|
+
change.newValue,
|
|
247
|
+
change.oldValue
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
break
|
|
251
|
+
|
|
252
|
+
default:
|
|
253
|
+
break
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
delayedError = addDelayedError(delayedError, error)
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
endPatchEmission()
|
|
260
|
+
} catch (error) {
|
|
261
|
+
delayedError = addDelayedError(delayedError, error)
|
|
262
|
+
}
|
|
263
|
+
throwDelayedError(delayedError)
|
|
203
264
|
}
|
|
204
265
|
|
|
205
266
|
const undefinedInsideArrayErrorMsg =
|
|
@@ -319,10 +380,10 @@ function arrayDidChangeSplice(
|
|
|
319
380
|
}
|
|
320
381
|
|
|
321
382
|
if (patches && readdPatches && readdPatches.length > 0) {
|
|
322
|
-
patches.push(
|
|
383
|
+
for (const patch of readdPatches) patches.push(patch)
|
|
323
384
|
}
|
|
324
385
|
if (invPatches && readdInvPatches && readdInvPatches.length > 0) {
|
|
325
|
-
invPatches.push(
|
|
386
|
+
for (const patch of readdInvPatches) invPatches.push(patch)
|
|
326
387
|
}
|
|
327
388
|
// We need to reverse once since inverse patches are applied in reverse.
|
|
328
389
|
// Repeated unshift calls here would make a large splice quadratic.
|
|
@@ -482,20 +543,8 @@ function interceptArrayMutationSplice(change: IArrayWillSplice) {
|
|
|
482
543
|
const oldNextIndex = change.index + change.removedCount
|
|
483
544
|
const newNextIndex = change.index + change.added.length
|
|
484
545
|
|
|
485
|
-
if (oldNextIndex !== newNextIndex) {
|
|
486
|
-
|
|
487
|
-
setParent(
|
|
488
|
-
change.object[i], // value
|
|
489
|
-
{
|
|
490
|
-
parent: change.object,
|
|
491
|
-
path: j,
|
|
492
|
-
}, // parentPath
|
|
493
|
-
true, // indexChangeAllowed
|
|
494
|
-
false, // isDataObject
|
|
495
|
-
// just re-indexing
|
|
496
|
-
false // cloneIfApplicable
|
|
497
|
-
)
|
|
498
|
-
}
|
|
546
|
+
if (oldNextIndex !== newNextIndex && oldNextIndex < change.object.length) {
|
|
547
|
+
reindexArrayChildren(change.object, oldNextIndex, newNextIndex)
|
|
499
548
|
}
|
|
500
549
|
}
|
|
501
550
|
|
|
@@ -19,7 +19,13 @@ import { getModelInfoForName, getModelNotRegisteredErrorMessage } from "../model
|
|
|
19
19
|
import { dataToModelNode } from "../parent/core"
|
|
20
20
|
import type { ParentPath } from "../parent/path"
|
|
21
21
|
import { setParent } from "../parent/setParent"
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
beginPatchEmission,
|
|
24
|
+
emitPatches,
|
|
25
|
+
endPatchEmission,
|
|
26
|
+
hasPatchListenersFor,
|
|
27
|
+
InternalPatchRecorder,
|
|
28
|
+
} from "../patch/emitPatch"
|
|
23
29
|
import {
|
|
24
30
|
flushInternalSnapshot,
|
|
25
31
|
freezeInternalSnapshot,
|
|
@@ -29,13 +35,23 @@ import {
|
|
|
29
35
|
updateInternalSnapshot,
|
|
30
36
|
} from "../snapshot/internal"
|
|
31
37
|
import { takeModelInitialDataSnapshot } from "../snapshot/modelInitialData"
|
|
38
|
+
import { withoutCachedTypeChecking } from "../types/createCachedTypeCheck"
|
|
32
39
|
import { failure, isPlainObject, isPrimitive, setProtoProp } from "../utils"
|
|
40
|
+
import {
|
|
41
|
+
addDelayedError,
|
|
42
|
+
type DelayedError,
|
|
43
|
+
throwDelayedError,
|
|
44
|
+
} from "../utils/forEachWithDelayedThrow"
|
|
33
45
|
import { setIfDifferent } from "../utils/setIfDifferent"
|
|
34
46
|
import { runningWithoutSnapshotOrPatches, setTweakedObjectUntweakers } from "./core"
|
|
35
47
|
import { TweakerPriority } from "./TweakerPriority"
|
|
36
48
|
import { markAsTweakedObject } from "./treeNodeMetadata"
|
|
37
49
|
import { registerTweaker, tweak } from "./tweak"
|
|
38
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
isTypeCheckingAfterChangeEnabled,
|
|
52
|
+
recordTypeCheckingBatchChange,
|
|
53
|
+
runTypeCheckingAfterChange,
|
|
54
|
+
} from "./typeChecking"
|
|
39
55
|
|
|
40
56
|
/**
|
|
41
57
|
* @internal
|
|
@@ -59,20 +75,19 @@ export function tweakPlainObject<T extends Record<string, any>>(
|
|
|
59
75
|
setParent(
|
|
60
76
|
tweakedObj, // value
|
|
61
77
|
parentPath,
|
|
62
|
-
false, // indexChangeAllowed
|
|
63
78
|
isDataObject,
|
|
64
79
|
// an object shouldn't be cloned
|
|
65
80
|
false // cloneIfApplicable
|
|
66
81
|
)
|
|
67
82
|
|
|
68
83
|
const initialDataSnapshot =
|
|
69
|
-
isDataObject && snapshotModelType
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const untransformedSn: any =
|
|
84
|
+
isDataObject && snapshotModelType !== undefined
|
|
85
|
+
? takeModelInitialDataSnapshot(tweakedObj)
|
|
86
|
+
: undefined
|
|
87
|
+
const untransformedSn: any = initialDataSnapshot ?? {}
|
|
73
88
|
|
|
74
89
|
// substitute initial values by tweaked values
|
|
75
|
-
const originalObjKeys =
|
|
90
|
+
const originalObjKeys = initialDataSnapshot ? [] : Object.keys(originalObj)
|
|
76
91
|
const originalObjKeysLen = originalObjKeys.length
|
|
77
92
|
for (let i = 0; i < originalObjKeysLen; i++) {
|
|
78
93
|
const k = originalObjKeys[i]
|
|
@@ -96,7 +111,6 @@ export function tweakPlainObject<T extends Record<string, any>>(
|
|
|
96
111
|
setParent(
|
|
97
112
|
tweakedValue, // value
|
|
98
113
|
path, // parentPath
|
|
99
|
-
false, // indexChangeAllowed
|
|
100
114
|
false, // isDataObject
|
|
101
115
|
// the value is already a new value (the result of a fromSnapshot)
|
|
102
116
|
false // cloneIfApplicable
|
|
@@ -116,7 +130,7 @@ export function tweakPlainObject<T extends Record<string, any>>(
|
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
let transformFn: SnapshotTransformFn | undefined
|
|
119
|
-
if (snapshotModelType) {
|
|
133
|
+
if (snapshotModelType !== undefined) {
|
|
120
134
|
untransformedSn[modelTypeKey] = snapshotModelType
|
|
121
135
|
|
|
122
136
|
const modelInfo = getModelInfoForName(snapshotModelType)
|
|
@@ -171,27 +185,12 @@ function objectDidChange(change: IObjectDidChange): void {
|
|
|
171
185
|
|
|
172
186
|
patchRecorder.reset()
|
|
173
187
|
|
|
174
|
-
switch (change.type) {
|
|
175
|
-
case "add":
|
|
176
|
-
emitObjectAddDeepChange(actualNode, obj, change.name, change.newValue)
|
|
177
|
-
break
|
|
178
|
-
|
|
179
|
-
case "update":
|
|
180
|
-
emitObjectUpdateDeepChange(actualNode, obj, change.name, change.newValue, change.oldValue)
|
|
181
|
-
break
|
|
182
|
-
|
|
183
|
-
case "remove":
|
|
184
|
-
emitObjectRemoveDeepChange(actualNode, obj, change.name, change.oldValue)
|
|
185
|
-
break
|
|
186
|
-
|
|
187
|
-
default:
|
|
188
|
-
throw failure("assertion error: unsupported object change type")
|
|
189
|
-
}
|
|
190
|
-
|
|
191
188
|
if (runningWithoutSnapshotOrPatches) {
|
|
192
189
|
return
|
|
193
190
|
}
|
|
194
191
|
|
|
192
|
+
recordTypeCheckingBatchChange(change)
|
|
193
|
+
|
|
195
194
|
const oldUntransformedSn = getInternalSnapshot(actualNode)!.untransformed
|
|
196
195
|
const shouldEmitPatches = hasPatchListenersFor(actualNode)
|
|
197
196
|
const shouldBuildInversePatches = shouldEmitPatches || isTypeCheckingAfterChangeEnabled()
|
|
@@ -219,9 +218,59 @@ function objectDidChange(change: IObjectDidChange): void {
|
|
|
219
218
|
throw failure("assertion error: unsupported object change type")
|
|
220
219
|
}
|
|
221
220
|
|
|
222
|
-
|
|
221
|
+
if (change.type !== "update" && isTypeCheckingAfterChangeEnabled()) {
|
|
222
|
+
// MobX notifies object observers before invalidating dependencies on missing
|
|
223
|
+
// keys (and removed keys in MobX 4). Cached ancestor checks may still
|
|
224
|
+
// describe the previous shape.
|
|
225
|
+
withoutCachedTypeChecking(obj, () => runTypeCheckingAfterChange(obj, patchRecorder))
|
|
226
|
+
} else {
|
|
227
|
+
runTypeCheckingAfterChange(obj, patchRecorder)
|
|
228
|
+
}
|
|
223
229
|
updateInternalSnapshot(actualNode, mutate)
|
|
224
|
-
|
|
230
|
+
|
|
231
|
+
// Listeners may mutate another node and reuse the shared recorder.
|
|
232
|
+
// Keep this change's patches before publishing the validated deep change.
|
|
233
|
+
const { patches, invPatches } = patchRecorder
|
|
234
|
+
patchRecorder.reset()
|
|
235
|
+
|
|
236
|
+
// Reserve chronological patch notifications, then finish deep-change delivery
|
|
237
|
+
// before publishing them. Reentrant edits append behind this mutation.
|
|
238
|
+
// Every step below runs even when an earlier one throws, so a failing patch recorder
|
|
239
|
+
// cannot keep the mutation from reaching the deep change and public patch listeners,
|
|
240
|
+
// and no failure is lost to a later one.
|
|
241
|
+
beginPatchEmission()
|
|
242
|
+
let delayedError: DelayedError
|
|
243
|
+
try {
|
|
244
|
+
emitPatches(actualNode, patches, invPatches)
|
|
245
|
+
} catch (error) {
|
|
246
|
+
delayedError = addDelayedError(delayedError, error)
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
switch (change.type) {
|
|
250
|
+
case "add":
|
|
251
|
+
emitObjectAddDeepChange(actualNode, obj, change.name, change.newValue)
|
|
252
|
+
break
|
|
253
|
+
|
|
254
|
+
case "update":
|
|
255
|
+
emitObjectUpdateDeepChange(actualNode, obj, change.name, change.newValue, change.oldValue)
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
case "remove":
|
|
259
|
+
emitObjectRemoveDeepChange(actualNode, obj, change.name, change.oldValue)
|
|
260
|
+
break
|
|
261
|
+
|
|
262
|
+
default:
|
|
263
|
+
throw failure("assertion error: unsupported object change type")
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
delayedError = addDelayedError(delayedError, error)
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
endPatchEmission()
|
|
270
|
+
} catch (error) {
|
|
271
|
+
delayedError = addDelayedError(delayedError, error)
|
|
272
|
+
}
|
|
273
|
+
throwDelayedError(delayedError)
|
|
225
274
|
}
|
|
226
275
|
|
|
227
276
|
function objectDidChangeRemove(
|