mobx-keystone 2.1.0 → 2.3.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 +17 -0
- package/README.md +1 -1
- package/dist/mobx-keystone.esm.js +555 -516
- package/dist/mobx-keystone.esm.mjs +555 -516
- package/dist/mobx-keystone.umd.js +558 -519
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +2 -10
- package/dist/types/modelShared/modelDecorator.d.ts +5 -2
- package/dist/types/modelShared/prop.d.ts +42 -29
- package/dist/types/ref/Ref.d.ts +1 -4
- package/dist/types/snapshot/SnapshotOf.d.ts +4 -4
- package/dist/types/snapshot/applySnapshot.d.ts +1 -1
- package/dist/types/snapshot/fromSnapshot.d.ts +2 -2
- package/dist/types/snapshot/getSnapshot.d.ts +1 -1
- package/dist/types/types/SnapshotTypeMismatchError.d.ts +3 -1
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/modelTypeMatching.d.ts +1 -0
- package/dist/types/types/schemas.d.ts +41 -37
- package/dist/types/types/tProp.d.ts +12 -6
- package/dist/types/types/types.d.ts +6 -6
- package/dist/types/types/utility/typeToStoredData.d.ts +14 -10
- package/dist/types/types/utility/typesCodecCore.d.ts +3 -0
- package/dist/types/utils/ModelPool.d.ts +0 -1
- package/dist/types/utils/types.d.ts +6 -0
- package/dist/types/wrappers/ArraySet.d.ts +1 -0
- package/package.json +13 -11
- package/src/action/modelFlow.ts +35 -66
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +41 -71
- package/src/actionMiddlewares/transactionMiddleware.ts +1 -1
- package/src/actionMiddlewares/undoMiddleware.ts +16 -22
- package/src/context/context.ts +15 -36
- package/src/dataModel/utils.ts +5 -28
- package/src/deepChange/onDeepChange.ts +44 -126
- package/src/model/BaseModel.ts +1 -1
- package/src/model/utils.ts +5 -10
- package/src/modelShared/modelDecorator.ts +13 -10
- package/src/modelShared/modelInfo.ts +75 -0
- package/src/modelShared/prop.ts +115 -111
- package/src/modelShared/sharedInternalModel.ts +2 -2
- package/src/parent/arrayPaths.ts +1 -4
- package/src/parent/path.ts +0 -10
- package/src/patch/patchRecorder.ts +13 -28
- package/src/ref/core.ts +6 -19
- package/src/rootStore/rootStore.ts +1 -3
- package/src/snapshot/SnapshotOf.ts +24 -48
- package/src/snapshot/applySnapshot.ts +1 -1
- package/src/snapshot/fromSnapshot.ts +10 -3
- package/src/snapshot/getSnapshot.ts +1 -1
- package/src/snapshot/internal.ts +0 -2
- package/src/snapshot/reconcileModelSnapshot.ts +5 -3
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +3 -1
- package/src/treeUtils/draft.ts +6 -1
- package/src/treeUtils/sandbox.ts +10 -7
- package/src/tweaker/tweakArray.ts +20 -50
- package/src/tweaker/tweakPlainObject.ts +9 -15
- package/src/types/SnapshotTypeMismatchError.ts +3 -1
- package/src/types/arrayBased/typesArray.ts +13 -19
- package/src/types/arrayBased/typesTuple.ts +16 -25
- package/src/types/createCachedTypeCheck.ts +17 -30
- package/src/types/index.ts +1 -0
- package/src/types/modelTypeMatching.ts +276 -0
- package/src/types/objectBased/typesDataModelData.ts +7 -8
- package/src/types/objectBased/typesModel.ts +23 -6
- package/src/types/objectBased/typesObject.ts +11 -15
- package/src/types/objectBased/typesRecord.ts +11 -15
- package/src/types/objectBased/typesRef.ts +23 -4
- package/src/types/schemas.ts +59 -71
- package/src/types/tProp.ts +29 -29
- package/src/types/utility/typeToStoredData.ts +22 -21
- package/src/types/utility/typesCodecCore.ts +8 -1
- package/src/types/utility/typesCodecSupport.ts +16 -10
- package/src/types/utility/typesMap.ts +43 -51
- package/src/types/utility/typesOr.ts +38 -8
- package/src/types/utility/typesRefinement.ts +7 -8
- package/src/types/utility/typesSkipCheck.ts +7 -8
- package/src/types/utility/typesTag.ts +7 -8
- package/src/utils/ModelPool.ts +1 -18
- package/src/utils/decorators.ts +5 -5
- package/src/utils/index.ts +1 -20
- package/src/utils/types.ts +5 -0
- package/src/wrappers/ArraySet.ts +27 -14
- package/src/wrappers/ObjectMap.ts +1 -6
- package/src/wrappers/asMap.ts +1 -21
- package/src/wrappers/asSet.ts +0 -8
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { modelTypeKey } from "../model/metadata"
|
|
2
|
+
import {
|
|
3
|
+
findModelInfoForPrototype,
|
|
4
|
+
getModelSubclassDistance,
|
|
5
|
+
getModelSubclassDistanceForName,
|
|
6
|
+
} from "../modelShared/modelInfo"
|
|
7
|
+
import { isObject } from "../utils"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Model type matching pass used while unions match a value against their branches, so they can
|
|
11
|
+
* prefer the branches whose models are the closest to the ones named by the snapshot
|
|
12
|
+
* (`$modelType`):
|
|
13
|
+
* - `none`: not matching union branches; model types match snapshots of their own model and of
|
|
14
|
+
* its subclasses.
|
|
15
|
+
* - `exact`: model types only match snapshots of their own model.
|
|
16
|
+
* - `closest`: model types also match snapshots of subclasses, adding how far the subclass is
|
|
17
|
+
* from the model to `subclassDistance`, so unions can pick the branch with the lowest total.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export type ModelTypeMatchingPass = "none" | "exact" | "closest"
|
|
22
|
+
|
|
23
|
+
let currentPass: ModelTypeMatchingPass = "none"
|
|
24
|
+
let subclassDistance = 0
|
|
25
|
+
let rejectedModelSnapshot = false
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export function getModelTypeMatchingPass(): ModelTypeMatchingPass {
|
|
31
|
+
return currentPass
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Records that a model type matched a snapshot of a subclass `distance` levels below it.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function addModelSubclassDistance(distance: number): void {
|
|
40
|
+
if (currentPass === "closest") {
|
|
41
|
+
subclassDistance += distance
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Records, while unions match their branches, that a model type (or ref) rejected a model
|
|
47
|
+
* snapshot that some other branch could accept: one of a subclass in the exact pass (it would
|
|
48
|
+
* match it in the closest pass), or one of another registered model.
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export function rejectModelSnapshot(): void {
|
|
53
|
+
if (currentPass !== "none") {
|
|
54
|
+
rejectedModelSnapshot = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Runs a value match, returning whether it matched, or `undefined` if it did not because a model
|
|
60
|
+
* type (or ref) rejected a model snapshot (see `rejectModelSnapshot`).
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export function matchUnlessModelSnapshotRejected<A, B>(
|
|
65
|
+
fn: (a: A, b: B) => unknown,
|
|
66
|
+
a: A,
|
|
67
|
+
b: B
|
|
68
|
+
): boolean | undefined {
|
|
69
|
+
const prevRejectedModelSnapshot = rejectedModelSnapshot
|
|
70
|
+
rejectedModelSnapshot = false
|
|
71
|
+
try {
|
|
72
|
+
const matched = !!fn(a, b)
|
|
73
|
+
return matched || !rejectedModelSnapshot ? matched : undefined
|
|
74
|
+
} finally {
|
|
75
|
+
// let outer matches know too
|
|
76
|
+
rejectedModelSnapshot ||= prevRejectedModelSnapshot
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Whether a snapshot `$modelType` matches a model (`modelName`): its own model or, outside the
|
|
82
|
+
* exact pass, a subclass of it. Registered names are compared rather than classes, since with
|
|
83
|
+
* hot reloading a subclass might still extend a previous registration of the model.
|
|
84
|
+
*
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
export function snapshotModelTypeMatches(snModelType: unknown, modelName: string): boolean {
|
|
88
|
+
if (typeof snModelType !== "string") {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
if (snModelType === modelName) {
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
const distance = getModelSubclassDistanceForName(snModelType, modelName)
|
|
95
|
+
if (distance === undefined) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
if (distance < 0 || currentPass === "exact") {
|
|
99
|
+
rejectModelSnapshot()
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
addModelSubclassDistance(distance)
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whether a snapshot `$modelType` names a registered model that is neither a model
|
|
108
|
+
* (`modelName`) nor a subclass of it, so processing the snapshot as that model would silently
|
|
109
|
+
* create an unrelated model (unregistered names are reported later by the model snapshotter).
|
|
110
|
+
*
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
export function isUnrelatedSnapshotModelType(snModelType: unknown, modelName: string): boolean {
|
|
114
|
+
return (
|
|
115
|
+
typeof snModelType === "string" &&
|
|
116
|
+
snModelType !== modelName &&
|
|
117
|
+
(getModelSubclassDistanceForName(snModelType, modelName) ?? 0) < 0
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Whether a model instance is of a model (`modelName`) or of a subclass of it, comparing
|
|
123
|
+
* registered names, since with hot reloading the instance model might still extend a previous
|
|
124
|
+
* registration of the model.
|
|
125
|
+
*
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
export function isModelInstanceOfModelName(value: object, modelName: string): boolean {
|
|
129
|
+
const valueModelInfo = findModelInfoForPrototype(Object.getPrototypeOf(value))
|
|
130
|
+
return !!valueModelInfo && getModelSubclassDistance(valueModelInfo, modelName) >= 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether a value is a snapshot that names its model (`$modelType`).
|
|
135
|
+
*
|
|
136
|
+
* @internal
|
|
137
|
+
*/
|
|
138
|
+
export function hasSnapshotModelType(value: unknown): boolean {
|
|
139
|
+
return isObject(value) && typeof value[modelTypeKey] === "string"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Runs a function in the given model type matching pass, restoring the current one afterwards.
|
|
144
|
+
*
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
export function runInModelTypeMatchingPass<A, B, R>(
|
|
148
|
+
pass: ModelTypeMatchingPass,
|
|
149
|
+
fn: (a: A, b: B) => R,
|
|
150
|
+
a: A,
|
|
151
|
+
b: B
|
|
152
|
+
): R {
|
|
153
|
+
if (pass === "none" && currentPass === "none") {
|
|
154
|
+
return fn(a, b)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const prevPass = currentPass
|
|
158
|
+
const prevDistance = subclassDistance
|
|
159
|
+
const prevRejectedModelSnapshot = rejectedModelSnapshot
|
|
160
|
+
currentPass = pass
|
|
161
|
+
subclassDistance = 0
|
|
162
|
+
rejectedModelSnapshot = false
|
|
163
|
+
try {
|
|
164
|
+
return fn(a, b)
|
|
165
|
+
} finally {
|
|
166
|
+
currentPass = prevPass
|
|
167
|
+
subclassDistance = prevDistance
|
|
168
|
+
rejectedModelSnapshot = prevRejectedModelSnapshot
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Finds the union branch (candidate) that matches a value, preferring branches that match the
|
|
174
|
+
* exact models named by the snapshot (branches without models count as exact matches), then
|
|
175
|
+
* the branches whose models are the closest base models of the snapshot ones (the fewest
|
|
176
|
+
* subclass levels away in total), and the first one on ties.
|
|
177
|
+
*
|
|
178
|
+
* @internal
|
|
179
|
+
*/
|
|
180
|
+
export function findClosestModelTypeMatch<T, R>(
|
|
181
|
+
candidates: ReadonlyArray<T>,
|
|
182
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
183
|
+
value: unknown
|
|
184
|
+
): R | undefined {
|
|
185
|
+
switch (currentPass) {
|
|
186
|
+
case "exact":
|
|
187
|
+
// nested inside an exact pass
|
|
188
|
+
return findNestedMatch(findFirstMatch, candidates, match, value, 0)
|
|
189
|
+
case "closest":
|
|
190
|
+
// nested inside a closest pass
|
|
191
|
+
return findNestedMatch(findClosestMatch, candidates, match, value, 0)
|
|
192
|
+
default:
|
|
193
|
+
try {
|
|
194
|
+
currentPass = "exact"
|
|
195
|
+
const exactMatch = findFirstMatch(candidates, match, value)
|
|
196
|
+
if (exactMatch != null) {
|
|
197
|
+
return exactMatch
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
currentPass = "closest"
|
|
201
|
+
subclassDistance = 0
|
|
202
|
+
// no branch matched exactly, so none can be less than one subclass level away
|
|
203
|
+
return findClosestMatch(candidates, match, value, 1)
|
|
204
|
+
} finally {
|
|
205
|
+
currentPass = "none"
|
|
206
|
+
subclassDistance = 0
|
|
207
|
+
rejectedModelSnapshot = false
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function findNestedMatch<T, R>(
|
|
213
|
+
find: (
|
|
214
|
+
candidates: ReadonlyArray<T>,
|
|
215
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
216
|
+
value: unknown,
|
|
217
|
+
minPossibleDistance: number
|
|
218
|
+
) => R | undefined,
|
|
219
|
+
candidates: ReadonlyArray<T>,
|
|
220
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
221
|
+
value: unknown,
|
|
222
|
+
minPossibleDistance: number
|
|
223
|
+
): R | undefined {
|
|
224
|
+
const prevRejectedModelSnapshot = rejectedModelSnapshot
|
|
225
|
+
const result = find(candidates, match, value, minPossibleDistance)
|
|
226
|
+
if (result != null) {
|
|
227
|
+
// model snapshots rejected by other branches do not matter then
|
|
228
|
+
rejectedModelSnapshot = prevRejectedModelSnapshot
|
|
229
|
+
}
|
|
230
|
+
return result
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Finds the first candidate that matches a value.
|
|
235
|
+
*
|
|
236
|
+
* @internal
|
|
237
|
+
*/
|
|
238
|
+
export function findFirstMatch<T, R>(
|
|
239
|
+
candidates: ReadonlyArray<T>,
|
|
240
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
241
|
+
value: unknown
|
|
242
|
+
): R | undefined {
|
|
243
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
244
|
+
const result = match(candidates[i], value)
|
|
245
|
+
if (result != null) {
|
|
246
|
+
return result
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return undefined
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function findClosestMatch<T, R>(
|
|
253
|
+
candidates: ReadonlyArray<T>,
|
|
254
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
255
|
+
value: unknown,
|
|
256
|
+
minPossibleDistance: number
|
|
257
|
+
): R | undefined {
|
|
258
|
+
const prevDistance = subclassDistance
|
|
259
|
+
let best: R | undefined
|
|
260
|
+
let bestDistance = Number.POSITIVE_INFINITY
|
|
261
|
+
|
|
262
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
263
|
+
subclassDistance = 0
|
|
264
|
+
const result = match(candidates[i], value)
|
|
265
|
+
if (result != null && subclassDistance < bestDistance) {
|
|
266
|
+
best = result
|
|
267
|
+
bestDistance = subclassDistance
|
|
268
|
+
if (bestDistance <= minPossibleDistance) {
|
|
269
|
+
break
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
subclassDistance = prevDistance + (best != null ? bestDistance : 0)
|
|
275
|
+
return best
|
|
276
|
+
}
|
|
@@ -90,6 +90,11 @@ export function typesDataModelData<M = never, K = M>(
|
|
|
90
90
|
|
|
91
91
|
const resolvedDataTypeChecker = resolveTypeChecker(standardDataType)
|
|
92
92
|
|
|
93
|
+
const processorPlan = snapshotProcessorPlan(
|
|
94
|
+
() => [resolvedDataTypeChecker],
|
|
95
|
+
([processor]) => processor
|
|
96
|
+
)
|
|
97
|
+
|
|
93
98
|
const thisTc: TypeChecker = new TypeChecker(
|
|
94
99
|
TypeCheckerBaseType.Object,
|
|
95
100
|
|
|
@@ -104,15 +109,9 @@ export function typesDataModelData<M = never, K = M>(
|
|
|
104
109
|
return resolvedDataTypeChecker.snapshotType(value) ? thisTc : null
|
|
105
110
|
},
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
() => [resolvedDataTypeChecker],
|
|
109
|
-
([processor]) => processor
|
|
110
|
-
),
|
|
112
|
+
processorPlan,
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
() => [resolvedDataTypeChecker],
|
|
114
|
-
([processor]) => processor
|
|
115
|
-
)
|
|
114
|
+
processorPlan
|
|
116
115
|
)
|
|
117
116
|
|
|
118
117
|
return thisTc
|
|
@@ -2,14 +2,20 @@ import type { O } from "ts-toolbelt"
|
|
|
2
2
|
import type { AnyModel } from "../../model/BaseModel"
|
|
3
3
|
import { getModelMetadata } from "../../model/getModelMetadata"
|
|
4
4
|
import { modelTypeKey } from "../../model/metadata"
|
|
5
|
-
import { isModelClass } from "../../model/utils"
|
|
5
|
+
import { isModel, isModelClass } from "../../model/utils"
|
|
6
6
|
import type { ModelClass } from "../../modelShared/BaseModelShared"
|
|
7
7
|
import { modelInfoByClass } from "../../modelShared/modelInfo"
|
|
8
8
|
import { getInternalModelClassPropsInfo } from "../../modelShared/modelPropsInfo"
|
|
9
9
|
import { noDefaultValue } from "../../modelShared/prop"
|
|
10
10
|
import { isObject, lazy, setProtoProp } from "../../utils"
|
|
11
11
|
import { getTypeInfo } from "../getTypeInfo"
|
|
12
|
+
import {
|
|
13
|
+
isModelInstanceOfModelName,
|
|
14
|
+
isUnrelatedSnapshotModelType,
|
|
15
|
+
snapshotModelTypeMatches,
|
|
16
|
+
} from "../modelTypeMatching"
|
|
12
17
|
import { registerStandardTypeResolver, resolveTypeChecker } from "../resolveTypeChecker"
|
|
18
|
+
import { SnapshotTypeMismatchError } from "../SnapshotTypeMismatchError"
|
|
13
19
|
import type { AnyStandardType, ModelType } from "../schemas"
|
|
14
20
|
import { TypeCheckError } from "../TypeCheckError"
|
|
15
21
|
import {
|
|
@@ -73,7 +79,10 @@ export function typesModel<M = never, K = M>(modelClass: _ClassOrObject<M, K>):
|
|
|
73
79
|
TypeCheckerBaseType.Object,
|
|
74
80
|
|
|
75
81
|
(value, path, typeCheckedValue) => {
|
|
76
|
-
if (
|
|
82
|
+
if (
|
|
83
|
+
!(value instanceof modelClazz) &&
|
|
84
|
+
!(isModel(value) && isModelInstanceOfModelName(value, modelInfo.name))
|
|
85
|
+
) {
|
|
77
86
|
return new TypeCheckError({
|
|
78
87
|
path,
|
|
79
88
|
expectedTypeName: typeName,
|
|
@@ -96,9 +105,9 @@ export function typesModel<M = never, K = M>(modelClass: _ClassOrObject<M, K>):
|
|
|
96
105
|
return null
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
108
|
+
const snModelType = value[modelTypeKey]
|
|
109
|
+
if (snModelType !== undefined) {
|
|
110
|
+
return snapshotModelTypeMatches(snModelType, modelInfo.name) ? thisTc : null
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
if (resolvedDataTypeChecker) {
|
|
@@ -112,7 +121,15 @@ export function typesModel<M = never, K = M>(modelClass: _ClassOrObject<M, K>):
|
|
|
112
121
|
// No withErrorPathSegment wrapping needed here — model snapshot processors
|
|
113
122
|
// delegate to the data type checker, which handles path segments at the property level.
|
|
114
123
|
(sn) => {
|
|
115
|
-
|
|
124
|
+
const snModelType = sn[modelTypeKey]
|
|
125
|
+
if (typeof snModelType === "string") {
|
|
126
|
+
// the snapshot names its own model; make sure it is this model or a subclass of it
|
|
127
|
+
if (isUnrelatedSnapshotModelType(snModelType, modelInfo.name)) {
|
|
128
|
+
throw new SnapshotTypeMismatchError({
|
|
129
|
+
expectedTypeName: typeName,
|
|
130
|
+
actualValue: sn,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
116
133
|
return sn
|
|
117
134
|
} else {
|
|
118
135
|
return {
|
|
@@ -173,6 +173,14 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
173
173
|
}
|
|
174
174
|
)
|
|
175
175
|
|
|
176
|
+
const processorPlan = snapshotProcessorPlan(
|
|
177
|
+
() => schemaEntries.map((entry) => entry.getResolvedChecker()),
|
|
178
|
+
(processors) =>
|
|
179
|
+
processors.some(Boolean)
|
|
180
|
+
? (obj: Record<string, unknown>) => applySnapshotProcessor(obj, processors)
|
|
181
|
+
: undefined
|
|
182
|
+
)
|
|
183
|
+
|
|
176
184
|
const thisTc: TypeChecker = new TypeChecker(
|
|
177
185
|
TypeCheckerBaseType.Object,
|
|
178
186
|
|
|
@@ -208,21 +216,9 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
208
216
|
return thisTc
|
|
209
217
|
},
|
|
210
218
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
processors.some(Boolean)
|
|
215
|
-
? (obj: Record<string, unknown>) => applySnapshotProcessor(obj, processors)
|
|
216
|
-
: undefined
|
|
217
|
-
),
|
|
218
|
-
|
|
219
|
-
snapshotProcessorPlan(
|
|
220
|
-
() => schemaEntries.map((entry) => entry.getResolvedChecker()),
|
|
221
|
-
(processors) =>
|
|
222
|
-
processors.some(Boolean)
|
|
223
|
-
? (obj: Record<string, unknown>) => applySnapshotProcessor(obj, processors)
|
|
224
|
-
: undefined
|
|
225
|
-
)
|
|
219
|
+
processorPlan,
|
|
220
|
+
|
|
221
|
+
processorPlan
|
|
226
222
|
)
|
|
227
223
|
|
|
228
224
|
return thisTc
|
|
@@ -83,6 +83,14 @@ export function typesRecord<T extends AnyType>(valueType: T): RecordType<T> {
|
|
|
83
83
|
|
|
84
84
|
const checkRecordValues = createChunkedRecordCachedCheck(iterateRecordEntries, checkRecordEntry)
|
|
85
85
|
|
|
86
|
+
const processorPlan = snapshotProcessorPlan(
|
|
87
|
+
() => [valueChecker],
|
|
88
|
+
([processor]) =>
|
|
89
|
+
processor
|
|
90
|
+
? (obj: Record<string, unknown>) => applySnapshotProcessor(obj, processor)
|
|
91
|
+
: undefined
|
|
92
|
+
)
|
|
93
|
+
|
|
86
94
|
const thisTc: TypeChecker = new TypeChecker(
|
|
87
95
|
TypeCheckerBaseType.Object,
|
|
88
96
|
|
|
@@ -126,21 +134,9 @@ export function typesRecord<T extends AnyType>(valueType: T): RecordType<T> {
|
|
|
126
134
|
return thisTc
|
|
127
135
|
},
|
|
128
136
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
processor
|
|
133
|
-
? (obj: Record<string, unknown>) => applySnapshotProcessor(obj, processor)
|
|
134
|
-
: undefined
|
|
135
|
-
),
|
|
136
|
-
|
|
137
|
-
snapshotProcessorPlan(
|
|
138
|
-
() => [valueChecker],
|
|
139
|
-
([processor]) =>
|
|
140
|
-
processor
|
|
141
|
-
? (obj: Record<string, unknown>) => applySnapshotProcessor(obj, processor)
|
|
142
|
-
: undefined
|
|
143
|
-
)
|
|
137
|
+
processorPlan,
|
|
138
|
+
|
|
139
|
+
processorPlan
|
|
144
140
|
)
|
|
145
141
|
|
|
146
142
|
return thisTc
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { modelTypeKey } from "../../model/metadata"
|
|
2
|
+
import { isModel } from "../../model/utils"
|
|
2
3
|
import { modelInfoByClass } from "../../modelShared/modelInfo"
|
|
3
4
|
import type { Ref, RefConstructor } from "../../ref/Ref"
|
|
4
5
|
import { isObject } from "../../utils"
|
|
6
|
+
import {
|
|
7
|
+
isModelInstanceOfModelName,
|
|
8
|
+
isUnrelatedSnapshotModelType,
|
|
9
|
+
snapshotModelTypeMatches,
|
|
10
|
+
} from "../modelTypeMatching"
|
|
5
11
|
import { typesString } from "../primitiveBased/typesPrimitive"
|
|
6
12
|
import { resolveTypeChecker } from "../resolveTypeChecker"
|
|
13
|
+
import { SnapshotTypeMismatchError } from "../SnapshotTypeMismatchError"
|
|
7
14
|
import type { ModelType } from "../schemas"
|
|
8
15
|
import { TypeCheckError } from "../TypeCheckError"
|
|
9
16
|
import { TypeChecker, TypeCheckerBaseType, TypeInfo } from "../TypeChecker"
|
|
@@ -36,7 +43,10 @@ export function typesRef<O extends object>(refConstructor: RefConstructor<O>): M
|
|
|
36
43
|
TypeCheckerBaseType.Object,
|
|
37
44
|
|
|
38
45
|
(value, path, typeCheckedValue) => {
|
|
39
|
-
if (
|
|
46
|
+
if (
|
|
47
|
+
!(value instanceof refConstructor.refClass) &&
|
|
48
|
+
!(isModel(value) && isModelInstanceOfModelName(value, modelInfo.name))
|
|
49
|
+
) {
|
|
40
50
|
return new TypeCheckError({
|
|
41
51
|
path,
|
|
42
52
|
expectedTypeName: typeName,
|
|
@@ -56,16 +66,25 @@ export function typesRef<O extends object>(refConstructor: RefConstructor<O>): M
|
|
|
56
66
|
return null
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
const snModelType = obj[modelTypeKey]
|
|
70
|
+
if (snModelType !== undefined) {
|
|
60
71
|
// fast check
|
|
61
|
-
return
|
|
72
|
+
return snapshotModelTypeMatches(snModelType, modelInfo.name) ? thisTc : null
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
return refDataTypeChecker.snapshotType(obj) ? thisTc : null
|
|
65
76
|
},
|
|
66
77
|
|
|
67
78
|
(sn: Record<string, unknown>) => {
|
|
68
|
-
|
|
79
|
+
const snModelType = sn[modelTypeKey]
|
|
80
|
+
if (typeof snModelType === "string") {
|
|
81
|
+
// the snapshot names its own model; make sure it is this ref or a subclass of it
|
|
82
|
+
if (isUnrelatedSnapshotModelType(snModelType, modelInfo.name)) {
|
|
83
|
+
throw new SnapshotTypeMismatchError({
|
|
84
|
+
expectedTypeName: typeName,
|
|
85
|
+
actualValue: sn,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
69
88
|
return sn
|
|
70
89
|
} else {
|
|
71
90
|
return {
|