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,417 +1,427 @@
1
- import { applySet } from "../action/applySet"
2
- import { getCurrentActionContext } from "../action/context"
3
- import { modelAction } from "../action/modelAction"
4
- import { AnyDataModel, BaseDataModel, baseDataModelPropNames } from "../dataModel/BaseDataModel"
5
- import type { DataModelConstructorOptions } from "../dataModel/DataModelConstructorOptions"
6
- import type { DataModelMetadata } from "../dataModel/getDataModelMetadata"
7
- import { getGlobalConfig } from "../globalConfig/globalConfig"
8
- import { AnyModel, BaseModel, baseModelPropNames } from "../model/BaseModel"
9
- import type { ModelMetadata } from "../model/getModelMetadata"
10
- import type { ModelConstructorOptions } from "../model/ModelConstructorOptions"
11
- import { modelTypeKey } from "../model/metadata"
12
- import { typesObject } from "../types/objectBased/typesObject"
13
- import { typesString } from "../types/primitiveBased/typesPrimitive"
14
- import type { AnyType } from "../types/schemas"
15
- import { tProp } from "../types/tProp"
16
- import { resolveStoredType } from "../types/utility/typesCodec"
17
- import { typesUnchecked } from "../types/utility/typesUnchecked"
18
- import { addHiddenProp, assertIsObject, failure, propNameToSetterName } from "../utils"
19
- import { chainFns } from "../utils/chainFns"
20
- import { withErrorPathSegment } from "../utils/errorDiagnostics"
21
- import { ModelClass, modelInitializedSymbol } from "./BaseModelShared"
22
- import { ModelClassInitializer, modelInitializersSymbol } from "./modelClassInitializer"
23
- import { getInternalModelClassPropsInfo, setInternalModelClassPropsInfo } from "./modelPropsInfo"
24
- import { modelMetadataSymbol, modelUnwrappedClassSymbol } from "./modelSymbols"
25
- import { AnyModelProp, getModelPropDefaultValue, ModelProps, noDefaultValue, prop } from "./prop"
26
- import { assertIsClassOrDataModelClass } from "./utils"
27
-
28
- function createGetModelInstanceDataField<M extends AnyModel | AnyDataModel>(
29
- modelProp: AnyModelProp,
30
- modelPropName: string
31
- ): (this: M) => unknown {
32
- const transformFn = modelProp._transform?.transform
33
-
34
- if (!transformFn) {
35
- // no need to use get since these vars always get on the initial $
36
- return function (this) {
37
- return this.$[modelPropName]
38
- }
39
- }
40
-
41
- const transformValue = (model: M, value: unknown) =>
42
- transformFn(value, model, modelPropName, (newValue) => {
43
- // use apply set instead to wrap it in an action
44
- // set the $ object to set the original value directly
45
- applySet(model.$, modelPropName, newValue)
46
- })
47
-
48
- return function (this) {
49
- // no need to use get since these vars always get on the initial $
50
- const value = this.$[modelPropName]
51
- return transformValue(this, value)
52
- }
53
- }
54
-
55
- type SetModelInstanceDataFieldFn = <M extends AnyModel | AnyDataModel>(
56
- modelProp: AnyModelProp,
57
- modelPropName: string,
58
- model: M,
59
- value: unknown
60
- ) => boolean | void
61
-
62
- const setModelInstanceDataField: SetModelInstanceDataFieldFn = (
63
- modelProp,
64
- modelPropName,
65
- model,
66
- value
67
- ): void => {
68
- if (modelProp._setter === "assign" && !getCurrentActionContext()) {
69
- // use apply set instead to wrap it in an action
70
- applySet(model, modelPropName as any, value)
71
- return
72
- }
73
-
74
- if (modelProp._setterValueTransform) {
75
- value = modelProp._setterValueTransform(value)
76
- }
77
-
78
- let untransformedValue = modelProp._transform
79
- ? withErrorPathSegment(modelPropName, () =>
80
- modelProp._transform!.untransform(value, model, modelPropName)
81
- )
82
- : value
83
-
84
- // apply default value if applicable
85
- if (untransformedValue == null) {
86
- const defaultValue = getModelPropDefaultValue(modelProp)
87
- if (defaultValue !== noDefaultValue) {
88
- untransformedValue = defaultValue
89
- }
90
- }
91
-
92
- // no need to use set since these vars always get on the initial $
93
- model.$[modelPropName] = untransformedValue
94
- }
95
-
96
- const setModelInstanceDataFieldWithPrecheck: SetModelInstanceDataFieldFn = (
97
- modelProp,
98
- modelPropName,
99
- model,
100
- value
101
- ): boolean => {
102
- // hack to only permit setting these values once fully constructed
103
- // this is to ignore abstract properties being set by babel
104
- // see https://github.com/xaviergonz/mobx-keystone/issues/18
105
- if (!(model as any)[modelInitializedSymbol]) {
106
- return false
107
- }
108
- setModelInstanceDataField(modelProp, modelPropName, model, value)
109
- return true
110
- }
111
-
112
- const defaultModelIdGenerator = () => getGlobalConfig().modelIdGenerator()
113
- const tPropForDefaultId = tProp(typesString, defaultModelIdGenerator)
114
- tPropForDefaultId._isId = true
115
- const propForDefaultId = prop(defaultModelIdGenerator)
116
- propForDefaultId._isId = true
117
-
118
- type FromSnapshotProcessorFn = (sn: any) => any
119
- type ToSnapshotProcessorFn = (sn: any, instance: any) => any
120
-
121
- export function sharedInternalModel<
122
- TProps extends ModelProps,
123
- TBaseModel extends AnyModel | AnyDataModel,
124
- >({
125
- modelProps,
126
- baseModel,
127
- type,
128
- valueType,
129
- fromSnapshotProcessor,
130
- toSnapshotProcessor,
131
- }: {
132
- modelProps: TProps
133
- baseModel: ModelClass<TBaseModel> | undefined
134
- type: "class" | "data"
135
- valueType: boolean
136
- fromSnapshotProcessor: FromSnapshotProcessorFn | undefined
137
- toSnapshotProcessor: ToSnapshotProcessorFn | undefined
138
- }): any {
139
- assertIsObject(modelProps, "modelProps")
140
-
141
- // make sure we avoid prototype pollution
142
- modelProps = Object.assign(Object.create(null), modelProps)
143
-
144
- if (baseModel) {
145
- assertIsClassOrDataModelClass(baseModel, "baseModel")
146
-
147
- // if the baseModel is wrapped with the model decorator get the original one
148
- const unwrappedClass = (baseModel as any)[modelUnwrappedClassSymbol]
149
- if (unwrappedClass) {
150
- baseModel = unwrappedClass
151
- assertIsClassOrDataModelClass(baseModel, "baseModel")
152
- }
153
- }
154
-
155
- const composedModelProps: ModelProps = modelProps
156
- let baseModelProps: ModelProps | undefined
157
- if (baseModel) {
158
- baseModelProps = getInternalModelClassPropsInfo(baseModel)
159
- for (const oldModelPropKey of Object.keys(baseModelProps)) {
160
- if (!modelProps[oldModelPropKey]) {
161
- composedModelProps[oldModelPropKey] = baseModelProps[oldModelPropKey]
162
- }
163
- }
164
- }
165
-
166
- // look for id keys
167
- const idKeys = Object.keys(composedModelProps).filter((k) => {
168
- const p = composedModelProps[k]
169
- return p._isId
170
- })
171
- if (type === "class") {
172
- if (idKeys.length > 1) {
173
- throw failure(`expected at most one idProp but got many: ${JSON.stringify(idKeys)}`)
174
- }
175
- } else if (idKeys.length > 0) {
176
- throw failure(`expected no idProp but got some: ${JSON.stringify(idKeys)}`)
177
- }
178
-
179
- const needsTypeChecker = Object.values(composedModelProps).some((mp) => !!mp._typeChecker)
180
-
181
- // transform id keys (only one really)
182
- let idKey: string | undefined
183
- if (idKeys.length > 0) {
184
- idKey = idKeys[0]
185
- const idProp = composedModelProps[idKey]
186
- const idPropGenerator = idProp._idGenerator
187
- const baseIdProp = baseModelProps?.[idKey]
188
- const baseIdPropGenerator = baseIdProp?._idGenerator
189
- const isOverridingBaseIdProp = !!baseIdProp?._isId && Object.hasOwn(modelProps, idKey)
190
- if (isOverridingBaseIdProp && baseIdPropGenerator !== idPropGenerator) {
191
- throw failure(
192
- `expected same idProp.withGenerator function when overriding a base idProp, but got different references`
193
- )
194
- }
195
- const resolvedIdGenerator = idPropGenerator ?? baseIdPropGenerator
196
- const effectiveModelIdGenerator = resolvedIdGenerator ?? defaultModelIdGenerator
197
- let baseProp: AnyModelProp
198
- if (effectiveModelIdGenerator === defaultModelIdGenerator) {
199
- baseProp = needsTypeChecker ? tPropForDefaultId : propForDefaultId
200
- } else {
201
- baseProp = needsTypeChecker
202
- ? tProp(typesString, effectiveModelIdGenerator)
203
- : prop(effectiveModelIdGenerator)
204
- baseProp._isId = true
205
- }
206
- if (idProp._setterValueTransform) {
207
- baseProp = baseProp.withSetter(idProp._setterValueTransform)
208
- } else {
209
- switch (idProp._setter) {
210
- case true:
211
- baseProp = baseProp.withSetter()
212
- break
213
- case "assign":
214
- baseProp = baseProp.withSetter("assign")
215
- break
216
- default:
217
- break
218
- }
219
- }
220
- baseProp._idGenerator = resolvedIdGenerator
221
- composedModelProps[idKey] = baseProp
222
- }
223
-
224
- // create type checker if needed
225
- let dataTypeChecker: AnyType | undefined
226
- if (needsTypeChecker) {
227
- const typeCheckerObj: {
228
- [k: string]: any
229
- } = {}
230
- for (const [k, mp] of Object.entries(composedModelProps)) {
231
- typeCheckerObj[k] = mp._typeChecker ? resolveStoredType(mp._typeChecker) : typesUnchecked()
232
- }
233
- dataTypeChecker = typesObject(() => typeCheckerObj)
234
- }
235
-
236
- const base: any = baseModel ?? (type === "class" ? BaseModel : BaseDataModel)
237
- const basePropNames = type === "class" ? baseModelPropNames : baseDataModelPropNames
238
-
239
- let propsToDeleteFromBase: string[] | undefined
240
-
241
- // we use this weird hack rather than just class CustomBaseModel extends base {}
242
- // in order to work around problems with ES5 classes extending ES6 classes
243
- // see https://github.com/xaviergonz/mobx-keystone/issues/15
244
- function ThisModel(
245
- this: any,
246
- initialData: any,
247
- constructorOptions?: ModelConstructorOptions | DataModelConstructorOptions
248
- ) {
249
- const modelClass = constructorOptions?.modelClass ?? this.constructor
250
- const baseModel = new base(initialData, {
251
- ...constructorOptions,
252
- modelClass,
253
- } as ModelConstructorOptions & DataModelConstructorOptions)
254
-
255
- // add prop, it is faster than having to go to the root of the prototype to not find it
256
- addHiddenProp(baseModel, modelInitializedSymbol, false, true)
257
-
258
- // make sure abstract classes do not override prototype props
259
- if (!propsToDeleteFromBase) {
260
- propsToDeleteFromBase = Object.keys(modelProps).filter(
261
- (p) => !basePropNames.has(p as any) && Object.hasOwn(baseModel, p)
262
- )
263
- }
264
-
265
- propsToDeleteFromBase.forEach((prop) => {
266
- delete baseModel[prop]
267
- })
268
-
269
- return baseModel
270
- }
271
-
272
- // copy static props from base
273
- Object.assign(ThisModel, base)
274
- delete (ThisModel as any)[modelUnwrappedClassSymbol]
275
-
276
- const initializers: ModelClassInitializer[] = base[modelInitializersSymbol]
277
- if (initializers) {
278
- ThisModel[modelInitializersSymbol] = initializers.slice()
279
- }
280
-
281
- setInternalModelClassPropsInfo(ThisModel as any, composedModelProps)
282
-
283
- if (type === "class") {
284
- const metadata: ModelMetadata = {
285
- dataType: dataTypeChecker,
286
- modelIdProperty: idKey,
287
- valueType,
288
- }
289
- ThisModel[modelMetadataSymbol] = metadata
290
- } else {
291
- const metadata: DataModelMetadata = {
292
- dataType: dataTypeChecker,
293
- }
294
- ThisModel[modelMetadataSymbol] = metadata
295
- }
296
-
297
- ThisModel.prototype = Object.create(base.prototype)
298
- ThisModel.prototype.constructor = ThisModel
299
-
300
- let setFn: SetModelInstanceDataFieldFn = (modelProp, modelPropName, model, value) => {
301
- if (setModelInstanceDataFieldWithPrecheck(modelProp, modelPropName, model, value)) {
302
- setFn = setModelInstanceDataField
303
- }
304
- }
305
-
306
- for (const [propName, propData] of Object.entries(modelProps)) {
307
- if (!(basePropNames as Set<string>).has(propName)) {
308
- const get = createGetModelInstanceDataField(propData, propName)
309
-
310
- Object.defineProperty(ThisModel.prototype, propName, {
311
- get,
312
- set(value: unknown) {
313
- setFn(propData, propName, this, value)
314
- },
315
- enumerable: true,
316
- configurable: false,
317
- })
318
- }
319
-
320
- if (propData._setter === true) {
321
- const setterName = propNameToSetterName(propName)
322
-
323
- if (!(basePropNames as Set<string>).has(setterName)) {
324
- const newPropDescriptor: any = modelAction(ThisModel.prototype, setterName, {
325
- value: function (this: any, value: any) {
326
- this[propName] = value
327
- },
328
- writable: true,
329
- enumerable: false,
330
- configurable: false,
331
- })
332
-
333
- Object.defineProperty(ThisModel.prototype, setterName, newPropDescriptor)
334
- }
335
- }
336
- }
337
-
338
- const modelPropsFromSnapshotProcessor = getModelPropsFromSnapshotProcessor(composedModelProps)
339
-
340
- const modelPropsToSnapshotProcessor = getModelPropsToSnapshotProcessor(composedModelProps)
341
-
342
- if (fromSnapshotProcessor) {
343
- const fn = fromSnapshotProcessor
344
- fromSnapshotProcessor = (sn) => {
345
- return {
346
- ...fn(sn),
347
- [modelTypeKey]: sn[modelTypeKey],
348
- }
349
- }
350
- }
351
-
352
- if (toSnapshotProcessor) {
353
- const fn = toSnapshotProcessor
354
- toSnapshotProcessor = (sn, modelInstance) => {
355
- if (modelInstance && !(modelInstance as any)[modelInitializedSymbol]) {
356
- return sn
357
- }
358
-
359
- return {
360
- ...fn(sn, modelInstance),
361
- [modelTypeKey]: sn[modelTypeKey],
362
- }
363
- }
364
- }
365
-
366
- ThisModel.fromSnapshotProcessor = chainFns(fromSnapshotProcessor, modelPropsFromSnapshotProcessor)
367
- ThisModel.toSnapshotProcessor = chainFns(modelPropsToSnapshotProcessor, toSnapshotProcessor)
368
-
369
- return ThisModel
370
- }
371
-
372
- function getModelPropsFromSnapshotProcessor(
373
- composedModelProps: ModelProps
374
- ): FromSnapshotProcessorFn | undefined {
375
- const propsWithFromSnapshotProcessor = Object.entries(composedModelProps).filter(
376
- ([_propName, propData]) => propData._fromSnapshotProcessor
377
- )
378
- if (propsWithFromSnapshotProcessor.length <= 0) {
379
- return undefined
380
- }
381
-
382
- return (sn) => {
383
- const newSn = { ...sn }
384
- for (const [propName, propData] of propsWithFromSnapshotProcessor) {
385
- if (propData._fromSnapshotProcessor) {
386
- newSn[propName] = withErrorPathSegment(propName, () =>
387
- propData._fromSnapshotProcessor!(sn[propName])
388
- )
389
- }
390
- }
391
- return newSn
392
- }
393
- }
394
-
395
- function getModelPropsToSnapshotProcessor(
396
- composedModelProps: ModelProps
397
- ): ToSnapshotProcessorFn | undefined {
398
- const propsWithToSnapshotProcessor = Object.entries(composedModelProps).filter(
399
- ([_propName, propData]) => propData._toSnapshotProcessor
400
- )
401
-
402
- if (propsWithToSnapshotProcessor.length <= 0) {
403
- return undefined
404
- }
405
-
406
- return (sn) => {
407
- const newSn = { ...sn }
408
- for (const [propName, propData] of propsWithToSnapshotProcessor) {
409
- if (propData._toSnapshotProcessor) {
410
- newSn[propName] = withErrorPathSegment(propName, () =>
411
- propData._toSnapshotProcessor!(sn[propName])
412
- )
413
- }
414
- }
415
- return newSn
416
- }
417
- }
1
+ import { applySet } from "../action/applySet"
2
+ import { getCurrentActionContext } from "../action/context"
3
+ import { modelAction } from "../action/modelAction"
4
+ import {
5
+ type AnyDataModel,
6
+ BaseDataModel,
7
+ baseDataModelPropNames,
8
+ } from "../dataModel/BaseDataModel"
9
+ import type { DataModelConstructorOptions } from "../dataModel/DataModelConstructorOptions"
10
+ import type { DataModelMetadata } from "../dataModel/getDataModelMetadata"
11
+ import { getGlobalConfig } from "../globalConfig/globalConfig"
12
+ import { type AnyModel, BaseModel, baseModelPropNames } from "../model/BaseModel"
13
+ import type { ModelMetadata } from "../model/getModelMetadata"
14
+ import type { ModelConstructorOptions } from "../model/ModelConstructorOptions"
15
+ import { modelTypeKey } from "../model/metadata"
16
+ import { typesObject } from "../types/objectBased/typesObject"
17
+ import { typesString } from "../types/primitiveBased/typesPrimitive"
18
+ import type { AnyType } from "../types/schemas"
19
+ import { tProp } from "../types/tProp"
20
+ import { resolveStoredType } from "../types/utility/typesCodec"
21
+ import { typesUnchecked } from "../types/utility/typesUnchecked"
22
+ import { addHiddenProp, assertIsObject, failure, propNameToSetterName } from "../utils"
23
+ import { chainFns } from "../utils/chainFns"
24
+ import { withErrorPathSegment } from "../utils/errorDiagnostics"
25
+ import { type ModelClass, modelInitializedSymbol } from "./BaseModelShared"
26
+ import { type ModelClassInitializer, modelInitializersSymbol } from "./modelClassInitializer"
27
+ import { getInternalModelClassPropsInfo, setInternalModelClassPropsInfo } from "./modelPropsInfo"
28
+ import { modelMetadataSymbol, modelUnwrappedClassSymbol } from "./modelSymbols"
29
+ import {
30
+ type AnyModelProp,
31
+ getModelPropStoredDefaultValue,
32
+ type ModelProps,
33
+ noDefaultValue,
34
+ prop,
35
+ } from "./prop"
36
+ import { assertIsClassOrDataModelClass } from "./utils"
37
+
38
+ function createGetModelInstanceDataField<M extends AnyModel | AnyDataModel>(
39
+ modelProp: AnyModelProp,
40
+ modelPropName: string
41
+ ): (this: M) => unknown {
42
+ const transformFn = modelProp._transform?.transform
43
+
44
+ if (!transformFn) {
45
+ // no need to use get since these vars always get on the initial $
46
+ return function (this) {
47
+ return this.$[modelPropName]
48
+ }
49
+ }
50
+
51
+ const transformValue = (model: M, value: unknown) =>
52
+ transformFn(value, model, modelPropName, (newValue) => {
53
+ // use apply set instead to wrap it in an action
54
+ // set the $ object to set the original value directly
55
+ applySet(model.$, modelPropName, newValue)
56
+ })
57
+
58
+ return function (this) {
59
+ // no need to use get since these vars always get on the initial $
60
+ const value = this.$[modelPropName]
61
+ return transformValue(this, value)
62
+ }
63
+ }
64
+
65
+ type SetModelInstanceDataFieldFn = <M extends AnyModel | AnyDataModel>(
66
+ modelProp: AnyModelProp,
67
+ modelPropName: string,
68
+ model: M,
69
+ value: unknown
70
+ ) => boolean | void
71
+
72
+ const setModelInstanceDataField: SetModelInstanceDataFieldFn = (
73
+ modelProp,
74
+ modelPropName,
75
+ model,
76
+ value
77
+ ): void => {
78
+ if (modelProp._setter === "assign" && !getCurrentActionContext()) {
79
+ // use apply set instead to wrap it in an action
80
+ applySet(model, modelPropName as any, value)
81
+ return
82
+ }
83
+
84
+ if (modelProp._setterValueTransform) {
85
+ value = modelProp._setterValueTransform(value)
86
+ }
87
+
88
+ let untransformedValue = modelProp._transform
89
+ ? withErrorPathSegment(modelPropName, () =>
90
+ modelProp._transform!.untransform(value, model, modelPropName)
91
+ )
92
+ : value
93
+
94
+ // apply default value if applicable
95
+ if (untransformedValue == null) {
96
+ const defaultValue = getModelPropStoredDefaultValue(modelProp, model, modelPropName)
97
+ if (defaultValue !== noDefaultValue) {
98
+ untransformedValue = defaultValue
99
+ }
100
+ }
101
+
102
+ // no need to use set since these vars always get on the initial $
103
+ model.$[modelPropName] = untransformedValue
104
+ }
105
+
106
+ const setModelInstanceDataFieldWithPrecheck: SetModelInstanceDataFieldFn = (
107
+ modelProp,
108
+ modelPropName,
109
+ model,
110
+ value
111
+ ): boolean => {
112
+ // hack to only permit setting these values once fully constructed
113
+ // this is to ignore abstract properties being set by babel
114
+ // see https://github.com/xaviergonz/mobx-keystone/issues/18
115
+ if (!(model as any)[modelInitializedSymbol]) {
116
+ return false
117
+ }
118
+ setModelInstanceDataField(modelProp, modelPropName, model, value)
119
+ return true
120
+ }
121
+
122
+ const defaultModelIdGenerator = () => getGlobalConfig().modelIdGenerator()
123
+ const tPropForDefaultId = tProp(typesString, defaultModelIdGenerator)
124
+ tPropForDefaultId._isId = true
125
+ const propForDefaultId = prop(defaultModelIdGenerator)
126
+ propForDefaultId._isId = true
127
+
128
+ type FromSnapshotProcessorFn = (sn: any) => any
129
+ type ToSnapshotProcessorFn = (sn: any, instance: any) => any
130
+
131
+ export function sharedInternalModel<
132
+ TProps extends ModelProps,
133
+ TBaseModel extends AnyModel | AnyDataModel,
134
+ >({
135
+ modelProps,
136
+ baseModel,
137
+ type,
138
+ valueType,
139
+ fromSnapshotProcessor,
140
+ toSnapshotProcessor,
141
+ }: {
142
+ modelProps: TProps
143
+ baseModel: ModelClass<TBaseModel> | undefined
144
+ type: "class" | "data"
145
+ valueType: boolean
146
+ fromSnapshotProcessor: FromSnapshotProcessorFn | undefined
147
+ toSnapshotProcessor: ToSnapshotProcessorFn | undefined
148
+ }): any {
149
+ assertIsObject(modelProps, "modelProps")
150
+
151
+ // make sure we avoid prototype pollution
152
+ modelProps = Object.assign(Object.create(null), modelProps)
153
+
154
+ if (baseModel) {
155
+ assertIsClassOrDataModelClass(baseModel, "baseModel")
156
+
157
+ // if the baseModel is wrapped with the model decorator get the original one
158
+ const unwrappedClass = (baseModel as any)[modelUnwrappedClassSymbol]
159
+ if (unwrappedClass) {
160
+ baseModel = unwrappedClass
161
+ assertIsClassOrDataModelClass(baseModel, "baseModel")
162
+ }
163
+ }
164
+
165
+ const composedModelProps: ModelProps = modelProps
166
+ let baseModelProps: ModelProps | undefined
167
+ if (baseModel) {
168
+ baseModelProps = getInternalModelClassPropsInfo(baseModel)
169
+ for (const oldModelPropKey of Object.keys(baseModelProps)) {
170
+ if (!modelProps[oldModelPropKey]) {
171
+ composedModelProps[oldModelPropKey] = baseModelProps[oldModelPropKey]
172
+ }
173
+ }
174
+ }
175
+
176
+ // look for id keys
177
+ const idKeys = Object.keys(composedModelProps).filter((k) => {
178
+ const p = composedModelProps[k]
179
+ return p._isId
180
+ })
181
+ if (type === "class") {
182
+ if (idKeys.length > 1) {
183
+ throw failure(`expected at most one idProp but got many: ${JSON.stringify(idKeys)}`)
184
+ }
185
+ } else if (idKeys.length > 0) {
186
+ throw failure(`expected no idProp but got some: ${JSON.stringify(idKeys)}`)
187
+ }
188
+
189
+ const needsTypeChecker = Object.values(composedModelProps).some((mp) => !!mp._typeChecker)
190
+
191
+ // transform id keys (only one really)
192
+ let idKey: string | undefined
193
+ if (idKeys.length > 0) {
194
+ idKey = idKeys[0]
195
+ const idProp = composedModelProps[idKey]
196
+ const idPropGenerator = idProp._idGenerator
197
+ const baseIdProp = baseModelProps?.[idKey]
198
+ const baseIdPropGenerator = baseIdProp?._idGenerator
199
+ const isOverridingBaseIdProp = !!baseIdProp?._isId && Object.hasOwn(modelProps, idKey)
200
+ if (isOverridingBaseIdProp && baseIdPropGenerator !== idPropGenerator) {
201
+ throw failure(
202
+ `expected same idProp.withGenerator function when overriding a base idProp, but got different references`
203
+ )
204
+ }
205
+ const resolvedIdGenerator = idPropGenerator ?? baseIdPropGenerator
206
+ const effectiveModelIdGenerator = resolvedIdGenerator ?? defaultModelIdGenerator
207
+ let baseProp: AnyModelProp
208
+ if (effectiveModelIdGenerator === defaultModelIdGenerator) {
209
+ baseProp = needsTypeChecker ? tPropForDefaultId : propForDefaultId
210
+ } else {
211
+ baseProp = needsTypeChecker
212
+ ? tProp(typesString, effectiveModelIdGenerator)
213
+ : prop(effectiveModelIdGenerator)
214
+ baseProp._isId = true
215
+ }
216
+ if (idProp._setterValueTransform) {
217
+ baseProp = baseProp.withSetter(idProp._setterValueTransform)
218
+ } else {
219
+ switch (idProp._setter) {
220
+ case true:
221
+ baseProp = baseProp.withSetter()
222
+ break
223
+ case "assign":
224
+ baseProp = baseProp.withSetter("assign")
225
+ break
226
+ default:
227
+ break
228
+ }
229
+ }
230
+ baseProp._idGenerator = resolvedIdGenerator
231
+ composedModelProps[idKey] = baseProp
232
+ }
233
+
234
+ // create type checker if needed
235
+ let dataTypeChecker: AnyType | undefined
236
+ if (needsTypeChecker) {
237
+ const typeCheckerObj: {
238
+ [k: string]: any
239
+ } = {}
240
+ for (const [k, mp] of Object.entries(composedModelProps)) {
241
+ typeCheckerObj[k] = mp._typeChecker ? resolveStoredType(mp._typeChecker) : typesUnchecked()
242
+ }
243
+ dataTypeChecker = typesObject(() => typeCheckerObj)
244
+ }
245
+
246
+ const base: any = baseModel ?? (type === "class" ? BaseModel : BaseDataModel)
247
+ const basePropNames = type === "class" ? baseModelPropNames : baseDataModelPropNames
248
+
249
+ let propsToDeleteFromBase: string[] | undefined
250
+
251
+ // we use this weird hack rather than just class CustomBaseModel extends base {}
252
+ // in order to work around problems with ES5 classes extending ES6 classes
253
+ // see https://github.com/xaviergonz/mobx-keystone/issues/15
254
+ function ThisModel(
255
+ this: any,
256
+ initialData: any,
257
+ constructorOptions?: ModelConstructorOptions | DataModelConstructorOptions
258
+ ) {
259
+ const modelClass = constructorOptions?.modelClass ?? this.constructor
260
+ const baseModel = new base(initialData, {
261
+ ...constructorOptions,
262
+ modelClass,
263
+ } as ModelConstructorOptions & DataModelConstructorOptions)
264
+
265
+ // add prop, it is faster than having to go to the root of the prototype to not find it
266
+ addHiddenProp(baseModel, modelInitializedSymbol, false, true)
267
+
268
+ // make sure abstract classes do not override prototype props
269
+ if (!propsToDeleteFromBase) {
270
+ propsToDeleteFromBase = Object.keys(modelProps).filter(
271
+ (p) => !basePropNames.has(p as any) && Object.hasOwn(baseModel, p)
272
+ )
273
+ }
274
+
275
+ propsToDeleteFromBase.forEach((prop) => {
276
+ delete baseModel[prop]
277
+ })
278
+
279
+ return baseModel
280
+ }
281
+
282
+ // copy static props from base
283
+ Object.assign(ThisModel, base)
284
+ delete (ThisModel as any)[modelUnwrappedClassSymbol]
285
+
286
+ const initializers: ModelClassInitializer[] = base[modelInitializersSymbol]
287
+ if (initializers) {
288
+ ThisModel[modelInitializersSymbol] = initializers.slice()
289
+ }
290
+
291
+ setInternalModelClassPropsInfo(ThisModel as any, composedModelProps)
292
+
293
+ if (type === "class") {
294
+ const metadata: ModelMetadata = {
295
+ dataType: dataTypeChecker,
296
+ modelIdProperty: idKey,
297
+ valueType,
298
+ }
299
+ ThisModel[modelMetadataSymbol] = metadata
300
+ } else {
301
+ const metadata: DataModelMetadata = {
302
+ dataType: dataTypeChecker,
303
+ }
304
+ ThisModel[modelMetadataSymbol] = metadata
305
+ }
306
+
307
+ ThisModel.prototype = Object.create(base.prototype)
308
+ ThisModel.prototype.constructor = ThisModel
309
+
310
+ let setFn: SetModelInstanceDataFieldFn = (modelProp, modelPropName, model, value) => {
311
+ if (setModelInstanceDataFieldWithPrecheck(modelProp, modelPropName, model, value)) {
312
+ setFn = setModelInstanceDataField
313
+ }
314
+ }
315
+
316
+ for (const [propName, propData] of Object.entries(modelProps)) {
317
+ if (!(basePropNames as Set<string>).has(propName)) {
318
+ const get = createGetModelInstanceDataField(propData, propName)
319
+
320
+ Object.defineProperty(ThisModel.prototype, propName, {
321
+ get,
322
+ set(value: unknown) {
323
+ setFn(propData, propName, this, value)
324
+ },
325
+ enumerable: true,
326
+ configurable: false,
327
+ })
328
+ }
329
+
330
+ if (propData._setter === true) {
331
+ const setterName = propNameToSetterName(propName)
332
+
333
+ if (!(basePropNames as Set<string>).has(setterName)) {
334
+ const newPropDescriptor: any = modelAction(ThisModel.prototype, setterName, {
335
+ value: function (this: any, value: any) {
336
+ this[propName] = value
337
+ },
338
+ writable: true,
339
+ enumerable: false,
340
+ configurable: false,
341
+ })
342
+
343
+ Object.defineProperty(ThisModel.prototype, setterName, newPropDescriptor)
344
+ }
345
+ }
346
+ }
347
+
348
+ const modelPropsFromSnapshotProcessor = getModelPropsFromSnapshotProcessor(composedModelProps)
349
+
350
+ const modelPropsToSnapshotProcessor = getModelPropsToSnapshotProcessor(composedModelProps)
351
+
352
+ if (fromSnapshotProcessor) {
353
+ const fn = fromSnapshotProcessor
354
+ fromSnapshotProcessor = (sn) => {
355
+ return {
356
+ ...fn(sn),
357
+ [modelTypeKey]: sn[modelTypeKey],
358
+ }
359
+ }
360
+ }
361
+
362
+ if (toSnapshotProcessor) {
363
+ const fn = toSnapshotProcessor
364
+ toSnapshotProcessor = (sn, modelInstance) => {
365
+ if (modelInstance && !(modelInstance as any)[modelInitializedSymbol]) {
366
+ return sn
367
+ }
368
+
369
+ return {
370
+ ...fn(sn, modelInstance),
371
+ [modelTypeKey]: sn[modelTypeKey],
372
+ }
373
+ }
374
+ }
375
+
376
+ ThisModel.fromSnapshotProcessor = chainFns(fromSnapshotProcessor, modelPropsFromSnapshotProcessor)
377
+ ThisModel.toSnapshotProcessor = chainFns(modelPropsToSnapshotProcessor, toSnapshotProcessor)
378
+
379
+ return ThisModel
380
+ }
381
+
382
+ function getModelPropsFromSnapshotProcessor(
383
+ composedModelProps: ModelProps
384
+ ): FromSnapshotProcessorFn | undefined {
385
+ const propsWithFromSnapshotProcessor = Object.entries(composedModelProps).filter(
386
+ ([_propName, propData]) => propData._fromSnapshotProcessor
387
+ )
388
+ if (propsWithFromSnapshotProcessor.length <= 0) {
389
+ return undefined
390
+ }
391
+
392
+ return (sn) => {
393
+ const newSn = { ...sn }
394
+ for (const [propName, propData] of propsWithFromSnapshotProcessor) {
395
+ if (propData._fromSnapshotProcessor) {
396
+ newSn[propName] = withErrorPathSegment(propName, () =>
397
+ propData._fromSnapshotProcessor!(sn[propName])
398
+ )
399
+ }
400
+ }
401
+ return newSn
402
+ }
403
+ }
404
+
405
+ function getModelPropsToSnapshotProcessor(
406
+ composedModelProps: ModelProps
407
+ ): ToSnapshotProcessorFn | undefined {
408
+ const propsWithToSnapshotProcessor = Object.entries(composedModelProps).filter(
409
+ ([_propName, propData]) => propData._toSnapshotProcessor
410
+ )
411
+
412
+ if (propsWithToSnapshotProcessor.length <= 0) {
413
+ return undefined
414
+ }
415
+
416
+ return (sn) => {
417
+ const newSn = { ...sn }
418
+ for (const [propName, propData] of propsWithToSnapshotProcessor) {
419
+ if (propData._toSnapshotProcessor) {
420
+ newSn[propName] = withErrorPathSegment(propName, () =>
421
+ propData._toSnapshotProcessor!(sn[propName])
422
+ )
423
+ }
424
+ }
425
+ return newSn
426
+ }
427
+ }