mobx-keystone 2.2.0 → 2.3.1
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 +10 -0
- package/dist/mobx-keystone.esm.js +426 -334
- package/dist/mobx-keystone.esm.mjs +426 -334
- package/dist/mobx-keystone.umd.js +429 -333
- package/dist/types/action/applyAction.d.ts +1 -1
- package/dist/types/action/applyMethodCall.d.ts +1 -1
- package/dist/types/action/index.d.ts +12 -12
- package/dist/types/action/isModelAction.d.ts +1 -1
- package/dist/types/action/middleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/actionSerialization.d.ts +3 -3
- package/dist/types/actionMiddlewares/actionSerialization/applySerializedAction.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionSerialization/arraySerializer.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/index.d.ts +3 -3
- package/dist/types/actionMiddlewares/actionSerialization/mapSerializer.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionSerialization/objectPathSerializer.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionSerialization/objectSnapshotSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/plainObjectSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/primitiveSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/actionSerialization/setSerializer.d.ts +2 -2
- package/dist/types/actionMiddlewares/actionTrackingMiddleware.d.ts +3 -3
- package/dist/types/actionMiddlewares/index.d.ts +6 -6
- package/dist/types/actionMiddlewares/onActionMiddleware.d.ts +3 -3
- package/dist/types/actionMiddlewares/readonlyMiddleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +2 -2
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +8 -8
- package/dist/types/computedTree/index.d.ts +1 -1
- package/dist/types/context/index.d.ts +1 -1
- package/dist/types/dataModel/BaseDataModel.d.ts +3 -3
- package/dist/types/dataModel/DataModel.d.ts +3 -3
- package/dist/types/dataModel/getDataModelMetadata.d.ts +3 -3
- package/dist/types/dataModel/index.d.ts +4 -4
- package/dist/types/dataModel/utils.d.ts +1 -1
- package/dist/types/deepChange/index.d.ts +1 -1
- package/dist/types/deepChange/onDeepChange.d.ts +1 -1
- package/dist/types/frozen/Frozen.d.ts +1 -1
- package/dist/types/frozen/index.d.ts +1 -1
- package/dist/types/globalConfig/index.d.ts +1 -1
- package/dist/types/index.d.ts +24 -24
- package/dist/types/model/BaseModel.d.ts +5 -5
- package/dist/types/model/Model.d.ts +3 -3
- package/dist/types/model/getModelMetadata.d.ts +3 -3
- package/dist/types/model/index.d.ts +6 -6
- package/dist/types/model/mixins.d.ts +4 -4
- package/dist/types/model/utils.d.ts +4 -4
- package/dist/types/modelShared/BaseModelShared.d.ts +5 -5
- package/dist/types/modelShared/index.d.ts +4 -4
- package/dist/types/modelShared/modelDecorator.d.ts +3 -3
- package/dist/types/modelShared/modelInfo.d.ts +3 -3
- package/dist/types/modelShared/newModel.d.ts +3 -3
- package/dist/types/modelShared/prop.d.ts +3 -3
- package/dist/types/modelShared/sharedInternalModel.d.ts +4 -4
- package/dist/types/parent/findParent.d.ts +1 -1
- package/dist/types/parent/index.d.ts +9 -9
- package/dist/types/parent/path.d.ts +1 -1
- package/dist/types/patch/Patch.d.ts +1 -1
- package/dist/types/patch/applyPatches.d.ts +1 -1
- package/dist/types/patch/emitPatch.d.ts +1 -1
- package/dist/types/patch/index.d.ts +5 -5
- package/dist/types/patch/jsonPatch.d.ts +2 -2
- package/dist/types/patch/patchRecorder.d.ts +2 -2
- package/dist/types/redux/index.d.ts +2 -2
- package/dist/types/redux/redux.d.ts +3 -3
- package/dist/types/ref/Ref.d.ts +3 -3
- package/dist/types/ref/core.d.ts +2 -2
- package/dist/types/ref/customRef.d.ts +2 -2
- package/dist/types/ref/index.d.ts +4 -4
- package/dist/types/ref/rootRef.d.ts +2 -2
- package/dist/types/rootStore/index.d.ts +1 -1
- package/dist/types/snapshot/SnapshotOf.d.ts +5 -5
- package/dist/types/snapshot/SnapshotProcessingError.d.ts +2 -2
- package/dist/types/snapshot/applySnapshot.d.ts +1 -1
- package/dist/types/snapshot/cloneTreeValue.d.ts +1 -1
- package/dist/types/snapshot/fromSnapshot.d.ts +2 -2
- package/dist/types/snapshot/getSnapshot.d.ts +2 -2
- package/dist/types/snapshot/index.d.ts +8 -8
- package/dist/types/snapshot/onSnapshot.d.ts +1 -1
- package/dist/types/standardActions/index.d.ts +3 -3
- package/dist/types/standardActions/objectActions.d.ts +1 -1
- package/dist/types/transforms/asMap.d.ts +1 -1
- package/dist/types/transforms/asSet.d.ts +2 -2
- package/dist/types/transforms/bigint.d.ts +1 -1
- package/dist/types/transforms/date.d.ts +1 -1
- package/dist/types/transforms/index.d.ts +4 -4
- package/dist/types/treeUtils/draft.d.ts +1 -1
- package/dist/types/treeUtils/index.d.ts +3 -3
- package/dist/types/tweaker/index.d.ts +2 -2
- package/dist/types/tweaker/tweak.d.ts +1 -1
- package/dist/types/types/SnapshotTypeMismatchError.d.ts +5 -3
- package/dist/types/types/TypeCheckError.d.ts +2 -2
- package/dist/types/types/TypeChecker.d.ts +3 -3
- package/dist/types/types/arrayBased/typesArray.d.ts +2 -2
- package/dist/types/types/arrayBased/typesTuple.d.ts +2 -2
- package/dist/types/types/getTypeInfo.d.ts +2 -2
- package/dist/types/types/index.d.ts +7 -7
- package/dist/types/types/modelTypeMatching.d.ts +1 -0
- package/dist/types/types/objectBased/typesArraySet.d.ts +3 -3
- package/dist/types/types/objectBased/typesDataModelData.d.ts +4 -4
- package/dist/types/types/objectBased/typesModel.d.ts +4 -4
- package/dist/types/types/objectBased/typesObject.d.ts +3 -3
- package/dist/types/types/objectBased/typesObjectMap.d.ts +3 -3
- package/dist/types/types/objectBased/typesRecord.d.ts +2 -2
- package/dist/types/types/objectBased/typesRef.d.ts +3 -3
- package/dist/types/types/primitiveBased/typesEnum.d.ts +1 -1
- package/dist/types/types/primitiveBased/typesPrimitive.d.ts +3 -3
- package/dist/types/types/primitiveBased/typesRefinedPrimitive.d.ts +2 -2
- package/dist/types/types/schemas.d.ts +5 -5
- package/dist/types/types/tProp.d.ts +4 -4
- package/dist/types/types/typeCheck.d.ts +2 -2
- package/dist/types/types/types.d.ts +39 -39
- package/dist/types/types/utility/typeToStoredData.d.ts +2 -2
- package/dist/types/types/utility/typesBigInt.d.ts +1 -1
- package/dist/types/types/utility/typesCodec.d.ts +6 -6
- package/dist/types/types/utility/typesCodecCore.d.ts +4 -4
- package/dist/types/types/utility/typesCodecSupport.d.ts +4 -4
- package/dist/types/types/utility/typesDate.d.ts +2 -2
- package/dist/types/types/utility/typesMap.d.ts +2 -2
- package/dist/types/types/utility/typesMaybe.d.ts +1 -1
- package/dist/types/types/utility/typesOr.d.ts +2 -2
- package/dist/types/types/utility/typesRefinement.d.ts +3 -3
- package/dist/types/types/utility/typesSet.d.ts +3 -3
- package/dist/types/types/utility/typesSkipCheck.d.ts +2 -2
- package/dist/types/types/utility/typesTag.d.ts +2 -2
- package/dist/types/types/utility/typesUnchecked.d.ts +2 -2
- package/dist/types/utils/ModelPool.d.ts +1 -1
- package/dist/types/wrappers/ArraySet.d.ts +1 -1
- package/dist/types/wrappers/ObjectMap.d.ts +1 -1
- package/dist/types/wrappers/asMap.d.ts +1 -1
- package/dist/types/wrappers/asSet.d.ts +1 -1
- package/dist/types/wrappers/index.d.ts +4 -4
- package/package.json +10 -23
- package/src/action/modelAction.ts +1 -1
- package/src/action/modelFlow.ts +1 -1
- package/src/dataModel/BaseDataModel.ts +5 -5
- package/src/model/BaseModel.ts +7 -6
- package/src/model/mixins.ts +1 -1
- package/src/model/newModel.ts +1 -1
- package/src/modelShared/modelDecorator.ts +6 -11
- package/src/modelShared/modelInfo.ts +76 -0
- package/src/modelShared/sharedInternalModel.ts +1 -1
- package/src/ref/Ref.ts +1 -1
- package/src/snapshot/SnapshotterAndReconcilerPriority.ts +2 -1
- package/src/snapshot/fromModelSnapshot.ts +1 -1
- package/src/snapshot/fromSnapshot.ts +1 -1
- package/src/transforms/ImmutableDate.ts +19 -19
- package/src/tweaker/tweakArray.ts +1 -1
- package/src/tweaker/tweakFrozen.ts +1 -1
- package/src/tweaker/tweakModel.ts +1 -1
- package/src/tweaker/tweakPlainObject.ts +1 -1
- package/src/types/SnapshotTypeMismatchError.ts +3 -1
- package/src/types/arrayBased/typesArray.ts +1 -1
- package/src/types/arrayBased/typesTuple.ts +1 -1
- package/src/types/modelTypeMatching.ts +276 -0
- package/src/types/objectBased/typesArraySet.ts +1 -1
- package/src/types/objectBased/typesModel.ts +24 -7
- package/src/types/objectBased/typesObject.ts +1 -1
- package/src/types/objectBased/typesObjectMap.ts +1 -1
- package/src/types/objectBased/typesRecord.ts +1 -1
- package/src/types/objectBased/typesRef.ts +24 -5
- package/src/types/primitiveBased/typesPrimitive.ts +1 -1
- package/src/types/schemas.ts +1 -1
- package/src/types/utility/typesCodecCore.ts +4 -2
- package/src/types/utility/typesCodecSupport.ts +15 -7
- package/src/types/utility/typesOr.ts +40 -10
- package/src/types/utility/typesRefinement.ts +1 -1
- package/src/utils/index.ts +20 -13
- package/src/wrappers/ArraySet.ts +3 -5
- package/src/wrappers/ObjectMap.ts +3 -5
|
@@ -22,6 +22,82 @@ export const modelInfoByName: {
|
|
|
22
22
|
*/
|
|
23
23
|
export const modelInfoByClass = new WeakMap<ModelClass<AnyModel | AnyDataModel>, ModelInfo>()
|
|
24
24
|
|
|
25
|
+
// registered model prototypes (looked up rather than reading `constructor`, since that might be
|
|
26
|
+
// a model prop)
|
|
27
|
+
const modelInfoByPrototype = new WeakMap<object, ModelInfo>()
|
|
28
|
+
|
|
29
|
+
// for each registered model, how many registered models below each of its base models
|
|
30
|
+
// (including itself, at 0) it is, by name
|
|
31
|
+
const ancestorDistancesByModelInfo = new WeakMap<ModelInfo, ReadonlyMap<string, number>>()
|
|
32
|
+
// the same, by name for the current registration of each model name
|
|
33
|
+
const ancestorDistancesByModelName = new Map<string, ReadonlyMap<string, number>>()
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Finds the info of the first registered model class in a prototype chain.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export function findModelInfoForPrototype(proto: object | null): ModelInfo | undefined {
|
|
41
|
+
for (let p = proto; p; p = Object.getPrototypeOf(p)) {
|
|
42
|
+
const modelInfo = modelInfoByPrototype.get(p)
|
|
43
|
+
if (modelInfo) {
|
|
44
|
+
return modelInfo
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Registers a model.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export function setModelInfo(modelInfo: ModelInfo, unwrappedClass: ModelClass<any>): void {
|
|
56
|
+
// the base classes were registered before, so their distances are already known; names are
|
|
57
|
+
// used rather than classes since with hot reloading a subclass might still extend a previous
|
|
58
|
+
// registration of a model
|
|
59
|
+
const baseModelInfo = findModelInfoForPrototype(Object.getPrototypeOf(unwrappedClass.prototype))
|
|
60
|
+
const ancestorDistances = new Map<string, number>()
|
|
61
|
+
if (baseModelInfo) {
|
|
62
|
+
ancestorDistancesByModelInfo.get(baseModelInfo)?.forEach((distance, name) => {
|
|
63
|
+
ancestorDistances.set(name, distance + 1)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
ancestorDistances.set(modelInfo.name, 0)
|
|
67
|
+
ancestorDistancesByModelInfo.set(modelInfo, ancestorDistances)
|
|
68
|
+
ancestorDistancesByModelName.set(modelInfo.name, ancestorDistances)
|
|
69
|
+
|
|
70
|
+
modelInfoByName[modelInfo.name] = modelInfo
|
|
71
|
+
modelInfoByClass.set(modelInfo.class, modelInfo)
|
|
72
|
+
modelInfoByClass.set(unwrappedClass, modelInfo)
|
|
73
|
+
modelInfoByPrototype.set(unwrappedClass.prototype, modelInfo)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* How many registered models a model is below a base model (0 if it is that model), or -1 if
|
|
78
|
+
* it does not extend it.
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
export function getModelSubclassDistance(modelInfo: ModelInfo, baseModelName: string): number {
|
|
83
|
+
return ancestorDistancesByModelInfo.get(modelInfo)?.get(baseModelName) ?? -1
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* How many registered models the model currently registered with a name is below a base model
|
|
88
|
+
* (0 if it is that model), -1 if it does not extend it, or `undefined` if the name is not
|
|
89
|
+
* registered.
|
|
90
|
+
*
|
|
91
|
+
* @internal
|
|
92
|
+
*/
|
|
93
|
+
export function getModelSubclassDistanceForName(
|
|
94
|
+
modelName: string,
|
|
95
|
+
baseModelName: string
|
|
96
|
+
): number | undefined {
|
|
97
|
+
const ancestorDistances = ancestorDistancesByModelName.get(modelName)
|
|
98
|
+
return ancestorDistances ? (ancestorDistances.get(baseModelName) ?? -1) : undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
25
101
|
/**
|
|
26
102
|
* Returns the model info for a model type name, or `undefined` if not found.
|
|
27
103
|
*
|
|
@@ -11,8 +11,8 @@ import type { DataModelMetadata } from "../dataModel/getDataModelMetadata"
|
|
|
11
11
|
import { getGlobalConfig } from "../globalConfig/globalConfig"
|
|
12
12
|
import { type AnyModel, BaseModel, baseModelPropNames } from "../model/BaseModel"
|
|
13
13
|
import type { ModelMetadata } from "../model/getModelMetadata"
|
|
14
|
-
import type { ModelConstructorOptions } from "../model/ModelConstructorOptions"
|
|
15
14
|
import { modelTypeKey } from "../model/metadata"
|
|
15
|
+
import type { ModelConstructorOptions } from "../model/ModelConstructorOptions"
|
|
16
16
|
import { typesObject } from "../types/objectBased/typesObject"
|
|
17
17
|
import { typesString } from "../types/primitiveBased/typesPrimitive"
|
|
18
18
|
import type { AnyType } from "../types/schemas"
|
package/src/ref/Ref.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Model } from "../model/Model"
|
|
2
1
|
import { modelTypeKey } from "../model/metadata"
|
|
2
|
+
import { Model } from "../model/Model"
|
|
3
3
|
import type { ModelClass } from "../modelShared/BaseModelShared"
|
|
4
4
|
import { typesString } from "../types/primitiveBased/typesPrimitive"
|
|
5
5
|
import { tProp } from "../types/tProp"
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @internal
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
/* oxlint-disable typescript/prefer-enum-initializers -- that's ok, it is not serialized */
|
|
5
5
|
export enum SnapshotterAndReconcilerPriority {
|
|
6
6
|
Array,
|
|
7
7
|
Frozen,
|
|
8
8
|
Model,
|
|
9
9
|
PlainObject,
|
|
10
10
|
}
|
|
11
|
+
/* oxlint-enable typescript/prefer-enum-initializers */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AnyModel } from "../model/BaseModel"
|
|
2
2
|
import { getModelIdPropertyName } from "../model/getModelMetadata"
|
|
3
|
-
import type { ModelConstructorOptions } from "../model/ModelConstructorOptions"
|
|
4
3
|
import { modelTypeKey } from "../model/metadata"
|
|
4
|
+
import type { ModelConstructorOptions } from "../model/ModelConstructorOptions"
|
|
5
5
|
import { getModelOrSnapshotTypeAndId, getSnapshotModelType } from "../model/utils"
|
|
6
6
|
import type { ModelClass } from "../modelShared/BaseModelShared"
|
|
7
7
|
import { getModelInfoForName, getModelNotRegisteredErrorMessage } from "../modelShared/modelInfo"
|
|
@@ -83,7 +83,7 @@ export function fromSnapshot<T>(arg1: any, arg2: any, arg3?: any): T {
|
|
|
83
83
|
let snapshot: any
|
|
84
84
|
let unprocessedSnapshot: unknown
|
|
85
85
|
let options: Partial<FromSnapshotOptions> | undefined
|
|
86
|
-
//
|
|
86
|
+
// oxlint-disable-next-line eslint/prefer-rest-params -- overload resolution needs the real call arity to distinguish omitted snapshots from omitted type args.
|
|
87
87
|
const standardType = arguments.length >= 2 ? resolveStandardTypeNoThrow(arg1) : undefined
|
|
88
88
|
|
|
89
89
|
if (isLateTypeChecker(arg1) || arg1 instanceof TypeChecker || standardType) {
|
|
@@ -3,25 +3,25 @@ import { failure } from "../utils"
|
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- the class implements this narrowed Date API.
|
|
7
|
+
export interface ImmutableDate extends Omit<
|
|
8
|
+
Date,
|
|
9
|
+
| "setTime"
|
|
10
|
+
| "setMilliseconds"
|
|
11
|
+
| "setUTCMilliseconds"
|
|
12
|
+
| "setSeconds"
|
|
13
|
+
| "setUTCSeconds"
|
|
14
|
+
| "setMinutes"
|
|
15
|
+
| "setUTCMinutes"
|
|
16
|
+
| "setHours"
|
|
17
|
+
| "setUTCHours"
|
|
18
|
+
| "setDate"
|
|
19
|
+
| "setUTCDate"
|
|
20
|
+
| "setMonth"
|
|
21
|
+
| "setUTCMonth"
|
|
22
|
+
| "setFullYear"
|
|
23
|
+
| "setUTCFullYear"
|
|
24
|
+
> {}
|
|
25
25
|
|
|
26
26
|
const errMessage = "this Date object is immutable"
|
|
27
27
|
|
|
@@ -36,7 +36,6 @@ import { failure, inDevMode, isArray, isPrimitive } from "../utils"
|
|
|
36
36
|
import { addDelayedError, type DelayedError } from "../utils/forEachWithDelayedThrow"
|
|
37
37
|
import { recordChangeForRollback, throwDelayedListenerError } from "./changeRollback"
|
|
38
38
|
import { activateHandlers, areHandlersActive, runningWithoutSnapshotOrPatches } from "./core"
|
|
39
|
-
import { TweakerPriority } from "./TweakerPriority"
|
|
40
39
|
import { markAsTweakedObject } from "./treeNodeMetadata"
|
|
41
40
|
import {
|
|
42
41
|
assertCanAttach,
|
|
@@ -46,6 +45,7 @@ import {
|
|
|
46
45
|
tweak,
|
|
47
46
|
} from "./tweak"
|
|
48
47
|
import { notifyTweakedChangeListeners } from "./tweakedChangeListeners"
|
|
48
|
+
import { TweakerPriority } from "./TweakerPriority"
|
|
49
49
|
import { isTypeCheckingAfterChangeEnabled, runTypeCheckingAfterChange } from "./typeChecking"
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -2,9 +2,9 @@ import { Frozen, frozenKey } from "../frozen/Frozen"
|
|
|
2
2
|
import type { ParentPath } from "../parent/path"
|
|
3
3
|
import { setParent } from "../parent/setParent"
|
|
4
4
|
import { setNewInternalSnapshot } from "../snapshot/internal"
|
|
5
|
-
import { TweakerPriority } from "./TweakerPriority"
|
|
6
5
|
import { markAsTweakedObject } from "./treeNodeMetadata"
|
|
7
6
|
import { registerTweaker } from "./tweak"
|
|
7
|
+
import { TweakerPriority } from "./TweakerPriority"
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @internal
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { isModel } from "../model/utils"
|
|
2
2
|
import type { ParentPath } from "../parent/path"
|
|
3
3
|
import { setParent } from "../parent/setParent"
|
|
4
|
-
import { TweakerPriority } from "./TweakerPriority"
|
|
5
4
|
import { markAsTweakedObject } from "./treeNodeMetadata"
|
|
6
5
|
import { registerTweaker } from "./tweak"
|
|
6
|
+
import { TweakerPriority } from "./TweakerPriority"
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @internal
|
|
@@ -43,10 +43,10 @@ import { addDelayedError, type DelayedError } from "../utils/forEachWithDelayedT
|
|
|
43
43
|
import { setIfDifferent } from "../utils/setIfDifferent"
|
|
44
44
|
import { recordChangeForRollback, throwDelayedListenerError } from "./changeRollback"
|
|
45
45
|
import { activateHandlers, runningWithoutSnapshotOrPatches } from "./core"
|
|
46
|
-
import { TweakerPriority } from "./TweakerPriority"
|
|
47
46
|
import { markAsTweakedObject, treeNodeMetadata } from "./treeNodeMetadata"
|
|
48
47
|
import { assertCanAttachValue, registerTweaker, tweak } from "./tweak"
|
|
49
48
|
import { notifyTweakedChangeListeners } from "./tweakedChangeListeners"
|
|
49
|
+
import { TweakerPriority } from "./TweakerPriority"
|
|
50
50
|
import { isTypeCheckingAfterChangeEnabled, runTypeCheckingAfterChange } from "./typeChecking"
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -14,7 +14,9 @@ export interface SnapshotTypeMismatchErrorData {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Thrown when a snapshot value does not match any of the expected types in a union
|
|
17
|
+
* Thrown when a snapshot value does not match any of the expected types in a union, or when a
|
|
18
|
+
* model snapshot names a model (`$modelType`) that is not the expected model or a subclass of it
|
|
19
|
+
* (extends `MobxKeystoneError`).
|
|
18
20
|
*
|
|
19
21
|
* Use `instanceof SnapshotTypeMismatchError` to distinguish snapshot type-mismatch errors
|
|
20
22
|
* from other `MobxKeystoneError` instances.
|
|
@@ -5,7 +5,6 @@ import { createChunkedArrayCachedCheck } from "../createCachedTypeCheck"
|
|
|
5
5
|
import { getTypeInfo } from "../getTypeInfo"
|
|
6
6
|
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
7
7
|
import type { AnyStandardType, AnyType, ArrayType } from "../schemas"
|
|
8
|
-
import { TypeCheckError } from "../TypeCheckError"
|
|
9
8
|
import {
|
|
10
9
|
lateTypeChecker,
|
|
11
10
|
snapshotProcessorPlan,
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
TypeInfo,
|
|
15
14
|
type TypeInfoGen,
|
|
16
15
|
} from "../TypeChecker"
|
|
16
|
+
import { TypeCheckError } from "../TypeCheckError"
|
|
17
17
|
import { prependPathElementToTypeCheckError } from "../typeCheckErrorUtils"
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -5,7 +5,6 @@ import { createWholeContainerCachedCheck } from "../createCachedTypeCheck"
|
|
|
5
5
|
import { getTypeInfo } from "../getTypeInfo"
|
|
6
6
|
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
7
7
|
import type { AnyStandardType, AnyType, ArrayType } from "../schemas"
|
|
8
|
-
import { TypeCheckError } from "../TypeCheckError"
|
|
9
8
|
import {
|
|
10
9
|
lateTypeChecker,
|
|
11
10
|
snapshotProcessorPlan,
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
TypeInfo,
|
|
15
14
|
type TypeInfoGen,
|
|
16
15
|
} from "../TypeChecker"
|
|
16
|
+
import { TypeCheckError } from "../TypeCheckError"
|
|
17
17
|
import { prependPathElementToTypeCheckError } from "../typeCheckErrorUtils"
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { modelTypeKey } from "../model/metadata"
|
|
2
|
+
import {
|
|
3
|
+
findModelInfoForPrototype,
|
|
4
|
+
getModelSubclassDistance,
|
|
5
|
+
getModelSubclassDistanceForName,
|
|
6
|
+
} from "../modelShared/modelInfo"
|
|
7
|
+
import { isObject } from "../utils"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Model type matching pass used while unions match a value against their branches, so they can
|
|
11
|
+
* prefer the branches whose models are the closest to the ones named by the snapshot
|
|
12
|
+
* (`$modelType`):
|
|
13
|
+
* - `none`: not matching union branches; model types match snapshots of their own model and of
|
|
14
|
+
* its subclasses.
|
|
15
|
+
* - `exact`: model types only match snapshots of their own model.
|
|
16
|
+
* - `closest`: model types also match snapshots of subclasses, adding how far the subclass is
|
|
17
|
+
* from the model to `subclassDistance`, so unions can pick the branch with the lowest total.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export type ModelTypeMatchingPass = "none" | "exact" | "closest"
|
|
22
|
+
|
|
23
|
+
let currentPass: ModelTypeMatchingPass = "none"
|
|
24
|
+
let subclassDistance = 0
|
|
25
|
+
let rejectedModelSnapshot = false
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export function getModelTypeMatchingPass(): ModelTypeMatchingPass {
|
|
31
|
+
return currentPass
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Records that a model type matched a snapshot of a subclass `distance` levels below it.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function addModelSubclassDistance(distance: number): void {
|
|
40
|
+
if (currentPass === "closest") {
|
|
41
|
+
subclassDistance += distance
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Records, while unions match their branches, that a model type (or ref) rejected a model
|
|
47
|
+
* snapshot that some other branch could accept: one of a subclass in the exact pass (it would
|
|
48
|
+
* match it in the closest pass), or one of another registered model.
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export function rejectModelSnapshot(): void {
|
|
53
|
+
if (currentPass !== "none") {
|
|
54
|
+
rejectedModelSnapshot = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Runs a value match, returning whether it matched, or `undefined` if it did not because a model
|
|
60
|
+
* type (or ref) rejected a model snapshot (see `rejectModelSnapshot`).
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export function matchUnlessModelSnapshotRejected<A, B>(
|
|
65
|
+
fn: (a: A, b: B) => unknown,
|
|
66
|
+
a: A,
|
|
67
|
+
b: B
|
|
68
|
+
): boolean | undefined {
|
|
69
|
+
const prevRejectedModelSnapshot = rejectedModelSnapshot
|
|
70
|
+
rejectedModelSnapshot = false
|
|
71
|
+
try {
|
|
72
|
+
const matched = !!fn(a, b)
|
|
73
|
+
return matched || !rejectedModelSnapshot ? matched : undefined
|
|
74
|
+
} finally {
|
|
75
|
+
// let outer matches know too
|
|
76
|
+
rejectedModelSnapshot ||= prevRejectedModelSnapshot
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Whether a snapshot `$modelType` matches a model (`modelName`): its own model or, outside the
|
|
82
|
+
* exact pass, a subclass of it. Registered names are compared rather than classes, since with
|
|
83
|
+
* hot reloading a subclass might still extend a previous registration of the model.
|
|
84
|
+
*
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
export function snapshotModelTypeMatches(snModelType: unknown, modelName: string): boolean {
|
|
88
|
+
if (typeof snModelType !== "string") {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
if (snModelType === modelName) {
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
const distance = getModelSubclassDistanceForName(snModelType, modelName)
|
|
95
|
+
if (distance === undefined) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
if (distance < 0 || currentPass === "exact") {
|
|
99
|
+
rejectModelSnapshot()
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
addModelSubclassDistance(distance)
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whether a snapshot `$modelType` names a registered model that is neither a model
|
|
108
|
+
* (`modelName`) nor a subclass of it, so processing the snapshot as that model would silently
|
|
109
|
+
* create an unrelated model (unregistered names are reported later by the model snapshotter).
|
|
110
|
+
*
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
export function isUnrelatedSnapshotModelType(snModelType: unknown, modelName: string): boolean {
|
|
114
|
+
return (
|
|
115
|
+
typeof snModelType === "string" &&
|
|
116
|
+
snModelType !== modelName &&
|
|
117
|
+
(getModelSubclassDistanceForName(snModelType, modelName) ?? 0) < 0
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Whether a model instance is of a model (`modelName`) or of a subclass of it, comparing
|
|
123
|
+
* registered names, since with hot reloading the instance model might still extend a previous
|
|
124
|
+
* registration of the model.
|
|
125
|
+
*
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
export function isModelInstanceOfModelName(value: object, modelName: string): boolean {
|
|
129
|
+
const valueModelInfo = findModelInfoForPrototype(Object.getPrototypeOf(value))
|
|
130
|
+
return !!valueModelInfo && getModelSubclassDistance(valueModelInfo, modelName) >= 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether a value is a snapshot that names its model (`$modelType`).
|
|
135
|
+
*
|
|
136
|
+
* @internal
|
|
137
|
+
*/
|
|
138
|
+
export function hasSnapshotModelType(value: unknown): boolean {
|
|
139
|
+
return isObject(value) && typeof value[modelTypeKey] === "string"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Runs a function in the given model type matching pass, restoring the current one afterwards.
|
|
144
|
+
*
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
export function runInModelTypeMatchingPass<A, B, R>(
|
|
148
|
+
pass: ModelTypeMatchingPass,
|
|
149
|
+
fn: (a: A, b: B) => R,
|
|
150
|
+
a: A,
|
|
151
|
+
b: B
|
|
152
|
+
): R {
|
|
153
|
+
if (pass === "none" && currentPass === "none") {
|
|
154
|
+
return fn(a, b)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const prevPass = currentPass
|
|
158
|
+
const prevDistance = subclassDistance
|
|
159
|
+
const prevRejectedModelSnapshot = rejectedModelSnapshot
|
|
160
|
+
currentPass = pass
|
|
161
|
+
subclassDistance = 0
|
|
162
|
+
rejectedModelSnapshot = false
|
|
163
|
+
try {
|
|
164
|
+
return fn(a, b)
|
|
165
|
+
} finally {
|
|
166
|
+
currentPass = prevPass
|
|
167
|
+
subclassDistance = prevDistance
|
|
168
|
+
rejectedModelSnapshot = prevRejectedModelSnapshot
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Finds the union branch (candidate) that matches a value, preferring branches that match the
|
|
174
|
+
* exact models named by the snapshot (branches without models count as exact matches), then
|
|
175
|
+
* the branches whose models are the closest base models of the snapshot ones (the fewest
|
|
176
|
+
* subclass levels away in total), and the first one on ties.
|
|
177
|
+
*
|
|
178
|
+
* @internal
|
|
179
|
+
*/
|
|
180
|
+
export function findClosestModelTypeMatch<T, R>(
|
|
181
|
+
candidates: ReadonlyArray<T>,
|
|
182
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
183
|
+
value: unknown
|
|
184
|
+
): R | undefined {
|
|
185
|
+
switch (currentPass) {
|
|
186
|
+
case "exact":
|
|
187
|
+
// nested inside an exact pass
|
|
188
|
+
return findNestedMatch(findFirstMatch, candidates, match, value, 0)
|
|
189
|
+
case "closest":
|
|
190
|
+
// nested inside a closest pass
|
|
191
|
+
return findNestedMatch(findClosestMatch, candidates, match, value, 0)
|
|
192
|
+
default:
|
|
193
|
+
try {
|
|
194
|
+
currentPass = "exact"
|
|
195
|
+
const exactMatch = findFirstMatch(candidates, match, value)
|
|
196
|
+
if (exactMatch != null) {
|
|
197
|
+
return exactMatch
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
currentPass = "closest"
|
|
201
|
+
subclassDistance = 0
|
|
202
|
+
// no branch matched exactly, so none can be less than one subclass level away
|
|
203
|
+
return findClosestMatch(candidates, match, value, 1)
|
|
204
|
+
} finally {
|
|
205
|
+
currentPass = "none"
|
|
206
|
+
subclassDistance = 0
|
|
207
|
+
rejectedModelSnapshot = false
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function findNestedMatch<T, R>(
|
|
213
|
+
find: (
|
|
214
|
+
candidates: ReadonlyArray<T>,
|
|
215
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
216
|
+
value: unknown,
|
|
217
|
+
minPossibleDistance: number
|
|
218
|
+
) => R | undefined,
|
|
219
|
+
candidates: ReadonlyArray<T>,
|
|
220
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
221
|
+
value: unknown,
|
|
222
|
+
minPossibleDistance: number
|
|
223
|
+
): R | undefined {
|
|
224
|
+
const prevRejectedModelSnapshot = rejectedModelSnapshot
|
|
225
|
+
const result = find(candidates, match, value, minPossibleDistance)
|
|
226
|
+
if (result != null) {
|
|
227
|
+
// model snapshots rejected by other branches do not matter then
|
|
228
|
+
rejectedModelSnapshot = prevRejectedModelSnapshot
|
|
229
|
+
}
|
|
230
|
+
return result
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Finds the first candidate that matches a value.
|
|
235
|
+
*
|
|
236
|
+
* @internal
|
|
237
|
+
*/
|
|
238
|
+
export function findFirstMatch<T, R>(
|
|
239
|
+
candidates: ReadonlyArray<T>,
|
|
240
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
241
|
+
value: unknown
|
|
242
|
+
): R | undefined {
|
|
243
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
244
|
+
const result = match(candidates[i], value)
|
|
245
|
+
if (result != null) {
|
|
246
|
+
return result
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return undefined
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function findClosestMatch<T, R>(
|
|
253
|
+
candidates: ReadonlyArray<T>,
|
|
254
|
+
match: (candidate: T, value: unknown) => R | null | undefined,
|
|
255
|
+
value: unknown,
|
|
256
|
+
minPossibleDistance: number
|
|
257
|
+
): R | undefined {
|
|
258
|
+
const prevDistance = subclassDistance
|
|
259
|
+
let best: R | undefined
|
|
260
|
+
let bestDistance = Number.POSITIVE_INFINITY
|
|
261
|
+
|
|
262
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
263
|
+
subclassDistance = 0
|
|
264
|
+
const result = match(candidates[i], value)
|
|
265
|
+
if (result != null && subclassDistance < bestDistance) {
|
|
266
|
+
best = result
|
|
267
|
+
bestDistance = subclassDistance
|
|
268
|
+
if (bestDistance <= minPossibleDistance) {
|
|
269
|
+
break
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
subclassDistance = prevDistance + (best != null ? bestDistance : 0)
|
|
275
|
+
return best
|
|
276
|
+
}
|
|
@@ -7,7 +7,6 @@ import { typesArray } from "../arrayBased/typesArray"
|
|
|
7
7
|
import { getTypeInfo } from "../getTypeInfo"
|
|
8
8
|
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
9
9
|
import type { AnyStandardType, AnyType, ModelType, TypeToData } from "../schemas"
|
|
10
|
-
import { TypeCheckError } from "../TypeCheckError"
|
|
11
10
|
import {
|
|
12
11
|
lateTypeChecker,
|
|
13
12
|
snapshotProcessorPlan,
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
TypeInfo,
|
|
17
16
|
type TypeInfoGen,
|
|
18
17
|
} from "../TypeChecker"
|
|
18
|
+
import { TypeCheckError } from "../TypeCheckError"
|
|
19
19
|
import { resolveCodecSupport } from "../utility/typesCodec"
|
|
20
20
|
import { typesObject } from "./typesObject"
|
|
21
21
|
|
|
@@ -2,16 +2,21 @@ import type { O } from "ts-toolbelt"
|
|
|
2
2
|
import type { AnyModel } from "../../model/BaseModel"
|
|
3
3
|
import { getModelMetadata } from "../../model/getModelMetadata"
|
|
4
4
|
import { modelTypeKey } from "../../model/metadata"
|
|
5
|
-
import { isModelClass } from "../../model/utils"
|
|
5
|
+
import { isModel, isModelClass } from "../../model/utils"
|
|
6
6
|
import type { ModelClass } from "../../modelShared/BaseModelShared"
|
|
7
7
|
import { modelInfoByClass } from "../../modelShared/modelInfo"
|
|
8
8
|
import { getInternalModelClassPropsInfo } from "../../modelShared/modelPropsInfo"
|
|
9
9
|
import { noDefaultValue } from "../../modelShared/prop"
|
|
10
10
|
import { isObject, lazy, setProtoProp } from "../../utils"
|
|
11
11
|
import { getTypeInfo } from "../getTypeInfo"
|
|
12
|
+
import {
|
|
13
|
+
isModelInstanceOfModelName,
|
|
14
|
+
isUnrelatedSnapshotModelType,
|
|
15
|
+
snapshotModelTypeMatches,
|
|
16
|
+
} from "../modelTypeMatching"
|
|
12
17
|
import { registerStandardTypeResolver, resolveTypeChecker } from "../resolveTypeChecker"
|
|
13
18
|
import type { AnyStandardType, ModelType } from "../schemas"
|
|
14
|
-
import {
|
|
19
|
+
import { SnapshotTypeMismatchError } from "../SnapshotTypeMismatchError"
|
|
15
20
|
import {
|
|
16
21
|
lateTypeChecker,
|
|
17
22
|
TypeChecker,
|
|
@@ -19,6 +24,7 @@ import {
|
|
|
19
24
|
TypeInfo,
|
|
20
25
|
type TypeInfoGen,
|
|
21
26
|
} from "../TypeChecker"
|
|
27
|
+
import { TypeCheckError } from "../TypeCheckError"
|
|
22
28
|
|
|
23
29
|
const cachedModelTypeChecker = new WeakMap<ModelClass<AnyModel>, TypeChecker>()
|
|
24
30
|
const modelStandardTypes = new WeakSet<object>()
|
|
@@ -73,7 +79,10 @@ export function typesModel<M = never, K = M>(modelClass: _ClassOrObject<M, K>):
|
|
|
73
79
|
TypeCheckerBaseType.Object,
|
|
74
80
|
|
|
75
81
|
(value, path, typeCheckedValue) => {
|
|
76
|
-
if (
|
|
82
|
+
if (
|
|
83
|
+
!(value instanceof modelClazz) &&
|
|
84
|
+
!(isModel(value) && isModelInstanceOfModelName(value, modelInfo.name))
|
|
85
|
+
) {
|
|
77
86
|
return new TypeCheckError({
|
|
78
87
|
path,
|
|
79
88
|
expectedTypeName: typeName,
|
|
@@ -96,9 +105,9 @@ export function typesModel<M = never, K = M>(modelClass: _ClassOrObject<M, K>):
|
|
|
96
105
|
return null
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
108
|
+
const snModelType = value[modelTypeKey]
|
|
109
|
+
if (snModelType !== undefined) {
|
|
110
|
+
return snapshotModelTypeMatches(snModelType, modelInfo.name) ? thisTc : null
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
if (resolvedDataTypeChecker) {
|
|
@@ -112,7 +121,15 @@ export function typesModel<M = never, K = M>(modelClass: _ClassOrObject<M, K>):
|
|
|
112
121
|
// No withErrorPathSegment wrapping needed here — model snapshot processors
|
|
113
122
|
// delegate to the data type checker, which handles path segments at the property level.
|
|
114
123
|
(sn) => {
|
|
115
|
-
|
|
124
|
+
const snModelType = sn[modelTypeKey]
|
|
125
|
+
if (typeof snModelType === "string") {
|
|
126
|
+
// the snapshot names its own model; make sure it is this model or a subclass of it
|
|
127
|
+
if (isUnrelatedSnapshotModelType(snModelType, modelInfo.name)) {
|
|
128
|
+
throw new SnapshotTypeMismatchError({
|
|
129
|
+
expectedTypeName: typeName,
|
|
130
|
+
actualValue: sn,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
116
133
|
return sn
|
|
117
134
|
} else {
|
|
118
135
|
return {
|
|
@@ -23,7 +23,6 @@ import type {
|
|
|
23
23
|
ObjectTypeFunction,
|
|
24
24
|
TypeToData,
|
|
25
25
|
} from "../schemas"
|
|
26
|
-
import { TypeCheckError } from "../TypeCheckError"
|
|
27
26
|
import {
|
|
28
27
|
type LateTypeChecker,
|
|
29
28
|
lateTypeChecker,
|
|
@@ -34,6 +33,7 @@ import {
|
|
|
34
33
|
TypeInfo,
|
|
35
34
|
type TypeInfoGen,
|
|
36
35
|
} from "../TypeChecker"
|
|
36
|
+
import { TypeCheckError } from "../TypeCheckError"
|
|
37
37
|
import { prependPathElementToTypeCheckError } from "../typeCheckErrorUtils"
|
|
38
38
|
|
|
39
39
|
const needsObservableObjectKeyTracking = getMobxVersion() < 5
|