mobx-keystone 2.0.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 +22 -0
- package/dist/mobx-keystone.esm.js +2341 -780
- package/dist/mobx-keystone.esm.mjs +2341 -780
- package/dist/mobx-keystone.umd.js +2346 -782
- package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +17 -10
- 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/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/applySnapshot.d.ts +1 -0
- package/dist/types/snapshot/fromSnapshot.d.ts +0 -3
- package/dist/types/tweaker/changeRollback.d.ts +1 -0
- package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
- package/dist/types/utils/ModelPool.d.ts +7 -1
- package/dist/types/utils/chunks.d.ts +1 -0
- package/package.json +2 -2
- package/src/actionMiddlewares/transactionMiddleware.ts +40 -41
- package/src/actionMiddlewares/undoMiddleware.ts +47 -13
- package/src/model/BaseModel.ts +6 -4
- package/src/modelShared/modelDecorator.ts +5 -25
- package/src/parent/arrayPaths.ts +242 -0
- package/src/parent/coreObjectChildren.ts +300 -48
- package/src/parent/onChildAttachedTo.ts +130 -44
- package/src/parent/path.ts +10 -2
- 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/ref/core.ts +63 -27
- package/src/ref/modelRefIdIndex.ts +131 -0
- package/src/ref/nodesById.ts +60 -0
- package/src/ref/refIdIndex.ts +209 -0
- package/src/snapshot/applySnapshot.ts +26 -18
- package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
- package/src/snapshot/fromSnapshot.ts +2 -6
- package/src/snapshot/internal.ts +67 -10
- 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 +107 -27
- package/src/tweaker/tweakPlainObject.ts +56 -31
- package/src/tweaker/tweakedChangeListeners.ts +61 -0
- package/src/tweaker/typeChecking.ts +5 -104
- package/src/types/arrayBased/typesArray.ts +10 -11
- package/src/types/createCachedTypeCheck.ts +522 -94
- package/src/types/objectBased/typesRecord.ts +2 -5
- package/src/utils/ModelPool.ts +75 -19
- package/src/utils/chunks.ts +271 -0
- package/src/utils/index.ts +37 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { IArrayDidChange, IObjectDidChange } from "mobx"
|
|
2
|
+
|
|
3
|
+
type TweakedChange = IArrayDidChange | IObjectDidChange
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export interface TweakedChangeListener<C extends TweakedChange> {
|
|
9
|
+
applyChange(change: C): void
|
|
10
|
+
/**
|
|
11
|
+
* Called when the target is tweaked again after being untweaked, since the
|
|
12
|
+
* changes made in between were not received.
|
|
13
|
+
*/
|
|
14
|
+
resync(): void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const changeListeners = new WeakMap<object, TweakedChangeListener<any>[]>()
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Listens to the changes of a tweaked array or plain object, in order, before
|
|
21
|
+
* anything else reacts to them (including the type checks after the change).
|
|
22
|
+
* Unlike `observe`, changes made while reacting to a change (such as a
|
|
23
|
+
* type-check rollback) are received after it.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function addTweakedChangeListener<T extends object>(
|
|
28
|
+
target: T,
|
|
29
|
+
listener: TweakedChangeListener<T extends readonly unknown[] ? IArrayDidChange : IObjectDidChange>
|
|
30
|
+
): void {
|
|
31
|
+
let listeners = changeListeners.get(target)
|
|
32
|
+
if (!listeners) {
|
|
33
|
+
listeners = []
|
|
34
|
+
changeListeners.set(target, listeners)
|
|
35
|
+
}
|
|
36
|
+
listeners.push(listener)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export function notifyTweakedChangeListeners(change: TweakedChange): void {
|
|
43
|
+
const listeners = changeListeners.get(change.object)
|
|
44
|
+
if (listeners) {
|
|
45
|
+
for (const listener of listeners) {
|
|
46
|
+
listener.applyChange(change)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export function resyncTweakedChangeListeners(target: object): void {
|
|
55
|
+
const listeners = changeListeners.get(target)
|
|
56
|
+
if (listeners) {
|
|
57
|
+
for (const listener of listeners) {
|
|
58
|
+
listener.resync()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type IArrayDidChange, type IObjectDidChange, remove, set } from "mobx"
|
|
2
1
|
import { isModelAutoTypeCheckingEnabled } from "../globalConfig/globalConfig"
|
|
3
2
|
import type { AnyModel } from "../model/BaseModel"
|
|
4
3
|
import { getModelMetadata } from "../model/getModelMetadata"
|
|
@@ -8,8 +7,9 @@ import { fastGetParent } from "../parent/path"
|
|
|
8
7
|
import { internalApplyPatches } from "../patch/applyPatches"
|
|
9
8
|
import type { InternalPatchRecorder } from "../patch/emitPatch"
|
|
10
9
|
import { internalApplySnapshot } from "../snapshot/applySnapshot"
|
|
10
|
+
import { deferModelTypeCheckToBatch } from "./changeRollback"
|
|
11
11
|
import { runWithoutSnapshotOrPatches } from "./core"
|
|
12
|
-
import { isTypeCheckingAllowed
|
|
12
|
+
import { isTypeCheckingAllowed } from "./withoutTypeChecking"
|
|
13
13
|
|
|
14
14
|
// Re-entrancy guard: when true, we are inside a type-check rollback.
|
|
15
15
|
// During rollback, observe callbacks still fire, but we skip the auto
|
|
@@ -37,7 +37,7 @@ function isModelWithTypeChecker(obj: object): obj is AnyModel {
|
|
|
37
37
|
* Walks up parent chain from `obj`, calling `callback` for each typed model
|
|
38
38
|
* including the target itself (nearest-first / bottom-up). Stops if the callback throws.
|
|
39
39
|
*/
|
|
40
|
-
function forEachTypedModelAncestor(obj: object, callback: (model: AnyModel) => void): void {
|
|
40
|
+
export function forEachTypedModelAncestor(obj: object, callback: (model: AnyModel) => void): void {
|
|
41
41
|
// obj might be a $ data object, so resolve to the model if applicable
|
|
42
42
|
const start = dataToModelNode(obj)
|
|
43
43
|
|
|
@@ -100,110 +100,11 @@ export function runTypeCheckingAfterChange(
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
interface TypeCheckingBatch {
|
|
104
|
-
models: Set<AnyModel>
|
|
105
|
-
undo: (() => void)[]
|
|
106
|
-
recording: boolean
|
|
107
|
-
/** Last recorded change target, so a run of changes to it walks ancestors once. */
|
|
108
|
-
previousTarget?: object
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let typeCheckingBatch: TypeCheckingBatch | undefined
|
|
112
|
-
|
|
113
103
|
/** @internal Called only when automatic type checking is enabled. */
|
|
114
104
|
export function typeCheckAfterCreation(model: AnyModel): void {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} else {
|
|
105
|
+
// a change batch validates it once it completes
|
|
106
|
+
if (!deferModelTypeCheckToBatch(model)) {
|
|
118
107
|
const error = model.typeCheck()
|
|
119
108
|
if (error) error.throw()
|
|
120
109
|
}
|
|
121
110
|
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* @internal
|
|
125
|
-
* Capture stored values before any listener can mutate them again. Restoring
|
|
126
|
-
* live values preserves identity and literal data, without snapshot processors
|
|
127
|
-
* or model defaults changing the state being restored.
|
|
128
|
-
*/
|
|
129
|
-
export function recordTypeCheckingBatchChange(change: IObjectDidChange | IArrayDidChange): void {
|
|
130
|
-
const batch = typeCheckingBatch
|
|
131
|
-
if (!batch?.recording) return
|
|
132
|
-
if (
|
|
133
|
-
change.type === "splice" &&
|
|
134
|
-
change.addedCount === change.removedCount &&
|
|
135
|
-
change.added.every((value, index) => value === change.removed[index])
|
|
136
|
-
)
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
const target = change.object
|
|
140
|
-
if (target !== batch.previousTarget) {
|
|
141
|
-
forEachTypedModelAncestor(target, (model) => batch.models.add(model))
|
|
142
|
-
batch.previousTarget = target
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (change.type === "splice") {
|
|
146
|
-
const { object, index, addedCount } = change
|
|
147
|
-
const removed = change.removed.slice()
|
|
148
|
-
batch.undo.push(() => object.spliceWithArray(index, addedCount, removed))
|
|
149
|
-
} else if ("index" in change) {
|
|
150
|
-
const { object, index, oldValue } = change
|
|
151
|
-
batch.undo.push(() => {
|
|
152
|
-
object[index] = oldValue
|
|
153
|
-
})
|
|
154
|
-
} else {
|
|
155
|
-
const { object } = change
|
|
156
|
-
const key = change.name as string
|
|
157
|
-
if (change.type === "add") {
|
|
158
|
-
batch.undo.push(() => remove(object, key))
|
|
159
|
-
} else {
|
|
160
|
-
const { oldValue } = change
|
|
161
|
-
batch.undo.push(() => set(object, key, oldValue))
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* @internal
|
|
168
|
-
* Nested calls and synchronous listener edits join the same validation batch.
|
|
169
|
-
* Rollback publishes compensating changes, just like snapshot reconciliation.
|
|
170
|
-
*/
|
|
171
|
-
export function withTypeCheckingBatch(fn: () => void): void {
|
|
172
|
-
if (typeCheckingBatch || !isTypeCheckingAfterChangeEnabled()) {
|
|
173
|
-
fn()
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const batch: TypeCheckingBatch = { models: new Set(), undo: [], recording: true }
|
|
178
|
-
typeCheckingBatch = batch
|
|
179
|
-
let validating = false
|
|
180
|
-
try {
|
|
181
|
-
withoutTypeChecking(fn)
|
|
182
|
-
// MobX has now invalidated dependencies, including added/removed keys.
|
|
183
|
-
validating = true
|
|
184
|
-
for (const model of batch.models) {
|
|
185
|
-
const error = model.typeCheck()
|
|
186
|
-
if (error) error.throw()
|
|
187
|
-
}
|
|
188
|
-
} catch (error) {
|
|
189
|
-
// Only a rejected validation rolls back. Errors thrown by the batch body
|
|
190
|
-
// itself (an invalid patch, a throwing listener) keep the same partial
|
|
191
|
-
// application they produce while automatic type checking is disabled.
|
|
192
|
-
if (validating) {
|
|
193
|
-
batch.recording = false
|
|
194
|
-
withoutTypeChecking(() => {
|
|
195
|
-
for (let i = batch.undo.length - 1; i >= 0; i--) {
|
|
196
|
-
try {
|
|
197
|
-
batch.undo[i]()
|
|
198
|
-
} catch {
|
|
199
|
-
// Keep restoring after a rollback listener throws. The original
|
|
200
|
-
// failure takes precedence over errors from compensating changes.
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
})
|
|
204
|
-
}
|
|
205
|
-
throw error
|
|
206
|
-
} finally {
|
|
207
|
-
typeCheckingBatch = undefined
|
|
208
|
-
}
|
|
209
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Path } from "../../parent/pathTypes"
|
|
2
2
|
import { isArray } from "../../utils"
|
|
3
3
|
import { withErrorPathSegment } from "../../utils/errorDiagnostics"
|
|
4
|
-
import {
|
|
4
|
+
import { createChunkedArrayCachedCheck } from "../createCachedTypeCheck"
|
|
5
5
|
import { getTypeInfo } from "../getTypeInfo"
|
|
6
6
|
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
7
7
|
import type { AnyStandardType, AnyType, ArrayType } from "../schemas"
|
|
@@ -38,18 +38,17 @@ export function typesArray<T extends AnyType>(itemType: T): ArrayType<T[]> {
|
|
|
38
38
|
const getTypeName = (...recursiveTypeCheckers: TypeChecker[]) =>
|
|
39
39
|
`Array<${itemChecker.getTypeName(...recursiveTypeCheckers, itemChecker)}>`
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return prependPathElementToTypeCheckError(error, path, index, typeCheckedValue)
|
|
41
|
+
const checkArrayItems = createChunkedArrayCachedCheck(
|
|
42
|
+
(items, firstIndex, path, typeCheckedValue) => {
|
|
43
|
+
for (let i = 0; i < items.length; i++) {
|
|
44
|
+
const error = itemChecker.check(items[i], emptyChildPath, typeCheckedValue)
|
|
45
|
+
if (error) {
|
|
46
|
+
return prependPathElementToTypeCheckError(error, path, firstIndex + i, typeCheckedValue)
|
|
47
|
+
}
|
|
49
48
|
}
|
|
49
|
+
return null
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
})
|
|
51
|
+
)
|
|
53
52
|
|
|
54
53
|
const thisTc: TypeChecker = new TypeChecker(
|
|
55
54
|
TypeCheckerBaseType.Array,
|