mobx-keystone 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -1
  3. package/dist/mobx-keystone.esm.js +555 -516
  4. package/dist/mobx-keystone.esm.mjs +555 -516
  5. package/dist/mobx-keystone.umd.js +558 -519
  6. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +2 -10
  7. package/dist/types/modelShared/modelDecorator.d.ts +5 -2
  8. package/dist/types/modelShared/prop.d.ts +42 -29
  9. package/dist/types/ref/Ref.d.ts +1 -4
  10. package/dist/types/snapshot/SnapshotOf.d.ts +4 -4
  11. package/dist/types/snapshot/applySnapshot.d.ts +1 -1
  12. package/dist/types/snapshot/fromSnapshot.d.ts +2 -2
  13. package/dist/types/snapshot/getSnapshot.d.ts +1 -1
  14. package/dist/types/types/SnapshotTypeMismatchError.d.ts +3 -1
  15. package/dist/types/types/index.d.ts +1 -0
  16. package/dist/types/types/modelTypeMatching.d.ts +1 -0
  17. package/dist/types/types/schemas.d.ts +41 -37
  18. package/dist/types/types/tProp.d.ts +12 -6
  19. package/dist/types/types/types.d.ts +6 -6
  20. package/dist/types/types/utility/typeToStoredData.d.ts +14 -10
  21. package/dist/types/types/utility/typesCodecCore.d.ts +3 -0
  22. package/dist/types/utils/ModelPool.d.ts +0 -1
  23. package/dist/types/utils/types.d.ts +6 -0
  24. package/dist/types/wrappers/ArraySet.d.ts +1 -0
  25. package/package.json +13 -11
  26. package/src/action/modelFlow.ts +35 -66
  27. package/src/actionMiddlewares/actionTrackingMiddleware.ts +41 -71
  28. package/src/actionMiddlewares/transactionMiddleware.ts +1 -1
  29. package/src/actionMiddlewares/undoMiddleware.ts +16 -22
  30. package/src/context/context.ts +15 -36
  31. package/src/dataModel/utils.ts +5 -28
  32. package/src/deepChange/onDeepChange.ts +44 -126
  33. package/src/model/BaseModel.ts +1 -1
  34. package/src/model/utils.ts +5 -10
  35. package/src/modelShared/modelDecorator.ts +13 -10
  36. package/src/modelShared/modelInfo.ts +75 -0
  37. package/src/modelShared/prop.ts +115 -111
  38. package/src/modelShared/sharedInternalModel.ts +2 -2
  39. package/src/parent/arrayPaths.ts +1 -4
  40. package/src/parent/path.ts +0 -10
  41. package/src/patch/patchRecorder.ts +13 -28
  42. package/src/ref/core.ts +6 -19
  43. package/src/rootStore/rootStore.ts +1 -3
  44. package/src/snapshot/SnapshotOf.ts +24 -48
  45. package/src/snapshot/applySnapshot.ts +1 -1
  46. package/src/snapshot/fromSnapshot.ts +10 -3
  47. package/src/snapshot/getSnapshot.ts +1 -1
  48. package/src/snapshot/internal.ts +0 -2
  49. package/src/snapshot/reconcileModelSnapshot.ts +5 -3
  50. package/src/snapshot/reconcilePlainObjectSnapshot.ts +3 -1
  51. package/src/treeUtils/draft.ts +6 -1
  52. package/src/treeUtils/sandbox.ts +10 -7
  53. package/src/tweaker/tweakArray.ts +20 -50
  54. package/src/tweaker/tweakPlainObject.ts +9 -15
  55. package/src/types/SnapshotTypeMismatchError.ts +3 -1
  56. package/src/types/arrayBased/typesArray.ts +13 -19
  57. package/src/types/arrayBased/typesTuple.ts +16 -25
  58. package/src/types/createCachedTypeCheck.ts +17 -30
  59. package/src/types/index.ts +1 -0
  60. package/src/types/modelTypeMatching.ts +276 -0
  61. package/src/types/objectBased/typesDataModelData.ts +7 -8
  62. package/src/types/objectBased/typesModel.ts +23 -6
  63. package/src/types/objectBased/typesObject.ts +11 -15
  64. package/src/types/objectBased/typesRecord.ts +11 -15
  65. package/src/types/objectBased/typesRef.ts +23 -4
  66. package/src/types/schemas.ts +59 -71
  67. package/src/types/tProp.ts +29 -29
  68. package/src/types/utility/typeToStoredData.ts +22 -21
  69. package/src/types/utility/typesCodecCore.ts +8 -1
  70. package/src/types/utility/typesCodecSupport.ts +16 -10
  71. package/src/types/utility/typesMap.ts +43 -51
  72. package/src/types/utility/typesOr.ts +38 -8
  73. package/src/types/utility/typesRefinement.ts +7 -8
  74. package/src/types/utility/typesSkipCheck.ts +7 -8
  75. package/src/types/utility/typesTag.ts +7 -8
  76. package/src/utils/ModelPool.ts +1 -18
  77. package/src/utils/decorators.ts +5 -5
  78. package/src/utils/index.ts +1 -20
  79. package/src/utils/types.ts +5 -0
  80. package/src/wrappers/ArraySet.ts +27 -14
  81. package/src/wrappers/ObjectMap.ts +1 -6
  82. package/src/wrappers/asMap.ts +1 -21
  83. package/src/wrappers/asSet.ts +0 -8
@@ -1,14 +1,11 @@
1
- import type { O } from "ts-toolbelt"
2
1
  import type { AnyDataModel } from "../dataModel/BaseDataModel"
3
2
  import type { AnyModel } from "../model/BaseModel"
4
3
  import type { ModelClass } from "../modelShared/BaseModelShared"
5
4
  import type { SnapshotInOf, SnapshotOutOf } from "../snapshot/SnapshotOf"
6
- import type { IsOptionalValue } from "../utils/types"
5
+ import type { IsOptionalValue, SimplifyObject } from "../utils/types"
7
6
 
8
7
  // type schemas
9
8
 
10
- // infer is there just to cache type generation
11
-
12
9
  export interface Type<Name, Data> {
13
10
  /** @ignore */
14
11
  $$type: Name
@@ -38,21 +35,21 @@ export interface CodecType<
38
35
  }
39
36
 
40
37
  type ArrayData<S extends readonly unknown[]> = number extends S["length"]
41
- ? Array<TypeToData<S[number]> extends infer R ? R : never>
38
+ ? Array<TypeToData<S[number]>>
42
39
  : {
43
- [k in keyof S]: TypeToData<S[k]> extends infer R ? R : never
40
+ [k in keyof S]: TypeToData<S[k]>
44
41
  }
45
42
 
46
43
  type ArraySnapshotInData<S extends readonly unknown[]> = number extends S["length"]
47
- ? Array<TypeToSnapshotIn<S[number]> extends infer R ? R : never>
44
+ ? Array<TypeToSnapshotIn<S[number]>>
48
45
  : {
49
- [k in keyof S]: TypeToSnapshotIn<S[k]> extends infer R ? R : never
46
+ [k in keyof S]: TypeToSnapshotIn<S[k]>
50
47
  }
51
48
 
52
49
  type ArraySnapshotOutData<S extends readonly unknown[]> = number extends S["length"]
53
- ? Array<TypeToSnapshotOut<S[number]> extends infer R ? R : never>
50
+ ? Array<TypeToSnapshotOut<S[number]>>
54
51
  : {
55
- [k in keyof S]: TypeToSnapshotOut<S[k]> extends infer R ? R : never
52
+ [k in keyof S]: TypeToSnapshotOut<S[k]>
56
53
  }
57
54
 
58
55
  export interface ArrayType<S extends readonly unknown[]> extends Type<"array", ArrayData<S>> {}
@@ -74,37 +71,28 @@ export type UndefinablePropsNames<T> = {
74
71
  * Computes the optional keys for an object type schema.
75
72
  * @ignore
76
73
  */
77
- export type ObjectOptionalKeys<S> = UndefinablePropsNames<{
78
- [k in keyof S]: TypeToDataOpt<S[k]> extends infer R ? R : never
79
- }>
80
-
81
- type ObjectData<S> = O.Optional<
82
- { [k in keyof S]: TypeToData<S[k]> extends infer R ? R : never },
83
- ObjectOptionalKeys<S>
74
+ export type ObjectOptionalKeys<S> = {
75
+ [K in keyof S]: IsOptionalValue<TypeToDataOpt<S[K]>, K, never>
76
+ }[keyof S]
77
+
78
+ type ObjectData<S, OK = ObjectOptionalKeys<S>> = SimplifyObject<
79
+ { [k in keyof S as k extends OK ? never : k]: TypeToData<S[k]> } & {
80
+ [k in keyof S as k extends OK ? k : never]?: TypeToData<S[k]>
81
+ }
84
82
  >
85
83
 
86
- type ObjectSnapshotInData<S> = O.Optional<
87
- { [k in keyof S]: TypeToSnapshotIn<S[k]> extends infer R ? R : never },
88
- ObjectOptionalKeys<S>
84
+ type ObjectSnapshotInData<S, OK = ObjectOptionalKeys<S>> = SimplifyObject<
85
+ { [k in keyof S as k extends OK ? never : k]: TypeToSnapshotIn<S[k]> } & {
86
+ [k in keyof S as k extends OK ? k : never]?: TypeToSnapshotIn<S[k]>
87
+ }
89
88
  >
90
89
 
91
- type ObjectSnapshotOutData<S> = O.Optional<
92
- { [k in keyof S]: TypeToSnapshotOut<S[k]> extends infer R ? R : never },
93
- ObjectOptionalKeys<S>
90
+ type ObjectSnapshotOutData<S, OK = ObjectOptionalKeys<S>> = SimplifyObject<
91
+ { [k in keyof S as k extends OK ? never : k]: TypeToSnapshotOut<S[k]> } & {
92
+ [k in keyof S as k extends OK ? k : never]?: TypeToSnapshotOut<S[k]>
93
+ }
94
94
  >
95
95
 
96
- type RecordData<S> = {
97
- [k: string]: TypeToData<S> extends infer R ? R : never
98
- }
99
-
100
- type RecordSnapshotInData<S> = {
101
- [k: string]: TypeToSnapshotIn<S> extends infer D ? D : never
102
- }
103
-
104
- type RecordSnapshotOutData<S> = {
105
- [k: string]: TypeToSnapshotOut<S> extends infer D ? D : never
106
- }
107
-
108
96
  export interface ObjectType<S> extends Type<"object", ObjectData<S>> {}
109
97
 
110
98
  export interface ObjectTypeFunction {
@@ -112,7 +100,9 @@ export interface ObjectTypeFunction {
112
100
  (): ObjectOfTypes
113
101
  }
114
102
 
115
- export interface RecordType<S> extends Type<"record", RecordData<S>> {}
103
+ // the record data types are written inline (instead of through an alias) so they are displayed as
104
+ // plain index signatures (e.g. `{ [k: string]: number }`)
105
+ export interface RecordType<S> extends Type<"record", { [k: string]: TypeToData<S> }> {}
116
106
 
117
107
  export type AnyStandardType =
118
108
  | IdentityType<any>
@@ -162,47 +152,45 @@ type TypeToSnapshotLeaf<S, Dir extends "in" | "out"> =
162
152
  ? Dir extends "in"
163
153
  ? SnapshotInOf<M>
164
154
  : SnapshotOutOf<M>
165
- : S extends { $$data: infer D }
166
- ? D
167
- : ConstructorToSnapshotData<S>
155
+ : ConstructorToSnapshotData<S>
168
156
 
169
157
  export type TypeToData<S> = S extends ObjectTypeFunction
170
- ? ObjectType<ReturnType<S>>["$$data"] extends infer R
171
- ? R
172
- : never
158
+ ? ObjectType<ReturnType<S>>["$$data"]
173
159
  : TypeToDataLeaf<S>
174
160
 
161
+ // standard types are dispatched through their `$$type` discriminant (and codec / identity types
162
+ // through indexed accesses), which is cheaper than checking them against each type interface
175
163
  export type TypeToSnapshotIn<S> = S extends ObjectTypeFunction
176
- ? ObjectSnapshotInData<ReturnType<S>> extends infer R
177
- ? R
178
- : never
179
- : S extends CodecType<any, infer D, any>
180
- ? D
181
- : S extends ArrayType<infer A>
182
- ? ArraySnapshotInData<A>
183
- : S extends ObjectType<infer O>
184
- ? ObjectSnapshotInData<O>
185
- : S extends RecordType<infer R>
186
- ? RecordSnapshotInData<R>
187
- : S extends ModelType<infer M>
188
- ? SnapshotInOf<M>
189
- : TypeToSnapshotLeaf<S, "in">
164
+ ? ObjectSnapshotInData<ReturnType<S>>
165
+ : S extends { $$type: infer K }
166
+ ? K extends "codec"
167
+ ? S["$$snapshotIn" & keyof S]
168
+ : S extends ArrayType<infer A>
169
+ ? ArraySnapshotInData<A>
170
+ : S extends ObjectType<infer O>
171
+ ? ObjectSnapshotInData<O>
172
+ : S extends RecordType<infer R>
173
+ ? { [k: string]: TypeToSnapshotIn<R> }
174
+ : S extends ModelType<infer M>
175
+ ? SnapshotInOf<M>
176
+ : S["$$data" & keyof S]
177
+ : TypeToSnapshotLeaf<S, "in">
190
178
 
191
179
  export type TypeToSnapshotOut<S> = S extends ObjectTypeFunction
192
- ? ObjectSnapshotOutData<ReturnType<S>> extends infer R
193
- ? R
194
- : never
195
- : S extends CodecType<any, any, infer D>
196
- ? D
197
- : S extends ArrayType<infer A>
198
- ? ArraySnapshotOutData<A>
199
- : S extends ObjectType<infer O>
200
- ? ObjectSnapshotOutData<O>
201
- : S extends RecordType<infer R>
202
- ? RecordSnapshotOutData<R>
203
- : S extends ModelType<infer M>
204
- ? SnapshotOutOf<M>
205
- : TypeToSnapshotLeaf<S, "out">
180
+ ? ObjectSnapshotOutData<ReturnType<S>>
181
+ : S extends { $$type: infer K }
182
+ ? K extends "codec"
183
+ ? S["$$snapshotOut" & keyof S]
184
+ : S extends ArrayType<infer A>
185
+ ? ArraySnapshotOutData<A>
186
+ : S extends ObjectType<infer O>
187
+ ? ObjectSnapshotOutData<O>
188
+ : S extends RecordType<infer R>
189
+ ? { [k: string]: TypeToSnapshotOut<R> }
190
+ : S extends ModelType<infer M>
191
+ ? SnapshotOutOf<M>
192
+ : S["$$data" & keyof S]
193
+ : TypeToSnapshotLeaf<S, "out">
206
194
 
207
195
  /** @ignore */
208
- export type TypeToDataOpt<S> = S extends { $$data: infer D } ? D & undefined : never
196
+ export type TypeToDataOpt<S> = S extends { $$data: unknown } ? S["$$data"] & undefined : never
@@ -6,6 +6,7 @@ import {
6
6
  toFullModelPropTransform,
7
7
  } from "../modelShared/prop"
8
8
  import { lazy } from "../utils"
9
+ import { getOrCreate } from "../utils/mapUtils"
9
10
  import {
10
11
  typesBoolean,
11
12
  typesNull,
@@ -24,26 +25,16 @@ const noDefaultValueSymbol = Symbol("noDefaultValue")
24
25
 
25
26
  const tPropCache = new WeakMap<AnyStandardType, Map<unknown, AnyModelProp>>()
26
27
 
27
- function createFromSnapshotProcessorGetter(
28
+ function createSnapshotProcessorGetter(
28
29
  getType: () => AnyStandardType,
29
- eagerType: AnyStandardType | undefined
30
+ eagerType: AnyStandardType | undefined,
31
+ getProcessor: (typeChecker: TypeChecker) => SnapshotProcessor | undefined
30
32
  ): (() => SnapshotProcessor | undefined) | undefined {
31
33
  if (eagerType instanceof TypeChecker) {
32
- const processor = eagerType.getFromSnapshotProcessor()
34
+ const processor = getProcessor(eagerType)
33
35
  return processor ? () => processor : undefined
34
36
  }
35
- return lazy(() => resolveTypeChecker(getType()).getFromSnapshotProcessor())
36
- }
37
-
38
- function createToSnapshotProcessorGetter(
39
- getType: () => AnyStandardType,
40
- eagerType: AnyStandardType | undefined
41
- ): (() => SnapshotProcessor | undefined) | undefined {
42
- if (eagerType instanceof TypeChecker) {
43
- const processor = eagerType.getToSnapshotProcessor()
44
- return processor ? () => processor : undefined
45
- }
46
- return lazy(() => resolveTypeChecker(getType()).getToSnapshotProcessor())
37
+ return lazy(() => getProcessor(resolveTypeChecker(getType())))
47
38
  }
48
39
 
49
40
  type AnyTypeOrArray = AnyType | ReadonlyArray<AnyType>
@@ -53,12 +44,22 @@ type StoredRequiredPropMetadata<TType extends AnyType> = {
53
44
  $storedCreationValueType: TypeToStoredData<TType>
54
45
  }
55
46
 
56
- type TypedMaybeOptionalModelProp<TType extends AnyType> = MaybeOptionalModelProp<
47
+ // exported (and publicly re-exported) so emitted declaration files can reference it by name
48
+ // instead of expanding it inline in every exported model class, which is much slower and larger
49
+ /**
50
+ * A model property created by `tProp` without a default value.
51
+ */
52
+ export type TypedMaybeOptionalModelProp<TType extends AnyType> = MaybeOptionalModelProp<
57
53
  TypeToData<TType>
58
54
  > &
59
55
  StoredRequiredPropMetadata<TType>
60
56
 
61
- type TypedOptionalModelProp<TType extends AnyType> = OptionalModelProp<TypeToData<TType>> & {
57
+ // exported (and publicly re-exported) so emitted declaration files can reference it by name
58
+ // instead of expanding it inline in every exported model class, which is much slower and larger
59
+ /**
60
+ * A model property created by `tProp` with a default value.
61
+ */
62
+ export type TypedOptionalModelProp<TType extends AnyType> = OptionalModelProp<TypeToData<TType>> & {
62
63
  $storedValueType: TypeToStoredData<TType>
63
64
  $storedCreationValueType: TypeToStoredData<TType> | null | undefined
64
65
  $typedFromSnapshotOverride: TypeToSnapshotIn<TType> | null | undefined
@@ -124,7 +125,7 @@ export function tProp(defaultValue: boolean): TypedOptionalModelProp<typeof type
124
125
  */
125
126
  export function tProp<TType extends AnyType>(
126
127
  type: TType,
127
- defaultFn: () => TypeToData<TType>
128
+ defaultFn: () => NoInfer<TypeToData<TType>>
128
129
  ): TypedOptionalModelProp<TType>
129
130
 
130
131
  /**
@@ -145,7 +146,7 @@ export function tProp<TType extends AnyType>(
145
146
  */
146
147
  export function tProp<TType extends ReadonlyArray<AnyType>>(
147
148
  type: TType,
148
- defaultFn: () => TypeToData<TType[number]>
149
+ defaultFn: () => NoInfer<TypeToData<TType[number]>>
149
150
  ): TypedOptionalModelProp<TType[number]>
150
151
 
151
152
  /**
@@ -167,7 +168,7 @@ export function tProp<TType extends ReadonlyArray<AnyType>>(
167
168
  */
168
169
  export function tProp<TType extends AnyType>(
169
170
  type: TType,
170
- defaultValue: TypeToData<TType>
171
+ defaultValue: NoInfer<TypeToData<TType>>
171
172
  ): TypedOptionalModelProp<TType>
172
173
 
173
174
  /**
@@ -188,7 +189,7 @@ export function tProp<TType extends AnyType>(
188
189
  */
189
190
  export function tProp<TType extends ReadonlyArray<AnyType>>(
190
191
  type: TType,
191
- defaultValue: TypeToData<TType[number]>
192
+ defaultValue: NoInfer<TypeToData<TType[number]>>
192
193
  ): TypedOptionalModelProp<TType[number]>
193
194
 
194
195
  /**
@@ -250,11 +251,7 @@ export function tProp(
250
251
  const typeChecker = resolveStandardType(resolvedType)
251
252
  const defKey = hasDefaultValue ? def : noDefaultValueSymbol
252
253
 
253
- let defValueCache = tPropCache.get(typeChecker)
254
- if (!defValueCache) {
255
- defValueCache = new Map()
256
- tPropCache.set(typeChecker, defValueCache)
257
- }
254
+ const defValueCache = getOrCreate(tPropCache, typeChecker, () => new Map())
258
255
 
259
256
  const cachedProp = defValueCache.get(defKey)
260
257
  if (cachedProp) {
@@ -301,11 +298,14 @@ export function tProp(
301
298
  ? toFullModelPropTransform(createCodecPropTransform(typeChecker))
302
299
  : undefined
303
300
  ),
304
- _getFromSnapshotProcessor: createFromSnapshotProcessorGetter(
301
+ _getFromSnapshotProcessor: createSnapshotProcessorGetter(
305
302
  getFromSnapshotType,
306
- eagerFromSnapshotType
303
+ eagerFromSnapshotType,
304
+ (tc) => tc.getFromSnapshotProcessor()
305
+ ),
306
+ _getToSnapshotProcessor: createSnapshotProcessorGetter(getStoredType, eagerStoredType, (tc) =>
307
+ tc.getToSnapshotProcessor()
307
308
  ),
308
- _getToSnapshotProcessor: createToSnapshotProcessorGetter(getStoredType, eagerStoredType),
309
309
  } satisfies Partial<AnyModelProp>)
310
310
 
311
311
  defValueCache.set(defKey, newProp)
@@ -1,7 +1,6 @@
1
- import type { O } from "ts-toolbelt"
1
+ import type { SimplifyObject } from "../../utils/types"
2
2
  import type {
3
3
  ArrayType,
4
- CodecType,
5
4
  ObjectOptionalKeys,
6
5
  ObjectType,
7
6
  ObjectTypeFunction,
@@ -10,28 +9,30 @@ import type {
10
9
  } from "../schemas"
11
10
 
12
11
  type ArrayStoredData<S extends readonly unknown[]> = number extends S["length"]
13
- ? Array<TypeToStoredData<S[number]> extends infer R ? R : never>
12
+ ? Array<TypeToStoredData<S[number]>>
14
13
  : {
15
- [k in keyof S]: TypeToStoredData<S[k]> extends infer R ? R : never
14
+ [k in keyof S]: TypeToStoredData<S[k]>
16
15
  }
17
16
 
18
- type ObjectStoredData<S> = O.Optional<
19
- { [k in keyof S]: TypeToStoredData<S[k]> extends infer R ? R : never },
20
- ObjectOptionalKeys<S>
17
+ type ObjectStoredData<S, OK = ObjectOptionalKeys<S>> = SimplifyObject<
18
+ { [k in keyof S as k extends OK ? never : k]: TypeToStoredData<S[k]> } & {
19
+ [k in keyof S as k extends OK ? k : never]?: TypeToStoredData<S[k]>
20
+ }
21
21
  >
22
22
 
23
+ // standard types are dispatched through their `$$type` discriminant (see TypeToSnapshotIn)
23
24
  export type TypeToStoredData<S> = S extends ObjectTypeFunction
24
- ? ObjectStoredData<ReturnType<S>> extends infer R
25
- ? R
26
- : never
27
- : S extends CodecType<any, any, any, infer D>
28
- ? D
29
- : S extends ArrayType<infer A>
30
- ? ArrayStoredData<A>
31
- : S extends ObjectType<infer O>
32
- ? ObjectStoredData<O>
33
- : S extends RecordType<infer R>
34
- ? {
35
- [k: string]: TypeToStoredData<R> extends infer D ? D : never
36
- }
37
- : TypeToData<S>
25
+ ? ObjectStoredData<ReturnType<S>>
26
+ : S extends { $$type: infer K }
27
+ ? K extends "codec"
28
+ ? S["$$storedData" & keyof S]
29
+ : S extends ArrayType<infer A>
30
+ ? ArrayStoredData<A>
31
+ : S extends ObjectType<infer O>
32
+ ? ObjectStoredData<O>
33
+ : S extends RecordType<infer R>
34
+ ? {
35
+ [k: string]: TypeToStoredData<R>
36
+ }
37
+ : TypeToData<S>
38
+ : TypeToData<S>
@@ -1,6 +1,7 @@
1
1
  import type { ModelPropTransform } from "../../modelShared/prop"
2
2
  import { lazy } from "../../utils"
3
3
  import { getTypeInfo } from "../getTypeInfo"
4
+ import { runInModelTypeMatchingPass } from "../modelTypeMatching"
4
5
  import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
5
6
  import type {
6
7
  AnyStandardType,
@@ -45,6 +46,11 @@ type CodecConfig<TEncodedType extends AnyType, TRuntime> = {
45
46
  is(value: unknown): value is TRuntime
46
47
  } & ModelPropTransform<TypeToData<TEncodedType>, TRuntime>
47
48
 
49
+ // exported (and publicly re-exported) so emitted declaration files can reference it by name
50
+ // instead of expanding it inline in every exported model class, which is much slower and larger
51
+ /**
52
+ * A codec type whose snapshot and stored forms are those of its encoded type.
53
+ */
48
54
  export type CodecFromEncoded<TEncodedType extends AnyType, TRuntime> = CodecType<
49
55
  TRuntime,
50
56
  TypeToSnapshotIn<TEncodedType>,
@@ -140,7 +146,8 @@ export function createCodecType<TEncodedType extends AnyType, TRuntime>(
140
146
  () => typeName,
141
147
  typeInfoGen,
142
148
 
143
- (value) => (is(value) ? thisTc : null),
149
+ // user code, so it must not see the model type matching pass of an outer union
150
+ (value) => (runInModelTypeMatchingPass("none", is, value, undefined) ? thisTc : null),
144
151
 
145
152
  (snapshot) => {
146
153
  const processedSnapshot = encodedTypeChecker.fromSnapshotProcessor(snapshot)
@@ -1,10 +1,11 @@
1
1
  import { isObservableArray, isObservableObject, remove } from "mobx"
2
2
  import type { ModelPropTransform } from "../../modelShared/prop"
3
- import { copyOwnEnumerableProps, isArray, isObject, lazy } from "../../utils"
3
+ import { copyOwnEnumerableProps, hasOwnProp, isArray, isObject, lazy } from "../../utils"
4
4
  import { setIfDifferent } from "../../utils/setIfDifferent"
5
5
  import { ArrayTypeInfo, typesArray } from "../arrayBased/typesArray"
6
6
  import { TupleTypeInfo, typesTuple } from "../arrayBased/typesTuple"
7
7
  import { getTypeInfo } from "../getTypeInfo"
8
+ import { findClosestModelTypeMatch, findFirstMatch } from "../modelTypeMatching"
8
9
  import { isDataModelDataStandardType } from "../objectBased/typesDataModelData"
9
10
  import { isModelStandardType } from "../objectBased/typesModel"
10
11
  import { ObjectTypeInfo, typesObject } from "../objectBased/typesObject"
@@ -136,7 +137,7 @@ function createObjectRuntimeAdapter(
136
137
  get(target, prop, receiver) {
137
138
  if (typeof prop === "string") {
138
139
  const childSupport = getChildSupports()[prop]
139
- if (childSupport && Object.hasOwn(target, prop)) {
140
+ if (childSupport && hasOwnProp(target, prop)) {
140
141
  return childSupport.adapter.toRuntime(target[prop], (newStoredValue) => {
141
142
  target[prop] = newStoredValue
142
143
  })
@@ -200,7 +201,7 @@ function createRecordRuntimeAdapter(
200
201
 
201
202
  const proxy = new Proxy(stored, {
202
203
  get(target, prop, receiver) {
203
- if (typeof prop === "string" && Object.hasOwn(target, prop)) {
204
+ if (typeof prop === "string" && hasOwnProp(target, prop)) {
204
205
  const valueSupport = getValueSupport()
205
206
  return valueSupport.adapter.toRuntime(target[prop], (newStoredValue) => {
206
207
  target[prop] = newStoredValue
@@ -577,6 +578,13 @@ function createCodecLeafRuntimeAdapter(
577
578
  }
578
579
  }
579
580
 
581
+ function matchCodecSupport(
582
+ support: ResolvedCodecSupport,
583
+ value: unknown
584
+ ): ResolvedCodecSupport | undefined {
585
+ return resolveTypeChecker(support.storedType).snapshotType(value) ? support : undefined
586
+ }
587
+
580
588
  function createOrRuntimeAdapter(
581
589
  childTypes: ReadonlyArray<AnyStandardType>,
582
590
  getChildSupports: () => ReadonlyArray<ResolvedCodecSupport>,
@@ -607,14 +615,12 @@ function createOrRuntimeAdapter(
607
615
  }
608
616
 
609
617
  const supports = getChildSupports()
610
- for (let i = 0; i < supports.length; i++) {
611
- const storedTypeChecker = resolveTypeChecker(supports[i].storedType)
612
- if (storedTypeChecker.snapshotType(value)) {
613
- return supports[i]
614
- }
615
- }
618
+ // prefer branches whose (possibly nested) models are the closest to the snapshot ones
619
+ const matchingSupport = isObject(value)
620
+ ? findClosestModelTypeMatch(supports, matchCodecSupport, value)
621
+ : findFirstMatch(supports, matchCodecSupport, value)
616
622
 
617
- return supports[0]
623
+ return matchingSupport ?? supports[0]
618
624
  }
619
625
 
620
626
  return {
@@ -41,18 +41,14 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
41
41
  return getValue(storedKey)
42
42
  }
43
43
  case "getOrInsert":
44
- return (key: TKeyRuntime, defaultValue: TValueRuntime) => {
45
- const storedKey = keyAdapter.toStored(key)
46
- if (!target.has(storedKey)) {
47
- target.set(storedKey, valueAdapter.toStored(defaultValue))
48
- }
49
- return getValue(storedKey)
50
- }
51
44
  case "getOrInsertComputed":
52
- return (key: TKeyRuntime, callback: (key: TKeyRuntime) => TValueRuntime) => {
45
+ return (key: TKeyRuntime, valueOrCallback: unknown) => {
53
46
  const storedKey = keyAdapter.toStored(key)
54
47
  if (!target.has(storedKey)) {
55
- const value = callback(key)
48
+ const value =
49
+ prop === "getOrInsert"
50
+ ? (valueOrCallback as TValueRuntime)
51
+ : (valueOrCallback as (key: TKeyRuntime) => TValueRuntime)(key)
56
52
  target.set(storedKey, valueAdapter.toStored(value))
57
53
  }
58
54
  return getValue(storedKey)
@@ -114,10 +110,18 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
114
110
  }) as unknown as Map<TKeyRuntime, TValueRuntime>
115
111
  }
116
112
 
117
- function makeObjectBackedMapTransform<TStoredValue, TRuntimeValue>(
118
- valueAdapter: RuntimeAdapter<TStoredValue, TRuntimeValue>
119
- ): ModelPropTransform<Record<string, TStoredValue>, Map<string, TRuntimeValue>> {
120
- const storedByRuntime = new WeakMap<Map<string, TRuntimeValue>, Record<string, TStoredValue>>()
113
+ function makeMapTransform<
114
+ TStored extends object,
115
+ TKeyStored,
116
+ TKeyRuntime,
117
+ TValueStored,
118
+ TValueRuntime,
119
+ >(
120
+ keyAdapter: RuntimeAdapter<TKeyStored, TKeyRuntime>,
121
+ valueAdapter: RuntimeAdapter<TValueStored, TValueRuntime>,
122
+ toStored: (runtimeMap: Map<TKeyRuntime, TValueRuntime>) => TStored
123
+ ): ModelPropTransform<TStored, Map<TKeyRuntime, TValueRuntime>> {
124
+ const storedByRuntime = new WeakMap<Map<TKeyRuntime, TValueRuntime>, TStored>()
121
125
 
122
126
  return {
123
127
  transform({ originalValue, cachedTransformedValue }) {
@@ -125,63 +129,51 @@ function makeObjectBackedMapTransform<TStoredValue, TRuntimeValue>(
125
129
  return cachedTransformedValue
126
130
  }
127
131
 
128
- const runtimeMap = makeMapProxy(asMap(originalValue), identityRuntimeAdapter, valueAdapter)
132
+ const runtimeMap = makeMapProxy(
133
+ asMap(originalValue as any) as Map<TKeyStored, TValueStored>,
134
+ keyAdapter,
135
+ valueAdapter
136
+ )
129
137
 
130
138
  storedByRuntime.set(runtimeMap, originalValue)
131
139
  return runtimeMap
132
140
  },
133
141
 
134
142
  untransform({ transformedValue }) {
135
- const cachedStored = storedByRuntime.get(transformedValue)
136
- if (cachedStored) {
137
- return cachedStored
138
- }
143
+ return storedByRuntime.get(transformedValue) ?? toStored(transformedValue)
144
+ },
145
+ }
146
+ }
139
147
 
148
+ function makeObjectBackedMapTransform<TStoredValue, TRuntimeValue>(
149
+ valueAdapter: RuntimeAdapter<TStoredValue, TRuntimeValue>
150
+ ): ModelPropTransform<Record<string, TStoredValue>, Map<string, TRuntimeValue>> {
151
+ return makeMapTransform(
152
+ identityRuntimeAdapter as RuntimeAdapter<string, string>,
153
+ valueAdapter,
154
+ (runtimeMap) => {
140
155
  const result: Record<string, TStoredValue> = {}
141
- transformedValue.forEach((value, key) => {
156
+ runtimeMap.forEach((value, key) => {
142
157
  const storedValue = valueAdapter.toStored(value)
143
158
  if (key === "__proto__") setProtoProp(result, storedValue)
144
159
  else result[key] = storedValue
145
160
  })
146
161
  return result
147
- },
148
- }
162
+ }
163
+ )
149
164
  }
150
165
 
151
166
  function makeArrayBackedMapTransform<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
152
167
  keyAdapter: RuntimeAdapter<TKeyStored, TKeyRuntime>,
153
168
  valueAdapter: RuntimeAdapter<TValueStored, TValueRuntime>
154
169
  ): ModelPropTransform<Array<[TKeyStored, TValueStored]>, Map<TKeyRuntime, TValueRuntime>> {
155
- const storedByRuntime = new WeakMap<
156
- Map<TKeyRuntime, TValueRuntime>,
157
- Array<[TKeyStored, TValueStored]>
158
- >()
159
-
160
- return {
161
- transform({ originalValue, cachedTransformedValue }) {
162
- if (cachedTransformedValue) {
163
- return cachedTransformedValue
164
- }
165
-
166
- const runtimeMap = makeMapProxy(asMap(originalValue), keyAdapter, valueAdapter)
167
-
168
- storedByRuntime.set(runtimeMap, originalValue)
169
- return runtimeMap
170
- },
171
-
172
- untransform({ transformedValue }) {
173
- const cachedStored = storedByRuntime.get(transformedValue)
174
- if (cachedStored) {
175
- return cachedStored
176
- }
177
-
178
- const result: Array<[TKeyStored, TValueStored]> = []
179
- transformedValue.forEach((value, key) => {
180
- result.push([keyAdapter.toStored(key), valueAdapter.toStored(value)])
181
- })
182
- return result
183
- },
184
- }
170
+ return makeMapTransform(keyAdapter, valueAdapter, (runtimeMap) => {
171
+ const result: Array<[TKeyStored, TValueStored]> = []
172
+ runtimeMap.forEach((value, key) => {
173
+ result.push([keyAdapter.toStored(key), valueAdapter.toStored(value)])
174
+ })
175
+ return result
176
+ })
185
177
  }
186
178
 
187
179
  export function typesMapFromObject<TValueType extends AnyType>(