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,684 +1,713 @@
1
- import type { SnapshotInOf, SnapshotOutOf } from "../snapshot/SnapshotOf"
2
- import type { AnyStandardType } from "../types/schemas"
3
- import { getOrCreate } from "../utils/mapUtils"
4
- import type { Flatten, IsNeverType, IsOptionalValue } from "../utils/types"
5
-
6
- /**
7
- * @ignore
8
- */
9
- export const noDefaultValue = Symbol("noDefaultValue")
10
-
11
- type SetterMode = boolean | "assign"
12
-
13
- export type ModelPropSetterValueTransform<T> = (value: T) => T
14
-
15
- /**
16
- * A model property.
17
- */
18
- export interface ModelProp<
19
- TPropValue,
20
- TPropCreationValue,
21
- TTransformedValue,
22
- TTransformedCreationValue,
23
- TIsRequired,
24
- TIsId extends boolean = false,
25
- THasSetter = never,
26
- TFromSnapshotOverride = never,
27
- TToSnapshotOverride = never,
28
- > {
29
- $valueType: TPropValue
30
- $creationValueType: TPropCreationValue
31
- $transformedValueType: TTransformedValue
32
- $transformedCreationValueType: TTransformedCreationValue
33
- $isRequired: TIsRequired
34
- $isId: TIsId
35
- $hasSetter: THasSetter
36
- $fromSnapshotOverride: TFromSnapshotOverride
37
- $toSnapshotOverride: TToSnapshotOverride
38
-
39
- _defaultFn: (() => TPropValue) | typeof noDefaultValue
40
- _defaultValue: TPropValue | typeof noDefaultValue
41
- _typeChecker: AnyStandardType | undefined
42
- _setter: SetterMode
43
- _setterValueTransform: ((value: unknown) => unknown) | undefined
44
- _isId: boolean
45
- _idGenerator?: (() => string) | undefined
46
- _transform:
47
- | {
48
- transform: (
49
- original: unknown,
50
- model: object,
51
- propName: PropertyKey,
52
- setOriginalValue: (newOriginalValue: unknown) => void
53
- ) => unknown
54
- untransform: (transformed: unknown, model: object, propName: PropertyKey) => unknown
55
- }
56
- | undefined
57
- _fromSnapshotProcessor?: (sn: unknown) => unknown
58
- _toSnapshotProcessor?: (sn: unknown) => unknown
59
-
60
- /**
61
- * Adds a setter to the property. The setter will be named `set${CapitalizedPropName}`
62
- * and will be available in the model instance.
63
- */
64
- withSetter(): ModelProp<
65
- TPropValue,
66
- TPropCreationValue,
67
- TTransformedValue,
68
- TTransformedCreationValue,
69
- TIsRequired,
70
- TIsId,
71
- string,
72
- TFromSnapshotOverride,
73
- TToSnapshotOverride
74
- >
75
- /**
76
- * Adds a setter with a transform to the property. The setter will be named `set${CapitalizedPropName}`
77
- * and will be available in the model instance.
78
- */
79
- withSetter(
80
- valueTransform: ModelPropSetterValueTransform<TTransformedValue>
81
- ): ModelProp<
82
- TPropValue,
83
- TPropCreationValue,
84
- TTransformedValue,
85
- TTransformedCreationValue,
86
- TIsRequired,
87
- TIsId,
88
- string,
89
- TFromSnapshotOverride,
90
- TToSnapshotOverride
91
- >
92
- /**
93
- * @deprecated Setter methods are preferred.
94
- */
95
- withSetter(
96
- mode: "assign"
97
- ): ModelProp<
98
- TPropValue,
99
- TPropCreationValue,
100
- TTransformedValue,
101
- TTransformedCreationValue,
102
- TIsRequired,
103
- TIsId,
104
- string,
105
- TFromSnapshotOverride,
106
- TToSnapshotOverride
107
- >
108
-
109
- /**
110
- * Sets a transform for the property instance value.
111
- *
112
- * @deprecated Prefer using `tProp(types.codec(...))` or one of the built-in codec types
113
- * (e.g. `types.dateAsTimestamp`, `types.bigint`, `types.mapFromObject(...)`) instead.
114
- * Wrap with `types.skipCheck(...)` if you don't need runtime validation.
115
- * Use `.withTransform(...)` only for one-off custom transforms without a codec equivalent.
116
- *
117
- * @template TTV Transformed value type.
118
- * @param transform Transform to be used.
119
- * @returns
120
- */
121
- withTransform<TTV>(
122
- transform: ModelPropTransform<NonNullable<TPropValue>, TTV>
123
- ): ModelProp<
124
- TPropValue,
125
- TPropCreationValue,
126
- TTV | Extract<TPropValue, null | undefined>,
127
- TTV | Extract<TPropCreationValue, null | undefined>,
128
- TIsRequired,
129
- TIsId,
130
- THasSetter,
131
- TFromSnapshotOverride,
132
- TToSnapshotOverride
133
- >
134
-
135
- /**
136
- * Sets snapshot processors for this property.
137
- *
138
- * `fromSnapshot` runs before assigning snapshot data into the model prop.
139
- * `toSnapshot` runs when exporting model data back to snapshot form.
140
- */
141
- withSnapshotProcessor<
142
- FS = TFromSnapshotOverride,
143
- TS = TToSnapshotOverride,
144
- This extends AnyModelProp = this,
145
- >(processor: {
146
- fromSnapshot?: (sn: FS) => ModelPropFromSnapshot<This>
147
- toSnapshot?: (sn: ModelPropToSnapshot<This>) => TS
148
- }): ModelProp<
149
- TPropValue,
150
- TPropCreationValue,
151
- TTransformedValue,
152
- TTransformedCreationValue,
153
- TIsRequired,
154
- TIsId,
155
- THasSetter,
156
- FS,
157
- TS
158
- >
159
- }
160
-
161
- /**
162
- * The snapshot in type of a model property.
163
- */
164
- export type ModelPropFromSnapshot<MP extends AnyModelProp> = IsNeverType<
165
- ModelPropFromSnapshotOverride<MP>,
166
- SnapshotInOf<ModelPropStoredCreationValue<MP>>,
167
- ModelPropFromSnapshotOverride<MP>
168
- >
169
-
170
- /**
171
- * The snapshot out type of a model property.
172
- */
173
- export type ModelPropToSnapshot<MP extends AnyModelProp> = IsNeverType<
174
- MP["$toSnapshotOverride"],
175
- SnapshotOutOf<ModelPropStoredValue<MP>>,
176
- MP["$toSnapshotOverride"]
177
- >
178
-
179
- export type ModelPropStoredValue<MP extends AnyModelProp> = MP extends {
180
- $storedValueType: infer TStoredValue
181
- }
182
- ? TStoredValue
183
- : MP["$valueType"]
184
-
185
- export type ModelPropStoredCreationValue<MP extends AnyModelProp> = MP extends {
186
- $storedCreationValueType: infer TStoredCreationValue
187
- }
188
- ? TStoredCreationValue
189
- : MP["$creationValueType"]
190
-
191
- export type ModelPropFromSnapshotOverride<MP extends AnyModelProp> = MP extends {
192
- $typedFromSnapshotOverride: infer TFromSnapshotOverride
193
- }
194
- ? TFromSnapshotOverride
195
- : MP["$fromSnapshotOverride"]
196
-
197
- /**
198
- * A model prop transform.
199
- */
200
- export interface ModelPropTransform<TOriginal, TTransformed> {
201
- /**
202
- * Converts the stored/original value into the transformed value exposed by the model prop.
203
- */
204
- transform(params: {
205
- originalValue: TOriginal
206
- cachedTransformedValue: TTransformed | undefined
207
- setOriginalValue(value: TOriginal): void
208
- }): TTransformed
209
-
210
- /**
211
- * Converts the transformed model value back into the original stored value.
212
- */
213
- untransform(params: {
214
- transformedValue: TTransformed
215
- cacheTransformedValue: () => void
216
- }): TOriginal
217
- }
218
-
219
- /**
220
- * Any model property.
221
- */
222
- export type AnyModelProp = ModelProp<any, any, any, any, any, any, any, any, any>
223
-
224
- /**
225
- * Model properties.
226
- */
227
- export interface ModelProps {
228
- [k: string]: AnyModelProp
229
- }
230
-
231
- export type RequiredModelProps<MP extends ModelProps> = {
232
- [K in keyof MP]: MP[K]["$isRequired"] & K
233
- }[keyof MP]
234
-
235
- export type ModelPropsToUntransformedData<MP extends ModelProps> = Flatten<{
236
- [k in keyof MP]: ModelPropStoredValue<MP[k]>
237
- }>
238
-
239
- export type ModelPropsToSnapshotData<MP extends ModelProps> = Flatten<{
240
- [k in keyof MP]: ModelPropToSnapshot<MP[k]> extends infer R ? R : never
241
- }>
242
-
243
- // we don't use O.Optional anymore since it generates unions too heavy
244
- // also if we use Pick over the optional props we will loose the ability to infer generics
245
- // we also don't use Flatten because if we do some generics won't work
246
- export type ModelPropsToUntransformedCreationData<MP extends ModelProps> = {
247
- [k in keyof MP]?: ModelPropStoredCreationValue<MP[k]>
248
- } & {
249
- [k in RequiredModelProps<MP>]: ModelPropStoredCreationValue<MP[k]>
250
- }
251
-
252
- // we don't use O.Optional anymore since it generates unions too heavy
253
- // also if we use Pick over the optional props we will loose the ability to infer generics
254
- export type ModelPropsToSnapshotCreationData<MP extends ModelProps> = Flatten<
255
- {
256
- [k in keyof MP]?: ModelPropFromSnapshot<MP[k]> extends infer R ? R : never
257
- } & {
258
- [k in {
259
- [K in keyof MP]: IsNeverType<
260
- ModelPropFromSnapshotOverride<MP[K]>,
261
- MP[K]["$isRequired"] & K, // no override
262
- IsOptionalValue<ModelPropFromSnapshotOverride<MP[K]>, never, K> // with override
263
- >
264
- }[keyof MP]]: ModelPropFromSnapshot<MP[k]> extends infer R ? R : never
265
- }
266
- >
267
-
268
- export type ModelPropsToTransformedData<MP extends ModelProps> = Flatten<{
269
- [k in keyof MP]: MP[k]["$transformedValueType"]
270
- }>
271
-
272
- // we don't use O.Optional anymore since it generates unions too heavy
273
- // also if we use Pick over the optional props we will loose the ability to infer generics
274
- // we also don't use Flatten because if we do some generics won't work
275
- // we also don't use Omit because if we do some generics won't work
276
- export type ModelPropsToTransformedCreationData<MP extends ModelProps> = {
277
- [k in keyof MP]?: MP[k]["$transformedCreationValueType"]
278
- } & {
279
- [k in RequiredModelProps<MP>]: MP[k]["$transformedCreationValueType"]
280
- }
281
-
282
- export type ModelPropsToSetter<MP extends ModelProps> = Flatten<{
283
- [k in keyof MP as MP[k]["$hasSetter"] & `set${Capitalize<k & string>}`]: (
284
- value: MP[k]["$transformedValueType"]
285
- ) => void
286
- }>
287
-
288
- export type ModelIdProp<T extends string = string> = ModelProp<
289
- T,
290
- T | undefined,
291
- T,
292
- T | undefined,
293
- never, // not required
294
- true
295
- >
296
-
297
- type TypedModelIdProp<T extends string = string, THasSetter = never> = Omit<
298
- ModelProp<T, T | undefined, T, T | undefined, never, true, THasSetter>,
299
- "withSetter"
300
- > & {
301
- /**
302
- * Enables generation of a setter method for the ID prop (`setId` for `id`, `setCustomId` for `customId`).
303
- */
304
- withSetter(): TypedModelIdProp<T, string>
305
- /**
306
- * Enables generation of a setter method for the ID prop and applies a value transform to setter input.
307
- */
308
- withSetter(valueTransform: ModelPropSetterValueTransform<T>): TypedModelIdProp<T, string>
309
- /**
310
- * @deprecated Setter methods are preferred.
311
- */
312
- withSetter(mode: "assign"): TypedModelIdProp<T, string>
313
-
314
- /**
315
- * Sets a custom generator for missing model IDs in this `idProp`.
316
- */
317
- withGenerator(generator: () => T): TypedModelIdProp<T, THasSetter>
318
-
319
- /**
320
- * Same as `idProp`, except that it might have a specific TypeScript string template as type.
321
- * E.g. `idProp.typedAs<`custom-${string}`>()`
322
- */
323
- typedAs<U extends string>(): TypedModelIdProp<U, THasSetter>
324
- }
325
-
326
- /**
327
- * A property that will be used as model id, accessible through $modelId.
328
- * Can only be used in models and there can be only one per model.
329
- */
330
- export const idProp = {
331
- _setter: false,
332
- _setterValueTransform: undefined,
333
- _isId: true,
334
- _idGenerator: undefined,
335
-
336
- withSetter(modeOrValueTransform?: SetterMode | ModelPropSetterValueTransform<unknown>) {
337
- const obj: AnyModelProp = Object.create(this)
338
- const setterConfig = parseSetterConfig(modeOrValueTransform)
339
- obj._setter = setterConfig.mode
340
- obj._setterValueTransform = setterConfig.valueTransform
341
- return obj
342
- },
343
-
344
- withGenerator(generator: () => string) {
345
- const obj: AnyModelProp = Object.create(this)
346
- obj._idGenerator = generator
347
- return obj
348
- },
349
-
350
- typedAs() {
351
- return this
352
- },
353
- } as unknown as TypedModelIdProp
354
-
355
- /**
356
- * @ignore
357
- */
358
- export type OnlyPrimitives<T> = Exclude<T, object>
359
-
360
- /**
361
- * A model prop that maybe / maybe not is optional, depending on if the value can take undefined.
362
- */
363
- export type MaybeOptionalModelProp<TPropValue> = ModelProp<
364
- TPropValue,
365
- TPropValue,
366
- TPropValue,
367
- TPropValue,
368
- IsOptionalValue<TPropValue, never, string> // calculate if required
369
- >
370
-
371
- /**
372
- * A model prop that is definitely optional.
373
- */
374
- export type OptionalModelProp<TPropValue> = ModelProp<
375
- TPropValue,
376
- TPropValue | null | undefined,
377
- TPropValue,
378
- TPropValue | null | undefined,
379
- never // not required
380
- >
381
-
382
- const baseProp: AnyModelProp = {
383
- ...({} as Pick<
384
- AnyModelProp,
385
- | "$valueType"
386
- | "$creationValueType"
387
- | "$transformedValueType"
388
- | "$transformedCreationValueType"
389
- | "$isRequired"
390
- | "$isId"
391
- | "$hasSetter"
392
- | "$fromSnapshotOverride"
393
- | "$toSnapshotOverride"
394
- >),
395
-
396
- _defaultFn: noDefaultValue,
397
- _defaultValue: noDefaultValue,
398
- _typeChecker: undefined,
399
- _setter: false,
400
- _setterValueTransform: undefined,
401
- _isId: false,
402
- _transform: undefined,
403
- _fromSnapshotProcessor: undefined,
404
- _toSnapshotProcessor: undefined,
405
-
406
- withSetter(modeOrValueTransform?: SetterMode | ModelPropSetterValueTransform<unknown>) {
407
- const obj: AnyModelProp = Object.create(this)
408
- const setterConfig = parseSetterConfig(modeOrValueTransform)
409
- obj._setter = setterConfig.mode
410
- obj._setterValueTransform = setterConfig.valueTransform
411
- return obj
412
- },
413
-
414
- withTransform(transform: ModelPropTransform<unknown, unknown>) {
415
- const obj: AnyModelProp = Object.create(this)
416
- obj._transform = composeFullTransforms(this._transform, toFullTransform(transform))
417
- return obj
418
- },
419
-
420
- withSnapshotProcessor({ fromSnapshot, toSnapshot }) {
421
- let newFromSnapshot: ((sn: any) => any) | undefined
422
-
423
- if (this._fromSnapshotProcessor && fromSnapshot) {
424
- const oldFn = this._fromSnapshotProcessor
425
- const newFn = fromSnapshot
426
- newFromSnapshot = (sn: any) => oldFn(newFn(sn))
427
- } else if (fromSnapshot) {
428
- newFromSnapshot = fromSnapshot
429
- } else {
430
- newFromSnapshot = this._fromSnapshotProcessor
431
- }
432
-
433
- let newToSnapshot: ((sn: any) => any) | undefined
434
-
435
- if (this._toSnapshotProcessor && toSnapshot) {
436
- const oldFn: (sn: unknown) => any = this._toSnapshotProcessor
437
- const newFn = toSnapshot
438
- newToSnapshot = (sn: unknown) => newFn(oldFn(sn))
439
- } else if (toSnapshot) {
440
- newToSnapshot = toSnapshot
441
- } else {
442
- newToSnapshot = this._toSnapshotProcessor
443
- }
444
-
445
- const obj: AnyModelProp = Object.create(this)
446
- obj._fromSnapshotProcessor = newFromSnapshot
447
- obj._toSnapshotProcessor = newToSnapshot
448
-
449
- return obj
450
- },
451
- }
452
-
453
- /**
454
- * Defines a model property, with an optional function to generate a default value
455
- * if the input snapshot / model creation data is `null` or `undefined`.
456
- *
457
- * Example:
458
- * ```ts
459
- * x: prop(() => 10) // an optional number, with a default value of 10
460
- * x: prop<number[]>(() => []) // an optional number array, with a default empty array
461
- * ```
462
- *
463
- * @template TValue Value type.
464
- * @param defaultFn Default value generator function.
465
- * @returns
466
- */
467
- export function prop<TValue>(defaultFn: () => TValue): OptionalModelProp<TValue>
468
-
469
- /**
470
- * Defines a model property, with an optional default value
471
- * if the input snapshot / model creation data is `null` or `undefined`.
472
- * You should only use this with primitive values and never with object values
473
- * (array, model, object, etc).
474
- *
475
- * Example:
476
- * ```ts
477
- * x: prop(10) // an optional number, with a default value of 10
478
- * ```
479
- *
480
- * @template TValue Value type.
481
- * @param defaultValue Default primitive value.
482
- * @returns
483
- */
484
- export function prop<TValue>(defaultValue: OnlyPrimitives<TValue>): OptionalModelProp<TValue>
485
-
486
- /**
487
- * Defines a model property with no default value.
488
- *
489
- * Example:
490
- * ```ts
491
- * x: prop<number>() // a required number
492
- * x: prop<number | undefined>() // an optional number, which defaults to undefined
493
- * ```
494
- *
495
- * @template TValue Value type.
496
- * @returns
497
- */
498
- export function prop<TValue>(): MaybeOptionalModelProp<TValue>
499
-
500
- // base
501
- export function prop(def?: any): AnyModelProp {
502
- const hasDefaultValue = arguments.length > 0
503
- if (!hasDefaultValue) {
504
- return baseProp
505
- }
506
-
507
- let p = propCache.get(def)
508
-
509
- if (!p) {
510
- p = Object.create(baseProp)
511
-
512
- if (typeof def === "function") {
513
- p!._defaultFn = def
514
- } else {
515
- p!._defaultValue = def
516
- }
517
-
518
- propCache.set(def, p!)
519
- }
520
-
521
- return p!
522
- }
523
-
524
- function parseSetterConfig(
525
- modeOrValueTransform?: SetterMode | ModelPropSetterValueTransform<unknown>
526
- ): {
527
- mode: SetterMode
528
- valueTransform: ((value: unknown) => unknown) | undefined
529
- } {
530
- if (typeof modeOrValueTransform === "function") {
531
- return {
532
- mode: true,
533
- valueTransform: modeOrValueTransform,
534
- }
535
- }
536
-
537
- if (
538
- modeOrValueTransform === undefined ||
539
- typeof modeOrValueTransform === "boolean" ||
540
- modeOrValueTransform === "assign"
541
- ) {
542
- return {
543
- mode: modeOrValueTransform ?? true,
544
- valueTransform: undefined,
545
- }
546
- }
547
-
548
- return {
549
- mode: true,
550
- valueTransform: undefined,
551
- }
552
- }
553
-
554
- const propCache = new Map<unknown, AnyModelProp>()
555
-
556
- let cacheTransformResult = false
557
- const cacheTransformedValueFn = () => {
558
- cacheTransformResult = true
559
- }
560
-
561
- function toFullTransform(transformObject: ModelPropTransform<unknown, unknown>) {
562
- const cache = new WeakMap<
563
- object,
564
- Map<PropertyKey, { originalValue: unknown; transformedValue: unknown }>
565
- >()
566
-
567
- const transform = (params: {
568
- originalValue: unknown
569
- cachedTransformedValue: unknown
570
- setOriginalValue(newOriginalValue: unknown): void
571
- }) => (params.originalValue == null ? params.originalValue : transformObject.transform(params))
572
-
573
- const untransform = (params: { transformedValue: unknown; cacheTransformedValue(): void }) =>
574
- params.transformedValue == null ? params.transformedValue : transformObject.untransform(params)
575
-
576
- return {
577
- transform(
578
- originalValue: unknown,
579
- model: object,
580
- propName: PropertyKey,
581
- setOriginalValue: (newOriginalValue: unknown) => void
582
- ) {
583
- const modelCache = getOrCreate(cache, model, () => new Map())
584
-
585
- let propCache = modelCache.get(propName)
586
- if (propCache?.originalValue !== originalValue) {
587
- // original changed, invalidate cache
588
- modelCache.delete(propName)
589
- propCache = undefined
590
- }
591
-
592
- const transformedValue = transform({
593
- originalValue,
594
- cachedTransformedValue: propCache?.transformedValue,
595
- setOriginalValue,
596
- })
597
-
598
- modelCache.set(propName, {
599
- originalValue,
600
- transformedValue,
601
- })
602
-
603
- return transformedValue
604
- },
605
-
606
- untransform(transformedValue: unknown, model: object, propName: PropertyKey) {
607
- const modelCache = getOrCreate(cache, model, () => new Map())
608
-
609
- cacheTransformResult = false
610
- const originalValue = untransform({
611
- transformedValue,
612
- cacheTransformedValue: cacheTransformedValueFn,
613
- })
614
- if (cacheTransformResult) {
615
- modelCache.set(propName, { originalValue, transformedValue })
616
- } else {
617
- modelCache.delete(propName)
618
- }
619
-
620
- return originalValue
621
- },
622
- }
623
- }
624
-
625
- function composeFullTransforms(
626
- outerTransform:
627
- | {
628
- transform(
629
- originalValue: unknown,
630
- model: object,
631
- propName: PropertyKey,
632
- setOriginalValue: (newOriginalValue: unknown) => void
633
- ): unknown
634
- untransform(transformedValue: unknown, model: object, propName: PropertyKey): unknown
635
- }
636
- | undefined,
637
- innerTransform: {
638
- transform(
639
- originalValue: unknown,
640
- model: object,
641
- propName: PropertyKey,
642
- setOriginalValue: (newOriginalValue: unknown) => void
643
- ): unknown
644
- untransform(transformedValue: unknown, model: object, propName: PropertyKey): unknown
645
- }
646
- ) {
647
- if (!outerTransform) {
648
- return innerTransform
649
- }
650
-
651
- return {
652
- transform(
653
- originalValue: unknown,
654
- model: object,
655
- propName: PropertyKey,
656
- setOriginalValue: (newOriginalValue: unknown) => void
657
- ) {
658
- const outerValue = outerTransform.transform(originalValue, model, propName, setOriginalValue)
659
- return innerTransform.transform(outerValue, model, propName, (newOuterValue) => {
660
- setOriginalValue(outerTransform.untransform(newOuterValue, model, propName))
661
- })
662
- },
663
-
664
- untransform(transformedValue: unknown, model: object, propName: PropertyKey) {
665
- const outerValue = innerTransform.untransform(transformedValue, model, propName)
666
- return outerTransform.untransform(outerValue, model, propName)
667
- },
668
- }
669
- }
670
-
671
- /**
672
- * @ignore
673
- */
674
- export function getModelPropDefaultValue(propData: AnyModelProp): unknown | typeof noDefaultValue {
675
- if (propData._defaultFn !== noDefaultValue) {
676
- return propData._defaultFn()
677
- }
678
-
679
- if (propData._defaultValue !== noDefaultValue) {
680
- return propData._defaultValue
681
- }
682
-
683
- return noDefaultValue
684
- }
1
+ import type { SnapshotInOf, SnapshotOutOf } from "../snapshot/SnapshotOf"
2
+ import type { AnyStandardType } from "../types/schemas"
3
+ import { runWithErrorDiagnosticsContext, withErrorPathSegment } from "../utils/errorDiagnostics"
4
+ import { getOrCreate } from "../utils/mapUtils"
5
+ import type { Flatten, IsNeverType, IsOptionalValue } from "../utils/types"
6
+
7
+ /**
8
+ * @ignore
9
+ */
10
+ export const noDefaultValue = Symbol("noDefaultValue")
11
+
12
+ type SetterMode = boolean | "assign"
13
+
14
+ export type ModelPropSetterValueTransform<T> = (value: T) => T
15
+
16
+ /**
17
+ * A model property.
18
+ */
19
+ export interface ModelProp<
20
+ TPropValue,
21
+ TPropCreationValue,
22
+ TTransformedValue,
23
+ TTransformedCreationValue,
24
+ TIsRequired,
25
+ TIsId extends boolean = false,
26
+ THasSetter = never,
27
+ TFromSnapshotOverride = never,
28
+ TToSnapshotOverride = never,
29
+ > {
30
+ $valueType: TPropValue
31
+ $creationValueType: TPropCreationValue
32
+ $transformedValueType: TTransformedValue
33
+ $transformedCreationValueType: TTransformedCreationValue
34
+ $isRequired: TIsRequired
35
+ $isId: TIsId
36
+ $hasSetter: THasSetter
37
+ $fromSnapshotOverride: TFromSnapshotOverride
38
+ $toSnapshotOverride: TToSnapshotOverride
39
+
40
+ _defaultFn: (() => TPropValue) | typeof noDefaultValue
41
+ _defaultValue: TPropValue | typeof noDefaultValue
42
+ // True when the declared default is a runtime/transformed value and must be
43
+ // converted before it can be stored in `$`.
44
+ _defaultValueIsTransformed: boolean
45
+ _typeChecker: AnyStandardType | undefined
46
+ _setter: SetterMode
47
+ _setterValueTransform: ((value: unknown) => unknown) | undefined
48
+ _isId: boolean
49
+ _idGenerator?: (() => string) | undefined
50
+ _transform:
51
+ | {
52
+ transform: (
53
+ original: unknown,
54
+ model: object,
55
+ propName: PropertyKey,
56
+ setOriginalValue: (newOriginalValue: unknown) => void
57
+ ) => unknown
58
+ untransform: (transformed: unknown, model: object, propName: PropertyKey) => unknown
59
+ }
60
+ | undefined
61
+ _fromSnapshotProcessor?: (sn: unknown) => unknown
62
+ _toSnapshotProcessor?: (sn: unknown) => unknown
63
+
64
+ /**
65
+ * Adds a setter to the property. The setter will be named `set${CapitalizedPropName}`
66
+ * and will be available in the model instance.
67
+ */
68
+ withSetter(): ModelProp<
69
+ TPropValue,
70
+ TPropCreationValue,
71
+ TTransformedValue,
72
+ TTransformedCreationValue,
73
+ TIsRequired,
74
+ TIsId,
75
+ string,
76
+ TFromSnapshotOverride,
77
+ TToSnapshotOverride
78
+ >
79
+ /**
80
+ * Adds a setter with a transform to the property. The setter will be named `set${CapitalizedPropName}`
81
+ * and will be available in the model instance.
82
+ */
83
+ withSetter(
84
+ valueTransform: ModelPropSetterValueTransform<TTransformedValue>
85
+ ): ModelProp<
86
+ TPropValue,
87
+ TPropCreationValue,
88
+ TTransformedValue,
89
+ TTransformedCreationValue,
90
+ TIsRequired,
91
+ TIsId,
92
+ string,
93
+ TFromSnapshotOverride,
94
+ TToSnapshotOverride
95
+ >
96
+ /**
97
+ * @deprecated Setter methods are preferred.
98
+ */
99
+ withSetter(
100
+ mode: "assign"
101
+ ): ModelProp<
102
+ TPropValue,
103
+ TPropCreationValue,
104
+ TTransformedValue,
105
+ TTransformedCreationValue,
106
+ TIsRequired,
107
+ TIsId,
108
+ string,
109
+ TFromSnapshotOverride,
110
+ TToSnapshotOverride
111
+ >
112
+
113
+ /**
114
+ * Sets a transform for the property instance value.
115
+ *
116
+ * @deprecated Prefer using `tProp(types.codec(...))` or one of the built-in codec types
117
+ * (e.g. `types.dateAsTimestamp`, `types.bigint`, `types.mapFromObject(...)`) instead.
118
+ * Wrap with `types.skipCheck(...)` if you don't need runtime validation.
119
+ * Use `.withTransform(...)` only for one-off custom transforms without a codec equivalent.
120
+ *
121
+ * @template TTV Transformed value type.
122
+ * @param transform Transform to be used.
123
+ * @returns
124
+ */
125
+ withTransform<TTV>(
126
+ transform: ModelPropTransform<NonNullable<TPropValue>, TTV>
127
+ ): ModelProp<
128
+ TPropValue,
129
+ TPropCreationValue,
130
+ TTV | Extract<TPropValue, null | undefined>,
131
+ TTV | Extract<TPropCreationValue, null | undefined>,
132
+ TIsRequired,
133
+ TIsId,
134
+ THasSetter,
135
+ TFromSnapshotOverride,
136
+ TToSnapshotOverride
137
+ >
138
+
139
+ /**
140
+ * Sets snapshot processors for this property.
141
+ *
142
+ * `fromSnapshot` runs before assigning snapshot data into the model prop.
143
+ * `toSnapshot` runs when exporting model data back to snapshot form.
144
+ */
145
+ withSnapshotProcessor<
146
+ FS = TFromSnapshotOverride,
147
+ TS = TToSnapshotOverride,
148
+ This extends AnyModelProp = this,
149
+ >(processor: {
150
+ fromSnapshot?: (sn: FS) => ModelPropFromSnapshot<This>
151
+ toSnapshot?: (sn: ModelPropToSnapshot<This>) => TS
152
+ }): ModelProp<
153
+ TPropValue,
154
+ TPropCreationValue,
155
+ TTransformedValue,
156
+ TTransformedCreationValue,
157
+ TIsRequired,
158
+ TIsId,
159
+ THasSetter,
160
+ FS,
161
+ TS
162
+ >
163
+ }
164
+
165
+ /**
166
+ * The snapshot in type of a model property.
167
+ */
168
+ export type ModelPropFromSnapshot<MP extends AnyModelProp> = IsNeverType<
169
+ ModelPropFromSnapshotOverride<MP>,
170
+ SnapshotInOf<ModelPropStoredCreationValue<MP>>,
171
+ ModelPropFromSnapshotOverride<MP>
172
+ >
173
+
174
+ /**
175
+ * The snapshot out type of a model property.
176
+ */
177
+ export type ModelPropToSnapshot<MP extends AnyModelProp> = IsNeverType<
178
+ MP["$toSnapshotOverride"],
179
+ SnapshotOutOf<ModelPropStoredValue<MP>>,
180
+ MP["$toSnapshotOverride"]
181
+ >
182
+
183
+ export type ModelPropStoredValue<MP extends AnyModelProp> = MP extends {
184
+ $storedValueType: infer TStoredValue
185
+ }
186
+ ? TStoredValue
187
+ : MP["$valueType"]
188
+
189
+ export type ModelPropStoredCreationValue<MP extends AnyModelProp> = MP extends {
190
+ $storedCreationValueType: infer TStoredCreationValue
191
+ }
192
+ ? TStoredCreationValue
193
+ : MP["$creationValueType"]
194
+
195
+ export type ModelPropFromSnapshotOverride<MP extends AnyModelProp> = MP extends {
196
+ $typedFromSnapshotOverride: infer TFromSnapshotOverride
197
+ }
198
+ ? TFromSnapshotOverride
199
+ : MP["$fromSnapshotOverride"]
200
+
201
+ /**
202
+ * A model prop transform.
203
+ */
204
+ export interface ModelPropTransform<TOriginal, TTransformed> {
205
+ /**
206
+ * Converts the stored/original value into the transformed value exposed by the model prop.
207
+ */
208
+ transform(params: {
209
+ originalValue: TOriginal
210
+ cachedTransformedValue: TTransformed | undefined
211
+ setOriginalValue(value: TOriginal): void
212
+ }): TTransformed
213
+
214
+ /**
215
+ * Converts the transformed model value back into the original stored value.
216
+ */
217
+ untransform(params: {
218
+ transformedValue: TTransformed
219
+ cacheTransformedValue: () => void
220
+ }): TOriginal
221
+ }
222
+
223
+ /**
224
+ * Any model property.
225
+ */
226
+ export type AnyModelProp = ModelProp<any, any, any, any, any, any, any, any, any>
227
+
228
+ /**
229
+ * Model properties.
230
+ */
231
+ export interface ModelProps {
232
+ [k: string]: AnyModelProp
233
+ }
234
+
235
+ export type RequiredModelProps<MP extends ModelProps> = {
236
+ [K in keyof MP]: MP[K]["$isRequired"] & K
237
+ }[keyof MP]
238
+
239
+ export type ModelPropsToUntransformedData<MP extends ModelProps> = Flatten<{
240
+ [k in keyof MP]: ModelPropStoredValue<MP[k]>
241
+ }>
242
+
243
+ export type ModelPropsToSnapshotData<MP extends ModelProps> = Flatten<{
244
+ [k in keyof MP]: ModelPropToSnapshot<MP[k]> extends infer R ? R : never
245
+ }>
246
+
247
+ // we don't use O.Optional anymore since it generates unions too heavy
248
+ // also if we use Pick over the optional props we will loose the ability to infer generics
249
+ // we also don't use Flatten because if we do some generics won't work
250
+ export type ModelPropsToUntransformedCreationData<MP extends ModelProps> = {
251
+ [k in keyof MP]?: ModelPropStoredCreationValue<MP[k]>
252
+ } & {
253
+ [k in RequiredModelProps<MP>]: ModelPropStoredCreationValue<MP[k]>
254
+ }
255
+
256
+ // we don't use O.Optional anymore since it generates unions too heavy
257
+ // also if we use Pick over the optional props we will loose the ability to infer generics
258
+ export type ModelPropsToSnapshotCreationData<MP extends ModelProps> = Flatten<
259
+ {
260
+ [k in keyof MP]?: ModelPropFromSnapshot<MP[k]> extends infer R ? R : never
261
+ } & {
262
+ [k in {
263
+ [K in keyof MP]: IsNeverType<
264
+ ModelPropFromSnapshotOverride<MP[K]>,
265
+ MP[K]["$isRequired"] & K, // no override
266
+ IsOptionalValue<ModelPropFromSnapshotOverride<MP[K]>, never, K> // with override
267
+ >
268
+ }[keyof MP]]: ModelPropFromSnapshot<MP[k]> extends infer R ? R : never
269
+ }
270
+ >
271
+
272
+ export type ModelPropsToTransformedData<MP extends ModelProps> = Flatten<{
273
+ [k in keyof MP]: MP[k]["$transformedValueType"]
274
+ }>
275
+
276
+ // we don't use O.Optional anymore since it generates unions too heavy
277
+ // also if we use Pick over the optional props we will loose the ability to infer generics
278
+ // we also don't use Flatten because if we do some generics won't work
279
+ // we also don't use Omit because if we do some generics won't work
280
+ export type ModelPropsToTransformedCreationData<MP extends ModelProps> = {
281
+ [k in keyof MP]?: MP[k]["$transformedCreationValueType"]
282
+ } & {
283
+ [k in RequiredModelProps<MP>]: MP[k]["$transformedCreationValueType"]
284
+ }
285
+
286
+ export type ModelPropsToSetter<MP extends ModelProps> = Flatten<{
287
+ [k in keyof MP as MP[k]["$hasSetter"] & `set${Capitalize<k & string>}`]: (
288
+ value: MP[k]["$transformedValueType"]
289
+ ) => void
290
+ }>
291
+
292
+ export type ModelIdProp<T extends string = string> = ModelProp<
293
+ T,
294
+ T | undefined,
295
+ T,
296
+ T | undefined,
297
+ never, // not required
298
+ true
299
+ >
300
+
301
+ type TypedModelIdProp<T extends string = string, THasSetter = never> = Omit<
302
+ ModelProp<T, T | undefined, T, T | undefined, never, true, THasSetter>,
303
+ "withSetter"
304
+ > & {
305
+ /**
306
+ * Enables generation of a setter method for the ID prop (`setId` for `id`, `setCustomId` for `customId`).
307
+ */
308
+ withSetter(): TypedModelIdProp<T, string>
309
+ /**
310
+ * Enables generation of a setter method for the ID prop and applies a value transform to setter input.
311
+ */
312
+ withSetter(valueTransform: ModelPropSetterValueTransform<T>): TypedModelIdProp<T, string>
313
+ /**
314
+ * @deprecated Setter methods are preferred.
315
+ */
316
+ withSetter(mode: "assign"): TypedModelIdProp<T, string>
317
+
318
+ /**
319
+ * Sets a custom generator for missing model IDs in this `idProp`.
320
+ */
321
+ withGenerator(generator: () => T): TypedModelIdProp<T, THasSetter>
322
+
323
+ /**
324
+ * Same as `idProp`, except that it might have a specific TypeScript string template as type.
325
+ * E.g. `idProp.typedAs<`custom-${string}`>()`
326
+ */
327
+ typedAs<U extends string>(): TypedModelIdProp<U, THasSetter>
328
+ }
329
+
330
+ /**
331
+ * A property that will be used as model id, accessible through $modelId.
332
+ * Can only be used in models and there can be only one per model.
333
+ */
334
+ export const idProp = {
335
+ _setter: false,
336
+ _setterValueTransform: undefined,
337
+ _isId: true,
338
+ _idGenerator: undefined,
339
+
340
+ withSetter(modeOrValueTransform?: SetterMode | ModelPropSetterValueTransform<unknown>) {
341
+ const obj: AnyModelProp = Object.create(this)
342
+ const setterConfig = parseSetterConfig(modeOrValueTransform)
343
+ obj._setter = setterConfig.mode
344
+ obj._setterValueTransform = setterConfig.valueTransform
345
+ return obj
346
+ },
347
+
348
+ withGenerator(generator: () => string) {
349
+ const obj: AnyModelProp = Object.create(this)
350
+ obj._idGenerator = generator
351
+ return obj
352
+ },
353
+
354
+ typedAs() {
355
+ return this
356
+ },
357
+ } as unknown as TypedModelIdProp
358
+
359
+ /**
360
+ * @ignore
361
+ */
362
+ export type OnlyPrimitives<T> = Exclude<T, object>
363
+
364
+ /**
365
+ * A model prop that maybe / maybe not is optional, depending on if the value can take undefined.
366
+ */
367
+ export type MaybeOptionalModelProp<TPropValue> = ModelProp<
368
+ TPropValue,
369
+ TPropValue,
370
+ TPropValue,
371
+ TPropValue,
372
+ IsOptionalValue<TPropValue, never, string> // calculate if required
373
+ >
374
+
375
+ /**
376
+ * A model prop that is definitely optional.
377
+ */
378
+ export type OptionalModelProp<TPropValue> = ModelProp<
379
+ TPropValue,
380
+ TPropValue | null | undefined,
381
+ TPropValue,
382
+ TPropValue | null | undefined,
383
+ never // not required
384
+ >
385
+
386
+ const baseProp: AnyModelProp = {
387
+ ...({} as Pick<
388
+ AnyModelProp,
389
+ | "$valueType"
390
+ | "$creationValueType"
391
+ | "$transformedValueType"
392
+ | "$transformedCreationValueType"
393
+ | "$isRequired"
394
+ | "$isId"
395
+ | "$hasSetter"
396
+ | "$fromSnapshotOverride"
397
+ | "$toSnapshotOverride"
398
+ >),
399
+
400
+ _defaultFn: noDefaultValue,
401
+ _defaultValue: noDefaultValue,
402
+ _defaultValueIsTransformed: false,
403
+ _typeChecker: undefined,
404
+ _setter: false,
405
+ _setterValueTransform: undefined,
406
+ _isId: false,
407
+ _transform: undefined,
408
+ _fromSnapshotProcessor: undefined,
409
+ _toSnapshotProcessor: undefined,
410
+
411
+ withSetter(modeOrValueTransform?: SetterMode | ModelPropSetterValueTransform<unknown>) {
412
+ const obj: AnyModelProp = Object.create(this)
413
+ const setterConfig = parseSetterConfig(modeOrValueTransform)
414
+ obj._setter = setterConfig.mode
415
+ obj._setterValueTransform = setterConfig.valueTransform
416
+ return obj
417
+ },
418
+
419
+ withTransform(transform: ModelPropTransform<unknown, unknown>) {
420
+ const obj: AnyModelProp = Object.create(this)
421
+ obj._transform = composeFullTransforms(this._transform, toFullTransform(transform))
422
+ return obj
423
+ },
424
+
425
+ withSnapshotProcessor({ fromSnapshot, toSnapshot }) {
426
+ let newFromSnapshot: ((sn: any) => any) | undefined
427
+
428
+ if (this._fromSnapshotProcessor && fromSnapshot) {
429
+ const oldFn = this._fromSnapshotProcessor
430
+ const newFn = fromSnapshot
431
+ newFromSnapshot = (sn: any) => oldFn(newFn(sn))
432
+ } else if (fromSnapshot) {
433
+ newFromSnapshot = fromSnapshot
434
+ } else {
435
+ newFromSnapshot = this._fromSnapshotProcessor
436
+ }
437
+
438
+ let newToSnapshot: ((sn: any) => any) | undefined
439
+
440
+ if (this._toSnapshotProcessor && toSnapshot) {
441
+ const oldFn: (sn: unknown) => any = this._toSnapshotProcessor
442
+ const newFn = toSnapshot
443
+ newToSnapshot = (sn: unknown) => newFn(oldFn(sn))
444
+ } else if (toSnapshot) {
445
+ newToSnapshot = toSnapshot
446
+ } else {
447
+ newToSnapshot = this._toSnapshotProcessor
448
+ }
449
+
450
+ const obj: AnyModelProp = Object.create(this)
451
+ obj._fromSnapshotProcessor = newFromSnapshot
452
+ obj._toSnapshotProcessor = newToSnapshot
453
+
454
+ return obj
455
+ },
456
+ }
457
+
458
+ /**
459
+ * Defines a model property, with an optional function to generate a default value
460
+ * if the input snapshot / model creation data is `null` or `undefined`.
461
+ *
462
+ * Example:
463
+ * ```ts
464
+ * x: prop(() => 10) // an optional number, with a default value of 10
465
+ * x: prop<number[]>(() => []) // an optional number array, with a default empty array
466
+ * ```
467
+ *
468
+ * @template TValue Value type.
469
+ * @param defaultFn Default value generator function.
470
+ * @returns
471
+ */
472
+ export function prop<TValue>(defaultFn: () => TValue): OptionalModelProp<TValue>
473
+
474
+ /**
475
+ * Defines a model property, with an optional default value
476
+ * if the input snapshot / model creation data is `null` or `undefined`.
477
+ * You should only use this with primitive values and never with object values
478
+ * (array, model, object, etc).
479
+ *
480
+ * Example:
481
+ * ```ts
482
+ * x: prop(10) // an optional number, with a default value of 10
483
+ * ```
484
+ *
485
+ * @template TValue Value type.
486
+ * @param defaultValue Default primitive value.
487
+ * @returns
488
+ */
489
+ export function prop<TValue>(defaultValue: OnlyPrimitives<TValue>): OptionalModelProp<TValue>
490
+
491
+ /**
492
+ * Defines a model property with no default value.
493
+ *
494
+ * Example:
495
+ * ```ts
496
+ * x: prop<number>() // a required number
497
+ * x: prop<number | undefined>() // an optional number, which defaults to undefined
498
+ * ```
499
+ *
500
+ * @template TValue Value type.
501
+ * @returns
502
+ */
503
+ export function prop<TValue>(): MaybeOptionalModelProp<TValue>
504
+
505
+ // base
506
+ export function prop(...args: [] | [def: any]): AnyModelProp {
507
+ const hasDefaultValue = args.length > 0
508
+ if (!hasDefaultValue) {
509
+ return baseProp
510
+ }
511
+
512
+ const [def] = args
513
+
514
+ let p = propCache.get(def)
515
+
516
+ if (!p) {
517
+ p = Object.create(baseProp)
518
+
519
+ if (typeof def === "function") {
520
+ p!._defaultFn = def
521
+ } else {
522
+ p!._defaultValue = def
523
+ }
524
+
525
+ propCache.set(def, p!)
526
+ }
527
+
528
+ return p!
529
+ }
530
+
531
+ function parseSetterConfig(
532
+ modeOrValueTransform?: SetterMode | ModelPropSetterValueTransform<unknown>
533
+ ): {
534
+ mode: SetterMode
535
+ valueTransform: ((value: unknown) => unknown) | undefined
536
+ } {
537
+ if (typeof modeOrValueTransform === "function") {
538
+ return {
539
+ mode: true,
540
+ valueTransform: modeOrValueTransform,
541
+ }
542
+ }
543
+
544
+ if (
545
+ modeOrValueTransform === undefined ||
546
+ typeof modeOrValueTransform === "boolean" ||
547
+ modeOrValueTransform === "assign"
548
+ ) {
549
+ return {
550
+ mode: modeOrValueTransform ?? true,
551
+ valueTransform: undefined,
552
+ }
553
+ }
554
+
555
+ return {
556
+ mode: true,
557
+ valueTransform: undefined,
558
+ }
559
+ }
560
+
561
+ const propCache = new Map<unknown, AnyModelProp>()
562
+
563
+ let cacheTransformResult = false
564
+ const cacheTransformedValueFn = () => {
565
+ cacheTransformResult = true
566
+ }
567
+
568
+ function toFullTransform(transformObject: ModelPropTransform<unknown, unknown>) {
569
+ const cache = new WeakMap<
570
+ object,
571
+ Map<PropertyKey, { originalValue: unknown; transformedValue: unknown }>
572
+ >()
573
+
574
+ const transform = (params: {
575
+ originalValue: unknown
576
+ cachedTransformedValue: unknown
577
+ setOriginalValue(newOriginalValue: unknown): void
578
+ }) => (params.originalValue == null ? params.originalValue : transformObject.transform(params))
579
+
580
+ const untransform = (params: { transformedValue: unknown; cacheTransformedValue(): void }) =>
581
+ params.transformedValue == null ? params.transformedValue : transformObject.untransform(params)
582
+
583
+ return {
584
+ transform(
585
+ originalValue: unknown,
586
+ model: object,
587
+ propName: PropertyKey,
588
+ setOriginalValue: (newOriginalValue: unknown) => void
589
+ ) {
590
+ const modelCache = getOrCreate(cache, model, () => new Map())
591
+
592
+ let propCache = modelCache.get(propName)
593
+ if (propCache?.originalValue !== originalValue) {
594
+ // original changed, invalidate cache
595
+ modelCache.delete(propName)
596
+ propCache = undefined
597
+ }
598
+
599
+ const transformedValue = transform({
600
+ originalValue,
601
+ cachedTransformedValue: propCache?.transformedValue,
602
+ setOriginalValue,
603
+ })
604
+
605
+ modelCache.set(propName, {
606
+ originalValue,
607
+ transformedValue,
608
+ })
609
+
610
+ return transformedValue
611
+ },
612
+
613
+ untransform(transformedValue: unknown, model: object, propName: PropertyKey) {
614
+ const modelCache = getOrCreate(cache, model, () => new Map())
615
+
616
+ cacheTransformResult = false
617
+ const originalValue = untransform({
618
+ transformedValue,
619
+ cacheTransformedValue: cacheTransformedValueFn,
620
+ })
621
+ if (cacheTransformResult) {
622
+ modelCache.set(propName, { originalValue, transformedValue })
623
+ } else {
624
+ modelCache.delete(propName)
625
+ }
626
+
627
+ return originalValue
628
+ },
629
+ }
630
+ }
631
+
632
+ function composeFullTransforms(
633
+ outerTransform:
634
+ | {
635
+ transform(
636
+ originalValue: unknown,
637
+ model: object,
638
+ propName: PropertyKey,
639
+ setOriginalValue: (newOriginalValue: unknown) => void
640
+ ): unknown
641
+ untransform(transformedValue: unknown, model: object, propName: PropertyKey): unknown
642
+ }
643
+ | undefined,
644
+ innerTransform: {
645
+ transform(
646
+ originalValue: unknown,
647
+ model: object,
648
+ propName: PropertyKey,
649
+ setOriginalValue: (newOriginalValue: unknown) => void
650
+ ): unknown
651
+ untransform(transformedValue: unknown, model: object, propName: PropertyKey): unknown
652
+ }
653
+ ) {
654
+ if (!outerTransform) {
655
+ return innerTransform
656
+ }
657
+
658
+ return {
659
+ transform(
660
+ originalValue: unknown,
661
+ model: object,
662
+ propName: PropertyKey,
663
+ setOriginalValue: (newOriginalValue: unknown) => void
664
+ ) {
665
+ const outerValue = outerTransform.transform(originalValue, model, propName, setOriginalValue)
666
+ return innerTransform.transform(outerValue, model, propName, (newOuterValue) => {
667
+ setOriginalValue(outerTransform.untransform(newOuterValue, model, propName))
668
+ })
669
+ },
670
+
671
+ untransform(transformedValue: unknown, model: object, propName: PropertyKey) {
672
+ const outerValue = innerTransform.untransform(transformedValue, model, propName)
673
+ return outerTransform.untransform(outerValue, model, propName)
674
+ },
675
+ }
676
+ }
677
+
678
+ /**
679
+ * @ignore
680
+ */
681
+ export function getModelPropDefaultValue(propData: AnyModelProp): unknown | typeof noDefaultValue {
682
+ if (propData._defaultFn !== noDefaultValue) {
683
+ return propData._defaultFn()
684
+ }
685
+
686
+ if (propData._defaultValue !== noDefaultValue) {
687
+ return propData._defaultValue
688
+ }
689
+
690
+ return noDefaultValue
691
+ }
692
+
693
+ /**
694
+ * @ignore
695
+ */
696
+ export function getModelPropStoredDefaultValue(
697
+ propData: AnyModelProp,
698
+ model: object,
699
+ propName: string
700
+ ): unknown | typeof noDefaultValue {
701
+ const defaultValue = getModelPropDefaultValue(propData)
702
+ if (defaultValue === noDefaultValue || !propData._defaultValueIsTransformed) {
703
+ return defaultValue
704
+ }
705
+
706
+ return propData._transform
707
+ ? runWithErrorDiagnosticsContext(() =>
708
+ withErrorPathSegment(propName, () =>
709
+ propData._transform!.untransform(defaultValue, model, propName)
710
+ )
711
+ )
712
+ : defaultValue
713
+ }