mobx-keystone 1.22.0 → 1.24.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 +12 -0
- package/LICENSE +21 -21
- package/README.md +1 -1
- package/dist/mobx-keystone.esm.js +7198 -6242
- package/dist/mobx-keystone.esm.mjs +7198 -6242
- package/dist/mobx-keystone.umd.js +7217 -6243
- package/dist/types/action/runUnprotected.d.ts +8 -4
- package/dist/types/model/Model.d.ts +5 -0
- package/dist/types/modelShared/modelDecorator.d.ts +1 -1
- package/dist/types/modelShared/prop.d.ts +8 -4
- package/dist/types/snapshot/modelInitialData.d.ts +1 -0
- package/dist/types/tweaker/treeNodeMetadata.d.ts +1 -0
- package/dist/types/types/createCachedTypeCheck.d.ts +1 -0
- package/dist/types/utils/index.d.ts +24 -0
- package/package.json +15 -11
- package/src/action/middleware.ts +5 -18
- package/src/action/mutationBatch.ts +72 -0
- package/src/action/runUnprotected.ts +21 -8
- package/src/action/wrapInAction.ts +45 -15
- package/src/actionMiddlewares/undoMiddleware.ts +19 -13
- package/src/dataModel/BaseDataModel.ts +4 -8
- package/src/deepChange/onDeepChange.ts +246 -16
- package/src/model/BaseModel.ts +4 -12
- package/src/model/Model.ts +5 -0
- package/src/model/newModel.ts +68 -36
- package/src/modelShared/modelDecorator.ts +64 -53
- package/src/modelShared/prop.ts +105 -63
- package/src/modelShared/sharedInternalModel.ts +91 -60
- package/src/parent/core.ts +16 -10
- package/src/parent/coreObjectChildren.ts +219 -21
- package/src/parent/path.ts +6 -5
- package/src/parent/setParent.ts +5 -5
- package/src/patch/emitPatch.ts +102 -20
- package/src/ref/Ref.ts +4 -5
- package/src/ref/core.ts +2 -2
- package/src/snapshot/fromArraySnapshot.ts +11 -2
- package/src/snapshot/fromModelSnapshot.ts +10 -3
- package/src/snapshot/fromPlainObjectSnapshot.ts +18 -7
- package/src/snapshot/fromSnapshot.ts +31 -9
- package/src/snapshot/getSnapshot.ts +8 -3
- package/src/snapshot/internal.ts +243 -82
- package/src/snapshot/modelInitialData.ts +41 -0
- package/src/snapshot/reconcileArraySnapshot.ts +9 -3
- package/src/snapshot/reconcileModelSnapshot.ts +97 -17
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +9 -3
- package/src/treeUtils/draft.ts +7 -8
- package/src/tweaker/core.ts +30 -5
- package/src/tweaker/treeNodeMetadata.ts +38 -0
- package/src/tweaker/tweak.ts +15 -4
- package/src/tweaker/tweakArray.ts +159 -121
- package/src/tweaker/tweakFrozen.ts +2 -2
- package/src/tweaker/tweakModel.ts +2 -2
- package/src/tweaker/tweakPlainObject.ts +117 -124
- package/src/tweaker/typeChecking.ts +10 -1
- package/src/types/TypeChecker.ts +261 -15
- package/src/types/arrayBased/typesArray.ts +38 -40
- package/src/types/arrayBased/typesTuple.ts +51 -34
- package/src/types/createCachedTypeCheck.ts +181 -0
- package/src/types/objectBased/typesArraySet.ts +42 -32
- package/src/types/objectBased/typesDataModelData.ts +18 -11
- package/src/types/objectBased/typesModel.ts +1 -1
- package/src/types/objectBased/typesObject.ts +57 -28
- package/src/types/objectBased/typesObjectMap.ts +51 -40
- package/src/types/objectBased/typesRecord.ts +59 -39
- package/src/types/objectBased/typesRef.ts +1 -1
- package/src/types/primitiveBased/typesPrimitive.ts +9 -9
- package/src/types/tProp.ts +80 -44
- package/src/types/utility/typesOr.ts +35 -24
- package/src/types/utility/typesRefinement.ts +21 -8
- package/src/types/utility/typesSkipCheck.ts +21 -8
- package/src/types/utility/typesTag.ts +21 -9
- package/src/types/utility/typesUnchecked.ts +2 -3
- package/src/utils/ModelPool.ts +34 -19
- package/src/utils/errorDiagnostics.ts +60 -18
- package/src/utils/index.ts +142 -9
- package/src/types/createPerEntryCachedCheck.ts +0 -166
- /package/dist/types/{types/createPerEntryCachedCheck.d.ts → action/mutationBatch.d.ts} +0 -0
|
@@ -15,10 +15,11 @@ import {
|
|
|
15
15
|
} from "../modelShared/modelSymbols"
|
|
16
16
|
import { refreshInternalSnapshot } from "../snapshot/internal"
|
|
17
17
|
import {
|
|
18
|
+
addLateInitializationFunction,
|
|
18
19
|
failure,
|
|
19
20
|
getMobxVersion,
|
|
20
21
|
logWarning,
|
|
21
|
-
|
|
22
|
+
makeObservableCompat,
|
|
22
23
|
runAfterNewSymbol,
|
|
23
24
|
runBeforeOnInitSymbol,
|
|
24
25
|
runLateInitializationFunctions,
|
|
@@ -34,11 +35,11 @@ import type { AnyFunction } from "../utils/AnyFunction"
|
|
|
34
35
|
*/
|
|
35
36
|
export const model =
|
|
36
37
|
(name: string) =>
|
|
37
|
-
<MC extends ModelClass<AnyModel | AnyDataModel>>(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
<MC extends ModelClass<AnyModel | AnyDataModel>>(
|
|
39
|
+
clazz: MC,
|
|
40
|
+
_context?: ClassDecoratorContext<MC>
|
|
41
|
+
): MC =>
|
|
42
|
+
internalModel(name, clazz) as any
|
|
42
43
|
|
|
43
44
|
const afterClassInitializationData = new WeakMap<
|
|
44
45
|
ModelClass<AnyModel | AnyDataModel>,
|
|
@@ -56,14 +57,14 @@ const runAfterClassInitialization = (
|
|
|
56
57
|
|
|
57
58
|
const tag = afterClassInitializationData.get(target)!
|
|
58
59
|
|
|
59
|
-
// compatibility with mobx 6
|
|
60
|
+
// compatibility with mobx 6 and 7
|
|
60
61
|
if (tag.needsMakeObservable) {
|
|
61
62
|
// we know it can be done and shouldn't fail
|
|
62
|
-
|
|
63
|
+
makeObservableCompat(instance)
|
|
63
64
|
} else if (tag.needsMakeObservable === undefined) {
|
|
64
65
|
if (getMobxVersion() >= 6) {
|
|
65
66
|
try {
|
|
66
|
-
|
|
67
|
+
makeObservableCompat(instance)
|
|
67
68
|
tag.needsMakeObservable = true
|
|
68
69
|
} catch (e) {
|
|
69
70
|
const err = e as Error
|
|
@@ -121,8 +122,7 @@ const proxyClassHandler: ProxyHandler<ModelClass<AnyModel | AnyDataModel>> = {
|
|
|
121
122
|
|
|
122
123
|
const internalModel = <MC extends ModelClass<AnyModel | AnyDataModel>>(
|
|
123
124
|
name: string,
|
|
124
|
-
clazz: MC
|
|
125
|
-
addInitializer: ((initializer: (this: any) => void) => void) | undefined
|
|
125
|
+
clazz: MC
|
|
126
126
|
): MC | void => {
|
|
127
127
|
const type = isModelClass(clazz) ? "class" : isDataModelClass(clazz) ? "data" : undefined
|
|
128
128
|
if (!type) {
|
|
@@ -151,48 +151,24 @@ const internalModel = <MC extends ModelClass<AnyModel | AnyDataModel>>(
|
|
|
151
151
|
// track if we fail so we only try it once per class
|
|
152
152
|
afterClassInitializationData.set(clazz, { needsMakeObservable: undefined, type })
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
addInitializer(function (this: any) {
|
|
157
|
-
runAfterClassInitialization(clazz, this)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
const modelInfo = {
|
|
161
|
-
name,
|
|
162
|
-
class: clazz,
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
modelInfoByName[name] = modelInfo
|
|
154
|
+
const decoratedClass = new Proxy<MC>(clazz, proxyClassHandler)
|
|
155
|
+
;(decoratedClass as any)[modelUnwrappedClassSymbol] = clazz
|
|
166
156
|
|
|
167
|
-
|
|
157
|
+
decoratedClass.prototype.constructor = decoratedClass
|
|
168
158
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
// non-standard decorator API, use proxies
|
|
174
|
-
|
|
175
|
-
// trick so plain new works
|
|
176
|
-
const proxyClass = new Proxy<MC>(clazz, proxyClassHandler)
|
|
177
|
-
|
|
178
|
-
// set or else it points to the undecorated class
|
|
179
|
-
proxyClass.prototype.constructor = proxyClass
|
|
180
|
-
;(proxyClass as any)[modelUnwrappedClassSymbol] = clazz
|
|
181
|
-
|
|
182
|
-
const modelInfo = {
|
|
183
|
-
name,
|
|
184
|
-
class: proxyClass,
|
|
185
|
-
}
|
|
159
|
+
const modelInfo = {
|
|
160
|
+
name,
|
|
161
|
+
class: decoratedClass,
|
|
162
|
+
}
|
|
186
163
|
|
|
187
|
-
|
|
164
|
+
modelInfoByName[name] = modelInfo
|
|
188
165
|
|
|
189
|
-
|
|
190
|
-
|
|
166
|
+
modelInfoByClass.set(decoratedClass, modelInfo)
|
|
167
|
+
modelInfoByClass.set(clazz, modelInfo)
|
|
191
168
|
|
|
192
|
-
|
|
169
|
+
runLateInitializationFunctions(clazz, runAfterModelDecoratorSymbol)
|
|
193
170
|
|
|
194
|
-
|
|
195
|
-
}
|
|
171
|
+
return decoratedClass
|
|
196
172
|
}
|
|
197
173
|
|
|
198
174
|
// basically taken from TS
|
|
@@ -243,18 +219,53 @@ export function decoratedModel<M, MC extends abstract new (...ags: any) => M>(
|
|
|
243
219
|
[k in keyof M]?: AnyFunction | ReadonlyArray<AnyFunction>
|
|
244
220
|
}
|
|
245
221
|
): MC {
|
|
222
|
+
const mobx7Annotations: Record<string, unknown> = {}
|
|
223
|
+
|
|
246
224
|
// decorate class members
|
|
247
225
|
for (const [k, decorator] of Object.entries(decorators)) {
|
|
226
|
+
const decoratorList = Array.isArray(decorator) ? decorator : [decorator]
|
|
227
|
+
const mobx7Annotation = decoratorList.find(isMobxAnnotation)
|
|
228
|
+
if (getMobxVersion() >= 7 && mobx7Annotation) {
|
|
229
|
+
if (decoratorList.length !== 1) {
|
|
230
|
+
throw failure("MobX 7 decorators cannot be combined in decoratedModel")
|
|
231
|
+
}
|
|
232
|
+
mobx7Annotations[k] = mobx7Annotation
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
|
|
248
236
|
const prototypeValueDesc = Object.getOwnPropertyDescriptor(clazz.prototype, k)
|
|
249
237
|
// TS seems to send null for methods in the prototype
|
|
250
238
|
// (which we substitute for the descriptor to avoid a double look-up) and void 0 (undefined) for props
|
|
251
|
-
tsDecorate(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
239
|
+
tsDecorate(decoratorList, clazz.prototype, k, prototypeValueDesc ? prototypeValueDesc : void 0)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (getMobxVersion() >= 7 && Object.keys(mobx7Annotations).length > 0) {
|
|
243
|
+
addLateInitializationFunction(clazz.prototype, runAfterNewSymbol, (instance) => {
|
|
244
|
+
makeObservableCompat(instance, mobx7Annotations)
|
|
245
|
+
})
|
|
257
246
|
}
|
|
258
247
|
|
|
259
248
|
return (name ? model(name)(clazz as unknown as ModelClass<AnyModel>) : clazz) as MC
|
|
260
249
|
}
|
|
250
|
+
|
|
251
|
+
type MobxAnnotation = {
|
|
252
|
+
annotationType_: string
|
|
253
|
+
make_: AnyFunction
|
|
254
|
+
extend_: AnyFunction
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// MobX uses this same structural check in its internal `isAnnotation` helper.
|
|
258
|
+
// That helper is not part of MobX's public root API, so mirror it here instead
|
|
259
|
+
// of depending on a private deep import that may not exist in every supported version.
|
|
260
|
+
function isMobxAnnotation(value: unknown): value is MobxAnnotation {
|
|
261
|
+
if ((typeof value !== "object" || value === null) && typeof value !== "function") {
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const annotation = value as Partial<MobxAnnotation>
|
|
266
|
+
return (
|
|
267
|
+
typeof annotation.annotationType_ === "string" &&
|
|
268
|
+
typeof annotation.make_ === "function" &&
|
|
269
|
+
typeof annotation.extend_ === "function"
|
|
270
|
+
)
|
|
271
|
+
}
|
package/src/modelShared/prop.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SnapshotInOf, SnapshotOutOf } from "../snapshot/SnapshotOf"
|
|
2
2
|
import type { AnyStandardType } from "../types/schemas"
|
|
3
|
+
import { lazy } from "../utils"
|
|
3
4
|
import { runWithErrorDiagnosticsContext, withErrorPathSegment } from "../utils/errorDiagnostics"
|
|
4
5
|
import { getOrCreate } from "../utils/mapUtils"
|
|
5
6
|
import type { Flatten, IsNeverType, IsOptionalValue } from "../utils/types"
|
|
@@ -13,6 +14,17 @@ type SetterMode = boolean | "assign"
|
|
|
13
14
|
|
|
14
15
|
export type ModelPropSetterValueTransform<T> = (value: T) => T
|
|
15
16
|
|
|
17
|
+
/** @internal */
|
|
18
|
+
export interface FullModelPropTransform {
|
|
19
|
+
transform: (
|
|
20
|
+
original: unknown,
|
|
21
|
+
model: object,
|
|
22
|
+
propName: PropertyKey,
|
|
23
|
+
setOriginalValue: (newOriginalValue: unknown) => void
|
|
24
|
+
) => unknown
|
|
25
|
+
untransform: (transformed: unknown, model: object, propName: PropertyKey) => unknown
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
/**
|
|
17
29
|
* A model property.
|
|
18
30
|
*/
|
|
@@ -42,24 +54,18 @@ export interface ModelProp<
|
|
|
42
54
|
// True when the declared default is a runtime/transformed value and must be
|
|
43
55
|
// converted before it can be stored in `$`.
|
|
44
56
|
_defaultValueIsTransformed: boolean
|
|
57
|
+
_getDefaultValueIsTransformed?: () => boolean
|
|
45
58
|
_typeChecker: AnyStandardType | undefined
|
|
46
59
|
_setter: SetterMode
|
|
47
60
|
_setterValueTransform: ((value: unknown) => unknown) | undefined
|
|
48
61
|
_isId: boolean
|
|
49
62
|
_idGenerator?: (() => string) | undefined
|
|
50
|
-
_transform:
|
|
51
|
-
|
|
52
|
-
transform: (
|
|
53
|
-
original: unknown,
|
|
54
|
-
model: object,
|
|
55
|
-
propName: PropertyKey,
|
|
56
|
-
setOriginalValue: (newOriginalValue: unknown) => void
|
|
57
|
-
) => unknown
|
|
58
|
-
untransform: (transformed: unknown, model: object, propName: PropertyKey) => unknown
|
|
59
|
-
}
|
|
60
|
-
| undefined
|
|
63
|
+
_transform: FullModelPropTransform | undefined
|
|
64
|
+
_getTransform?: () => FullModelPropTransform | undefined
|
|
61
65
|
_fromSnapshotProcessor?: (sn: unknown) => unknown
|
|
62
66
|
_toSnapshotProcessor?: (sn: unknown) => unknown
|
|
67
|
+
_getFromSnapshotProcessor?: () => ((sn: unknown) => unknown) | undefined
|
|
68
|
+
_getToSnapshotProcessor?: () => ((sn: unknown) => unknown) | undefined
|
|
63
69
|
|
|
64
70
|
/**
|
|
65
71
|
* Adds a setter to the property. The setter will be named `set${CapitalizedPropName}`
|
|
@@ -141,6 +147,9 @@ export interface ModelProp<
|
|
|
141
147
|
*
|
|
142
148
|
* `fromSnapshot` runs before assigning snapshot data into the model prop.
|
|
143
149
|
* `toSnapshot` runs when exporting model data back to snapshot form.
|
|
150
|
+
* Both processors must be pure and deterministic, and their canonical output
|
|
151
|
+
* must round-trip to the same stored value. Reconciliation and snapshot
|
|
152
|
+
* generation may skip calls when that canonical snapshot is already current.
|
|
144
153
|
*/
|
|
145
154
|
withSnapshotProcessor<
|
|
146
155
|
FS = TFromSnapshotOverride,
|
|
@@ -400,13 +409,17 @@ const baseProp: AnyModelProp = {
|
|
|
400
409
|
_defaultFn: noDefaultValue,
|
|
401
410
|
_defaultValue: noDefaultValue,
|
|
402
411
|
_defaultValueIsTransformed: false,
|
|
412
|
+
_getDefaultValueIsTransformed: undefined,
|
|
403
413
|
_typeChecker: undefined,
|
|
404
414
|
_setter: false,
|
|
405
415
|
_setterValueTransform: undefined,
|
|
406
416
|
_isId: false,
|
|
407
417
|
_transform: undefined,
|
|
418
|
+
_getTransform: undefined,
|
|
408
419
|
_fromSnapshotProcessor: undefined,
|
|
409
420
|
_toSnapshotProcessor: undefined,
|
|
421
|
+
_getFromSnapshotProcessor: undefined,
|
|
422
|
+
_getToSnapshotProcessor: undefined,
|
|
410
423
|
|
|
411
424
|
withSetter(modeOrValueTransform?: SetterMode | ModelPropSetterValueTransform<unknown>) {
|
|
412
425
|
const obj: AnyModelProp = Object.create(this)
|
|
@@ -418,43 +431,88 @@ const baseProp: AnyModelProp = {
|
|
|
418
431
|
|
|
419
432
|
withTransform(transform: ModelPropTransform<unknown, unknown>) {
|
|
420
433
|
const obj: AnyModelProp = Object.create(this)
|
|
421
|
-
|
|
434
|
+
const innerTransform = toFullModelPropTransform(transform)
|
|
435
|
+
if (this._getTransform) {
|
|
436
|
+
const oldGet = this._getTransform
|
|
437
|
+
obj._getTransform = lazy(() => composeFullTransforms(oldGet(), innerTransform))
|
|
438
|
+
obj._transform = undefined
|
|
439
|
+
} else {
|
|
440
|
+
obj._transform = composeFullTransforms(this._transform, innerTransform)
|
|
441
|
+
}
|
|
422
442
|
return obj
|
|
423
443
|
},
|
|
424
444
|
|
|
425
445
|
withSnapshotProcessor({ fromSnapshot, toSnapshot }) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (this._fromSnapshotProcessor && fromSnapshot) {
|
|
429
|
-
const oldFn = this._fromSnapshotProcessor
|
|
430
|
-
const newFn = fromSnapshot
|
|
431
|
-
newFromSnapshot = (sn: any) => oldFn(newFn(sn))
|
|
432
|
-
} else if (fromSnapshot) {
|
|
433
|
-
newFromSnapshot = fromSnapshot
|
|
434
|
-
} else {
|
|
435
|
-
newFromSnapshot = this._fromSnapshotProcessor
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
let newToSnapshot: ((sn: any) => any) | undefined
|
|
446
|
+
const obj: AnyModelProp = Object.create(this)
|
|
439
447
|
|
|
440
|
-
if (
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
+
if (fromSnapshot) {
|
|
449
|
+
const newProcessor = fromSnapshot as (sn: unknown) => unknown
|
|
450
|
+
if (this._getFromSnapshotProcessor) {
|
|
451
|
+
const oldGet = this._getFromSnapshotProcessor
|
|
452
|
+
obj._getFromSnapshotProcessor = lazy(() => {
|
|
453
|
+
const oldProcessor = oldGet()
|
|
454
|
+
return oldProcessor ? (sn: unknown) => oldProcessor(newProcessor(sn)) : newProcessor
|
|
455
|
+
})
|
|
456
|
+
obj._fromSnapshotProcessor = undefined
|
|
457
|
+
} else {
|
|
458
|
+
const oldProcessor = this._fromSnapshotProcessor
|
|
459
|
+
obj._fromSnapshotProcessor = oldProcessor
|
|
460
|
+
? (sn: unknown) => oldProcessor(newProcessor(sn))
|
|
461
|
+
: newProcessor
|
|
462
|
+
}
|
|
448
463
|
}
|
|
449
464
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
465
|
+
if (toSnapshot) {
|
|
466
|
+
const newProcessor = toSnapshot as (sn: unknown) => unknown
|
|
467
|
+
if (this._getToSnapshotProcessor) {
|
|
468
|
+
const oldGet = this._getToSnapshotProcessor
|
|
469
|
+
obj._getToSnapshotProcessor = lazy(() => {
|
|
470
|
+
const oldProcessor = oldGet()
|
|
471
|
+
return oldProcessor ? (sn: unknown) => newProcessor(oldProcessor(sn)) : newProcessor
|
|
472
|
+
})
|
|
473
|
+
obj._toSnapshotProcessor = undefined
|
|
474
|
+
} else {
|
|
475
|
+
const oldProcessor = this._toSnapshotProcessor
|
|
476
|
+
obj._toSnapshotProcessor = oldProcessor
|
|
477
|
+
? (sn: unknown) => newProcessor(oldProcessor(sn))
|
|
478
|
+
: newProcessor
|
|
479
|
+
}
|
|
480
|
+
}
|
|
453
481
|
|
|
454
482
|
return obj
|
|
455
483
|
},
|
|
456
484
|
}
|
|
457
485
|
|
|
486
|
+
/** @internal */
|
|
487
|
+
export function getModelPropFromSnapshotProcessor(
|
|
488
|
+
modelProp: AnyModelProp
|
|
489
|
+
): ((sn: unknown) => unknown) | undefined {
|
|
490
|
+
return modelProp._getFromSnapshotProcessor
|
|
491
|
+
? modelProp._getFromSnapshotProcessor()
|
|
492
|
+
: modelProp._fromSnapshotProcessor
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** @internal */
|
|
496
|
+
export function getModelPropToSnapshotProcessor(
|
|
497
|
+
modelProp: AnyModelProp
|
|
498
|
+
): ((sn: unknown) => unknown) | undefined {
|
|
499
|
+
return modelProp._getToSnapshotProcessor
|
|
500
|
+
? modelProp._getToSnapshotProcessor()
|
|
501
|
+
: modelProp._toSnapshotProcessor
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** @internal */
|
|
505
|
+
export function getModelPropTransform(modelProp: AnyModelProp): FullModelPropTransform | undefined {
|
|
506
|
+
return modelProp._getTransform ? modelProp._getTransform() : modelProp._transform
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** @internal */
|
|
510
|
+
export function getModelPropDefaultValueIsTransformed(modelProp: AnyModelProp): boolean {
|
|
511
|
+
return modelProp._getDefaultValueIsTransformed
|
|
512
|
+
? modelProp._getDefaultValueIsTransformed()
|
|
513
|
+
: modelProp._defaultValueIsTransformed
|
|
514
|
+
}
|
|
515
|
+
|
|
458
516
|
/**
|
|
459
517
|
* Defines a model property, with an optional function to generate a default value
|
|
460
518
|
* if the input snapshot / model creation data is `null` or `undefined`.
|
|
@@ -565,7 +623,10 @@ const cacheTransformedValueFn = () => {
|
|
|
565
623
|
cacheTransformResult = true
|
|
566
624
|
}
|
|
567
625
|
|
|
568
|
-
|
|
626
|
+
/** @internal */
|
|
627
|
+
export function toFullModelPropTransform(
|
|
628
|
+
transformObject: ModelPropTransform<unknown, unknown>
|
|
629
|
+
): FullModelPropTransform {
|
|
569
630
|
const cache = new WeakMap<
|
|
570
631
|
object,
|
|
571
632
|
Map<PropertyKey, { originalValue: unknown; transformedValue: unknown }>
|
|
@@ -630,27 +691,9 @@ function toFullTransform(transformObject: ModelPropTransform<unknown, unknown>)
|
|
|
630
691
|
}
|
|
631
692
|
|
|
632
693
|
function composeFullTransforms(
|
|
633
|
-
outerTransform:
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
originalValue: unknown,
|
|
637
|
-
model: object,
|
|
638
|
-
propName: PropertyKey,
|
|
639
|
-
setOriginalValue: (newOriginalValue: unknown) => void
|
|
640
|
-
): unknown
|
|
641
|
-
untransform(transformedValue: unknown, model: object, propName: PropertyKey): unknown
|
|
642
|
-
}
|
|
643
|
-
| undefined,
|
|
644
|
-
innerTransform: {
|
|
645
|
-
transform(
|
|
646
|
-
originalValue: unknown,
|
|
647
|
-
model: object,
|
|
648
|
-
propName: PropertyKey,
|
|
649
|
-
setOriginalValue: (newOriginalValue: unknown) => void
|
|
650
|
-
): unknown
|
|
651
|
-
untransform(transformedValue: unknown, model: object, propName: PropertyKey): unknown
|
|
652
|
-
}
|
|
653
|
-
) {
|
|
694
|
+
outerTransform: FullModelPropTransform | undefined,
|
|
695
|
+
innerTransform: FullModelPropTransform
|
|
696
|
+
): FullModelPropTransform {
|
|
654
697
|
if (!outerTransform) {
|
|
655
698
|
return innerTransform
|
|
656
699
|
}
|
|
@@ -699,15 +742,14 @@ export function getModelPropStoredDefaultValue(
|
|
|
699
742
|
propName: string
|
|
700
743
|
): unknown | typeof noDefaultValue {
|
|
701
744
|
const defaultValue = getModelPropDefaultValue(propData)
|
|
702
|
-
if (defaultValue === noDefaultValue || !propData
|
|
745
|
+
if (defaultValue === noDefaultValue || !getModelPropDefaultValueIsTransformed(propData)) {
|
|
703
746
|
return defaultValue
|
|
704
747
|
}
|
|
705
748
|
|
|
706
|
-
|
|
749
|
+
const transform = getModelPropTransform(propData)
|
|
750
|
+
return transform
|
|
707
751
|
? runWithErrorDiagnosticsContext(() =>
|
|
708
|
-
withErrorPathSegment(propName, () =>
|
|
709
|
-
propData._transform!.untransform(defaultValue, model, propName)
|
|
710
|
-
)
|
|
752
|
+
withErrorPathSegment(propName, () => transform.untransform(defaultValue, model, propName))
|
|
711
753
|
)
|
|
712
754
|
: defaultValue
|
|
713
755
|
}
|
|
@@ -28,7 +28,10 @@ import { getInternalModelClassPropsInfo, setInternalModelClassPropsInfo } from "
|
|
|
28
28
|
import { modelMetadataSymbol, modelUnwrappedClassSymbol } from "./modelSymbols"
|
|
29
29
|
import {
|
|
30
30
|
type AnyModelProp,
|
|
31
|
+
getModelPropFromSnapshotProcessor,
|
|
31
32
|
getModelPropStoredDefaultValue,
|
|
33
|
+
getModelPropToSnapshotProcessor,
|
|
34
|
+
getModelPropTransform,
|
|
32
35
|
type ModelProps,
|
|
33
36
|
noDefaultValue,
|
|
34
37
|
prop,
|
|
@@ -39,21 +42,36 @@ function createGetModelInstanceDataField<M extends AnyModel | AnyDataModel>(
|
|
|
39
42
|
modelProp: AnyModelProp,
|
|
40
43
|
modelPropName: string
|
|
41
44
|
): (this: M) => unknown {
|
|
42
|
-
const
|
|
45
|
+
const fixedTransform = modelProp._getTransform ? undefined : modelProp._transform
|
|
43
46
|
|
|
44
|
-
if (!
|
|
47
|
+
if (!modelProp._getTransform && !fixedTransform) {
|
|
45
48
|
// no need to use get since these vars always get on the initial $
|
|
46
49
|
return function (this) {
|
|
47
50
|
return this.$[modelPropName]
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
type TransformValue = (model: M, value: unknown) => unknown
|
|
55
|
+
const createTransformValue =
|
|
56
|
+
(transform: NonNullable<ReturnType<typeof getModelPropTransform>>): TransformValue =>
|
|
57
|
+
(model, value) =>
|
|
58
|
+
transform.transform(value, model, modelPropName, (newValue) => {
|
|
59
|
+
// use apply set instead to wrap it in an action
|
|
60
|
+
// set the $ object to set the original value directly
|
|
61
|
+
applySet(model.$, modelPropName, newValue)
|
|
62
|
+
})
|
|
63
|
+
let transformValue: TransformValue
|
|
64
|
+
if (fixedTransform) {
|
|
65
|
+
transformValue = createTransformValue(fixedTransform)
|
|
66
|
+
} else {
|
|
67
|
+
transformValue = (model, value) => {
|
|
68
|
+
const transform = getModelPropTransform(modelProp)
|
|
69
|
+
transformValue = transform
|
|
70
|
+
? createTransformValue(transform)
|
|
71
|
+
: (_model, storedValue) => storedValue
|
|
72
|
+
return transformValue(model, value)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
57
75
|
|
|
58
76
|
return function (this) {
|
|
59
77
|
// no need to use get since these vars always get on the initial $
|
|
@@ -85,10 +103,9 @@ const setModelInstanceDataField: SetModelInstanceDataFieldFn = (
|
|
|
85
103
|
value = modelProp._setterValueTransform(value)
|
|
86
104
|
}
|
|
87
105
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
106
|
+
const transform = modelProp._getTransform ? modelProp._getTransform() : modelProp._transform
|
|
107
|
+
let untransformedValue = transform
|
|
108
|
+
? withErrorPathSegment(modelPropName, () => transform.untransform(value, model, modelPropName))
|
|
92
109
|
: value
|
|
93
110
|
|
|
94
111
|
// apply default value if applicable
|
|
@@ -128,6 +145,27 @@ propForDefaultId._isId = true
|
|
|
128
145
|
type FromSnapshotProcessorFn = (sn: any) => any
|
|
129
146
|
type ToSnapshotProcessorFn = (sn: any, instance: any) => any
|
|
130
147
|
|
|
148
|
+
function defineLazySnapshotProcessor(
|
|
149
|
+
modelClass: object,
|
|
150
|
+
property: "fromSnapshotProcessor" | "toSnapshotProcessor",
|
|
151
|
+
compile: () => FromSnapshotProcessorFn | ToSnapshotProcessorFn | undefined
|
|
152
|
+
): void {
|
|
153
|
+
Object.defineProperty(modelClass, property, {
|
|
154
|
+
configurable: true,
|
|
155
|
+
enumerable: true,
|
|
156
|
+
get() {
|
|
157
|
+
const processor = compile()
|
|
158
|
+
Object.defineProperty(modelClass, property, {
|
|
159
|
+
configurable: true,
|
|
160
|
+
enumerable: true,
|
|
161
|
+
writable: true,
|
|
162
|
+
value: processor,
|
|
163
|
+
})
|
|
164
|
+
return processor
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
131
169
|
export function sharedInternalModel<
|
|
132
170
|
TProps extends ModelProps,
|
|
133
171
|
TBaseModel extends AnyModel | AnyDataModel,
|
|
@@ -234,13 +272,13 @@ export function sharedInternalModel<
|
|
|
234
272
|
// create type checker if needed
|
|
235
273
|
let dataTypeChecker: AnyType | undefined
|
|
236
274
|
if (needsTypeChecker) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
275
|
+
dataTypeChecker = typesObject(() => {
|
|
276
|
+
const typeCheckerObj: Record<string, AnyType> = {}
|
|
277
|
+
for (const [k, mp] of Object.entries(composedModelProps)) {
|
|
278
|
+
typeCheckerObj[k] = mp._typeChecker ? resolveStoredType(mp._typeChecker) : typesUnchecked()
|
|
279
|
+
}
|
|
280
|
+
return typeCheckerObj
|
|
281
|
+
})
|
|
244
282
|
}
|
|
245
283
|
|
|
246
284
|
const base: any = baseModel ?? (type === "class" ? BaseModel : BaseDataModel)
|
|
@@ -279,8 +317,17 @@ export function sharedInternalModel<
|
|
|
279
317
|
return baseModel
|
|
280
318
|
}
|
|
281
319
|
|
|
282
|
-
//
|
|
283
|
-
|
|
320
|
+
// Copy static props from the base without reading its lazy snapshot processor
|
|
321
|
+
// accessors. This keeps subclass declaration from compiling the parent model's
|
|
322
|
+
// processor graph before snapshot processing is actually needed.
|
|
323
|
+
for (const key of Reflect.ownKeys(base)) {
|
|
324
|
+
if (key === "fromSnapshotProcessor" || key === "toSnapshotProcessor") {
|
|
325
|
+
continue
|
|
326
|
+
}
|
|
327
|
+
if (Object.prototype.propertyIsEnumerable.call(base, key)) {
|
|
328
|
+
;(ThisModel as any)[key] = (base as any)[key]
|
|
329
|
+
}
|
|
330
|
+
}
|
|
284
331
|
delete (ThisModel as any)[modelUnwrappedClassSymbol]
|
|
285
332
|
|
|
286
333
|
const initializers: ModelClassInitializer[] = base[modelInitializersSymbol]
|
|
@@ -345,10 +392,6 @@ export function sharedInternalModel<
|
|
|
345
392
|
}
|
|
346
393
|
}
|
|
347
394
|
|
|
348
|
-
const modelPropsFromSnapshotProcessor = getModelPropsFromSnapshotProcessor(composedModelProps)
|
|
349
|
-
|
|
350
|
-
const modelPropsToSnapshotProcessor = getModelPropsToSnapshotProcessor(composedModelProps)
|
|
351
|
-
|
|
352
395
|
if (fromSnapshotProcessor) {
|
|
353
396
|
const fn = fromSnapshotProcessor
|
|
354
397
|
fromSnapshotProcessor = (sn) => {
|
|
@@ -373,54 +416,42 @@ export function sharedInternalModel<
|
|
|
373
416
|
}
|
|
374
417
|
}
|
|
375
418
|
|
|
376
|
-
ThisModel
|
|
377
|
-
|
|
419
|
+
defineLazySnapshotProcessor(ThisModel, "fromSnapshotProcessor", () =>
|
|
420
|
+
chainFns(
|
|
421
|
+
fromSnapshotProcessor,
|
|
422
|
+
getModelPropsSnapshotProcessor(composedModelProps, getModelPropFromSnapshotProcessor)
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
defineLazySnapshotProcessor(ThisModel, "toSnapshotProcessor", () =>
|
|
426
|
+
chainFns(
|
|
427
|
+
getModelPropsSnapshotProcessor(composedModelProps, getModelPropToSnapshotProcessor),
|
|
428
|
+
toSnapshotProcessor
|
|
429
|
+
)
|
|
430
|
+
)
|
|
378
431
|
|
|
379
432
|
return ThisModel
|
|
380
433
|
}
|
|
381
434
|
|
|
382
|
-
function
|
|
383
|
-
composedModelProps: ModelProps
|
|
435
|
+
function getModelPropsSnapshotProcessor(
|
|
436
|
+
composedModelProps: ModelProps,
|
|
437
|
+
getProcessor: (prop: AnyModelProp) => ((sn: unknown) => unknown) | undefined
|
|
384
438
|
): FromSnapshotProcessorFn | undefined {
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
}
|
|
439
|
+
const propsWithProcessor: Array<readonly [string, (sn: unknown) => unknown]> = []
|
|
440
|
+
for (const [propName, propData] of Object.entries(composedModelProps)) {
|
|
441
|
+
const processor = getProcessor(propData)
|
|
442
|
+
if (processor) {
|
|
443
|
+
propsWithProcessor.push([propName, processor])
|
|
400
444
|
}
|
|
401
|
-
return newSn
|
|
402
445
|
}
|
|
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
446
|
|
|
412
|
-
if (
|
|
447
|
+
if (propsWithProcessor.length === 0) {
|
|
413
448
|
return undefined
|
|
414
449
|
}
|
|
415
450
|
|
|
416
451
|
return (sn) => {
|
|
417
452
|
const newSn = { ...sn }
|
|
418
|
-
for (const [propName,
|
|
419
|
-
|
|
420
|
-
newSn[propName] = withErrorPathSegment(propName, () =>
|
|
421
|
-
propData._toSnapshotProcessor!(sn[propName])
|
|
422
|
-
)
|
|
423
|
-
}
|
|
453
|
+
for (const [propName, processor] of propsWithProcessor) {
|
|
454
|
+
newSn[propName] = withErrorPathSegment(propName, () => processor(sn[propName]))
|
|
424
455
|
}
|
|
425
456
|
return newSn
|
|
426
457
|
}
|