mobx-keystone 1.25.0 → 2.1.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 +145 -0
- package/dist/mobx-keystone.esm.js +3627 -1540
- package/dist/mobx-keystone.esm.mjs +3627 -1540
- package/dist/mobx-keystone.umd.js +3638 -1543
- package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +17 -10
- package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
- package/dist/types/deepChange/onDeepChange.d.ts +9 -0
- package/dist/types/parent/arrayPaths.d.ts +1 -0
- package/dist/types/parent/detach.d.ts +1 -1
- package/dist/types/parent/findChildren.d.ts +1 -1
- package/dist/types/parent/treeOrder.d.ts +1 -0
- package/dist/types/patch/applyPatches.d.ts +3 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
- package/dist/types/ref/modelRefIdIndex.d.ts +1 -0
- package/dist/types/ref/nodesById.d.ts +1 -0
- package/dist/types/ref/refIdIndex.d.ts +1 -0
- package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
- package/dist/types/snapshot/applySnapshot.d.ts +1 -0
- package/dist/types/snapshot/fromSnapshot.d.ts +0 -3
- package/dist/types/standardActions/arrayActions.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +0 -4
- package/dist/types/tweaker/changeRollback.d.ts +1 -0
- package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
- package/dist/types/types/TypeCheckError.d.ts +1 -0
- package/dist/types/types/utility/typesRefinement.d.ts +2 -2
- package/dist/types/utils/ModelPool.d.ts +7 -1
- package/dist/types/utils/chunks.d.ts +1 -0
- 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 +2 -2
- 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 +55 -25
- package/src/actionMiddlewares/undoMiddleware.ts +112 -59
- 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 +10 -13
- package/src/model/newModel.ts +21 -24
- package/src/model/utils.ts +2 -2
- package/src/modelShared/modelClassInitializer.ts +3 -2
- package/src/modelShared/modelDecorator.ts +39 -39
- package/src/modelShared/modelInfo.ts +1 -1
- package/src/modelShared/prop.ts +6 -9
- package/src/modelShared/sharedInternalModel.ts +16 -3
- package/src/parent/arrayPaths.ts +242 -0
- package/src/parent/coreObjectChildren.ts +300 -48
- package/src/parent/detach.ts +1 -1
- package/src/parent/findChildren.ts +4 -7
- package/src/parent/onChildAttachedTo.ts +157 -55
- package/src/parent/path.ts +15 -5
- package/src/parent/setParent.ts +34 -15
- package/src/parent/treeOrder.ts +53 -0
- package/src/parent/walkTree.ts +12 -101
- package/src/patch/applyPatches.ts +74 -34
- 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 +81 -29
- package/src/ref/modelRefIdIndex.ts +131 -0
- package/src/ref/nodesById.ts +60 -0
- package/src/ref/refIdIndex.ts +209 -0
- 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 +27 -19
- package/src/snapshot/clone.ts +1 -2
- package/src/snapshot/cloneTreeValue.ts +1 -2
- package/src/snapshot/fromModelSnapshot.ts +6 -2
- package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
- package/src/snapshot/fromSnapshot.ts +2 -6
- package/src/snapshot/internal.ts +89 -19
- package/src/snapshot/onSnapshot.ts +12 -1
- package/src/snapshot/reconcileModelSnapshot.ts +6 -6
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -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/changeRollback.ts +218 -0
- package/src/tweaker/core.ts +33 -22
- package/src/tweaker/treeNodeMetadata.ts +12 -2
- package/src/tweaker/tweak.ts +183 -19
- package/src/tweaker/tweakArray.ts +176 -33
- package/src/tweaker/tweakPlainObject.ts +117 -39
- package/src/tweaker/tweakedChangeListeners.ts +61 -0
- package/src/tweaker/typeChecking.ts +25 -15
- package/src/types/TypeCheckError.ts +17 -19
- package/src/types/arrayBased/typesArray.ts +10 -11
- package/src/types/createCachedTypeCheck.ts +541 -88
- 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 +8 -18
- 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 +78 -22
- package/src/utils/chainFns.ts +4 -0
- package/src/utils/chunks.ts +271 -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 +71 -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
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { action, reaction, runInAction } from "mobx"
|
|
1
|
+
import { action, createAtom, reaction, runInAction } from "mobx"
|
|
2
2
|
import { assertTweakedObject } from "../tweaker/core"
|
|
3
3
|
import { assertIsFunction } from "../utils"
|
|
4
|
+
import {
|
|
5
|
+
addObjectChildrenListener,
|
|
6
|
+
type ObjectChildrenListener,
|
|
7
|
+
removeObjectChildrenListener,
|
|
8
|
+
} from "./coreObjectChildren"
|
|
4
9
|
import { getChildrenObjects } from "./getChildrenObjects"
|
|
10
|
+
import { compareTreePaths, getPathFromAncestor } from "./treeOrder"
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Runs a callback everytime a new object is attached to a given node.
|
|
@@ -32,11 +38,10 @@ export function onChildAttachedTo(
|
|
|
32
38
|
assertIsFunction(target, "target")
|
|
33
39
|
assertIsFunction(fn, "fn")
|
|
34
40
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
41
|
+
const { deep = false, fireForCurrentChildren = true } = options ?? {}
|
|
42
|
+
|
|
43
|
+
let disposed = false
|
|
44
|
+
let runCleanupAfterDispose = false
|
|
40
45
|
|
|
41
46
|
const detachDisposers = new WeakMap<object, () => void>()
|
|
42
47
|
|
|
@@ -48,81 +53,178 @@ export function onChildAttachedTo(
|
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
const runDetachDisposers = (nodes: readonly object[]) => {
|
|
57
|
+
let firstError: { value: unknown } | undefined
|
|
58
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
59
|
+
try {
|
|
60
|
+
runDetachDisposer(nodes[i])
|
|
61
|
+
} catch (value) {
|
|
62
|
+
firstError ??= { value }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return firstError
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
const addDetachDisposer = (n: object, disposer: (() => void) | void) => {
|
|
52
69
|
if (disposer) {
|
|
53
|
-
|
|
70
|
+
const detach = action(disposer)
|
|
71
|
+
if (disposed) {
|
|
72
|
+
if (runCleanupAfterDispose) detach()
|
|
73
|
+
} else {
|
|
74
|
+
detachDisposers.set(n, detach)
|
|
75
|
+
}
|
|
54
76
|
}
|
|
55
77
|
}
|
|
56
78
|
|
|
57
|
-
const getChildrenObjectOpts = { deep
|
|
58
|
-
const
|
|
79
|
+
const getChildrenObjectOpts = { deep }
|
|
80
|
+
const getTarget = () => {
|
|
59
81
|
const t = target()
|
|
60
82
|
assertTweakedObject(t, "target()")
|
|
61
|
-
|
|
62
|
-
return new Set(getChildrenObjects(t, getChildrenObjectOpts))
|
|
83
|
+
return t
|
|
63
84
|
}
|
|
64
85
|
|
|
65
|
-
|
|
86
|
+
/** Children the callback ran for, with increasing numbers in the order it did. */
|
|
87
|
+
const currentChildren = new Map<object, number>()
|
|
88
|
+
let lastChildOrder = 0
|
|
66
89
|
|
|
67
|
-
const
|
|
68
|
-
(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const currentChildrenIter = currentChildren.values()
|
|
74
|
-
let currentChildrenCur = currentChildrenIter.next()
|
|
75
|
-
while (!currentChildrenCur.done) {
|
|
76
|
-
const n = currentChildrenCur.value
|
|
77
|
-
if (!newChildren.has(n)) {
|
|
78
|
-
currentChildren.delete(n)
|
|
79
|
-
|
|
80
|
-
// we should run it in inverse order
|
|
81
|
-
disposersToRun.push(n)
|
|
82
|
-
}
|
|
90
|
+
const updateChildren = (dead: readonly object[], born: Iterable<object>) => {
|
|
91
|
+
for (const n of dead) {
|
|
92
|
+
currentChildren.delete(n)
|
|
93
|
+
}
|
|
94
|
+
// we should run them in inverse order
|
|
95
|
+
let firstError = runDetachDisposers(dead)
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
for (const n of born) {
|
|
98
|
+
if (disposed) {
|
|
99
|
+
break
|
|
85
100
|
}
|
|
101
|
+
currentChildren.set(n, ++lastChildOrder)
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
try {
|
|
104
|
+
const detachAction = runInAction(() => fn(n))
|
|
105
|
+
addDetachDisposer(n, detachAction)
|
|
106
|
+
} catch (value) {
|
|
107
|
+
firstError ??= { value }
|
|
91
108
|
}
|
|
109
|
+
}
|
|
110
|
+
if (firstError) {
|
|
111
|
+
throw firstError.value
|
|
112
|
+
}
|
|
113
|
+
}
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
const updateChildrenByDiff = (newChildren: ReadonlySet<object>) => {
|
|
116
|
+
const dead: object[] = []
|
|
117
|
+
for (const n of currentChildren.keys()) {
|
|
118
|
+
if (!newChildren.has(n)) {
|
|
119
|
+
dead.push(n)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const born: object[] = []
|
|
123
|
+
for (const n of newChildren) {
|
|
124
|
+
if (!currentChildren.has(n)) {
|
|
125
|
+
born.push(n)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
updateChildren(dead, born)
|
|
129
|
+
}
|
|
100
130
|
|
|
101
|
-
|
|
102
|
-
|
|
131
|
+
/** Updates the children given the only nodes that may have been attached or detached. */
|
|
132
|
+
const updateChildrenIncrementally = (t: object, nodes: ReadonlySet<object>) => {
|
|
133
|
+
const dead: object[] = []
|
|
134
|
+
const born: { node: object; path: object[] }[] = []
|
|
135
|
+
for (const node of nodes) {
|
|
136
|
+
const path = getPathFromAncestor(t, node, false)
|
|
137
|
+
const isChild = path !== undefined && (deep || path.length === 1)
|
|
138
|
+
if (currentChildren.has(node)) {
|
|
139
|
+
if (!isChild) {
|
|
140
|
+
dead.push(node)
|
|
103
141
|
}
|
|
142
|
+
} else if (isChild) {
|
|
143
|
+
born.push({ node, path })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// same orders as a diff: the old children in the order they were added,
|
|
147
|
+
// and the new ones in the order of the children set
|
|
148
|
+
dead.sort((a, b) => currentChildren.get(a)! - currentChildren.get(b)!)
|
|
149
|
+
born.sort((a, b) => compareTreePaths(a.path, b.path, true))
|
|
150
|
+
updateChildren(
|
|
151
|
+
dead,
|
|
152
|
+
born.map((b) => b.node)
|
|
153
|
+
)
|
|
154
|
+
}
|
|
104
155
|
|
|
105
|
-
|
|
156
|
+
// nodes that may have been attached to or detached from the target since
|
|
157
|
+
// the last update
|
|
158
|
+
let touched = new Set<object>()
|
|
159
|
+
const touchedAtom = createAtom("onChildAttachedTo")
|
|
160
|
+
const listener: ObjectChildrenListener = {
|
|
161
|
+
deep,
|
|
162
|
+
onChildrenChanged(nodes) {
|
|
163
|
+
for (const n of nodes) {
|
|
164
|
+
touched.add(n)
|
|
106
165
|
}
|
|
166
|
+
touchedAtom.reportChanged()
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
let listenedTarget: object | undefined
|
|
170
|
+
|
|
171
|
+
const stopListening = () => {
|
|
172
|
+
if (listenedTarget) {
|
|
173
|
+
removeObjectChildrenListener(listenedTarget, listener)
|
|
174
|
+
listenedTarget = undefined
|
|
175
|
+
}
|
|
176
|
+
touched = new Set()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const update = (t: object) => {
|
|
180
|
+
if (t !== listenedTarget) {
|
|
181
|
+
stopListening()
|
|
182
|
+
addObjectChildrenListener(t, listener)
|
|
183
|
+
listenedTarget = t
|
|
184
|
+
updateChildrenByDiff(getChildrenObjects(t, getChildrenObjectOpts))
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const nodes = touched
|
|
189
|
+
touched = new Set()
|
|
190
|
+
// (even when most children changed, this is about as fast as a diff)
|
|
191
|
+
if (nodes.size > 0) {
|
|
192
|
+
updateChildrenIncrementally(t, nodes)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!fireForCurrentChildren) {
|
|
197
|
+
for (const n of getChildrenObjects(getTarget(), getChildrenObjectOpts)) {
|
|
198
|
+
currentChildren.set(n, ++lastChildOrder)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const disposer = reaction(
|
|
203
|
+
() => {
|
|
204
|
+
const t = getTarget()
|
|
205
|
+
touchedAtom.reportObserved()
|
|
206
|
+
// a new object, so every change runs the effect
|
|
207
|
+
return { t }
|
|
208
|
+
},
|
|
209
|
+
({ t }) => {
|
|
210
|
+
update(t)
|
|
107
211
|
},
|
|
108
212
|
{
|
|
109
213
|
fireImmediately: true,
|
|
110
214
|
}
|
|
111
215
|
)
|
|
112
216
|
|
|
113
|
-
return (
|
|
217
|
+
return (runPendingDetachDisposers: boolean) => {
|
|
218
|
+
if (disposed) return
|
|
219
|
+
disposed = true
|
|
220
|
+
runCleanupAfterDispose = runPendingDetachDisposers
|
|
114
221
|
disposer()
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const currentChildrenIter = currentChildren.values()
|
|
118
|
-
let currentChildrenCur = currentChildrenIter.next()
|
|
119
|
-
while (!currentChildrenCur.done) {
|
|
120
|
-
const n = currentChildrenCur.value
|
|
121
|
-
runDetachDisposer(n)
|
|
122
|
-
|
|
123
|
-
currentChildrenCur = currentChildrenIter.next()
|
|
124
|
-
}
|
|
125
|
-
}
|
|
222
|
+
stopListening()
|
|
223
|
+
const pendingChildren = runPendingDetachDisposers ? Array.from(currentChildren.keys()) : []
|
|
126
224
|
currentChildren.clear()
|
|
225
|
+
const firstError = runDetachDisposers(pendingChildren)
|
|
226
|
+
if (firstError) {
|
|
227
|
+
throw firstError.value
|
|
228
|
+
}
|
|
127
229
|
}
|
|
128
230
|
}
|
package/src/parent/path.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { isModel } from "../model/utils"
|
|
|
3
3
|
import { assertTweakedObject } from "../tweaker/core"
|
|
4
4
|
import { treeNodeMetadata } from "../tweaker/treeNodeMetadata"
|
|
5
5
|
import { hasOwnProp, isArray, isObject } from "../utils"
|
|
6
|
+
import { LazyArrayParentPath } from "./arrayPaths"
|
|
6
7
|
import {
|
|
7
8
|
dataToModelNode,
|
|
8
9
|
getDataObjectParent,
|
|
@@ -61,7 +62,10 @@ export interface RootPath<T extends object> {
|
|
|
61
62
|
export function getParentPath<T extends object = any>(value: object): ParentPath<T> | undefined {
|
|
62
63
|
assertTweakedObject(value, "value")
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
const parentPath = fastGetParentPath<T>(value, true)
|
|
66
|
+
return parentPath instanceof LazyArrayParentPath
|
|
67
|
+
? (parentPath.toParentPath() as ParentPath<T>)
|
|
68
|
+
: parentPath
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
/**
|
|
@@ -71,10 +75,14 @@ export function fastGetParentPath<T extends object = any>(
|
|
|
71
75
|
value: object,
|
|
72
76
|
useAtom: boolean
|
|
73
77
|
): ParentPath<T> | undefined {
|
|
78
|
+
const parentPath = treeNodeMetadata.get(value)?.parentPath
|
|
74
79
|
if (useAtom) {
|
|
75
80
|
reportParentPathObserved(value)
|
|
81
|
+
if (parentPath instanceof LazyArrayParentPath) {
|
|
82
|
+
parentPath.reportObserved()
|
|
83
|
+
}
|
|
76
84
|
}
|
|
77
|
-
return
|
|
85
|
+
return parentPath as ParentPath<T> | undefined
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
/**
|
|
@@ -267,11 +275,13 @@ export function resolvePath<T = any>(
|
|
|
267
275
|
const dataNode = modelToDataNode(current)
|
|
268
276
|
if (p in dataNode) {
|
|
269
277
|
current = dataNode
|
|
270
|
-
} else if (!(p in current)) {
|
|
271
|
-
return unresolved
|
|
272
278
|
}
|
|
273
279
|
}
|
|
274
280
|
|
|
281
|
+
if (!(p in current)) {
|
|
282
|
+
return unresolved
|
|
283
|
+
}
|
|
284
|
+
|
|
275
285
|
current = current[p]
|
|
276
286
|
}
|
|
277
287
|
|
|
@@ -323,7 +333,7 @@ export function resolvePathCheckingIds<T = any>(
|
|
|
323
333
|
|
|
324
334
|
// check just to avoid mobx warnings about trying to access out of bounds index
|
|
325
335
|
if (isArray(current)) {
|
|
326
|
-
if (+p >= current.length) {
|
|
336
|
+
if (+p >= current.length || !(p in current)) {
|
|
327
337
|
return { resolved: false }
|
|
328
338
|
}
|
|
329
339
|
} else if (!hasOwnProp(current, p)) {
|
package/src/parent/setParent.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { isModel } from "../model/utils"
|
|
|
6
6
|
import { attachToRootStore, detachFromRootStore } from "../rootStore/attachDetach"
|
|
7
7
|
import { fastIsRootStoreNoAtom } from "../rootStore/rootStore"
|
|
8
8
|
import { clone } from "../snapshot/clone"
|
|
9
|
+
import { flushInternalSnapshot } from "../snapshot/internal"
|
|
9
10
|
import { isTweakedObject } from "../tweaker/core"
|
|
10
11
|
import { treeNodeMetadata } from "../tweaker/treeNodeMetadata"
|
|
11
12
|
import { tryUntweak } from "../tweaker/tweak"
|
|
@@ -16,9 +17,11 @@ import {
|
|
|
16
17
|
reportParentPathChanged,
|
|
17
18
|
setDataObjectParent,
|
|
18
19
|
} from "./core"
|
|
19
|
-
import { addObjectChild, removeObjectChild } from "./coreObjectChildren"
|
|
20
|
+
import { addObjectChild, hasObjectChildren, removeObjectChild } from "./coreObjectChildren"
|
|
20
21
|
import { fastGetParentPath, fastGetRoot, type ParentPath } from "./path"
|
|
21
22
|
|
|
23
|
+
const reindexChunkSize = 4096
|
|
24
|
+
|
|
22
25
|
/**
|
|
23
26
|
* Updates the unchanged tail of an array before a splice is applied. These
|
|
24
27
|
* children keep their parent, so neither attachment nor child-index work is
|
|
@@ -28,18 +31,24 @@ import { fastGetParentPath, fastGetRoot, type ParentPath } from "./path"
|
|
|
28
31
|
export const reindexArrayChildren = action(
|
|
29
32
|
"reindexArrayChildren",
|
|
30
33
|
(array: readonly unknown[], oldStart: number, newStart: number): void => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
// Copy the tail in chunks: indexed reads through an observable array are
|
|
35
|
+
// much slower than a slice, and chunking bounds the temporary copy size.
|
|
36
|
+
const length = array.length
|
|
37
|
+
for (let chunkStart = oldStart; chunkStart < length; chunkStart += reindexChunkSize) {
|
|
38
|
+
const chunk = array.slice(chunkStart, chunkStart + reindexChunkSize)
|
|
39
|
+
for (let i = 0, j = newStart + chunkStart - oldStart; i < chunk.length; i++, j++) {
|
|
40
|
+
const value = chunk[i]
|
|
41
|
+
if (isPrimitive(value)) {
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
const metadata = treeNodeMetadata.get(value as object)
|
|
45
|
+
if (inDevMode && metadata?.parentPath?.parent !== array) {
|
|
46
|
+
throw failure("assertion failed: reindexed child must already belong to the array")
|
|
47
|
+
}
|
|
48
|
+
// Replace the path object: callers may have retained the previous path.
|
|
49
|
+
metadata!.parentPath = { parent: array, path: j }
|
|
50
|
+
reportParentPathChanged(value as object)
|
|
39
51
|
}
|
|
40
|
-
// Replace the path object: callers may have retained the previous path.
|
|
41
|
-
metadata!.parentPath = { parent: array, path: j }
|
|
42
|
-
reportParentPathChanged(value as object)
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
)
|
|
@@ -86,6 +95,12 @@ export const setParent = action(
|
|
|
86
95
|
return value
|
|
87
96
|
}
|
|
88
97
|
|
|
98
|
+
// The new parent captures this node's snapshot during attachment. Flush
|
|
99
|
+
// descendant edits made while detached before that snapshot is copied.
|
|
100
|
+
if (parentPath && !oldParentPath) {
|
|
101
|
+
flushInternalSnapshot(value, false)
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
// make sure the new parent actually points to models when we give model data objs
|
|
90
105
|
if (parentPath) {
|
|
91
106
|
const actualParent = dataToModelNode(parentPath.parent)
|
|
@@ -116,11 +131,15 @@ export const setParent = action(
|
|
|
116
131
|
|
|
117
132
|
const postUntweaker = parentPath ? undefined : tryUntweak(value)
|
|
118
133
|
|
|
119
|
-
|
|
134
|
+
// A model's root store hooks run when its own root changes. A plain node
|
|
135
|
+
// has none of its own, but one attached with children may bring models
|
|
136
|
+
// into a root store. (When detached, its children get detached one by one.)
|
|
137
|
+
const checkRootStore =
|
|
138
|
+
value instanceof BaseModel || (!oldParentPath && !!parentPath && hasObjectChildren(value))
|
|
120
139
|
|
|
121
140
|
let oldRoot: any
|
|
122
141
|
let oldRootStore: any
|
|
123
|
-
if (
|
|
142
|
+
if (checkRootStore) {
|
|
124
143
|
oldRoot = fastGetRoot(value, false)
|
|
125
144
|
oldRootStore = fastIsRootStoreNoAtom(oldRoot) ? oldRoot : undefined
|
|
126
145
|
}
|
|
@@ -137,7 +156,7 @@ export const setParent = action(
|
|
|
137
156
|
}
|
|
138
157
|
reportParentPathChanged(value)
|
|
139
158
|
|
|
140
|
-
if (
|
|
159
|
+
if (checkRootStore) {
|
|
141
160
|
const newRoot = fastGetRoot(value, false)
|
|
142
161
|
const newRootStore = fastIsRootStoreNoAtom(newRoot) ? newRoot : undefined
|
|
143
162
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { treeNodeMetadata } from "../tweaker/treeNodeMetadata"
|
|
2
|
+
import { reportParentPathObserved } from "./core"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Nodes from right below the ancestor down to the node, or `undefined` when the
|
|
6
|
+
* node is not below it. Optionally observes their parents.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export function getPathFromAncestor(
|
|
11
|
+
ancestor: object,
|
|
12
|
+
node: object,
|
|
13
|
+
observeParents: boolean
|
|
14
|
+
): object[] | undefined {
|
|
15
|
+
const path: object[] = []
|
|
16
|
+
let current: object | undefined = node
|
|
17
|
+
while (current !== ancestor) {
|
|
18
|
+
if (!current) {
|
|
19
|
+
return undefined
|
|
20
|
+
}
|
|
21
|
+
path.push(current)
|
|
22
|
+
if (observeParents) {
|
|
23
|
+
reportParentPathObserved(current)
|
|
24
|
+
}
|
|
25
|
+
current = treeNodeMetadata.get(current)?.parentPath?.parent
|
|
26
|
+
}
|
|
27
|
+
return path.reverse()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Compares two paths from the same ancestor (see `getPathFromAncestor`) by the
|
|
32
|
+
* order a walk of the tree visits their nodes. The walk visits the children of
|
|
33
|
+
* a node in the order they were attached, and visits parents before their
|
|
34
|
+
* children when `parentsFirst`, or after them otherwise.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function compareTreePaths(
|
|
39
|
+
a: readonly object[],
|
|
40
|
+
b: readonly object[],
|
|
41
|
+
parentsFirst: boolean
|
|
42
|
+
): number {
|
|
43
|
+
const length = Math.min(a.length, b.length)
|
|
44
|
+
for (let i = 0; i < length; i++) {
|
|
45
|
+
if (a[i] !== b[i]) {
|
|
46
|
+
// siblings
|
|
47
|
+
return treeNodeMetadata.get(a[i])!.attachOrder - treeNodeMetadata.get(b[i])!.attachOrder
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// the shorter path ends at an ancestor of the other node
|
|
51
|
+
const depthDifference = a.length - b.length
|
|
52
|
+
return parentsFirst ? depthDifference : -depthDifference
|
|
53
|
+
}
|
package/src/parent/walkTree.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { computed, type IComputedValue } from "mobx"
|
|
2
1
|
import { assertTweakedObject } from "../tweaker/core"
|
|
3
2
|
import { getObjectChildren } from "./coreObjectChildren"
|
|
4
3
|
|
|
@@ -75,108 +74,20 @@ function walkTreeChildrenFirst<T = void>(
|
|
|
75
74
|
root: object,
|
|
76
75
|
visit: (node: object) => T | undefined
|
|
77
76
|
): T | undefined {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (ret !== undefined) {
|
|
90
|
-
return ret
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return undefined
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @internal
|
|
98
|
-
*/
|
|
99
|
-
export interface ComputedWalkTreeAggregate<R> {
|
|
100
|
-
walk(target: object): Map<R, object> | undefined
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getComputedTreeResult<R>(
|
|
104
|
-
computedFns: WeakMap<object, IComputedValue<Map<R, object> | undefined>>,
|
|
105
|
-
visit: (node: object) => R | undefined,
|
|
106
|
-
tree: object
|
|
107
|
-
): Map<R, object> | undefined {
|
|
108
|
-
let cmpted = computedFns.get(tree)
|
|
109
|
-
if (!cmpted) {
|
|
110
|
-
cmpted = computed(() => {
|
|
111
|
-
return walkTreeAggregate(tree, visit, (ch) => getComputedTreeResult(computedFns, visit, ch))
|
|
112
|
-
})
|
|
113
|
-
computedFns.set(tree, cmpted)
|
|
114
|
-
}
|
|
115
|
-
return cmpted.get()
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* @internal
|
|
120
|
-
*/
|
|
121
|
-
export function computedWalkTreeAggregate<R>(
|
|
122
|
-
visit: (node: object) => R | undefined
|
|
123
|
-
): ComputedWalkTreeAggregate<R> {
|
|
124
|
-
const computedFns = new WeakMap<object, IComputedValue<Map<R, object> | undefined>>()
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
walk: (n) => getComputedTreeResult(computedFns, visit, n),
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function walkTreeAggregate<R>(
|
|
132
|
-
target: object,
|
|
133
|
-
visit: (node: object) => R | undefined,
|
|
134
|
-
recurse: (node: object) => Map<R, object> | undefined
|
|
135
|
-
): Map<R, object> | undefined {
|
|
136
|
-
let map: Map<R, object> | undefined
|
|
137
|
-
const rootVal = visit(target)
|
|
138
|
-
|
|
139
|
-
const childrenMap = getObjectChildren(target)
|
|
140
|
-
const childrenIter = childrenMap.values()
|
|
141
|
-
let ch = childrenIter.next()
|
|
142
|
-
|
|
143
|
-
// small optimization, if there is only one child and this
|
|
144
|
-
// object provides no value we can just reuse the child ones
|
|
145
|
-
if (rootVal === undefined && childrenMap.size === 1) {
|
|
146
|
-
return recurse(ch.value!)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
while (!ch.done) {
|
|
150
|
-
const childMap = recurse(ch.value)
|
|
151
|
-
|
|
152
|
-
if (childMap) {
|
|
153
|
-
if (!map) {
|
|
154
|
-
map = new Map()
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// add child map keys/values to own map
|
|
158
|
-
const mapIter = childMap.keys()
|
|
159
|
-
|
|
160
|
-
let mapCur = mapIter.next()
|
|
161
|
-
while (!mapCur.done) {
|
|
162
|
-
const key = mapCur.value
|
|
163
|
-
const val = childMap.get(key)!
|
|
164
|
-
map.set(key, val)
|
|
165
|
-
mapCur = mapIter.next()
|
|
77
|
+
const stack = [{ node: root, children: getObjectChildren(root).values() }]
|
|
78
|
+
while (stack.length > 0) {
|
|
79
|
+
const frame = stack[stack.length - 1]
|
|
80
|
+
const child = frame.children.next()
|
|
81
|
+
if (!child.done) {
|
|
82
|
+
stack.push({ node: child.value, children: getObjectChildren(child.value).values() })
|
|
83
|
+
} else {
|
|
84
|
+
stack.pop()
|
|
85
|
+
const ret = visit(frame.node)
|
|
86
|
+
if (ret !== undefined) {
|
|
87
|
+
return ret
|
|
166
88
|
}
|
|
167
89
|
}
|
|
168
|
-
|
|
169
|
-
ch = childrenIter.next()
|
|
170
90
|
}
|
|
171
91
|
|
|
172
|
-
|
|
173
|
-
// priority than child ones
|
|
174
|
-
if (rootVal !== undefined) {
|
|
175
|
-
if (!map) {
|
|
176
|
-
map = new Map()
|
|
177
|
-
}
|
|
178
|
-
map.set(rootVal, target)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return map
|
|
92
|
+
return undefined
|
|
182
93
|
}
|