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.
- package/CHANGELOG.md +864 -853
- package/README.md +61 -61
- package/dist/mobx-keystone.esm.js +11336 -11148
- package/dist/mobx-keystone.esm.mjs +11336 -11148
- package/dist/mobx-keystone.umd.js +12229 -11738
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +1 -1
- package/dist/types/modelShared/prop.d.ts +5 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +17 -4
- package/dist/types/ref/Ref.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +1 -1
- package/dist/types/types/TypeChecker.d.ts +1 -1
- package/dist/types/types/arrayBased/typesArray.d.ts +1 -1
- package/dist/types/types/arrayBased/typesTuple.d.ts +1 -1
- package/dist/types/types/objectBased/typesArraySet.d.ts +1 -1
- package/dist/types/types/objectBased/typesDataModelData.d.ts +1 -1
- package/dist/types/types/objectBased/typesModel.d.ts +1 -1
- package/dist/types/types/objectBased/typesObject.d.ts +3 -3
- package/dist/types/types/objectBased/typesObjectMap.d.ts +1 -1
- package/dist/types/types/objectBased/typesRecord.d.ts +1 -1
- package/dist/types/types/primitiveBased/typesPrimitive.d.ts +1 -1
- package/dist/types/types/types.d.ts +3 -3
- package/dist/types/types/utility/typesCodec.d.ts +1 -6
- package/dist/types/types/utility/typesCodecCore.d.ts +1 -1
- package/dist/types/types/utility/typesOr.d.ts +1 -1
- package/dist/types/types/utility/typesRefinement.d.ts +1 -1
- package/dist/types/types/utility/typesSkipCheck.d.ts +1 -1
- package/dist/types/types/utility/typesTag.d.ts +1 -1
- package/dist/types/wrappers/ObjectMap.d.ts +2 -0
- package/dist/types/wrappers/asMap.d.ts +10 -6
- package/package.json +14 -12
- package/src/action/applyAction.ts +125 -114
- package/src/action/applyMethodCall.ts +37 -37
- package/src/action/isModelAction.ts +16 -16
- package/src/action/middleware.ts +137 -137
- package/src/action/modelFlow.ts +240 -240
- package/src/action/modelFlowPromiseGenerator.ts +127 -125
- package/src/action/pendingActions.ts +42 -42
- package/src/action/wrapInAction.ts +156 -156
- package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +1 -1
- package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +172 -172
- package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +20 -20
- package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +17 -17
- package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +42 -42
- package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +50 -50
- package/src/actionMiddlewares/actionSerialization/objectSnapshotSerializer.ts +21 -21
- package/src/actionMiddlewares/actionSerialization/plainObjectSerializer.ts +24 -32
- package/src/actionMiddlewares/actionSerialization/primitiveSerializer.ts +51 -51
- package/src/actionMiddlewares/actionSerialization/setSerializer.ts +37 -37
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +3 -3
- package/src/actionMiddlewares/onActionMiddleware.ts +2 -2
- package/src/actionMiddlewares/readonlyMiddleware.ts +2 -2
- package/src/actionMiddlewares/transactionMiddleware.ts +4 -4
- package/src/actionMiddlewares/undoMiddleware.ts +804 -796
- package/src/actionMiddlewares/utils.ts +2 -2
- package/src/computedTree/computedTree.ts +143 -143
- package/src/context/context.ts +1 -1
- package/src/dataModel/BaseDataModel.ts +194 -192
- package/src/dataModel/actions.ts +1 -1
- package/src/dataModel/newDataModel.ts +57 -57
- package/src/deepChange/index.ts +1 -1
- package/src/deepChange/onDeepChange.ts +244 -244
- package/src/frozen/Frozen.ts +146 -146
- package/src/globalConfig/globalConfig.ts +107 -107
- package/src/index.ts +24 -24
- package/src/model/BaseModel.ts +275 -273
- package/src/model/ModelConstructorOptions.ts +15 -15
- package/src/model/getModelMetadata.ts +51 -51
- package/src/model/index.ts +6 -6
- package/src/model/mixins.ts +288 -288
- package/src/model/newModel.ts +301 -301
- package/src/model/utils.ts +249 -249
- package/src/modelShared/modelDecorator.ts +260 -253
- package/src/modelShared/modelInfo.ts +50 -50
- package/src/modelShared/modelSymbols.ts +3 -3
- package/src/modelShared/newModel.ts +25 -25
- package/src/modelShared/prop.ts +713 -684
- package/src/modelShared/sharedInternalModel.ts +427 -417
- package/src/modelShared/utils.ts +24 -24
- package/src/parent/core.ts +65 -65
- package/src/parent/coreObjectChildren.ts +180 -180
- package/src/parent/detach.ts +47 -47
- package/src/parent/findParent.ts +1 -1
- package/src/parent/getChildrenObjects.ts +25 -25
- package/src/parent/path.ts +386 -381
- package/src/parent/setParent.ts +149 -149
- package/src/parent/walkTree.ts +1 -1
- package/src/patch/applyPatches.ts +187 -187
- package/src/patch/jsonPatch.ts +108 -108
- package/src/patch/patchRecorder.ts +6 -1
- package/src/redux/connectReduxDevTools.ts +39 -7
- package/src/redux/redux.ts +6 -2
- package/src/ref/Ref.ts +1 -1
- package/src/ref/core.ts +339 -345
- package/src/ref/customRef.ts +3 -3
- package/src/ref/rootRef.ts +100 -100
- package/src/rootStore/attachDetach.ts +89 -89
- package/src/rootStore/rootStore.ts +1 -1
- package/src/snapshot/SnapshotOf.ts +104 -104
- package/src/snapshot/SnapshotProcessingError.ts +45 -45
- package/src/snapshot/applySnapshot.ts +177 -181
- package/src/snapshot/cloneTreeValue.ts +30 -30
- package/src/snapshot/fromArraySnapshot.ts +27 -33
- package/src/snapshot/fromModelSnapshot.ts +70 -70
- package/src/snapshot/fromPlainObjectSnapshot.ts +47 -42
- package/src/snapshot/fromSnapshot.ts +198 -198
- package/src/snapshot/getSnapshot.ts +80 -79
- package/src/snapshot/index.ts +8 -8
- package/src/snapshot/internal.ts +230 -230
- package/src/snapshot/reconcileArraySnapshot.ts +70 -70
- package/src/snapshot/reconcileModelSnapshot.ts +155 -150
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +72 -72
- package/src/snapshot/reconcileSnapshot.ts +88 -88
- package/src/standardActions/actions.ts +64 -64
- package/src/standardActions/objectActions.ts +60 -60
- package/src/transforms/ImmutableDate.ts +93 -93
- package/src/transforms/asMap.ts +50 -50
- package/src/transforms/asSet.ts +22 -22
- package/src/transforms/bigint.ts +20 -20
- package/src/transforms/date.ts +40 -40
- package/src/treeUtils/deepEquals.ts +55 -55
- package/src/treeUtils/sandbox.ts +6 -3
- package/src/tweaker/TweakerPriority.ts +9 -9
- package/src/tweaker/tweak.ts +4 -3
- package/src/tweaker/tweakArray.ts +530 -475
- package/src/tweaker/tweakFrozen.ts +42 -42
- package/src/tweaker/tweakModel.ts +1 -1
- package/src/tweaker/tweakPlainObject.ts +355 -355
- package/src/tweaker/typeChecking.ts +101 -101
- package/src/types/SnapshotTypeMismatchError.ts +46 -46
- package/src/types/TypeCheckError.ts +183 -184
- package/src/types/TypeChecker.ts +202 -185
- package/src/types/arrayBased/typesArray.ts +136 -137
- package/src/types/arrayBased/typesTuple.ts +138 -139
- package/src/types/createPerEntryCachedCheck.ts +166 -160
- package/src/types/index.ts +6 -6
- package/src/types/objectBased/typesArraySet.ts +133 -134
- package/src/types/objectBased/typesDataModelData.ts +203 -204
- package/src/types/objectBased/typesModel.ts +220 -221
- package/src/types/objectBased/typesObject.ts +308 -310
- package/src/types/objectBased/typesObjectMap.ts +142 -143
- package/src/types/objectBased/typesRecord.ts +156 -157
- package/src/types/objectBased/typesRef.ts +89 -89
- package/src/types/primitiveBased/typesEnum.ts +63 -63
- package/src/types/primitiveBased/typesPrimitive.ts +219 -220
- package/src/types/registerDefaultStandardTypeResolvers.ts +21 -21
- package/src/types/resolveTypeChecker.ts +78 -78
- package/src/types/schemas.ts +208 -208
- package/src/types/tProp.ts +277 -268
- package/src/types/typeCheck.ts +21 -21
- package/src/types/typeCheckErrorUtils.ts +23 -23
- package/src/types/types.ts +142 -142
- package/src/types/utility/typeToStoredData.ts +37 -37
- package/src/types/utility/typesBigInt.ts +15 -15
- package/src/types/utility/typesCodec.ts +41 -38
- package/src/types/utility/typesCodecCore.ts +178 -178
- package/src/types/utility/typesCodecSupport.ts +806 -806
- package/src/types/utility/typesDate.ts +27 -27
- package/src/types/utility/typesMap.ts +253 -222
- package/src/types/utility/typesOr.ts +263 -259
- package/src/types/utility/typesRefinement.ts +131 -125
- package/src/types/utility/typesSet.ts +110 -110
- package/src/types/utility/typesSkipCheck.ts +69 -70
- package/src/types/utility/typesTag.ts +88 -82
- package/src/types/utility/typesUnchecked.ts +40 -40
- package/src/utils/AnyFunction.ts +4 -4
- package/src/utils/ModelPool.ts +59 -59
- package/src/utils/chainFns.ts +18 -18
- package/src/utils/decorators.ts +274 -274
- package/src/utils/errorDiagnostics.ts +204 -204
- package/src/utils/index.ts +443 -372
- package/src/wrappers/ArraySet.ts +156 -156
- package/src/wrappers/ObjectMap.ts +155 -136
- package/src/wrappers/asMap.ts +65 -30
- package/src/wrappers/asSet.ts +3 -3
package/src/model/mixins.ts
CHANGED
|
@@ -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
|
+
}
|