mobx-keystone 1.25.0 → 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 +123 -0
- package/dist/mobx-keystone.esm.js +1478 -952
- package/dist/mobx-keystone.esm.mjs +1478 -952
- package/dist/mobx-keystone.umd.js +1483 -952
- 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 +1 -1
- 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 +65 -46
- 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 +4 -9
- package/src/model/newModel.ts +21 -24
- 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/onChildAttachedTo.ts +44 -28
- package/src/parent/path.ts +5 -3
- package/src/parent/setParent.ts +7 -0
- package/src/parent/walkTree.ts +21 -37
- package/src/patch/applyPatches.ts +69 -34
- 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 +1 -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/internal.ts +22 -9
- package/src/snapshot/onSnapshot.ts +12 -1
- package/src/snapshot/reconcileModelSnapshot.ts +6 -6
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -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 +2 -1
- package/src/tweaker/tweakArray.ts +83 -20
- package/src/tweaker/tweakPlainObject.ts +76 -23
- package/src/tweaker/typeChecking.ts +124 -15
- 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,2 +1,2 @@
|
|
|
1
1
|
import { ActionCallArgumentSerializer } from './core';
|
|
2
|
-
export declare const dateSerializer: ActionCallArgumentSerializer<Date, number>;
|
|
2
|
+
export declare const dateSerializer: ActionCallArgumentSerializer<Date, number | null>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -11,6 +11,8 @@ export declare enum DeepChangeType {
|
|
|
11
11
|
ObjectRemove = "objectRemove"
|
|
12
12
|
}
|
|
13
13
|
export interface DeepChangeBase {
|
|
14
|
+
/** Whether this notification overlaps a mutation made by a change listener. */
|
|
15
|
+
readonly isReentrant?: boolean;
|
|
14
16
|
readonly type: DeepChangeType;
|
|
15
17
|
readonly path: Path;
|
|
16
18
|
/**
|
|
@@ -78,6 +80,13 @@ export type OnDeepChangeListener = (change: DeepChange) => void;
|
|
|
78
80
|
/**
|
|
79
81
|
* Adds a listener that will be called every time a deep change is generated for the tree of the given target object.
|
|
80
82
|
* Unlike `onPatches`, this provides raw MobX change information including proper splice detection for arrays.
|
|
83
|
+
* Individual mutations notify listeners after automatic type checking succeeds and snapshots update;
|
|
84
|
+
* rejected mutations and their quiet rollbacks do not notify listeners.
|
|
85
|
+
* Listener errors do not stop delivery to other listeners. After delivery completes,
|
|
86
|
+
* a single error is rethrown unchanged and multiple errors are grouped in a MobxKeystoneAggregateError.
|
|
87
|
+
* Listeners added while callbacks for the same target are running start with the next
|
|
88
|
+
* change delivered to that target. Listeners disposed during delivery do not receive
|
|
89
|
+
* the current change if their callback has not run yet.
|
|
81
90
|
*
|
|
82
91
|
* @param subtreeRoot Subtree root object of the deep change listener.
|
|
83
92
|
* @param listener The listener function that will be called every time a change is generated for the object or its children.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Detaches a given object from a tree.
|
|
3
3
|
* If the parent is an object / model, detaching will delete the property.
|
|
4
4
|
* If the parent is an array detaching will remove the node by splicing it.
|
|
5
|
-
* If there's no parent it
|
|
5
|
+
* If there's no parent it does nothing.
|
|
6
6
|
*
|
|
7
7
|
* @param node Object to be detached.
|
|
8
8
|
*/
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @param predicate Function that will be run for every child of the root object.
|
|
7
7
|
* @param [options] An optional object with the `deep` option (defaults to `false`) set to `true` to
|
|
8
8
|
* get the children deeply or `false` to get them shallowly.
|
|
9
|
-
* @returns A readonly
|
|
9
|
+
* @returns A readonly set with the matching children.
|
|
10
10
|
*/
|
|
11
11
|
export declare function findChildren<T extends object = any>(root: object, predicate: (node: object) => boolean, options?: {
|
|
12
12
|
deep?: boolean;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Patch } from '../patch/Patch';
|
|
2
2
|
/**
|
|
3
3
|
* Applies the given patches to the given target object.
|
|
4
|
+
* Automatic type checking validates the completed batch, including nested lists.
|
|
5
|
+
* A rejected batch is rolled back with compensating patches.
|
|
4
6
|
*
|
|
5
7
|
* @param node Target object.
|
|
6
8
|
* @param patches List of patches to apply.
|
|
@@ -20,5 +20,6 @@ export interface ConnectReduxDevToolsOptions {
|
|
|
20
20
|
* @param remotedevConnection The result of a connect method from the remotedev package (usually the result of `remoteDev.connectViaExtension(...)`).
|
|
21
21
|
* @param target Object to use as root.
|
|
22
22
|
* @param [options] Optional options object.
|
|
23
|
+
* @returns An idempotent disposer that stops logging and unsubscribes from monitor messages.
|
|
23
24
|
*/
|
|
24
|
-
export declare function connectReduxDevTools(remotedevPackage: any, remotedevConnection: any, target: object, options?: ConnectReduxDevToolsOptions): void;
|
|
25
|
+
export declare function connectReduxDevTools(remotedevPackage: any, remotedevConnection: any, target: object, options?: ConnectReduxDevToolsOptions): () => void;
|
|
@@ -34,13 +34,13 @@ export interface SnapshotInOfFrozen<F extends Frozen<any>> extends FrozenData<F[
|
|
|
34
34
|
}
|
|
35
35
|
export interface SnapshotInOfObjectMap<V> {
|
|
36
36
|
items?: {
|
|
37
|
-
[k: string]:
|
|
37
|
+
[k: string]: SnapshotInOf<V>;
|
|
38
38
|
};
|
|
39
39
|
[modelTypeKey]?: string;
|
|
40
40
|
[modelIdKey]: string;
|
|
41
41
|
}
|
|
42
42
|
export interface SnapshotInOfArraySet<V> {
|
|
43
|
-
items?:
|
|
43
|
+
items?: SnapshotInOf<V>[];
|
|
44
44
|
[modelTypeKey]?: string;
|
|
45
45
|
[modelIdKey]: string;
|
|
46
46
|
}
|
|
@@ -2,7 +2,7 @@ export declare const arrayActions: {
|
|
|
2
2
|
set: <T>(array: T[], index: number, value: any) => void;
|
|
3
3
|
delete: <T>(array: T[], index: number) => boolean;
|
|
4
4
|
setLength: <T>(array: T[], length: number) => void;
|
|
5
|
-
concat: <T>(array: T[], ...items: ConcatArray<T>[]) => T[];
|
|
5
|
+
concat: <T>(array: T[], ...items: (T | ConcatArray<T>)[]) => T[];
|
|
6
6
|
copyWithin: <T>(array: T[], target: number, start: number, end?: number) => T[];
|
|
7
7
|
fill: <T>(array: T[], value: T, start?: number, end?: number) => T[];
|
|
8
8
|
pop: <T>(array: T[]) => T | undefined;
|
|
@@ -42,10 +42,6 @@ export declare class SandboxManager {
|
|
|
42
42
|
* code block on a readonly node.
|
|
43
43
|
*/
|
|
44
44
|
private allowWrite;
|
|
45
|
-
/**
|
|
46
|
-
* Whether changes made in the sandbox are currently being committed to the original subtree.
|
|
47
|
-
*/
|
|
48
|
-
private isCommitting;
|
|
49
45
|
private readonly subtreeRoot;
|
|
50
46
|
/**
|
|
51
47
|
* Creates an instance of `SandboxManager`.
|
|
@@ -32,8 +32,8 @@ import { TypeInfo } from '../TypeChecker';
|
|
|
32
32
|
* @template T Base type.
|
|
33
33
|
* @param baseType Base type.
|
|
34
34
|
* @param checkFn Function that will receive the data (if it passes the base type
|
|
35
|
-
* check) and return null or
|
|
36
|
-
*
|
|
35
|
+
* check) and return null or true if there were no errors or either a TypeCheckError instance or
|
|
36
|
+
* false if there were.
|
|
37
37
|
* @returns
|
|
38
38
|
*/
|
|
39
39
|
export declare function typesRefinement<T extends AnyType>(baseType: T, checkFn: (data: TypeToData<T>) => TypeCheckError | null | boolean, typeName?: string): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
export declare class MobxKeystoneError extends Error {
|
|
5
5
|
constructor(msg: string);
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* A mobx-keystone error that groups several failures that happened while delivering
|
|
9
|
+
* a single notification, such as multiple patch listeners or root store hooks throwing.
|
|
10
|
+
*
|
|
11
|
+
* It is only used when more than one callback failed; a lone failure is rethrown as is.
|
|
12
|
+
*/
|
|
13
|
+
export declare class MobxKeystoneAggregateError extends MobxKeystoneError {
|
|
14
|
+
/**
|
|
15
|
+
* The values thrown by the failing callbacks, in the order they were thrown.
|
|
16
|
+
*/
|
|
17
|
+
readonly errors: readonly unknown[];
|
|
18
|
+
constructor(errors: readonly unknown[], msg: string);
|
|
19
|
+
}
|
|
7
20
|
/**
|
|
8
21
|
* MobX method action decorator compatible with both legacy decorators and
|
|
9
22
|
* MobX 7's standard decorator API.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function getOrCreate<K, V
|
|
2
|
-
export declare function getOrCreate<K extends object, V
|
|
1
|
+
export declare function getOrCreate<K, V>(map: Map<K, V>, key: K, create: () => V): V;
|
|
2
|
+
export declare function getOrCreate<K extends object, V>(map: WeakMap<K, V>, key: K, create: () => V): V;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -86,7 +86,7 @@ export function applyAction<TRet = any>(subtreeRoot: object, call: ActionCall):
|
|
|
86
86
|
)} could not be resolved`
|
|
87
87
|
)
|
|
88
88
|
}
|
|
89
|
-
assertTweakedObject(current,
|
|
89
|
+
assertTweakedObject(current, "resolved target", true)
|
|
90
90
|
|
|
91
91
|
if (isBuiltInAction(call.actionName)) {
|
|
92
92
|
const fnToCall = builtInActionToFunction[call.actionName] as AnyFunction | undefined
|
package/src/action/middleware.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fastGetParent } from "../parent/path"
|
|
2
2
|
import { assertTweakedObject } from "../tweaker/core"
|
|
3
|
-
import { assertIsFunction, assertIsObject,
|
|
3
|
+
import { assertIsFunction, assertIsObject, failure } from "../utils"
|
|
4
4
|
import type { ActionContext } from "./context"
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -37,33 +37,9 @@ const perObjectActionMiddlewares = new WeakMap<object, PartialActionMiddleware[]
|
|
|
37
37
|
/**
|
|
38
38
|
* @internal
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*/
|
|
44
|
-
export function forEachActionMiddleware(
|
|
45
|
-
obj: object,
|
|
46
|
-
callback: (middleware: PartialActionMiddleware) => void
|
|
47
|
-
): void {
|
|
48
|
-
// when we call a middleware we will call the middlewares of that object plus all parent objects
|
|
49
|
-
// the parent object middlewares are run last
|
|
50
|
-
|
|
51
|
-
// since an array like [a, b, c] will be called like c(b(a())) this means that we need to call
|
|
52
|
-
// the parent object ones at the end of the array
|
|
53
|
-
|
|
54
|
-
let current: unknown = obj
|
|
55
|
-
while (current) {
|
|
56
|
-
const objMwares = perObjectActionMiddlewares.get(current)
|
|
57
|
-
if (objMwares && objMwares.length > 0) {
|
|
58
|
-
objMwares.forEach(callback)
|
|
59
|
-
}
|
|
60
|
-
current = fastGetParent(current, false)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* @internal
|
|
65
|
-
*
|
|
66
|
-
* Returns the action middlewares to be run over a given object.
|
|
40
|
+
* Returns the action middlewares to be run over a given object, from the object itself to the
|
|
41
|
+
* topmost parent. Since an array like [a, b, c] will be called like c(b(a())) the caller runs
|
|
42
|
+
* them in reverse, so the parent object ones run last.
|
|
67
43
|
*
|
|
68
44
|
* @returns
|
|
69
45
|
*/
|
|
@@ -111,14 +87,19 @@ export function addActionMiddleware(mware: ActionMiddleware): ActionMiddlewareDi
|
|
|
111
87
|
|
|
112
88
|
const actualMware = { middleware, subtreeRoot, filter }
|
|
113
89
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
90
|
+
// Each action keeps the middleware arrays it collected. Update registrations by
|
|
91
|
+
// replacement so user code cannot shift or extend an in-progress chain.
|
|
92
|
+
const objMwares = perObjectActionMiddlewares.get(subtreeRoot) ?? []
|
|
93
|
+
perObjectActionMiddlewares.set(subtreeRoot, [...objMwares, actualMware])
|
|
94
|
+
|
|
95
|
+
let disposed = false
|
|
121
96
|
return () => {
|
|
122
|
-
|
|
97
|
+
if (disposed) return
|
|
98
|
+
disposed = true
|
|
99
|
+
const current = perObjectActionMiddlewares.get(subtreeRoot)!
|
|
100
|
+
perObjectActionMiddlewares.set(
|
|
101
|
+
subtreeRoot,
|
|
102
|
+
current.filter((entry) => entry !== actualMware)
|
|
103
|
+
)
|
|
123
104
|
}
|
|
124
105
|
}
|
package/src/action/modelFlow.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { O } from "ts-toolbelt"
|
|
|
2
2
|
import { failure } from "../utils"
|
|
3
3
|
import { copyFunctionMetadata, decorateWrapMethodOrField } from "../utils/decorators"
|
|
4
4
|
import { type ActionContext, ActionContextActionType, ActionContextAsyncStepType } from "./context"
|
|
5
|
-
import { promiseGenerator } from "./modelFlowPromiseGenerator"
|
|
6
5
|
import { type WrapInActionOverrideContextFn, wrapInAction } from "./wrapInAction"
|
|
7
6
|
|
|
8
7
|
const modelFlowSymbol = Symbol("modelFlow")
|
|
@@ -133,7 +132,7 @@ export function flow<R, Args extends any[]>({
|
|
|
133
132
|
function next(ret: any): void {
|
|
134
133
|
if (ret && typeof ret.then === "function") {
|
|
135
134
|
// an async iterator
|
|
136
|
-
ret.then(next, reject)
|
|
135
|
+
Promise.resolve(ret).then(next, reject).catch(reject)
|
|
137
136
|
} else if (ret.done) {
|
|
138
137
|
// done
|
|
139
138
|
wrapInAction({
|
|
@@ -154,7 +153,7 @@ export function flow<R, Args extends any[]>({
|
|
|
154
153
|
}).call(target, ret.value)
|
|
155
154
|
} else {
|
|
156
155
|
// continue
|
|
157
|
-
Promise.resolve(ret.value).then(onFulfilled, onRejected)
|
|
156
|
+
Promise.resolve(ret.value).then(onFulfilled, onRejected).catch(reject)
|
|
158
157
|
}
|
|
159
158
|
}
|
|
160
159
|
|
|
@@ -235,6 +234,6 @@ export function _async<A extends any[], R>(
|
|
|
235
234
|
* @param promise Promise.
|
|
236
235
|
* @returns
|
|
237
236
|
*/
|
|
238
|
-
export function _await<T>(promise: Promise<T>): Generator<Promise<T>, T, unknown> {
|
|
239
|
-
return
|
|
237
|
+
export function* _await<T>(promise: Promise<T>): Generator<Promise<T>, T, unknown> {
|
|
238
|
+
return (yield promise) as T
|
|
240
239
|
}
|
|
@@ -57,16 +57,16 @@ export function finishMutationBatch(): void {
|
|
|
57
57
|
if (typeof finishers === "function") {
|
|
58
58
|
finishers()
|
|
59
59
|
} else if (finishers) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
60
|
+
let lastError: { value: unknown } | undefined
|
|
61
|
+
for (let i = finishers.length - 1; i >= 0; i--) {
|
|
62
|
+
try {
|
|
63
|
+
finishers[i]()
|
|
64
|
+
} catch (value) {
|
|
65
|
+
lastError = { value }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (lastError) {
|
|
69
|
+
throw lastError.value
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { forEachWithDelayedThrow } from "../utils/forEachWithDelayedThrow"
|
|
1
2
|
import { getCurrentActionContext } from "./context"
|
|
2
3
|
import { getActionProtection } from "./protection"
|
|
3
4
|
|
|
@@ -32,11 +33,9 @@ export function tryRunPendingActions(): void {
|
|
|
32
33
|
pendingActionsRunning = true
|
|
33
34
|
|
|
34
35
|
try {
|
|
35
|
-
|
|
36
|
-
const nextAction = pendingActions.shift()!
|
|
37
|
-
nextAction()
|
|
38
|
-
}
|
|
36
|
+
forEachWithDelayedThrow(pendingActions, (nextAction) => nextAction())
|
|
39
37
|
} finally {
|
|
38
|
+
pendingActions.length = 0
|
|
40
39
|
pendingActionsRunning = false
|
|
41
40
|
}
|
|
42
41
|
}
|
|
@@ -2,6 +2,7 @@ import type { O } from "ts-toolbelt"
|
|
|
2
2
|
import type { ActionCall } from "../../action/applyAction"
|
|
3
3
|
import { assertTweakedObject } from "../../tweaker/core"
|
|
4
4
|
import { failure, isJSONPrimitive, isPlainObject } from "../../utils"
|
|
5
|
+
import { getSafeErrorValuePreview } from "../../utils/errorDiagnostics"
|
|
5
6
|
import type { JSONPrimitiveValue } from "../../utils/types"
|
|
6
7
|
import { arraySerializer } from "./arraySerializer"
|
|
7
8
|
import { type ActionCallArgumentSerializer, cannotSerialize } from "./core"
|
|
@@ -13,7 +14,7 @@ import { plainObjectSerializer } from "./plainObjectSerializer"
|
|
|
13
14
|
import { primitiveSerializer } from "./primitiveSerializer"
|
|
14
15
|
import { setSerializer } from "./setSerializer"
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
let serializersArray: ActionCallArgumentSerializer<any, any>[] = []
|
|
17
18
|
const serializersMap = new Map<string, ActionCallArgumentSerializer<any, any>>()
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -32,19 +33,22 @@ export function registerActionCallArgumentSerializer(
|
|
|
32
33
|
if (serializersArray.includes(serializer)) {
|
|
33
34
|
throw failure("action call argument serializer already registered")
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
const id = serializer.id
|
|
37
|
+
if (serializersMap.has(id)) {
|
|
36
38
|
throw failure(`action call argument serializer with id '${serializer.id}' already registered`)
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
serializersArray
|
|
40
|
-
serializersMap.set(
|
|
41
|
+
serializersArray = [serializer, ...serializersArray]
|
|
42
|
+
serializersMap.set(id, serializer)
|
|
41
43
|
|
|
44
|
+
let disposed = false
|
|
42
45
|
return () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
serializersArray.splice(index, 1)
|
|
46
|
+
if (disposed) {
|
|
47
|
+
return
|
|
46
48
|
}
|
|
47
|
-
|
|
49
|
+
disposed = true
|
|
50
|
+
serializersArray = serializersArray.filter((entry) => entry !== serializer)
|
|
51
|
+
serializersMap.delete(id)
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -106,13 +110,12 @@ export function serializeActionCallArgument(
|
|
|
106
110
|
return argValue
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
const origValue = argValue
|
|
110
|
-
|
|
111
113
|
const serialize = (v: any) => serializeActionCallArgument(v, targetRoot)
|
|
112
114
|
|
|
113
115
|
// try serializers
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const serializers = serializersArray
|
|
117
|
+
for (let i = 0; i < serializers.length; i++) {
|
|
118
|
+
const serializer = serializers[i]
|
|
116
119
|
const serializedValue = serializer.serialize(argValue, serialize, targetRoot)
|
|
117
120
|
if (serializedValue !== cannotSerialize) {
|
|
118
121
|
return {
|
|
@@ -122,7 +125,9 @@ export function serializeActionCallArgument(
|
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
throw failure(
|
|
128
|
+
throw failure(
|
|
129
|
+
`serializeActionCallArgument could not serialize the given value: ${getSafeErrorValuePreview(argValue)}`
|
|
130
|
+
)
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
/**
|
|
@@ -150,7 +155,7 @@ export function serializeActionCall(
|
|
|
150
155
|
return {
|
|
151
156
|
...actionCall,
|
|
152
157
|
serialized: true,
|
|
153
|
-
args: actionCall.args
|
|
158
|
+
args: Array.from(actionCall.args, serialize),
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
161
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { runInAction } from "mobx"
|
|
2
2
|
import { applyAction } from "../../action/applyAction"
|
|
3
3
|
import { frozenKey } from "../../frozen/Frozen"
|
|
4
|
+
import type { AnyModel } from "../../model/BaseModel"
|
|
4
5
|
import { getModelIdPropertyName } from "../../model/getModelMetadata"
|
|
5
6
|
import { isModel } from "../../model/utils"
|
|
6
|
-
import { resolvePath } from "../../parent/path"
|
|
7
|
+
import { fastGetParentToChildPath, resolvePath } from "../../parent/path"
|
|
7
8
|
import type { WritablePath } from "../../parent/pathTypes"
|
|
8
9
|
import { applyPatches } from "../../patch/applyPatches"
|
|
9
10
|
import { onPatches } from "../../patch/emitPatch"
|
|
@@ -53,16 +54,28 @@ export function applySerializedActionAndTrackNewModelIds<TRet = any>(
|
|
|
53
54
|
|
|
54
55
|
const deserializedCall = deserializeActionCall(call, subtreeRoot)
|
|
55
56
|
|
|
56
|
-
const
|
|
57
|
+
const modelsWithChangedIds = new Map<AnyModel, string>()
|
|
57
58
|
|
|
58
59
|
// set a patch listener to track changes to model ids
|
|
59
60
|
const patchDisposer = onPatches(subtreeRoot, (patches) => {
|
|
60
|
-
scanPatchesForModelIdChanges(subtreeRoot,
|
|
61
|
+
scanPatchesForModelIdChanges(subtreeRoot, modelsWithChangedIds, patches)
|
|
61
62
|
})
|
|
62
63
|
|
|
63
64
|
try {
|
|
64
65
|
const returnValue = applyAction(subtreeRoot, deserializedCall)
|
|
65
66
|
|
|
67
|
+
const modelIdOverrides: Patch[] = []
|
|
68
|
+
for (const [model, idProperty] of modelsWithChangedIds) {
|
|
69
|
+
const path = fastGetParentToChildPath(subtreeRoot, model, false)
|
|
70
|
+
if (path) {
|
|
71
|
+
modelIdOverrides.push({
|
|
72
|
+
op: "replace",
|
|
73
|
+
path: [...path, idProperty],
|
|
74
|
+
value: model.$[idProperty],
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
return {
|
|
67
80
|
returnValue,
|
|
68
81
|
serializedActionCall: {
|
|
@@ -75,24 +88,23 @@ export function applySerializedActionAndTrackNewModelIds<TRet = any>(
|
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
function scanPatchesForModelIdChanges(
|
|
91
|
+
function scanPatchesForModelIdChanges(
|
|
92
|
+
root: object,
|
|
93
|
+
modelsWithChangedIds: Map<AnyModel, string>,
|
|
94
|
+
patches: Patch[]
|
|
95
|
+
) {
|
|
79
96
|
const len = patches.length
|
|
80
97
|
for (let i = 0; i < len; i++) {
|
|
81
98
|
const patch = patches[i]
|
|
82
99
|
if (patch.op === "replace" || patch.op === "add") {
|
|
83
|
-
deepScanValueForModelIdChanges(
|
|
84
|
-
root,
|
|
85
|
-
modelIdOverrides,
|
|
86
|
-
patch.value,
|
|
87
|
-
patch.path as WritablePath
|
|
88
|
-
)
|
|
100
|
+
deepScanValueForModelIdChanges(root, modelsWithChangedIds, patch.value, patch.path.slice())
|
|
89
101
|
}
|
|
90
102
|
}
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
function deepScanValueForModelIdChanges(
|
|
94
106
|
root: object,
|
|
95
|
-
|
|
107
|
+
modelsWithChangedIds: Map<AnyModel, string>,
|
|
96
108
|
value: any,
|
|
97
109
|
path: WritablePath
|
|
98
110
|
) {
|
|
@@ -102,20 +114,17 @@ function deepScanValueForModelIdChanges(
|
|
|
102
114
|
|
|
103
115
|
if (isModel(parent)) {
|
|
104
116
|
const propertyName = path[path.length - 1]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
path: path.slice(),
|
|
110
|
-
value: value,
|
|
111
|
-
})
|
|
117
|
+
const idProperty = getModelIdPropertyName(parent.constructor as any)
|
|
118
|
+
if (idProperty !== undefined && propertyName === idProperty) {
|
|
119
|
+
// Resolve the final path and ID after the action, since this model may move or detach.
|
|
120
|
+
modelsWithChangedIds.set(parent, idProperty)
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
} else if (Array.isArray(value)) {
|
|
115
124
|
const len = value.length
|
|
116
125
|
for (let i = 0; i < len; i++) {
|
|
117
126
|
path.push(i)
|
|
118
|
-
deepScanValueForModelIdChanges(root,
|
|
127
|
+
deepScanValueForModelIdChanges(root, modelsWithChangedIds, value[i], path)
|
|
119
128
|
path.pop()
|
|
120
129
|
}
|
|
121
130
|
} else if (isObject(value)) {
|
|
@@ -128,7 +137,7 @@ function deepScanValueForModelIdChanges(
|
|
|
128
137
|
const propValue = value[propName]
|
|
129
138
|
|
|
130
139
|
path.push(propName)
|
|
131
|
-
deepScanValueForModelIdChanges(root,
|
|
140
|
+
deepScanValueForModelIdChanges(root, modelsWithChangedIds, propValue, path)
|
|
132
141
|
path.pop()
|
|
133
142
|
}
|
|
134
143
|
}
|
|
@@ -11,7 +11,7 @@ export const arraySerializer: ActionCallArgumentSerializer<any[] | IObservableAr
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// this will also transform observable arrays into non-observable ones
|
|
14
|
-
return
|
|
14
|
+
return Array.from(value, serialize)
|
|
15
15
|
},
|
|
16
16
|
|
|
17
17
|
deserialize(arr, deserialize) {
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { namespace } from "../../utils"
|
|
2
2
|
import { type ActionCallArgumentSerializer, cannotSerialize } from "./core"
|
|
3
3
|
|
|
4
|
-
export const dateSerializer: ActionCallArgumentSerializer<Date, number> = {
|
|
4
|
+
export const dateSerializer: ActionCallArgumentSerializer<Date, number | null> = {
|
|
5
5
|
id: `${namespace}/dateAsTimestamp`,
|
|
6
6
|
|
|
7
7
|
serialize(date) {
|
|
8
8
|
if (!(date instanceof Date)) {
|
|
9
9
|
return cannotSerialize
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
const timestamp = +date
|
|
12
|
+
return Number.isNaN(timestamp) ? null : timestamp
|
|
12
13
|
},
|
|
13
14
|
|
|
14
15
|
deserialize(timestamp) {
|
|
15
|
-
return new Date(timestamp)
|
|
16
|
+
return new Date(timestamp === null ? Number.NaN : timestamp)
|
|
16
17
|
},
|
|
17
18
|
}
|
|
@@ -15,13 +15,8 @@ export const mapSerializer: ActionCallArgumentSerializer<
|
|
|
15
15
|
|
|
16
16
|
const arr: [any, any][] = []
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
while (!cur.done) {
|
|
21
|
-
const k = cur.value
|
|
22
|
-
const v = map.get(k)
|
|
23
|
-
arr.push([serialize(k), serialize(v)])
|
|
24
|
-
cur = iter.next()
|
|
18
|
+
for (const [key, value] of map.entries()) {
|
|
19
|
+
arr.push([serialize(key), serialize(value)])
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
return arr
|
|
@@ -30,11 +25,8 @@ export const mapSerializer: ActionCallArgumentSerializer<
|
|
|
30
25
|
deserialize(arr, deserialize) {
|
|
31
26
|
const map = new Map()
|
|
32
27
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const k = arr[i][0]
|
|
36
|
-
const v = arr[i][1]
|
|
37
|
-
map.set(deserialize(k), deserialize(v))
|
|
28
|
+
for (const [key, value] of arr) {
|
|
29
|
+
map.set(deserialize(key), deserialize(value))
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
return map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { isObservableSet, type ObservableSet } from "mobx"
|
|
2
2
|
import { namespace } from "../../utils"
|
|
3
3
|
import { type ActionCallArgumentSerializer, cannotSerialize } from "./core"
|
|
4
4
|
|
|
@@ -6,21 +6,12 @@ export const setSerializer: ActionCallArgumentSerializer<Set<any> | ObservableSe
|
|
|
6
6
|
id: `${namespace}/setAsArray`,
|
|
7
7
|
|
|
8
8
|
serialize(set, serialize) {
|
|
9
|
-
if (!(set instanceof Set)) {
|
|
9
|
+
if (!(set instanceof Set || isObservableSet(set))) {
|
|
10
10
|
return cannotSerialize
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const iter = set.keys()
|
|
16
|
-
let cur = iter.next()
|
|
17
|
-
while (!cur.done) {
|
|
18
|
-
const k = cur.value
|
|
19
|
-
arr.push(serialize(k))
|
|
20
|
-
cur = iter.next()
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return arr
|
|
13
|
+
// Array.from closes the iterator if an element cannot be serialized.
|
|
14
|
+
return Array.from(set.keys(), serialize)
|
|
24
15
|
},
|
|
25
16
|
|
|
26
17
|
deserialize(arr, deserialize) {
|