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,288 +1,288 @@
1
- import {
2
- type AbstractModelClass,
3
- type ModelClass,
4
- modelClass,
5
- propsTypeSymbol,
6
- } from "../modelShared/BaseModelShared"
7
- import type { ModelProps, ModelPropsToTransformedData } from "../modelShared/prop"
8
- import type { AnyModel, BaseModelKeys } from "./BaseModel"
9
- import { ExtendedModel, Model } from "./Model"
10
- import { isModelClass } from "./utils"
11
-
12
- type ReqObj<Req> = [unknown] extends [Req] ? unknown : Req extends object ? Req : never
13
-
14
- const reqMarkerBrand = Symbol("reqMarkerBrand")
15
-
16
- /**
17
- * Typed requirement marker created by `req<Req>()`.
18
- * Pass it as the second argument to `defineModelMixin` to declare that the base model must already
19
- * contain the `Req` shape before the mixin is applied.
20
- */
21
- export type ReqMarker<Req extends object> = { readonly [reqMarkerBrand]: Req }
22
-
23
- const reqMarkerSingleton = { [reqMarkerBrand]: undefined } as unknown as ReqMarker<any>
24
-
25
- /**
26
- * Creates a typed requirement marker for `defineModelMixin`.
27
- *
28
- * @template Req Shape that must exist on the base model instance.
29
- *
30
- * @example
31
- * const producerMixin = defineModelMixin(
32
- * { produced: tProp(types.number, 0) },
33
- * req<{ quantity: number }>()
34
- * )
35
- */
36
- export function req<Req extends object>(): ReqMarker<Req> {
37
- return reqMarkerSingleton
38
- }
39
-
40
- function isReqMarker(v: unknown): v is ReqMarker<any> {
41
- return typeof v === "object" && v !== null && (reqMarkerBrand as any) in v
42
- }
43
-
44
- /**
45
- * Mixin function type for model classes.
46
- *
47
- * @template Added Shape added by the mixin.
48
- * @template Req Shape that must exist in the input model instance.
49
- * @template ExtraProps ModelProps to merge into the class's propsTypeSymbol marker. When using
50
- * `defineModelMixin`, this is always set to the real `ModelProps` object so that `ModelData` /
51
- * `ModelCreationData` resolve to the exact prop types.
52
- */
53
- export type ModelMixin<
54
- Added extends object,
55
- Req = unknown,
56
- ExtraProps extends ModelProps = ModelProps,
57
- > = <B extends ModelClass<AnyModel & ReqObj<Req>>>(
58
- base: B
59
- ) => ModelClass<InstanceType<B> & Added & { [propsTypeSymbol]: ExtraProps }>
60
-
61
- type MixinAdded<M extends ModelMixin<any, any, any>> =
62
- M extends ModelMixin<infer Added, any, any> ? Added : never
63
- type MixinReq<M extends ModelMixin<any, any, any>> =
64
- M extends ModelMixin<any, infer Req, any> ? ReqObj<Req> : never
65
- type MixinExtraProps<M extends ModelMixin<any, any, any>> =
66
- M extends ModelMixin<any, any, infer P> ? P : never
67
- type ApplyMixin<B extends ModelClass<AnyModel>, M extends ModelMixin<any, any, any>> = ModelClass<
68
- InstanceType<B> & MixinAdded<M> & { [propsTypeSymbol]: MixinExtraProps<M> }
69
- >
70
-
71
- type ApplyMixins<
72
- B extends ModelClass<AnyModel>,
73
- M extends readonly ModelMixin<any, any, any>[],
74
- > = M extends readonly [infer First, ...infer Rest]
75
- ? First extends ModelMixin<any, any, any>
76
- ? ApplyMixins<ApplyMixin<B, First>, Extract<Rest, readonly ModelMixin<any, any, any>[]>>
77
- : never
78
- : B
79
-
80
- /**
81
- * Computes the resulting model class after applying a mixin tuple to a base model class.
82
- */
83
- export type ComposedModelClass<
84
- B extends ModelClass<AnyModel>,
85
- M extends readonly ModelMixin<any, any, any>[],
86
- > = ApplyMixins<B, M>
87
-
88
- /**
89
- * Computes the resulting model instance type after applying a mixin tuple to a base model class.
90
- */
91
- export type ComposedModelInstance<
92
- B extends ModelClass<AnyModel>,
93
- M extends readonly ModelMixin<any, any, any>[],
94
- > = InstanceType<ComposedModelClass<B, M>>
95
-
96
- type ValidateMixins<
97
- B extends ModelClass<AnyModel>,
98
- M extends readonly ModelMixin<any, any, any>[],
99
- > = M extends readonly [infer First, ...infer Rest]
100
- ? First extends ModelMixin<any, any, any>
101
- ? InstanceType<B> extends AnyModel & MixinReq<First>
102
- ? readonly [
103
- First,
104
- ...ValidateMixins<
105
- ApplyMixin<B, First>,
106
- Extract<Rest, readonly ModelMixin<any, any, any>[]>
107
- >,
108
- ]
109
- : never
110
- : never
111
- : readonly []
112
-
113
- /**
114
- * Defines a model mixin from a `ModelProps` object.
115
- *
116
- * `ModelData` / `ModelCreationData` on the composed class resolve to the exact prop types.
117
- *
118
- * @template MP Model properties type (inferred from `props`).
119
- * @param props Model properties object.
120
- *
121
- * @example
122
- * const countableMixin = defineModelMixin({ quantity: tProp(types.number, 0) })
123
- */
124
- export function defineModelMixin<MP extends ModelProps>(
125
- props: MP
126
- ): ModelMixin<ModelPropsToTransformedData<MP>, unknown, MP>
127
-
128
- /**
129
- * Defines a model mixin from a `ModelProps` object, with a requirement on the base model.
130
- *
131
- * Pass `req<Req>()` as the second argument to declare that the base model must already contain the
132
- * `Req` shape before the mixin is applied.
133
- *
134
- * @template Req Shape that must exist on the base model instance (specified via `req<Req>()`).
135
- * @template MP Model properties type (inferred from `props`).
136
- * @param props Model properties object.
137
- * @param requirement Requirement marker created by `req<Req>()`.
138
- *
139
- * @example
140
- * const producerMixin = defineModelMixin(
141
- * { produced: tProp(types.number, 0) },
142
- * req<{ quantity: number }>()
143
- * )
144
- */
145
- export function defineModelMixin<Req extends object, MP extends ModelProps>(
146
- props: MP,
147
- requirement: ReqMarker<Req>
148
- ): ModelMixin<ModelPropsToTransformedData<MP>, Req, MP>
149
-
150
- /**
151
- * Defines a model mixin from a `ModelProps` object and a builder that can add actions and other
152
- * methods.
153
- *
154
- * The props are applied via `ExtendedModel` first; the resulting pre-extended class is passed to
155
- * the builder so you can simply `extend Base` without calling `ExtendedModel` yourself.
156
- * `ModelData` / `ModelCreationData` resolve to the exact prop types from `MP`.
157
- *
158
- * @template MP Model properties type (inferred from `props`).
159
- * @template C Built class type (inferred from `build` return type).
160
- * @param props Model properties object.
161
- * @param build Builder receiving the pre-extended base class.
162
- *
163
- * @example
164
- * const countableMixin = defineModelMixin(
165
- * { quantity: tProp(types.number, 0) },
166
- * (Base) => class Countable extends Base {
167
- * increment() { this.quantity++ }
168
- * }
169
- * )
170
- */
171
- export function defineModelMixin<
172
- MP extends ModelProps,
173
- C extends AbstractModelClass<AnyModel & ModelPropsToTransformedData<MP>>,
174
- >(
175
- props: MP,
176
- build: (base: AbstractModelClass<AnyModel & ModelPropsToTransformedData<MP>>) => C
177
- ): ModelMixin<Omit<InstanceType<C>, BaseModelKeys>, unknown, MP>
178
-
179
- /**
180
- * Defines a model mixin from a `ModelProps` object and a builder, with a requirement on the base
181
- * model.
182
- *
183
- * Pass `req<Req>()` as the second argument to declare that the base model must already contain the
184
- * `Req` shape. The `Base` class received by the builder includes both the prop types and `Req`.
185
- *
186
- * @template Req Shape that must exist on the base model instance (specified via `req<Req>()`).
187
- * @template MP Model properties type (inferred from `props`).
188
- * @template C Built class type (inferred from `build` return type).
189
- * @param props Model properties object.
190
- * @param requirement Requirement marker created by `req<Req>()`.
191
- * @param build Builder receiving the pre-extended base class (typed with both props and `Req`).
192
- *
193
- * @example
194
- * const producerMixin = defineModelMixin(
195
- * { produced: tProp(types.number, 0) },
196
- * req<{ quantity: number }>(),
197
- * (Base) => class Producer extends Base {
198
- * produceTotal() { return this.produced + this.quantity }
199
- * }
200
- * )
201
- */
202
- export function defineModelMixin<
203
- Req extends object,
204
- MP extends ModelProps,
205
- C extends AbstractModelClass<AnyModel & ModelPropsToTransformedData<MP>>,
206
- >(
207
- props: MP,
208
- requirement: ReqMarker<Req>,
209
- build: (base: AbstractModelClass<AnyModel & ModelPropsToTransformedData<MP> & Req>) => C
210
- ): ModelMixin<Omit<InstanceType<C>, BaseModelKeys>, Req, MP>
211
-
212
- // implementation
213
- export function defineModelMixin(...args: any[]): any {
214
- const props: ModelProps = args[0]
215
- let build: ((base: AbstractModelClass<AnyModel>) => AbstractModelClass<AnyModel>) | undefined
216
-
217
- if (args.length === 3) {
218
- // (props, req, build)
219
- build = args[2]
220
- } else if (args.length === 2 && !isReqMarker(args[1])) {
221
- // (props, build)
222
- build = args[1]
223
- }
224
- // else: (props) or (props, req) — build stays undefined
225
-
226
- return (<B extends ModelClass<AnyModel>>(base: B) => {
227
- const extended = ExtendedModel(modelClass(base) as any, props) as AbstractModelClass<AnyModel>
228
- return (build ? build(extended) : extended) as unknown as ModelClass<InstanceType<B>>
229
- }) as ModelMixin<any, any, any>
230
- }
231
-
232
- // Empty base used when composeMixins is called without an explicit base class.
233
- class MixinEmptyBase extends Model({}) {}
234
-
235
- /**
236
- * Composes several model mixins without an explicit base model class.
237
- *
238
- * An implicit empty base (`Model({})`) is used. Useful for bundling mixins to reuse across
239
- * multiple concrete classes:
240
- *
241
- * ```ts
242
- * const ProductBase = composeMixins(countableMixin, producerMixin)
243
- *
244
- * @model("myApp/Product")
245
- * class Product extends ExtendedModel(ProductBase, {}) {}
246
- * ```
247
- *
248
- * @template M Mixin tuple type.
249
- * @param mixins Mixins to apply.
250
- * @returns A model class with all mixin additions applied over an empty base.
251
- */
252
- export function composeMixins<M extends readonly ModelMixin<any, any, any>[]>(
253
- ...mixins: M & ValidateMixins<typeof MixinEmptyBase, M>
254
- ): ComposedModelClass<typeof MixinEmptyBase, M>
255
-
256
- /**
257
- * Composes several model mixins over a base model class.
258
- *
259
- * @template B Base model class.
260
- * @template M Mixin tuple type.
261
- * @param base Base model class.
262
- * @param mixins Mixins to apply.
263
- * @returns A model class with all mixin additions applied.
264
- */
265
- export function composeMixins<
266
- B extends ModelClass<AnyModel>,
267
- M extends readonly ModelMixin<any, any, any>[],
268
- >(base: B, ...mixins: M & ValidateMixins<B, M>): ComposedModelClass<B, M>
269
-
270
- // implementation
271
- export function composeMixins(...args: any[]): any {
272
- let base: ModelClass<AnyModel>
273
- let mixins: ModelMixin<any, any, any>[]
274
-
275
- if (isModelClass(args[0])) {
276
- base = args[0]
277
- mixins = args.slice(1)
278
- } else {
279
- base = MixinEmptyBase
280
- mixins = args
281
- }
282
-
283
- let current = base
284
- for (const mixin of mixins) {
285
- current = mixin(current)
286
- }
287
- return current
288
- }
1
+ import {
2
+ type AbstractModelClass,
3
+ type ModelClass,
4
+ modelClass,
5
+ propsTypeSymbol,
6
+ } from "../modelShared/BaseModelShared"
7
+ import type { ModelProps, ModelPropsToTransformedData } from "../modelShared/prop"
8
+ import type { AnyModel, BaseModelKeys } from "./BaseModel"
9
+ import { ExtendedModel, Model } from "./Model"
10
+ import { isModelClass } from "./utils"
11
+
12
+ type ReqObj<Req> = [unknown] extends [Req] ? unknown : Req extends object ? Req : never
13
+
14
+ const reqMarkerBrand = Symbol("reqMarkerBrand")
15
+
16
+ /**
17
+ * Typed requirement marker created by `req<Req>()`.
18
+ * Pass it as the second argument to `defineModelMixin` to declare that the base model must already
19
+ * contain the `Req` shape before the mixin is applied.
20
+ */
21
+ export type ReqMarker<Req extends object> = { readonly [reqMarkerBrand]: Req }
22
+
23
+ const reqMarkerSingleton = { [reqMarkerBrand]: undefined } as unknown as ReqMarker<any>
24
+
25
+ /**
26
+ * Creates a typed requirement marker for `defineModelMixin`.
27
+ *
28
+ * @template Req Shape that must exist on the base model instance.
29
+ *
30
+ * @example
31
+ * const producerMixin = defineModelMixin(
32
+ * { produced: tProp(types.number, 0) },
33
+ * req<{ quantity: number }>()
34
+ * )
35
+ */
36
+ export function req<Req extends object>(): ReqMarker<Req> {
37
+ return reqMarkerSingleton
38
+ }
39
+
40
+ function isReqMarker(v: unknown): v is ReqMarker<any> {
41
+ return typeof v === "object" && v !== null && (reqMarkerBrand as any) in v
42
+ }
43
+
44
+ /**
45
+ * Mixin function type for model classes.
46
+ *
47
+ * @template Added Shape added by the mixin.
48
+ * @template Req Shape that must exist in the input model instance.
49
+ * @template ExtraProps ModelProps to merge into the class's propsTypeSymbol marker. When using
50
+ * `defineModelMixin`, this is always set to the real `ModelProps` object so that `ModelData` /
51
+ * `ModelCreationData` resolve to the exact prop types.
52
+ */
53
+ export type ModelMixin<
54
+ Added extends object,
55
+ Req = unknown,
56
+ ExtraProps extends ModelProps = ModelProps,
57
+ > = <B extends ModelClass<AnyModel & ReqObj<Req>>>(
58
+ base: B
59
+ ) => ModelClass<InstanceType<B> & Added & { [propsTypeSymbol]: ExtraProps }>
60
+
61
+ type MixinAdded<M extends ModelMixin<any, any, any>> =
62
+ M extends ModelMixin<infer Added, any, any> ? Added : never
63
+ type MixinReq<M extends ModelMixin<any, any, any>> =
64
+ M extends ModelMixin<any, infer Req, any> ? ReqObj<Req> : never
65
+ type MixinExtraProps<M extends ModelMixin<any, any, any>> =
66
+ M extends ModelMixin<any, any, infer P> ? P : never
67
+ type ApplyMixin<B extends ModelClass<AnyModel>, M extends ModelMixin<any, any, any>> = ModelClass<
68
+ InstanceType<B> & MixinAdded<M> & { [propsTypeSymbol]: MixinExtraProps<M> }
69
+ >
70
+
71
+ type ApplyMixins<
72
+ B extends ModelClass<AnyModel>,
73
+ M extends readonly ModelMixin<any, any, any>[],
74
+ > = M extends readonly [infer First, ...infer Rest]
75
+ ? First extends ModelMixin<any, any, any>
76
+ ? ApplyMixins<ApplyMixin<B, First>, Extract<Rest, readonly ModelMixin<any, any, any>[]>>
77
+ : never
78
+ : B
79
+
80
+ /**
81
+ * Computes the resulting model class after applying a mixin tuple to a base model class.
82
+ */
83
+ export type ComposedModelClass<
84
+ B extends ModelClass<AnyModel>,
85
+ M extends readonly ModelMixin<any, any, any>[],
86
+ > = ApplyMixins<B, M>
87
+
88
+ /**
89
+ * Computes the resulting model instance type after applying a mixin tuple to a base model class.
90
+ */
91
+ export type ComposedModelInstance<
92
+ B extends ModelClass<AnyModel>,
93
+ M extends readonly ModelMixin<any, any, any>[],
94
+ > = InstanceType<ComposedModelClass<B, M>>
95
+
96
+ type ValidateMixins<
97
+ B extends ModelClass<AnyModel>,
98
+ M extends readonly ModelMixin<any, any, any>[],
99
+ > = M extends readonly [infer First, ...infer Rest]
100
+ ? First extends ModelMixin<any, any, any>
101
+ ? InstanceType<B> extends AnyModel & MixinReq<First>
102
+ ? readonly [
103
+ First,
104
+ ...ValidateMixins<
105
+ ApplyMixin<B, First>,
106
+ Extract<Rest, readonly ModelMixin<any, any, any>[]>
107
+ >,
108
+ ]
109
+ : never
110
+ : never
111
+ : readonly []
112
+
113
+ /**
114
+ * Defines a model mixin from a `ModelProps` object.
115
+ *
116
+ * `ModelData` / `ModelCreationData` on the composed class resolve to the exact prop types.
117
+ *
118
+ * @template MP Model properties type (inferred from `props`).
119
+ * @param props Model properties object.
120
+ *
121
+ * @example
122
+ * const countableMixin = defineModelMixin({ quantity: tProp(types.number, 0) })
123
+ */
124
+ export function defineModelMixin<MP extends ModelProps>(
125
+ props: MP
126
+ ): ModelMixin<ModelPropsToTransformedData<MP>, unknown, MP>
127
+
128
+ /**
129
+ * Defines a model mixin from a `ModelProps` object, with a requirement on the base model.
130
+ *
131
+ * Pass `req<Req>()` as the second argument to declare that the base model must already contain the
132
+ * `Req` shape before the mixin is applied.
133
+ *
134
+ * @template Req Shape that must exist on the base model instance (specified via `req<Req>()`).
135
+ * @template MP Model properties type (inferred from `props`).
136
+ * @param props Model properties object.
137
+ * @param requirement Requirement marker created by `req<Req>()`.
138
+ *
139
+ * @example
140
+ * const producerMixin = defineModelMixin(
141
+ * { produced: tProp(types.number, 0) },
142
+ * req<{ quantity: number }>()
143
+ * )
144
+ */
145
+ export function defineModelMixin<Req extends object, MP extends ModelProps>(
146
+ props: MP,
147
+ requirement: ReqMarker<Req>
148
+ ): ModelMixin<ModelPropsToTransformedData<MP>, Req, MP>
149
+
150
+ /**
151
+ * Defines a model mixin from a `ModelProps` object and a builder that can add actions and other
152
+ * methods.
153
+ *
154
+ * The props are applied via `ExtendedModel` first; the resulting pre-extended class is passed to
155
+ * the builder so you can simply `extend Base` without calling `ExtendedModel` yourself.
156
+ * `ModelData` / `ModelCreationData` resolve to the exact prop types from `MP`.
157
+ *
158
+ * @template MP Model properties type (inferred from `props`).
159
+ * @template C Built class type (inferred from `build` return type).
160
+ * @param props Model properties object.
161
+ * @param build Builder receiving the pre-extended base class.
162
+ *
163
+ * @example
164
+ * const countableMixin = defineModelMixin(
165
+ * { quantity: tProp(types.number, 0) },
166
+ * (Base) => class Countable extends Base {
167
+ * increment() { this.quantity++ }
168
+ * }
169
+ * )
170
+ */
171
+ export function defineModelMixin<
172
+ MP extends ModelProps,
173
+ C extends AbstractModelClass<AnyModel & ModelPropsToTransformedData<MP>>,
174
+ >(
175
+ props: MP,
176
+ build: (base: AbstractModelClass<AnyModel & ModelPropsToTransformedData<MP>>) => C
177
+ ): ModelMixin<Omit<InstanceType<C>, BaseModelKeys>, unknown, MP>
178
+
179
+ /**
180
+ * Defines a model mixin from a `ModelProps` object and a builder, with a requirement on the base
181
+ * model.
182
+ *
183
+ * Pass `req<Req>()` as the second argument to declare that the base model must already contain the
184
+ * `Req` shape. The `Base` class received by the builder includes both the prop types and `Req`.
185
+ *
186
+ * @template Req Shape that must exist on the base model instance (specified via `req<Req>()`).
187
+ * @template MP Model properties type (inferred from `props`).
188
+ * @template C Built class type (inferred from `build` return type).
189
+ * @param props Model properties object.
190
+ * @param requirement Requirement marker created by `req<Req>()`.
191
+ * @param build Builder receiving the pre-extended base class (typed with both props and `Req`).
192
+ *
193
+ * @example
194
+ * const producerMixin = defineModelMixin(
195
+ * { produced: tProp(types.number, 0) },
196
+ * req<{ quantity: number }>(),
197
+ * (Base) => class Producer extends Base {
198
+ * produceTotal() { return this.produced + this.quantity }
199
+ * }
200
+ * )
201
+ */
202
+ export function defineModelMixin<
203
+ Req extends object,
204
+ MP extends ModelProps,
205
+ C extends AbstractModelClass<AnyModel & ModelPropsToTransformedData<MP>>,
206
+ >(
207
+ props: MP,
208
+ requirement: ReqMarker<Req>,
209
+ build: (base: AbstractModelClass<AnyModel & ModelPropsToTransformedData<MP> & Req>) => C
210
+ ): ModelMixin<Omit<InstanceType<C>, BaseModelKeys>, Req, MP>
211
+
212
+ // implementation
213
+ export function defineModelMixin(...args: any[]): any {
214
+ const props: ModelProps = args[0]
215
+ let build: ((base: AbstractModelClass<AnyModel>) => AbstractModelClass<AnyModel>) | undefined
216
+
217
+ if (args.length === 3) {
218
+ // (props, req, build)
219
+ build = args[2]
220
+ } else if (args.length === 2 && !isReqMarker(args[1])) {
221
+ // (props, build)
222
+ build = args[1]
223
+ }
224
+ // else: (props) or (props, req) — build stays undefined
225
+
226
+ return (<B extends ModelClass<AnyModel>>(base: B) => {
227
+ const extended = ExtendedModel(modelClass(base) as any, props) as AbstractModelClass<AnyModel>
228
+ return (build ? build(extended) : extended) as unknown as ModelClass<InstanceType<B>>
229
+ }) as ModelMixin<any, any, any>
230
+ }
231
+
232
+ // Empty base used when composeMixins is called without an explicit base class.
233
+ class MixinEmptyBase extends Model({}) {}
234
+
235
+ /**
236
+ * Composes several model mixins without an explicit base model class.
237
+ *
238
+ * An implicit empty base (`Model({})`) is used. Useful for bundling mixins to reuse across
239
+ * multiple concrete classes:
240
+ *
241
+ * ```ts
242
+ * const ProductBase = composeMixins(countableMixin, producerMixin)
243
+ *
244
+ * @model("myApp/Product")
245
+ * class Product extends ExtendedModel(ProductBase, {}) {}
246
+ * ```
247
+ *
248
+ * @template M Mixin tuple type.
249
+ * @param mixins Mixins to apply.
250
+ * @returns A model class with all mixin additions applied over an empty base.
251
+ */
252
+ export function composeMixins<M extends readonly ModelMixin<any, any, any>[]>(
253
+ ...mixins: M & ValidateMixins<typeof MixinEmptyBase, M>
254
+ ): ComposedModelClass<typeof MixinEmptyBase, M>
255
+
256
+ /**
257
+ * Composes several model mixins over a base model class.
258
+ *
259
+ * @template B Base model class.
260
+ * @template M Mixin tuple type.
261
+ * @param base Base model class.
262
+ * @param mixins Mixins to apply.
263
+ * @returns A model class with all mixin additions applied.
264
+ */
265
+ export function composeMixins<
266
+ B extends ModelClass<AnyModel>,
267
+ M extends readonly ModelMixin<any, any, any>[],
268
+ >(base: B, ...mixins: M & ValidateMixins<B, M>): ComposedModelClass<B, M>
269
+
270
+ // implementation
271
+ export function composeMixins(...args: any[]): any {
272
+ let base: ModelClass<AnyModel>
273
+ let mixins: ModelMixin<any, any, any>[]
274
+
275
+ if (isModelClass(args[0])) {
276
+ base = args[0]
277
+ mixins = args.slice(1)
278
+ } else {
279
+ base = MixinEmptyBase
280
+ mixins = args
281
+ }
282
+
283
+ let current = base
284
+ for (const mixin of mixins) {
285
+ current = mixin(current)
286
+ }
287
+ return current
288
+ }