mobx-keystone 1.22.0 → 1.23.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 +7 -0
- package/dist/mobx-keystone.esm.js +6702 -5797
- package/dist/mobx-keystone.esm.mjs +6702 -5797
- package/dist/mobx-keystone.umd.js +6718 -5798
- package/dist/types/action/runUnprotected.d.ts +8 -4
- package/dist/types/model/Model.d.ts +5 -0
- package/dist/types/modelShared/prop.d.ts +8 -4
- package/dist/types/snapshot/modelInitialData.d.ts +1 -0
- package/dist/types/tweaker/treeNodeMetadata.d.ts +1 -0
- package/dist/types/types/createCachedTypeCheck.d.ts +1 -0
- package/package.json +11 -10
- package/src/action/middleware.ts +5 -18
- package/src/action/mutationBatch.ts +72 -0
- package/src/action/runUnprotected.ts +21 -8
- package/src/action/wrapInAction.ts +45 -15
- package/src/dataModel/BaseDataModel.ts +4 -8
- package/src/deepChange/onDeepChange.ts +246 -16
- package/src/model/BaseModel.ts +4 -12
- package/src/model/Model.ts +5 -0
- package/src/model/newModel.ts +68 -36
- package/src/modelShared/prop.ts +105 -63
- package/src/modelShared/sharedInternalModel.ts +91 -60
- package/src/parent/core.ts +16 -10
- package/src/parent/coreObjectChildren.ts +219 -21
- package/src/parent/path.ts +6 -5
- package/src/parent/setParent.ts +5 -5
- package/src/patch/emitPatch.ts +102 -20
- package/src/snapshot/fromArraySnapshot.ts +11 -2
- package/src/snapshot/fromModelSnapshot.ts +10 -3
- package/src/snapshot/fromPlainObjectSnapshot.ts +18 -7
- package/src/snapshot/fromSnapshot.ts +31 -9
- package/src/snapshot/getSnapshot.ts +8 -3
- package/src/snapshot/internal.ts +243 -82
- package/src/snapshot/modelInitialData.ts +41 -0
- package/src/snapshot/reconcileArraySnapshot.ts +9 -3
- package/src/snapshot/reconcileModelSnapshot.ts +97 -17
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +9 -3
- package/src/tweaker/core.ts +30 -5
- package/src/tweaker/treeNodeMetadata.ts +38 -0
- package/src/tweaker/tweak.ts +15 -4
- package/src/tweaker/tweakArray.ts +159 -121
- package/src/tweaker/tweakFrozen.ts +2 -2
- package/src/tweaker/tweakModel.ts +2 -2
- package/src/tweaker/tweakPlainObject.ts +114 -122
- package/src/tweaker/typeChecking.ts +10 -1
- package/src/types/TypeChecker.ts +261 -15
- package/src/types/arrayBased/typesArray.ts +38 -40
- package/src/types/arrayBased/typesTuple.ts +51 -34
- package/src/types/createCachedTypeCheck.ts +181 -0
- package/src/types/objectBased/typesArraySet.ts +42 -32
- package/src/types/objectBased/typesDataModelData.ts +18 -11
- package/src/types/objectBased/typesModel.ts +1 -1
- package/src/types/objectBased/typesObject.ts +57 -28
- package/src/types/objectBased/typesObjectMap.ts +51 -40
- package/src/types/objectBased/typesRecord.ts +59 -39
- package/src/types/objectBased/typesRef.ts +1 -1
- package/src/types/primitiveBased/typesPrimitive.ts +9 -9
- package/src/types/tProp.ts +80 -44
- package/src/types/utility/typesOr.ts +35 -24
- package/src/types/utility/typesRefinement.ts +21 -8
- package/src/types/utility/typesSkipCheck.ts +21 -8
- package/src/types/utility/typesTag.ts +21 -9
- package/src/types/utility/typesUnchecked.ts +2 -3
- package/src/utils/ModelPool.ts +34 -19
- package/src/utils/errorDiagnostics.ts +60 -18
- package/src/utils/index.ts +9 -7
- package/src/types/createPerEntryCachedCheck.ts +0 -166
- /package/dist/types/{types/createPerEntryCachedCheck.d.ts → action/mutationBatch.d.ts} +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { computed, type IComputedValue, type IObjectDidChange, observe } from "mobx"
|
|
2
|
+
import type { Path } from "../parent/pathTypes"
|
|
3
|
+
import { isTweakedObject } from "../tweaker/core"
|
|
4
|
+
import { TypeCheckError } from "./TypeCheckError"
|
|
5
|
+
|
|
6
|
+
const emptyPath: Path = []
|
|
7
|
+
|
|
8
|
+
function contextualizeCachedError(
|
|
9
|
+
error: TypeCheckError | null,
|
|
10
|
+
path: Path,
|
|
11
|
+
typeCheckedValue: any
|
|
12
|
+
): TypeCheckError | null {
|
|
13
|
+
if (!error) {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return new TypeCheckError({
|
|
18
|
+
path: [...path, ...error.path],
|
|
19
|
+
expectedTypeName: error.expectedTypeName,
|
|
20
|
+
actualValue: error.actualValue,
|
|
21
|
+
typeCheckedValue,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type CachedEntry = IComputedValue<TypeCheckError | null>
|
|
26
|
+
|
|
27
|
+
interface EntryCache<K> {
|
|
28
|
+
get(key: K): CachedEntry | undefined
|
|
29
|
+
set(key: K, entry: CachedEntry): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type IterateEntries<K> = (
|
|
33
|
+
value: any,
|
|
34
|
+
checkEntry: (entryKey: K) => TypeCheckError | null
|
|
35
|
+
) => TypeCheckError | null
|
|
36
|
+
|
|
37
|
+
type CheckEntry<K> = (
|
|
38
|
+
value: any,
|
|
39
|
+
entryKey: K,
|
|
40
|
+
path: Path,
|
|
41
|
+
typeCheckedValue: any
|
|
42
|
+
) => TypeCheckError | null
|
|
43
|
+
|
|
44
|
+
function createAdaptivePerEntryCachedCheck<K>(
|
|
45
|
+
iterateEntries: IterateEntries<K>,
|
|
46
|
+
checkEntry: CheckEntry<K>,
|
|
47
|
+
createEntryCache: (value: object) => EntryCache<K>
|
|
48
|
+
): (value: any, path: Path, typeCheckedValue: any) => TypeCheckError | null {
|
|
49
|
+
const aggregationCache = new WeakMap<object, IComputedValue<TypeCheckError | null>>()
|
|
50
|
+
|
|
51
|
+
const checkAllEntries = (value: any, path: Path, typeCheckedValue: any) =>
|
|
52
|
+
iterateEntries(value, (key) => checkEntry(value, key, path, typeCheckedValue))
|
|
53
|
+
|
|
54
|
+
return (value, path, typeCheckedValue) => {
|
|
55
|
+
if (!isTweakedObject(value, true)) {
|
|
56
|
+
return checkAllEntries(value, path, typeCheckedValue)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let aggregation = aggregationCache.get(value)
|
|
60
|
+
if (!aggregation) {
|
|
61
|
+
let entries: EntryCache<K> | undefined
|
|
62
|
+
let isFirstEvaluation = true
|
|
63
|
+
const cachedCheckEntry = (key: K): TypeCheckError | null => {
|
|
64
|
+
if (!entries) {
|
|
65
|
+
entries = createEntryCache(value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let entry = entries.get(key)
|
|
69
|
+
if (!entry) {
|
|
70
|
+
entry = computed(() => checkEntry(value, key, emptyPath, undefined))
|
|
71
|
+
entries.set(key, entry)
|
|
72
|
+
}
|
|
73
|
+
return entry.get()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
aggregation = computed(
|
|
77
|
+
() => {
|
|
78
|
+
if (isFirstEvaluation) {
|
|
79
|
+
isFirstEvaluation = false
|
|
80
|
+
return checkAllEntries(value, emptyPath, undefined)
|
|
81
|
+
}
|
|
82
|
+
return iterateEntries(value, cachedCheckEntry)
|
|
83
|
+
},
|
|
84
|
+
{ keepAlive: true }
|
|
85
|
+
)
|
|
86
|
+
aggregationCache.set(value, aggregation)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return contextualizeCachedError(aggregation.get(), path, typeCheckedValue)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @internal
|
|
95
|
+
*
|
|
96
|
+
* Creates an adaptive check for dynamic records.
|
|
97
|
+
*
|
|
98
|
+
* For tweaked (observable) values:
|
|
99
|
+
* 1. The first complete check is cached without allocating per-entry state.
|
|
100
|
+
* 2. After the first invalidation, per-entry computeds each read one property, so
|
|
101
|
+
* changing property A only invalidates property A's computed.
|
|
102
|
+
* 3. The aggregation computed iterates per-entry computeds. Thanks to MobX's propagation,
|
|
103
|
+
* if a per-entry computed re-evaluates but returns the same result (e.g. null → null),
|
|
104
|
+
* the aggregation body is skipped entirely.
|
|
105
|
+
* 4. A pruning observer is installed when the promoted entry cache is first used
|
|
106
|
+
* and removes stale entries immediately when record keys are deleted.
|
|
107
|
+
*
|
|
108
|
+
* Cached checks produce context-free errors. A fresh error is contextualized
|
|
109
|
+
* with the current path and checked root without executing user checkers again.
|
|
110
|
+
*
|
|
111
|
+
* For non-tweaked values (plain snapshots), the entry iterator and checker run
|
|
112
|
+
* directly with the caller's context.
|
|
113
|
+
*
|
|
114
|
+
* @param iterateEntries Iterates the promoted per-entry computeds and returns
|
|
115
|
+
* the first error, or null.
|
|
116
|
+
* @param checkEntry Checks a single entry. The utility wraps it in a
|
|
117
|
+
* non-keepAlive computed automatically after promotion.
|
|
118
|
+
*/
|
|
119
|
+
export function createAdaptiveRecordCachedCheck(
|
|
120
|
+
iterateEntries: IterateEntries<PropertyKey>,
|
|
121
|
+
checkEntry: CheckEntry<PropertyKey>
|
|
122
|
+
): (value: any, path: Path, typeCheckedValue: any) => TypeCheckError | null {
|
|
123
|
+
return createAdaptivePerEntryCachedCheck(iterateEntries, checkEntry, (value) => {
|
|
124
|
+
const entries = new Map<PropertyKey, CachedEntry>()
|
|
125
|
+
observe(value, (change: IObjectDidChange) => {
|
|
126
|
+
if (change.type === "remove") {
|
|
127
|
+
entries.delete(change.name)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
return entries
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates one reactive computed for a complete container check.
|
|
136
|
+
*
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
139
|
+
export function createWholeContainerCachedCheck(
|
|
140
|
+
check: (value: any, path: Path, typeCheckedValue: any) => TypeCheckError | null
|
|
141
|
+
): (value: any, path: Path, typeCheckedValue: any) => TypeCheckError | null {
|
|
142
|
+
const cache = new WeakMap<object, IComputedValue<TypeCheckError | null>>()
|
|
143
|
+
|
|
144
|
+
return (value, path, typeCheckedValue) => {
|
|
145
|
+
if (!isTweakedObject(value, true)) {
|
|
146
|
+
return check(value, path, typeCheckedValue)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let cached = cache.get(value)
|
|
150
|
+
if (!cached) {
|
|
151
|
+
cached = computed(() => check(value, emptyPath, undefined), { keepAlive: true })
|
|
152
|
+
cache.set(value, cached)
|
|
153
|
+
}
|
|
154
|
+
const error = cached.get()
|
|
155
|
+
return contextualizeCachedError(error, path, typeCheckedValue)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Fixed-schema per-entry cache. Numeric entry keys use
|
|
161
|
+
* array slots, and the aggregation closure owns that array, so only one
|
|
162
|
+
* WeakMap is needed for each checked object. The aggregation caches one
|
|
163
|
+
* complete check first and promotes itself to per-entry computeds after its
|
|
164
|
+
* first dependency invalidation.
|
|
165
|
+
*
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
export function createIndexedPerEntryCachedCheck(
|
|
169
|
+
iterateEntries: IterateEntries<number>,
|
|
170
|
+
checkEntry: CheckEntry<number>
|
|
171
|
+
): (value: any, path: Path, typeCheckedValue: any) => TypeCheckError | null {
|
|
172
|
+
return createAdaptivePerEntryCachedCheck(iterateEntries, checkEntry, () => {
|
|
173
|
+
const entries: Array<CachedEntry | undefined> = []
|
|
174
|
+
return {
|
|
175
|
+
get: (index) => entries[index],
|
|
176
|
+
set: (index, entry) => {
|
|
177
|
+
entries[index] = entry
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
}
|
|
@@ -10,6 +10,7 @@ import type { AnyStandardType, AnyType, ModelType, TypeToData } from "../schemas
|
|
|
10
10
|
import { TypeCheckError } from "../TypeCheckError"
|
|
11
11
|
import {
|
|
12
12
|
lateTypeChecker,
|
|
13
|
+
snapshotProcessorPlan,
|
|
13
14
|
TypeChecker,
|
|
14
15
|
TypeCheckerBaseType,
|
|
15
16
|
TypeInfo,
|
|
@@ -31,13 +32,13 @@ import { typesObject } from "./typesObject"
|
|
|
31
32
|
* @returns
|
|
32
33
|
*/
|
|
33
34
|
export function typesArraySet<T extends AnyType>(valueType: T): ModelType<ArraySet<TypeToData<T>>> {
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const resolvedValueType = resolveStandardType(valueType)
|
|
36
|
+
const typeInfoGen: TypeInfoGen = (t) => new ArraySetTypeInfo(t, resolvedValueType)
|
|
36
37
|
return lateTypeChecker(() => {
|
|
37
38
|
const modelInfo = modelInfoByClass.get(ArraySet)!
|
|
38
39
|
|
|
39
|
-
const valueChecker = resolveTypeChecker(
|
|
40
|
-
const valueSupport = resolveCodecSupport(
|
|
40
|
+
const valueChecker = resolveTypeChecker(resolvedValueType)
|
|
41
|
+
const valueSupport = resolveCodecSupport(resolvedValueType)
|
|
41
42
|
const storedValueChecker = resolveTypeChecker(valueSupport.storedType)
|
|
42
43
|
|
|
43
44
|
const getTypeName = (...recursiveTypeCheckers: TypeChecker[]) =>
|
|
@@ -81,34 +82,43 @@ export function typesArraySet<T extends AnyType>(valueType: T): ModelType<ArrayS
|
|
|
81
82
|
return resolvedTc.snapshotType(obj) ? thisTc : null
|
|
82
83
|
},
|
|
83
84
|
|
|
84
|
-
(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
85
|
+
snapshotProcessorPlan(
|
|
86
|
+
() => [storedValueChecker],
|
|
87
|
+
([itemProcessor]) =>
|
|
88
|
+
(sn: { items: unknown[] }) => {
|
|
89
|
+
const items = itemProcessor
|
|
90
|
+
? withErrorPathSegment("items", () =>
|
|
91
|
+
sn.items.map((v, i) => withErrorPathSegment(i, () => itemProcessor(v)))
|
|
92
|
+
)
|
|
93
|
+
: sn.items
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
...sn,
|
|
97
|
+
[modelTypeKey]: modelInfo.name,
|
|
98
|
+
items,
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
true
|
|
102
|
+
),
|
|
103
|
+
|
|
104
|
+
snapshotProcessorPlan(
|
|
105
|
+
() => [storedValueChecker],
|
|
106
|
+
([itemProcessor]) =>
|
|
107
|
+
itemProcessor
|
|
108
|
+
? (sn: { items: unknown[]; [modelTypeKey]?: string }) => {
|
|
109
|
+
const items = withErrorPathSegment("items", () =>
|
|
110
|
+
sn.items.map((v, i) => withErrorPathSegment(i, () => itemProcessor(v)))
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
const snCopy = {
|
|
114
|
+
...sn,
|
|
115
|
+
items,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return snCopy
|
|
119
|
+
}
|
|
120
|
+
: undefined
|
|
121
|
+
)
|
|
112
122
|
)
|
|
113
123
|
|
|
114
124
|
return thisTc
|
|
@@ -12,10 +12,15 @@ import { getInternalModelClassPropsInfo } from "../../modelShared/modelPropsInfo
|
|
|
12
12
|
import { noDefaultValue } from "../../modelShared/prop"
|
|
13
13
|
import { failure, lazy } from "../../utils"
|
|
14
14
|
import { getTypeInfo } from "../getTypeInfo"
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
registerStandardTypeResolver,
|
|
17
|
+
resolveStandardType,
|
|
18
|
+
resolveTypeChecker,
|
|
19
|
+
} from "../resolveTypeChecker"
|
|
16
20
|
import type { AnyStandardType, IdentityType } from "../schemas"
|
|
17
21
|
import {
|
|
18
22
|
lateTypeChecker,
|
|
23
|
+
snapshotProcessorPlan,
|
|
19
24
|
TypeChecker,
|
|
20
25
|
TypeCheckerBaseType,
|
|
21
26
|
TypeInfo,
|
|
@@ -71,19 +76,19 @@ export function typesDataModelData<M = never, K = M>(
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
const typeInfoGen: TypeInfoGen = (t) => new DataModelDataTypeInfo(t, modelClazz)
|
|
74
|
-
|
|
79
|
+
const dataType = getDataModelMetadata(modelClazz).dataType
|
|
80
|
+
const standardDataType = dataType ? resolveStandardType(dataType) : undefined
|
|
75
81
|
const tc = lateTypeChecker(() => {
|
|
76
82
|
const modelInfo = modelInfoByClass.get(modelClazz)!
|
|
77
83
|
const typeName = `DataModelData(${modelInfo.name})`
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
if (!dataTypeChecker) {
|
|
85
|
+
if (!standardDataType) {
|
|
81
86
|
throw failure(
|
|
82
87
|
`type checking cannot be performed over data model data of type '${modelInfo.name}' since that model type has no data type declared, consider adding a data type or using types.unchecked() instead`
|
|
83
88
|
)
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
const resolvedDataTypeChecker = resolveTypeChecker(
|
|
91
|
+
const resolvedDataTypeChecker = resolveTypeChecker(standardDataType)
|
|
87
92
|
|
|
88
93
|
const thisTc: TypeChecker = new TypeChecker(
|
|
89
94
|
TypeCheckerBaseType.Object,
|
|
@@ -99,13 +104,15 @@ export function typesDataModelData<M = never, K = M>(
|
|
|
99
104
|
return resolvedDataTypeChecker.snapshotType(value) ? thisTc : null
|
|
100
105
|
},
|
|
101
106
|
|
|
102
|
-
(
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
snapshotProcessorPlan(
|
|
108
|
+
() => [resolvedDataTypeChecker],
|
|
109
|
+
([processor]) => processor
|
|
110
|
+
),
|
|
105
111
|
|
|
106
|
-
(
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
snapshotProcessorPlan(
|
|
113
|
+
() => [resolvedDataTypeChecker],
|
|
114
|
+
([processor]) => processor
|
|
115
|
+
)
|
|
109
116
|
)
|
|
110
117
|
|
|
111
118
|
return thisTc
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isObservableObject, keys } from "mobx"
|
|
1
2
|
import type { O } from "ts-toolbelt"
|
|
2
3
|
import { Frozen } from "../../frozen/Frozen"
|
|
3
4
|
import type { Path } from "../../parent/pathTypes"
|
|
4
|
-
import { assertIsFunction, assertIsObject, isObject, lazy } from "../../utils"
|
|
5
|
+
import { assertIsFunction, assertIsObject, getMobxVersion, isObject, lazy } from "../../utils"
|
|
5
6
|
import { withErrorPathSegment } from "../../utils/errorDiagnostics"
|
|
6
|
-
import {
|
|
7
|
+
import { createIndexedPerEntryCachedCheck } from "../createCachedTypeCheck"
|
|
7
8
|
import { getTypeInfo } from "../getTypeInfo"
|
|
8
9
|
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
9
10
|
import type {
|
|
@@ -17,6 +18,8 @@ import { TypeCheckError } from "../TypeCheckError"
|
|
|
17
18
|
import {
|
|
18
19
|
type LateTypeChecker,
|
|
19
20
|
lateTypeChecker,
|
|
21
|
+
type SnapshotProcessor,
|
|
22
|
+
snapshotProcessorPlan,
|
|
20
23
|
TypeChecker,
|
|
21
24
|
TypeCheckerBaseType,
|
|
22
25
|
TypeInfo,
|
|
@@ -24,6 +27,8 @@ import {
|
|
|
24
27
|
} from "../TypeChecker"
|
|
25
28
|
import { prependPathElementToTypeCheckError } from "../typeCheckErrorUtils"
|
|
26
29
|
|
|
30
|
+
const needsObservableObjectKeyTracking = getMobxVersion() < 5
|
|
31
|
+
|
|
27
32
|
function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGen): S {
|
|
28
33
|
assertIsFunction(objFn, "objFn")
|
|
29
34
|
|
|
@@ -33,6 +38,7 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
33
38
|
|
|
34
39
|
type SchemaEntry = Readonly<{
|
|
35
40
|
propName: string
|
|
41
|
+
processorIndex: number
|
|
36
42
|
getResolvedChecker: () => TypeChecker
|
|
37
43
|
}>
|
|
38
44
|
type CheckedSchemaEntry = Readonly<{
|
|
@@ -41,10 +47,11 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
41
47
|
}>
|
|
42
48
|
|
|
43
49
|
const schemaEntries: ReadonlyArray<SchemaEntry> = Object.entries(objectSchema).map(
|
|
44
|
-
([propName, unresolvedChecker]) => {
|
|
50
|
+
([propName, unresolvedChecker], processorIndex) => {
|
|
45
51
|
let resolvedChecker: TypeChecker | undefined
|
|
46
52
|
return {
|
|
47
53
|
propName,
|
|
54
|
+
processorIndex,
|
|
48
55
|
getResolvedChecker: () => {
|
|
49
56
|
if (!resolvedChecker) {
|
|
50
57
|
resolvedChecker = resolveTypeChecker(unresolvedChecker)
|
|
@@ -95,7 +102,10 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
95
102
|
return `{ ${propsMsg.join(" ")} }`
|
|
96
103
|
}
|
|
97
104
|
|
|
98
|
-
const applySnapshotProcessor = (
|
|
105
|
+
const applySnapshotProcessor = (
|
|
106
|
+
obj: Record<string, unknown>,
|
|
107
|
+
processors: ReadonlyArray<SnapshotProcessor | undefined>
|
|
108
|
+
) => {
|
|
99
109
|
const newObj: typeof obj = {}
|
|
100
110
|
|
|
101
111
|
// note: we allow excess properties when checking objects
|
|
@@ -104,10 +114,8 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
104
114
|
const k = keys[i]
|
|
105
115
|
const schemaEntry = schemaEntryByPropName[k]
|
|
106
116
|
if (schemaEntry) {
|
|
107
|
-
const
|
|
108
|
-
newObj[k] = withErrorPathSegment(k, () =>
|
|
109
|
-
mode === "from" ? tc.fromSnapshotProcessor(obj[k]) : tc.toSnapshotProcessor(obj[k])
|
|
110
|
-
)
|
|
117
|
+
const processor = processors[schemaEntry.processorIndex]
|
|
118
|
+
newObj[k] = processor ? withErrorPathSegment(k, () => processor(obj[k])) : obj[k]
|
|
111
119
|
} else {
|
|
112
120
|
// unknown prop, copy as is
|
|
113
121
|
newObj[k] = obj[k]
|
|
@@ -130,17 +138,26 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
130
138
|
return null
|
|
131
139
|
}
|
|
132
140
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
const iterateObjectProps = (
|
|
142
|
+
obj: any,
|
|
143
|
+
checkEntry: (entryIndex: number) => TypeCheckError | null
|
|
144
|
+
): TypeCheckError | null => {
|
|
145
|
+
const entries = checkedSchemaEntries()
|
|
146
|
+
// MobX 4 does not track reads of absent properties. Newer versions can
|
|
147
|
+
// avoid the extra key-set read.
|
|
148
|
+
if (needsObservableObjectKeyTracking && isObservableObject(obj)) {
|
|
149
|
+
void keys(obj)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < entries.length; i++) {
|
|
153
|
+
const error = checkEntry(i)
|
|
154
|
+
if (error) return error
|
|
155
|
+
}
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const checkObjectProps = createIndexedPerEntryCachedCheck(
|
|
160
|
+
iterateObjectProps,
|
|
144
161
|
(obj, entryIndex, path, typeCheckedValue) => {
|
|
145
162
|
const entry = checkedSchemaEntries()[entryIndex]
|
|
146
163
|
if (!entry) {
|
|
@@ -189,13 +206,21 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
189
206
|
return thisTc
|
|
190
207
|
},
|
|
191
208
|
|
|
192
|
-
(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
209
|
+
snapshotProcessorPlan(
|
|
210
|
+
() => schemaEntries.map((entry) => entry.getResolvedChecker()),
|
|
211
|
+
(processors) =>
|
|
212
|
+
processors.some(Boolean)
|
|
213
|
+
? (obj: Record<string, unknown>) => applySnapshotProcessor(obj, processors)
|
|
214
|
+
: undefined
|
|
215
|
+
),
|
|
216
|
+
|
|
217
|
+
snapshotProcessorPlan(
|
|
218
|
+
() => schemaEntries.map((entry) => entry.getResolvedChecker()),
|
|
219
|
+
(processors) =>
|
|
220
|
+
processors.some(Boolean)
|
|
221
|
+
? (obj: Record<string, unknown>) => applySnapshotProcessor(obj, processors)
|
|
222
|
+
: undefined
|
|
223
|
+
)
|
|
199
224
|
)
|
|
200
225
|
|
|
201
226
|
return thisTc
|
|
@@ -223,6 +248,9 @@ export function typesObject<T>(objectFunction: T): T {
|
|
|
223
248
|
// we can't type this function or else we won't be able to make it work recursively
|
|
224
249
|
const typeInfoGen: TypeInfoGen = (t) => new ObjectTypeInfo(t, objectFunction as any)
|
|
225
250
|
|
|
251
|
+
// The schema callback cannot be evaluated here because it may reference the
|
|
252
|
+
// object type recursively. Keep the unresolved capability conservative; the
|
|
253
|
+
// concrete checker still omits processors when every schema entry is identity-only.
|
|
226
254
|
return typesObjectHelper(objectFunction, false, typeInfoGen) as any
|
|
227
255
|
}
|
|
228
256
|
|
|
@@ -281,12 +309,13 @@ export class ObjectTypeInfo extends TypeInfo {
|
|
|
281
309
|
* @returns
|
|
282
310
|
*/
|
|
283
311
|
export function typesFrozen<T extends AnyType>(dataType: T): ModelType<Frozen<TypeToData<T>>> {
|
|
312
|
+
const resolvedDataType = resolveStandardType(dataType)
|
|
284
313
|
return typesObjectHelper(
|
|
285
314
|
() => ({
|
|
286
|
-
data:
|
|
315
|
+
data: resolvedDataType,
|
|
287
316
|
}),
|
|
288
317
|
true,
|
|
289
|
-
(t) => new FrozenTypeInfo(t,
|
|
318
|
+
(t) => new FrozenTypeInfo(t, resolvedDataType)
|
|
290
319
|
) as any
|
|
291
320
|
}
|
|
292
321
|
|
|
@@ -9,6 +9,7 @@ import type { AnyStandardType, AnyType, ModelType, TypeToData } from "../schemas
|
|
|
9
9
|
import { TypeCheckError } from "../TypeCheckError"
|
|
10
10
|
import {
|
|
11
11
|
lateTypeChecker,
|
|
12
|
+
snapshotProcessorPlan,
|
|
12
13
|
TypeChecker,
|
|
13
14
|
TypeCheckerBaseType,
|
|
14
15
|
TypeInfo,
|
|
@@ -33,13 +34,13 @@ import { typesRecord } from "./typesRecord"
|
|
|
33
34
|
export function typesObjectMap<T extends AnyType>(
|
|
34
35
|
valueType: T
|
|
35
36
|
): ModelType<ObjectMap<TypeToData<T>>> {
|
|
36
|
-
const
|
|
37
|
-
|
|
37
|
+
const resolvedValueType = resolveStandardType(valueType)
|
|
38
|
+
const typeInfoGen: TypeInfoGen = (t) => new ObjectMapTypeInfo(t, resolvedValueType)
|
|
38
39
|
return lateTypeChecker(() => {
|
|
39
40
|
const modelInfo = modelInfoByClass.get(ObjectMap)!
|
|
40
41
|
|
|
41
|
-
const valueChecker = resolveTypeChecker(
|
|
42
|
-
const valueSupport = resolveCodecSupport(
|
|
42
|
+
const valueChecker = resolveTypeChecker(resolvedValueType)
|
|
43
|
+
const valueSupport = resolveCodecSupport(resolvedValueType)
|
|
43
44
|
const storedValueChecker = resolveTypeChecker(valueSupport.storedType)
|
|
44
45
|
|
|
45
46
|
const getTypeName = (...recursiveTypeCheckers: TypeChecker[]) =>
|
|
@@ -82,42 +83,52 @@ export function typesObjectMap<T extends AnyType>(
|
|
|
82
83
|
return resolvedDataTypeChecker.snapshotType(obj) ? thisTc : null
|
|
83
84
|
},
|
|
84
85
|
|
|
85
|
-
(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
86
|
+
snapshotProcessorPlan(
|
|
87
|
+
() => [storedValueChecker],
|
|
88
|
+
([itemProcessor]) =>
|
|
89
|
+
(sn: { items: Record<string, unknown> }) => {
|
|
90
|
+
let items = sn.items
|
|
91
|
+
|
|
92
|
+
if (itemProcessor) {
|
|
93
|
+
items = {}
|
|
94
|
+
withErrorPathSegment("items", () => {
|
|
95
|
+
for (const k of Object.keys(sn.items)) {
|
|
96
|
+
items[k] = withErrorPathSegment(k, () => itemProcessor(sn.items[k]))
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
...sn,
|
|
103
|
+
[modelTypeKey]: modelInfo.name,
|
|
104
|
+
items,
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
true
|
|
108
|
+
),
|
|
109
|
+
|
|
110
|
+
snapshotProcessorPlan(
|
|
111
|
+
() => [storedValueChecker],
|
|
112
|
+
([itemProcessor]) =>
|
|
113
|
+
itemProcessor
|
|
114
|
+
? (sn: { items: Record<string, unknown>; [modelTypeKey]?: string }) => {
|
|
115
|
+
const newItems: (typeof sn)["items"] = {}
|
|
116
|
+
|
|
117
|
+
withErrorPathSegment("items", () => {
|
|
118
|
+
for (const k of Object.keys(sn.items)) {
|
|
119
|
+
newItems[k] = withErrorPathSegment(k, () => itemProcessor(sn.items[k]))
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const snCopy = {
|
|
124
|
+
...sn,
|
|
125
|
+
items: newItems,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return snCopy
|
|
129
|
+
}
|
|
130
|
+
: undefined
|
|
131
|
+
)
|
|
121
132
|
)
|
|
122
133
|
|
|
123
134
|
return thisTc
|