mobx-keystone 1.20.0 → 1.22.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 +864 -853
- package/README.md +61 -61
- package/dist/mobx-keystone.esm.js +11336 -11148
- package/dist/mobx-keystone.esm.mjs +11336 -11148
- package/dist/mobx-keystone.umd.js +12229 -11738
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +1 -1
- package/dist/types/modelShared/prop.d.ts +5 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +17 -4
- package/dist/types/ref/Ref.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +1 -1
- package/dist/types/types/TypeChecker.d.ts +1 -1
- package/dist/types/types/arrayBased/typesArray.d.ts +1 -1
- package/dist/types/types/arrayBased/typesTuple.d.ts +1 -1
- package/dist/types/types/objectBased/typesArraySet.d.ts +1 -1
- package/dist/types/types/objectBased/typesDataModelData.d.ts +1 -1
- package/dist/types/types/objectBased/typesModel.d.ts +1 -1
- package/dist/types/types/objectBased/typesObject.d.ts +3 -3
- package/dist/types/types/objectBased/typesObjectMap.d.ts +1 -1
- package/dist/types/types/objectBased/typesRecord.d.ts +1 -1
- package/dist/types/types/primitiveBased/typesPrimitive.d.ts +1 -1
- package/dist/types/types/types.d.ts +3 -3
- package/dist/types/types/utility/typesCodec.d.ts +1 -6
- package/dist/types/types/utility/typesCodecCore.d.ts +1 -1
- package/dist/types/types/utility/typesOr.d.ts +1 -1
- package/dist/types/types/utility/typesRefinement.d.ts +1 -1
- package/dist/types/types/utility/typesSkipCheck.d.ts +1 -1
- package/dist/types/types/utility/typesTag.d.ts +1 -1
- package/dist/types/wrappers/ObjectMap.d.ts +2 -0
- package/dist/types/wrappers/asMap.d.ts +10 -6
- package/package.json +14 -12
- package/src/action/applyAction.ts +125 -114
- package/src/action/applyMethodCall.ts +37 -37
- package/src/action/isModelAction.ts +16 -16
- package/src/action/middleware.ts +137 -137
- package/src/action/modelFlow.ts +240 -240
- package/src/action/modelFlowPromiseGenerator.ts +127 -125
- package/src/action/pendingActions.ts +42 -42
- package/src/action/wrapInAction.ts +156 -156
- package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +1 -1
- package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +172 -172
- package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +20 -20
- package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +17 -17
- package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +42 -42
- package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +50 -50
- package/src/actionMiddlewares/actionSerialization/objectSnapshotSerializer.ts +21 -21
- package/src/actionMiddlewares/actionSerialization/plainObjectSerializer.ts +24 -32
- package/src/actionMiddlewares/actionSerialization/primitiveSerializer.ts +51 -51
- package/src/actionMiddlewares/actionSerialization/setSerializer.ts +37 -37
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +3 -3
- package/src/actionMiddlewares/onActionMiddleware.ts +2 -2
- package/src/actionMiddlewares/readonlyMiddleware.ts +2 -2
- package/src/actionMiddlewares/transactionMiddleware.ts +4 -4
- package/src/actionMiddlewares/undoMiddleware.ts +804 -796
- package/src/actionMiddlewares/utils.ts +2 -2
- package/src/computedTree/computedTree.ts +143 -143
- package/src/context/context.ts +1 -1
- package/src/dataModel/BaseDataModel.ts +194 -192
- package/src/dataModel/actions.ts +1 -1
- package/src/dataModel/newDataModel.ts +57 -57
- package/src/deepChange/index.ts +1 -1
- package/src/deepChange/onDeepChange.ts +244 -244
- package/src/frozen/Frozen.ts +146 -146
- package/src/globalConfig/globalConfig.ts +107 -107
- package/src/index.ts +24 -24
- package/src/model/BaseModel.ts +275 -273
- package/src/model/ModelConstructorOptions.ts +15 -15
- package/src/model/getModelMetadata.ts +51 -51
- package/src/model/index.ts +6 -6
- package/src/model/mixins.ts +288 -288
- package/src/model/newModel.ts +301 -301
- package/src/model/utils.ts +249 -249
- package/src/modelShared/modelDecorator.ts +260 -253
- package/src/modelShared/modelInfo.ts +50 -50
- package/src/modelShared/modelSymbols.ts +3 -3
- package/src/modelShared/newModel.ts +25 -25
- package/src/modelShared/prop.ts +713 -684
- package/src/modelShared/sharedInternalModel.ts +427 -417
- package/src/modelShared/utils.ts +24 -24
- package/src/parent/core.ts +65 -65
- package/src/parent/coreObjectChildren.ts +180 -180
- package/src/parent/detach.ts +47 -47
- package/src/parent/findParent.ts +1 -1
- package/src/parent/getChildrenObjects.ts +25 -25
- package/src/parent/path.ts +386 -381
- package/src/parent/setParent.ts +149 -149
- package/src/parent/walkTree.ts +1 -1
- package/src/patch/applyPatches.ts +187 -187
- package/src/patch/jsonPatch.ts +108 -108
- package/src/patch/patchRecorder.ts +6 -1
- package/src/redux/connectReduxDevTools.ts +39 -7
- package/src/redux/redux.ts +6 -2
- package/src/ref/Ref.ts +1 -1
- package/src/ref/core.ts +339 -345
- package/src/ref/customRef.ts +3 -3
- package/src/ref/rootRef.ts +100 -100
- package/src/rootStore/attachDetach.ts +89 -89
- package/src/rootStore/rootStore.ts +1 -1
- package/src/snapshot/SnapshotOf.ts +104 -104
- package/src/snapshot/SnapshotProcessingError.ts +45 -45
- package/src/snapshot/applySnapshot.ts +177 -181
- package/src/snapshot/cloneTreeValue.ts +30 -30
- package/src/snapshot/fromArraySnapshot.ts +27 -33
- package/src/snapshot/fromModelSnapshot.ts +70 -70
- package/src/snapshot/fromPlainObjectSnapshot.ts +47 -42
- package/src/snapshot/fromSnapshot.ts +198 -198
- package/src/snapshot/getSnapshot.ts +80 -79
- package/src/snapshot/index.ts +8 -8
- package/src/snapshot/internal.ts +230 -230
- package/src/snapshot/reconcileArraySnapshot.ts +70 -70
- package/src/snapshot/reconcileModelSnapshot.ts +155 -150
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +72 -72
- package/src/snapshot/reconcileSnapshot.ts +88 -88
- package/src/standardActions/actions.ts +64 -64
- package/src/standardActions/objectActions.ts +60 -60
- package/src/transforms/ImmutableDate.ts +93 -93
- package/src/transforms/asMap.ts +50 -50
- package/src/transforms/asSet.ts +22 -22
- package/src/transforms/bigint.ts +20 -20
- package/src/transforms/date.ts +40 -40
- package/src/treeUtils/deepEquals.ts +55 -55
- package/src/treeUtils/sandbox.ts +6 -3
- package/src/tweaker/TweakerPriority.ts +9 -9
- package/src/tweaker/tweak.ts +4 -3
- package/src/tweaker/tweakArray.ts +530 -475
- package/src/tweaker/tweakFrozen.ts +42 -42
- package/src/tweaker/tweakModel.ts +1 -1
- package/src/tweaker/tweakPlainObject.ts +355 -355
- package/src/tweaker/typeChecking.ts +101 -101
- package/src/types/SnapshotTypeMismatchError.ts +46 -46
- package/src/types/TypeCheckError.ts +183 -184
- package/src/types/TypeChecker.ts +202 -185
- package/src/types/arrayBased/typesArray.ts +136 -137
- package/src/types/arrayBased/typesTuple.ts +138 -139
- package/src/types/createPerEntryCachedCheck.ts +166 -160
- package/src/types/index.ts +6 -6
- package/src/types/objectBased/typesArraySet.ts +133 -134
- package/src/types/objectBased/typesDataModelData.ts +203 -204
- package/src/types/objectBased/typesModel.ts +220 -221
- package/src/types/objectBased/typesObject.ts +308 -310
- package/src/types/objectBased/typesObjectMap.ts +142 -143
- package/src/types/objectBased/typesRecord.ts +156 -157
- package/src/types/objectBased/typesRef.ts +89 -89
- package/src/types/primitiveBased/typesEnum.ts +63 -63
- package/src/types/primitiveBased/typesPrimitive.ts +219 -220
- package/src/types/registerDefaultStandardTypeResolvers.ts +21 -21
- package/src/types/resolveTypeChecker.ts +78 -78
- package/src/types/schemas.ts +208 -208
- package/src/types/tProp.ts +277 -268
- package/src/types/typeCheck.ts +21 -21
- package/src/types/typeCheckErrorUtils.ts +23 -23
- package/src/types/types.ts +142 -142
- package/src/types/utility/typeToStoredData.ts +37 -37
- package/src/types/utility/typesBigInt.ts +15 -15
- package/src/types/utility/typesCodec.ts +41 -38
- package/src/types/utility/typesCodecCore.ts +178 -178
- package/src/types/utility/typesCodecSupport.ts +806 -806
- package/src/types/utility/typesDate.ts +27 -27
- package/src/types/utility/typesMap.ts +253 -222
- package/src/types/utility/typesOr.ts +263 -259
- package/src/types/utility/typesRefinement.ts +131 -125
- package/src/types/utility/typesSet.ts +110 -110
- package/src/types/utility/typesSkipCheck.ts +69 -70
- package/src/types/utility/typesTag.ts +88 -82
- package/src/types/utility/typesUnchecked.ts +40 -40
- package/src/utils/AnyFunction.ts +4 -4
- package/src/utils/ModelPool.ts +59 -59
- package/src/utils/chainFns.ts +18 -18
- package/src/utils/decorators.ts +274 -274
- package/src/utils/errorDiagnostics.ts +204 -204
- package/src/utils/index.ts +443 -372
- package/src/wrappers/ArraySet.ts +156 -156
- package/src/wrappers/ObjectMap.ts +155 -136
- package/src/wrappers/asMap.ts +65 -30
- package/src/wrappers/asSet.ts +3 -3
|
@@ -1,259 +1,263 @@
|
|
|
1
|
-
import { failure, lazy } from "../../utils"
|
|
2
|
-
import { getTypeInfo } from "../getTypeInfo"
|
|
3
|
-
import {
|
|
4
|
-
resolveStandardType,
|
|
5
|
-
resolveStandardTypeNoThrow,
|
|
6
|
-
resolveTypeChecker,
|
|
7
|
-
} from "../resolveTypeChecker"
|
|
8
|
-
import { SnapshotTypeMismatchError } from "../SnapshotTypeMismatchError"
|
|
9
|
-
import type { AnyStandardType, AnyType } from "../schemas"
|
|
10
|
-
import { TypeCheckError } from "../TypeCheckError"
|
|
11
|
-
import {
|
|
12
|
-
getTypeCheckerBaseTypeFromValue,
|
|
13
|
-
lateTypeChecker,
|
|
14
|
-
TypeChecker,
|
|
15
|
-
TypeCheckerBaseType,
|
|
16
|
-
TypeInfo,
|
|
17
|
-
TypeInfoGen,
|
|
18
|
-
} from "../TypeChecker"
|
|
19
|
-
import { typesUnchecked } from "./typesUnchecked"
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A type that represents the union of several other types (a | b | c | ...).
|
|
23
|
-
* Accepts a dispatcher that, given a snapshot, returns the type
|
|
24
|
-
* that snapshot is.
|
|
25
|
-
*
|
|
26
|
-
* @template T Type.
|
|
27
|
-
* @param dispatcher Function that given a snapshot returns the type.
|
|
28
|
-
* @param orTypes Possible types.
|
|
29
|
-
* @returns
|
|
30
|
-
*/
|
|
31
|
-
export function typesOr<T extends AnyType[]>(
|
|
32
|
-
dispatcher: (sn: any) => T[number],
|
|
33
|
-
...orTypes: T
|
|
34
|
-
): T[number]
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* A type that represents the union of several other types (a | b | c | ...).
|
|
38
|
-
*
|
|
39
|
-
* Example:
|
|
40
|
-
* ```ts
|
|
41
|
-
* const booleanOrNumberType = types.or(types.boolean, types.number)
|
|
42
|
-
* ```
|
|
43
|
-
*
|
|
44
|
-
* @template T Type.
|
|
45
|
-
* @param orTypes Possible types.
|
|
46
|
-
* @returns
|
|
47
|
-
*/
|
|
48
|
-
export function typesOr<T extends AnyType[]>(...orTypes: T): T[number]
|
|
49
|
-
|
|
50
|
-
export function typesOr(
|
|
51
|
-
dispatcherOrType: ((sn: any) => AnyType) | AnyType,
|
|
52
|
-
...moreOrTypes: AnyType[]
|
|
53
|
-
): AnyType {
|
|
54
|
-
const orTypes = moreOrTypes.slice()
|
|
55
|
-
let finalDispatcher: ((sn: any) => TypeChecker) | undefined
|
|
56
|
-
|
|
57
|
-
const firstTypeChecker = resolveStandardTypeNoThrow(dispatcherOrType as AnyType)
|
|
58
|
-
|
|
59
|
-
if (firstTypeChecker) {
|
|
60
|
-
orTypes.unshift(firstTypeChecker)
|
|
61
|
-
} else {
|
|
62
|
-
const dispatcher = dispatcherOrType as (sn: any) => AnyType
|
|
63
|
-
finalDispatcher = (sn: any) => {
|
|
64
|
-
const type = dispatcher(sn)
|
|
65
|
-
const typeChecker = resolveTypeChecker(type)
|
|
66
|
-
return typeChecker
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (orTypes.length <= 0) {
|
|
71
|
-
throw failure("or type must have at least 1 possible type")
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const originalDispatcher = firstTypeChecker
|
|
75
|
-
? undefined
|
|
76
|
-
: (dispatcherOrType as (sn: any) => AnyType)
|
|
77
|
-
const typeInfoGen: TypeInfoGen = (t) =>
|
|
78
|
-
new OrTypeInfo(t, orTypes.map(resolveStandardType), originalDispatcher)
|
|
79
|
-
|
|
80
|
-
return lateTypeChecker(() => {
|
|
81
|
-
const checkers = orTypes.map(resolveTypeChecker)
|
|
82
|
-
|
|
83
|
-
// if the or includes unchecked then it is unchecked
|
|
84
|
-
if (checkers.some((tc) => tc.unchecked)) {
|
|
85
|
-
return typesUnchecked() as any
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const getTypeName = (...recursiveTypeCheckers: TypeChecker[]) => {
|
|
89
|
-
const typeNames = checkers.map((tc) => {
|
|
90
|
-
if (recursiveTypeCheckers.includes(tc)) {
|
|
91
|
-
return "..."
|
|
92
|
-
}
|
|
93
|
-
return tc.getTypeName(...recursiveTypeCheckers, tc)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
return typeNames.join(" | ")
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let thisTcBaseType: TypeCheckerBaseType
|
|
100
|
-
if (checkers.some((c) => c.baseType !== checkers[0].baseType)) {
|
|
101
|
-
thisTcBaseType = TypeCheckerBaseType.Any
|
|
102
|
-
} else {
|
|
103
|
-
thisTcBaseType = checkers[0].baseType
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const anyCheckers: TypeChecker[] = []
|
|
107
|
-
const primitiveOrAnyCheckers: TypeChecker[] = []
|
|
108
|
-
const objectOrAnyCheckers: TypeChecker[] = []
|
|
109
|
-
const arrayOrAnyCheckers: TypeChecker[] = []
|
|
110
|
-
|
|
111
|
-
for (let i = 0; i < checkers.length; i++) {
|
|
112
|
-
const checker = checkers[i]
|
|
113
|
-
switch (checker.baseType) {
|
|
114
|
-
case TypeCheckerBaseType.Primitive:
|
|
115
|
-
primitiveOrAnyCheckers.push(checker)
|
|
116
|
-
break
|
|
117
|
-
case TypeCheckerBaseType.Object:
|
|
118
|
-
objectOrAnyCheckers.push(checker)
|
|
119
|
-
break
|
|
120
|
-
case TypeCheckerBaseType.Array:
|
|
121
|
-
arrayOrAnyCheckers.push(checker)
|
|
122
|
-
break
|
|
123
|
-
case TypeCheckerBaseType.Any:
|
|
124
|
-
anyCheckers.push(checker)
|
|
125
|
-
primitiveOrAnyCheckers.push(checker)
|
|
126
|
-
objectOrAnyCheckers.push(checker)
|
|
127
|
-
arrayOrAnyCheckers.push(checker)
|
|
128
|
-
break
|
|
129
|
-
default:
|
|
130
|
-
break
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const getCandidateCheckers = (
|
|
135
|
-
valueBaseType: TypeCheckerBaseType
|
|
136
|
-
): ReadonlyArray<TypeChecker> => {
|
|
137
|
-
switch (valueBaseType) {
|
|
138
|
-
case TypeCheckerBaseType.Primitive:
|
|
139
|
-
return primitiveOrAnyCheckers
|
|
140
|
-
case TypeCheckerBaseType.Object:
|
|
141
|
-
return objectOrAnyCheckers
|
|
142
|
-
case TypeCheckerBaseType.Array:
|
|
143
|
-
return arrayOrAnyCheckers
|
|
144
|
-
case TypeCheckerBaseType.Any:
|
|
145
|
-
return anyCheckers
|
|
146
|
-
default:
|
|
147
|
-
return anyCheckers
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const thisTc: TypeChecker = new TypeChecker(
|
|
152
|
-
thisTcBaseType,
|
|
153
|
-
|
|
154
|
-
(value, path, typeCheckedValue) => {
|
|
155
|
-
// Union types must always full-check every alternative. Partial checking could
|
|
156
|
-
// produce false positives when overlapping alternatives each pass on different
|
|
157
|
-
// property subsets without any single alternative passing in full.
|
|
158
|
-
const valueBaseType = getTypeCheckerBaseTypeFromValue(value)
|
|
159
|
-
const candidateCheckers = getCandidateCheckers(valueBaseType)
|
|
160
|
-
|
|
161
|
-
for (let i = 0; i < candidateCheckers.length; i++) {
|
|
162
|
-
const tc = candidateCheckers[i]
|
|
163
|
-
if (tc.skipCheck) {
|
|
164
|
-
// For skipCheck branches, verify structural match without deep validation
|
|
165
|
-
if (tc.snapshotType(value)) {
|
|
166
|
-
return null
|
|
167
|
-
}
|
|
168
|
-
} else if (!tc.check(value, path, typeCheckedValue)) {
|
|
169
|
-
return null
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return new TypeCheckError({
|
|
174
|
-
path,
|
|
175
|
-
expectedTypeName: getTypeName(thisTc),
|
|
176
|
-
actualValue: value,
|
|
177
|
-
typeCheckedValue,
|
|
178
|
-
})
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
getTypeName,
|
|
182
|
-
typeInfoGen,
|
|
183
|
-
|
|
184
|
-
(value) => {
|
|
185
|
-
const valueBaseType = getTypeCheckerBaseTypeFromValue(value)
|
|
186
|
-
const checkerForBaseType = getCandidateCheckers(valueBaseType)
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
checkerForBaseType.length === 1 &&
|
|
190
|
-
checkerForBaseType[0].baseType === valueBaseType &&
|
|
191
|
-
!checkerForBaseType[0].skipCheck
|
|
192
|
-
) {
|
|
193
|
-
// when there is only one valid option accept it without asking
|
|
194
|
-
// this is done because:
|
|
195
|
-
// 1) performance (avoid checking structure if not needed)
|
|
196
|
-
// 2) so we can accept untyped models when paired with undefined | null
|
|
197
|
-
return checkerForBaseType[0]
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
for (let i = 0; i < checkerForBaseType.length; i++) {
|
|
201
|
-
const matchingType = checkerForBaseType[i].snapshotType(value)
|
|
202
|
-
if (matchingType) {
|
|
203
|
-
return matchingType
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return null
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
(sn) => {
|
|
211
|
-
const type = finalDispatcher ? finalDispatcher(sn) : thisTc.snapshotType(sn)
|
|
212
|
-
if (!type) {
|
|
213
|
-
throw new SnapshotTypeMismatchError({
|
|
214
|
-
expectedTypeName: getTypeName(thisTc),
|
|
215
|
-
actualValue: sn,
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return type.fromSnapshotProcessor(sn)
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
(sn) => {
|
|
223
|
-
const type = finalDispatcher ? finalDispatcher(sn) : thisTc.snapshotType(sn)
|
|
224
|
-
if (!type) {
|
|
225
|
-
throw new SnapshotTypeMismatchError({
|
|
226
|
-
expectedTypeName: getTypeName(thisTc),
|
|
227
|
-
actualValue: sn,
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return type.toSnapshotProcessor(sn)
|
|
232
|
-
}
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
return thisTc
|
|
236
|
-
}, typeInfoGen) as any
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* `types.or` type info.
|
|
241
|
-
*/
|
|
242
|
-
export class OrTypeInfo extends TypeInfo {
|
|
243
|
-
readonly kind = "or"
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
1
|
+
import { failure, lazy } from "../../utils"
|
|
2
|
+
import { getTypeInfo } from "../getTypeInfo"
|
|
3
|
+
import {
|
|
4
|
+
resolveStandardType,
|
|
5
|
+
resolveStandardTypeNoThrow,
|
|
6
|
+
resolveTypeChecker,
|
|
7
|
+
} from "../resolveTypeChecker"
|
|
8
|
+
import { SnapshotTypeMismatchError } from "../SnapshotTypeMismatchError"
|
|
9
|
+
import type { AnyStandardType, AnyType } from "../schemas"
|
|
10
|
+
import { TypeCheckError } from "../TypeCheckError"
|
|
11
|
+
import {
|
|
12
|
+
getTypeCheckerBaseTypeFromValue,
|
|
13
|
+
lateTypeChecker,
|
|
14
|
+
TypeChecker,
|
|
15
|
+
TypeCheckerBaseType,
|
|
16
|
+
TypeInfo,
|
|
17
|
+
type TypeInfoGen,
|
|
18
|
+
} from "../TypeChecker"
|
|
19
|
+
import { typesUnchecked } from "./typesUnchecked"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A type that represents the union of several other types (a | b | c | ...).
|
|
23
|
+
* Accepts a dispatcher that, given a snapshot, returns the type
|
|
24
|
+
* that snapshot is.
|
|
25
|
+
*
|
|
26
|
+
* @template T Type.
|
|
27
|
+
* @param dispatcher Function that given a snapshot returns the type.
|
|
28
|
+
* @param orTypes Possible types.
|
|
29
|
+
* @returns
|
|
30
|
+
*/
|
|
31
|
+
export function typesOr<T extends AnyType[]>(
|
|
32
|
+
dispatcher: (sn: any) => T[number],
|
|
33
|
+
...orTypes: T
|
|
34
|
+
): T[number]
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A type that represents the union of several other types (a | b | c | ...).
|
|
38
|
+
*
|
|
39
|
+
* Example:
|
|
40
|
+
* ```ts
|
|
41
|
+
* const booleanOrNumberType = types.or(types.boolean, types.number)
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @template T Type.
|
|
45
|
+
* @param orTypes Possible types.
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
export function typesOr<T extends AnyType[]>(...orTypes: T): T[number]
|
|
49
|
+
|
|
50
|
+
export function typesOr(
|
|
51
|
+
dispatcherOrType: ((sn: any) => AnyType) | AnyType,
|
|
52
|
+
...moreOrTypes: AnyType[]
|
|
53
|
+
): AnyType {
|
|
54
|
+
const orTypes = moreOrTypes.slice()
|
|
55
|
+
let finalDispatcher: ((sn: any) => TypeChecker) | undefined
|
|
56
|
+
|
|
57
|
+
const firstTypeChecker = resolveStandardTypeNoThrow(dispatcherOrType as AnyType)
|
|
58
|
+
|
|
59
|
+
if (firstTypeChecker) {
|
|
60
|
+
orTypes.unshift(firstTypeChecker)
|
|
61
|
+
} else {
|
|
62
|
+
const dispatcher = dispatcherOrType as (sn: any) => AnyType
|
|
63
|
+
finalDispatcher = (sn: any) => {
|
|
64
|
+
const type = dispatcher(sn)
|
|
65
|
+
const typeChecker = resolveTypeChecker(type)
|
|
66
|
+
return typeChecker
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (orTypes.length <= 0) {
|
|
71
|
+
throw failure("or type must have at least 1 possible type")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const originalDispatcher = firstTypeChecker
|
|
75
|
+
? undefined
|
|
76
|
+
: (dispatcherOrType as (sn: any) => AnyType)
|
|
77
|
+
const typeInfoGen: TypeInfoGen = (t) =>
|
|
78
|
+
new OrTypeInfo(t, orTypes.map(resolveStandardType), originalDispatcher)
|
|
79
|
+
|
|
80
|
+
return lateTypeChecker(() => {
|
|
81
|
+
const checkers = orTypes.map(resolveTypeChecker)
|
|
82
|
+
|
|
83
|
+
// if the or includes unchecked then it is unchecked
|
|
84
|
+
if (checkers.some((tc) => tc.unchecked)) {
|
|
85
|
+
return typesUnchecked() as any
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const getTypeName = (...recursiveTypeCheckers: TypeChecker[]) => {
|
|
89
|
+
const typeNames = checkers.map((tc) => {
|
|
90
|
+
if (recursiveTypeCheckers.includes(tc)) {
|
|
91
|
+
return "..."
|
|
92
|
+
}
|
|
93
|
+
return tc.getTypeName(...recursiveTypeCheckers, tc)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return typeNames.join(" | ")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let thisTcBaseType: TypeCheckerBaseType
|
|
100
|
+
if (checkers.some((c) => c.baseType !== checkers[0].baseType)) {
|
|
101
|
+
thisTcBaseType = TypeCheckerBaseType.Any
|
|
102
|
+
} else {
|
|
103
|
+
thisTcBaseType = checkers[0].baseType
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const anyCheckers: TypeChecker[] = []
|
|
107
|
+
const primitiveOrAnyCheckers: TypeChecker[] = []
|
|
108
|
+
const objectOrAnyCheckers: TypeChecker[] = []
|
|
109
|
+
const arrayOrAnyCheckers: TypeChecker[] = []
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < checkers.length; i++) {
|
|
112
|
+
const checker = checkers[i]
|
|
113
|
+
switch (checker.baseType) {
|
|
114
|
+
case TypeCheckerBaseType.Primitive:
|
|
115
|
+
primitiveOrAnyCheckers.push(checker)
|
|
116
|
+
break
|
|
117
|
+
case TypeCheckerBaseType.Object:
|
|
118
|
+
objectOrAnyCheckers.push(checker)
|
|
119
|
+
break
|
|
120
|
+
case TypeCheckerBaseType.Array:
|
|
121
|
+
arrayOrAnyCheckers.push(checker)
|
|
122
|
+
break
|
|
123
|
+
case TypeCheckerBaseType.Any:
|
|
124
|
+
anyCheckers.push(checker)
|
|
125
|
+
primitiveOrAnyCheckers.push(checker)
|
|
126
|
+
objectOrAnyCheckers.push(checker)
|
|
127
|
+
arrayOrAnyCheckers.push(checker)
|
|
128
|
+
break
|
|
129
|
+
default:
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const getCandidateCheckers = (
|
|
135
|
+
valueBaseType: TypeCheckerBaseType
|
|
136
|
+
): ReadonlyArray<TypeChecker> => {
|
|
137
|
+
switch (valueBaseType) {
|
|
138
|
+
case TypeCheckerBaseType.Primitive:
|
|
139
|
+
return primitiveOrAnyCheckers
|
|
140
|
+
case TypeCheckerBaseType.Object:
|
|
141
|
+
return objectOrAnyCheckers
|
|
142
|
+
case TypeCheckerBaseType.Array:
|
|
143
|
+
return arrayOrAnyCheckers
|
|
144
|
+
case TypeCheckerBaseType.Any:
|
|
145
|
+
return anyCheckers
|
|
146
|
+
default:
|
|
147
|
+
return anyCheckers
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const thisTc: TypeChecker = new TypeChecker(
|
|
152
|
+
thisTcBaseType,
|
|
153
|
+
|
|
154
|
+
(value, path, typeCheckedValue) => {
|
|
155
|
+
// Union types must always full-check every alternative. Partial checking could
|
|
156
|
+
// produce false positives when overlapping alternatives each pass on different
|
|
157
|
+
// property subsets without any single alternative passing in full.
|
|
158
|
+
const valueBaseType = getTypeCheckerBaseTypeFromValue(value)
|
|
159
|
+
const candidateCheckers = getCandidateCheckers(valueBaseType)
|
|
160
|
+
|
|
161
|
+
for (let i = 0; i < candidateCheckers.length; i++) {
|
|
162
|
+
const tc = candidateCheckers[i]
|
|
163
|
+
if (tc.skipCheck) {
|
|
164
|
+
// For skipCheck branches, verify structural match without deep validation
|
|
165
|
+
if (tc.snapshotType(value)) {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
} else if (!tc.check(value, path, typeCheckedValue)) {
|
|
169
|
+
return null
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return new TypeCheckError({
|
|
174
|
+
path,
|
|
175
|
+
expectedTypeName: getTypeName(thisTc),
|
|
176
|
+
actualValue: value,
|
|
177
|
+
typeCheckedValue,
|
|
178
|
+
})
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
getTypeName,
|
|
182
|
+
typeInfoGen,
|
|
183
|
+
|
|
184
|
+
(value) => {
|
|
185
|
+
const valueBaseType = getTypeCheckerBaseTypeFromValue(value)
|
|
186
|
+
const checkerForBaseType = getCandidateCheckers(valueBaseType)
|
|
187
|
+
|
|
188
|
+
if (
|
|
189
|
+
checkerForBaseType.length === 1 &&
|
|
190
|
+
checkerForBaseType[0].baseType === valueBaseType &&
|
|
191
|
+
!checkerForBaseType[0].skipCheck
|
|
192
|
+
) {
|
|
193
|
+
// when there is only one valid option accept it without asking
|
|
194
|
+
// this is done because:
|
|
195
|
+
// 1) performance (avoid checking structure if not needed)
|
|
196
|
+
// 2) so we can accept untyped models when paired with undefined | null
|
|
197
|
+
return checkerForBaseType[0]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (let i = 0; i < checkerForBaseType.length; i++) {
|
|
201
|
+
const matchingType = checkerForBaseType[i].snapshotType(value)
|
|
202
|
+
if (matchingType) {
|
|
203
|
+
return matchingType
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return null
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
(sn) => {
|
|
211
|
+
const type = finalDispatcher ? finalDispatcher(sn) : thisTc.snapshotType(sn)
|
|
212
|
+
if (!type) {
|
|
213
|
+
throw new SnapshotTypeMismatchError({
|
|
214
|
+
expectedTypeName: getTypeName(thisTc),
|
|
215
|
+
actualValue: sn,
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return type.fromSnapshotProcessor(sn)
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
(sn) => {
|
|
223
|
+
const type = finalDispatcher ? finalDispatcher(sn) : thisTc.snapshotType(sn)
|
|
224
|
+
if (!type) {
|
|
225
|
+
throw new SnapshotTypeMismatchError({
|
|
226
|
+
expectedTypeName: getTypeName(thisTc),
|
|
227
|
+
actualValue: sn,
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return type.toSnapshotProcessor(sn)
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return thisTc
|
|
236
|
+
}, typeInfoGen) as any
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* `types.or` type info.
|
|
241
|
+
*/
|
|
242
|
+
export class OrTypeInfo extends TypeInfo {
|
|
243
|
+
readonly kind = "or"
|
|
244
|
+
readonly orTypes: ReadonlyArray<AnyStandardType>
|
|
245
|
+
readonly dispatcher: ((sn: any) => AnyType) | undefined
|
|
246
|
+
|
|
247
|
+
// memoize to always return the same array on the getter
|
|
248
|
+
private _orTypeInfos = lazy(() => this.orTypes.map(getTypeInfo))
|
|
249
|
+
|
|
250
|
+
get orTypeInfos(): ReadonlyArray<TypeInfo> {
|
|
251
|
+
return this._orTypeInfos()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
constructor(
|
|
255
|
+
thisType: AnyStandardType,
|
|
256
|
+
orTypes: ReadonlyArray<AnyStandardType>,
|
|
257
|
+
dispatcher: ((sn: any) => AnyType) | undefined
|
|
258
|
+
) {
|
|
259
|
+
super(thisType)
|
|
260
|
+
this.orTypes = orTypes
|
|
261
|
+
this.dispatcher = dispatcher
|
|
262
|
+
}
|
|
263
|
+
}
|