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.
Files changed (174) hide show
  1. package/CHANGELOG.md +864 -853
  2. package/README.md +61 -61
  3. package/dist/mobx-keystone.esm.js +11336 -11148
  4. package/dist/mobx-keystone.esm.mjs +11336 -11148
  5. package/dist/mobx-keystone.umd.js +12229 -11738
  6. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +1 -1
  7. package/dist/types/modelShared/prop.d.ts +5 -0
  8. package/dist/types/redux/connectReduxDevTools.d.ts +17 -4
  9. package/dist/types/ref/Ref.d.ts +1 -1
  10. package/dist/types/treeUtils/sandbox.d.ts +1 -1
  11. package/dist/types/types/TypeChecker.d.ts +1 -1
  12. package/dist/types/types/arrayBased/typesArray.d.ts +1 -1
  13. package/dist/types/types/arrayBased/typesTuple.d.ts +1 -1
  14. package/dist/types/types/objectBased/typesArraySet.d.ts +1 -1
  15. package/dist/types/types/objectBased/typesDataModelData.d.ts +1 -1
  16. package/dist/types/types/objectBased/typesModel.d.ts +1 -1
  17. package/dist/types/types/objectBased/typesObject.d.ts +3 -3
  18. package/dist/types/types/objectBased/typesObjectMap.d.ts +1 -1
  19. package/dist/types/types/objectBased/typesRecord.d.ts +1 -1
  20. package/dist/types/types/primitiveBased/typesPrimitive.d.ts +1 -1
  21. package/dist/types/types/types.d.ts +3 -3
  22. package/dist/types/types/utility/typesCodec.d.ts +1 -6
  23. package/dist/types/types/utility/typesCodecCore.d.ts +1 -1
  24. package/dist/types/types/utility/typesOr.d.ts +1 -1
  25. package/dist/types/types/utility/typesRefinement.d.ts +1 -1
  26. package/dist/types/types/utility/typesSkipCheck.d.ts +1 -1
  27. package/dist/types/types/utility/typesTag.d.ts +1 -1
  28. package/dist/types/wrappers/ObjectMap.d.ts +2 -0
  29. package/dist/types/wrappers/asMap.d.ts +10 -6
  30. package/package.json +14 -12
  31. package/src/action/applyAction.ts +125 -114
  32. package/src/action/applyMethodCall.ts +37 -37
  33. package/src/action/isModelAction.ts +16 -16
  34. package/src/action/middleware.ts +137 -137
  35. package/src/action/modelFlow.ts +240 -240
  36. package/src/action/modelFlowPromiseGenerator.ts +127 -125
  37. package/src/action/pendingActions.ts +42 -42
  38. package/src/action/wrapInAction.ts +156 -156
  39. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +1 -1
  40. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +172 -172
  41. package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +20 -20
  42. package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +17 -17
  43. package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +42 -42
  44. package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +50 -50
  45. package/src/actionMiddlewares/actionSerialization/objectSnapshotSerializer.ts +21 -21
  46. package/src/actionMiddlewares/actionSerialization/plainObjectSerializer.ts +24 -32
  47. package/src/actionMiddlewares/actionSerialization/primitiveSerializer.ts +51 -51
  48. package/src/actionMiddlewares/actionSerialization/setSerializer.ts +37 -37
  49. package/src/actionMiddlewares/actionTrackingMiddleware.ts +3 -3
  50. package/src/actionMiddlewares/onActionMiddleware.ts +2 -2
  51. package/src/actionMiddlewares/readonlyMiddleware.ts +2 -2
  52. package/src/actionMiddlewares/transactionMiddleware.ts +4 -4
  53. package/src/actionMiddlewares/undoMiddleware.ts +804 -796
  54. package/src/actionMiddlewares/utils.ts +2 -2
  55. package/src/computedTree/computedTree.ts +143 -143
  56. package/src/context/context.ts +1 -1
  57. package/src/dataModel/BaseDataModel.ts +194 -192
  58. package/src/dataModel/actions.ts +1 -1
  59. package/src/dataModel/newDataModel.ts +57 -57
  60. package/src/deepChange/index.ts +1 -1
  61. package/src/deepChange/onDeepChange.ts +244 -244
  62. package/src/frozen/Frozen.ts +146 -146
  63. package/src/globalConfig/globalConfig.ts +107 -107
  64. package/src/index.ts +24 -24
  65. package/src/model/BaseModel.ts +275 -273
  66. package/src/model/ModelConstructorOptions.ts +15 -15
  67. package/src/model/getModelMetadata.ts +51 -51
  68. package/src/model/index.ts +6 -6
  69. package/src/model/mixins.ts +288 -288
  70. package/src/model/newModel.ts +301 -301
  71. package/src/model/utils.ts +249 -249
  72. package/src/modelShared/modelDecorator.ts +260 -253
  73. package/src/modelShared/modelInfo.ts +50 -50
  74. package/src/modelShared/modelSymbols.ts +3 -3
  75. package/src/modelShared/newModel.ts +25 -25
  76. package/src/modelShared/prop.ts +713 -684
  77. package/src/modelShared/sharedInternalModel.ts +427 -417
  78. package/src/modelShared/utils.ts +24 -24
  79. package/src/parent/core.ts +65 -65
  80. package/src/parent/coreObjectChildren.ts +180 -180
  81. package/src/parent/detach.ts +47 -47
  82. package/src/parent/findParent.ts +1 -1
  83. package/src/parent/getChildrenObjects.ts +25 -25
  84. package/src/parent/path.ts +386 -381
  85. package/src/parent/setParent.ts +149 -149
  86. package/src/parent/walkTree.ts +1 -1
  87. package/src/patch/applyPatches.ts +187 -187
  88. package/src/patch/jsonPatch.ts +108 -108
  89. package/src/patch/patchRecorder.ts +6 -1
  90. package/src/redux/connectReduxDevTools.ts +39 -7
  91. package/src/redux/redux.ts +6 -2
  92. package/src/ref/Ref.ts +1 -1
  93. package/src/ref/core.ts +339 -345
  94. package/src/ref/customRef.ts +3 -3
  95. package/src/ref/rootRef.ts +100 -100
  96. package/src/rootStore/attachDetach.ts +89 -89
  97. package/src/rootStore/rootStore.ts +1 -1
  98. package/src/snapshot/SnapshotOf.ts +104 -104
  99. package/src/snapshot/SnapshotProcessingError.ts +45 -45
  100. package/src/snapshot/applySnapshot.ts +177 -181
  101. package/src/snapshot/cloneTreeValue.ts +30 -30
  102. package/src/snapshot/fromArraySnapshot.ts +27 -33
  103. package/src/snapshot/fromModelSnapshot.ts +70 -70
  104. package/src/snapshot/fromPlainObjectSnapshot.ts +47 -42
  105. package/src/snapshot/fromSnapshot.ts +198 -198
  106. package/src/snapshot/getSnapshot.ts +80 -79
  107. package/src/snapshot/index.ts +8 -8
  108. package/src/snapshot/internal.ts +230 -230
  109. package/src/snapshot/reconcileArraySnapshot.ts +70 -70
  110. package/src/snapshot/reconcileModelSnapshot.ts +155 -150
  111. package/src/snapshot/reconcilePlainObjectSnapshot.ts +72 -72
  112. package/src/snapshot/reconcileSnapshot.ts +88 -88
  113. package/src/standardActions/actions.ts +64 -64
  114. package/src/standardActions/objectActions.ts +60 -60
  115. package/src/transforms/ImmutableDate.ts +93 -93
  116. package/src/transforms/asMap.ts +50 -50
  117. package/src/transforms/asSet.ts +22 -22
  118. package/src/transforms/bigint.ts +20 -20
  119. package/src/transforms/date.ts +40 -40
  120. package/src/treeUtils/deepEquals.ts +55 -55
  121. package/src/treeUtils/sandbox.ts +6 -3
  122. package/src/tweaker/TweakerPriority.ts +9 -9
  123. package/src/tweaker/tweak.ts +4 -3
  124. package/src/tweaker/tweakArray.ts +530 -475
  125. package/src/tweaker/tweakFrozen.ts +42 -42
  126. package/src/tweaker/tweakModel.ts +1 -1
  127. package/src/tweaker/tweakPlainObject.ts +355 -355
  128. package/src/tweaker/typeChecking.ts +101 -101
  129. package/src/types/SnapshotTypeMismatchError.ts +46 -46
  130. package/src/types/TypeCheckError.ts +183 -184
  131. package/src/types/TypeChecker.ts +202 -185
  132. package/src/types/arrayBased/typesArray.ts +136 -137
  133. package/src/types/arrayBased/typesTuple.ts +138 -139
  134. package/src/types/createPerEntryCachedCheck.ts +166 -160
  135. package/src/types/index.ts +6 -6
  136. package/src/types/objectBased/typesArraySet.ts +133 -134
  137. package/src/types/objectBased/typesDataModelData.ts +203 -204
  138. package/src/types/objectBased/typesModel.ts +220 -221
  139. package/src/types/objectBased/typesObject.ts +308 -310
  140. package/src/types/objectBased/typesObjectMap.ts +142 -143
  141. package/src/types/objectBased/typesRecord.ts +156 -157
  142. package/src/types/objectBased/typesRef.ts +89 -89
  143. package/src/types/primitiveBased/typesEnum.ts +63 -63
  144. package/src/types/primitiveBased/typesPrimitive.ts +219 -220
  145. package/src/types/registerDefaultStandardTypeResolvers.ts +21 -21
  146. package/src/types/resolveTypeChecker.ts +78 -78
  147. package/src/types/schemas.ts +208 -208
  148. package/src/types/tProp.ts +277 -268
  149. package/src/types/typeCheck.ts +21 -21
  150. package/src/types/typeCheckErrorUtils.ts +23 -23
  151. package/src/types/types.ts +142 -142
  152. package/src/types/utility/typeToStoredData.ts +37 -37
  153. package/src/types/utility/typesBigInt.ts +15 -15
  154. package/src/types/utility/typesCodec.ts +41 -38
  155. package/src/types/utility/typesCodecCore.ts +178 -178
  156. package/src/types/utility/typesCodecSupport.ts +806 -806
  157. package/src/types/utility/typesDate.ts +27 -27
  158. package/src/types/utility/typesMap.ts +253 -222
  159. package/src/types/utility/typesOr.ts +263 -259
  160. package/src/types/utility/typesRefinement.ts +131 -125
  161. package/src/types/utility/typesSet.ts +110 -110
  162. package/src/types/utility/typesSkipCheck.ts +69 -70
  163. package/src/types/utility/typesTag.ts +88 -82
  164. package/src/types/utility/typesUnchecked.ts +40 -40
  165. package/src/utils/AnyFunction.ts +4 -4
  166. package/src/utils/ModelPool.ts +59 -59
  167. package/src/utils/chainFns.ts +18 -18
  168. package/src/utils/decorators.ts +274 -274
  169. package/src/utils/errorDiagnostics.ts +204 -204
  170. package/src/utils/index.ts +443 -372
  171. package/src/wrappers/ArraySet.ts +156 -156
  172. package/src/wrappers/ObjectMap.ts +155 -136
  173. package/src/wrappers/asMap.ts +65 -30
  174. 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
- // memoize to always return the same object
246
- private _props = lazy(() => {
247
- const objSchema = this._objTypeFn()
248
-
249
- const propTypes: O.Writable<ObjectTypeInfoProps> = {}
250
- Object.keys(objSchema).forEach((propName) => {
251
- const type = resolveStandardType(objSchema[propName])
252
- propTypes[propName] = { type, typeInfo: getTypeInfo(type) }
253
- })
254
- return propTypes
255
- })
256
-
257
- get props(): ObjectTypeInfoProps {
258
- return this._props()
259
- }
260
-
261
- constructor(
262
- thisType: AnyStandardType,
263
- private _objTypeFn: ObjectTypeFunction
264
- ) {
265
- super(thisType)
266
- }
267
- }
268
-
269
- /**
270
- * A type that represents frozen data.
271
- *
272
- * Example:
273
- * ```ts
274
- * const frozenNumberType = types.frozen(types.number)
275
- * const frozenAnyType = types.frozen(types.unchecked<any>())
276
- * const frozenNumberArrayType = types.frozen(types.array(types.number))
277
- * const frozenUncheckedNumberArrayType = types.frozen(types.unchecked<number[]>())
278
- * ```
279
- *
280
- * @template T Type.
281
- * @param dataType Type of the frozen data.
282
- * @returns
283
- */
284
- export function typesFrozen<T extends AnyType>(dataType: T): ModelType<Frozen<TypeToData<T>>> {
285
- return typesObjectHelper(
286
- () => ({
287
- data: dataType,
288
- }),
289
- true,
290
- (t) => new FrozenTypeInfo(t, resolveStandardType(dataType))
291
- ) as any
292
- }
293
-
294
- /**
295
- * `types.frozen` type info.
296
- */
297
- export class FrozenTypeInfo extends TypeInfo {
298
- readonly kind = "frozen"
299
-
300
- get dataTypeInfo(): TypeInfo {
301
- return getTypeInfo(this.dataType)
302
- }
303
-
304
- constructor(
305
- thisType: AnyStandardType,
306
- readonly dataType: AnyStandardType
307
- ) {
308
- super(thisType)
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
+ }