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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HookAction } from "../action/hookActions"
|
|
2
2
|
import { wrapModelMethodInActionIfNeeded } from "../action/wrapInAction"
|
|
3
3
|
import type { AnyDataModel } from "../dataModel/BaseDataModel"
|
|
4
|
+
import { dataModelInstanceCache } from "../dataModel/dataModelInstanceCache"
|
|
4
5
|
import { isDataModelClass } from "../dataModel/utils"
|
|
5
6
|
import { enterInitPhase, exitInitPhase } from "../deepChange/onDeepChange"
|
|
6
7
|
import { getGlobalConfig } from "../globalConfig"
|
|
@@ -41,22 +42,19 @@ export const model =
|
|
|
41
42
|
): MC =>
|
|
42
43
|
internalModel(name, clazz) as any
|
|
43
44
|
|
|
45
|
+
interface AfterClassInitializationData {
|
|
46
|
+
needsMakeObservable: boolean | undefined
|
|
47
|
+
type: "class" | "data"
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
const afterClassInitializationData = new WeakMap<
|
|
45
51
|
ModelClass<AnyModel | AnyDataModel>,
|
|
46
|
-
|
|
47
|
-
needsMakeObservable: boolean | undefined
|
|
48
|
-
type: "class" | "data"
|
|
49
|
-
}
|
|
52
|
+
AfterClassInitializationData
|
|
50
53
|
>()
|
|
51
54
|
|
|
52
|
-
const runAfterClassInitialization = (
|
|
53
|
-
target: ModelClass<AnyModel | AnyDataModel>,
|
|
54
|
-
instance: any
|
|
55
|
-
) => {
|
|
55
|
+
const runAfterClassInitialization = (tag: AfterClassInitializationData, instance: any) => {
|
|
56
56
|
runLateInitializationFunctions(instance, runAfterNewSymbol)
|
|
57
57
|
|
|
58
|
-
const tag = afterClassInitializationData.get(target)!
|
|
59
|
-
|
|
60
58
|
// compatibility with mobx 6 and 7
|
|
61
59
|
if (tag.needsMakeObservable) {
|
|
62
60
|
// we know it can be done and shouldn't fail
|
|
@@ -112,11 +110,33 @@ const runAfterClassInitialization = (
|
|
|
112
110
|
|
|
113
111
|
const proxyClassHandler: ProxyHandler<ModelClass<AnyModel | AnyDataModel>> = {
|
|
114
112
|
construct(clazz, args) {
|
|
115
|
-
const
|
|
113
|
+
const tag = afterClassInitializationData.get(clazz)!
|
|
116
114
|
|
|
117
|
-
|
|
115
|
+
if (tag.type !== "data") {
|
|
116
|
+
const instance = new (clazz as any)(...args)
|
|
117
|
+
runAfterClassInitialization(tag, instance)
|
|
118
|
+
return instance
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Data models are cached by their backing data, so constructing one over data that
|
|
122
|
+
// already has an instance must not rerun its class fields nor its lazy initializer.
|
|
123
|
+
const modelClass = args[1]?.modelClass ?? modelInfoByClass.get(clazz)!.class
|
|
124
|
+
const cached = dataModelInstanceCache.get(modelClass)?.get(args[0])
|
|
125
|
+
if (cached) {
|
|
126
|
+
return cached
|
|
127
|
+
}
|
|
118
128
|
|
|
119
|
-
|
|
129
|
+
let instance: any
|
|
130
|
+
try {
|
|
131
|
+
instance = new (clazz as any)(...args)
|
|
132
|
+
runAfterClassInitialization(tag, instance)
|
|
133
|
+
return instance
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// The base constructor cleans up after itself, but class fields and late
|
|
136
|
+
// initialization run after it returns, so discard those instances too.
|
|
137
|
+
dataModelInstanceCache.get(modelClass)?.delete(instance ? instance.$ : args[0])
|
|
138
|
+
throw error
|
|
139
|
+
}
|
|
120
140
|
},
|
|
121
141
|
}
|
|
122
142
|
|
|
@@ -245,7 +265,7 @@ export function decoratedModel<M, MC extends abstract new (...ags: any) => M>(
|
|
|
245
265
|
})
|
|
246
266
|
}
|
|
247
267
|
|
|
248
|
-
return (name ? model(name)(clazz as unknown as ModelClass<AnyModel>) : clazz) as MC
|
|
268
|
+
return (name !== undefined ? model(name)(clazz as unknown as ModelClass<AnyModel>) : clazz) as MC
|
|
249
269
|
}
|
|
250
270
|
|
|
251
271
|
type MobxAnnotation = {
|
package/src/modelShared/prop.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SnapshotInOf, SnapshotOutOf } from "../snapshot/SnapshotOf"
|
|
2
2
|
import type { AnyStandardType } from "../types/schemas"
|
|
3
|
-
import { lazy } from "../utils"
|
|
3
|
+
import { isEqualOrBothNaN, lazy } from "../utils"
|
|
4
4
|
import { runWithErrorDiagnosticsContext, withErrorPathSegment } from "../utils/errorDiagnostics"
|
|
5
5
|
import { getOrCreate } from "../utils/mapUtils"
|
|
6
6
|
import type { Flatten, IsNeverType, IsOptionalValue } from "../utils/types"
|
|
@@ -618,11 +618,6 @@ function parseSetterConfig(
|
|
|
618
618
|
|
|
619
619
|
const propCache = new Map<unknown, AnyModelProp>()
|
|
620
620
|
|
|
621
|
-
let cacheTransformResult = false
|
|
622
|
-
const cacheTransformedValueFn = () => {
|
|
623
|
-
cacheTransformResult = true
|
|
624
|
-
}
|
|
625
|
-
|
|
626
621
|
/** @internal */
|
|
627
622
|
export function toFullModelPropTransform(
|
|
628
623
|
transformObject: ModelPropTransform<unknown, unknown>
|
|
@@ -651,7 +646,7 @@ export function toFullModelPropTransform(
|
|
|
651
646
|
const modelCache = getOrCreate(cache, model, () => new Map())
|
|
652
647
|
|
|
653
648
|
let propCache = modelCache.get(propName)
|
|
654
|
-
if (propCache
|
|
649
|
+
if (!propCache || !isEqualOrBothNaN(propCache.originalValue, originalValue)) {
|
|
655
650
|
// original changed, invalidate cache
|
|
656
651
|
modelCache.delete(propName)
|
|
657
652
|
propCache = undefined
|
|
@@ -674,10 +669,12 @@ export function toFullModelPropTransform(
|
|
|
674
669
|
untransform(transformedValue: unknown, model: object, propName: PropertyKey) {
|
|
675
670
|
const modelCache = getOrCreate(cache, model, () => new Map())
|
|
676
671
|
|
|
677
|
-
cacheTransformResult = false
|
|
672
|
+
let cacheTransformResult = false
|
|
678
673
|
const originalValue = untransform({
|
|
679
674
|
transformedValue,
|
|
680
|
-
cacheTransformedValue:
|
|
675
|
+
cacheTransformedValue: () => {
|
|
676
|
+
cacheTransformResult = true
|
|
677
|
+
},
|
|
681
678
|
})
|
|
682
679
|
if (cacheTransformResult) {
|
|
683
680
|
modelCache.set(propName, { originalValue, transformedValue })
|
|
@@ -19,7 +19,14 @@ import type { AnyType } from "../types/schemas"
|
|
|
19
19
|
import { tProp } from "../types/tProp"
|
|
20
20
|
import { resolveStoredType } from "../types/utility/typesCodec"
|
|
21
21
|
import { typesUnchecked } from "../types/utility/typesUnchecked"
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
addHiddenProp,
|
|
24
|
+
assertIsObject,
|
|
25
|
+
failure,
|
|
26
|
+
hasOwnProp,
|
|
27
|
+
propNameToSetterName,
|
|
28
|
+
setProtoProp,
|
|
29
|
+
} from "../utils"
|
|
23
30
|
import { chainFns } from "../utils/chainFns"
|
|
24
31
|
import { withErrorPathSegment } from "../utils/errorDiagnostics"
|
|
25
32
|
import { type ModelClass, modelInitializedSymbol } from "./BaseModelShared"
|
|
@@ -275,7 +282,9 @@ export function sharedInternalModel<
|
|
|
275
282
|
dataTypeChecker = typesObject(() => {
|
|
276
283
|
const typeCheckerObj: Record<string, AnyType> = {}
|
|
277
284
|
for (const [k, mp] of Object.entries(composedModelProps)) {
|
|
278
|
-
|
|
285
|
+
const typeChecker = mp._typeChecker ? resolveStoredType(mp._typeChecker) : typesUnchecked()
|
|
286
|
+
if (k === "__proto__") setProtoProp(typeCheckerObj, typeChecker)
|
|
287
|
+
else typeCheckerObj[k] = typeChecker
|
|
279
288
|
}
|
|
280
289
|
return typeCheckerObj
|
|
281
290
|
})
|
|
@@ -451,7 +460,11 @@ function getModelPropsSnapshotProcessor(
|
|
|
451
460
|
return (sn) => {
|
|
452
461
|
const newSn = { ...sn }
|
|
453
462
|
for (const [propName, processor] of propsWithProcessor) {
|
|
454
|
-
|
|
463
|
+
const value = withErrorPathSegment(propName, () =>
|
|
464
|
+
processor(hasOwnProp(sn, propName) ? sn[propName] : undefined)
|
|
465
|
+
)
|
|
466
|
+
if (propName === "__proto__") setProtoProp(newSn, value)
|
|
467
|
+
else newSn[propName] = value
|
|
455
468
|
}
|
|
456
469
|
return newSn
|
|
457
470
|
}
|
package/src/parent/detach.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { fastGetParentPathIncludingDataObjects } from "./path"
|
|
|
10
10
|
* Detaches a given object from a tree.
|
|
11
11
|
* If the parent is an object / model, detaching will delete the property.
|
|
12
12
|
* If the parent is an array detaching will remove the node by splicing it.
|
|
13
|
-
* If there's no parent it
|
|
13
|
+
* If there's no parent it does nothing.
|
|
14
14
|
*
|
|
15
15
|
* @param node Object to be detached.
|
|
16
16
|
*/
|
|
@@ -8,7 +8,7 @@ import { getChildrenObjects } from "./getChildrenObjects"
|
|
|
8
8
|
* @param predicate Function that will be run for every child of the root object.
|
|
9
9
|
* @param [options] An optional object with the `deep` option (defaults to `false`) set to `true` to
|
|
10
10
|
* get the children deeply or `false` to get them shallowly.
|
|
11
|
-
* @returns A readonly
|
|
11
|
+
* @returns A readonly set with the matching children.
|
|
12
12
|
*/
|
|
13
13
|
export function findChildren<T extends object = any>(
|
|
14
14
|
root: object,
|
|
@@ -21,13 +21,10 @@ export function findChildren<T extends object = any>(
|
|
|
21
21
|
|
|
22
22
|
const set = new Set<any>()
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (predicate(cur.value)) {
|
|
28
|
-
set.add(cur.value)
|
|
24
|
+
for (const child of children) {
|
|
25
|
+
if (predicate(child)) {
|
|
26
|
+
set.add(child)
|
|
29
27
|
}
|
|
30
|
-
cur = iter.next()
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
return set
|
package/src/parent/findParent.ts
CHANGED
|
@@ -19,8 +19,7 @@ export function findParent<T extends object = any>(
|
|
|
19
19
|
predicate: (parentNode: object) => boolean,
|
|
20
20
|
maxDepth = 0
|
|
21
21
|
): T | undefined {
|
|
22
|
-
|
|
23
|
-
return foundParentPath ? foundParentPath.parent : undefined
|
|
22
|
+
return findParentInternal<T>(child, predicate, maxDepth)
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
/**
|
|
@@ -56,22 +55,28 @@ export function findParentPath<T extends object = any>(
|
|
|
56
55
|
predicate: (parentNode: object) => boolean,
|
|
57
56
|
maxDepth = 0
|
|
58
57
|
): FoundParentPath<T> | undefined {
|
|
59
|
-
assertTweakedObject(child, "child")
|
|
60
|
-
|
|
61
58
|
const path: WritablePath = []
|
|
59
|
+
const parent = findParentInternal<T>(child, predicate, maxDepth, path)
|
|
60
|
+
return parent ? { parent, path: path.reverse() } : undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function findParentInternal<T extends object>(
|
|
64
|
+
child: object,
|
|
65
|
+
predicate: (parentNode: object) => boolean,
|
|
66
|
+
maxDepth: number,
|
|
67
|
+
path?: WritablePath
|
|
68
|
+
): T | undefined {
|
|
69
|
+
assertTweakedObject(child, "child")
|
|
62
70
|
|
|
63
71
|
let current: any = child
|
|
64
72
|
let depth = 0
|
|
65
73
|
|
|
66
74
|
let parentPath: ParentPath<any> | undefined
|
|
67
75
|
while ((parentPath = fastGetParentPath(current, true))) {
|
|
68
|
-
path
|
|
76
|
+
path?.push(parentPath.path)
|
|
69
77
|
current = parentPath.parent
|
|
70
78
|
if (predicate(current)) {
|
|
71
|
-
return
|
|
72
|
-
parent: current,
|
|
73
|
-
path,
|
|
74
|
-
}
|
|
79
|
+
return current
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
depth++
|
|
@@ -32,11 +32,10 @@ export function onChildAttachedTo(
|
|
|
32
32
|
assertIsFunction(target, "target")
|
|
33
33
|
assertIsFunction(fn, "fn")
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
35
|
+
const { deep = false, fireForCurrentChildren = true } = options ?? {}
|
|
36
|
+
|
|
37
|
+
let disposed = false
|
|
38
|
+
let runCleanupAfterDispose = false
|
|
40
39
|
|
|
41
40
|
const detachDisposers = new WeakMap<object, () => void>()
|
|
42
41
|
|
|
@@ -48,32 +47,38 @@ export function onChildAttachedTo(
|
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
|
|
50
|
+
const runDetachDisposers = (nodes: object[]) => {
|
|
51
|
+
let firstError: { value: unknown } | undefined
|
|
52
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
53
|
+
try {
|
|
54
|
+
runDetachDisposer(nodes[i])
|
|
55
|
+
} catch (value) {
|
|
56
|
+
firstError ??= { value }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return firstError
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
const addDetachDisposer = (n: object, disposer: (() => void) | void) => {
|
|
52
63
|
if (disposer) {
|
|
53
|
-
|
|
64
|
+
const detach = action(disposer)
|
|
65
|
+
if (disposed) {
|
|
66
|
+
if (runCleanupAfterDispose) detach()
|
|
67
|
+
} else {
|
|
68
|
+
detachDisposers.set(n, detach)
|
|
69
|
+
}
|
|
54
70
|
}
|
|
55
71
|
}
|
|
56
72
|
|
|
57
|
-
const getChildrenObjectOpts = { deep
|
|
73
|
+
const getChildrenObjectOpts = { deep }
|
|
58
74
|
const getCurrentChildren = () => {
|
|
59
75
|
const t = target()
|
|
60
76
|
assertTweakedObject(t, "target()")
|
|
61
77
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const set = new Set<object>()
|
|
65
|
-
|
|
66
|
-
const iter = children.values()
|
|
67
|
-
let cur = iter.next()
|
|
68
|
-
while (!cur.done) {
|
|
69
|
-
set.add(cur.value)
|
|
70
|
-
cur = iter.next()
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return set
|
|
78
|
+
return new Set(getChildrenObjects(t, getChildrenObjectOpts))
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
const currentChildren =
|
|
81
|
+
const currentChildren = fireForCurrentChildren ? new Set<object>() : getCurrentChildren()
|
|
77
82
|
|
|
78
83
|
const disposer = reaction(
|
|
79
84
|
() => getCurrentChildren(),
|
|
@@ -95,45 +100,45 @@ export function onChildAttachedTo(
|
|
|
95
100
|
currentChildrenCur = currentChildrenIter.next()
|
|
96
101
|
}
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
for (let i = disposersToRun.length - 1; i >= 0; i--) {
|
|
100
|
-
runDetachDisposer(disposersToRun[i])
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
+
let firstError = runDetachDisposers(disposersToRun)
|
|
103
104
|
|
|
104
105
|
// find new
|
|
105
106
|
const newChildrenIter = newChildren.values()
|
|
106
107
|
let newChildrenCur = newChildrenIter.next()
|
|
107
|
-
while (!newChildrenCur.done) {
|
|
108
|
+
while (!disposed && !newChildrenCur.done) {
|
|
108
109
|
const n = newChildrenCur.value
|
|
109
110
|
if (!currentChildren.has(n)) {
|
|
110
111
|
currentChildren.add(n)
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
try {
|
|
114
|
+
const detachAction = runInAction(() => fn(n))
|
|
115
|
+
addDetachDisposer(n, detachAction)
|
|
116
|
+
} catch (value) {
|
|
117
|
+
firstError ??= { value }
|
|
118
|
+
}
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
newChildrenCur = newChildrenIter.next()
|
|
117
122
|
}
|
|
123
|
+
if (firstError) {
|
|
124
|
+
throw firstError.value
|
|
125
|
+
}
|
|
118
126
|
},
|
|
119
127
|
{
|
|
120
128
|
fireImmediately: true,
|
|
121
129
|
}
|
|
122
130
|
)
|
|
123
131
|
|
|
124
|
-
return (
|
|
132
|
+
return (runPendingDetachDisposers: boolean) => {
|
|
133
|
+
if (disposed) return
|
|
134
|
+
disposed = true
|
|
135
|
+
runCleanupAfterDispose = runPendingDetachDisposers
|
|
125
136
|
disposer()
|
|
126
|
-
|
|
127
|
-
if (runDetachDisposers) {
|
|
128
|
-
const currentChildrenIter = currentChildren.values()
|
|
129
|
-
let currentChildrenCur = currentChildrenIter.next()
|
|
130
|
-
while (!currentChildrenCur.done) {
|
|
131
|
-
const n = currentChildrenCur.value
|
|
132
|
-
runDetachDisposer(n)
|
|
133
|
-
|
|
134
|
-
currentChildrenCur = currentChildrenIter.next()
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
+
const pendingChildren = runPendingDetachDisposers ? Array.from(currentChildren) : []
|
|
137
138
|
currentChildren.clear()
|
|
139
|
+
const firstError = runDetachDisposers(pendingChildren)
|
|
140
|
+
if (firstError) {
|
|
141
|
+
throw firstError.value
|
|
142
|
+
}
|
|
138
143
|
}
|
|
139
144
|
}
|
package/src/parent/path.ts
CHANGED
|
@@ -175,10 +175,12 @@ export function fastGetRootPath<T extends object = any>(
|
|
|
175
175
|
let parentPath: ParentPath<any> | undefined
|
|
176
176
|
while ((parentPath = fastGetParentPath(root, useAtom))) {
|
|
177
177
|
root = parentPath.parent
|
|
178
|
-
path.
|
|
179
|
-
pathObjects.
|
|
178
|
+
path.push(parentPath.path)
|
|
179
|
+
pathObjects.push(parentPath.parent)
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
path.reverse()
|
|
183
|
+
pathObjects.reverse()
|
|
182
184
|
return { root, path, pathObjects } as RootPath<any>
|
|
183
185
|
}
|
|
184
186
|
|
|
@@ -265,11 +267,13 @@ export function resolvePath<T = any>(
|
|
|
265
267
|
const dataNode = modelToDataNode(current)
|
|
266
268
|
if (p in dataNode) {
|
|
267
269
|
current = dataNode
|
|
268
|
-
} else if (!(p in current)) {
|
|
269
|
-
return unresolved
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
if (!(p in current)) {
|
|
274
|
+
return unresolved
|
|
275
|
+
}
|
|
276
|
+
|
|
273
277
|
current = current[p]
|
|
274
278
|
}
|
|
275
279
|
|
|
@@ -321,7 +325,7 @@ export function resolvePathCheckingIds<T = any>(
|
|
|
321
325
|
|
|
322
326
|
// check just to avoid mobx warnings about trying to access out of bounds index
|
|
323
327
|
if (isArray(current)) {
|
|
324
|
-
if (+p >= current.length) {
|
|
328
|
+
if (+p >= current.length || !(p in current)) {
|
|
325
329
|
return { resolved: false }
|
|
326
330
|
}
|
|
327
331
|
} else if (!hasOwnProp(current, p)) {
|
|
@@ -376,11 +380,11 @@ export function fastGetParentToChildPath(
|
|
|
376
380
|
let current = toChild
|
|
377
381
|
let parentPath: ParentPath<any> | undefined
|
|
378
382
|
while ((parentPath = fastGetParentPath(current, useAtom))) {
|
|
379
|
-
path.
|
|
383
|
+
path.push(parentPath.path)
|
|
380
384
|
|
|
381
385
|
current = parentPath.parent
|
|
382
386
|
if (current === fromParent) {
|
|
383
|
-
return path
|
|
387
|
+
return path.reverse()
|
|
384
388
|
}
|
|
385
389
|
}
|
|
386
390
|
return undefined
|
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"
|
|
@@ -19,6 +20,31 @@ import {
|
|
|
19
20
|
import { addObjectChild, removeObjectChild } from "./coreObjectChildren"
|
|
20
21
|
import { fastGetParentPath, fastGetRoot, type ParentPath } from "./path"
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Updates the unchanged tail of an array before a splice is applied. These
|
|
25
|
+
* children keep their parent, so neither attachment nor child-index work is
|
|
26
|
+
* needed. One action batches path notifications for the entire tail.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export const reindexArrayChildren = action(
|
|
30
|
+
"reindexArrayChildren",
|
|
31
|
+
(array: readonly unknown[], oldStart: number, newStart: number): void => {
|
|
32
|
+
for (let i = oldStart, j = newStart; i < array.length; i++, j++) {
|
|
33
|
+
const value = array[i]
|
|
34
|
+
if (isPrimitive(value)) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
const metadata = treeNodeMetadata.get(value as object)
|
|
38
|
+
if (inDevMode && metadata?.parentPath?.parent !== array) {
|
|
39
|
+
throw failure("assertion failed: reindexed child must already belong to the array")
|
|
40
|
+
}
|
|
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
|
+
}
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
22
48
|
/**
|
|
23
49
|
* @internal
|
|
24
50
|
*/
|
|
@@ -27,7 +53,6 @@ export const setParent = action(
|
|
|
27
53
|
(
|
|
28
54
|
value: any,
|
|
29
55
|
parentPath: ParentPath<any> | undefined,
|
|
30
|
-
indexChangeAllowed: boolean,
|
|
31
56
|
isDataObject: boolean,
|
|
32
57
|
cloneIfApplicable: boolean
|
|
33
58
|
): any => {
|
|
@@ -36,11 +61,6 @@ export const setParent = action(
|
|
|
36
61
|
}
|
|
37
62
|
|
|
38
63
|
if (inDevMode) {
|
|
39
|
-
if (indexChangeAllowed && cloneIfApplicable) {
|
|
40
|
-
throw failure(
|
|
41
|
-
"assertion failed: 'indexChangeAllowed' and 'cloneIfApplicable' cannot be set at the same time"
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
64
|
if (typeof value === "function" || typeof value === "symbol") {
|
|
45
65
|
throw failure(`assertion failed: value cannot be a function or a symbol`)
|
|
46
66
|
}
|
|
@@ -67,6 +87,12 @@ export const setParent = action(
|
|
|
67
87
|
return value
|
|
68
88
|
}
|
|
69
89
|
|
|
90
|
+
// The new parent captures this node's snapshot during attachment. Flush
|
|
91
|
+
// descendant edits made while detached before that snapshot is copied.
|
|
92
|
+
if (parentPath && !oldParentPath) {
|
|
93
|
+
flushInternalSnapshot(value, false)
|
|
94
|
+
}
|
|
95
|
+
|
|
70
96
|
// make sure the new parent actually points to models when we give model data objs
|
|
71
97
|
if (parentPath) {
|
|
72
98
|
const actualParent = dataToModelNode(parentPath.parent)
|
|
@@ -92,14 +118,7 @@ export const setParent = action(
|
|
|
92
118
|
}
|
|
93
119
|
|
|
94
120
|
if (oldParentPath && parentPath) {
|
|
95
|
-
|
|
96
|
-
// just changing the index
|
|
97
|
-
treeNodeMetadata.get(value)!.parentPath = parentPath
|
|
98
|
-
reportParentPathChanged(value)
|
|
99
|
-
return value
|
|
100
|
-
} else {
|
|
101
|
-
throw failure("an object cannot be assigned a new parent when it already has one")
|
|
102
|
-
}
|
|
121
|
+
throw failure("an object cannot be assigned a new parent when it already has one")
|
|
103
122
|
}
|
|
104
123
|
|
|
105
124
|
const postUntweaker = parentPath ? undefined : tryUntweak(value)
|
package/src/parent/walkTree.ts
CHANGED
|
@@ -75,19 +75,19 @@ function walkTreeChildrenFirst<T = void>(
|
|
|
75
75
|
root: object,
|
|
76
76
|
visit: (node: object) => T | undefined
|
|
77
77
|
): T | undefined {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
|
|
78
|
+
const stack = [{ node: root, children: getObjectChildren(root).values() }]
|
|
79
|
+
while (stack.length > 0) {
|
|
80
|
+
const frame = stack[stack.length - 1]
|
|
81
|
+
const child = frame.children.next()
|
|
82
|
+
if (!child.done) {
|
|
83
|
+
stack.push({ node: child.value, children: getObjectChildren(child.value).values() })
|
|
84
|
+
} else {
|
|
85
|
+
stack.pop()
|
|
86
|
+
const ret = visit(frame.node)
|
|
87
|
+
if (ret !== undefined) {
|
|
88
|
+
return ret
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
|
-
ch = childrenIter.next()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const ret = visit(root)
|
|
89
|
-
if (ret !== undefined) {
|
|
90
|
-
return ret
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
return undefined
|
|
@@ -136,37 +136,21 @@ function walkTreeAggregate<R>(
|
|
|
136
136
|
let map: Map<R, object> | undefined
|
|
137
137
|
const rootVal = visit(target)
|
|
138
138
|
|
|
139
|
-
const
|
|
140
|
-
const childrenIter = childrenMap.values()
|
|
141
|
-
let ch = childrenIter.next()
|
|
139
|
+
const children = getObjectChildren(target)
|
|
142
140
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return recurse(ch.value!)
|
|
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!)
|
|
147
144
|
}
|
|
148
145
|
|
|
149
|
-
|
|
150
|
-
const childMap = recurse(
|
|
151
|
-
|
|
146
|
+
for (const child of children) {
|
|
147
|
+
const childMap = recurse(child)
|
|
152
148
|
if (childMap) {
|
|
153
|
-
|
|
154
|
-
|
|
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()
|
|
149
|
+
map ??= new Map()
|
|
150
|
+
for (const [key, value] of childMap) {
|
|
151
|
+
map.set(key, value)
|
|
166
152
|
}
|
|
167
153
|
}
|
|
168
|
-
|
|
169
|
-
ch = childrenIter.next()
|
|
170
154
|
}
|
|
171
155
|
|
|
172
156
|
// add it at the end so parent resolutions have higher
|