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,310 +1,308 @@
|
|
|
1
|
-
import type { O } from "ts-toolbelt"
|
|
2
|
-
import { Frozen } from "../../frozen/Frozen"
|
|
3
|
-
import type { Path } from "../../parent/pathTypes"
|
|
4
|
-
import { assertIsFunction, assertIsObject, isObject, lazy } from "../../utils"
|
|
5
|
-
import { withErrorPathSegment } from "../../utils/errorDiagnostics"
|
|
6
|
-
import { createPerEntryCachedCheck } from "../createPerEntryCachedCheck"
|
|
7
|
-
import { getTypeInfo } from "../getTypeInfo"
|
|
8
|
-
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
9
|
-
import type {
|
|
10
|
-
AnyStandardType,
|
|
11
|
-
AnyType,
|
|
12
|
-
ModelType,
|
|
13
|
-
ObjectTypeFunction,
|
|
14
|
-
TypeToData,
|
|
15
|
-
} from "../schemas"
|
|
16
|
-
import { TypeCheckError } from "../TypeCheckError"
|
|
17
|
-
import {
|
|
18
|
-
LateTypeChecker,
|
|
19
|
-
lateTypeChecker,
|
|
20
|
-
TypeChecker,
|
|
21
|
-
TypeCheckerBaseType,
|
|
22
|
-
TypeInfo,
|
|
23
|
-
TypeInfoGen,
|
|
24
|
-
} from "../TypeChecker"
|
|
25
|
-
import { prependPathElementToTypeCheckError } from "../typeCheckErrorUtils"
|
|
26
|
-
|
|
27
|
-
function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGen): S {
|
|
28
|
-
assertIsFunction(objFn, "objFn")
|
|
29
|
-
|
|
30
|
-
return lateTypeChecker(() => {
|
|
31
|
-
const objectSchema: Record<string, TypeChecker | LateTypeChecker> = objFn()
|
|
32
|
-
assertIsObject(objectSchema, "objectSchema")
|
|
33
|
-
|
|
34
|
-
type SchemaEntry = Readonly<{
|
|
35
|
-
propName: string
|
|
36
|
-
getResolvedChecker: () => TypeChecker
|
|
37
|
-
}>
|
|
38
|
-
type CheckedSchemaEntry = Readonly<{
|
|
39
|
-
propName: string
|
|
40
|
-
checker: TypeChecker
|
|
41
|
-
}>
|
|
42
|
-
|
|
43
|
-
const schemaEntries: ReadonlyArray<SchemaEntry> = Object.entries(objectSchema).map(
|
|
44
|
-
([propName, unresolvedChecker]) => {
|
|
45
|
-
let resolvedChecker: TypeChecker | undefined
|
|
46
|
-
return {
|
|
47
|
-
propName,
|
|
48
|
-
getResolvedChecker: () => {
|
|
49
|
-
if (!resolvedChecker) {
|
|
50
|
-
resolvedChecker = resolveTypeChecker(unresolvedChecker)
|
|
51
|
-
}
|
|
52
|
-
return resolvedChecker
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
const schemaEntryByPropName: Record<string, SchemaEntry> = Object.create(null)
|
|
59
|
-
for (let i = 0; i < schemaEntries.length; i++) {
|
|
60
|
-
const schemaEntry = schemaEntries[i]
|
|
61
|
-
schemaEntryByPropName[schemaEntry.propName] = schemaEntry
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Precompute the entries we actually need to type-check.
|
|
65
|
-
// This avoids per-check branching for unchecked props and checker re-resolution.
|
|
66
|
-
const checkedSchemaEntries = lazy(() => {
|
|
67
|
-
const checkedEntries: CheckedSchemaEntry[] = []
|
|
68
|
-
for (let i = 0; i < schemaEntries.length; i++) {
|
|
69
|
-
const schemaEntry = schemaEntries[i]
|
|
70
|
-
const checker = schemaEntry.getResolvedChecker()
|
|
71
|
-
if (!checker.unchecked && !checker.skipCheck) {
|
|
72
|
-
checkedEntries.push({
|
|
73
|
-
propName: schemaEntry.propName,
|
|
74
|
-
checker,
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return checkedEntries
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const emptyChildPath: Path = []
|
|
82
|
-
|
|
83
|
-
const getTypeName = (...recursiveTypeCheckers: TypeChecker[]) => {
|
|
84
|
-
const propsMsg: string[] = []
|
|
85
|
-
for (let i = 0; i < schemaEntries.length; i++) {
|
|
86
|
-
const { propName, getResolvedChecker } = schemaEntries[i]
|
|
87
|
-
const tc = getResolvedChecker()
|
|
88
|
-
let propTypename = "..."
|
|
89
|
-
if (!recursiveTypeCheckers.includes(tc)) {
|
|
90
|
-
propTypename = tc.getTypeName(...recursiveTypeCheckers, tc)
|
|
91
|
-
}
|
|
92
|
-
propsMsg.push(`${propName}: ${propTypename};`)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return `{ ${propsMsg.join(" ")} }`
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const applySnapshotProcessor = (obj: Record<string, unknown>, mode: "from" | "to") => {
|
|
99
|
-
const newObj: typeof obj = {}
|
|
100
|
-
|
|
101
|
-
// note: we allow excess properties when checking objects
|
|
102
|
-
const keys = Object.keys(obj)
|
|
103
|
-
for (let i = 0; i < keys.length; i++) {
|
|
104
|
-
const k = keys[i]
|
|
105
|
-
const schemaEntry = schemaEntryByPropName[k]
|
|
106
|
-
if (schemaEntry) {
|
|
107
|
-
const tc = schemaEntry.getResolvedChecker()
|
|
108
|
-
newObj[k] = withErrorPathSegment(k, () =>
|
|
109
|
-
mode === "from" ? tc.fromSnapshotProcessor(obj[k]) : tc.toSnapshotProcessor(obj[k])
|
|
110
|
-
)
|
|
111
|
-
} else {
|
|
112
|
-
// unknown prop, copy as is
|
|
113
|
-
newObj[k] = obj[k]
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return newObj
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const checkObjectType = (obj: any, path: Path, typeCheckedValue: any) => {
|
|
121
|
-
if (!isObject(obj) || (frozen && !(obj instanceof Frozen))) {
|
|
122
|
-
return new TypeCheckError({
|
|
123
|
-
path,
|
|
124
|
-
expectedTypeName: getTypeName(thisTc),
|
|
125
|
-
actualValue: obj,
|
|
126
|
-
typeCheckedValue,
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return null
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// No setupCachePruning needed: iterates fixed schemaEntries, not dynamic object keys,
|
|
134
|
-
// so entries never become stale.
|
|
135
|
-
const checkObjectProps = createPerEntryCachedCheck<number>(
|
|
136
|
-
(_obj, checkEntry) => {
|
|
137
|
-
const entries = checkedSchemaEntries()
|
|
138
|
-
for (let i = 0; i < entries.length; i++) {
|
|
139
|
-
const error = checkEntry(i)
|
|
140
|
-
if (error) return error
|
|
141
|
-
}
|
|
142
|
-
return null
|
|
143
|
-
},
|
|
144
|
-
(obj, entryIndex, path, typeCheckedValue) => {
|
|
145
|
-
const entry = checkedSchemaEntries()[entryIndex]
|
|
146
|
-
if (!entry) {
|
|
147
|
-
return null
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const error = entry.checker.check(obj[entry.propName], emptyChildPath, typeCheckedValue)
|
|
151
|
-
return error
|
|
152
|
-
? prependPathElementToTypeCheckError(error, path, entry.propName, typeCheckedValue)
|
|
153
|
-
: null
|
|
154
|
-
}
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
const thisTc: TypeChecker = new TypeChecker(
|
|
158
|
-
TypeCheckerBaseType.Object,
|
|
159
|
-
|
|
160
|
-
(obj, path, typeCheckedValue) => {
|
|
161
|
-
const objectTypeError = checkObjectType(obj, path, typeCheckedValue)
|
|
162
|
-
if (objectTypeError) {
|
|
163
|
-
return objectTypeError
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return checkObjectProps(obj, path, typeCheckedValue)
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
getTypeName,
|
|
170
|
-
typeInfoGen,
|
|
171
|
-
|
|
172
|
-
(obj) => {
|
|
173
|
-
if (!isObject(obj)) {
|
|
174
|
-
return null
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// note: we allow excess properties when checking objects
|
|
178
|
-
for (let i = 0; i < schemaEntries.length; i++) {
|
|
179
|
-
const schemaEntry = schemaEntries[i]
|
|
180
|
-
const tc = schemaEntry.getResolvedChecker()
|
|
181
|
-
const objVal = obj[schemaEntry.propName]
|
|
182
|
-
|
|
183
|
-
const valueActualChecker = tc.snapshotType(objVal)
|
|
184
|
-
if (!valueActualChecker) {
|
|
185
|
-
return null
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return thisTc
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
(obj: Record<string, unknown>) => {
|
|
193
|
-
return applySnapshotProcessor(obj, "from")
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
(obj: Record<string, unknown>) => {
|
|
197
|
-
return applySnapshotProcessor(obj, "to")
|
|
198
|
-
}
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
return thisTc
|
|
202
|
-
}, typeInfoGen) as any
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* A type that represents a plain object.
|
|
207
|
-
* Note that the parameter must be a function that returns an object. This is done so objects can support self / cross types.
|
|
208
|
-
*
|
|
209
|
-
* Example:
|
|
210
|
-
* ```ts
|
|
211
|
-
* // notice the ({ ... }), not just { ... }
|
|
212
|
-
* const pointType = types.object(() => ({
|
|
213
|
-
* x: types.number,
|
|
214
|
-
* y: types.number
|
|
215
|
-
* }))
|
|
216
|
-
* ```
|
|
217
|
-
*
|
|
218
|
-
* @template T Type.
|
|
219
|
-
* @param objectFunction Function that generates an object with types.
|
|
220
|
-
* @returns
|
|
221
|
-
*/
|
|
222
|
-
export function typesObject<T>(objectFunction: T): T {
|
|
223
|
-
// we can't type this function or else we won't be able to make it work recursively
|
|
224
|
-
const typeInfoGen: TypeInfoGen = (t) => new ObjectTypeInfo(t, objectFunction as any)
|
|
225
|
-
|
|
226
|
-
return typesObjectHelper(objectFunction, false, typeInfoGen) as any
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* `types.object` type info for an object props.
|
|
231
|
-
*/
|
|
232
|
-
export interface ObjectTypeInfoProps {
|
|
233
|
-
readonly [propName: string]: Readonly<{
|
|
234
|
-
type: AnyStandardType
|
|
235
|
-
typeInfo: TypeInfo
|
|
236
|
-
}>
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* `types.object` type info.
|
|
241
|
-
*/
|
|
242
|
-
export class ObjectTypeInfo extends TypeInfo {
|
|
243
|
-
readonly kind = "object"
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
* const
|
|
275
|
-
* const
|
|
276
|
-
* const
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* @
|
|
281
|
-
* @
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
readonly
|
|
299
|
-
|
|
300
|
-
get dataTypeInfo(): TypeInfo {
|
|
301
|
-
return getTypeInfo(this.dataType)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
constructor(
|
|
305
|
-
thisType
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
}
|
|
1
|
+
import type { O } from "ts-toolbelt"
|
|
2
|
+
import { Frozen } from "../../frozen/Frozen"
|
|
3
|
+
import type { Path } from "../../parent/pathTypes"
|
|
4
|
+
import { assertIsFunction, assertIsObject, isObject, lazy } from "../../utils"
|
|
5
|
+
import { withErrorPathSegment } from "../../utils/errorDiagnostics"
|
|
6
|
+
import { createPerEntryCachedCheck } from "../createPerEntryCachedCheck"
|
|
7
|
+
import { getTypeInfo } from "../getTypeInfo"
|
|
8
|
+
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
9
|
+
import type {
|
|
10
|
+
AnyStandardType,
|
|
11
|
+
AnyType,
|
|
12
|
+
ModelType,
|
|
13
|
+
ObjectTypeFunction,
|
|
14
|
+
TypeToData,
|
|
15
|
+
} from "../schemas"
|
|
16
|
+
import { TypeCheckError } from "../TypeCheckError"
|
|
17
|
+
import {
|
|
18
|
+
type LateTypeChecker,
|
|
19
|
+
lateTypeChecker,
|
|
20
|
+
TypeChecker,
|
|
21
|
+
TypeCheckerBaseType,
|
|
22
|
+
TypeInfo,
|
|
23
|
+
type TypeInfoGen,
|
|
24
|
+
} from "../TypeChecker"
|
|
25
|
+
import { prependPathElementToTypeCheckError } from "../typeCheckErrorUtils"
|
|
26
|
+
|
|
27
|
+
function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGen): S {
|
|
28
|
+
assertIsFunction(objFn, "objFn")
|
|
29
|
+
|
|
30
|
+
return lateTypeChecker(() => {
|
|
31
|
+
const objectSchema: Record<string, TypeChecker | LateTypeChecker> = objFn()
|
|
32
|
+
assertIsObject(objectSchema, "objectSchema")
|
|
33
|
+
|
|
34
|
+
type SchemaEntry = Readonly<{
|
|
35
|
+
propName: string
|
|
36
|
+
getResolvedChecker: () => TypeChecker
|
|
37
|
+
}>
|
|
38
|
+
type CheckedSchemaEntry = Readonly<{
|
|
39
|
+
propName: string
|
|
40
|
+
checker: TypeChecker
|
|
41
|
+
}>
|
|
42
|
+
|
|
43
|
+
const schemaEntries: ReadonlyArray<SchemaEntry> = Object.entries(objectSchema).map(
|
|
44
|
+
([propName, unresolvedChecker]) => {
|
|
45
|
+
let resolvedChecker: TypeChecker | undefined
|
|
46
|
+
return {
|
|
47
|
+
propName,
|
|
48
|
+
getResolvedChecker: () => {
|
|
49
|
+
if (!resolvedChecker) {
|
|
50
|
+
resolvedChecker = resolveTypeChecker(unresolvedChecker)
|
|
51
|
+
}
|
|
52
|
+
return resolvedChecker
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const schemaEntryByPropName: Record<string, SchemaEntry> = Object.create(null)
|
|
59
|
+
for (let i = 0; i < schemaEntries.length; i++) {
|
|
60
|
+
const schemaEntry = schemaEntries[i]
|
|
61
|
+
schemaEntryByPropName[schemaEntry.propName] = schemaEntry
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Precompute the entries we actually need to type-check.
|
|
65
|
+
// This avoids per-check branching for unchecked props and checker re-resolution.
|
|
66
|
+
const checkedSchemaEntries = lazy(() => {
|
|
67
|
+
const checkedEntries: CheckedSchemaEntry[] = []
|
|
68
|
+
for (let i = 0; i < schemaEntries.length; i++) {
|
|
69
|
+
const schemaEntry = schemaEntries[i]
|
|
70
|
+
const checker = schemaEntry.getResolvedChecker()
|
|
71
|
+
if (!checker.unchecked && !checker.skipCheck) {
|
|
72
|
+
checkedEntries.push({
|
|
73
|
+
propName: schemaEntry.propName,
|
|
74
|
+
checker,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return checkedEntries
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const emptyChildPath: Path = []
|
|
82
|
+
|
|
83
|
+
const getTypeName = (...recursiveTypeCheckers: TypeChecker[]) => {
|
|
84
|
+
const propsMsg: string[] = []
|
|
85
|
+
for (let i = 0; i < schemaEntries.length; i++) {
|
|
86
|
+
const { propName, getResolvedChecker } = schemaEntries[i]
|
|
87
|
+
const tc = getResolvedChecker()
|
|
88
|
+
let propTypename = "..."
|
|
89
|
+
if (!recursiveTypeCheckers.includes(tc)) {
|
|
90
|
+
propTypename = tc.getTypeName(...recursiveTypeCheckers, tc)
|
|
91
|
+
}
|
|
92
|
+
propsMsg.push(`${propName}: ${propTypename};`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return `{ ${propsMsg.join(" ")} }`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const applySnapshotProcessor = (obj: Record<string, unknown>, mode: "from" | "to") => {
|
|
99
|
+
const newObj: typeof obj = {}
|
|
100
|
+
|
|
101
|
+
// note: we allow excess properties when checking objects
|
|
102
|
+
const keys = Object.keys(obj)
|
|
103
|
+
for (let i = 0; i < keys.length; i++) {
|
|
104
|
+
const k = keys[i]
|
|
105
|
+
const schemaEntry = schemaEntryByPropName[k]
|
|
106
|
+
if (schemaEntry) {
|
|
107
|
+
const tc = schemaEntry.getResolvedChecker()
|
|
108
|
+
newObj[k] = withErrorPathSegment(k, () =>
|
|
109
|
+
mode === "from" ? tc.fromSnapshotProcessor(obj[k]) : tc.toSnapshotProcessor(obj[k])
|
|
110
|
+
)
|
|
111
|
+
} else {
|
|
112
|
+
// unknown prop, copy as is
|
|
113
|
+
newObj[k] = obj[k]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return newObj
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const checkObjectType = (obj: any, path: Path, typeCheckedValue: any) => {
|
|
121
|
+
if (!isObject(obj) || (frozen && !(obj instanceof Frozen))) {
|
|
122
|
+
return new TypeCheckError({
|
|
123
|
+
path,
|
|
124
|
+
expectedTypeName: getTypeName(thisTc),
|
|
125
|
+
actualValue: obj,
|
|
126
|
+
typeCheckedValue,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// No setupCachePruning needed: iterates fixed schemaEntries, not dynamic object keys,
|
|
134
|
+
// so entries never become stale.
|
|
135
|
+
const checkObjectProps = createPerEntryCachedCheck<number>(
|
|
136
|
+
(_obj, checkEntry) => {
|
|
137
|
+
const entries = checkedSchemaEntries()
|
|
138
|
+
for (let i = 0; i < entries.length; i++) {
|
|
139
|
+
const error = checkEntry(i)
|
|
140
|
+
if (error) return error
|
|
141
|
+
}
|
|
142
|
+
return null
|
|
143
|
+
},
|
|
144
|
+
(obj, entryIndex, path, typeCheckedValue) => {
|
|
145
|
+
const entry = checkedSchemaEntries()[entryIndex]
|
|
146
|
+
if (!entry) {
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const error = entry.checker.check(obj[entry.propName], emptyChildPath, typeCheckedValue)
|
|
151
|
+
return error
|
|
152
|
+
? prependPathElementToTypeCheckError(error, path, entry.propName, typeCheckedValue)
|
|
153
|
+
: null
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const thisTc: TypeChecker = new TypeChecker(
|
|
158
|
+
TypeCheckerBaseType.Object,
|
|
159
|
+
|
|
160
|
+
(obj, path, typeCheckedValue) => {
|
|
161
|
+
const objectTypeError = checkObjectType(obj, path, typeCheckedValue)
|
|
162
|
+
if (objectTypeError) {
|
|
163
|
+
return objectTypeError
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return checkObjectProps(obj, path, typeCheckedValue)
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
getTypeName,
|
|
170
|
+
typeInfoGen,
|
|
171
|
+
|
|
172
|
+
(obj) => {
|
|
173
|
+
if (!isObject(obj)) {
|
|
174
|
+
return null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// note: we allow excess properties when checking objects
|
|
178
|
+
for (let i = 0; i < schemaEntries.length; i++) {
|
|
179
|
+
const schemaEntry = schemaEntries[i]
|
|
180
|
+
const tc = schemaEntry.getResolvedChecker()
|
|
181
|
+
const objVal = obj[schemaEntry.propName]
|
|
182
|
+
|
|
183
|
+
const valueActualChecker = tc.snapshotType(objVal)
|
|
184
|
+
if (!valueActualChecker) {
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return thisTc
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
(obj: Record<string, unknown>) => {
|
|
193
|
+
return applySnapshotProcessor(obj, "from")
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
(obj: Record<string, unknown>) => {
|
|
197
|
+
return applySnapshotProcessor(obj, "to")
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return thisTc
|
|
202
|
+
}, typeInfoGen) as any
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* A type that represents a plain object.
|
|
207
|
+
* Note that the parameter must be a function that returns an object. This is done so objects can support self / cross types.
|
|
208
|
+
*
|
|
209
|
+
* Example:
|
|
210
|
+
* ```ts
|
|
211
|
+
* // notice the ({ ... }), not just { ... }
|
|
212
|
+
* const pointType = types.object(() => ({
|
|
213
|
+
* x: types.number,
|
|
214
|
+
* y: types.number
|
|
215
|
+
* }))
|
|
216
|
+
* ```
|
|
217
|
+
*
|
|
218
|
+
* @template T Type.
|
|
219
|
+
* @param objectFunction Function that generates an object with types.
|
|
220
|
+
* @returns
|
|
221
|
+
*/
|
|
222
|
+
export function typesObject<T>(objectFunction: T): T {
|
|
223
|
+
// we can't type this function or else we won't be able to make it work recursively
|
|
224
|
+
const typeInfoGen: TypeInfoGen = (t) => new ObjectTypeInfo(t, objectFunction as any)
|
|
225
|
+
|
|
226
|
+
return typesObjectHelper(objectFunction, false, typeInfoGen) as any
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* `types.object` type info for an object props.
|
|
231
|
+
*/
|
|
232
|
+
export interface ObjectTypeInfoProps {
|
|
233
|
+
readonly [propName: string]: Readonly<{
|
|
234
|
+
type: AnyStandardType
|
|
235
|
+
typeInfo: TypeInfo
|
|
236
|
+
}>
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* `types.object` type info.
|
|
241
|
+
*/
|
|
242
|
+
export class ObjectTypeInfo extends TypeInfo {
|
|
243
|
+
readonly kind = "object"
|
|
244
|
+
private readonly _objTypeFn: ObjectTypeFunction
|
|
245
|
+
|
|
246
|
+
// memoize to always return the same object
|
|
247
|
+
private _props = lazy(() => {
|
|
248
|
+
const objSchema = this._objTypeFn()
|
|
249
|
+
|
|
250
|
+
const propTypes: O.Writable<ObjectTypeInfoProps> = {}
|
|
251
|
+
Object.keys(objSchema).forEach((propName) => {
|
|
252
|
+
const type = resolveStandardType(objSchema[propName])
|
|
253
|
+
propTypes[propName] = { type, typeInfo: getTypeInfo(type) }
|
|
254
|
+
})
|
|
255
|
+
return propTypes
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
get props(): ObjectTypeInfoProps {
|
|
259
|
+
return this._props()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
constructor(thisType: AnyStandardType, objTypeFn: ObjectTypeFunction) {
|
|
263
|
+
super(thisType)
|
|
264
|
+
this._objTypeFn = objTypeFn
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* A type that represents frozen data.
|
|
270
|
+
*
|
|
271
|
+
* Example:
|
|
272
|
+
* ```ts
|
|
273
|
+
* const frozenNumberType = types.frozen(types.number)
|
|
274
|
+
* const frozenAnyType = types.frozen(types.unchecked<any>())
|
|
275
|
+
* const frozenNumberArrayType = types.frozen(types.array(types.number))
|
|
276
|
+
* const frozenUncheckedNumberArrayType = types.frozen(types.unchecked<number[]>())
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* @template T Type.
|
|
280
|
+
* @param dataType Type of the frozen data.
|
|
281
|
+
* @returns
|
|
282
|
+
*/
|
|
283
|
+
export function typesFrozen<T extends AnyType>(dataType: T): ModelType<Frozen<TypeToData<T>>> {
|
|
284
|
+
return typesObjectHelper(
|
|
285
|
+
() => ({
|
|
286
|
+
data: dataType,
|
|
287
|
+
}),
|
|
288
|
+
true,
|
|
289
|
+
(t) => new FrozenTypeInfo(t, resolveStandardType(dataType))
|
|
290
|
+
) as any
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* `types.frozen` type info.
|
|
295
|
+
*/
|
|
296
|
+
export class FrozenTypeInfo extends TypeInfo {
|
|
297
|
+
readonly kind = "frozen"
|
|
298
|
+
readonly dataType: AnyStandardType
|
|
299
|
+
|
|
300
|
+
get dataTypeInfo(): TypeInfo {
|
|
301
|
+
return getTypeInfo(this.dataType)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
constructor(thisType: AnyStandardType, dataType: AnyStandardType) {
|
|
305
|
+
super(thisType)
|
|
306
|
+
this.dataType = dataType
|
|
307
|
+
}
|
|
308
|
+
}
|