mobx-keystone 2.3.0 → 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 +20 -0
- package/dist/mobx-keystone.esm.js +3737 -2722
- package/dist/mobx-keystone.esm.mjs +3737 -2722
- package/dist/mobx-keystone.umd.js +3750 -2721
- package/dist/types/action/CallKind.d.ts +29 -0
- package/dist/types/action/applyAction.d.ts +8 -2
- package/dist/types/action/applyMethodCall.d.ts +1 -1
- package/dist/types/action/index.d.ts +13 -12
- package/dist/types/action/isModelAction.d.ts +1 -1
- package/dist/types/action/middleware.d.ts +1 -1
- package/dist/types/action/resolveCallName.d.ts +1 -0
- package/dist/types/actionMiddlewares/actionSerialization/actionSerialization.d.ts +17 -3
- package/dist/types/actionMiddlewares/actionSerialization/applySerializedAction.d.ts +8 -2
- package/dist/types/actionMiddlewares/actionSerialization/arraySerializer.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionSerialization/core.d.ts +7 -0
- package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/index.d.ts +3 -3
- package/dist/types/actionMiddlewares/actionSerialization/mapSerializer.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionSerialization/objectPathSerializer.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionSerialization/objectSnapshotSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/plainObjectSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/primitiveSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/setSerializer.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionTrackingMiddleware.d.ts +3 -3
- package/dist/types/actionMiddlewares/index.d.ts +6 -6
- package/dist/types/actionMiddlewares/onActionMiddleware.d.ts +3 -3
- package/dist/types/actionMiddlewares/readonlyMiddleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +2 -2
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +8 -8
- package/dist/types/computedTree/index.d.ts +1 -1
- package/dist/types/context/index.d.ts +1 -1
- package/dist/types/dataModel/BaseDataModel.d.ts +3 -3
- package/dist/types/dataModel/DataModel.d.ts +3 -3
- package/dist/types/dataModel/getDataModelMetadata.d.ts +3 -3
- package/dist/types/dataModel/index.d.ts +4 -4
- package/dist/types/dataModel/utils.d.ts +1 -1
- package/dist/types/deepChange/index.d.ts +1 -1
- package/dist/types/deepChange/onDeepChange.d.ts +1 -1
- package/dist/types/frozen/Frozen.d.ts +1 -1
- package/dist/types/frozen/index.d.ts +1 -1
- package/dist/types/globalConfig/index.d.ts +1 -1
- package/dist/types/index.d.ts +25 -24
- package/dist/types/model/BaseModel.d.ts +5 -5
- package/dist/types/model/Model.d.ts +3 -3
- package/dist/types/model/getModelMetadata.d.ts +3 -3
- package/dist/types/model/index.d.ts +6 -6
- package/dist/types/model/mixins.d.ts +4 -4
- package/dist/types/model/utils.d.ts +4 -4
- package/dist/types/modelShared/BaseModelShared.d.ts +5 -5
- package/dist/types/modelShared/index.d.ts +4 -4
- package/dist/types/modelShared/modelDecorator.d.ts +3 -3
- package/dist/types/modelShared/modelInfo.d.ts +3 -3
- package/dist/types/modelShared/newModel.d.ts +3 -3
- package/dist/types/modelShared/prop.d.ts +3 -3
- package/dist/types/modelShared/sharedInternalModel.d.ts +4 -4
- package/dist/types/parent/findParent.d.ts +1 -1
- package/dist/types/parent/index.d.ts +9 -9
- package/dist/types/parent/path.d.ts +1 -1
- package/dist/types/patch/Patch.d.ts +1 -1
- package/dist/types/patch/applyPatches.d.ts +1 -1
- package/dist/types/patch/emitPatch.d.ts +7 -1
- package/dist/types/patch/index.d.ts +5 -5
- package/dist/types/patch/jsonPatch.d.ts +2 -2
- package/dist/types/patch/patchRecorder.d.ts +2 -2
- package/dist/types/redux/index.d.ts +2 -2
- package/dist/types/redux/redux.d.ts +3 -3
- package/dist/types/ref/Ref.d.ts +3 -3
- package/dist/types/ref/core.d.ts +2 -2
- package/dist/types/ref/customRef.d.ts +2 -2
- package/dist/types/ref/index.d.ts +4 -4
- package/dist/types/ref/rootRef.d.ts +2 -2
- package/dist/types/rootStore/index.d.ts +1 -1
- 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/dist/types/snapshot/SnapshotOf.d.ts +5 -5
- package/dist/types/snapshot/SnapshotProcessingError.d.ts +2 -2
- package/dist/types/snapshot/applySnapshot.d.ts +1 -1
- package/dist/types/snapshot/cloneTreeValue.d.ts +1 -1
- package/dist/types/snapshot/fromSnapshot.d.ts +2 -2
- package/dist/types/snapshot/getSnapshot.d.ts +2 -2
- package/dist/types/snapshot/index.d.ts +8 -8
- package/dist/types/snapshot/onSnapshot.d.ts +1 -1
- package/dist/types/standardActions/index.d.ts +3 -3
- package/dist/types/standardActions/objectActions.d.ts +1 -1
- package/dist/types/transforms/asMap.d.ts +1 -1
- package/dist/types/transforms/asSet.d.ts +2 -2
- package/dist/types/transforms/bigint.d.ts +1 -1
- package/dist/types/transforms/date.d.ts +1 -1
- package/dist/types/transforms/index.d.ts +4 -4
- package/dist/types/treeUtils/draft.d.ts +1 -1
- package/dist/types/treeUtils/index.d.ts +3 -3
- package/dist/types/tweaker/index.d.ts +2 -2
- package/dist/types/tweaker/tweak.d.ts +1 -1
- package/dist/types/types/SnapshotTypeMismatchError.d.ts +2 -2
- package/dist/types/types/TypeCheckError.d.ts +2 -2
- package/dist/types/types/TypeChecker.d.ts +3 -3
- package/dist/types/types/arrayBased/typesArray.d.ts +2 -2
- package/dist/types/types/arrayBased/typesTuple.d.ts +2 -2
- package/dist/types/types/getTypeInfo.d.ts +2 -2
- package/dist/types/types/index.d.ts +7 -7
- package/dist/types/types/objectBased/typesArraySet.d.ts +3 -3
- package/dist/types/types/objectBased/typesDataModelData.d.ts +4 -4
- package/dist/types/types/objectBased/typesModel.d.ts +4 -4
- package/dist/types/types/objectBased/typesObject.d.ts +3 -3
- package/dist/types/types/objectBased/typesObjectMap.d.ts +3 -3
- package/dist/types/types/objectBased/typesRecord.d.ts +2 -2
- package/dist/types/types/objectBased/typesRef.d.ts +3 -3
- package/dist/types/types/primitiveBased/typesEnum.d.ts +1 -1
- package/dist/types/types/primitiveBased/typesPrimitive.d.ts +3 -3
- package/dist/types/types/primitiveBased/typesRefinedPrimitive.d.ts +2 -2
- package/dist/types/types/schemas.d.ts +5 -5
- package/dist/types/types/tProp.d.ts +4 -4
- package/dist/types/types/typeCheck.d.ts +2 -2
- package/dist/types/types/types.d.ts +39 -39
- package/dist/types/types/utility/typeToStoredData.d.ts +2 -2
- package/dist/types/types/utility/typesBigInt.d.ts +1 -1
- package/dist/types/types/utility/typesCodec.d.ts +6 -6
- package/dist/types/types/utility/typesCodecCore.d.ts +4 -4
- package/dist/types/types/utility/typesCodecSupport.d.ts +4 -4
- package/dist/types/types/utility/typesDate.d.ts +2 -2
- package/dist/types/types/utility/typesMap.d.ts +2 -2
- package/dist/types/types/utility/typesMaybe.d.ts +1 -1
- package/dist/types/types/utility/typesOr.d.ts +2 -2
- package/dist/types/types/utility/typesRefinement.d.ts +3 -3
- package/dist/types/types/utility/typesSet.d.ts +3 -3
- package/dist/types/types/utility/typesSkipCheck.d.ts +2 -2
- package/dist/types/types/utility/typesTag.d.ts +2 -2
- package/dist/types/types/utility/typesUnchecked.d.ts +2 -2
- package/dist/types/utils/ModelPool.d.ts +1 -1
- package/dist/types/wrappers/ArraySet.d.ts +1 -1
- package/dist/types/wrappers/ObjectMap.d.ts +1 -1
- package/dist/types/wrappers/asMap.d.ts +1 -1
- package/dist/types/wrappers/asSet.d.ts +1 -1
- package/dist/types/wrappers/index.d.ts +4 -4
- package/package.json +5 -18
- 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/modelAction.ts +1 -1
- package/src/action/modelFlow.ts +1 -1
- 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/dataModel/BaseDataModel.ts +5 -5
- package/src/index.ts +1 -0
- package/src/model/BaseModel.ts +7 -6
- package/src/model/mixins.ts +1 -1
- package/src/model/newModel.ts +1 -1
- package/src/modelShared/modelDecorator.ts +4 -6
- package/src/modelShared/modelInfo.ts +2 -1
- package/src/modelShared/sharedInternalModel.ts +1 -1
- package/src/parent/path.ts +45 -12
- package/src/patch/emitPatch.ts +6 -0
- package/src/ref/Ref.ts +1 -1
- 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/snapshot/SnapshotterAndReconcilerPriority.ts +2 -1
- package/src/snapshot/fromModelSnapshot.ts +1 -1
- package/src/snapshot/fromSnapshot.ts +1 -1
- package/src/standardActions/actions.ts +11 -3
- package/src/transforms/ImmutableDate.ts +19 -19
- package/src/tweaker/tweakArray.ts +1 -1
- package/src/tweaker/tweakFrozen.ts +1 -1
- package/src/tweaker/tweakModel.ts +1 -1
- package/src/tweaker/tweakPlainObject.ts +1 -1
- package/src/types/arrayBased/typesArray.ts +1 -1
- package/src/types/arrayBased/typesTuple.ts +1 -1
- package/src/types/objectBased/typesArraySet.ts +1 -1
- package/src/types/objectBased/typesModel.ts +2 -2
- package/src/types/objectBased/typesObject.ts +1 -1
- package/src/types/objectBased/typesObjectMap.ts +1 -1
- package/src/types/objectBased/typesRecord.ts +1 -1
- package/src/types/objectBased/typesRef.ts +2 -2
- package/src/types/primitiveBased/typesPrimitive.ts +1 -1
- package/src/types/schemas.ts +1 -1
- package/src/types/utility/typesCodecCore.ts +1 -1
- package/src/types/utility/typesCodecSupport.ts +2 -0
- package/src/types/utility/typesOr.ts +2 -2
- package/src/types/utility/typesRefinement.ts +1 -1
- package/src/utils/errorDiagnostics.ts +9 -4
- package/src/utils/index.ts +20 -13
- package/src/wrappers/ArraySet.ts +3 -5
- package/src/wrappers/ObjectMap.ts +3 -5
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { transaction } from "mobx"
|
|
2
|
+
import { CallKind } from "../action/CallKind"
|
|
3
|
+
import { isTreeNode } from "../tweaker/core"
|
|
4
|
+
import { failure, hasOwnProp, isObject, isPlainObject } from "../utils"
|
|
5
|
+
import type { Call, ResolvedCall } from "./Call"
|
|
6
|
+
import { CallError, CallErrorCode } from "./CallError"
|
|
7
|
+
import {
|
|
8
|
+
assertIsCallShape,
|
|
9
|
+
assertIsTargetInTree,
|
|
10
|
+
assertIsTreeRoot,
|
|
11
|
+
getResolvedCallData,
|
|
12
|
+
toResolvedCall,
|
|
13
|
+
} from "./resolvedCall"
|
|
14
|
+
import { serializationMarkerKey } from "./ValueSerializer"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options of `applyCall` and `applyCallAsync`.
|
|
18
|
+
*/
|
|
19
|
+
export interface ApplyCallOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Decides whether a call may run, after its target and name are resolved and before anything
|
|
22
|
+
* runs. Returning false throws a `CallError` with code `Forbidden`; an error it throws passes
|
|
23
|
+
* through as it is. Reading `call.args` decodes the arguments (once).
|
|
24
|
+
*
|
|
25
|
+
* Defaults to `allowModelActions`.
|
|
26
|
+
*/
|
|
27
|
+
canCall?(call: ResolvedCall): boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The default `canCall`: allows model actions (`CallKind.ModelAction`) only.
|
|
32
|
+
*
|
|
33
|
+
* @param call The call.
|
|
34
|
+
* @returns Whether it is a model action.
|
|
35
|
+
*/
|
|
36
|
+
export function allowModelActions(call: ResolvedCall): boolean {
|
|
37
|
+
return call.kind === CallKind.ModelAction
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A `canCall` that allows every call, for calls from a source that is trusted.
|
|
42
|
+
*
|
|
43
|
+
* @param _call The call.
|
|
44
|
+
* @returns `true`.
|
|
45
|
+
*/
|
|
46
|
+
export function allowAnyCall(_call: ResolvedCall): boolean {
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const kindDescriptions: Record<CallKind, string> = {
|
|
51
|
+
[CallKind.ModelAction]: "a model action",
|
|
52
|
+
[CallKind.StandaloneAction]: "a standalone action",
|
|
53
|
+
[CallKind.StandardAction]: "a standard action",
|
|
54
|
+
[CallKind.BuiltInAction]: "a built-in action",
|
|
55
|
+
[CallKind.View]: "a view",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function looksLikeEncodedCall(call: Call): boolean {
|
|
59
|
+
const target: unknown = call.target
|
|
60
|
+
return isPlainObject(target) && hasOwnProp(target, serializationMarkerKey) && !isTreeNode(target)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function wrongKind(call: Call, message: string): CallError {
|
|
64
|
+
return new CallError({
|
|
65
|
+
code: CallErrorCode.WrongKind,
|
|
66
|
+
message: `'${call.name}' ${message}`,
|
|
67
|
+
path: ["name"],
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isPromiseLike(value: unknown): boolean {
|
|
72
|
+
return (
|
|
73
|
+
(isObject(value) || typeof value === "function") &&
|
|
74
|
+
typeof (value as { then?: unknown }).then === "function"
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolves and checks a call, and returns the function that runs it.
|
|
80
|
+
*/
|
|
81
|
+
function prepareCall(
|
|
82
|
+
root: object,
|
|
83
|
+
call: Call | ResolvedCall,
|
|
84
|
+
options: ApplyCallOptions | undefined,
|
|
85
|
+
allowFlows: boolean
|
|
86
|
+
): () => unknown {
|
|
87
|
+
assertIsTreeRoot(root)
|
|
88
|
+
|
|
89
|
+
if (!isObject(call)) {
|
|
90
|
+
throw failure("call must be an object")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const isResolved = getResolvedCallData(call) !== undefined
|
|
94
|
+
if (!isResolved) {
|
|
95
|
+
if (looksLikeEncodedCall(call)) {
|
|
96
|
+
throw failure("this looks like an EncodedCall; decode it first")
|
|
97
|
+
}
|
|
98
|
+
assertIsCallShape(call)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// also checked for decoded calls, since they could be applied to another tree
|
|
102
|
+
assertIsTargetInTree(call.target, root)
|
|
103
|
+
|
|
104
|
+
const resolvedCall = isResolved ? (call as ResolvedCall) : toResolvedCall(call)
|
|
105
|
+
const { resolution, hasArgs } = getResolvedCallData(resolvedCall)!
|
|
106
|
+
|
|
107
|
+
if (!allowFlows && resolution.isFlow) {
|
|
108
|
+
throw wrongKind(resolvedCall, "is a flow; use applyCallAsync")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (resolution.kind !== CallKind.View) {
|
|
112
|
+
if (!hasArgs) {
|
|
113
|
+
throw wrongKind(resolvedCall, "is an action; pass args")
|
|
114
|
+
}
|
|
115
|
+
} else if (resolution.viewForm === "getter") {
|
|
116
|
+
if (hasArgs) {
|
|
117
|
+
throw wrongKind(resolvedCall, "is a getter; leave args out")
|
|
118
|
+
}
|
|
119
|
+
} else if (!hasArgs) {
|
|
120
|
+
throw wrongKind(resolvedCall, "is a method; pass args")
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const canCall = options?.canCall ?? allowModelActions
|
|
124
|
+
if (!canCall(resolvedCall)) {
|
|
125
|
+
const kindDescription = kindDescriptions[resolution.kind]
|
|
126
|
+
throw new CallError({
|
|
127
|
+
code: CallErrorCode.Forbidden,
|
|
128
|
+
message: options?.canCall
|
|
129
|
+
? `'${resolvedCall.name}' (${kindDescription}) was refused by canCall`
|
|
130
|
+
: `'${resolvedCall.name}' is ${kindDescription}, which the default canCall refuses; pass canCall to allow it`,
|
|
131
|
+
path: [],
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return () => {
|
|
136
|
+
const args = resolvedCall.args ?? []
|
|
137
|
+
|
|
138
|
+
if (resolution.kind !== CallKind.View) {
|
|
139
|
+
return resolution.run(args)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// one batch, so computeds read by the view stay cached while it runs
|
|
143
|
+
const result = transaction(() => resolution.run(args))
|
|
144
|
+
if (isPromiseLike(result)) {
|
|
145
|
+
throw wrongKind(resolvedCall, "returned a promise; async reads belong in a flow")
|
|
146
|
+
}
|
|
147
|
+
return result
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Runs a call (as returned by `Serializer.decodeCall`, or built in process) on a tree and returns
|
|
153
|
+
* its result as is. Flows are refused (`call.isFlow`); use `applyCallAsync` for them.
|
|
154
|
+
*
|
|
155
|
+
* The call's name is resolved on its target (see `CallKind`), then `canCall` decides whether it
|
|
156
|
+
* may run (by default only model actions may). Errors about the call are `CallError`s; errors
|
|
157
|
+
* thrown by the action or view are rethrown as they are.
|
|
158
|
+
*
|
|
159
|
+
* @template TRet Return type.
|
|
160
|
+
* @param root Root of the tree.
|
|
161
|
+
* @param call The call.
|
|
162
|
+
* @param options Options.
|
|
163
|
+
* @returns The result of the call.
|
|
164
|
+
*/
|
|
165
|
+
export function applyCall<TRet = unknown>(
|
|
166
|
+
root: object,
|
|
167
|
+
call: Call | ResolvedCall,
|
|
168
|
+
options?: ApplyCallOptions
|
|
169
|
+
): TRet {
|
|
170
|
+
return prepareCall(root, call, options, false)() as TRet
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Like `applyCall`, but accepts flows too and always returns a promise, which resolves to the
|
|
175
|
+
* result once the call (or the flow) ends. Every error is a rejection.
|
|
176
|
+
*
|
|
177
|
+
* @template TRet Return type.
|
|
178
|
+
* @param root Root of the tree.
|
|
179
|
+
* @param call The call.
|
|
180
|
+
* @param options Options.
|
|
181
|
+
* @returns A promise of the result of the call.
|
|
182
|
+
*/
|
|
183
|
+
export async function applyCallAsync<TRet = unknown>(
|
|
184
|
+
root: object,
|
|
185
|
+
call: Call | ResolvedCall,
|
|
186
|
+
options?: ApplyCallOptions
|
|
187
|
+
): Promise<TRet> {
|
|
188
|
+
const run = prepareCall(root, call, options, true)
|
|
189
|
+
return (await run()) as TRet
|
|
190
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { AnyDataModel } from "../dataModel/BaseDataModel"
|
|
2
|
+
import { isDataModel, isDataModelClass } from "../dataModel/utils"
|
|
3
|
+
import { Frozen } from "../frozen/Frozen"
|
|
4
|
+
import { isModel } from "../model/utils"
|
|
5
|
+
import { findModelInfoForPrototype, getModelInfoForName } from "../modelShared/modelInfo"
|
|
6
|
+
import { fromSnapshot } from "../snapshot/fromSnapshot"
|
|
7
|
+
import { getSnapshot } from "../snapshot/getSnapshot"
|
|
8
|
+
import { hasOwnProp, isArray, isMap, isPlainObject, isSet } from "../utils"
|
|
9
|
+
import { badPayloadError, CallError, CallErrorCode } from "./CallError"
|
|
10
|
+
import type { TypeofResult } from "./codec"
|
|
11
|
+
import type { JsonValue } from "./JsonValue"
|
|
12
|
+
import type { ValueSerializer } from "./ValueSerializer"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*
|
|
17
|
+
* Marks a serializer whose encoded value is written as it is, without the marker
|
|
18
|
+
* (snapshots already mark themselves).
|
|
19
|
+
*/
|
|
20
|
+
export const unmarkedEncodingSymbol = Symbol("unmarkedEncoding")
|
|
21
|
+
|
|
22
|
+
const snapshotSerializer: ValueSerializer<object, any> = {
|
|
23
|
+
tag: "snapshot",
|
|
24
|
+
|
|
25
|
+
is(value): value is object {
|
|
26
|
+
return isModel(value) || value instanceof Frozen
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
encode(value) {
|
|
30
|
+
return getSnapshot(value)
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
decode(json) {
|
|
34
|
+
return fromSnapshot(json)
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
;(snapshotSerializer as unknown as Record<symbol, boolean>)[unmarkedEncodingSymbol] = true
|
|
38
|
+
|
|
39
|
+
const dataModelSerializer: ValueSerializer<AnyDataModel, { type: string; data: JsonValue }> = {
|
|
40
|
+
tag: "dataModel",
|
|
41
|
+
|
|
42
|
+
is(value): value is AnyDataModel {
|
|
43
|
+
return isDataModel(value)
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
encode(value, ctx) {
|
|
47
|
+
// data models can only be created from registered classes
|
|
48
|
+
const modelInfo = findModelInfoForPrototype(Object.getPrototypeOf(value))!
|
|
49
|
+
return { type: modelInfo.name, data: ctx.encode(value.$) }
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
decode(json, ctx) {
|
|
53
|
+
if (!isPlainObject(json) || typeof json.type !== "string" || !hasOwnProp(json, "data")) {
|
|
54
|
+
throw badPayloadError("dataModel", json)
|
|
55
|
+
}
|
|
56
|
+
const modelInfo = getModelInfoForName(json.type)
|
|
57
|
+
if (!modelInfo || !isDataModelClass(modelInfo.class)) {
|
|
58
|
+
throw new CallError({
|
|
59
|
+
code: CallErrorCode.BadArgument,
|
|
60
|
+
message: `no data model is registered as '${json.type}'`,
|
|
61
|
+
previewValue: json,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return new modelInfo.class(ctx.decode(json.data))
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const dateSerializer: ValueSerializer<Date, string | number | null> = {
|
|
69
|
+
tag: "date",
|
|
70
|
+
|
|
71
|
+
is(value): value is Date {
|
|
72
|
+
return value instanceof Date
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
encode(value) {
|
|
76
|
+
return Number.isNaN(value.getTime()) ? null : value.toISOString()
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
decode(json) {
|
|
80
|
+
if (json === null) {
|
|
81
|
+
return new Date(Number.NaN)
|
|
82
|
+
}
|
|
83
|
+
const date = typeof json === "number" || typeof json === "string" ? new Date(json) : undefined
|
|
84
|
+
if (!date || Number.isNaN(date.getTime())) {
|
|
85
|
+
throw badPayloadError("date", json)
|
|
86
|
+
}
|
|
87
|
+
return date
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const mapSerializer: ValueSerializer<Map<unknown, unknown>, [JsonValue, JsonValue][]> = {
|
|
92
|
+
tag: "map",
|
|
93
|
+
|
|
94
|
+
is(value): value is Map<unknown, unknown> {
|
|
95
|
+
return isMap(value)
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
encode(value, ctx) {
|
|
99
|
+
return Array.from(value.entries(), ([k, v], i): [JsonValue, JsonValue] => [
|
|
100
|
+
ctx.encode(k, i),
|
|
101
|
+
ctx.encode(v, i),
|
|
102
|
+
])
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
decode(json, ctx) {
|
|
106
|
+
if (!isArray(json)) {
|
|
107
|
+
throw badPayloadError("map", json)
|
|
108
|
+
}
|
|
109
|
+
const map = new Map<unknown, unknown>()
|
|
110
|
+
json.forEach((entry, i) => {
|
|
111
|
+
if (!isArray(entry) || entry.length !== 2) {
|
|
112
|
+
throw badPayloadError("map", json)
|
|
113
|
+
}
|
|
114
|
+
map.set(ctx.decode(entry[0], i), ctx.decode(entry[1], i))
|
|
115
|
+
})
|
|
116
|
+
return map
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const setSerializer: ValueSerializer<Set<unknown>, JsonValue[]> = {
|
|
121
|
+
tag: "set",
|
|
122
|
+
|
|
123
|
+
is(value): value is Set<unknown> {
|
|
124
|
+
return isSet(value)
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
encode(value, ctx) {
|
|
128
|
+
return Array.from(value.values(), (v, i) => ctx.encode(v, i))
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
decode(json, ctx) {
|
|
132
|
+
if (!isArray(json)) {
|
|
133
|
+
throw badPayloadError("set", json)
|
|
134
|
+
}
|
|
135
|
+
return new Set(json.map((v, i) => ctx.decode(v, i)))
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const nonFiniteNumbers: Record<string, number> = {
|
|
140
|
+
NaN: Number.NaN,
|
|
141
|
+
Infinity: Number.POSITIVE_INFINITY,
|
|
142
|
+
"-Infinity": Number.NEGATIVE_INFINITY,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const numberSerializer: ValueSerializer<number, string> = {
|
|
146
|
+
tag: "number",
|
|
147
|
+
|
|
148
|
+
is(value): value is number {
|
|
149
|
+
return typeof value === "number" && !Number.isFinite(value)
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
encode(value) {
|
|
153
|
+
return String(value)
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
decode(json) {
|
|
157
|
+
if (typeof json !== "string" || !hasOwnProp(nonFiniteNumbers, json)) {
|
|
158
|
+
throw badPayloadError("number", json)
|
|
159
|
+
}
|
|
160
|
+
return nonFiniteNumbers[json]
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const bigintSerializer: ValueSerializer<bigint, string> = {
|
|
165
|
+
tag: "bigint",
|
|
166
|
+
|
|
167
|
+
is(value): value is bigint {
|
|
168
|
+
return typeof value === "bigint"
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
encode(value) {
|
|
172
|
+
return value.toString()
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
decode(json) {
|
|
176
|
+
if (typeof json !== "string" || !/^-?\d+$/.test(json)) {
|
|
177
|
+
throw badPayloadError("bigint", json)
|
|
178
|
+
}
|
|
179
|
+
return BigInt(json)
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const undefinedSerializer: ValueSerializer<undefined, undefined> = {
|
|
184
|
+
tag: "undefined",
|
|
185
|
+
|
|
186
|
+
is(value): value is undefined {
|
|
187
|
+
return value === undefined
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
encode() {
|
|
191
|
+
return undefined
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
decode() {
|
|
195
|
+
return undefined
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The built-in value serializers, appended in this order to every serializer's list (unless it is
|
|
201
|
+
* created with `builtins: false`). Pick some of them to build a list by hand, e.g.
|
|
202
|
+
* `createSerializer({ serializers: [builtinSerializers.date], builtins: false })`.
|
|
203
|
+
*/
|
|
204
|
+
export const builtinSerializers = Object.freeze({
|
|
205
|
+
/**
|
|
206
|
+
* Models and frozen data that aren't references, as their snapshots (unmarked, since snapshots
|
|
207
|
+
* mark themselves with `$modelType` / `$frozen`), decoded with `fromSnapshot` into new
|
|
208
|
+
* instances. Tree arrays and plain objects that aren't references are walked like any other.
|
|
209
|
+
*/
|
|
210
|
+
snapshot: snapshotSerializer,
|
|
211
|
+
/**
|
|
212
|
+
* Data model instances, as their registered name and their data (encoded, so data of the tree
|
|
213
|
+
* goes as a reference), decoded as a new instance over the decoded data.
|
|
214
|
+
*/
|
|
215
|
+
dataModel: dataModelSerializer,
|
|
216
|
+
/**
|
|
217
|
+
* Dates, as `toISOString()` (an invalid date as `null`); decodes an ISO string, a timestamp or
|
|
218
|
+
* `null`.
|
|
219
|
+
*/
|
|
220
|
+
date: dateSerializer,
|
|
221
|
+
/**
|
|
222
|
+
* Maps (plain or observable), as an array of `[key, value]` entries; decoded as plain maps.
|
|
223
|
+
*/
|
|
224
|
+
map: mapSerializer,
|
|
225
|
+
/**
|
|
226
|
+
* Sets (plain or observable), as an array of their values; decoded as plain sets.
|
|
227
|
+
*/
|
|
228
|
+
set: setSerializer,
|
|
229
|
+
/**
|
|
230
|
+
* The numbers JSON can't hold: `"NaN"`, `"Infinity"`, `"-Infinity"`.
|
|
231
|
+
*/
|
|
232
|
+
number: numberSerializer,
|
|
233
|
+
/**
|
|
234
|
+
* Bigints, as a decimal string, with no loss of precision.
|
|
235
|
+
*/
|
|
236
|
+
bigint: bigintSerializer,
|
|
237
|
+
/**
|
|
238
|
+
* `undefined` (as an array item, an argument or an object property, which keeps its key).
|
|
239
|
+
*/
|
|
240
|
+
undefined: undefinedSerializer,
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @internal
|
|
245
|
+
*
|
|
246
|
+
* The only `typeof` of the values each built-in encodes, so encoding can skip the built-ins that
|
|
247
|
+
* can't apply to a value.
|
|
248
|
+
*/
|
|
249
|
+
export const builtinEncodedTypeof: ReadonlyMap<ValueSerializer, TypeofResult> = new Map<
|
|
250
|
+
ValueSerializer,
|
|
251
|
+
TypeofResult
|
|
252
|
+
>([
|
|
253
|
+
[snapshotSerializer, "object"],
|
|
254
|
+
[dataModelSerializer, "object"],
|
|
255
|
+
[dateSerializer, "object"],
|
|
256
|
+
[mapSerializer, "object"],
|
|
257
|
+
[setSerializer, "object"],
|
|
258
|
+
[numberSerializer, "number"],
|
|
259
|
+
[bigintSerializer, "bigint"],
|
|
260
|
+
[undefinedSerializer, "undefined"],
|
|
261
|
+
])
|