mobx-keystone 2.3.1 → 2.4.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 +16 -0
- package/dist/mobx-keystone.esm.js +5157 -3981
- package/dist/mobx-keystone.esm.mjs +5157 -3981
- package/dist/mobx-keystone.umd.js +5170 -3980
- package/dist/types/action/CallKind.d.ts +29 -0
- package/dist/types/action/applyAction.d.ts +7 -1
- package/dist/types/action/index.d.ts +1 -0
- package/dist/types/action/resolveCallName.d.ts +1 -0
- package/dist/types/actionMiddlewares/actionSerialization/actionSerialization.d.ts +14 -0
- package/dist/types/actionMiddlewares/actionSerialization/applySerializedAction.d.ts +6 -0
- package/dist/types/actionMiddlewares/actionSerialization/core.d.ts +7 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/patch/emitPatch.d.ts +6 -0
- package/dist/types/serialization/Call.d.ts +55 -0
- package/dist/types/serialization/CallError.d.ts +96 -0
- package/dist/types/serialization/JsonValue.d.ts +6 -0
- package/dist/types/serialization/Serializer.d.ts +84 -0
- package/dist/types/serialization/ValueSerializer.d.ts +88 -0
- package/dist/types/serialization/applyCall.d.ts +54 -0
- package/dist/types/serialization/builtinSerializers.d.ts +49 -0
- package/dist/types/serialization/codec.d.ts +1 -0
- package/dist/types/serialization/index.d.ts +8 -0
- package/dist/types/serialization/references.d.ts +74 -0
- package/dist/types/serialization/resolvedCall.d.ts +1 -0
- package/package.json +1 -1
- package/src/action/CallKind.ts +29 -0
- package/src/action/applyAction.ts +22 -49
- package/src/action/applySet.ts +21 -1
- package/src/action/index.ts +1 -0
- package/src/action/resolveCallName.ts +207 -0
- package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +14 -0
- package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +6 -0
- package/src/actionMiddlewares/actionSerialization/core.ts +7 -0
- package/src/actionMiddlewares/actionSerialization/index.ts +2 -0
- package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +3 -2
- package/src/index.ts +1 -0
- package/src/parent/path.ts +45 -12
- package/src/patch/emitPatch.ts +6 -0
- package/src/serialization/Call.ts +58 -0
- package/src/serialization/CallError.ts +153 -0
- package/src/serialization/JsonValue.ts +12 -0
- package/src/serialization/Serializer.ts +321 -0
- package/src/serialization/ValueSerializer.ts +98 -0
- package/src/serialization/applyCall.ts +190 -0
- package/src/serialization/builtinSerializers.ts +261 -0
- package/src/serialization/codec.ts +338 -0
- package/src/serialization/index.ts +15 -0
- package/src/serialization/references.ts +225 -0
- package/src/serialization/resolvedCall.ts +169 -0
- package/src/standardActions/actions.ts +11 -3
- package/src/utils/errorDiagnostics.ts +9 -4
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { type CallResolution, resolveCallName } from "../action/resolveCallName"
|
|
2
|
+
import { fastGetRootPath, isRoot } from "../parent/path"
|
|
3
|
+
import { isTreeNode } from "../tweaker/core"
|
|
4
|
+
import { failure, isArray } from "../utils"
|
|
5
|
+
import type { Call, ResolvedCall } from "./Call"
|
|
6
|
+
import { CallError, CallErrorCode } from "./CallError"
|
|
7
|
+
import type { JsonValue } from "./JsonValue"
|
|
8
|
+
import { isNodeOfTree } from "./references"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export interface ResolvedCallData {
|
|
14
|
+
readonly resolution: CallResolution
|
|
15
|
+
/**
|
|
16
|
+
* Whether the call has `args`, known without decoding them.
|
|
17
|
+
*/
|
|
18
|
+
readonly hasArgs: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const resolvedCallData = new WeakMap<object, ResolvedCallData>()
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function getResolvedCallData(call: object): ResolvedCallData | undefined {
|
|
27
|
+
return resolvedCallData.get(call)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export function assertIsTreeRoot(root: object): void {
|
|
34
|
+
if (!isTreeNode(root) || !isRoot(root)) {
|
|
35
|
+
throw failure("root must be a tree root")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function describeTargetLocation(target: unknown): string {
|
|
40
|
+
if (!isTreeNode(target)) {
|
|
41
|
+
return "it is not a tree node"
|
|
42
|
+
}
|
|
43
|
+
const { path } = fastGetRootPath(target, false)
|
|
44
|
+
return path.length === 0
|
|
45
|
+
? "it is the root of another tree (or detached)"
|
|
46
|
+
: `it is at path ${JSON.stringify(path)} of another tree`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @internal
|
|
51
|
+
*
|
|
52
|
+
* Throws `NotFound` unless the call target is a node of the root's tree.
|
|
53
|
+
*/
|
|
54
|
+
export function assertIsTargetInTree(target: unknown, root: object): asserts target is object {
|
|
55
|
+
if (!isNodeOfTree(target, root)) {
|
|
56
|
+
throw new CallError({
|
|
57
|
+
code: CallErrorCode.NotFound,
|
|
58
|
+
message: `the call target is not a node of the root's tree: ${describeTargetLocation(target)}`,
|
|
59
|
+
path: ["target"],
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @internal
|
|
66
|
+
*
|
|
67
|
+
* Resolves a call's name on its target, throwing `UnknownName` when nothing can be called.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveCallNameOrThrow(target: object, name: string): CallResolution {
|
|
70
|
+
let resolution: ReturnType<typeof resolveCallName>
|
|
71
|
+
try {
|
|
72
|
+
resolution = resolveCallName(target, name)
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// a data model member on a target that isn't valid data for the data model
|
|
75
|
+
throw new CallError({
|
|
76
|
+
code: CallErrorCode.WrongKind,
|
|
77
|
+
message: `'${name}' cannot be called on the target: its data model can't be created over it`,
|
|
78
|
+
path: ["target"],
|
|
79
|
+
cause: e,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
if (resolution === "hook") {
|
|
83
|
+
throw new CallError({
|
|
84
|
+
code: CallErrorCode.UnknownName,
|
|
85
|
+
message: `calls to hooks (${name}) cannot be applied`,
|
|
86
|
+
path: ["name"],
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
if (resolution === "unknown") {
|
|
90
|
+
throw new CallError({
|
|
91
|
+
code: CallErrorCode.UnknownName,
|
|
92
|
+
message: `'${name}' cannot be called on the target: it is not an action or a view`,
|
|
93
|
+
path: ["name"],
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
return resolution
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @internal
|
|
101
|
+
*
|
|
102
|
+
* Arguments still to decode: `decode` runs once, when `args` is first read.
|
|
103
|
+
*/
|
|
104
|
+
export interface EncodedArgs {
|
|
105
|
+
readonly rawArgs: JsonValue[]
|
|
106
|
+
decode(): unknown[]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @internal
|
|
111
|
+
*
|
|
112
|
+
* Creates a resolved call, with its arguments, or with arguments still to decode.
|
|
113
|
+
*/
|
|
114
|
+
export function createResolvedCall(
|
|
115
|
+
target: object,
|
|
116
|
+
name: string,
|
|
117
|
+
resolution: CallResolution,
|
|
118
|
+
args: unknown[] | EncodedArgs | undefined
|
|
119
|
+
): ResolvedCall {
|
|
120
|
+
const call: { -readonly [K in keyof ResolvedCall]: ResolvedCall[K] } = {
|
|
121
|
+
target,
|
|
122
|
+
name,
|
|
123
|
+
kind: resolution.kind,
|
|
124
|
+
isFlow: resolution.isFlow,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (args === undefined || isArray(args)) {
|
|
128
|
+
call.args = args
|
|
129
|
+
} else {
|
|
130
|
+
call.rawArgs = args.rawArgs
|
|
131
|
+
let decodedArgs: unknown[] | undefined
|
|
132
|
+
Object.defineProperty(call, "args", {
|
|
133
|
+
enumerable: true,
|
|
134
|
+
get: () => (decodedArgs ??= args.decode()),
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Object.freeze(call)
|
|
139
|
+
resolvedCallData.set(call, { resolution, hasArgs: args !== undefined })
|
|
140
|
+
return call
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @internal
|
|
145
|
+
*
|
|
146
|
+
* Checks the name and args of a call built in process.
|
|
147
|
+
*/
|
|
148
|
+
export function assertIsCallShape(call: Call): void {
|
|
149
|
+
if (typeof call.name !== "string") {
|
|
150
|
+
throw failure("call name must be a string")
|
|
151
|
+
}
|
|
152
|
+
if (call.args !== undefined && !isArray(call.args)) {
|
|
153
|
+
throw failure("call args must be an array or undefined")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @internal
|
|
159
|
+
*
|
|
160
|
+
* Resolves a call built in process (its target must be a node of the tree).
|
|
161
|
+
*/
|
|
162
|
+
export function toResolvedCall(call: Call): ResolvedCall {
|
|
163
|
+
return createResolvedCall(
|
|
164
|
+
call.target,
|
|
165
|
+
call.name,
|
|
166
|
+
resolveCallNameOrThrow(call.target, call.name),
|
|
167
|
+
call.args
|
|
168
|
+
)
|
|
169
|
+
}
|
|
@@ -12,12 +12,20 @@ import { copyFunctionMetadata } from "../utils/decorators"
|
|
|
12
12
|
*/
|
|
13
13
|
type TargetedAction = AnyFunction
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export interface StandaloneActionEntry {
|
|
19
|
+
readonly fn: TargetedAction
|
|
20
|
+
readonly isFlow: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const standaloneActionRegistry = new Map<string, StandaloneActionEntry>()
|
|
16
24
|
|
|
17
25
|
/**
|
|
18
26
|
* @internal
|
|
19
27
|
*/
|
|
20
|
-
export function getStandaloneAction(actionName: string) {
|
|
28
|
+
export function getStandaloneAction(actionName: string): StandaloneActionEntry | undefined {
|
|
21
29
|
return standaloneActionRegistry.get(actionName)
|
|
22
30
|
}
|
|
23
31
|
|
|
@@ -59,6 +67,6 @@ export function addStandaloneAction(fullActionName: string, fn: TargetedAction,
|
|
|
59
67
|
// preserve user-provided metadata without leaking internal action/flow markers.
|
|
60
68
|
copyFunctionMetadata(fn, finalAction)
|
|
61
69
|
|
|
62
|
-
standaloneActionRegistry.set(fullActionName, finalAction)
|
|
70
|
+
standaloneActionRegistry.set(fullActionName, { fn: finalAction, isFlow })
|
|
63
71
|
return finalAction
|
|
64
72
|
}
|
|
@@ -63,11 +63,16 @@ export function getCurrentErrorDiagnosticsContext(): ErrorDiagnosticsContext | u
|
|
|
63
63
|
* @internal
|
|
64
64
|
*/
|
|
65
65
|
export function runWithErrorDiagnosticsContext<T>(fn: () => T): T {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return fn()
|
|
69
|
-
}
|
|
66
|
+
return getCurrentErrorDiagnosticsContext() ? fn() : runWithNewErrorDiagnosticsContext(fn)
|
|
67
|
+
}
|
|
70
68
|
|
|
69
|
+
/**
|
|
70
|
+
* @internal
|
|
71
|
+
*
|
|
72
|
+
* Like `runWithErrorDiagnosticsContext`, but always with a new context, so paths start empty
|
|
73
|
+
* even when called while another context is active.
|
|
74
|
+
*/
|
|
75
|
+
export function runWithNewErrorDiagnosticsContext<T>(fn: () => T): T {
|
|
71
76
|
const ctx = new ErrorDiagnosticsContext()
|
|
72
77
|
errorDiagnosticsContextStack.push(ctx)
|
|
73
78
|
try {
|