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.
Files changed (51) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/mobx-keystone.esm.js +5157 -3981
  3. package/dist/mobx-keystone.esm.mjs +5157 -3981
  4. package/dist/mobx-keystone.umd.js +5170 -3980
  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 +84 -0
  18. package/dist/types/serialization/ValueSerializer.d.ts +88 -0
  19. package/dist/types/serialization/applyCall.d.ts +54 -0
  20. package/dist/types/serialization/builtinSerializers.d.ts +49 -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 +321 -0
  43. package/src/serialization/ValueSerializer.ts +98 -0
  44. package/src/serialization/applyCall.ts +190 -0
  45. package/src/serialization/builtinSerializers.ts +261 -0
  46. package/src/serialization/codec.ts +338 -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,338 @@
1
+ import { frozenKey } from "../frozen/Frozen"
2
+ import { modelTypeKey } from "../model/metadata"
3
+ import type { PathElement } from "../parent/pathTypes"
4
+ import { copyOwnEnumerableProps, hasOwnProp, isArray, isObject, isPlainObject } from "../utils"
5
+ import { getCurrentErrorDiagnosticsContext } from "../utils/errorDiagnostics"
6
+ import { builtinEncodedTypeof, unmarkedEncodingSymbol } from "./builtinSerializers"
7
+ import { badPayloadError, CallError, CallErrorCode } from "./CallError"
8
+ import type { JsonValue } from "./JsonValue"
9
+ import type { DecodeContext, EncodeContext, ValueSerializer } from "./ValueSerializer"
10
+ import { serializationMarkerKey } from "./ValueSerializer"
11
+
12
+ /**
13
+ * @internal
14
+ *
15
+ * The tag of an escaped plain object: `{ "$mobxKeystone": "object", "v": { ...fields } }`.
16
+ */
17
+ export const escapedObjectTag = "object"
18
+
19
+ const reservedKeys: readonly string[] = [serializationMarkerKey, modelTypeKey, frozenKey]
20
+
21
+ /**
22
+ * @internal
23
+ */
24
+ export interface SerializerConfig {
25
+ /**
26
+ * Custom serializers followed by the built-ins (if any).
27
+ */
28
+ readonly serializers: readonly ValueSerializer[]
29
+ /**
30
+ * The serializers that could encode a value, by its `typeof`, or `"plain"` for plain arrays and
31
+ * objects, which no built-in encodes (custom ones are in every list).
32
+ */
33
+ readonly byTypeof: ReadonlyMap<SerializerGroup, readonly ValueSerializer[]>
34
+ readonly byTag: ReadonlyMap<string, ValueSerializer>
35
+ /**
36
+ * The serializer that decodes values with `$modelType` / `$frozen` (the snapshot serializer),
37
+ * if in the list.
38
+ */
39
+ readonly unmarked: ValueSerializer | undefined
40
+ }
41
+
42
+ const typeofResults = [
43
+ "string",
44
+ "number",
45
+ "bigint",
46
+ "boolean",
47
+ "symbol",
48
+ "undefined",
49
+ "object",
50
+ "function",
51
+ ] as const
52
+
53
+ /**
54
+ * @internal
55
+ */
56
+ export type TypeofResult = (typeof typeofResults)[number]
57
+
58
+ type SerializerGroup = TypeofResult | "plain"
59
+
60
+ /**
61
+ * @internal
62
+ *
63
+ * Groups serializers by the `typeof` of the values they could encode, plus `"plain"` for plain
64
+ * arrays and objects, keeping their order.
65
+ */
66
+ export function groupSerializersByTypeof(
67
+ serializers: readonly ValueSerializer[]
68
+ ): ReadonlyMap<SerializerGroup, readonly ValueSerializer[]> {
69
+ const byTypeof = new Map<SerializerGroup, readonly ValueSerializer[]>(
70
+ typeofResults.map((t) => [
71
+ t,
72
+ serializers.filter((s) => {
73
+ const encodedTypeof = builtinEncodedTypeof.get(s)
74
+ return encodedTypeof === undefined || encodedTypeof === t
75
+ }),
76
+ ])
77
+ )
78
+ byTypeof.set(
79
+ "plain",
80
+ serializers.filter((s) => !builtinEncodedTypeof.has(s))
81
+ )
82
+ return byTypeof
83
+ }
84
+
85
+ type PlainKind = "array" | "object" | undefined
86
+
87
+ function getPlainKind(value: unknown): PlainKind {
88
+ if (!isObject(value)) {
89
+ return undefined
90
+ }
91
+ if (isArray(value)) {
92
+ return "array"
93
+ }
94
+ return isPlainObject(value) ? "object" : undefined
95
+ }
96
+
97
+ /**
98
+ * Runs `fn` with `key` pushed to the error path (without allocating a closure per value).
99
+ */
100
+ function withPathSegment<A, R>(key: PathElement, fn: (arg: A) => R, arg: A): R {
101
+ const diagnostics = getCurrentErrorDiagnosticsContext()
102
+ if (!diagnostics) {
103
+ return fn(arg)
104
+ }
105
+ diagnostics.pushPath(key)
106
+ try {
107
+ return fn(arg)
108
+ } finally {
109
+ diagnostics.popPath()
110
+ }
111
+ }
112
+
113
+ /**
114
+ * @internal
115
+ */
116
+ export function isUnmarkedSerializer(serializer: ValueSerializer): boolean {
117
+ return unmarkedEncodingSymbol in serializer
118
+ }
119
+
120
+ /**
121
+ * @internal
122
+ */
123
+ export function markValue(tag: string, payload: JsonValue | undefined): JsonValue {
124
+ return payload === undefined
125
+ ? { [serializationMarkerKey]: tag }
126
+ : { [serializationMarkerKey]: tag, v: payload }
127
+ }
128
+
129
+ /**
130
+ * @internal
131
+ */
132
+ export function wrapSerializerError(
133
+ error: unknown,
134
+ code: CallErrorCode,
135
+ tag: string,
136
+ value: unknown
137
+ ): CallError {
138
+ if (error instanceof CallError) {
139
+ return error
140
+ }
141
+ const op = code === CallErrorCode.CannotEncode ? "encode" : "decode"
142
+ return new CallError({
143
+ code,
144
+ message: `the '${tag}' serializer failed to ${op} the value`,
145
+ previewValue: value,
146
+ cause: error,
147
+ })
148
+ }
149
+
150
+ function describeValue(value: unknown): string {
151
+ switch (typeof value) {
152
+ case "function":
153
+ return "a function"
154
+ case "symbol":
155
+ return "a symbol"
156
+ case "object": {
157
+ const proto = value === null ? null : Object.getPrototypeOf(value)
158
+ const name = proto?.constructor?.name
159
+ return name ? `an instance of ${name}` : "an object"
160
+ }
161
+ default:
162
+ return String(value)
163
+ }
164
+ }
165
+
166
+ /**
167
+ * @internal
168
+ *
169
+ * Creates the encoding context for one encoding: `ctx.encode` encodes any value, going through
170
+ * the list of serializers. It must run inside an error diagnostics context.
171
+ */
172
+ export function createEncodeContext(config: SerializerConfig, root: object): EncodeContext {
173
+ // values being encoded, to detect cycles
174
+ const ancestors = new Set<unknown>()
175
+
176
+ const encodeWithSerializer = (serializer: ValueSerializer, value: unknown): JsonValue => {
177
+ let payload: JsonValue | undefined
178
+ try {
179
+ payload = serializer.encode(value, ctx)
180
+ } catch (e) {
181
+ throw wrapSerializerError(e, CallErrorCode.CannotEncode, serializer.tag, value)
182
+ }
183
+ return isUnmarkedSerializer(serializer) ? payload! : markValue(serializer.tag, payload)
184
+ }
185
+
186
+ const encodePlain = (value: unknown, plainKind: PlainKind): JsonValue => {
187
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
188
+ return value
189
+ }
190
+ if (typeof value === "number" && Number.isFinite(value)) {
191
+ return value
192
+ }
193
+ if (plainKind === "array") {
194
+ const array = value as readonly unknown[]
195
+ const len = array.length
196
+ const out: JsonValue[] = new Array(len)
197
+ for (let i = 0; i < len; i++) {
198
+ out[i] = ctx.encode(array[i], i)
199
+ }
200
+ return out
201
+ }
202
+ if (plainKind === "object") {
203
+ const object = value as Record<string, unknown>
204
+ const out = copyOwnEnumerableProps({} as Record<string, JsonValue>, object, ctx.encode)
205
+ return reservedKeys.some((k) => hasOwnProp(object, k))
206
+ ? markValue(escapedObjectTag, out)
207
+ : out
208
+ }
209
+ throw new CallError({
210
+ code: CallErrorCode.CannotEncode,
211
+ message: `cannot encode ${describeValue(value)}: no serializer handles it`,
212
+ previewValue: value,
213
+ })
214
+ }
215
+
216
+ const encodeAny = (value: unknown): JsonValue => {
217
+ const plainKind = getPlainKind(value)
218
+ const serializers = config.byTypeof.get(plainKind ? "plain" : typeof value)!
219
+ const len = serializers.length
220
+ for (let i = 0; i < len; i++) {
221
+ const serializer = serializers[i]
222
+ let claimed: boolean
223
+ try {
224
+ claimed = serializer.is(value, ctx)
225
+ } catch (e) {
226
+ throw wrapSerializerError(e, CallErrorCode.CannotEncode, serializer.tag, value)
227
+ }
228
+ if (claimed) {
229
+ return encodeWithSerializer(serializer, value)
230
+ }
231
+ }
232
+ return encodePlain(value, plainKind)
233
+ }
234
+
235
+ const encodeChecked = (value: unknown): JsonValue => {
236
+ if (!isObject(value) && typeof value !== "function") {
237
+ return encodeAny(value)
238
+ }
239
+ if (ancestors.has(value)) {
240
+ throw new CallError({
241
+ code: CallErrorCode.CannotEncode,
242
+ message: "cannot encode a value that contains itself",
243
+ })
244
+ }
245
+ ancestors.add(value)
246
+ try {
247
+ return encodeAny(value)
248
+ } finally {
249
+ ancestors.delete(value)
250
+ }
251
+ }
252
+
253
+ const ctx: EncodeContext = {
254
+ root,
255
+ encode(value: unknown, key?: PathElement): JsonValue {
256
+ return key === undefined ? encodeChecked(value) : withPathSegment(key, encodeChecked, value)
257
+ },
258
+ }
259
+
260
+ return ctx
261
+ }
262
+
263
+ /**
264
+ * @internal
265
+ *
266
+ * Creates the decoding context for one decoding: `ctx.decode` decodes any JSON value. It must run
267
+ * inside an error diagnostics context.
268
+ */
269
+ export function createDecodeContext(config: SerializerConfig, root: object): DecodeContext {
270
+ const decodeWithSerializer = (serializer: ValueSerializer, json: unknown): unknown => {
271
+ try {
272
+ return serializer.decode(json, ctx)
273
+ } catch (e) {
274
+ throw wrapSerializerError(e, CallErrorCode.BadArgument, serializer.tag, json)
275
+ }
276
+ }
277
+
278
+ const decodeFields = (json: Record<string, unknown>): Record<string, unknown> =>
279
+ copyOwnEnumerableProps({} as Record<string, unknown>, json, (v, k) =>
280
+ ctx.decode(v as JsonValue, k)
281
+ )
282
+
283
+ const decodeMarked = (json: Record<string, unknown>): unknown => {
284
+ const tag = json[serializationMarkerKey]
285
+ if (tag === escapedObjectTag) {
286
+ if (!isPlainObject(json.v)) {
287
+ throw badPayloadError(escapedObjectTag, json)
288
+ }
289
+ return decodeFields(json.v)
290
+ }
291
+
292
+ const serializer = typeof tag === "string" ? config.byTag.get(tag) : undefined
293
+ if (!serializer) {
294
+ throw new CallError({
295
+ code: CallErrorCode.BadArgument,
296
+ message: `no serializer handles the tag ${JSON.stringify(tag)}`,
297
+ previewValue: json,
298
+ })
299
+ }
300
+ return decodeWithSerializer(serializer, json.v)
301
+ }
302
+
303
+ const decodeAny = (json: unknown): unknown => {
304
+ if (
305
+ json === null ||
306
+ typeof json === "string" ||
307
+ typeof json === "number" ||
308
+ typeof json === "boolean"
309
+ ) {
310
+ return json
311
+ }
312
+ if (isArray(json)) {
313
+ return json.map((v, i) => ctx.decode(v, i))
314
+ }
315
+ if (isPlainObject(json)) {
316
+ if (hasOwnProp(json, serializationMarkerKey)) {
317
+ return decodeMarked(json)
318
+ }
319
+ if (config.unmarked && (hasOwnProp(json, modelTypeKey) || hasOwnProp(json, frozenKey))) {
320
+ return decodeWithSerializer(config.unmarked, json)
321
+ }
322
+ return decodeFields(json)
323
+ }
324
+ throw new CallError({
325
+ code: CallErrorCode.BadArgument,
326
+ message: `cannot decode ${describeValue(json)}: it is not a JSON value`,
327
+ previewValue: json,
328
+ })
329
+ }
330
+
331
+ const ctx: DecodeContext = {
332
+ root,
333
+ decode: (json: JsonValue, key?: PathElement): unknown =>
334
+ key === undefined ? decodeAny(json) : withPathSegment(key, decodeAny, json),
335
+ }
336
+
337
+ return ctx
338
+ }
@@ -0,0 +1,15 @@
1
+ export * from "./applyCall"
2
+ export { builtinSerializers } from "./builtinSerializers"
3
+ export * from "./Call"
4
+ export { CallError, CallErrorCode, type CallErrorData, type CallErrorJson } from "./CallError"
5
+ export * from "./JsonValue"
6
+ export {
7
+ type IdRefJson,
8
+ idRefs,
9
+ type PathRefJson,
10
+ pathRefs,
11
+ type ReferenceNodeInfo,
12
+ type ReferenceSerializerOptions,
13
+ } from "./references"
14
+ export * from "./Serializer"
15
+ export * from "./ValueSerializer"
@@ -0,0 +1,225 @@
1
+ import { rootPathToTargetPathIds } from "../actionMiddlewares/utils"
2
+ import type { AnyModel } from "../model/BaseModel"
3
+ import { getModelMetadata } from "../model/getModelMetadata"
4
+ import { modelTypeKey } from "../model/metadata"
5
+ import { isModel } from "../model/utils"
6
+ import {
7
+ fastGetRoot,
8
+ fastGetRootPath,
9
+ isPathElement,
10
+ resolvePathCheckingIds,
11
+ skipIdChecking,
12
+ } from "../parent/path"
13
+ import type { Path, PathElement } from "../parent/pathTypes"
14
+ import { getModelRefId, resolveId } from "../ref/core"
15
+ import { isTreeNode } from "../tweaker/core"
16
+ import { isArray, isPlainObject } from "../utils"
17
+ import { badPayloadError, CallError, CallErrorCode } from "./CallError"
18
+ import type { ValueSerializer } from "./ValueSerializer"
19
+
20
+ /**
21
+ * What a reference serializer's `when` option is told about a node.
22
+ */
23
+ export interface ReferenceNodeInfo {
24
+ /**
25
+ * Whether the node is a model.
26
+ */
27
+ readonly isModel: boolean
28
+ /**
29
+ * Whether the node is a value-type model (`Model({...}, { valueType: true })`).
30
+ */
31
+ readonly isValueType: boolean
32
+ }
33
+
34
+ /**
35
+ * Options of the built-in reference serializers.
36
+ */
37
+ export interface ReferenceSerializerOptions {
38
+ /**
39
+ * Decides, for every node the serializer could reference, whether it does. A node it declines
40
+ * falls through the list like any other value: a model ends up as its snapshot, a tree array
41
+ * or plain object is walked. The call's target ignores it: it is always a reference.
42
+ *
43
+ * Defaults to declining value-type models: `(node, { isValueType }) => !isValueType`.
44
+ */
45
+ when?(node: object, info: ReferenceNodeInfo): boolean
46
+ }
47
+
48
+ /**
49
+ * A path reference, the payload of `{ "$mobxKeystone": "pathRef", "v": ... }`.
50
+ */
51
+ export type PathRefJson = {
52
+ /**
53
+ * Path from the root to the node, through data nodes (as in patches).
54
+ */
55
+ path: PathElement[]
56
+ /**
57
+ * Id of the model reached by each step of the path (`null` for non-models).
58
+ * Optional when decoding: without it, ids aren't checked.
59
+ */
60
+ ids?: (string | null)[]
61
+ }
62
+
63
+ /**
64
+ * An id reference, the payload of `{ "$mobxKeystone": "idRef", "v": ... }`.
65
+ */
66
+ export type IdRefJson = {
67
+ /**
68
+ * The model type (`$modelType`).
69
+ */
70
+ type: string
71
+ /**
72
+ * The model id.
73
+ */
74
+ id: string
75
+ }
76
+
77
+ const defaultWhen = (_node: object, info: ReferenceNodeInfo) => !info.isValueType
78
+
79
+ function getNodeInfo(node: object): ReferenceNodeInfo {
80
+ const nodeIsModel = isModel(node)
81
+ return {
82
+ isModel: nodeIsModel,
83
+ isValueType: nodeIsModel && getModelMetadata(node).valueType,
84
+ }
85
+ }
86
+
87
+ /**
88
+ * @internal
89
+ */
90
+ export function isNodeOfTree(value: unknown, root: object): value is object {
91
+ return isTreeNode(value) && fastGetRoot(value, false) === root
92
+ }
93
+
94
+ /**
95
+ * @internal
96
+ */
97
+ export const pathRefTag = "pathRef"
98
+
99
+ /**
100
+ * @internal
101
+ *
102
+ * Encodes a node of the tree as a full path reference.
103
+ */
104
+ export function encodePathRef(node: object): PathRefJson {
105
+ const rootPath = fastGetRootPath(node, false)
106
+ return {
107
+ path: [...rootPath.path],
108
+ ids: rootPathToTargetPathIds(rootPath),
109
+ }
110
+ }
111
+
112
+ /**
113
+ * @internal
114
+ *
115
+ * Decodes a path reference against a tree.
116
+ */
117
+ export function decodePathRef(json: unknown, root: object): object {
118
+ if (!isPlainObject(json) || !isArray(json.path) || !json.path.every(isPathElement)) {
119
+ throw badPayloadError(pathRefTag, json)
120
+ }
121
+ const path = json.path as Path
122
+ const ids = json.ids
123
+ if (
124
+ ids !== undefined &&
125
+ (!isArray(ids) ||
126
+ ids.length !== path.length ||
127
+ !ids.every((id) => id === null || typeof id === "string"))
128
+ ) {
129
+ throw badPayloadError(pathRefTag, json)
130
+ }
131
+
132
+ const result = resolvePathCheckingIds(
133
+ root,
134
+ path,
135
+ (ids as (string | null)[] | undefined) ?? path.map(() => skipIdChecking)
136
+ )
137
+ // only tree nodes can be referenced, so anything else is not a valid reference
138
+ if (!result.resolved || !isTreeNode(result.value)) {
139
+ throw new CallError({
140
+ code: CallErrorCode.NotFound,
141
+ message: `object at path ${JSON.stringify(path)}${
142
+ ids === undefined ? "" : ` with ids ${JSON.stringify(ids)}`
143
+ } could not be resolved`,
144
+ })
145
+ }
146
+ return result.value
147
+ }
148
+
149
+ /**
150
+ * References nodes of the tree by their path from the root, checking the ids of the models along
151
+ * it: `{ "$mobxKeystone": "pathRef", "v": { "path": ["cards", 2], "ids": [null, "c1"] } }`.
152
+ * It reaches any node, including tree arrays and plain objects.
153
+ *
154
+ * @param options Options.
155
+ * @returns The reference serializer.
156
+ */
157
+ export function pathRefs(
158
+ options?: ReferenceSerializerOptions
159
+ ): ValueSerializer<object, PathRefJson> {
160
+ const when = options?.when ?? defaultWhen
161
+
162
+ return {
163
+ tag: pathRefTag,
164
+
165
+ is(value, ctx): value is object {
166
+ return isNodeOfTree(value, ctx.root) && when(value, getNodeInfo(value))
167
+ },
168
+
169
+ encode: encodePathRef,
170
+
171
+ decode(json, ctx) {
172
+ return decodePathRef(json, ctx.root)
173
+ },
174
+
175
+ claimsTarget(value, ctx) {
176
+ return isNodeOfTree(value, ctx.root)
177
+ },
178
+ }
179
+ }
180
+
181
+ function claimsById(value: unknown, root: object): value is AnyModel {
182
+ return isModel(value) && getModelRefId(value) !== undefined && isNodeOfTree(value, root)
183
+ }
184
+
185
+ /**
186
+ * References models of the tree that have an id by their type and id:
187
+ * `{ "$mobxKeystone": "idRef", "v": { "type": "app/Card", "id": "c1" } }`. Ids are read and
188
+ * resolved as `getModelRefId` and `resolveId` do, and the type must match.
189
+ *
190
+ * @param options Options.
191
+ * @returns The reference serializer.
192
+ */
193
+ export function idRefs(options?: ReferenceSerializerOptions): ValueSerializer<AnyModel, IdRefJson> {
194
+ const when = options?.when ?? defaultWhen
195
+
196
+ return {
197
+ tag: "idRef",
198
+
199
+ is(value, ctx): value is AnyModel {
200
+ return claimsById(value, ctx.root) && when(value, getNodeInfo(value))
201
+ },
202
+
203
+ encode(value) {
204
+ return { type: value[modelTypeKey], id: getModelRefId(value)! }
205
+ },
206
+
207
+ decode(json, ctx) {
208
+ if (!isPlainObject(json) || typeof json.type !== "string" || typeof json.id !== "string") {
209
+ throw badPayloadError("idRef", json)
210
+ }
211
+ const found = resolveId(ctx.root, json.id)
212
+ if (!isModel(found) || found[modelTypeKey] !== json.type) {
213
+ throw new CallError({
214
+ code: CallErrorCode.NotFound,
215
+ message: `model of type '${json.type}' with id '${json.id}' could not be resolved`,
216
+ })
217
+ }
218
+ return found
219
+ },
220
+
221
+ claimsTarget(value, ctx) {
222
+ return claimsById(value, ctx.root)
223
+ },
224
+ }
225
+ }