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
|
@@ -3,10 +3,11 @@ import { BuiltInAction } from "../action/builtInActions"
|
|
|
3
3
|
import { ActionContextActionType } from "../action/context"
|
|
4
4
|
import { wrapInAction } from "../action/wrapInAction"
|
|
5
5
|
import { modelToDataNode } from "../parent/core"
|
|
6
|
-
import type { PathElement } from "../parent/pathTypes"
|
|
7
6
|
import type { Patch } from "../patch/Patch"
|
|
7
|
+
import { internalApplySnapshot } from "../snapshot/applySnapshot"
|
|
8
8
|
import { reconcileSnapshot } from "../snapshot/reconcileSnapshot"
|
|
9
9
|
import { assertTweakedObject } from "../tweaker/core"
|
|
10
|
+
import { withTypeCheckingBatch } from "../tweaker/typeChecking"
|
|
10
11
|
import { failure, inDevMode, isArray, lazy } from "../utils"
|
|
11
12
|
import { runWithErrorDiagnosticsContext, withErrorPathSegments } from "../utils/errorDiagnostics"
|
|
12
13
|
import { ModelPool } from "../utils/ModelPool"
|
|
@@ -14,6 +15,8 @@ import { setIfDifferent } from "../utils/setIfDifferent"
|
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Applies the given patches to the given target object.
|
|
18
|
+
* Automatic type checking validates the completed batch, including nested lists.
|
|
19
|
+
* A rejected batch is rolled back with compensating patches.
|
|
17
20
|
*
|
|
18
21
|
* @param node Target object.
|
|
19
22
|
* @param patches List of patches to apply.
|
|
@@ -45,33 +48,35 @@ export function internalApplyPatches(
|
|
|
45
48
|
const obj = this
|
|
46
49
|
const modelPool = new ModelPool(obj)
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
withTypeCheckingBatch(() => {
|
|
52
|
+
if (reverse) {
|
|
53
|
+
let i = patches.length
|
|
54
|
+
while (i--) {
|
|
55
|
+
const p = patches[i]
|
|
56
|
+
if (isArray(p)) {
|
|
57
|
+
let j = p.length
|
|
58
|
+
while (j--) {
|
|
59
|
+
applySinglePatchWithPath(obj, p[j], modelPool)
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
applySinglePatchWithPath(obj, p as Patch, modelPool)
|
|
56
63
|
}
|
|
57
|
-
} else {
|
|
58
|
-
applySinglePatchWithPath(obj, p as Patch, modelPool)
|
|
59
64
|
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
} else {
|
|
66
|
+
const len = patches.length
|
|
67
|
+
for (let i = 0; i < len; i++) {
|
|
68
|
+
const p = patches[i]
|
|
69
|
+
if (isArray(p)) {
|
|
70
|
+
const len2 = p.length
|
|
71
|
+
for (let j = 0; j < len2; j++) {
|
|
72
|
+
applySinglePatchWithPath(obj, p[j], modelPool)
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
applySinglePatchWithPath(obj, p as Patch, modelPool)
|
|
69
76
|
}
|
|
70
|
-
} else {
|
|
71
|
-
applySinglePatchWithPath(obj, p as Patch, modelPool)
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
|
-
}
|
|
79
|
+
})
|
|
75
80
|
})
|
|
76
81
|
}
|
|
77
82
|
|
|
@@ -84,16 +89,38 @@ const wrappedInternalApplyPatches = lazy(() =>
|
|
|
84
89
|
)
|
|
85
90
|
|
|
86
91
|
function applySinglePatch(obj: object, patch: Patch, modelPool: ModelPool): void {
|
|
87
|
-
const
|
|
92
|
+
const path = patch.path
|
|
93
|
+
if (inDevMode && !isArray(path)) {
|
|
94
|
+
throw failure(`invalid path: ${path}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (path.length === 0) {
|
|
98
|
+
if (patch.op === "add" || patch.op === "replace") {
|
|
99
|
+
internalApplySnapshot.call(obj, patch.value)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
throw failure("a patch cannot remove the root node")
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let target: any = modelToDataNode(obj)
|
|
106
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
107
|
+
target = modelToDataNode(target[path[i]])
|
|
108
|
+
}
|
|
109
|
+
const prop = path[path.length - 1]
|
|
88
110
|
|
|
89
111
|
if (isArray(target)) {
|
|
112
|
+
if (patch.op === "replace" && prop === "length") {
|
|
113
|
+
target.length = patch.value
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const index = getPatchArrayIndex(prop, target.length, patch.op)
|
|
90
118
|
switch (patch.op) {
|
|
91
119
|
case "add": {
|
|
92
|
-
const index = +prop!
|
|
93
120
|
// reconcile from the pool if possible
|
|
94
121
|
const newValue = reconcileSnapshot(undefined, patch.value, modelPool, target)
|
|
95
122
|
if (index < 0) {
|
|
96
|
-
// extension
|
|
123
|
+
// non-standard extension: a negative index appends, just like "-"
|
|
97
124
|
target.push(newValue)
|
|
98
125
|
} else {
|
|
99
126
|
target.splice(index, 0, newValue)
|
|
@@ -102,21 +129,15 @@ function applySinglePatch(obj: object, patch: Patch, modelPool: ModelPool): void
|
|
|
102
129
|
}
|
|
103
130
|
|
|
104
131
|
case "remove": {
|
|
105
|
-
const index = +prop!
|
|
106
132
|
// no reconciliation, removing
|
|
107
133
|
target.splice(index, 1)
|
|
108
134
|
break
|
|
109
135
|
}
|
|
110
136
|
|
|
111
137
|
case "replace": {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const index = +prop!
|
|
116
|
-
// try to reconcile
|
|
117
|
-
const newValue = reconcileSnapshot(target[index], patch.value, modelPool, target)
|
|
118
|
-
setIfDifferent(target, index as any, newValue)
|
|
119
|
-
}
|
|
138
|
+
// try to reconcile
|
|
139
|
+
const newValue = reconcileSnapshot(target[index], patch.value, modelPool, target)
|
|
140
|
+
setIfDifferent(target, index as any, newValue)
|
|
120
141
|
break
|
|
121
142
|
}
|
|
122
143
|
|
|
@@ -152,36 +173,30 @@ function applySinglePatch(obj: object, patch: Patch, modelPool: ModelPool): void
|
|
|
152
173
|
}
|
|
153
174
|
}
|
|
154
175
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
applySinglePatch(obj, patch, modelPool)
|
|
158
|
-
})
|
|
159
|
-
}
|
|
176
|
+
// canonical decimal integers only, so "01", "1.0" or "1e0" are not mistaken for indexes
|
|
177
|
+
const patchArrayIndexPattern = /^(?:0|-?[1-9]\d*)$/
|
|
160
178
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (inDevMode) {
|
|
166
|
-
if (!isArray(path)) {
|
|
167
|
-
throw failure(`invalid path: ${path}`)
|
|
168
|
-
}
|
|
179
|
+
function getPatchArrayIndex(prop: string | number, length: number, op: Patch["op"]): number {
|
|
180
|
+
// JSON Patch spells "at the end of the array" as "-", which only makes sense when adding
|
|
181
|
+
if (prop === "-" && op === "add") {
|
|
182
|
+
return length
|
|
169
183
|
}
|
|
170
184
|
|
|
171
|
-
|
|
185
|
+
const index =
|
|
186
|
+
typeof prop === "string" && !patchArrayIndexPattern.test(prop) ? Number.NaN : Number(prop)
|
|
172
187
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
188
|
+
// additions may also target the end of the array, plus negative indexes as an append extension
|
|
189
|
+
const valid =
|
|
190
|
+
Number.isInteger(index) && (op === "add" ? index <= length : index >= 0 && index < length)
|
|
191
|
+
if (!valid) {
|
|
192
|
+
throw failure(`invalid array index '${String(prop)}' for '${op}' patch`)
|
|
177
193
|
}
|
|
178
194
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
195
|
+
return index
|
|
196
|
+
}
|
|
182
197
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
198
|
+
function applySinglePatchWithPath(obj: object, patch: Patch, modelPool: ModelPool): void {
|
|
199
|
+
withErrorPathSegments(patch.path, () => {
|
|
200
|
+
applySinglePatch(obj, patch, modelPool)
|
|
201
|
+
})
|
|
187
202
|
}
|
package/src/patch/emitPatch.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { PathElement } from "../parent/pathTypes"
|
|
|
5
5
|
import { freezeInternalSnapshot, getInternalSnapshot } from "../snapshot/internal"
|
|
6
6
|
import { assertTweakedObject } from "../tweaker/core"
|
|
7
7
|
import { assertIsFunction, deleteFromArray, isPrimitive } from "../utils"
|
|
8
|
+
import { forEachWithDelayedThrow } from "../utils/forEachWithDelayedThrow"
|
|
8
9
|
import type { Patch } from "./Patch"
|
|
9
10
|
|
|
10
11
|
const emptyPatchArray: Patch[] = []
|
|
@@ -31,12 +32,34 @@ export class InternalPatchRecorder {
|
|
|
31
32
|
// empty arrays and must not trigger ancestor type checking.
|
|
32
33
|
this.hasChanges = this.patches.length > 0 || this.invPatches.length > 0
|
|
33
34
|
}
|
|
35
|
+
}
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const runNotification = (notify: () => void) => notify()
|
|
38
|
+
|
|
39
|
+
let patchEmissionDepth = 0
|
|
40
|
+
let emittingPendingPatches = false
|
|
41
|
+
const pendingPatchNotifications: (() => void)[] = []
|
|
42
|
+
|
|
43
|
+
/** @internal */
|
|
44
|
+
export function beginPatchEmission(): void {
|
|
45
|
+
patchEmissionDepth++
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @internal */
|
|
49
|
+
export function endPatchEmission(): void {
|
|
50
|
+
patchEmissionDepth--
|
|
51
|
+
flushPendingPatches()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function flushPendingPatches(): void {
|
|
55
|
+
if (patchEmissionDepth > 0 || emittingPendingPatches || pendingPatchNotifications.length === 0)
|
|
56
|
+
return
|
|
57
|
+
emittingPendingPatches = true
|
|
58
|
+
try {
|
|
59
|
+
forEachWithDelayedThrow(pendingPatchNotifications, (notify) => notify())
|
|
60
|
+
} finally {
|
|
61
|
+
pendingPatchNotifications.length = 0
|
|
62
|
+
emittingPendingPatches = false
|
|
40
63
|
}
|
|
41
64
|
}
|
|
42
65
|
|
|
@@ -44,11 +67,22 @@ export class InternalPatchRecorder {
|
|
|
44
67
|
* @internal
|
|
45
68
|
*/
|
|
46
69
|
export function emitPatches(obj: object, patches: Patch[], invPatches: Patch[]): void {
|
|
47
|
-
if (patches.length > 0) {
|
|
48
|
-
|
|
70
|
+
if (patches.length > 0 && (globalPatchListeners.length > 0 || patchListenerCount > 0)) {
|
|
71
|
+
const synchronousNotifications: (() => void)[] = []
|
|
72
|
+
if (globalPatchListeners.length > 0) {
|
|
73
|
+
emitGlobalPatch(obj, patches, invPatches, synchronousNotifications)
|
|
74
|
+
}
|
|
49
75
|
if (patchListenerCount > 0) {
|
|
50
|
-
emitPatch(obj, patches, invPatches)
|
|
76
|
+
emitPatch(obj, patches, invPatches, synchronousNotifications)
|
|
51
77
|
}
|
|
78
|
+
// Reserve every public notification and its path before recorder callbacks
|
|
79
|
+
// can cause further mutations. Recorders must run within the mutation's
|
|
80
|
+
// action / withoutUndo scope, even while public delivery is deferred.
|
|
81
|
+
// A throwing recorder must not stop the ones registered on ancestor nodes,
|
|
82
|
+
// nor swallow the deferred public delivery, so the flush is just one more
|
|
83
|
+
// step of the same delayed-throw delivery.
|
|
84
|
+
synchronousNotifications.push(flushPendingPatches)
|
|
85
|
+
forEachWithDelayedThrow(synchronousNotifications, runNotification)
|
|
52
86
|
}
|
|
53
87
|
}
|
|
54
88
|
|
|
@@ -71,8 +105,14 @@ export type OnGlobalPatchesListener = (
|
|
|
71
105
|
*/
|
|
72
106
|
export type OnPatchesDisposer = () => void
|
|
73
107
|
|
|
74
|
-
|
|
75
|
-
|
|
108
|
+
interface PatchSubscription<Listener> {
|
|
109
|
+
listener: Listener
|
|
110
|
+
synchronous: boolean
|
|
111
|
+
active: boolean
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const patchListeners = new WeakMap<object, PatchSubscription<OnPatchesListener>[]>()
|
|
115
|
+
const globalPatchListeners: PatchSubscription<OnGlobalPatchesListener>[] = []
|
|
76
116
|
let patchListenerCount = 0
|
|
77
117
|
|
|
78
118
|
/**
|
|
@@ -113,6 +153,15 @@ export function hasPatchListenersFor(target: object): boolean {
|
|
|
113
153
|
* @returns A disposer to stop listening to patches.
|
|
114
154
|
*/
|
|
115
155
|
export function onPatches(subtreeRoot: object, listener: OnPatchesListener): OnPatchesDisposer {
|
|
156
|
+
return internalOnPatches(subtreeRoot, listener, false)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** @internal */
|
|
160
|
+
export function internalOnPatches(
|
|
161
|
+
subtreeRoot: object,
|
|
162
|
+
listener: OnPatchesListener,
|
|
163
|
+
synchronous: boolean
|
|
164
|
+
): OnPatchesDisposer {
|
|
116
165
|
assertTweakedObject(subtreeRoot, "subtreeRoot")
|
|
117
166
|
assertIsFunction(listener, "listener")
|
|
118
167
|
|
|
@@ -126,10 +175,12 @@ export function onPatches(subtreeRoot: object, listener: OnPatchesListener): OnP
|
|
|
126
175
|
patchListeners.set(subtreeRoot, listenersForObject)
|
|
127
176
|
}
|
|
128
177
|
|
|
129
|
-
|
|
178
|
+
const subscription = { listener, synchronous, active: true }
|
|
179
|
+
listenersForObject.push(subscription)
|
|
130
180
|
patchListenerCount++
|
|
131
181
|
return () => {
|
|
132
|
-
|
|
182
|
+
subscription.active = false
|
|
183
|
+
if (deleteFromArray(listenersForObject, subscription)) {
|
|
133
184
|
patchListenerCount--
|
|
134
185
|
}
|
|
135
186
|
}
|
|
@@ -143,25 +194,81 @@ export function onPatches(subtreeRoot: object, listener: OnPatchesListener): OnP
|
|
|
143
194
|
* @returns A disposer to stop listening to patches.
|
|
144
195
|
*/
|
|
145
196
|
export function onGlobalPatches(listener: OnGlobalPatchesListener): OnPatchesDisposer {
|
|
197
|
+
return internalOnGlobalPatches(listener, false)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** @internal */
|
|
201
|
+
export function internalOnGlobalPatches(
|
|
202
|
+
listener: OnGlobalPatchesListener,
|
|
203
|
+
synchronous: boolean
|
|
204
|
+
): OnPatchesDisposer {
|
|
146
205
|
assertIsFunction(listener, "listener")
|
|
147
206
|
|
|
148
207
|
if (!isAction(listener)) {
|
|
149
208
|
listener = action(listener.name || "onGlobalPatchesListener", listener)
|
|
150
209
|
}
|
|
151
210
|
|
|
152
|
-
|
|
211
|
+
const subscription = { listener, synchronous, active: true }
|
|
212
|
+
globalPatchListeners.push(subscription)
|
|
153
213
|
return () => {
|
|
154
|
-
|
|
214
|
+
subscription.active = false
|
|
215
|
+
deleteFromArray(globalPatchListeners, subscription)
|
|
155
216
|
}
|
|
156
217
|
}
|
|
157
218
|
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
219
|
+
function notifyPatchSubscriptions<Args extends unknown[]>(
|
|
220
|
+
subscriptions: PatchSubscription<(...args: Args) => void>[],
|
|
221
|
+
args: Args,
|
|
222
|
+
synchronous: boolean
|
|
223
|
+
): void {
|
|
224
|
+
forEachWithDelayedThrow(subscriptions, (subscription) => {
|
|
225
|
+
if (subscription.active && subscription.synchronous === synchronous) {
|
|
226
|
+
subscription.listener(...args)
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function reservePatchNotifications<Args extends unknown[]>(
|
|
232
|
+
subscriptions: PatchSubscription<(...args: Args) => void>[],
|
|
233
|
+
args: Args,
|
|
234
|
+
synchronousNotifications: (() => void)[]
|
|
235
|
+
): void {
|
|
236
|
+
let hasSynchronous = false
|
|
237
|
+
let hasDeferred = false
|
|
238
|
+
for (let i = 0; i < subscriptions.length; i++) {
|
|
239
|
+
if (subscriptions[i].synchronous) {
|
|
240
|
+
hasSynchronous = true
|
|
241
|
+
} else {
|
|
242
|
+
hasDeferred = true
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!hasSynchronous && !hasDeferred) {
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// A listener installed by a reentrant action must not receive earlier patches.
|
|
250
|
+
const current = subscriptions.slice()
|
|
251
|
+
if (hasDeferred) {
|
|
252
|
+
pendingPatchNotifications.push(() => notifyPatchSubscriptions(current, args, false))
|
|
253
|
+
}
|
|
254
|
+
if (hasSynchronous) {
|
|
255
|
+
synchronousNotifications.push(() => notifyPatchSubscriptions(current, args, true))
|
|
162
256
|
}
|
|
163
257
|
}
|
|
164
258
|
|
|
259
|
+
function emitGlobalPatch(
|
|
260
|
+
obj: object,
|
|
261
|
+
patches: Patch[],
|
|
262
|
+
inversePatches: Patch[],
|
|
263
|
+
synchronousNotifications: (() => void)[]
|
|
264
|
+
): void {
|
|
265
|
+
reservePatchNotifications(
|
|
266
|
+
globalPatchListeners,
|
|
267
|
+
[obj, patches, inversePatches],
|
|
268
|
+
synchronousNotifications
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
165
272
|
// `reversedPrefix` holds the path segments from the changed node up to (but not
|
|
166
273
|
// including) the current target, in child-to-root order. The prefix to prepend to
|
|
167
274
|
// a patch is that slice reversed (root-to-child); `prefixCount` is how many of the
|
|
@@ -171,7 +278,8 @@ function emitPatchForTarget(
|
|
|
171
278
|
patches: Patch[],
|
|
172
279
|
inversePatches: Patch[],
|
|
173
280
|
reversedPrefix: readonly PathElement[],
|
|
174
|
-
prefixCount: number
|
|
281
|
+
prefixCount: number,
|
|
282
|
+
synchronousNotifications: (() => void)[]
|
|
175
283
|
): void {
|
|
176
284
|
const listenersForObject = patchListeners.get(obj)
|
|
177
285
|
|
|
@@ -184,19 +292,26 @@ function emitPatchForTarget(
|
|
|
184
292
|
const invPatchesWithPathPrefix =
|
|
185
293
|
prefixCount > 0 ? prefixPatches(inversePatches, reversedPrefix, prefixCount) : inversePatches
|
|
186
294
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
295
|
+
// Capture mutation-time paths before a deep-change listener can move nodes.
|
|
296
|
+
reservePatchNotifications(
|
|
297
|
+
listenersForObject,
|
|
298
|
+
[patchesWithPathPrefix, invPatchesWithPathPrefix],
|
|
299
|
+
synchronousNotifications
|
|
300
|
+
)
|
|
191
301
|
}
|
|
192
302
|
|
|
193
|
-
function emitPatch(
|
|
303
|
+
function emitPatch(
|
|
304
|
+
obj: object,
|
|
305
|
+
patches: Patch[],
|
|
306
|
+
inversePatches: Patch[],
|
|
307
|
+
synchronousNotifications: (() => void)[]
|
|
308
|
+
): void {
|
|
194
309
|
// Segments are pushed in child-to-root order (O(1) each) rather than unshifted
|
|
195
310
|
// at the front (O(depth) each), keeping the whole walk O(depth) instead of
|
|
196
311
|
// O(depth^2) on deep trees.
|
|
197
312
|
const reversedPrefix: PathElement[] = []
|
|
198
313
|
|
|
199
|
-
emitPatchForTarget(obj, patches, inversePatches, reversedPrefix, 0)
|
|
314
|
+
emitPatchForTarget(obj, patches, inversePatches, reversedPrefix, 0, synchronousNotifications)
|
|
200
315
|
|
|
201
316
|
// and also emit subtree listeners all the way to the root
|
|
202
317
|
let parentPath = fastGetParentPath(obj, false)
|
|
@@ -207,7 +322,8 @@ function emitPatch(obj: object, patches: Patch[], inversePatches: Patch[]): void
|
|
|
207
322
|
patches,
|
|
208
323
|
inversePatches,
|
|
209
324
|
reversedPrefix,
|
|
210
|
-
reversedPrefix.length
|
|
325
|
+
reversedPrefix.length,
|
|
326
|
+
synchronousNotifications
|
|
211
327
|
)
|
|
212
328
|
|
|
213
329
|
parentPath = fastGetParentPath(parentPath.parent, false)
|
package/src/patch/jsonPatch.ts
CHANGED
|
@@ -41,6 +41,8 @@ function escapePathComponent(path: string | number): string {
|
|
|
41
41
|
return path.replace(/~/g, "~0").replace(/\//g, "~1")
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
const invalidPointerEscape = /~(?:[^01]|$)/
|
|
45
|
+
|
|
44
46
|
/**
|
|
45
47
|
* Unescapes a json pointer path.
|
|
46
48
|
*
|
|
@@ -48,6 +50,9 @@ function escapePathComponent(path: string | number): string {
|
|
|
48
50
|
* @return The unescaped path
|
|
49
51
|
*/
|
|
50
52
|
function unescapePathComponent(path: string): string {
|
|
53
|
+
if (invalidPointerEscape.test(path)) {
|
|
54
|
+
throw failure("invalid JSON pointer escape: expected ~0 or ~1")
|
|
55
|
+
}
|
|
51
56
|
return path.replace(/~1/g, "/").replace(/~0/g, "~")
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { observable } from "mobx"
|
|
2
2
|
import { assertTweakedObject } from "../tweaker/core"
|
|
3
3
|
import {
|
|
4
|
+
internalOnGlobalPatches,
|
|
5
|
+
internalOnPatches,
|
|
4
6
|
type OnPatchesDisposer,
|
|
5
7
|
type OnPatchesListener,
|
|
6
|
-
onGlobalPatches,
|
|
7
|
-
onPatches,
|
|
8
8
|
} from "./emitPatch"
|
|
9
9
|
import type { Patch } from "./Patch"
|
|
10
10
|
|
|
@@ -111,18 +111,22 @@ export function internalPatchRecorder(
|
|
|
111
111
|
let onPatchesDisposer: OnPatchesDisposer
|
|
112
112
|
|
|
113
113
|
if (subtreeRoot) {
|
|
114
|
-
onPatchesDisposer =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
onPatchesDisposer = internalOnPatches(
|
|
115
|
+
subtreeRoot,
|
|
116
|
+
(p, invP) => {
|
|
117
|
+
if (recording && filter(p, invP)) {
|
|
118
|
+
events.push({
|
|
119
|
+
target: subtreeRoot,
|
|
120
|
+
patches: p,
|
|
121
|
+
inversePatches: invP,
|
|
122
|
+
})
|
|
123
|
+
opts?.onPatches?.(p, invP)
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
true
|
|
127
|
+
)
|
|
124
128
|
} else {
|
|
125
|
-
onPatchesDisposer =
|
|
129
|
+
onPatchesDisposer = internalOnGlobalPatches((target, p, invP) => {
|
|
126
130
|
if (recording && filter(p, invP)) {
|
|
127
131
|
events.push({
|
|
128
132
|
target,
|
|
@@ -131,7 +135,7 @@ export function internalPatchRecorder(
|
|
|
131
135
|
})
|
|
132
136
|
opts?.onPatches?.(p, invP)
|
|
133
137
|
}
|
|
134
|
-
})
|
|
138
|
+
}, true)
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
return {
|
|
@@ -33,6 +33,7 @@ export interface ConnectReduxDevToolsOptions {
|
|
|
33
33
|
* @param remotedevConnection The result of a connect method from the remotedev package (usually the result of `remoteDev.connectViaExtension(...)`).
|
|
34
34
|
* @param target Object to use as root.
|
|
35
35
|
* @param [options] Optional options object.
|
|
36
|
+
* @returns An idempotent disposer that stops logging and unsubscribes from monitor messages.
|
|
36
37
|
*/
|
|
37
38
|
export function connectReduxDevTools(
|
|
38
39
|
remotedevPackage: any,
|
|
@@ -42,37 +43,35 @@ export function connectReduxDevTools(
|
|
|
42
43
|
) {
|
|
43
44
|
assertTweakedObject(target, "target")
|
|
44
45
|
|
|
45
|
-
const
|
|
46
|
-
logArgsNearName: true,
|
|
47
|
-
logChildActions: true,
|
|
48
|
-
...options,
|
|
49
|
-
}
|
|
46
|
+
const { logArgsNearName = true, logChildActions = true } = options ?? {}
|
|
50
47
|
|
|
48
|
+
let disposed = false
|
|
51
49
|
let handlingMonitorAction = 0
|
|
52
50
|
|
|
51
|
+
let initialState = getSnapshot(target)
|
|
52
|
+
let lastLoggedSnapshot = initialState
|
|
53
|
+
remotedevConnection.init(initialState)
|
|
54
|
+
|
|
53
55
|
// subscribe to change state (if need more than just logging)
|
|
54
|
-
remotedevConnection.subscribe((message: any) => {
|
|
55
|
-
if (message.type === "DISPATCH") {
|
|
56
|
+
const unsubscribe = remotedevConnection.subscribe((message: any) => {
|
|
57
|
+
if (!disposed && message.type === "DISPATCH") {
|
|
56
58
|
handleMonitorActions(remotedevConnection, target, message)
|
|
57
59
|
}
|
|
58
60
|
})
|
|
59
61
|
|
|
60
|
-
const initialState = getSnapshot(target)
|
|
61
|
-
remotedevConnection.init(initialState)
|
|
62
|
-
|
|
63
62
|
let currentActionId = 0
|
|
64
63
|
const actionIdSymbol = Symbol("actionId")
|
|
65
64
|
|
|
66
|
-
actionTrackingMiddleware(target, {
|
|
65
|
+
const disposeMiddleware = actionTrackingMiddleware(target, {
|
|
67
66
|
filter(ctx) {
|
|
68
|
-
return
|
|
67
|
+
return logChildActions || !hasDevToolsTrackedParentContext(ctx)
|
|
69
68
|
},
|
|
70
69
|
onStart(ctx) {
|
|
71
70
|
ctx.data[actionIdSymbol] = currentActionId++
|
|
72
71
|
},
|
|
73
72
|
onResume(ctx) {
|
|
74
73
|
// give a chance to the parent to log its own changes before the child starts
|
|
75
|
-
if (
|
|
74
|
+
if (logChildActions && ctx.parentContext) {
|
|
76
75
|
log(ctx.parentContext, undefined)
|
|
77
76
|
}
|
|
78
77
|
log(ctx, undefined)
|
|
@@ -85,6 +84,13 @@ export function connectReduxDevTools(
|
|
|
85
84
|
},
|
|
86
85
|
})
|
|
87
86
|
|
|
87
|
+
return () => {
|
|
88
|
+
if (disposed) return
|
|
89
|
+
disposed = true
|
|
90
|
+
disposeMiddleware()
|
|
91
|
+
if (typeof unsubscribe === "function") unsubscribe()
|
|
92
|
+
}
|
|
93
|
+
|
|
88
94
|
function handleMonitorActions(remotedev2: any, target2: any, message: any) {
|
|
89
95
|
try {
|
|
90
96
|
handlingMonitorAction++
|
|
@@ -96,7 +102,8 @@ export function connectReduxDevTools(
|
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
case "COMMIT":
|
|
99
|
-
|
|
105
|
+
initialState = getSnapshot(target2)
|
|
106
|
+
return remotedev2.init(initialState)
|
|
100
107
|
|
|
101
108
|
case "ROLLBACK": {
|
|
102
109
|
const state = remotedevPackage.extractState(message)
|
|
@@ -122,13 +129,12 @@ export function connectReduxDevTools(
|
|
|
122
129
|
}
|
|
123
130
|
} finally {
|
|
124
131
|
handlingMonitorAction--
|
|
132
|
+
lastLoggedSnapshot = getSnapshot(target2)
|
|
125
133
|
}
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
let lastLoggedSnapshot = initialState
|
|
129
|
-
|
|
130
136
|
function log(ctx: SimpleActionContext, result: ActionTrackingResult | undefined) {
|
|
131
|
-
if (handlingMonitorAction) {
|
|
137
|
+
if (disposed || handlingMonitorAction) {
|
|
132
138
|
return
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -160,7 +166,7 @@ export function connectReduxDevTools(
|
|
|
160
166
|
const pathStr = "[/" + rootPath.path.join("/") + "] "
|
|
161
167
|
let name = pathStr + ctx.actionName
|
|
162
168
|
|
|
163
|
-
if (
|
|
169
|
+
if (logArgsNearName) {
|
|
164
170
|
let args = ctx.args
|
|
165
171
|
.map((a) => {
|
|
166
172
|
try {
|
package/src/redux/redux.ts
CHANGED
|
@@ -83,7 +83,7 @@ export function asReduxStore<T extends object>(
|
|
|
83
83
|
return getSnapshot(target)
|
|
84
84
|
},
|
|
85
85
|
dispatch(action) {
|
|
86
|
-
return
|
|
86
|
+
return dispatch(action)
|
|
87
87
|
},
|
|
88
88
|
subscribe(listener) {
|
|
89
89
|
return onSnapshot(target, listener)
|
|
@@ -91,25 +91,9 @@ export function asReduxStore<T extends object>(
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
const runners = middlewares.map((mw) => mw(store))
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
function runMiddlewares<T>(
|
|
99
|
-
initialAction: ReduxAction,
|
|
100
|
-
runners: ReadonlyArray<ReduxRunner<T>>,
|
|
101
|
-
next: ReduxStore<T>["dispatch"]
|
|
102
|
-
): ReduxAction {
|
|
103
|
-
let i = 0
|
|
104
|
-
|
|
105
|
-
function runNextMiddleware(action: ReduxAction): ReduxAction {
|
|
106
|
-
if (i < runners.length) {
|
|
107
|
-
const runner = runners[i]
|
|
108
|
-
i++
|
|
109
|
-
return runner(runNextMiddleware)(action)
|
|
110
|
-
}
|
|
111
|
-
return next(action)
|
|
94
|
+
let dispatch = defaultDispatch
|
|
95
|
+
for (let i = runners.length - 1; i >= 0; i--) {
|
|
96
|
+
dispatch = runners[i](dispatch)
|
|
112
97
|
}
|
|
113
|
-
|
|
114
|
-
return runNextMiddleware(initialAction)
|
|
98
|
+
return store
|
|
115
99
|
}
|