mobx-keystone 1.25.0 → 2.1.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 (141) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/dist/mobx-keystone.esm.js +3627 -1540
  3. package/dist/mobx-keystone.esm.mjs +3627 -1540
  4. package/dist/mobx-keystone.umd.js +3638 -1543
  5. package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
  6. package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
  7. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +17 -10
  8. package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
  9. package/dist/types/deepChange/onDeepChange.d.ts +9 -0
  10. package/dist/types/parent/arrayPaths.d.ts +1 -0
  11. package/dist/types/parent/detach.d.ts +1 -1
  12. package/dist/types/parent/findChildren.d.ts +1 -1
  13. package/dist/types/parent/treeOrder.d.ts +1 -0
  14. package/dist/types/patch/applyPatches.d.ts +3 -0
  15. package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
  16. package/dist/types/ref/modelRefIdIndex.d.ts +1 -0
  17. package/dist/types/ref/nodesById.d.ts +1 -0
  18. package/dist/types/ref/refIdIndex.d.ts +1 -0
  19. package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
  20. package/dist/types/snapshot/applySnapshot.d.ts +1 -0
  21. package/dist/types/snapshot/fromSnapshot.d.ts +0 -3
  22. package/dist/types/standardActions/arrayActions.d.ts +1 -1
  23. package/dist/types/treeUtils/sandbox.d.ts +0 -4
  24. package/dist/types/tweaker/changeRollback.d.ts +1 -0
  25. package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
  26. package/dist/types/types/TypeCheckError.d.ts +1 -0
  27. package/dist/types/types/utility/typesRefinement.d.ts +2 -2
  28. package/dist/types/utils/ModelPool.d.ts +7 -1
  29. package/dist/types/utils/chunks.d.ts +1 -0
  30. package/dist/types/utils/forEachWithDelayedThrow.d.ts +1 -0
  31. package/dist/types/utils/index.d.ts +13 -0
  32. package/dist/types/utils/mapUtils.d.ts +2 -2
  33. package/dist/types/utils/setLike.d.ts +1 -0
  34. package/package.json +2 -2
  35. package/src/action/applyAction.ts +1 -1
  36. package/src/action/middleware.ts +17 -36
  37. package/src/action/modelFlow.ts +4 -5
  38. package/src/action/mutationBatch.ts +10 -10
  39. package/src/action/pendingActions.ts +3 -4
  40. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +19 -14
  41. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +29 -20
  42. package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +1 -1
  43. package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +4 -3
  44. package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +4 -12
  45. package/src/actionMiddlewares/actionSerialization/setSerializer.ts +4 -13
  46. package/src/actionMiddlewares/actionTrackingMiddleware.ts +11 -13
  47. package/src/actionMiddlewares/transactionMiddleware.ts +55 -25
  48. package/src/actionMiddlewares/undoMiddleware.ts +112 -59
  49. package/src/actionMiddlewares/utils.ts +2 -1
  50. package/src/computedTree/computedTree.ts +14 -3
  51. package/src/context/context.ts +17 -45
  52. package/src/dataModel/BaseDataModel.ts +20 -16
  53. package/src/dataModel/dataModelInstanceCache.ts +8 -0
  54. package/src/deepChange/onDeepChange.ts +179 -24
  55. package/src/frozen/Frozen.ts +50 -33
  56. package/src/model/BaseModel.ts +10 -13
  57. package/src/model/newModel.ts +21 -24
  58. package/src/model/utils.ts +2 -2
  59. package/src/modelShared/modelClassInitializer.ts +3 -2
  60. package/src/modelShared/modelDecorator.ts +39 -39
  61. package/src/modelShared/modelInfo.ts +1 -1
  62. package/src/modelShared/prop.ts +6 -9
  63. package/src/modelShared/sharedInternalModel.ts +16 -3
  64. package/src/parent/arrayPaths.ts +242 -0
  65. package/src/parent/coreObjectChildren.ts +300 -48
  66. package/src/parent/detach.ts +1 -1
  67. package/src/parent/findChildren.ts +4 -7
  68. package/src/parent/onChildAttachedTo.ts +157 -55
  69. package/src/parent/path.ts +15 -5
  70. package/src/parent/setParent.ts +34 -15
  71. package/src/parent/treeOrder.ts +53 -0
  72. package/src/parent/walkTree.ts +12 -101
  73. package/src/patch/applyPatches.ts +74 -34
  74. package/src/patch/emitPatch.ts +142 -26
  75. package/src/patch/jsonPatch.ts +5 -0
  76. package/src/patch/patchRecorder.ts +18 -14
  77. package/src/redux/connectReduxDevTools.ts +24 -18
  78. package/src/redux/redux.ts +5 -21
  79. package/src/ref/core.ts +81 -29
  80. package/src/ref/modelRefIdIndex.ts +131 -0
  81. package/src/ref/nodesById.ts +60 -0
  82. package/src/ref/refIdIndex.ts +209 -0
  83. package/src/rootStore/attachDetach.ts +27 -16
  84. package/src/rootStore/rootStore.ts +10 -6
  85. package/src/snapshot/SnapshotOf.ts +2 -2
  86. package/src/snapshot/applySnapshot.ts +27 -19
  87. package/src/snapshot/clone.ts +1 -2
  88. package/src/snapshot/cloneTreeValue.ts +1 -2
  89. package/src/snapshot/fromModelSnapshot.ts +6 -2
  90. package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
  91. package/src/snapshot/fromSnapshot.ts +2 -6
  92. package/src/snapshot/internal.ts +89 -19
  93. package/src/snapshot/onSnapshot.ts +12 -1
  94. package/src/snapshot/reconcileModelSnapshot.ts +6 -6
  95. package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -4
  96. package/src/standardActions/arrayActions.ts +12 -2
  97. package/src/standardActions/objectActions.ts +3 -2
  98. package/src/transforms/ImmutableDate.ts +4 -0
  99. package/src/transforms/asMap.ts +3 -1
  100. package/src/treeUtils/deepEquals.ts +15 -13
  101. package/src/treeUtils/draft.ts +8 -0
  102. package/src/treeUtils/sandbox.ts +31 -27
  103. package/src/tweaker/changeRollback.ts +218 -0
  104. package/src/tweaker/core.ts +33 -22
  105. package/src/tweaker/treeNodeMetadata.ts +12 -2
  106. package/src/tweaker/tweak.ts +183 -19
  107. package/src/tweaker/tweakArray.ts +176 -33
  108. package/src/tweaker/tweakPlainObject.ts +117 -39
  109. package/src/tweaker/tweakedChangeListeners.ts +61 -0
  110. package/src/tweaker/typeChecking.ts +25 -15
  111. package/src/types/TypeCheckError.ts +17 -19
  112. package/src/types/arrayBased/typesArray.ts +10 -11
  113. package/src/types/createCachedTypeCheck.ts +541 -88
  114. package/src/types/objectBased/typesDataModelData.ts +4 -2
  115. package/src/types/objectBased/typesModel.ts +4 -2
  116. package/src/types/objectBased/typesObject.ts +20 -16
  117. package/src/types/objectBased/typesRecord.ts +8 -18
  118. package/src/types/objectBased/typesRef.ts +2 -2
  119. package/src/types/primitiveBased/typesEnum.ts +4 -2
  120. package/src/types/primitiveBased/typesPrimitive.ts +9 -4
  121. package/src/types/typeCheckErrorUtils.ts +1 -0
  122. package/src/types/utility/typesCodecSupport.ts +97 -56
  123. package/src/types/utility/typesMap.ts +24 -41
  124. package/src/types/utility/typesRefinement.ts +3 -2
  125. package/src/types/utility/typesSet.ts +24 -0
  126. package/src/utils/ModelPool.ts +78 -22
  127. package/src/utils/chainFns.ts +4 -0
  128. package/src/utils/chunks.ts +271 -0
  129. package/src/utils/decorators.ts +1 -1
  130. package/src/utils/errorDiagnostics.ts +6 -2
  131. package/src/utils/forEachWithDelayedThrow.ts +83 -0
  132. package/src/utils/index.ts +71 -2
  133. package/src/utils/mapUtils.ts +3 -7
  134. package/src/utils/setIfDifferent.ts +3 -2
  135. package/src/utils/setLike.ts +134 -0
  136. package/src/wrappers/ArraySet.ts +5 -9
  137. package/src/wrappers/ObjectMap.ts +15 -11
  138. package/src/wrappers/asMap.ts +30 -18
  139. package/src/wrappers/asSet.ts +36 -12
  140. package/dist/types/action/modelFlowPromiseGenerator.d.ts +0 -1
  141. package/src/action/modelFlowPromiseGenerator.ts +0 -127
@@ -1,9 +1,10 @@
1
1
  import { runInAction } from "mobx"
2
2
  import { applyAction } from "../../action/applyAction"
3
3
  import { frozenKey } from "../../frozen/Frozen"
4
+ import type { AnyModel } from "../../model/BaseModel"
4
5
  import { getModelIdPropertyName } from "../../model/getModelMetadata"
5
6
  import { isModel } from "../../model/utils"
6
- import { resolvePath } from "../../parent/path"
7
+ import { fastGetParentToChildPath, resolvePath } from "../../parent/path"
7
8
  import type { WritablePath } from "../../parent/pathTypes"
8
9
  import { applyPatches } from "../../patch/applyPatches"
9
10
  import { onPatches } from "../../patch/emitPatch"
@@ -53,16 +54,28 @@ export function applySerializedActionAndTrackNewModelIds<TRet = any>(
53
54
 
54
55
  const deserializedCall = deserializeActionCall(call, subtreeRoot)
55
56
 
56
- const modelIdOverrides: Patch[] = []
57
+ const modelsWithChangedIds = new Map<AnyModel, string>()
57
58
 
58
59
  // set a patch listener to track changes to model ids
59
60
  const patchDisposer = onPatches(subtreeRoot, (patches) => {
60
- scanPatchesForModelIdChanges(subtreeRoot, modelIdOverrides, patches)
61
+ scanPatchesForModelIdChanges(subtreeRoot, modelsWithChangedIds, patches)
61
62
  })
62
63
 
63
64
  try {
64
65
  const returnValue = applyAction(subtreeRoot, deserializedCall)
65
66
 
67
+ const modelIdOverrides: Patch[] = []
68
+ for (const [model, idProperty] of modelsWithChangedIds) {
69
+ const path = fastGetParentToChildPath(subtreeRoot, model, false)
70
+ if (path) {
71
+ modelIdOverrides.push({
72
+ op: "replace",
73
+ path: [...path, idProperty],
74
+ value: model.$[idProperty],
75
+ })
76
+ }
77
+ }
78
+
66
79
  return {
67
80
  returnValue,
68
81
  serializedActionCall: {
@@ -75,24 +88,23 @@ export function applySerializedActionAndTrackNewModelIds<TRet = any>(
75
88
  }
76
89
  }
77
90
 
78
- function scanPatchesForModelIdChanges(root: object, modelIdOverrides: Patch[], patches: Patch[]) {
91
+ function scanPatchesForModelIdChanges(
92
+ root: object,
93
+ modelsWithChangedIds: Map<AnyModel, string>,
94
+ patches: Patch[]
95
+ ) {
79
96
  const len = patches.length
80
97
  for (let i = 0; i < len; i++) {
81
98
  const patch = patches[i]
82
99
  if (patch.op === "replace" || patch.op === "add") {
83
- deepScanValueForModelIdChanges(
84
- root,
85
- modelIdOverrides,
86
- patch.value,
87
- patch.path as WritablePath
88
- )
100
+ deepScanValueForModelIdChanges(root, modelsWithChangedIds, patch.value, patch.path.slice())
89
101
  }
90
102
  }
91
103
  }
92
104
 
93
105
  function deepScanValueForModelIdChanges(
94
106
  root: object,
95
- modelIdOverrides: Patch[],
107
+ modelsWithChangedIds: Map<AnyModel, string>,
96
108
  value: any,
97
109
  path: WritablePath
98
110
  ) {
@@ -102,20 +114,17 @@ function deepScanValueForModelIdChanges(
102
114
 
103
115
  if (isModel(parent)) {
104
116
  const propertyName = path[path.length - 1]
105
- if (propertyName === getModelIdPropertyName(parent.constructor as any)) {
106
- // found one
107
- modelIdOverrides.push({
108
- op: "replace",
109
- path: path.slice(),
110
- value: value,
111
- })
117
+ const idProperty = getModelIdPropertyName(parent.constructor as any)
118
+ if (idProperty !== undefined && propertyName === idProperty) {
119
+ // Resolve the final path and ID after the action, since this model may move or detach.
120
+ modelsWithChangedIds.set(parent, idProperty)
112
121
  }
113
122
  }
114
123
  } else if (Array.isArray(value)) {
115
124
  const len = value.length
116
125
  for (let i = 0; i < len; i++) {
117
126
  path.push(i)
118
- deepScanValueForModelIdChanges(root, modelIdOverrides, value[i], path)
127
+ deepScanValueForModelIdChanges(root, modelsWithChangedIds, value[i], path)
119
128
  path.pop()
120
129
  }
121
130
  } else if (isObject(value)) {
@@ -128,7 +137,7 @@ function deepScanValueForModelIdChanges(
128
137
  const propValue = value[propName]
129
138
 
130
139
  path.push(propName)
131
- deepScanValueForModelIdChanges(root, modelIdOverrides, propValue, path)
140
+ deepScanValueForModelIdChanges(root, modelsWithChangedIds, propValue, path)
132
141
  path.pop()
133
142
  }
134
143
  }
@@ -11,7 +11,7 @@ export const arraySerializer: ActionCallArgumentSerializer<any[] | IObservableAr
11
11
  }
12
12
 
13
13
  // this will also transform observable arrays into non-observable ones
14
- return value.map(serialize)
14
+ return Array.from(value, serialize)
15
15
  },
16
16
 
17
17
  deserialize(arr, deserialize) {
@@ -1,17 +1,18 @@
1
1
  import { namespace } from "../../utils"
2
2
  import { type ActionCallArgumentSerializer, cannotSerialize } from "./core"
3
3
 
4
- export const dateSerializer: ActionCallArgumentSerializer<Date, number> = {
4
+ export const dateSerializer: ActionCallArgumentSerializer<Date, number | null> = {
5
5
  id: `${namespace}/dateAsTimestamp`,
6
6
 
7
7
  serialize(date) {
8
8
  if (!(date instanceof Date)) {
9
9
  return cannotSerialize
10
10
  }
11
- return +date
11
+ const timestamp = +date
12
+ return Number.isNaN(timestamp) ? null : timestamp
12
13
  },
13
14
 
14
15
  deserialize(timestamp) {
15
- return new Date(timestamp)
16
+ return new Date(timestamp === null ? Number.NaN : timestamp)
16
17
  },
17
18
  }
@@ -15,13 +15,8 @@ export const mapSerializer: ActionCallArgumentSerializer<
15
15
 
16
16
  const arr: [any, any][] = []
17
17
 
18
- const iter = map.keys()
19
- let cur = iter.next()
20
- while (!cur.done) {
21
- const k = cur.value
22
- const v = map.get(k)
23
- arr.push([serialize(k), serialize(v)])
24
- cur = iter.next()
18
+ for (const [key, value] of map.entries()) {
19
+ arr.push([serialize(key), serialize(value)])
25
20
  }
26
21
 
27
22
  return arr
@@ -30,11 +25,8 @@ export const mapSerializer: ActionCallArgumentSerializer<
30
25
  deserialize(arr, deserialize) {
31
26
  const map = new Map()
32
27
 
33
- const len = arr.length
34
- for (let i = 0; i < len; i++) {
35
- const k = arr[i][0]
36
- const v = arr[i][1]
37
- map.set(deserialize(k), deserialize(v))
28
+ for (const [key, value] of arr) {
29
+ map.set(deserialize(key), deserialize(value))
38
30
  }
39
31
 
40
32
  return map
@@ -1,4 +1,4 @@
1
- import type { ObservableSet } from "mobx"
1
+ import { isObservableSet, type ObservableSet } from "mobx"
2
2
  import { namespace } from "../../utils"
3
3
  import { type ActionCallArgumentSerializer, cannotSerialize } from "./core"
4
4
 
@@ -6,21 +6,12 @@ export const setSerializer: ActionCallArgumentSerializer<Set<any> | ObservableSe
6
6
  id: `${namespace}/setAsArray`,
7
7
 
8
8
  serialize(set, serialize) {
9
- if (!(set instanceof Set)) {
9
+ if (!(set instanceof Set || isObservableSet(set))) {
10
10
  return cannotSerialize
11
11
  }
12
12
 
13
- const arr: any[] = []
14
-
15
- const iter = set.keys()
16
- let cur = iter.next()
17
- while (!cur.done) {
18
- const k = cur.value
19
- arr.push(serialize(k))
20
- cur = iter.next()
21
- }
22
-
23
- return arr
13
+ // Array.from closes the iterator if an element cannot be serialized.
14
+ return Array.from(set.keys(), serialize)
24
15
  },
25
16
 
26
17
  deserialize(arr, deserialize) {
@@ -253,15 +253,13 @@ export function actionTrackingMiddleware(
253
253
  state: State.Finished,
254
254
  })
255
255
 
256
- if (hooks.onFinish) {
257
- ret = hooks.onFinish(simpleCtx, ret) || ret
258
- }
259
-
260
- if (parentResumed) {
261
- suspend(parentCtx!)
256
+ try {
257
+ return hooks.onFinish?.(simpleCtx, ret) || ret
258
+ } finally {
259
+ if (parentResumed) {
260
+ suspend(parentCtx!)
261
+ }
262
262
  }
263
-
264
- return ret
265
263
  }
266
264
 
267
265
  const resume = (simpleCtx: SimpleActionContext, real: boolean) => {
@@ -323,10 +321,11 @@ export function actionTrackingMiddleware(
323
321
  retObj = finish(simpleCtx, retObj)
324
322
  } else {
325
323
  try {
326
- retObj = finish(simpleCtx, { result: ActionTrackingResult.Return, value: next() })
324
+ retObj = { result: ActionTrackingResult.Return, value: next() }
327
325
  } catch (err) {
328
- retObj = finish(simpleCtx, { result: ActionTrackingResult.Throw, value: err })
326
+ retObj = { result: ActionTrackingResult.Throw, value: err }
329
327
  }
328
+ retObj = finish(simpleCtx, retObj)
330
329
  }
331
330
 
332
331
  return returnOrThrowActionTrackingReturn(retObj)
@@ -409,9 +408,8 @@ const simpleDataContextSymbol = Symbol("simpleDataContext")
409
408
  * @returns Simplified action context.
410
409
  */
411
410
  export function simplifyActionContext(ctx: ActionContext): SimpleActionContext {
412
- while (ctx.previousAsyncStepContext) {
413
- ctx = ctx.previousAsyncStepContext
414
- }
411
+ // every async step points to the step that spawned its flow (the spawn step points to itself)
412
+ ctx = ctx.spawnAsyncStepContext ?? ctx
415
413
 
416
414
  let simpleCtx = ctx.data[simpleDataContextSymbol]
417
415
  if (!simpleCtx) {
@@ -1,11 +1,13 @@
1
1
  import type { ActionMiddlewareDisposer } from "../action/middleware"
2
+ import { runUnprotected } from "../action/runUnprotected"
2
3
  import type { AnyModel } from "../model/BaseModel"
3
4
  import { assertIsModel } from "../model/utils"
4
5
  import { addModelClassInitializer } from "../modelShared/modelClassInitializer"
5
- import { applyPatches } from "../patch"
6
- import { internalPatchRecorder, type PatchRecorder } from "../patch/patchRecorder"
7
- import { assertIsObject, failure } from "../utils"
6
+ import { ChangeRollbackRecorder } from "../tweaker/changeRollback"
7
+ import { withoutTypeChecking } from "../tweaker/withoutTypeChecking"
8
+ import { assertIsObject, failure, MobxKeystoneAggregateError } from "../utils"
8
9
  import { checkDecoratorContext } from "../utils/decorators"
10
+ import { pushDelayedError } from "../utils/forEachWithDelayedThrow"
9
11
  import {
10
12
  ActionTrackingResult,
11
13
  actionTrackingMiddleware,
@@ -14,7 +16,7 @@ import {
14
16
 
15
17
  /**
16
18
  * Creates a transaction middleware, which reverts changes made by an action / child
17
- * actions when the root action throws an exception by applying inverse patches.
19
+ * actions when the root action throws an exception, restoring the values those changes replaced.
18
20
  *
19
21
  * @template M Model
20
22
  * @param target Object with the root target model object (`model`) and root action name (`actionName`).
@@ -34,14 +36,18 @@ export function transactionMiddleware<M extends AnyModel>(target: {
34
36
  throw failure("target.actionName must be a string")
35
37
  }
36
38
 
37
- const patchRecorderSymbol = Symbol("patchRecorder")
38
- function initPatchRecorder(ctx: SimpleActionContext) {
39
- ctx.rootContext.data[patchRecorderSymbol] = internalPatchRecorder(undefined, {
40
- recording: false,
41
- })
39
+ const rollbackRecorderSymbol = Symbol("rollbackRecorder")
40
+ function initRollbackRecorder(ctx: SimpleActionContext) {
41
+ ctx.rootContext.data[rollbackRecorderSymbol] = {
42
+ recorder: new ChangeRollbackRecorder(),
43
+ activeCount: 0,
44
+ }
42
45
  }
43
- function getPatchRecorder(ctx: SimpleActionContext): PatchRecorder {
44
- return ctx.rootContext.data[patchRecorderSymbol]
46
+ function getRollbackRecorderData(ctx: SimpleActionContext): {
47
+ recorder: ChangeRollbackRecorder
48
+ activeCount: number
49
+ } {
50
+ return ctx.rootContext.data[rollbackRecorderSymbol]
45
51
  }
46
52
 
47
53
  return actionTrackingMiddleware(model, {
@@ -52,30 +58,54 @@ export function transactionMiddleware<M extends AnyModel>(target: {
52
58
  },
53
59
  onStart(ctx) {
54
60
  if (ctx === ctx.rootContext) {
55
- initPatchRecorder(ctx)
61
+ initRollbackRecorder(ctx)
56
62
  }
57
63
  },
64
+ // Actions nest, so recording must only stop once every action of the transaction
65
+ // has been suspended, not as soon as the innermost one returns.
58
66
  onResume(ctx) {
59
- getPatchRecorder(ctx).recording = true
67
+ const data = getRollbackRecorderData(ctx)
68
+ data.activeCount++
69
+ data.recorder.recording = true
60
70
  },
61
71
  onSuspend(ctx) {
62
- getPatchRecorder(ctx).recording = false
72
+ const data = getRollbackRecorderData(ctx)
73
+ data.activeCount--
74
+ data.recorder.recording = data.activeCount > 0
63
75
  },
64
76
  onFinish(ctx, ret) {
65
77
  if (ctx === ctx.rootContext) {
66
- const patchRecorder = getPatchRecorder(ctx)
78
+ const recorder = getRollbackRecorderData(ctx).recorder
79
+ // stop recording, so the rollback is not recorded as well
80
+ recorder.dispose()
67
81
 
68
- try {
69
- if (ret.result === ActionTrackingResult.Throw) {
70
- // undo changes (backwards for inverse patches)
71
- const { events } = patchRecorder
72
- for (let i = events.length - 1; i >= 0; i--) {
73
- const event = events[i]
74
- applyPatches(event.target, event.inversePatches, true)
75
- }
82
+ if (ret.result === ActionTrackingResult.Throw) {
83
+ // revert changes (backwards). Restoring the replaced values themselves
84
+ // (rather than applying inverse patches) keeps node identities, so
85
+ // nodes that were detached and then changed are restored too.
86
+ const { reverts } = recorder
87
+ const rollbackErrors: unknown[] = []
88
+ runUnprotected(() => {
89
+ // it restores the state before the action, which was already checked
90
+ withoutTypeChecking(() => {
91
+ for (let i = reverts.length - 1; i >= 0; i--) {
92
+ // A listener may throw after a change has already been reverted.
93
+ // Keep going so one failure cannot skip the remaining restoration.
94
+ try {
95
+ reverts[i]()
96
+ } catch (error) {
97
+ pushDelayedError(rollbackErrors, error)
98
+ }
99
+ }
100
+ })
101
+ })
102
+ if (rollbackErrors.length > 0) {
103
+ // Keep the action error first so the reason for the rollback is not lost.
104
+ throw new MobxKeystoneAggregateError(
105
+ [ret.value, ...rollbackErrors],
106
+ "transaction rollback callbacks failed"
107
+ )
76
108
  }
77
- } finally {
78
- patchRecorder.dispose()
79
109
  }
80
110
  }
81
111
  },