mobx-keystone 2.0.0 → 2.2.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 +33 -0
- package/README.md +1 -1
- package/dist/mobx-keystone.esm.js +2561 -1214
- package/dist/mobx-keystone.esm.mjs +2561 -1214
- package/dist/mobx-keystone.umd.js +2566 -1220
- package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +11 -12
- package/dist/types/modelShared/modelDecorator.d.ts +5 -2
- package/dist/types/modelShared/prop.d.ts +42 -29
- package/dist/types/parent/arrayPaths.d.ts +1 -0
- package/dist/types/parent/treeOrder.d.ts +1 -0
- package/dist/types/patch/applyPatches.d.ts +2 -1
- package/dist/types/ref/Ref.d.ts +1 -4
- 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 +4 -4
- package/dist/types/snapshot/applySnapshot.d.ts +2 -1
- package/dist/types/snapshot/fromSnapshot.d.ts +2 -5
- package/dist/types/snapshot/getSnapshot.d.ts +1 -1
- package/dist/types/tweaker/changeRollback.d.ts +1 -0
- package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/schemas.d.ts +41 -37
- package/dist/types/types/tProp.d.ts +12 -6
- package/dist/types/types/types.d.ts +6 -6
- package/dist/types/types/utility/typeToStoredData.d.ts +14 -10
- package/dist/types/types/utility/typesCodecCore.d.ts +3 -0
- package/dist/types/utils/ModelPool.d.ts +7 -2
- package/dist/types/utils/chunks.d.ts +1 -0
- package/dist/types/utils/types.d.ts +6 -0
- package/dist/types/wrappers/ArraySet.d.ts +1 -0
- package/package.json +5 -3
- package/src/action/modelFlow.ts +35 -66
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +41 -71
- package/src/actionMiddlewares/transactionMiddleware.ts +41 -42
- package/src/actionMiddlewares/undoMiddleware.ts +63 -35
- package/src/context/context.ts +15 -36
- package/src/dataModel/utils.ts +5 -28
- package/src/deepChange/onDeepChange.ts +44 -126
- package/src/model/BaseModel.ts +7 -5
- package/src/model/utils.ts +5 -10
- package/src/modelShared/modelDecorator.ts +16 -30
- package/src/modelShared/prop.ts +115 -111
- package/src/modelShared/sharedInternalModel.ts +2 -2
- package/src/parent/arrayPaths.ts +239 -0
- package/src/parent/coreObjectChildren.ts +300 -48
- package/src/parent/onChildAttachedTo.ts +130 -44
- package/src/parent/path.ts +10 -12
- package/src/parent/setParent.ts +27 -15
- package/src/parent/treeOrder.ts +53 -0
- package/src/parent/walkTree.ts +0 -73
- package/src/patch/applyPatches.ts +31 -26
- package/src/patch/patchRecorder.ts +13 -28
- package/src/ref/core.ts +69 -46
- package/src/ref/modelRefIdIndex.ts +131 -0
- package/src/ref/nodesById.ts +60 -0
- package/src/ref/refIdIndex.ts +209 -0
- package/src/rootStore/rootStore.ts +1 -3
- package/src/snapshot/SnapshotOf.ts +24 -48
- package/src/snapshot/applySnapshot.ts +27 -19
- package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
- package/src/snapshot/fromSnapshot.ts +12 -9
- package/src/snapshot/getSnapshot.ts +1 -1
- package/src/snapshot/internal.ts +67 -12
- package/src/snapshot/reconcileModelSnapshot.ts +5 -3
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +3 -1
- package/src/treeUtils/draft.ts +6 -1
- package/src/treeUtils/sandbox.ts +10 -7
- package/src/tweaker/changeRollback.ts +218 -0
- package/src/tweaker/core.ts +33 -20
- package/src/tweaker/treeNodeMetadata.ts +12 -2
- package/src/tweaker/tweak.ts +181 -18
- package/src/tweaker/tweakArray.ts +127 -77
- package/src/tweaker/tweakPlainObject.ts +65 -46
- package/src/tweaker/tweakedChangeListeners.ts +61 -0
- package/src/tweaker/typeChecking.ts +5 -104
- package/src/types/arrayBased/typesArray.ts +23 -30
- package/src/types/arrayBased/typesTuple.ts +16 -25
- package/src/types/createCachedTypeCheck.ts +509 -94
- package/src/types/index.ts +1 -0
- package/src/types/objectBased/typesDataModelData.ts +7 -8
- package/src/types/objectBased/typesObject.ts +11 -15
- package/src/types/objectBased/typesRecord.ts +12 -19
- package/src/types/schemas.ts +59 -71
- package/src/types/tProp.ts +29 -29
- package/src/types/utility/typeToStoredData.ts +22 -21
- package/src/types/utility/typesCodecCore.ts +5 -0
- package/src/types/utility/typesCodecSupport.ts +3 -3
- package/src/types/utility/typesMap.ts +43 -51
- package/src/types/utility/typesRefinement.ts +7 -8
- package/src/types/utility/typesSkipCheck.ts +7 -8
- package/src/types/utility/typesTag.ts +7 -8
- package/src/utils/ModelPool.ts +76 -37
- package/src/utils/chunks.ts +271 -0
- package/src/utils/decorators.ts +5 -5
- package/src/utils/index.ts +38 -20
- package/src/utils/types.ts +5 -0
- package/src/wrappers/ArraySet.ts +27 -14
- package/src/wrappers/ObjectMap.ts +1 -6
- package/src/wrappers/asMap.ts +1 -21
- package/src/wrappers/asSet.ts +0 -8
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
|
/**
|
|
@@ -119,16 +127,6 @@ export function fastGetParent<T extends object = any>(
|
|
|
119
127
|
return fastGetParentPath(value, useAtom)?.parent
|
|
120
128
|
}
|
|
121
129
|
|
|
122
|
-
/**
|
|
123
|
-
* @internal
|
|
124
|
-
*/
|
|
125
|
-
export function fastGetParentIncludingDataObjects<T extends object = any>(
|
|
126
|
-
value: object,
|
|
127
|
-
useAtom: boolean
|
|
128
|
-
): T | undefined {
|
|
129
|
-
return fastGetParentPathIncludingDataObjects(value, useAtom)?.parent
|
|
130
|
-
}
|
|
131
|
-
|
|
132
130
|
/**
|
|
133
131
|
* Returns if a given object is a model interim data object (`$`).
|
|
134
132
|
*
|
package/src/parent/setParent.ts
CHANGED
|
@@ -17,9 +17,11 @@ import {
|
|
|
17
17
|
reportParentPathChanged,
|
|
18
18
|
setDataObjectParent,
|
|
19
19
|
} from "./core"
|
|
20
|
-
import { addObjectChild, removeObjectChild } from "./coreObjectChildren"
|
|
20
|
+
import { addObjectChild, hasObjectChildren, removeObjectChild } from "./coreObjectChildren"
|
|
21
21
|
import { fastGetParentPath, fastGetRoot, type ParentPath } from "./path"
|
|
22
22
|
|
|
23
|
+
const reindexChunkSize = 4096
|
|
24
|
+
|
|
23
25
|
/**
|
|
24
26
|
* Updates the unchanged tail of an array before a splice is applied. These
|
|
25
27
|
* children keep their parent, so neither attachment nor child-index work is
|
|
@@ -29,18 +31,24 @@ import { fastGetParentPath, fastGetRoot, type ParentPath } from "./path"
|
|
|
29
31
|
export const reindexArrayChildren = action(
|
|
30
32
|
"reindexArrayChildren",
|
|
31
33
|
(array: readonly unknown[], oldStart: number, newStart: number): void => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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)
|
|
40
51
|
}
|
|
41
|
-
// Replace the path object: callers may have retained the previous path.
|
|
42
|
-
metadata!.parentPath = { parent: array, path: j }
|
|
43
|
-
reportParentPathChanged(value as object)
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
)
|
|
@@ -123,11 +131,15 @@ export const setParent = action(
|
|
|
123
131
|
|
|
124
132
|
const postUntweaker = parentPath ? undefined : tryUntweak(value)
|
|
125
133
|
|
|
126
|
-
|
|
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))
|
|
127
139
|
|
|
128
140
|
let oldRoot: any
|
|
129
141
|
let oldRootStore: any
|
|
130
|
-
if (
|
|
142
|
+
if (checkRootStore) {
|
|
131
143
|
oldRoot = fastGetRoot(value, false)
|
|
132
144
|
oldRootStore = fastIsRootStoreNoAtom(oldRoot) ? oldRoot : undefined
|
|
133
145
|
}
|
|
@@ -144,7 +156,7 @@ export const setParent = action(
|
|
|
144
156
|
}
|
|
145
157
|
reportParentPathChanged(value)
|
|
146
158
|
|
|
147
|
-
if (
|
|
159
|
+
if (checkRootStore) {
|
|
148
160
|
const newRoot = fastGetRoot(value, false)
|
|
149
161
|
const newRootStore = fastIsRootStoreNoAtom(newRoot) ? newRoot : undefined
|
|
150
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
|
|
|
@@ -92,75 +91,3 @@ function walkTreeChildrenFirst<T = void>(
|
|
|
92
91
|
|
|
93
92
|
return undefined
|
|
94
93
|
}
|
|
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 children = getObjectChildren(target)
|
|
140
|
-
|
|
141
|
-
// With one child and no local value, the child's map can be reused.
|
|
142
|
-
if (rootVal === undefined && children.size === 1) {
|
|
143
|
-
return recurse(children.values().next().value!)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
for (const child of children) {
|
|
147
|
-
const childMap = recurse(child)
|
|
148
|
-
if (childMap) {
|
|
149
|
-
map ??= new Map()
|
|
150
|
-
for (const [key, value] of childMap) {
|
|
151
|
-
map.set(key, value)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// add it at the end so parent resolutions have higher
|
|
157
|
-
// priority than child ones
|
|
158
|
-
if (rootVal !== undefined) {
|
|
159
|
-
if (!map) {
|
|
160
|
-
map = new Map()
|
|
161
|
-
}
|
|
162
|
-
map.set(rootVal, target)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return map
|
|
166
|
-
}
|
|
@@ -6,8 +6,8 @@ import { modelToDataNode } from "../parent/core"
|
|
|
6
6
|
import type { Patch } from "../patch/Patch"
|
|
7
7
|
import { internalApplySnapshot } from "../snapshot/applySnapshot"
|
|
8
8
|
import { reconcileSnapshot } from "../snapshot/reconcileSnapshot"
|
|
9
|
+
import { withChangeBatch } from "../tweaker/changeRollback"
|
|
9
10
|
import { assertTweakedObject } from "../tweaker/core"
|
|
10
|
-
import { withTypeCheckingBatch } from "../tweaker/typeChecking"
|
|
11
11
|
import { failure, inDevMode, isArray, lazy } from "../utils"
|
|
12
12
|
import { runWithErrorDiagnosticsContext, withErrorPathSegments } from "../utils/errorDiagnostics"
|
|
13
13
|
import { ModelPool } from "../utils/ModelPool"
|
|
@@ -16,7 +16,8 @@ import { setIfDifferent } from "../utils/setIfDifferent"
|
|
|
16
16
|
/**
|
|
17
17
|
* Applies the given patches to the given target object.
|
|
18
18
|
* Automatic type checking validates the completed batch, including nested lists.
|
|
19
|
-
* A
|
|
19
|
+
* A batch that throws or is rejected is rolled back with compensating patches.
|
|
20
|
+
* Errors thrown by listeners do not roll it back.
|
|
20
21
|
*
|
|
21
22
|
* @param node Target object.
|
|
22
23
|
* @param patches List of patches to apply.
|
|
@@ -48,35 +49,39 @@ export function internalApplyPatches(
|
|
|
48
49
|
const obj = this
|
|
49
50
|
const modelPool = new ModelPool(obj)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
try {
|
|
53
|
+
withChangeBatch(() => {
|
|
54
|
+
if (reverse) {
|
|
55
|
+
let i = patches.length
|
|
56
|
+
while (i--) {
|
|
57
|
+
const p = patches[i]
|
|
58
|
+
if (isArray(p)) {
|
|
59
|
+
let j = p.length
|
|
60
|
+
while (j--) {
|
|
61
|
+
applySinglePatchWithPath(obj, p[j], modelPool)
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
applySinglePatchWithPath(obj, p as Patch, modelPool)
|
|
60
65
|
}
|
|
61
|
-
} else {
|
|
62
|
-
applySinglePatchWithPath(obj, p as Patch, modelPool)
|
|
63
66
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
} else {
|
|
68
|
+
const len = patches.length
|
|
69
|
+
for (let i = 0; i < len; i++) {
|
|
70
|
+
const p = patches[i]
|
|
71
|
+
if (isArray(p)) {
|
|
72
|
+
const len2 = p.length
|
|
73
|
+
for (let j = 0; j < len2; j++) {
|
|
74
|
+
applySinglePatchWithPath(obj, p[j], modelPool)
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
applySinglePatchWithPath(obj, p as Patch, modelPool)
|
|
73
78
|
}
|
|
74
|
-
} else {
|
|
75
|
-
applySinglePatchWithPath(obj, p as Patch, modelPool)
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
81
|
+
})
|
|
82
|
+
} finally {
|
|
83
|
+
modelPool.release()
|
|
84
|
+
}
|
|
80
85
|
})
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -108,36 +108,21 @@ export function internalPatchRecorder(
|
|
|
108
108
|
deep: false,
|
|
109
109
|
})
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
patches: p,
|
|
121
|
-
inversePatches: invP,
|
|
122
|
-
})
|
|
123
|
-
opts?.onPatches?.(p, invP)
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
true
|
|
127
|
-
)
|
|
128
|
-
} else {
|
|
129
|
-
onPatchesDisposer = internalOnGlobalPatches((target, p, invP) => {
|
|
130
|
-
if (recording && filter(p, invP)) {
|
|
131
|
-
events.push({
|
|
132
|
-
target,
|
|
133
|
-
patches: p,
|
|
134
|
-
inversePatches: invP,
|
|
135
|
-
})
|
|
136
|
-
opts?.onPatches?.(p, invP)
|
|
137
|
-
}
|
|
138
|
-
}, true)
|
|
111
|
+
const record = (target: object, p: Patch[], invP: Patch[]) => {
|
|
112
|
+
if (recording && filter(p, invP)) {
|
|
113
|
+
events.push({
|
|
114
|
+
target,
|
|
115
|
+
patches: p,
|
|
116
|
+
inversePatches: invP,
|
|
117
|
+
})
|
|
118
|
+
opts?.onPatches?.(p, invP)
|
|
119
|
+
}
|
|
139
120
|
}
|
|
140
121
|
|
|
122
|
+
const onPatchesDisposer: OnPatchesDisposer = subtreeRoot
|
|
123
|
+
? internalOnPatches(subtreeRoot, (p, invP) => record(subtreeRoot, p, invP), true)
|
|
124
|
+
: internalOnGlobalPatches(record, true)
|
|
125
|
+
|
|
141
126
|
return {
|
|
142
127
|
get recording() {
|
|
143
128
|
return recording
|
package/src/ref/core.ts
CHANGED
|
@@ -2,16 +2,14 @@ import { action, type ObservableSet, observable, reaction, when } from "mobx"
|
|
|
2
2
|
import { isModel } from "../model/utils"
|
|
3
3
|
import type { ModelClass } from "../modelShared/BaseModelShared"
|
|
4
4
|
import { model } from "../modelShared/modelDecorator"
|
|
5
|
-
import {
|
|
6
|
-
getDeepObjectChildren,
|
|
7
|
-
registerDeepObjectChildrenExtension,
|
|
8
|
-
} from "../parent/coreObjectChildren"
|
|
5
|
+
import { registerDeepObjectChildrenExtension } from "../parent/coreObjectChildren"
|
|
9
6
|
import { fastGetRoot } from "../parent/path"
|
|
10
|
-
import { type ComputedWalkTreeAggregate, computedWalkTreeAggregate } from "../parent/walkTree"
|
|
11
7
|
import { assertTweakedObject } from "../tweaker/core"
|
|
12
8
|
import { assertIsObject, failure, mobxAction } from "../utils"
|
|
13
9
|
import { getOrCreate } from "../utils/mapUtils"
|
|
10
|
+
import { modelRefIdNotIndexed, resolveModelRefId } from "./modelRefIdIndex"
|
|
14
11
|
import { Ref, type RefConstructor } from "./Ref"
|
|
12
|
+
import { resolveRefIdByIndex } from "./refIdIndex"
|
|
15
13
|
|
|
16
14
|
interface BackRefs<T extends object> {
|
|
17
15
|
all: ObservableSet<Ref<T>>
|
|
@@ -192,12 +190,6 @@ export function getModelRefId(target: object): string | undefined {
|
|
|
192
190
|
return undefined
|
|
193
191
|
}
|
|
194
192
|
|
|
195
|
-
// one computed id tree per id function
|
|
196
|
-
const computedIdTrees = new WeakMap<
|
|
197
|
-
(node: object) => string | undefined,
|
|
198
|
-
ComputedWalkTreeAggregate<string>
|
|
199
|
-
>()
|
|
200
|
-
|
|
201
193
|
/**
|
|
202
194
|
* Resolves a node given its ID.
|
|
203
195
|
*
|
|
@@ -212,38 +204,32 @@ export function resolveId<T extends object>(
|
|
|
212
204
|
id: string,
|
|
213
205
|
getId: RefIdResolver = getModelRefId
|
|
214
206
|
): T | undefined {
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
207
|
+
// parent resolutions have priority over child ones
|
|
208
|
+
if (getId(root) === id) {
|
|
209
|
+
return root as T
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (getId === getModelRefId) {
|
|
213
|
+
const found = resolveModelRefId(root, id)
|
|
214
|
+
if (found !== modelRefIdNotIndexed) {
|
|
215
|
+
return found as T | undefined
|
|
216
|
+
}
|
|
217
|
+
}
|
|
219
218
|
|
|
220
|
-
|
|
221
|
-
return
|
|
219
|
+
// custom ids (or models with a custom `getRefId`)
|
|
220
|
+
return resolveRefIdByIndex(root, id, getId) as T | undefined
|
|
222
221
|
}
|
|
223
222
|
|
|
223
|
+
const createRefSet = () => observable.set<Ref<any>>(undefined, { deep: false })
|
|
224
|
+
|
|
225
|
+
const createBackRefs = (): BackRefs<any> => ({ all: createRefSet(), byType: new WeakMap() })
|
|
226
|
+
|
|
224
227
|
function getBackRefs<T extends object>(
|
|
225
228
|
target: T,
|
|
226
229
|
refType?: RefConstructor<T>
|
|
227
230
|
): ObservableSet<Ref<T>> {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
backRefs = {
|
|
231
|
-
all: observable.set(undefined, { deep: false }),
|
|
232
|
-
byType: new WeakMap(),
|
|
233
|
-
}
|
|
234
|
-
objectBackRefs.set(target, backRefs)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (refType) {
|
|
238
|
-
let byType = backRefs.byType.get(refType)
|
|
239
|
-
if (!byType) {
|
|
240
|
-
byType = observable.set(undefined, { deep: false })
|
|
241
|
-
backRefs.byType.set(refType, byType)
|
|
242
|
-
}
|
|
243
|
-
return byType
|
|
244
|
-
} else {
|
|
245
|
-
return backRefs.all
|
|
246
|
-
}
|
|
231
|
+
const backRefs: BackRefs<T> = getOrCreate(objectBackRefs, target, createBackRefs)
|
|
232
|
+
return refType ? getOrCreate(backRefs.byType, refType, createRefSet) : backRefs.all
|
|
247
233
|
}
|
|
248
234
|
|
|
249
235
|
/**
|
|
@@ -285,14 +271,25 @@ export function getRefsResolvingTo<T extends object>(
|
|
|
285
271
|
oldBackRefs.forEach(updateRef)
|
|
286
272
|
}
|
|
287
273
|
|
|
288
|
-
const refsChildrenOfRoot = getDeepChildrenRefs(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
refs
|
|
274
|
+
const refsChildrenOfRoot = getDeepChildrenRefs.get(fastGetRoot(target, true))
|
|
275
|
+
if (shouldForceUpdateTrackedRefs) {
|
|
276
|
+
const refs = refType ? refsChildrenOfRoot.byType.get(refType.refClass) : refsChildrenOfRoot.all
|
|
277
|
+
refs?.forEach(updateRef)
|
|
292
278
|
} else {
|
|
293
|
-
refs
|
|
279
|
+
// tracked refs keep their back-references up to date by themselves
|
|
280
|
+
const untrackedRefs = refType
|
|
281
|
+
? refsChildrenOfRoot.untrackedByType.get(refType.refClass)
|
|
282
|
+
: refsChildrenOfRoot.untracked
|
|
283
|
+
if (untrackedRefs && untrackedRefs.size > 0) {
|
|
284
|
+
// copy, since updating back-references might change the tree
|
|
285
|
+
for (const ref of Array.from(untrackedRefs)) {
|
|
286
|
+
updateRef(ref)
|
|
287
|
+
if (trackedRefs.has(ref)) {
|
|
288
|
+
deleteRef(refsChildrenOfRoot.untracked, refsChildrenOfRoot.untrackedByType, ref)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
294
292
|
}
|
|
295
|
-
refs?.forEach(updateRef)
|
|
296
293
|
|
|
297
294
|
return getBackRefs(target, refType)
|
|
298
295
|
}
|
|
@@ -332,9 +329,24 @@ function isReactionDelayed() {
|
|
|
332
329
|
return reactionDelayed
|
|
333
330
|
}
|
|
334
331
|
|
|
332
|
+
type RefsByType = WeakMap<ModelClass<Ref<any>>, Set<Ref<any>>>
|
|
333
|
+
|
|
335
334
|
interface DeepChildrenRefs {
|
|
336
335
|
all: Set<Ref<any>>
|
|
337
|
-
byType:
|
|
336
|
+
byType: RefsByType
|
|
337
|
+
/** Refs whose back-references might not be tracked yet. */
|
|
338
|
+
untracked: Set<Ref<any>>
|
|
339
|
+
untrackedByType: RefsByType
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function addRef(all: Set<Ref<any>>, byType: RefsByType, ref: Ref<any>) {
|
|
343
|
+
all.add(ref)
|
|
344
|
+
getOrCreate(byType, ref.constructor as ModelClass<Ref<any>>, () => new Set()).add(ref)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function deleteRef(all: Set<Ref<any>>, byType: RefsByType, ref: Ref<any>) {
|
|
348
|
+
all.delete(ref)
|
|
349
|
+
byType.get(ref.constructor as ModelClass<Ref<any>>)?.delete(ref)
|
|
338
350
|
}
|
|
339
351
|
|
|
340
352
|
const getDeepChildrenRefs = registerDeepObjectChildrenExtension<DeepChildrenRefs>({
|
|
@@ -342,14 +354,25 @@ const getDeepChildrenRefs = registerDeepObjectChildrenExtension<DeepChildrenRefs
|
|
|
342
354
|
return {
|
|
343
355
|
all: new Set(),
|
|
344
356
|
byType: new WeakMap(),
|
|
357
|
+
untracked: new Set(),
|
|
358
|
+
untrackedByType: new WeakMap(),
|
|
345
359
|
}
|
|
346
360
|
},
|
|
347
361
|
|
|
348
362
|
addNode(node, data) {
|
|
349
363
|
if (node instanceof Ref) {
|
|
350
|
-
data.all.
|
|
351
|
-
|
|
352
|
-
|
|
364
|
+
addRef(data.all, data.byType, node)
|
|
365
|
+
if (!trackedRefs.has(node)) {
|
|
366
|
+
addRef(data.untracked, data.untrackedByType, node)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
removeNode(node, data) {
|
|
372
|
+
if (node instanceof Ref) {
|
|
373
|
+
deleteRef(data.all, data.byType, node)
|
|
374
|
+
deleteRef(data.untracked, data.untrackedByType, node)
|
|
353
375
|
}
|
|
376
|
+
return true
|
|
354
377
|
},
|
|
355
378
|
})
|