mobx-keystone 1.22.0 → 1.24.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 +12 -0
- package/LICENSE +21 -21
- package/README.md +1 -1
- package/dist/mobx-keystone.esm.js +7198 -6242
- package/dist/mobx-keystone.esm.mjs +7198 -6242
- package/dist/mobx-keystone.umd.js +7217 -6243
- package/dist/types/action/runUnprotected.d.ts +8 -4
- package/dist/types/model/Model.d.ts +5 -0
- package/dist/types/modelShared/modelDecorator.d.ts +1 -1
- 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/dist/types/utils/index.d.ts +24 -0
- package/package.json +15 -11
- 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/actionMiddlewares/undoMiddleware.ts +19 -13
- 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/modelDecorator.ts +64 -53
- 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/ref/Ref.ts +4 -5
- package/src/ref/core.ts +2 -2
- 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/treeUtils/draft.ts +7 -8
- 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 +117 -124
- 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 +142 -9
- 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.
|
|
@@ -9,7 +9,7 @@ import { AnyFunction } from '../utils/AnyFunction';
|
|
|
9
9
|
* @param name Unique name for the model type. Note that this name must be unique for your whole
|
|
10
10
|
* application, so it is usually a good idea to use some prefix unique to your application domain.
|
|
11
11
|
*/
|
|
12
|
-
export declare const model: (name: string) => <MC extends ModelClass<AnyModel | AnyDataModel>>(clazz: MC,
|
|
12
|
+
export declare const model: (name: string) => <MC extends ModelClass<AnyModel | AnyDataModel>>(clazz: MC, _context?: ClassDecoratorContext<MC>) => MC;
|
|
13
13
|
/**
|
|
14
14
|
* Marks a class (which MUST inherit from the `Model` abstract class)
|
|
15
15
|
* as a model and decorates some of its methods/properties.
|
|
@@ -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 {};
|
|
@@ -4,3 +4,27 @@
|
|
|
4
4
|
export declare class MobxKeystoneError extends Error {
|
|
5
5
|
constructor(msg: string);
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* MobX method action decorator compatible with both legacy decorators and
|
|
9
|
+
* MobX 7's standard decorator API.
|
|
10
|
+
*
|
|
11
|
+
* This is a decorator compatibility helper, not a replacement for other
|
|
12
|
+
* `action` overloads such as `action(fn)`. Field decorators are not supported.
|
|
13
|
+
*
|
|
14
|
+
* @hidden
|
|
15
|
+
*/
|
|
16
|
+
export declare function mobxAction<This, Args extends unknown[], Return>(value: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>): (this: This, ...args: Args) => Return;
|
|
17
|
+
export declare function mobxAction<This, Args extends unknown[], Return>(target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<(this: This, ...args: Args) => Return>): TypedPropertyDescriptor<(this: This, ...args: Args) => Return> | void;
|
|
18
|
+
/**
|
|
19
|
+
* MobX getter computed decorator compatible with both legacy decorators and
|
|
20
|
+
* MobX 7's standard decorator API.
|
|
21
|
+
*
|
|
22
|
+
* This is a decorator compatibility helper, not a replacement for other
|
|
23
|
+
* `computed` overloads. Field decorators and decorator options are not supported.
|
|
24
|
+
* Under MobX 7's legacy decorator transform the getter is backed by a computed
|
|
25
|
+
* value, but is not registered as a computed property for MobX introspection.
|
|
26
|
+
*
|
|
27
|
+
* @hidden
|
|
28
|
+
*/
|
|
29
|
+
export declare function mobxComputed<This, Value>(value: (this: This) => Value, context: ClassGetterDecoratorContext<This, Value>): ((this: This) => Value) | void;
|
|
30
|
+
export declare function mobxComputed<Value>(target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<Value>): TypedPropertyDescriptor<Value> | void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobx-keystone",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.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,33 +58,37 @@
|
|
|
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
|
-
"mobx": "^6.0.0 || ^5.0.0 || ^4.0.0"
|
|
66
|
+
"mobx": "^7.0.0 || ^6.0.0 || ^5.0.0 || ^4.0.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@babel/core": "^8.0.1",
|
|
70
70
|
"@babel/plugin-proposal-decorators": "^8.0.2",
|
|
71
|
+
"@babel/plugin-transform-class-static-block": "^8.0.1",
|
|
71
72
|
"@babel/plugin-transform-class-properties": "^8.0.1",
|
|
72
73
|
"@babel/plugin-transform-typescript": "^8.0.1",
|
|
73
74
|
"@babel/preset-env": "^8.0.2",
|
|
74
75
|
"@swc/core": "^1.15.43",
|
|
75
76
|
"@types/babel__core": "^7.20.5",
|
|
76
|
-
"@types/node": "^26.
|
|
77
|
-
"@
|
|
77
|
+
"@types/node": "^26.1.1",
|
|
78
|
+
"@typescript/typescript6": "^6.0.2",
|
|
79
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
78
80
|
"mobx-v4": "npm:mobx@^4.15.7",
|
|
79
81
|
"mobx-v5": "npm:mobx@^5.15.7",
|
|
82
|
+
"mobx-v6": "npm:mobx@^6.0.0",
|
|
83
|
+
"mobx-v7": "npm:mobx@^7.0.0",
|
|
80
84
|
"shx": "^0.4.0",
|
|
81
85
|
"spec.ts": "^1.1.3",
|
|
82
86
|
"ts-node": "^10.9.2",
|
|
83
|
-
"typedoc": "^0.28.
|
|
84
|
-
"typescript": "^
|
|
85
|
-
"vite": "^8.1.
|
|
87
|
+
"typedoc": "^0.28.20",
|
|
88
|
+
"typescript": "^7.0.2",
|
|
89
|
+
"vite": "^8.1.4",
|
|
86
90
|
"vite-plugin-dts": "^5.0.3",
|
|
87
|
-
"vitest": "^4.1.
|
|
91
|
+
"vitest": "^4.1.10"
|
|
88
92
|
},
|
|
89
93
|
"dependencies": {
|
|
90
94
|
"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
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { action, computed } from "mobx"
|
|
2
1
|
import type { ActionMiddlewareDisposer } from "../action/middleware"
|
|
3
2
|
import { modelAction } from "../action/modelAction"
|
|
4
3
|
import { Model } from "../model/Model"
|
|
@@ -10,7 +9,14 @@ import { assertTweakedObject } from "../tweaker/core"
|
|
|
10
9
|
import { typesArray } from "../types/arrayBased/typesArray"
|
|
11
10
|
import { tProp } from "../types/tProp"
|
|
12
11
|
import { typesUnchecked } from "../types/utility/typesUnchecked"
|
|
13
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
failure,
|
|
14
|
+
getMobxVersion,
|
|
15
|
+
makeObservableCompat,
|
|
16
|
+
mobxAction,
|
|
17
|
+
mobxComputed,
|
|
18
|
+
namespace,
|
|
19
|
+
} from "../utils"
|
|
14
20
|
import { actionTrackingMiddleware, type SimpleActionContext } from "./actionTrackingMiddleware"
|
|
15
21
|
|
|
16
22
|
/**
|
|
@@ -306,7 +312,7 @@ export class UndoManager {
|
|
|
306
312
|
* The undo stack, where the first operation to undo will be the last of the array.
|
|
307
313
|
* Do not manipulate this array directly.
|
|
308
314
|
*/
|
|
309
|
-
@
|
|
315
|
+
@mobxComputed
|
|
310
316
|
get undoQueue(): ReadonlyArray<UndoEvent> {
|
|
311
317
|
return this.store.undoEvents
|
|
312
318
|
}
|
|
@@ -315,7 +321,7 @@ export class UndoManager {
|
|
|
315
321
|
* The redo stack, where the first operation to redo will be the last of the array.
|
|
316
322
|
* Do not manipulate this array directly.
|
|
317
323
|
*/
|
|
318
|
-
@
|
|
324
|
+
@mobxComputed
|
|
319
325
|
get redoQueue(): ReadonlyArray<UndoEvent> {
|
|
320
326
|
return this.store.redoEvents
|
|
321
327
|
}
|
|
@@ -323,7 +329,7 @@ export class UndoManager {
|
|
|
323
329
|
/**
|
|
324
330
|
* The number of undo actions available.
|
|
325
331
|
*/
|
|
326
|
-
@
|
|
332
|
+
@mobxComputed
|
|
327
333
|
get undoLevels() {
|
|
328
334
|
return this.undoQueue.length
|
|
329
335
|
}
|
|
@@ -331,7 +337,7 @@ export class UndoManager {
|
|
|
331
337
|
/**
|
|
332
338
|
* If undo can be performed (if there is at least one undo action available).
|
|
333
339
|
*/
|
|
334
|
-
@
|
|
340
|
+
@mobxComputed
|
|
335
341
|
get canUndo() {
|
|
336
342
|
return this.undoLevels > 0
|
|
337
343
|
}
|
|
@@ -339,7 +345,7 @@ export class UndoManager {
|
|
|
339
345
|
/**
|
|
340
346
|
* Clears the undo queue.
|
|
341
347
|
*/
|
|
342
|
-
@
|
|
348
|
+
@mobxAction
|
|
343
349
|
clearUndo() {
|
|
344
350
|
this.store._clearUndo()
|
|
345
351
|
}
|
|
@@ -347,7 +353,7 @@ export class UndoManager {
|
|
|
347
353
|
/**
|
|
348
354
|
* The number of redo actions available.
|
|
349
355
|
*/
|
|
350
|
-
@
|
|
356
|
+
@mobxComputed
|
|
351
357
|
get redoLevels() {
|
|
352
358
|
return this.redoQueue.length
|
|
353
359
|
}
|
|
@@ -355,7 +361,7 @@ export class UndoManager {
|
|
|
355
361
|
/**
|
|
356
362
|
* If redo can be performed (if there is at least one redo action available)
|
|
357
363
|
*/
|
|
358
|
-
@
|
|
364
|
+
@mobxComputed
|
|
359
365
|
get canRedo() {
|
|
360
366
|
return this.redoLevels > 0
|
|
361
367
|
}
|
|
@@ -363,7 +369,7 @@ export class UndoManager {
|
|
|
363
369
|
/**
|
|
364
370
|
* Clears the redo queue.
|
|
365
371
|
*/
|
|
366
|
-
@
|
|
372
|
+
@mobxAction
|
|
367
373
|
clearRedo() {
|
|
368
374
|
this.store._clearRedo()
|
|
369
375
|
}
|
|
@@ -372,7 +378,7 @@ export class UndoManager {
|
|
|
372
378
|
* Undoes the last action.
|
|
373
379
|
* Will throw if there is no action to undo.
|
|
374
380
|
*/
|
|
375
|
-
@
|
|
381
|
+
@mobxAction
|
|
376
382
|
undo() {
|
|
377
383
|
if (!this.canUndo) {
|
|
378
384
|
throw failure("nothing to undo")
|
|
@@ -397,7 +403,7 @@ export class UndoManager {
|
|
|
397
403
|
* Redoes the previous action.
|
|
398
404
|
* Will throw if there is no action to redo.
|
|
399
405
|
*/
|
|
400
|
-
@
|
|
406
|
+
@mobxAction
|
|
401
407
|
redo() {
|
|
402
408
|
if (!this.canRedo) {
|
|
403
409
|
throw failure("nothing to redo")
|
|
@@ -619,7 +625,7 @@ export class UndoManager {
|
|
|
619
625
|
this.options = options
|
|
620
626
|
|
|
621
627
|
if (getMobxVersion() >= 6) {
|
|
622
|
-
|
|
628
|
+
makeObservableCompat(this)
|
|
623
629
|
}
|
|
624
630
|
|
|
625
631
|
this.store = store ?? new UndoStore({})
|
|
@@ -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
|
})
|