mobx-keystone 1.24.1 → 2.0.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 (122) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/README.md +5 -1
  3. package/dist/mobx-keystone.esm.js +1608 -1072
  4. package/dist/mobx-keystone.esm.mjs +1608 -1072
  5. package/dist/mobx-keystone.umd.js +1613 -1072
  6. package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
  7. package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
  8. package/dist/types/deepChange/onDeepChange.d.ts +9 -0
  9. package/dist/types/parent/detach.d.ts +1 -1
  10. package/dist/types/parent/findChildren.d.ts +1 -1
  11. package/dist/types/patch/applyPatches.d.ts +2 -0
  12. package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
  13. package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
  14. package/dist/types/standardActions/arrayActions.d.ts +1 -1
  15. package/dist/types/treeUtils/sandbox.d.ts +0 -4
  16. package/dist/types/types/TypeCheckError.d.ts +1 -0
  17. package/dist/types/types/utility/typesRefinement.d.ts +2 -2
  18. package/dist/types/utils/forEachWithDelayedThrow.d.ts +1 -0
  19. package/dist/types/utils/index.d.ts +13 -0
  20. package/dist/types/utils/mapUtils.d.ts +2 -2
  21. package/dist/types/utils/setLike.d.ts +1 -0
  22. package/package.json +7 -7
  23. package/src/action/applyAction.ts +1 -1
  24. package/src/action/middleware.ts +17 -36
  25. package/src/action/modelFlow.ts +4 -5
  26. package/src/action/mutationBatch.ts +10 -10
  27. package/src/action/pendingActions.ts +3 -4
  28. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +19 -14
  29. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +29 -20
  30. package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +1 -1
  31. package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +4 -3
  32. package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +4 -12
  33. package/src/actionMiddlewares/actionSerialization/setSerializer.ts +4 -13
  34. package/src/actionMiddlewares/actionTrackingMiddleware.ts +11 -13
  35. package/src/actionMiddlewares/transactionMiddleware.ts +42 -11
  36. package/src/actionMiddlewares/undoMiddleware.ts +76 -60
  37. package/src/actionMiddlewares/utils.ts +2 -1
  38. package/src/computedTree/computedTree.ts +14 -3
  39. package/src/context/context.ts +17 -45
  40. package/src/dataModel/BaseDataModel.ts +20 -16
  41. package/src/dataModel/dataModelInstanceCache.ts +8 -0
  42. package/src/deepChange/onDeepChange.ts +179 -24
  43. package/src/frozen/Frozen.ts +50 -33
  44. package/src/model/BaseModel.ts +15 -14
  45. package/src/model/newModel.ts +32 -28
  46. package/src/model/utils.ts +2 -2
  47. package/src/modelShared/modelClassInitializer.ts +3 -2
  48. package/src/modelShared/modelDecorator.ts +34 -14
  49. package/src/modelShared/modelInfo.ts +1 -1
  50. package/src/modelShared/prop.ts +6 -9
  51. package/src/modelShared/sharedInternalModel.ts +16 -3
  52. package/src/parent/detach.ts +1 -1
  53. package/src/parent/findChildren.ts +4 -7
  54. package/src/parent/findParent.ts +14 -9
  55. package/src/parent/onChildAttachedTo.ts +45 -40
  56. package/src/parent/path.ts +11 -7
  57. package/src/parent/setParent.ts +33 -14
  58. package/src/parent/walkTree.ts +21 -37
  59. package/src/patch/applyPatches.ts +75 -60
  60. package/src/patch/emitPatch.ts +142 -26
  61. package/src/patch/jsonPatch.ts +5 -0
  62. package/src/patch/patchRecorder.ts +18 -14
  63. package/src/redux/connectReduxDevTools.ts +24 -18
  64. package/src/redux/redux.ts +5 -21
  65. package/src/ref/core.ts +18 -2
  66. package/src/rootStore/attachDetach.ts +27 -16
  67. package/src/rootStore/rootStore.ts +10 -6
  68. package/src/snapshot/SnapshotOf.ts +2 -2
  69. package/src/snapshot/applySnapshot.ts +10 -1
  70. package/src/snapshot/clone.ts +1 -2
  71. package/src/snapshot/cloneTreeValue.ts +1 -2
  72. package/src/snapshot/fromModelSnapshot.ts +6 -2
  73. package/src/snapshot/fromSnapshot.ts +3 -1
  74. package/src/snapshot/internal.ts +22 -9
  75. package/src/snapshot/modelInitialData.ts +14 -19
  76. package/src/snapshot/onSnapshot.ts +12 -1
  77. package/src/snapshot/reconcileModelSnapshot.ts +16 -8
  78. package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -4
  79. package/src/snapshot/reconcileSnapshot.ts +13 -4
  80. package/src/standardActions/arrayActions.ts +12 -2
  81. package/src/standardActions/objectActions.ts +3 -2
  82. package/src/transforms/ImmutableDate.ts +4 -0
  83. package/src/transforms/asMap.ts +3 -1
  84. package/src/treeUtils/deepEquals.ts +15 -13
  85. package/src/treeUtils/draft.ts +8 -0
  86. package/src/treeUtils/sandbox.ts +31 -27
  87. package/src/tweaker/core.ts +2 -4
  88. package/src/tweaker/tweak.ts +8 -8
  89. package/src/tweaker/tweakArray.ts +86 -37
  90. package/src/tweaker/tweakFrozen.ts +0 -1
  91. package/src/tweaker/tweakModel.ts +0 -1
  92. package/src/tweaker/tweakPlainObject.ts +78 -29
  93. package/src/tweaker/typeChecking.ts +132 -33
  94. package/src/types/TypeCheckError.ts +17 -19
  95. package/src/types/createCachedTypeCheck.ts +27 -2
  96. package/src/types/objectBased/typesDataModelData.ts +4 -2
  97. package/src/types/objectBased/typesModel.ts +4 -2
  98. package/src/types/objectBased/typesObject.ts +20 -16
  99. package/src/types/objectBased/typesRecord.ts +6 -13
  100. package/src/types/objectBased/typesRef.ts +2 -2
  101. package/src/types/primitiveBased/typesEnum.ts +4 -2
  102. package/src/types/primitiveBased/typesPrimitive.ts +9 -4
  103. package/src/types/typeCheckErrorUtils.ts +1 -0
  104. package/src/types/utility/typesCodecSupport.ts +97 -56
  105. package/src/types/utility/typesMap.ts +24 -41
  106. package/src/types/utility/typesRefinement.ts +3 -2
  107. package/src/types/utility/typesSet.ts +24 -0
  108. package/src/utils/ModelPool.ts +3 -3
  109. package/src/utils/chainFns.ts +4 -0
  110. package/src/utils/decorators.ts +1 -1
  111. package/src/utils/errorDiagnostics.ts +6 -2
  112. package/src/utils/forEachWithDelayedThrow.ts +83 -0
  113. package/src/utils/index.ts +34 -2
  114. package/src/utils/mapUtils.ts +3 -7
  115. package/src/utils/setIfDifferent.ts +3 -2
  116. package/src/utils/setLike.ts +134 -0
  117. package/src/wrappers/ArraySet.ts +5 -9
  118. package/src/wrappers/ObjectMap.ts +15 -11
  119. package/src/wrappers/asMap.ts +30 -18
  120. package/src/wrappers/asSet.ts +36 -12
  121. package/dist/types/action/modelFlowPromiseGenerator.d.ts +0 -1
  122. package/src/action/modelFlowPromiseGenerator.ts +0 -127
@@ -1,6 +1,6 @@
1
1
  import { isObservableArray, isObservableObject, remove } from "mobx"
2
2
  import type { ModelPropTransform } from "../../modelShared/prop"
3
- import { isArray, isObject, lazy } from "../../utils"
3
+ import { copyOwnEnumerableProps, 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"
@@ -19,23 +19,26 @@ import {
19
19
  type RuntimeAdapter,
20
20
  } from "./typesCodecCore"
21
21
  import { OrTypeInfo, typesOr } from "./typesOr"
22
- import { RefinementTypeInfo } from "./typesRefinement"
22
+ import { RefinementTypeInfo, typesRefinement } from "./typesRefinement"
23
23
  import { SkipCheckTypeInfo, typesSkipCheck } from "./typesSkipCheck"
24
24
  import { TagTypeInfo, typesTag } from "./typesTag"
25
25
 
26
26
  const codecSupportCache = new WeakMap<AnyStandardType, ResolvedCodecSupport>()
27
27
 
28
28
  function getArrayIndex(prop: PropertyKey): number | undefined {
29
- // Proxy get/set traps always receive string|symbol keys (ECMAScript ToPropertyKey),
30
- // so we only need to check for string numeric indices.
31
- if (typeof prop === "string" && /^(0|[1-9]\d*)$/.test(prop)) {
32
- return Number(prop)
33
- }
34
-
35
- return undefined
29
+ // Proxy traps receive string or symbol keys, including strings for numeric indices.
30
+ if (typeof prop !== "string") return undefined
31
+ const first = prop.charCodeAt(0)
32
+ if (first < 48 || first > 57) return undefined
33
+
34
+ const index = Number(prop)
35
+ return Number.isInteger(index) && index >= 0 && index < 0xffffffff && String(index) === prop
36
+ ? index
37
+ : undefined
36
38
  }
37
39
 
38
40
  function normalizeArrayIndex(index: number, length: number): number {
41
+ index = Math.trunc(index) || 0
39
42
  if (index < 0) {
40
43
  return Math.max(length + index, 0)
41
44
  }
@@ -44,6 +47,7 @@ function normalizeArrayIndex(index: number, length: number): number {
44
47
  }
45
48
 
46
49
  function resolveArrayAtIndex(index: number, length: number): number | undefined {
50
+ index = Math.trunc(index) || 0
47
51
  const resolvedIndex = index < 0 ? length + index : index
48
52
 
49
53
  if (resolvedIndex < 0 || resolvedIndex >= length) {
@@ -90,11 +94,7 @@ function objectLikeToStored(
90
94
  return runtime as never
91
95
  }
92
96
 
93
- const stored: Record<string, unknown> = {}
94
- for (const key of Object.keys(runtime)) {
95
- stored[key] = convertValue(key, runtime[key])
96
- }
97
- return stored
97
+ return copyOwnEnumerableProps({}, runtime, (value, key) => convertValue(key, value))
98
98
  }
99
99
 
100
100
  /**
@@ -155,8 +155,7 @@ function createObjectRuntimeAdapter(
155
155
  }
156
156
  }
157
157
 
158
- Reflect.set(target, prop, value)
159
- return true
158
+ return Reflect.set(target, prop, value)
160
159
  },
161
160
 
162
161
  ...objectRecordProxyTraps,
@@ -217,8 +216,7 @@ function createRecordRuntimeAdapter(
217
216
  return true
218
217
  }
219
218
 
220
- Reflect.set(target, prop, value)
221
- return true
219
+ return Reflect.set(target, prop, value)
222
220
  },
223
221
 
224
222
  ...objectRecordProxyTraps,
@@ -250,7 +248,9 @@ function createArrayLikeRuntimeAdapter(
250
248
  const storedByRuntime = new WeakMap<unknown[], unknown[]>()
251
249
 
252
250
  const decodeIndex = (target: unknown[], index: number) =>
253
- index < 0 || index >= target.length || !Reflect.has(target, index)
251
+ index < 0 ||
252
+ index >= target.length ||
253
+ (!isObservableArray(target) && !Reflect.has(target, index))
254
254
  ? undefined
255
255
  : getItemAdapter(index).toRuntime(target[index], (newStoredValue) => {
256
256
  target[index] = newStoredValue
@@ -265,13 +265,18 @@ function createArrayLikeRuntimeAdapter(
265
265
  return Reflect.apply(method, receiver, args)
266
266
  }
267
267
  const materializeRuntimeValues = (target: unknown[]) =>
268
- Array.from({ length: target.length }, (_, index) => decodeIndex(target, index))
268
+ target.map((_value, index) => decodeIndex(target, index))
269
269
  const replaceStoredValues = (target: unknown[], runtimeValues: unknown[]) => {
270
- target.splice(
271
- 0,
272
- target.length,
273
- ...runtimeValues.map((value, index) => getItemAdapter(index).toStored(value))
274
- )
270
+ const storedValues = runtimeValues.map((value, index) => getItemAdapter(index).toStored(value))
271
+ if (isObservableArray(target)) {
272
+ target.replace(storedValues)
273
+ } else {
274
+ target.length = storedValues.length
275
+ for (let i = 0; i < storedValues.length; i++) {
276
+ if (i in storedValues) target[i] = storedValues[i]
277
+ else delete target[i]
278
+ }
279
+ }
275
280
  }
276
281
  const mutateViaRuntime = (
277
282
  target: unknown[],
@@ -296,6 +301,11 @@ function createArrayLikeRuntimeAdapter(
296
301
  }
297
302
 
298
303
  const proxy: unknown[] = new Proxy(stored, {
304
+ has(target, prop) {
305
+ const index = getArrayIndex(prop)
306
+ if (index !== undefined && isObservableArray(target)) return index < target.length
307
+ return Reflect.has(target, prop)
308
+ },
299
309
  get(target, prop, receiver) {
300
310
  const index = getArrayIndex(prop)
301
311
  if (index !== undefined) {
@@ -332,8 +342,9 @@ function createArrayLikeRuntimeAdapter(
332
342
  }
333
343
 
334
344
  const indexValue = target.length - 1
345
+ const present = isObservableArray(target) || indexValue in target
335
346
  const value = target.pop()
336
- return getItemAdapter(indexValue).toRuntime(value)
347
+ return present ? getItemAdapter(indexValue).toRuntime(value) : undefined
337
348
  }
338
349
  case "shift":
339
350
  return () => {
@@ -341,8 +352,9 @@ function createArrayLikeRuntimeAdapter(
341
352
  return undefined
342
353
  }
343
354
 
355
+ const present = isObservableArray(target) || 0 in target
344
356
  const value = target.shift()
345
- return getItemAdapter(0).toRuntime(value)
357
+ return present ? getItemAdapter(0).toRuntime(value) : undefined
346
358
  }
347
359
  case "unshift":
348
360
  return (...items: unknown[]) =>
@@ -350,12 +362,21 @@ function createArrayLikeRuntimeAdapter(
350
362
  ...items.map((item, indexValue) => getItemAdapter(indexValue).toStored(item))
351
363
  )
352
364
  case "splice":
353
- return (start: number, deleteCount?: number, ...items: unknown[]) => {
365
+ return (...args: [start?: number, deleteCount?: number, ...items: unknown[]]) => {
366
+ const [start = 0, deleteCount, ...items] = args
354
367
  const normalizedStart = normalizeArrayIndex(start, target.length)
355
368
  const actualDeleteCount =
356
- deleteCount === undefined
357
- ? target.length - normalizedStart
358
- : Math.max(0, Math.min(deleteCount, target.length - normalizedStart))
369
+ args.length === 0
370
+ ? 0
371
+ : args.length === 1
372
+ ? target.length - normalizedStart
373
+ : Math.max(
374
+ 0,
375
+ Math.min(
376
+ Math.trunc(deleteCount ?? 0) || 0,
377
+ target.length - normalizedStart
378
+ )
379
+ )
359
380
 
360
381
  const removedValues = target
361
382
  .slice(normalizedStart, normalizedStart + actualDeleteCount)
@@ -443,8 +464,7 @@ function createArrayLikeRuntimeAdapter(
443
464
  return true
444
465
  }
445
466
 
446
- Reflect.set(target, prop, value)
447
- return true
467
+ return Reflect.set(target, prop, value)
448
468
  },
449
469
  })
450
470
 
@@ -461,7 +481,7 @@ function createArrayLikeRuntimeAdapter(
461
481
  return cachedStored
462
482
  }
463
483
 
464
- return Array.from(runtime, (value, index) => getItemAdapter(index).toStored(value))
484
+ return runtime.map((value, index) => getItemAdapter(index).toStored(value))
465
485
  }
466
486
 
467
487
  return runtime as never
@@ -490,44 +510,55 @@ function createCodecLeafRuntimeAdapter(
490
510
  toRuntime(stored, setStored) {
491
511
  const cachedRuntime = isObject(stored) ? runtimeByStored.get(stored) : undefined
492
512
 
493
- const runtime = transform.transform({
513
+ // Declared up front (rather than initialized from the call) so that a setter invoked
514
+ // while decoding can read it without hitting the temporal dead zone.
515
+ let runtime: unknown
516
+ let storedChanged = false
517
+ runtime = transform.transform({
494
518
  originalValue: stored,
495
519
  cachedTransformedValue: cachedRuntime,
496
520
  setOriginalValue(newStoredValue) {
497
521
  if (setStored) {
498
- setStored(newStoredValue)
522
+ try {
523
+ setStored(newStoredValue)
524
+ } finally {
525
+ // A retained codec setter can replace the value after decoding. Neither
526
+ // direction of the cache may keep associating it with the old stored value.
527
+ storedChanged = true
528
+ if (isObject(runtime)) storedByRuntime.delete(runtime)
529
+ if (isObject(stored)) runtimeByStored.delete(stored)
530
+ }
499
531
  }
500
532
  },
501
533
  })
502
534
 
503
- if (isObject(runtime)) {
504
- storedByRuntime.set(runtime, stored)
505
- }
506
-
507
- if (isObject(stored)) {
508
- runtimeByStored.set(stored, runtime)
535
+ if (!storedChanged) {
536
+ if (isObject(runtime)) storedByRuntime.set(runtime, stored)
537
+ if (isObject(stored)) runtimeByStored.set(stored, runtime)
509
538
  }
510
539
 
511
540
  return runtime
512
541
  },
513
542
 
514
543
  toStored(runtime) {
515
- const cachedStored = isObject(runtime) ? storedByRuntime.get(runtime) : undefined
516
- if (cachedStored !== undefined) {
517
- return cachedStored
544
+ if (isObject(runtime)) {
545
+ const cachedStored = storedByRuntime.get(runtime)
546
+ if (cachedStored !== undefined || storedByRuntime.has(runtime)) {
547
+ return cachedStored
548
+ }
518
549
  }
519
550
 
551
+ let cacheResult = false
520
552
  const stored = transform.untransform({
521
553
  transformedValue: runtime,
522
- cacheTransformedValue: () => {},
554
+ cacheTransformedValue: () => {
555
+ cacheResult = true
556
+ },
523
557
  })
524
558
 
525
- if (isObject(runtime)) {
526
- storedByRuntime.set(runtime, stored)
527
- }
528
-
529
- if (isObject(stored)) {
530
- runtimeByStored.set(stored, runtime)
559
+ if (cacheResult) {
560
+ if (isObject(runtime)) storedByRuntime.set(runtime, stored)
561
+ if (isObject(stored)) runtimeByStored.set(stored, runtime)
531
562
  }
532
563
 
533
564
  return stored
@@ -536,7 +567,7 @@ function createCodecLeafRuntimeAdapter(
536
567
  toStoredIfCached(runtime) {
537
568
  if (isObject(runtime)) {
538
569
  const cachedStored = storedByRuntime.get(runtime)
539
- if (cachedStored !== undefined) {
570
+ if (cachedStored !== undefined || storedByRuntime.has(runtime)) {
540
571
  return { found: true, value: cachedStored }
541
572
  }
542
573
  }
@@ -675,7 +706,7 @@ function resolveCodecSupportForStandardType(type: AnyStandardType): ResolvedCode
675
706
  }
676
707
  } else if (typeInfo instanceof ObjectTypeInfo) {
677
708
  const childSupports = lazy(() => {
678
- const supports: Record<string, ResolvedCodecSupport> = {}
709
+ const supports: Record<string, ResolvedCodecSupport> = Object.create(null)
679
710
  const props = typeInfo.props
680
711
  for (const propName of Object.keys(props)) {
681
712
  supports[propName] = resolveCodecSupportForStandardType(props[propName].type)
@@ -690,7 +721,7 @@ function resolveCodecSupportForStandardType(type: AnyStandardType): ResolvedCode
690
721
  : {
691
722
  hasCodec: true,
692
723
  storedType: typesObject(() => {
693
- const storedProps: Record<string, AnyStandardType> = {}
724
+ const storedProps: Record<string, AnyStandardType> = Object.create(null)
694
725
  const props = typeInfo.props
695
726
  for (const propName of Object.keys(props)) {
696
727
  storedProps[propName] = childSupports()[propName].storedType
@@ -722,7 +753,17 @@ function resolveCodecSupportForStandardType(type: AnyStandardType): ResolvedCode
722
753
  } else if (typeInfo instanceof RefinementTypeInfo) {
723
754
  const baseSupport = resolveCodecSupportForStandardType(typeInfo.baseType)
724
755
 
725
- resolvedSupport = baseSupport.hasCodec ? baseSupport : noCodecSupport(type)
756
+ resolvedSupport = !baseSupport.hasCodec
757
+ ? noCodecSupport(type)
758
+ : {
759
+ hasCodec: true,
760
+ storedType: typesRefinement(
761
+ baseSupport.storedType,
762
+ (stored) => typeInfo.checkFunction(baseSupport.adapter.toRuntime(stored)),
763
+ typeInfo.typeName
764
+ ),
765
+ adapter: baseSupport.adapter,
766
+ }
726
767
  } else if (typeInfo instanceof SkipCheckTypeInfo) {
727
768
  const baseSupport = resolveCodecSupportForStandardType(typeInfo.baseType)
728
769
 
@@ -1,5 +1,5 @@
1
1
  import type { ModelPropTransform } from "../../modelShared/prop"
2
- import { isMap } from "../../utils"
2
+ import { isMap, setProtoProp } from "../../utils"
3
3
  import { asMap } from "../../wrappers/asMap"
4
4
  import { typesArray } from "../arrayBased/typesArray"
5
5
  import { typesTuple } from "../arrayBased/typesTuple"
@@ -19,6 +19,15 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
19
19
  keyAdapter: RuntimeAdapter<TKeyStored, TKeyRuntime>,
20
20
  valueAdapter: RuntimeAdapter<TValueStored, TValueRuntime>
21
21
  ): Map<TKeyRuntime, TValueRuntime> {
22
+ // Writes through the decoded value must land back on the entry it came from.
23
+ const toRuntimeValue = (storedKey: TKeyStored, storedValue: TValueStored): TValueRuntime =>
24
+ valueAdapter.toRuntime(storedValue, (newStoredValue) => {
25
+ storedMap.set(storedKey, newStoredValue)
26
+ })
27
+
28
+ const getValue = (storedKey: TKeyStored): TValueRuntime =>
29
+ toRuntimeValue(storedKey, storedMap.get(storedKey) as TValueStored)
30
+
22
31
  return new Proxy(storedMap, {
23
32
  get(target, prop, receiver) {
24
33
  switch (prop) {
@@ -29,43 +38,24 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
29
38
  return undefined
30
39
  }
31
40
 
32
- return valueAdapter.toRuntime(
33
- target.get(storedKey) as TValueStored,
34
- (newStoredValue) => {
35
- target.set(storedKey, newStoredValue)
36
- }
37
- )
41
+ return getValue(storedKey)
38
42
  }
39
43
  case "getOrInsert":
40
44
  return (key: TKeyRuntime, defaultValue: TValueRuntime) => {
41
45
  const storedKey = keyAdapter.toStored(key)
42
- if (target.has(storedKey)) {
43
- return valueAdapter.toRuntime(
44
- target.get(storedKey) as TValueStored,
45
- (newStoredValue) => {
46
- target.set(storedKey, newStoredValue)
47
- }
48
- )
46
+ if (!target.has(storedKey)) {
47
+ target.set(storedKey, valueAdapter.toStored(defaultValue))
49
48
  }
50
-
51
- target.set(storedKey, valueAdapter.toStored(defaultValue))
52
- return defaultValue
49
+ return getValue(storedKey)
53
50
  }
54
51
  case "getOrInsertComputed":
55
52
  return (key: TKeyRuntime, callback: (key: TKeyRuntime) => TValueRuntime) => {
56
53
  const storedKey = keyAdapter.toStored(key)
57
- if (target.has(storedKey)) {
58
- return valueAdapter.toRuntime(
59
- target.get(storedKey) as TValueStored,
60
- (newStoredValue) => {
61
- target.set(storedKey, newStoredValue)
62
- }
63
- )
54
+ if (!target.has(storedKey)) {
55
+ const value = callback(key)
56
+ target.set(storedKey, valueAdapter.toStored(value))
64
57
  }
65
-
66
- const value = callback(key)
67
- target.set(storedKey, valueAdapter.toStored(value))
68
- return value
58
+ return getValue(storedKey)
69
59
  }
70
60
  case "set":
71
61
  return (key: TKeyRuntime, value: TValueRuntime) => {
@@ -92,9 +82,7 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
92
82
  target.forEach((value, key) => {
93
83
  callback.call(
94
84
  thisArg,
95
- valueAdapter.toRuntime(value, (newStoredValue) => {
96
- target.set(key, newStoredValue)
97
- }),
85
+ toRuntimeValue(key, value),
98
86
  keyAdapter.toRuntime(key),
99
87
  receiver
100
88
  )
@@ -104,12 +92,7 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
104
92
  case Symbol.iterator:
105
93
  return function* () {
106
94
  for (const [key, value] of target.entries()) {
107
- yield [
108
- keyAdapter.toRuntime(key),
109
- valueAdapter.toRuntime(value, (newStoredValue) => {
110
- target.set(key, newStoredValue)
111
- }),
112
- ] as const
95
+ yield [keyAdapter.toRuntime(key), toRuntimeValue(key, value)] as const
113
96
  }
114
97
  }
115
98
  case "keys":
@@ -121,9 +104,7 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
121
104
  case "values":
122
105
  return function* () {
123
106
  for (const [key, value] of target.entries()) {
124
- yield valueAdapter.toRuntime(value, (newStoredValue) => {
125
- target.set(key, newStoredValue)
126
- })
107
+ yield toRuntimeValue(key, value)
127
108
  }
128
109
  }
129
110
  default:
@@ -158,7 +139,9 @@ function makeObjectBackedMapTransform<TStoredValue, TRuntimeValue>(
158
139
 
159
140
  const result: Record<string, TStoredValue> = {}
160
141
  transformedValue.forEach((value, key) => {
161
- result[key] = valueAdapter.toStored(value)
142
+ const storedValue = valueAdapter.toStored(value)
143
+ if (key === "__proto__") setProtoProp(result, storedValue)
144
+ else result[key] = storedValue
162
145
  })
163
146
  return result
164
147
  },
@@ -41,8 +41,8 @@ import {
41
41
  * @template T Base type.
42
42
  * @param baseType Base type.
43
43
  * @param checkFn Function that will receive the data (if it passes the base type
44
- * check) and return null or false if there were no errors or either a TypeCheckError instance or
45
- * true if there were.
44
+ * check) and return null or true if there were no errors or either a TypeCheckError instance or
45
+ * false if there were.
46
46
  * @returns
47
47
  */
48
48
  export function typesRefinement<T extends AnyType>(
@@ -89,6 +89,7 @@ export function typesRefinement<T extends AnyType>(
89
89
  // override typeCheckedValue
90
90
  return new TypeCheckError({
91
91
  path: refinementErr.path,
92
+ modelTrail: refinementErr.modelTrail,
92
93
  expectedTypeName: refinementErr.expectedTypeName,
93
94
  actualValue: refinementErr.actualValue,
94
95
  typeCheckedValue,
@@ -1,6 +1,15 @@
1
1
  import type { ObservableSet } from "mobx"
2
2
  import type { ModelPropTransform } from "../../modelShared/prop"
3
3
  import { isSet } from "../../utils"
4
+ import {
5
+ isSetLikeDisjointFrom,
6
+ isSetLikeSubsetOf,
7
+ isSetLikeSupersetOf,
8
+ setLikeDifference,
9
+ setLikeIntersection,
10
+ setLikeSymmetricDifference,
11
+ setLikeUnion,
12
+ } from "../../utils/setLike"
4
13
  import { asSet } from "../../wrappers/asSet"
5
14
  import { typesArray } from "../arrayBased/typesArray"
6
15
  import type { AnyType, ArrayType, TypeToData } from "../schemas"
@@ -52,6 +61,21 @@ function makeArrayBackedSetTransform<TStored, TRuntime>(
52
61
  callback.call(thisArg, runtimeValue, runtimeValue, receiver)
53
62
  })
54
63
  }
64
+ case "union":
65
+ return (other: ReadonlySetLike<unknown>) => setLikeUnion(receiver, other)
66
+ case "intersection":
67
+ return (other: ReadonlySetLike<unknown>) => setLikeIntersection(receiver, other)
68
+ case "difference":
69
+ return (other: ReadonlySetLike<unknown>) => setLikeDifference(receiver, other)
70
+ case "symmetricDifference":
71
+ return (other: ReadonlySetLike<unknown>) =>
72
+ setLikeSymmetricDifference(receiver, other)
73
+ case "isSubsetOf":
74
+ return (other: ReadonlySetLike<unknown>) => isSetLikeSubsetOf(receiver, other)
75
+ case "isSupersetOf":
76
+ return (other: ReadonlySetLike<unknown>) => isSetLikeSupersetOf(receiver, other)
77
+ case "isDisjointFrom":
78
+ return (other: ReadonlySetLike<unknown>) => isSetLikeDisjointFrom(receiver, other)
55
79
  case "entries":
56
80
  return function* () {
57
81
  for (const value of target.values()) {
@@ -24,7 +24,7 @@ export class ModelPool {
24
24
  }
25
25
 
26
26
  findModelByTypeAndId(modelType: string, modelId: string | undefined): AnyModel | undefined {
27
- return modelId ? this.pool.get(modelType)?.get(modelId) : undefined
27
+ return modelId !== undefined ? this.pool.get(modelType)?.get(modelId) : undefined
28
28
  }
29
29
 
30
30
  findModelForSnapshot(sn: any): AnyModel | undefined {
@@ -36,7 +36,7 @@ export class ModelPool {
36
36
  const modelInfo = getModelInfoForName(modelType)!
37
37
  const modelIdPropertyName = getModelIdPropertyName(modelInfo.class as ModelClass<AnyModel>)
38
38
 
39
- return modelIdPropertyName
39
+ return modelIdPropertyName !== undefined
40
40
  ? this.findModelByTypeAndId(modelType, (sn as any)[modelIdPropertyName])
41
41
  : undefined
42
42
  }
@@ -56,7 +56,7 @@ let getDeepChildrenModels: GetDeepChildrenModels = (data) => {
56
56
  addNode(node, extensionData) {
57
57
  if (isModel(node)) {
58
58
  const id = node[modelIdKey]
59
- if (id) {
59
+ if (id !== undefined) {
60
60
  const modelType = node[modelTypeKey]
61
61
  let modelsById = extensionData.get(modelType)
62
62
  if (!modelsById) {
@@ -4,6 +4,10 @@ export function chainFns<F extends Function>(...fns: (F | undefined)[]): F | und
4
4
  return undefined
5
5
  }
6
6
 
7
+ if (definedFns.length === 1) {
8
+ return definedFns[0]
9
+ }
10
+
7
11
  const chainedFn = (v: any, ...args: any[]) => {
8
12
  let ret = v
9
13
 
@@ -110,7 +110,7 @@ export function decorateWrapMethodOrField(
110
110
  const baseDescriptor: PropertyDescriptor | undefined = args[2]
111
111
 
112
112
  checkModelDecoratorTaget(decoratorName, target)
113
- checkDecoratorContext("transaction", propertyKey, false)
113
+ checkDecoratorContext(decoratorName, propertyKey, false)
114
114
 
115
115
  const data = getActionNameAndContextOverride(target, propertyKey, true)
116
116
 
@@ -190,7 +190,7 @@ function formatErrorPathForMessage(path: Path | undefined = getErrorPathSnapshot
190
190
  /**
191
191
  * @internal
192
192
  */
193
- function getSafeErrorValuePreview(value: unknown, maxLen = 200): string {
193
+ export function getSafeErrorValuePreview(value: unknown, maxLen = 200): string {
194
194
  let str: string | undefined
195
195
  try {
196
196
  str = JSON.stringify(value)
@@ -203,7 +203,11 @@ function getSafeErrorValuePreview(value: unknown, maxLen = 200): string {
203
203
  try {
204
204
  str = String(value)
205
205
  } catch {
206
- str = Object.prototype.toString.call(value)
206
+ try {
207
+ str = Object.prototype.toString.call(value)
208
+ } catch {
209
+ str = "<unprintable value>"
210
+ }
207
211
  }
208
212
  }
209
213
 
@@ -0,0 +1,83 @@
1
+ import { MobxKeystoneAggregateError } from "./index"
2
+
3
+ /**
4
+ * A failure kept aside while the rest of a delivery runs. It is boxed so that a thrown
5
+ * `undefined` / `null` can still be rethrown as is, and holds a `MobxKeystoneAggregateError`
6
+ * once more than one callback of the same delivery has failed.
7
+ * @internal
8
+ */
9
+ export type DelayedError =
10
+ | { value: unknown; aggregateErrors?: undefined }
11
+ | { value: MobxKeystoneAggregateError; aggregateErrors: unknown[] }
12
+ | undefined
13
+
14
+ /**
15
+ * Adds a failure to the ones already kept aside for the same delivery: a lone failure is kept
16
+ * as is, several are grouped in a `MobxKeystoneAggregateError`. Nested delivery is flattened,
17
+ * so `errors` is always a flat list of callback failures. The accumulator is updated in place.
18
+ * @internal
19
+ */
20
+ export function addDelayedError(
21
+ current: DelayedError,
22
+ error: unknown,
23
+ msg = "multiple callbacks failed"
24
+ ): DelayedError {
25
+ if (!current) {
26
+ return { value: error }
27
+ }
28
+
29
+ let errors = current.aggregateErrors
30
+ if (!errors) {
31
+ // Start from a copy: an aggregate thrown by a callback is not ours to mutate.
32
+ errors = flattenDelayedError(current.value)
33
+ current = { value: new MobxKeystoneAggregateError(errors, msg), aggregateErrors: errors }
34
+ }
35
+
36
+ pushDelayedError(errors, error)
37
+ return current
38
+ }
39
+
40
+ /**
41
+ * Reports the failures kept aside for a delivery, if there were any.
42
+ * @internal
43
+ */
44
+ export function throwDelayedError(delayedError: DelayedError): void {
45
+ if (delayedError) {
46
+ throw delayedError.value
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Runs every callback, then reports the failures (if any) once delivery has finished.
52
+ * Iterates the collection directly, so appended array items are also visited.
53
+ * @internal
54
+ */
55
+ export function forEachWithDelayedThrow<T>(items: Iterable<T>, fn: (item: T) => void): void {
56
+ let delayedError: DelayedError
57
+ for (const item of items) {
58
+ try {
59
+ fn(item)
60
+ } catch (error) {
61
+ delayedError = addDelayedError(delayedError, error)
62
+ }
63
+ }
64
+ throwDelayedError(delayedError)
65
+ }
66
+
67
+ /** @internal */
68
+ export function flattenDelayedError(error: unknown): unknown[] {
69
+ return error instanceof MobxKeystoneAggregateError ? [...error.errors] : [error]
70
+ }
71
+
72
+ /** @internal */
73
+ export function pushDelayedError(errors: unknown[], error: unknown): void {
74
+ if (error instanceof MobxKeystoneAggregateError) {
75
+ const nestedErrors = error.errors
76
+ const length = nestedErrors.length
77
+ for (let i = 0; i < length; i++) {
78
+ errors.push(nestedErrors[i])
79
+ }
80
+ } else {
81
+ errors.push(error)
82
+ }
83
+ }