mobx-keystone 1.22.0 → 1.23.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 +7 -0
- package/dist/mobx-keystone.esm.js +6702 -5797
- package/dist/mobx-keystone.esm.mjs +6702 -5797
- package/dist/mobx-keystone.umd.js +6718 -5798
- package/dist/types/action/runUnprotected.d.ts +8 -4
- package/dist/types/model/Model.d.ts +5 -0
- package/dist/types/modelShared/prop.d.ts +8 -4
- package/dist/types/snapshot/modelInitialData.d.ts +1 -0
- package/dist/types/tweaker/treeNodeMetadata.d.ts +1 -0
- package/dist/types/types/createCachedTypeCheck.d.ts +1 -0
- package/package.json +11 -10
- package/src/action/middleware.ts +5 -18
- package/src/action/mutationBatch.ts +72 -0
- package/src/action/runUnprotected.ts +21 -8
- package/src/action/wrapInAction.ts +45 -15
- package/src/dataModel/BaseDataModel.ts +4 -8
- package/src/deepChange/onDeepChange.ts +246 -16
- package/src/model/BaseModel.ts +4 -12
- package/src/model/Model.ts +5 -0
- package/src/model/newModel.ts +68 -36
- package/src/modelShared/prop.ts +105 -63
- package/src/modelShared/sharedInternalModel.ts +91 -60
- package/src/parent/core.ts +16 -10
- package/src/parent/coreObjectChildren.ts +219 -21
- package/src/parent/path.ts +6 -5
- package/src/parent/setParent.ts +5 -5
- package/src/patch/emitPatch.ts +102 -20
- package/src/snapshot/fromArraySnapshot.ts +11 -2
- package/src/snapshot/fromModelSnapshot.ts +10 -3
- package/src/snapshot/fromPlainObjectSnapshot.ts +18 -7
- package/src/snapshot/fromSnapshot.ts +31 -9
- package/src/snapshot/getSnapshot.ts +8 -3
- package/src/snapshot/internal.ts +243 -82
- package/src/snapshot/modelInitialData.ts +41 -0
- package/src/snapshot/reconcileArraySnapshot.ts +9 -3
- package/src/snapshot/reconcileModelSnapshot.ts +97 -17
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +9 -3
- package/src/tweaker/core.ts +30 -5
- package/src/tweaker/treeNodeMetadata.ts +38 -0
- package/src/tweaker/tweak.ts +15 -4
- package/src/tweaker/tweakArray.ts +159 -121
- package/src/tweaker/tweakFrozen.ts +2 -2
- package/src/tweaker/tweakModel.ts +2 -2
- package/src/tweaker/tweakPlainObject.ts +114 -122
- package/src/tweaker/typeChecking.ts +10 -1
- package/src/types/TypeChecker.ts +261 -15
- package/src/types/arrayBased/typesArray.ts +38 -40
- package/src/types/arrayBased/typesTuple.ts +51 -34
- package/src/types/createCachedTypeCheck.ts +181 -0
- package/src/types/objectBased/typesArraySet.ts +42 -32
- package/src/types/objectBased/typesDataModelData.ts +18 -11
- package/src/types/objectBased/typesModel.ts +1 -1
- package/src/types/objectBased/typesObject.ts +57 -28
- package/src/types/objectBased/typesObjectMap.ts +51 -40
- package/src/types/objectBased/typesRecord.ts +59 -39
- package/src/types/objectBased/typesRef.ts +1 -1
- package/src/types/primitiveBased/typesPrimitive.ts +9 -9
- package/src/types/tProp.ts +80 -44
- package/src/types/utility/typesOr.ts +35 -24
- package/src/types/utility/typesRefinement.ts +21 -8
- package/src/types/utility/typesSkipCheck.ts +21 -8
- package/src/types/utility/typesTag.ts +21 -9
- package/src/types/utility/typesUnchecked.ts +2 -3
- package/src/utils/ModelPool.ts +34 -19
- package/src/utils/errorDiagnostics.ts +60 -18
- package/src/utils/index.ts +9 -7
- package/src/types/createPerEntryCachedCheck.ts +0 -166
- /package/dist/types/{types/createPerEntryCachedCheck.d.ts → action/mutationBatch.d.ts} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runs a block in
|
|
3
|
-
*
|
|
2
|
+
* Runs a block in unprotected mode, as if it were run inside a model action.
|
|
3
|
+
* Structural mutations share one synchronous mutation batch, but the block is
|
|
4
|
+
* not recorded as an action. Consider using a proper model action when action
|
|
5
|
+
* recording is needed.
|
|
4
6
|
*
|
|
5
7
|
* @template T Return type.
|
|
6
8
|
* @param name Mobx action name.
|
|
@@ -9,8 +11,10 @@
|
|
|
9
11
|
*/
|
|
10
12
|
export declare function runUnprotected<T>(name: string, fn: () => T): T;
|
|
11
13
|
/**
|
|
12
|
-
* Runs a block in
|
|
13
|
-
*
|
|
14
|
+
* Runs a block in unprotected mode, as if it were run inside a model action.
|
|
15
|
+
* Structural mutations share one synchronous mutation batch, but the block is
|
|
16
|
+
* not recorded as an action. Consider using a proper model action when action
|
|
17
|
+
* recording is needed.
|
|
14
18
|
*
|
|
15
19
|
* @template T Return type.
|
|
16
20
|
* @param fn Action block.
|
|
@@ -76,6 +76,9 @@ export interface ModelOptions<TProps extends ModelProps, FS, TS> {
|
|
|
76
76
|
/**
|
|
77
77
|
* Optional transformation that will be run when converting from a snapshot to the data part of the model.
|
|
78
78
|
* Useful for example to do versioning and keep the data part up to date with the latest version of the model.
|
|
79
|
+
* Snapshot processors must be pure and deterministic. The model's canonical output snapshot must round-trip
|
|
80
|
+
* through this processor to the same model data. Reconciliation may skip invoking it when the input already
|
|
81
|
+
* equals the model's current canonical output snapshot.
|
|
79
82
|
*
|
|
80
83
|
* @param sn The custom input snapshot.
|
|
81
84
|
* @returns An input snapshot that must match the expected model input snapshot.
|
|
@@ -83,6 +86,8 @@ export interface ModelOptions<TProps extends ModelProps, FS, TS> {
|
|
|
83
86
|
fromSnapshotProcessor?: (sn: FS) => FromSnapshotDefaultType<TProps>;
|
|
84
87
|
/**
|
|
85
88
|
* Optional transformation that will be run when converting the data part of the model into a snapshot.
|
|
89
|
+
* Snapshot processors must be pure and deterministic, and their canonical output must round-trip through the
|
|
90
|
+
* corresponding input processor to the same model data. Snapshot generation may cache processor results.
|
|
86
91
|
*
|
|
87
92
|
* @param sn The output snapshot.
|
|
88
93
|
* @param modelInstance The model instance.
|
|
@@ -23,17 +23,18 @@ export interface ModelProp<TPropValue, TPropCreationValue, TTransformedValue, TT
|
|
|
23
23
|
_defaultFn: (() => TPropValue) | typeof noDefaultValue;
|
|
24
24
|
_defaultValue: TPropValue | typeof noDefaultValue;
|
|
25
25
|
_defaultValueIsTransformed: boolean;
|
|
26
|
+
_getDefaultValueIsTransformed?: () => boolean;
|
|
26
27
|
_typeChecker: AnyStandardType | undefined;
|
|
27
28
|
_setter: SetterMode;
|
|
28
29
|
_setterValueTransform: ((value: unknown) => unknown) | undefined;
|
|
29
30
|
_isId: boolean;
|
|
30
31
|
_idGenerator?: (() => string) | undefined;
|
|
31
|
-
_transform:
|
|
32
|
-
|
|
33
|
-
untransform: (transformed: unknown, model: object, propName: PropertyKey) => unknown;
|
|
34
|
-
} | undefined;
|
|
32
|
+
_transform: FullModelPropTransform | undefined;
|
|
33
|
+
_getTransform?: () => FullModelPropTransform | undefined;
|
|
35
34
|
_fromSnapshotProcessor?: (sn: unknown) => unknown;
|
|
36
35
|
_toSnapshotProcessor?: (sn: unknown) => unknown;
|
|
36
|
+
_getFromSnapshotProcessor?: () => ((sn: unknown) => unknown) | undefined;
|
|
37
|
+
_getToSnapshotProcessor?: () => ((sn: unknown) => unknown) | undefined;
|
|
37
38
|
/**
|
|
38
39
|
* Adds a setter to the property. The setter will be named `set${CapitalizedPropName}`
|
|
39
40
|
* and will be available in the model instance.
|
|
@@ -66,6 +67,9 @@ export interface ModelProp<TPropValue, TPropCreationValue, TTransformedValue, TT
|
|
|
66
67
|
*
|
|
67
68
|
* `fromSnapshot` runs before assigning snapshot data into the model prop.
|
|
68
69
|
* `toSnapshot` runs when exporting model data back to snapshot form.
|
|
70
|
+
* Both processors must be pure and deterministic, and their canonical output
|
|
71
|
+
* must round-trip to the same stored value. Reconciliation and snapshot
|
|
72
|
+
* generation may skip calls when that canonical snapshot is already current.
|
|
69
73
|
*/
|
|
70
74
|
withSnapshotProcessor<FS = TFromSnapshotOverride, TS = TToSnapshotOverride, This extends AnyModelProp = this>(processor: {
|
|
71
75
|
fromSnapshot?: (sn: FS) => ModelPropFromSnapshot<This>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobx-keystone",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"description": "A MobX powered state management solution based on data trees with first class support for TypeScript, snapshots, patches and much more",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mobx",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"test": "vitest run",
|
|
59
59
|
"test:ci": "vitest run --coverage",
|
|
60
60
|
"test:perf": "pnpm run build && pnpm run test:perf:run",
|
|
61
|
-
"test:perf:run": "cd perf_bench && NODE_ENV=production /usr/bin/time node --expose-gc --require ts-node/register ./report.ts",
|
|
62
|
-
"memtest": "cd perf_bench && NODE_ENV=production node --expose-gc --require ts-node/register ./memtest.ts",
|
|
63
|
-
"build-docs": "shx rm -rf api-docs && typedoc --options ./typedocconfig.js src/index.ts && shx rm -rf ../../apps/site/generated-static/api && shx mkdir -p ../../apps/site/generated-static/api && shx cp -R ./api-docs/* ../../apps/site/generated-static/api"
|
|
61
|
+
"test:perf:run": "cd perf_bench && NODE_ENV=production TS_NODE_COMPILER=@typescript/typescript6 /usr/bin/time node --expose-gc --require ts-node/register ./report.ts",
|
|
62
|
+
"memtest": "cd perf_bench && NODE_ENV=production TS_NODE_COMPILER=@typescript/typescript6 node --expose-gc --require ts-node/register ./memtest.ts",
|
|
63
|
+
"build-docs": "shx rm -rf api-docs && node --import ./scripts/typedoc-typescript6-register.mjs ./node_modules/typedoc/dist/cli.js --options ./typedocconfig.js src/index.ts && shx rm -rf ../../apps/site/generated-static/api && shx mkdir -p ../../apps/site/generated-static/api && shx cp -R ./api-docs/* ../../apps/site/generated-static/api"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"mobx": "^6.0.0 || ^5.0.0 || ^4.0.0"
|
|
@@ -73,18 +73,19 @@
|
|
|
73
73
|
"@babel/preset-env": "^8.0.2",
|
|
74
74
|
"@swc/core": "^1.15.43",
|
|
75
75
|
"@types/babel__core": "^7.20.5",
|
|
76
|
-
"@types/node": "^26.
|
|
77
|
-
"@
|
|
76
|
+
"@types/node": "^26.1.1",
|
|
77
|
+
"@typescript/typescript6": "^6.0.2",
|
|
78
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
78
79
|
"mobx-v4": "npm:mobx@^4.15.7",
|
|
79
80
|
"mobx-v5": "npm:mobx@^5.15.7",
|
|
80
81
|
"shx": "^0.4.0",
|
|
81
82
|
"spec.ts": "^1.1.3",
|
|
82
83
|
"ts-node": "^10.9.2",
|
|
83
|
-
"typedoc": "^0.28.
|
|
84
|
-
"typescript": "^
|
|
85
|
-
"vite": "^8.1.
|
|
84
|
+
"typedoc": "^0.28.20",
|
|
85
|
+
"typescript": "^7.0.2",
|
|
86
|
+
"vite": "^8.1.4",
|
|
86
87
|
"vite-plugin-dts": "^5.0.3",
|
|
87
|
-
"vitest": "^4.1.
|
|
88
|
+
"vitest": "^4.1.10"
|
|
88
89
|
},
|
|
89
90
|
"dependencies": {
|
|
90
91
|
"fast-deep-equal": "^3.1.3",
|
package/src/action/middleware.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { fastGetParent } from "../parent/path"
|
|
2
|
-
import { isChildOfParent } from "../parent/path2"
|
|
3
2
|
import { assertTweakedObject } from "../tweaker/core"
|
|
4
3
|
import { assertIsFunction, assertIsObject, deleteFromArray, failure } from "../utils"
|
|
5
4
|
import type { ActionContext } from "./context"
|
|
@@ -31,7 +30,7 @@ export interface ActionMiddleware {
|
|
|
31
30
|
*/
|
|
32
31
|
export type ActionMiddlewareDisposer = () => void
|
|
33
32
|
|
|
34
|
-
type PartialActionMiddleware = Pick<ActionMiddleware, "filter" | "middleware">
|
|
33
|
+
type PartialActionMiddleware = Pick<ActionMiddleware, "subtreeRoot" | "filter" | "middleware">
|
|
35
34
|
|
|
36
35
|
const perObjectActionMiddlewares = new WeakMap<object, PartialActionMiddleware[]>()
|
|
37
36
|
|
|
@@ -96,7 +95,7 @@ export function addActionMiddleware(mware: ActionMiddleware): ActionMiddlewareDi
|
|
|
96
95
|
assertIsObject(mware, "middleware")
|
|
97
96
|
|
|
98
97
|
const { middleware, subtreeRoot } = mware
|
|
99
|
-
|
|
98
|
+
const { filter } = mware
|
|
100
99
|
|
|
101
100
|
assertTweakedObject(subtreeRoot, "middleware.subtreeRoot")
|
|
102
101
|
assertIsFunction(middleware, "middleware.middleware")
|
|
@@ -107,21 +106,10 @@ export function addActionMiddleware(mware: ActionMiddleware): ActionMiddlewareDi
|
|
|
107
106
|
// reminder: never turn middlewares into actions or else
|
|
108
107
|
// reactions will not be picked up by the undo manager
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
ctx.target === subtreeRoot || isChildOfParent(ctx.target, subtreeRoot)
|
|
113
|
-
|
|
114
|
-
if (filter) {
|
|
115
|
-
const customFilter = filter
|
|
116
|
-
filter = (ctx) => {
|
|
117
|
-
return targetFilter(ctx) && customFilter(ctx)
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
filter = targetFilter
|
|
121
|
-
}
|
|
122
|
-
}
|
|
109
|
+
// Middleware collection establishes the initial subtree match. The runner
|
|
110
|
+
// revalidates it after user middleware or filter code may have changed parentage.
|
|
123
111
|
|
|
124
|
-
const actualMware = { middleware, filter }
|
|
112
|
+
const actualMware = { middleware, subtreeRoot, filter }
|
|
125
113
|
|
|
126
114
|
let objMwares = perObjectActionMiddlewares.get(subtreeRoot)
|
|
127
115
|
if (objMwares) {
|
|
@@ -130,7 +118,6 @@ export function addActionMiddleware(mware: ActionMiddleware): ActionMiddlewareDi
|
|
|
130
118
|
objMwares = [actualMware]
|
|
131
119
|
perObjectActionMiddlewares.set(subtreeRoot, objMwares)
|
|
132
120
|
}
|
|
133
|
-
|
|
134
121
|
return () => {
|
|
135
122
|
deleteFromArray(objMwares, actualMware)
|
|
136
123
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
let mutationBatchActive = false
|
|
2
|
+
let mutationBatchFinishers: (() => void) | (() => void)[] | undefined
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Starts a synchronous mutation batch if one is not already active.
|
|
6
|
+
*
|
|
7
|
+
* @returns `true` if this call owns the batch and must finish it.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export function startMutationBatch(): boolean {
|
|
11
|
+
if (mutationBatchActive) {
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
mutationBatchActive = true
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export function isMutationBatchActive(): boolean {
|
|
23
|
+
return mutationBatchActive
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Registers a callback to run when the outermost synchronous mutation batch
|
|
28
|
+
* ends. The first callback is stored directly so the common single-finisher
|
|
29
|
+
* case does not allocate an array.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export function addMutationBatchFinisher(finisher: () => void): void {
|
|
34
|
+
const currentFinishers = mutationBatchFinishers
|
|
35
|
+
if (!currentFinishers) {
|
|
36
|
+
mutationBatchFinishers = finisher
|
|
37
|
+
} else if (typeof currentFinishers === "function") {
|
|
38
|
+
mutationBatchFinishers = [currentFinishers, finisher]
|
|
39
|
+
} else {
|
|
40
|
+
currentFinishers.push(finisher)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Finishes a batch started by `startMutationBatch`. Every finisher runs even
|
|
46
|
+
* if an earlier one throws, so a finisher's cleanup of its own module state
|
|
47
|
+
* cannot be skipped by another consumer's exception.
|
|
48
|
+
*
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
export function finishMutationBatch(): void {
|
|
52
|
+
mutationBatchActive = false
|
|
53
|
+
|
|
54
|
+
const finishers = mutationBatchFinishers
|
|
55
|
+
mutationBatchFinishers = undefined
|
|
56
|
+
|
|
57
|
+
if (typeof finishers === "function") {
|
|
58
|
+
finishers()
|
|
59
|
+
} else if (finishers) {
|
|
60
|
+
runFinishers(finishers, finishers.length - 1)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function runFinishers(finishers: (() => void)[], index: number): void {
|
|
65
|
+
try {
|
|
66
|
+
finishers[index]()
|
|
67
|
+
} finally {
|
|
68
|
+
if (index > 0) {
|
|
69
|
+
runFinishers(finishers, index - 1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { action } from "mobx"
|
|
2
|
+
import { finishMutationBatch, startMutationBatch } from "./mutationBatch"
|
|
2
3
|
import { tryRunPendingActions } from "./pendingActions"
|
|
3
4
|
import { getActionProtection, setActionProtection } from "./protection"
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Runs a block in
|
|
7
|
-
*
|
|
7
|
+
* Runs a block in unprotected mode, as if it were run inside a model action.
|
|
8
|
+
* Structural mutations share one synchronous mutation batch, but the block is
|
|
9
|
+
* not recorded as an action. Consider using a proper model action when action
|
|
10
|
+
* recording is needed.
|
|
8
11
|
*
|
|
9
12
|
* @template T Return type.
|
|
10
13
|
* @param name Mobx action name.
|
|
@@ -14,8 +17,10 @@ import { getActionProtection, setActionProtection } from "./protection"
|
|
|
14
17
|
export function runUnprotected<T>(name: string, fn: () => T): T
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
|
-
* Runs a block in
|
|
18
|
-
*
|
|
20
|
+
* Runs a block in unprotected mode, as if it were run inside a model action.
|
|
21
|
+
* Structural mutations share one synchronous mutation batch, but the block is
|
|
22
|
+
* not recorded as an action. Consider using a proper model action when action
|
|
23
|
+
* recording is needed.
|
|
19
24
|
*
|
|
20
25
|
* @template T Return type.
|
|
21
26
|
* @param fn Action block.
|
|
@@ -28,7 +33,9 @@ export function runUnprotected<T>(arg1: any, arg2?: any): T {
|
|
|
28
33
|
const name = typeof arg1 === "string" ? arg1 : undefined
|
|
29
34
|
const fn: () => T = typeof arg1 === "string" ? arg2 : arg1
|
|
30
35
|
|
|
36
|
+
let mutationBatchOwner = false
|
|
31
37
|
const innerAction = () => {
|
|
38
|
+
mutationBatchOwner = startMutationBatch()
|
|
32
39
|
const oldActionProtection = getActionProtection()
|
|
33
40
|
setActionProtection(false)
|
|
34
41
|
|
|
@@ -41,9 +48,15 @@ export function runUnprotected<T>(arg1: any, arg2?: any): T {
|
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
try {
|
|
52
|
+
if (name) {
|
|
53
|
+
return action(name, innerAction)()
|
|
54
|
+
} else {
|
|
55
|
+
return action(innerAction)()
|
|
56
|
+
}
|
|
57
|
+
} finally {
|
|
58
|
+
if (mutationBatchOwner) {
|
|
59
|
+
finishMutationBatch()
|
|
60
|
+
}
|
|
48
61
|
}
|
|
49
62
|
}
|
|
@@ -2,6 +2,7 @@ import { action } from "mobx"
|
|
|
2
2
|
import type { O } from "ts-toolbelt"
|
|
3
3
|
import type { AnyDataModel } from "../dataModel/BaseDataModel"
|
|
4
4
|
import type { AnyModel } from "../model/BaseModel"
|
|
5
|
+
import { fastGetParent } from "../parent/path"
|
|
5
6
|
import type { AnyFunction } from "../utils/AnyFunction"
|
|
6
7
|
import { copyFunctionMetadata } from "../utils/decorators"
|
|
7
8
|
import {
|
|
@@ -13,8 +14,20 @@ import {
|
|
|
13
14
|
import { isModelAction, modelActionSymbol } from "./isModelAction"
|
|
14
15
|
import { getPerObjectActionMiddlewares } from "./middleware"
|
|
15
16
|
import type { FlowFinisher } from "./modelFlow"
|
|
17
|
+
import { finishMutationBatch, startMutationBatch } from "./mutationBatch"
|
|
16
18
|
import { tryRunPendingActions } from "./pendingActions"
|
|
17
19
|
|
|
20
|
+
function isActionTargetInSubtree(target: object, subtreeRoot: object): boolean {
|
|
21
|
+
let current: object | undefined = target
|
|
22
|
+
while (current) {
|
|
23
|
+
if (current === subtreeRoot) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
current = fastGetParent(current, false)
|
|
27
|
+
}
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
/**
|
|
19
32
|
* @internal
|
|
20
33
|
*/
|
|
@@ -77,29 +90,44 @@ export function wrapInAction<T extends Function>({
|
|
|
77
90
|
setCurrentActionContext(context)
|
|
78
91
|
|
|
79
92
|
const perObjectMiddlewares = getPerObjectActionMiddlewares(context.target)
|
|
80
|
-
|
|
81
93
|
let objectIndex = perObjectMiddlewares.length - 1 // from topmost parent to the object itself
|
|
82
94
|
let objectMwareIndex = 0
|
|
95
|
+
let scopeMayHaveChanged = false
|
|
83
96
|
|
|
84
97
|
const runNextMiddleware = (): unknown => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
while (objectIndex >= 0) {
|
|
99
|
+
const objectMwares = perObjectMiddlewares[objectIndex]
|
|
100
|
+
const mwareData = objectMwares[objectMwareIndex]
|
|
101
|
+
|
|
102
|
+
// Advance before calling user code so next() continues with the following middleware.
|
|
103
|
+
objectMwareIndex++
|
|
104
|
+
if (objectMwareIndex >= objectMwares.length) {
|
|
105
|
+
objectMwareIndex = 0
|
|
106
|
+
objectIndex--
|
|
107
|
+
}
|
|
91
108
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
if (
|
|
110
|
+
scopeMayHaveChanged &&
|
|
111
|
+
!isActionTargetInSubtree(context.target, mwareData.subtreeRoot)
|
|
112
|
+
) {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (mwareData.filter) {
|
|
117
|
+
scopeMayHaveChanged = true
|
|
118
|
+
if (!mwareData.filter(context)) {
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
scopeMayHaveChanged = true
|
|
124
|
+
return mwareData.middleware(context, runNextMiddleware)
|
|
97
125
|
}
|
|
98
126
|
|
|
99
|
-
|
|
100
|
-
return filterPassed ? mwareData.middleware(context, runNextMiddleware) : runNextMiddleware()
|
|
127
|
+
return fn.apply(this, args)
|
|
101
128
|
}
|
|
102
129
|
|
|
130
|
+
const mutationBatchOwner = startMutationBatch()
|
|
103
131
|
try {
|
|
104
132
|
const ret = runNextMiddleware()
|
|
105
133
|
|
|
@@ -117,7 +145,9 @@ export function wrapInAction<T extends Function>({
|
|
|
117
145
|
}
|
|
118
146
|
} finally {
|
|
119
147
|
setCurrentActionContext(context.parentContext)
|
|
120
|
-
|
|
148
|
+
if (mutationBatchOwner) {
|
|
149
|
+
finishMutationBatch()
|
|
150
|
+
}
|
|
121
151
|
tryRunPendingActions()
|
|
122
152
|
}
|
|
123
153
|
}
|
|
@@ -32,7 +32,7 @@ const dataModelInstanceCache = new WeakMap<ModelClass<AnyDataModel>, WeakMap<any
|
|
|
32
32
|
*/
|
|
33
33
|
export abstract class BaseDataModel<TProps extends ModelProps> {
|
|
34
34
|
// just to make typing work properly
|
|
35
|
-
[propsTypeSymbol]
|
|
35
|
+
declare [propsTypeSymbol]: TProps
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Called after the instance is created when there's the first call to `fn(M, data)`.
|
|
@@ -105,9 +105,10 @@ export abstract class BaseDataModel<TProps extends ModelProps> {
|
|
|
105
105
|
let changed = false
|
|
106
106
|
|
|
107
107
|
// apply untransform (if any)
|
|
108
|
-
|
|
108
|
+
const transform = propData._getTransform ? propData._getTransform() : propData._transform
|
|
109
|
+
if (transform) {
|
|
109
110
|
changed = true
|
|
110
|
-
newValue =
|
|
111
|
+
newValue = transform.untransform(newValue, this, k)
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
// apply default value (if needed)
|
|
@@ -143,11 +144,6 @@ export abstract class BaseDataModel<TProps extends ModelProps> {
|
|
|
143
144
|
|
|
144
145
|
Object.setPrototypeOf(this, modelClass.prototype)
|
|
145
146
|
|
|
146
|
-
const self = this as any
|
|
147
|
-
|
|
148
|
-
// delete unnecessary props
|
|
149
|
-
delete self[propsTypeSymbol]
|
|
150
|
-
|
|
151
147
|
internalNewDataModel(this, tweakedData as any, {
|
|
152
148
|
modelClass,
|
|
153
149
|
})
|