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,5 +1,14 @@
1
+ import { modelTypeKey } from "../../model/metadata"
2
+ import { getModelInfoForName } from "../../modelShared/modelInfo"
1
3
  import { failure, lazy } from "../../utils"
2
4
  import { getTypeInfo } from "../getTypeInfo"
5
+ import {
6
+ findClosestModelTypeMatch,
7
+ findFirstMatch,
8
+ getModelTypeMatchingPass,
9
+ hasSnapshotModelType,
10
+ matchUnlessModelSnapshotRejected,
11
+ } from "../modelTypeMatching"
3
12
  import {
4
13
  resolveStandardType,
5
14
  resolveStandardTypeNoThrow,
@@ -195,17 +204,34 @@ export function typesOr(
195
204
  // this is done because:
196
205
  // 1) performance (avoid checking structure if not needed)
197
206
  // 2) so we can accept untyped models when paired with undefined | null
198
- return checkerForBaseType[0]
199
- }
200
-
201
- for (let i = 0; i < checkerForBaseType.length; i++) {
202
- const matchingType = checkerForBaseType[i].snapshotType(value)
203
- if (matchingType) {
204
- return matchingType
207
+ const candidate = checkerForBaseType[0]
208
+ if (
209
+ getModelTypeMatchingPass() !== "none" &&
210
+ valueBaseType !== TypeCheckerBaseType.Primitive
211
+ ) {
212
+ // an outer union is matching its branches, so let it know whether this one matches
213
+ // the (possibly nested) snapshot models; if it does not because a (possibly nested)
214
+ // model type rejected a model snapshot (in the exact pass, also one of a subclass),
215
+ // or because the snapshot names a registered model this branch does not accept, the
216
+ // outer union has to try other branches first, else it might pick a farther branch
217
+ // or one that rejects the snapshot
218
+ const matched = matchUnlessModelSnapshotRejected(matchSnapshotType, candidate, value)
219
+ if (
220
+ matched === undefined ||
221
+ (!matched &&
222
+ hasSnapshotModelType(value) &&
223
+ getModelInfoForName((value as any)[modelTypeKey]))
224
+ ) {
225
+ return null
226
+ }
205
227
  }
228
+ return candidate
206
229
  }
207
230
 
208
- return null
231
+ // prefer branches whose (possibly nested) models are the closest to the snapshot ones
232
+ return valueBaseType === TypeCheckerBaseType.Primitive
233
+ ? (findFirstMatch(checkerForBaseType, matchSnapshotType, value) ?? null)
234
+ : (findClosestModelTypeMatch(checkerForBaseType, matchSnapshotType, value) ?? null)
209
235
  },
210
236
 
211
237
  snapshotProcessorPlan(
@@ -272,3 +298,7 @@ export class OrTypeInfo extends TypeInfo {
272
298
  this.dispatcher = dispatcher
273
299
  }
274
300
  }
301
+
302
+ function matchSnapshotType(checker: TypeChecker, value: unknown): TypeChecker | null {
303
+ return checker.snapshotType(value)
304
+ }
@@ -62,6 +62,11 @@ export function typesRefinement<T extends AnyType>(
62
62
  return `${refinementName}<${baseTypeName}>`
63
63
  }
64
64
 
65
+ const processorPlan = snapshotProcessorPlan(
66
+ () => [baseChecker],
67
+ ([processor]) => processor
68
+ )
69
+
65
70
  const thisTc: TypeChecker = new TypeChecker(
66
71
  baseChecker.baseType,
67
72
 
@@ -103,14 +108,8 @@ export function typesRefinement<T extends AnyType>(
103
108
  // we cannot check refinement here since it checks data instances, not snapshots
104
109
  (sn) => baseChecker.snapshotType(sn),
105
110
 
106
- snapshotProcessorPlan(
107
- () => [baseChecker],
108
- ([processor]) => processor
109
- ),
110
- snapshotProcessorPlan(
111
- () => [baseChecker],
112
- ([processor]) => processor
113
- )
111
+ processorPlan,
112
+ processorPlan
114
113
  )
115
114
 
116
115
  return thisTc
@@ -42,20 +42,19 @@ export function typesSkipCheck<T extends AnyType>(baseType: T): T {
42
42
  return `skipCheck<${baseTypeName}>`
43
43
  }
44
44
 
45
+ const processorPlan = snapshotProcessorPlan(
46
+ () => [baseChecker],
47
+ ([processor]) => processor
48
+ )
49
+
45
50
  const thisTc: TypeChecker = new TypeChecker(
46
51
  baseChecker.baseType,
47
52
  (_data, _path, _typeCheckedValue) => null, // always passes validation
48
53
  getTypeName,
49
54
  typeInfoGen,
50
55
  (sn) => baseChecker.snapshotType(sn),
51
- snapshotProcessorPlan(
52
- () => [baseChecker],
53
- ([processor]) => processor
54
- ),
55
- snapshotProcessorPlan(
56
- () => [baseChecker],
57
- ([processor]) => processor
58
- )
56
+ processorPlan,
57
+ processorPlan
59
58
  )
60
59
  thisTc.skipCheck = true
61
60
 
@@ -52,20 +52,19 @@ export function typesTag<T extends AnyType, A>(baseType: T, tag: A, typeName?: s
52
52
  return `${taggedName}<${baseTypeName}>`
53
53
  }
54
54
 
55
+ const processorPlan = snapshotProcessorPlan(
56
+ () => [baseChecker],
57
+ ([processor]) => processor
58
+ )
59
+
55
60
  const thisTc: TypeChecker = new TypeChecker(
56
61
  baseChecker.baseType,
57
62
  (data, path, typeCheckedValue) => baseChecker.check(data, path, typeCheckedValue),
58
63
  getTypeName,
59
64
  typeInfoGen,
60
65
  (sn) => baseChecker.snapshotType(sn),
61
- snapshotProcessorPlan(
62
- () => [baseChecker],
63
- ([processor]) => processor
64
- ),
65
- snapshotProcessorPlan(
66
- () => [baseChecker],
67
- ([processor]) => processor
68
- )
66
+ processorPlan,
67
+ processorPlan
69
68
  )
70
69
 
71
70
  return thisTc
@@ -1,9 +1,6 @@
1
1
  import type { AnyModel } from "../model/BaseModel"
2
- import { getModelIdPropertyName } from "../model/getModelMetadata"
3
2
  import { modelIdKey, modelTypeKey } from "../model/metadata"
4
- import { getSnapshotModelType, isModel } from "../model/utils"
5
- import type { ModelClass } from "../modelShared/BaseModelShared"
6
- import { getModelInfoForName } from "../modelShared/modelInfo"
3
+ import { isModel } from "../model/utils"
7
4
  import { dataToModelNode } from "../parent/core"
8
5
  import {
9
6
  type DeepObjectChildrenExtensionAccessor,
@@ -46,20 +43,6 @@ export class ModelPool {
46
43
  findModelByTypeAndId(modelType: string, modelId: string | undefined): AnyModel | undefined {
47
44
  return modelId !== undefined ? this.pool.get(modelType)?.get(modelId) : undefined
48
45
  }
49
-
50
- findModelForSnapshot(sn: any): AnyModel | undefined {
51
- const modelType = getSnapshotModelType(sn)
52
- if (modelType === undefined) {
53
- return undefined
54
- }
55
-
56
- const modelInfo = getModelInfoForName(modelType)!
57
- const modelIdPropertyName = getModelIdPropertyName(modelInfo.class as ModelClass<AnyModel>)
58
-
59
- return modelIdPropertyName !== undefined
60
- ? this.findModelByTypeAndId(modelType, (sn as any)[modelIdPropertyName])
61
- : undefined
62
- }
63
46
  }
64
47
 
65
48
  let deepChildrenModels: DeepObjectChildrenExtensionAccessor<DeepChildrenModels> | undefined
@@ -109,7 +109,7 @@ export function decorateWrapMethodOrField(
109
109
  const propertyKey: string = args[1]
110
110
  const baseDescriptor: PropertyDescriptor | undefined = args[2]
111
111
 
112
- checkModelDecoratorTaget(decoratorName, target)
112
+ checkModelDecoratorTarget(decoratorName, target)
113
113
  checkDecoratorContext(decoratorName, propertyKey, false)
114
114
 
115
115
  const data = getActionNameAndContextOverride(target, propertyKey, true)
@@ -166,7 +166,7 @@ export function decorateWrapMethodOrField(
166
166
  inited = true
167
167
 
168
168
  const target = this
169
- checkModelDecoratorTaget(decoratorName, target)
169
+ checkModelDecoratorTarget(decoratorName, target)
170
170
 
171
171
  // find the deepest proto that matches the value
172
172
  let proto = this
@@ -195,7 +195,7 @@ export function decorateWrapMethodOrField(
195
195
  const instance = this
196
196
 
197
197
  if (!data) {
198
- checkModelDecoratorTaget(decoratorName, instance)
198
+ checkModelDecoratorTarget(decoratorName, instance)
199
199
  data = getActionNameAndContextOverride(instance, propertyKey, false)
200
200
  }
201
201
 
@@ -207,7 +207,7 @@ export function decorateWrapMethodOrField(
207
207
  }
208
208
 
209
209
  default:
210
- throw failure(`@${decoratorName} can only be used on fields or methods}`)
210
+ throw failure(`@${decoratorName} can only be used on fields or methods`)
211
211
  }
212
212
  }
213
213
  }
@@ -270,7 +270,7 @@ function getActionNameAndContextOverride(
270
270
  }
271
271
  }
272
272
 
273
- function checkModelDecoratorTaget(decoratorName: string, target: any) {
273
+ function checkModelDecoratorTarget(decoratorName: string, target: any) {
274
274
  if (!inDevMode) {
275
275
  return
276
276
  }
@@ -267,15 +267,6 @@ export function assertIsObject(value: unknown, argName: string): asserts value i
267
267
  }
268
268
  }
269
269
 
270
- /**
271
- * @internal
272
- */
273
- export function assertIsPlainObject(value: unknown, argName: string): asserts value is object {
274
- if (!isPlainObject(value)) {
275
- throw failure(`${argName} must be a plain object`)
276
- }
277
- }
278
-
279
270
  /**
280
271
  * @internal
281
272
  */
@@ -342,15 +333,6 @@ export function assertIsPrimitive(
342
333
  }
343
334
  }
344
335
 
345
- /**
346
- * @internal
347
- */
348
- export function assertIsString(value: unknown, argName: string): asserts value is string {
349
- if (typeof value !== "string") {
350
- throw failure(`${argName} must be a string`)
351
- }
352
- }
353
-
354
336
  /**
355
337
  * @internal
356
338
  */
@@ -372,8 +354,7 @@ export function addLateInitializationFunction(
372
354
  fn: (instance: any) => void
373
355
  ) {
374
356
  let array: LateInitializationFunctionsArray | undefined = target[symbol]
375
- // biome-ignore lint/suspicious/noPrototypeBuiltins: support old browsers
376
- if (!(array && Object.prototype.hasOwnProperty.call(target, symbol))) {
357
+ if (!(array && hasOwnProp(target, symbol))) {
377
358
  // leave base array unmodified, create new array in the derived class
378
359
  array = array ? array.slice() : []
379
360
  addHiddenProp(target, symbol, array)
@@ -46,3 +46,8 @@ export type IsNeverType<T, IfNever, IfNotNever> = [T] extends [never] ? IfNever
46
46
  * @ignore
47
47
  */
48
48
  export type Flatten<T> = T extends Record<any, any> ? { [P in keyof T]: T[P] } : T
49
+
50
+ /**
51
+ * @ignore
52
+ */
53
+ export type SimplifyObject<T> = { [P in keyof T]: T[P] } & {}
@@ -8,6 +8,15 @@ import { typesArray } from "../types/arrayBased/typesArray"
8
8
  import { tProp } from "../types/tProp"
9
9
  import { typesUnchecked } from "../types/utility/typesUnchecked"
10
10
  import { namespace } from "../utils"
11
+ import {
12
+ isSetLikeDisjointFrom,
13
+ isSetLikeSubsetOf,
14
+ isSetLikeSupersetOf,
15
+ setLikeDifference,
16
+ setLikeIntersection,
17
+ setLikeSymmetricDifference,
18
+ setLikeUnion,
19
+ } from "../utils/setLike"
11
20
 
12
21
  const arraySetBase = Model({
13
22
  [modelIdKey]: idProp,
@@ -96,39 +105,43 @@ export class ArraySet<V> extends arraySetBase implements Set<V> {
96
105
 
97
106
  readonly [Symbol.toStringTag] = "ArraySet"
98
107
 
108
+ // `has` scans the items, so the set operations look values up in a set built
109
+ // on the first lookup instead (not before, since many of them never look up)
110
+ private toSetLike(): ReadonlySetLike<V> {
111
+ let lookup: Set<V> | undefined
112
+ return {
113
+ size: this.items.length,
114
+ has: (value) => (lookup ??= new Set(this.items)).has(value),
115
+ keys: () => this.values(),
116
+ }
117
+ }
118
+
99
119
  union<U>(other: ReadonlySetLike<U>): Set<V | U> {
100
- const s = new Set(this)
101
- return s.union(other)
120
+ return setLikeUnion(this.toSetLike(), other)
102
121
  }
103
122
 
104
123
  intersection<U>(other: ReadonlySetLike<U>): Set<V & U> {
105
- const s = new Set(this)
106
- return s.intersection(other)
124
+ return setLikeIntersection(this.toSetLike(), other)
107
125
  }
108
126
 
109
127
  difference<U>(other: ReadonlySetLike<U>): Set<V> {
110
- const s = new Set(this)
111
- return s.difference(other)
128
+ return setLikeDifference(this.toSetLike(), other)
112
129
  }
113
130
 
114
131
  symmetricDifference<U>(other: ReadonlySetLike<U>): Set<V | U> {
115
- const s = new Set(this)
116
- return s.symmetricDifference(other)
132
+ return setLikeSymmetricDifference(this.toSetLike(), other)
117
133
  }
118
134
 
119
135
  isSubsetOf(other: ReadonlySetLike<unknown>): boolean {
120
- const s = new Set(this)
121
- return s.isSubsetOf(other)
136
+ return isSetLikeSubsetOf(this.toSetLike(), other)
122
137
  }
123
138
 
124
139
  isSupersetOf(other: ReadonlySetLike<unknown>): boolean {
125
- const s = new Set(this)
126
- return s.isSupersetOf(other)
140
+ return isSetLikeSupersetOf(this.toSetLike(), other)
127
141
  }
128
142
 
129
143
  isDisjointFrom(other: ReadonlySetLike<unknown>): boolean {
130
- const s = new Set(this)
131
- return s.isDisjointFrom(other)
144
+ return isSetLikeDisjointFrom(this.toSetLike(), other)
132
145
  }
133
146
  }
134
147
 
@@ -70,12 +70,7 @@ export class ObjectMap<V> extends objectMapBase implements Map<string, V> {
70
70
  }
71
71
 
72
72
  getOrInsert(key: string, defaultValue: V): V {
73
- if (this.has(key)) {
74
- return this.get(key) as V
75
- }
76
-
77
- this.set(key, defaultValue)
78
- return this.get(key) as V
73
+ return this.getOrInsertComputed(key, () => defaultValue)
79
74
  }
80
75
 
81
76
  getOrInsertComputed(key: string, callback: (key: string) => V): V {
@@ -4,8 +4,6 @@ import {
4
4
  type IObjectDidChange,
5
5
  type IObservableArray,
6
6
  intercept,
7
- isObservableArray,
8
- isObservableObject,
9
7
  type ObservableMap,
10
8
  observable,
11
9
  observe,
@@ -21,7 +19,6 @@ import {
21
19
  failure,
22
20
  getMobxVersion,
23
21
  hasOwnProp,
24
- inDevMode,
25
22
  isArray,
26
23
  isEqualOrBothNaN,
27
24
  setProtoProp,
@@ -44,12 +41,7 @@ function addTs6MapExtras<K, V>(map: ObservableMap<K, V>): ObservableMap<K, V> &
44
41
 
45
42
  if (!mapWithExtras.getOrInsert) {
46
43
  mapWithExtras.getOrInsert = function (key: K, defaultValue: V): V {
47
- if (this.has(key)) {
48
- return this.get(key) as V
49
- }
50
-
51
- this.set(key, defaultValue)
52
- return this.get(key) as V
44
+ return this.getOrInsertComputed(key, () => defaultValue)
53
45
  }
54
46
  }
55
47
 
@@ -70,12 +62,6 @@ function addTs6MapExtras<K, V>(map: ObservableMap<K, V>): ObservableMap<K, V> &
70
62
 
71
63
  const observableMapBackedByObservableObject = action(
72
64
  <T>(obj: object): ObservableMapWithDataObject<string, T, typeof obj> => {
73
- if (inDevMode) {
74
- if (!isObservableObject(obj)) {
75
- throw failure("assertion failed: expected an observable object")
76
- }
77
- }
78
-
79
65
  const map = transaction(() =>
80
66
  untracked(() => {
81
67
  const map = observable.map<string, T>(undefined, { deep: false })
@@ -175,12 +161,6 @@ const observableMapBackedByObservableObject = action(
175
161
  const observableMapBackedByObservableArray = <K, T>(
176
162
  array: IObservableArray<[K, T]>
177
163
  ): ObservableMapWithDataObject<K, T, typeof array> => {
178
- if (inDevMode) {
179
- if (!isObservableArray(array)) {
180
- throw failure("assertion failed: expected an observable array")
181
- }
182
- }
183
-
184
164
  const map: ObservableMap<K, T> = untracked(() => {
185
165
  if (getMobxVersion() >= 6) {
186
166
  return observable.map<K, T>(array, { deep: false })
@@ -3,7 +3,6 @@ import {
3
3
  type IObservableArray,
4
4
  type ISetWillChange,
5
5
  intercept,
6
- isObservableArray,
7
6
  type ObservableSet,
8
7
  observable,
9
8
  observe,
@@ -16,7 +15,6 @@ import {
16
15
  assertIsSet,
17
16
  failure,
18
17
  getMobxVersion,
19
- inDevMode,
20
18
  isEqualOrBothNaN,
21
19
  } from "../utils"
22
20
  import { tag } from "../utils/tag"
@@ -24,12 +22,6 @@ import { tag } from "../utils/tag"
24
22
  const observableSetBackedByObservableArray = <T>(
25
23
  array: IObservableArray<T>
26
24
  ): ObservableSet<T> & { dataObject: typeof array } => {
27
- if (inDevMode) {
28
- if (!isObservableArray(array)) {
29
- throw failure("assertion failed: expected an observable array")
30
- }
31
- }
32
-
33
25
  const set = transaction(() =>
34
26
  untracked(() => {
35
27
  if (getMobxVersion() >= 6) {