mobx-keystone 2.3.1 → 2.4.1

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/mobx-keystone.esm.js +3604 -2414
  3. package/dist/mobx-keystone.esm.mjs +3604 -2414
  4. package/dist/mobx-keystone.umd.js +3685 -2481
  5. package/dist/types/action/CallKind.d.ts +29 -0
  6. package/dist/types/action/applyAction.d.ts +7 -1
  7. package/dist/types/action/index.d.ts +1 -0
  8. package/dist/types/action/resolveCallName.d.ts +1 -0
  9. package/dist/types/actionMiddlewares/actionSerialization/actionSerialization.d.ts +14 -0
  10. package/dist/types/actionMiddlewares/actionSerialization/applySerializedAction.d.ts +6 -0
  11. package/dist/types/actionMiddlewares/actionSerialization/core.d.ts +7 -0
  12. package/dist/types/index.d.ts +1 -0
  13. package/dist/types/patch/emitPatch.d.ts +6 -0
  14. package/dist/types/serialization/Call.d.ts +55 -0
  15. package/dist/types/serialization/CallError.d.ts +96 -0
  16. package/dist/types/serialization/JsonValue.d.ts +6 -0
  17. package/dist/types/serialization/Serializer.d.ts +98 -0
  18. package/dist/types/serialization/ValueSerializer.d.ts +93 -0
  19. package/dist/types/serialization/applyCall.d.ts +54 -0
  20. package/dist/types/serialization/builtinSerializers.d.ts +53 -0
  21. package/dist/types/serialization/codec.d.ts +1 -0
  22. package/dist/types/serialization/index.d.ts +8 -0
  23. package/dist/types/serialization/references.d.ts +74 -0
  24. package/dist/types/serialization/resolvedCall.d.ts +1 -0
  25. package/package.json +1 -1
  26. package/src/action/CallKind.ts +29 -0
  27. package/src/action/applyAction.ts +22 -49
  28. package/src/action/applySet.ts +21 -1
  29. package/src/action/index.ts +1 -0
  30. package/src/action/resolveCallName.ts +207 -0
  31. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +14 -0
  32. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +6 -0
  33. package/src/actionMiddlewares/actionSerialization/core.ts +7 -0
  34. package/src/actionMiddlewares/actionSerialization/index.ts +2 -0
  35. package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +3 -2
  36. package/src/index.ts +1 -0
  37. package/src/parent/path.ts +45 -12
  38. package/src/patch/emitPatch.ts +6 -0
  39. package/src/serialization/Call.ts +58 -0
  40. package/src/serialization/CallError.ts +153 -0
  41. package/src/serialization/JsonValue.ts +12 -0
  42. package/src/serialization/Serializer.ts +342 -0
  43. package/src/serialization/ValueSerializer.ts +104 -0
  44. package/src/serialization/applyCall.ts +190 -0
  45. package/src/serialization/builtinSerializers.ts +264 -0
  46. package/src/serialization/codec.ts +360 -0
  47. package/src/serialization/index.ts +15 -0
  48. package/src/serialization/references.ts +225 -0
  49. package/src/serialization/resolvedCall.ts +169 -0
  50. package/src/standardActions/actions.ts +11 -3
  51. 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
- const standaloneActionRegistry = new Map<string, TargetedAction>()
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
- const existing = getCurrentErrorDiagnosticsContext()
67
- if (existing) {
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 {