@requence/event-sourcing 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/{chunk-24xjphpk.js → chunk-06anxtk6.js} +6 -6
- package/dist/{chunk-24xjphpk.js.map → chunk-06anxtk6.js.map} +2 -2
- package/dist/{chunk-263c07sn.js → chunk-1mr4cwpw.js} +8 -8
- package/dist/{chunk-263c07sn.js.map → chunk-1mr4cwpw.js.map} +2 -2
- package/dist/{chunk-3ejt4h7x.js → chunk-2wqpktzp.js} +78 -66
- package/dist/chunk-2wqpktzp.js.map +10 -0
- package/dist/{chunk-dgzw1nj9.js → chunk-467qtkth.js} +8 -3
- package/dist/chunk-467qtkth.js.map +10 -0
- package/dist/chunk-6dn6wxg7.js +11 -0
- package/dist/{chunk-jd4q8fcf.js.map → chunk-6dn6wxg7.js.map} +2 -2
- package/dist/{chunk-pc1q4se9.js → chunk-9qtjz725.js} +2 -2
- package/dist/{chunk-pc1q4se9.js.map → chunk-9qtjz725.js.map} +2 -2
- package/dist/{chunk-tfy01fah.js → chunk-c7sytwpp.js} +4 -4
- package/dist/{chunk-tfy01fah.js.map → chunk-c7sytwpp.js.map} +2 -2
- package/dist/{chunk-vvv14qxy.js → chunk-d6jqbrq2.js} +33 -31
- package/dist/{chunk-vvv14qxy.js.map → chunk-d6jqbrq2.js.map} +3 -3
- package/dist/{chunk-vep2byx4.js → chunk-f95c3da4.js} +77 -64
- package/dist/chunk-f95c3da4.js.map +10 -0
- package/dist/chunk-fpjnk143.js +13 -0
- package/dist/{chunk-6q8mr1sg.js.map → chunk-fpjnk143.js.map} +2 -2
- package/dist/{chunk-8qn1nqnh.js → chunk-fxb9fbvz.js} +9 -9
- package/dist/{chunk-8qn1nqnh.js.map → chunk-fxb9fbvz.js.map} +2 -2
- package/dist/chunk-hmvwn02w.js +22 -0
- package/dist/{chunk-6yrb30rx.js.map → chunk-hmvwn02w.js.map} +2 -2
- package/dist/{chunk-ty9bhdvx.js → chunk-j4mwjx07.js} +5 -3
- package/dist/chunk-j4mwjx07.js.map +10 -0
- package/dist/{chunk-391m4mfp.js → chunk-jw9e0fr0.js} +13 -13
- package/dist/{chunk-391m4mfp.js.map → chunk-jw9e0fr0.js.map} +2 -2
- package/dist/chunk-mbxxvskx.js +67 -0
- package/dist/chunk-mbxxvskx.js.map +10 -0
- package/dist/{chunk-yn2sjcg3.js → chunk-nrhw3kde.js} +57 -8
- package/dist/chunk-nrhw3kde.js.map +10 -0
- package/dist/{chunk-57b88rt5.js → chunk-pcggjrxr.js} +32 -32
- package/dist/{chunk-57b88rt5.js.map → chunk-pcggjrxr.js.map} +2 -2
- package/dist/chunk-tkwe4r0y.js +22 -0
- package/dist/{chunk-2fe8jjdc.js.map → chunk-tkwe4r0y.js.map} +2 -2
- package/dist/{chunk-z388cd25.js → chunk-zkz6ew8c.js} +5 -5
- package/dist/{chunk-z388cd25.js.map → chunk-zkz6ew8c.js.map} +2 -2
- package/dist/createAggregateRoot.d.ts +1 -1
- package/dist/createAggregateRoot.d.ts.map +1 -1
- package/dist/createAggregateRoot.js +15 -14
- package/dist/createAggregateRoot.js.map +1 -1
- package/dist/createCheckpointApi.d.ts +30 -0
- package/dist/createCheckpointApi.d.ts.map +1 -1
- package/dist/createCheckpointApi.js +5 -5
- package/dist/createCheckpointApi.js.map +1 -1
- package/dist/createEventListener.js +8 -8
- package/dist/createEventListener.js.map +1 -1
- package/dist/createEventStore.js +16 -16
- package/dist/createEventStore.js.map +1 -1
- package/dist/createProcessManager.d.ts.map +1 -1
- package/dist/createProcessManager.js +12 -12
- package/dist/createProcessManager.js.map +1 -1
- package/dist/createProjection.d.ts.map +1 -1
- package/dist/createProjection.js +12 -12
- package/dist/createProjection.js.map +1 -1
- package/dist/drizzle/index.js +132 -132
- package/dist/drizzle/index.js.map +2 -2
- package/dist/drizzle/postgres.js +14 -14
- package/dist/drizzle/postgres.js.map +1 -1
- package/dist/errors.js +24 -24
- package/dist/errors.js.map +1 -1
- package/dist/index.js +63 -63
- package/dist/index.js.map +1 -1
- package/dist/lock.d.ts +11 -3
- package/dist/lock.d.ts.map +1 -1
- package/dist/lock.js +4 -2
- package/dist/lock.js.map +1 -1
- package/dist/memory/index.d.ts +14 -3
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +52 -45
- package/dist/memory/index.js.map +3 -3
- package/dist/memory/index.spec.d.ts +2 -0
- package/dist/memory/index.spec.d.ts.map +1 -0
- package/dist/redis/index.js +5 -4
- package/dist/redis/index.js.map +1 -1
- package/dist/redis/redisLock.d.ts +1 -1
- package/dist/redis/redisLock.d.ts.map +1 -1
- package/dist/redis/redisLock.js +5 -4
- package/dist/redis/redisLock.js.map +1 -1
- package/dist/refresh.js +9 -9
- package/dist/refresh.js.map +1 -1
- package/dist/replay.js +9 -9
- package/dist/replay.js.map +1 -1
- package/dist/snapshotGenerator.js +2 -2
- package/dist/superjson.js +8 -8
- package/dist/superjson.js.map +1 -1
- package/dist/surreal/index.d.ts +35 -0
- package/dist/surreal/index.d.ts.map +1 -0
- package/dist/surreal/index.js +415 -0
- package/dist/surreal/index.js.map +10 -0
- package/dist/surreal/index.spec.d.ts +2 -0
- package/dist/surreal/index.spec.d.ts.map +1 -0
- package/dist/surreal/schema.d.ts +12 -0
- package/dist/surreal/schema.d.ts.map +1 -0
- package/dist/surreal/schema.js +12 -0
- package/dist/surreal/schema.js.map +9 -0
- package/dist/tests/projectionMultiInstance.spec.d.ts +2 -0
- package/dist/tests/projectionMultiInstance.spec.d.ts.map +1 -0
- package/dist/tests/storageDriverContract.d.ts +19 -0
- package/dist/tests/storageDriverContract.d.ts.map +1 -0
- package/dist/transaction.js +1 -1
- package/dist/utils.js +6 -6
- package/dist/utils.js.map +1 -1
- package/dist/wrappedEventBuilder.js +8 -8
- package/dist/wrappedEventBuilder.js.map +1 -1
- package/package.json +19 -10
- package/src/createAggregateRoot.ts +24 -5
- package/src/createCheckpointApi.ts +98 -0
- package/src/createProcessManager.ts +7 -6
- package/src/createProjection.ts +91 -65
- package/src/lock.ts +17 -3
- package/src/memory/index.spec.ts +4 -0
- package/src/memory/index.ts +55 -29
- package/src/redis/redisLock.ts +6 -2
- package/src/surreal/index.spec.ts +152 -0
- package/src/surreal/index.ts +604 -0
- package/src/surreal/schema.ts +68 -0
- package/src/tests/aggregateRoot.spec.ts +48 -3
- package/src/tests/eventStore.spec.ts +1 -3
- package/src/tests/processManagerMultiInstance.spec.ts +18 -7
- package/src/tests/projectionMultiInstance.spec.ts +283 -0
- package/src/tests/storageDriverContract.ts +612 -0
- package/dist/chunk-2fe8jjdc.js +0 -22
- package/dist/chunk-3ejt4h7x.js.map +0 -10
- package/dist/chunk-6q8mr1sg.js +0 -13
- package/dist/chunk-6yrb30rx.js +0 -22
- package/dist/chunk-dgzw1nj9.js.map +0 -10
- package/dist/chunk-jd4q8fcf.js +0 -11
- package/dist/chunk-ty9bhdvx.js.map +0 -10
- package/dist/chunk-vep2byx4.js.map +0 -10
- package/dist/chunk-yn2sjcg3.js.map +0 -10
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
CheckpointConcurrencyError2,
|
|
3
|
+
RefreshingSkipError2,
|
|
4
|
+
ProcessManagerEventHandlerExecutionError2
|
|
5
|
+
} from "./chunk-jw9e0fr0.js";
|
|
4
6
|
import {
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
createCheckpointApi2
|
|
8
|
+
} from "./chunk-nrhw3kde.js";
|
|
7
9
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
isRefreshing2,
|
|
11
|
+
withRefreshing2
|
|
12
|
+
} from "./chunk-hmvwn02w.js";
|
|
11
13
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
RefreshingSkipError
|
|
15
|
-
} from "./chunk-391m4mfp.js";
|
|
14
|
+
clone2
|
|
15
|
+
} from "./chunk-fpjnk143.js";
|
|
16
16
|
|
|
17
17
|
// src/createProcessManager.ts
|
|
18
18
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -20,13 +20,13 @@ var processManagerInfoStore = new AsyncLocalStorage;
|
|
|
20
20
|
function withProcessManagerInfo(info, handler) {
|
|
21
21
|
return processManagerInfoStore.run(info, handler);
|
|
22
22
|
}
|
|
23
|
-
function
|
|
23
|
+
function getProcessManagerInfo2() {
|
|
24
24
|
return processManagerInfoStore.getStore();
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
return Boolean(
|
|
26
|
+
function isInsideProcessManager2() {
|
|
27
|
+
return Boolean(getProcessManagerInfo2());
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function buildProcessManagerCreator2(params) {
|
|
30
30
|
return (name, options) => {
|
|
31
31
|
let idle = true;
|
|
32
32
|
let createEventHandlers = () => ({});
|
|
@@ -57,7 +57,7 @@ function buildProcessManagerCreator(params) {
|
|
|
57
57
|
const unlocked = !exclusive ? Promise.resolve() : locks.get(event.streamId)?.catch(() => {}) ?? Promise.resolve();
|
|
58
58
|
const runner = (async () => {
|
|
59
59
|
await unlocked;
|
|
60
|
-
const refreshing =
|
|
60
|
+
const refreshing = isRefreshing2();
|
|
61
61
|
const useCas = hasState && !after && !hydrating && !refreshing;
|
|
62
62
|
while (true) {
|
|
63
63
|
const currentHandler = after ? createAfterEffects(state)[`after${event.type}`] : createEventHandlers(state)[`on${event.type}`];
|
|
@@ -69,7 +69,7 @@ function buildProcessManagerCreator(params) {
|
|
|
69
69
|
try {
|
|
70
70
|
await currentHandler(event, { refreshing });
|
|
71
71
|
} catch (error) {
|
|
72
|
-
if (error instanceof
|
|
72
|
+
if (error instanceof RefreshingSkipError2) {
|
|
73
73
|
if (refreshing) {
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
@@ -78,18 +78,18 @@ function buildProcessManagerCreator(params) {
|
|
|
78
78
|
if (!(error instanceof Error)) {
|
|
79
79
|
throw error;
|
|
80
80
|
}
|
|
81
|
-
throw new
|
|
81
|
+
throw new ProcessManagerEventHandlerExecutionError2(name, after ? `after${event.type}` : `on${event.type}`, event, error);
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
84
|
try {
|
|
85
85
|
await checkpointApi.upsert(event, { state }, useCas ? "cas" : "monotonic");
|
|
86
86
|
break;
|
|
87
87
|
} catch (error) {
|
|
88
|
-
if (!(error instanceof
|
|
88
|
+
if (!(error instanceof CheckpointConcurrencyError2)) {
|
|
89
89
|
throw error;
|
|
90
90
|
}
|
|
91
91
|
const checkpoint = await checkpointApi.load();
|
|
92
|
-
state = checkpoint?.metadata?.state != null ?
|
|
92
|
+
state = checkpoint?.metadata?.state != null ? clone2(checkpoint.metadata.state) : clone2(initialState);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
})();
|
|
@@ -114,21 +114,23 @@ function buildProcessManagerCreator(params) {
|
|
|
114
114
|
await hydrated;
|
|
115
115
|
},
|
|
116
116
|
async beginSession() {
|
|
117
|
-
if (!hasState)
|
|
117
|
+
if (!hasState) {
|
|
118
118
|
return;
|
|
119
|
+
}
|
|
119
120
|
const checkpoint = await checkpointApi.load();
|
|
120
121
|
if (checkpoint) {
|
|
121
|
-
state = checkpoint.metadata?.state != null ?
|
|
122
|
+
state = checkpoint.metadata?.state != null ? clone2(checkpoint.metadata.state) : clone2(initialState);
|
|
122
123
|
lastEventPosition = checkpoint.lastEventPosition;
|
|
123
124
|
} else {
|
|
124
|
-
state =
|
|
125
|
+
state = clone2(initialState);
|
|
125
126
|
lastEventPosition = -1;
|
|
126
127
|
}
|
|
127
128
|
sessionActive = true;
|
|
128
129
|
},
|
|
129
130
|
endSession() {
|
|
130
|
-
if (!hasState)
|
|
131
|
+
if (!hasState) {
|
|
131
132
|
return;
|
|
133
|
+
}
|
|
132
134
|
state = null;
|
|
133
135
|
sessionActive = false;
|
|
134
136
|
},
|
|
@@ -139,8 +141,8 @@ function buildProcessManagerCreator(params) {
|
|
|
139
141
|
await apply(event, true);
|
|
140
142
|
},
|
|
141
143
|
withState(nextState) {
|
|
142
|
-
state =
|
|
143
|
-
initialState =
|
|
144
|
+
state = clone2(nextState);
|
|
145
|
+
initialState = clone2(nextState);
|
|
144
146
|
hasState = true;
|
|
145
147
|
return api;
|
|
146
148
|
},
|
|
@@ -188,7 +190,7 @@ function buildProcessManagerCreator(params) {
|
|
|
188
190
|
await hydrated;
|
|
189
191
|
if (!sessionActive && hasState) {
|
|
190
192
|
const checkpoint = await params.checkpoint.get("processManager", name);
|
|
191
|
-
return checkpoint?.metadata?.state ??
|
|
193
|
+
return checkpoint?.metadata?.state ?? clone2(initialState);
|
|
192
194
|
}
|
|
193
195
|
return state;
|
|
194
196
|
},
|
|
@@ -198,7 +200,7 @@ function buildProcessManagerCreator(params) {
|
|
|
198
200
|
...createEventHandlers(null),
|
|
199
201
|
...createAfterEffects(null)
|
|
200
202
|
});
|
|
201
|
-
checkpointApi =
|
|
203
|
+
checkpointApi = createCheckpointApi2({
|
|
202
204
|
...params.checkpoint,
|
|
203
205
|
name,
|
|
204
206
|
relatedStreamEvents,
|
|
@@ -228,7 +230,7 @@ function buildProcessManagerCreator(params) {
|
|
|
228
230
|
$inferState: null
|
|
229
231
|
};
|
|
230
232
|
async function refresh() {
|
|
231
|
-
await
|
|
233
|
+
await withRefreshing2(async () => {
|
|
232
234
|
await api.beginRefresh();
|
|
233
235
|
await checkpointApi.rehydrated({
|
|
234
236
|
getMetadata() {
|
|
@@ -245,6 +247,6 @@ function buildProcessManagerCreator(params) {
|
|
|
245
247
|
};
|
|
246
248
|
}
|
|
247
249
|
|
|
248
|
-
export {
|
|
250
|
+
export { getProcessManagerInfo2, isInsideProcessManager2, buildProcessManagerCreator2 };
|
|
249
251
|
|
|
250
|
-
//# debugId=
|
|
252
|
+
//# debugId=81E4DAF885A8BB5964756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/createProcessManager.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { AsyncLocalStorage } from 'node:async_hooks'\n\nimport type { SettledOptions } from './createAggregateRoot.ts'\nimport {\n type CheckpointApi,\n type CheckpointMethods,\n createCheckpointApi,\n} from './createCheckpointApi.ts'\nimport type { LoadEvents, OnProgress } from './createEventStore.ts'\nimport {\n CheckpointConcurrencyError,\n ProcessManagerEventHandlerExecutionError,\n RefreshingSkipError,\n} from './errors.ts'\nimport { isRefreshing, withRefreshing } from './refresh.ts'\nimport { clone } from './superjson.ts'\nimport type {\n AfterEffectHandlers,\n BaseOutputEvent,\n EventHandlers,\n EventTemplate,\n Keys,\n Merge,\n StreamEvents,\n} from './utilityTypes.js'\n\n/**\n * Options for `createProcessManager`.\n *\n * `settled` — default {@link SettledOptions} applied when the process manager\n * auto-settles the streams its handlers dispatched commands on (explicit\n * `settled()` calls are not allowed inside a process manager).\n */\nexport type ProcessManagerOptions = {\n settled?: SettledOptions\n}\n\ntype ProcessManagerInfo = {\n name: string\n event: BaseOutputEvent\n settledDefaults?: SettledOptions\n}\nconst processManagerInfoStore = new AsyncLocalStorage<ProcessManagerInfo>()\n\nfunction withProcessManagerInfo(\n info: ProcessManagerInfo,\n handler: () => Promise<void>,\n) {\n return processManagerInfoStore.run(info, handler)\n}\n\nexport function getProcessManagerInfo() {\n return processManagerInfoStore.getStore()\n}\n\nexport function isInsideProcessManager() {\n return Boolean(getProcessManagerInfo())\n}\n\ntype ProcessManagerEventHandlers<Events extends EventTemplate> = EventHandlers<\n Events,\n { refreshing: boolean }\n>\n\ntype ProcessManagerAfterEffectHandlers<Events extends EventTemplate> =\n AfterEffectHandlers<Events, { refreshing: boolean }>\n\nexport type InternalProcessManager = {\n apply: (event: BaseOutputEvent) => Promise<void>\n applyAfter: (event: BaseOutputEvent) => Promise<void>\n beginSession: () => Promise<void>\n endSession: () => void\n setRelatedStreamEvents: (\n generateRelatedStreamEvents: (\n eventHandlers: EventHandlers<EventTemplate>,\n ) => StreamEvents[],\n ) => void\n isReady(): Promise<void>\n beginRefresh(): Promise<void>\n progressRefresh(\n index: number,\n event: BaseOutputEvent,\n after: boolean,\n ): Promise<void>\n endRefresh(): void\n init(catchUp: boolean): Promise<void>\n hasState(): boolean\n}\n\nexport type ProcessManagerApi<\n State = never,\n Events extends EventTemplate = never,\n Flags extends {\n withState: boolean\n withEventHandlers: boolean\n concurrent: boolean\n withAfterEffects: boolean\n } = {\n withState: false\n withEventHandlers: false\n concurrent: false\n withAfterEffects: false\n },\n> = {\n withState: <NextState>(\n state: NextState,\n ) => Omit<\n ProcessManagerApi<NextState, Events, Merge<Flags, { withState: true }>>,\n Keys<Merge<Flags, { withState: true }>>\n >\n withEventHandlers: (\n handlers: [State] extends [never]\n ? ProcessManagerEventHandlers<Events>\n : (state: State) => ProcessManagerEventHandlers<Events>,\n ) => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { withEventHandlers: true }>>,\n Keys<Merge<Flags, { withEventHandlers: true }>>\n >\n withAfterEffects: (\n handlers: [State] extends [never]\n ? ProcessManagerAfterEffectHandlers<Events>\n : (state: State) => ProcessManagerAfterEffectHandlers<Events>,\n ) => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { withAfterEffects: true }>>,\n Keys<Merge<Flags, { withAfterEffects: true }>>\n >\n concurrent: () => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { concurrent: true }>>,\n Keys<Merge<Flags, { concurrent: true }>>\n >\n isReady(): Promise<void>\n $inferState: State\n} & (Flags['withState'] extends true\n ? {\n refreshState: () => Promise<void>\n state: () => Promise<State>\n }\n : {})\n\nexport function buildProcessManagerCreator(params: {\n loadEvents: LoadEvents\n checkpoint: CheckpointMethods\n onRefresh?: OnProgress\n}) {\n return (name: string, options?: ProcessManagerOptions): ProcessManagerApi => {\n let idle = true\n let createEventHandlers: (\n state: any,\n ) => ProcessManagerEventHandlers<any> = () => ({})\n let createAfterEffects: (\n state: any,\n ) => ProcessManagerAfterEffectHandlers<any> = () => ({})\n\n let hasState = false\n let state: any = null\n let initialState: any = null\n let exclusive = true\n let sessionActive = false\n let generateRelatedStreamEvents: (\n eventHandlers: EventHandlers<any>,\n ) => StreamEvents[]\n let checkpointApi: CheckpointApi\n let lastEventPosition = -1\n const { promise: hydrated, resolve: resolveHydrated } =\n Promise.withResolvers<void>()\n\n const locks = new Map<string, Promise<void>>()\n const apply = async (\n event: BaseOutputEvent,\n after: boolean,\n hydrating = false,\n ) => {\n const handler = after\n ? createAfterEffects(state)[`after${event.type}`]\n : createEventHandlers(state)[`on${event.type}`]\n\n if (!handler) {\n return Promise.resolve()\n }\n\n if (hydrating) {\n lastEventPosition = event.position\n } else {\n await hydrated\n // event was already applied through hydration\n if (event.position <= lastEventPosition) {\n return\n }\n }\n const unlocked = !exclusive\n ? Promise.resolve()\n : (locks.get(event.streamId)?.catch(() => {}) ?? Promise.resolve())\n\n const runner = (async () => {\n await unlocked\n const refreshing = isRefreshing()\n // Fold state into the process-manager checkpoint with optimistic\n // concurrency control. Only the stateful `on<Event>` fold needs\n // compare-and-swap — after-effects and stateless managers persist a\n // plain cursor, which is safe to write monotonically. When a concurrent\n // instance advances the checkpoint between our read and write, reload\n // the latest state and re-apply this event on top of it.\n const useCas = hasState && !after && !hydrating && !refreshing\n while (true) {\n const currentHandler = after\n ? createAfterEffects(state)[`after${event.type}`]\n : createEventHandlers(state)[`on${event.type}`]\n await withProcessManagerInfo(\n {\n name,\n event,\n settledDefaults: options?.settled,\n },\n async () => {\n try {\n await currentHandler!(event, { refreshing })\n } catch (error) {\n if (error instanceof RefreshingSkipError) {\n if (refreshing) {\n return\n }\n\n throw new Error(\n 'RefreshingSkipError should never be thrown outside refresh',\n )\n }\n\n if (!(error instanceof Error)) {\n throw error\n }\n\n throw new ProcessManagerEventHandlerExecutionError(\n name,\n after ? `after${event.type}` : `on${event.type}`,\n event,\n error,\n )\n }\n },\n )\n\n try {\n await checkpointApi.upsert(\n event,\n { state },\n useCas ? 'cas' : 'monotonic',\n )\n break\n } catch (error) {\n if (!(error instanceof CheckpointConcurrencyError)) {\n throw error\n }\n\n // A concurrent writer advanced the checkpoint. Reload the latest\n // folded state (and version) and re-apply this event on top of it.\n // This event is emitted only by this instance, so it is never\n // already part of the reloaded state — we must always re-fold it\n // (do not skip on position: events are processed out of global\n // order across instances).\n const checkpoint = await checkpointApi.load()\n state =\n checkpoint?.metadata?.state != null\n ? clone(checkpoint.metadata.state)\n : clone(initialState)\n }\n }\n })()\n\n if (exclusive) {\n locks.set(event.streamId, runner)\n\n runner\n .finally(() => {\n if (locks.get(event.streamId) === runner) {\n locks.delete(event.streamId)\n }\n })\n .catch(() => {})\n return runner\n }\n\n return Promise.resolve()\n }\n\n let resolveRefresh: null | (() => void) = null\n\n const api = {\n name,\n hasState() {\n return hasState\n },\n async isReady() {\n await hydrated\n },\n async beginSession() {\n if (!hasState) return\n // Load through the checkpoint API so the local version is synced with\n // the store; the subsequent fold relies on it for compare-and-swap.\n const checkpoint = await checkpointApi.load()\n if (checkpoint) {\n state =\n checkpoint.metadata?.state != null\n ? clone(checkpoint.metadata.state)\n : clone(initialState)\n lastEventPosition = checkpoint.lastEventPosition\n } else {\n state = clone(initialState)\n lastEventPosition = -1\n }\n sessionActive = true\n },\n endSession() {\n if (!hasState) return\n state = null\n sessionActive = false\n },\n async apply(event) {\n await apply(event, false)\n },\n async applyAfter(event) {\n await apply(event, true)\n },\n withState(nextState) {\n state = clone(nextState)\n initialState = clone(nextState)\n hasState = true\n return api as any\n },\n concurrent() {\n exclusive = false\n return api\n },\n withEventHandlers(handlers) {\n createEventHandlers =\n typeof handlers === 'function' ? (handlers as any) : () => handlers\n\n return api\n },\n withAfterEffects(handlers) {\n createAfterEffects =\n typeof handlers === 'function' ? (handlers as any) : () => handlers\n\n return api\n },\n setRelatedStreamEvents(generate) {\n generateRelatedStreamEvents = generate\n },\n async beginRefresh() {\n params.onRefresh?.begin?.(name)\n await checkpointApi.clear()\n lastEventPosition = -1\n state = initialState\n },\n async progressRefresh(index, event, after) {\n await apply(event, after, true)\n\n if (\n !checkpointApi.relatedStreamEvents ||\n checkpointApi.relatedStreamEvents.some((streamEvent) =>\n streamEvent.events.includes(event.type),\n )\n ) {\n params.onRefresh?.progress?.(name, index, event)\n }\n },\n endRefresh() {\n params.onRefresh?.end?.(name)\n resolveHydrated()\n },\n async refreshState() {\n if (!idle) {\n await refresh()\n return\n }\n const { promise, resolve } = Promise.withResolvers<void>()\n resolveRefresh = resolve\n return promise\n },\n async state() {\n await hydrated\n if (!sessionActive && hasState) {\n const checkpoint = await params.checkpoint.get(\n 'processManager',\n name,\n )\n return checkpoint?.metadata?.state ?? clone(initialState)\n }\n return state\n },\n async init(catchUp) {\n idle = false\n const relatedStreamEvents = generateRelatedStreamEvents({\n ...createEventHandlers(null),\n ...createAfterEffects(null),\n })\n\n checkpointApi = createCheckpointApi({\n ...params.checkpoint,\n name,\n relatedStreamEvents,\n type: 'processManager',\n loadEvents: params.loadEvents,\n apply: (event) => apply(event, false, true),\n applyAfter: (event) => apply(event, true, true),\n })\n\n if (resolveRefresh) {\n await refresh()\n return\n }\n if (!catchUp) {\n return\n }\n\n await checkpointApi.rehydrated({\n getMetadata() {\n return { state }\n },\n onMetadata(metadata) {\n state = metadata.state ?? state\n },\n })\n\n // Release state after hydration — it will be loaded\n // from the checkpoint on the next beginSession call\n state = null\n\n resolveHydrated()\n },\n $inferState: null as never,\n } as ProcessManagerApi & InternalProcessManager\n\n async function refresh() {\n await withRefreshing(async () => {\n await api.beginRefresh()\n await checkpointApi.rehydrated({\n getMetadata() {\n return { state }\n },\n onProgress(index, event) {\n params.onRefresh?.progress?.(name, index, event)\n },\n })\n })\n api.endRefresh()\n }\n\n return api\n }\n}\n"
|
|
5
|
+
"import { AsyncLocalStorage } from 'node:async_hooks'\n\nimport type { SettledOptions } from './createAggregateRoot.ts'\nimport {\n type CheckpointApi,\n type CheckpointMethods,\n createCheckpointApi,\n} from './createCheckpointApi.ts'\nimport type { LoadEvents, OnProgress } from './createEventStore.ts'\nimport {\n CheckpointConcurrencyError,\n ProcessManagerEventHandlerExecutionError,\n RefreshingSkipError,\n} from './errors.ts'\nimport { isRefreshing, withRefreshing } from './refresh.ts'\nimport { clone } from './superjson.ts'\nimport type {\n AfterEffectHandlers,\n BaseOutputEvent,\n EventHandlers,\n EventTemplate,\n Keys,\n Merge,\n StreamEvents,\n} from './utilityTypes.js'\n\n/**\n * Options for `createProcessManager`.\n *\n * `settled` — default {@link SettledOptions} applied when the process manager\n * auto-settles the streams its handlers dispatched commands on (explicit\n * `settled()` calls are not allowed inside a process manager).\n */\nexport type ProcessManagerOptions = {\n settled?: SettledOptions\n}\n\ntype ProcessManagerInfo = {\n name: string\n event: BaseOutputEvent\n settledDefaults?: SettledOptions\n}\nconst processManagerInfoStore = new AsyncLocalStorage<ProcessManagerInfo>()\n\nfunction withProcessManagerInfo(\n info: ProcessManagerInfo,\n handler: () => Promise<void>,\n) {\n return processManagerInfoStore.run(info, handler)\n}\n\nexport function getProcessManagerInfo() {\n return processManagerInfoStore.getStore()\n}\n\nexport function isInsideProcessManager() {\n return Boolean(getProcessManagerInfo())\n}\n\ntype ProcessManagerEventHandlers<Events extends EventTemplate> = EventHandlers<\n Events,\n { refreshing: boolean }\n>\n\ntype ProcessManagerAfterEffectHandlers<Events extends EventTemplate> =\n AfterEffectHandlers<Events, { refreshing: boolean }>\n\nexport type InternalProcessManager = {\n apply: (event: BaseOutputEvent) => Promise<void>\n applyAfter: (event: BaseOutputEvent) => Promise<void>\n beginSession: () => Promise<void>\n endSession: () => void\n setRelatedStreamEvents: (\n generateRelatedStreamEvents: (\n eventHandlers: EventHandlers<EventTemplate>,\n ) => StreamEvents[],\n ) => void\n isReady(): Promise<void>\n beginRefresh(): Promise<void>\n progressRefresh(\n index: number,\n event: BaseOutputEvent,\n after: boolean,\n ): Promise<void>\n endRefresh(): void\n init(catchUp: boolean): Promise<void>\n hasState(): boolean\n}\n\nexport type ProcessManagerApi<\n State = never,\n Events extends EventTemplate = never,\n Flags extends {\n withState: boolean\n withEventHandlers: boolean\n concurrent: boolean\n withAfterEffects: boolean\n } = {\n withState: false\n withEventHandlers: false\n concurrent: false\n withAfterEffects: false\n },\n> = {\n withState: <NextState>(\n state: NextState,\n ) => Omit<\n ProcessManagerApi<NextState, Events, Merge<Flags, { withState: true }>>,\n Keys<Merge<Flags, { withState: true }>>\n >\n withEventHandlers: (\n handlers: [State] extends [never]\n ? ProcessManagerEventHandlers<Events>\n : (state: State) => ProcessManagerEventHandlers<Events>,\n ) => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { withEventHandlers: true }>>,\n Keys<Merge<Flags, { withEventHandlers: true }>>\n >\n withAfterEffects: (\n handlers: [State] extends [never]\n ? ProcessManagerAfterEffectHandlers<Events>\n : (state: State) => ProcessManagerAfterEffectHandlers<Events>,\n ) => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { withAfterEffects: true }>>,\n Keys<Merge<Flags, { withAfterEffects: true }>>\n >\n concurrent: () => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { concurrent: true }>>,\n Keys<Merge<Flags, { concurrent: true }>>\n >\n isReady(): Promise<void>\n $inferState: State\n} & (Flags['withState'] extends true\n ? {\n refreshState: () => Promise<void>\n state: () => Promise<State>\n }\n : {})\n\nexport function buildProcessManagerCreator(params: {\n loadEvents: LoadEvents\n checkpoint: CheckpointMethods\n onRefresh?: OnProgress\n}) {\n return (name: string, options?: ProcessManagerOptions): ProcessManagerApi => {\n let idle = true\n let createEventHandlers: (\n state: any,\n ) => ProcessManagerEventHandlers<any> = () => ({})\n let createAfterEffects: (\n state: any,\n ) => ProcessManagerAfterEffectHandlers<any> = () => ({})\n\n let hasState = false\n let state: any = null\n let initialState: any = null\n let exclusive = true\n let sessionActive = false\n let generateRelatedStreamEvents: (\n eventHandlers: EventHandlers<any>,\n ) => StreamEvents[]\n let checkpointApi: CheckpointApi\n let lastEventPosition = -1\n const { promise: hydrated, resolve: resolveHydrated } =\n Promise.withResolvers<void>()\n\n const locks = new Map<string, Promise<void>>()\n const apply = async (\n event: BaseOutputEvent,\n after: boolean,\n hydrating = false,\n ) => {\n const handler = after\n ? createAfterEffects(state)[`after${event.type}`]\n : createEventHandlers(state)[`on${event.type}`]\n\n if (!handler) {\n return Promise.resolve()\n }\n\n if (hydrating) {\n lastEventPosition = event.position\n } else {\n await hydrated\n // event was already applied through hydration\n if (event.position <= lastEventPosition) {\n return\n }\n }\n const unlocked = !exclusive\n ? Promise.resolve()\n : (locks.get(event.streamId)?.catch(() => {}) ?? Promise.resolve())\n\n const runner = (async () => {\n await unlocked\n const refreshing = isRefreshing()\n // Fold state into the process-manager checkpoint with optimistic\n // concurrency control. Only the stateful `on<Event>` fold needs\n // compare-and-swap — after-effects and stateless managers persist a\n // plain cursor, which is safe to write monotonically. When a concurrent\n // instance advances the checkpoint between our read and write, reload\n // the latest state and re-apply this event on top of it.\n const useCas = hasState && !after && !hydrating && !refreshing\n while (true) {\n const currentHandler = after\n ? createAfterEffects(state)[`after${event.type}`]\n : createEventHandlers(state)[`on${event.type}`]\n await withProcessManagerInfo(\n {\n name,\n event,\n settledDefaults: options?.settled,\n },\n async () => {\n try {\n await currentHandler!(event, { refreshing })\n } catch (error) {\n if (error instanceof RefreshingSkipError) {\n if (refreshing) {\n return\n }\n\n throw new Error(\n 'RefreshingSkipError should never be thrown outside refresh',\n )\n }\n\n if (!(error instanceof Error)) {\n throw error\n }\n\n throw new ProcessManagerEventHandlerExecutionError(\n name,\n after ? `after${event.type}` : `on${event.type}`,\n event,\n error,\n )\n }\n },\n )\n\n try {\n await checkpointApi.upsert(\n event,\n { state },\n useCas ? 'cas' : 'monotonic',\n )\n break\n } catch (error) {\n if (!(error instanceof CheckpointConcurrencyError)) {\n throw error\n }\n\n // A concurrent writer advanced the checkpoint. Reload the latest\n // folded state (and version) and re-apply this event on top of it.\n // This event is emitted only by this instance, so it is never\n // already part of the reloaded state — we must always re-fold it\n // (do not skip on position: events are processed out of global\n // order across instances).\n const checkpoint = await checkpointApi.load()\n state =\n checkpoint?.metadata?.state != null\n ? clone(checkpoint.metadata.state)\n : clone(initialState)\n }\n }\n })()\n\n if (exclusive) {\n locks.set(event.streamId, runner)\n\n runner\n .finally(() => {\n if (locks.get(event.streamId) === runner) {\n locks.delete(event.streamId)\n }\n })\n .catch(() => {})\n return runner\n }\n\n return Promise.resolve()\n }\n\n let resolveRefresh: null | (() => void) = null\n\n const api = {\n name,\n hasState() {\n return hasState\n },\n async isReady() {\n await hydrated\n },\n async beginSession() {\n if (!hasState) {\n return\n }\n // Load through the checkpoint API so the local version is synced with\n // the store; the subsequent fold relies on it for compare-and-swap.\n const checkpoint = await checkpointApi.load()\n if (checkpoint) {\n state =\n checkpoint.metadata?.state != null\n ? clone(checkpoint.metadata.state)\n : clone(initialState)\n lastEventPosition = checkpoint.lastEventPosition\n } else {\n state = clone(initialState)\n lastEventPosition = -1\n }\n sessionActive = true\n },\n endSession() {\n if (!hasState) {\n return\n }\n state = null\n sessionActive = false\n },\n async apply(event) {\n await apply(event, false)\n },\n async applyAfter(event) {\n await apply(event, true)\n },\n withState(nextState) {\n state = clone(nextState)\n initialState = clone(nextState)\n hasState = true\n return api as any\n },\n concurrent() {\n exclusive = false\n return api\n },\n withEventHandlers(handlers) {\n createEventHandlers =\n typeof handlers === 'function' ? (handlers as any) : () => handlers\n\n return api\n },\n withAfterEffects(handlers) {\n createAfterEffects =\n typeof handlers === 'function' ? (handlers as any) : () => handlers\n\n return api\n },\n setRelatedStreamEvents(generate) {\n generateRelatedStreamEvents = generate\n },\n async beginRefresh() {\n params.onRefresh?.begin?.(name)\n await checkpointApi.clear()\n lastEventPosition = -1\n state = initialState\n },\n async progressRefresh(index, event, after) {\n await apply(event, after, true)\n\n if (\n !checkpointApi.relatedStreamEvents ||\n checkpointApi.relatedStreamEvents.some((streamEvent) =>\n streamEvent.events.includes(event.type),\n )\n ) {\n params.onRefresh?.progress?.(name, index, event)\n }\n },\n endRefresh() {\n params.onRefresh?.end?.(name)\n resolveHydrated()\n },\n async refreshState() {\n if (!idle) {\n await refresh()\n return\n }\n const { promise, resolve } = Promise.withResolvers<void>()\n resolveRefresh = resolve\n return promise\n },\n async state() {\n await hydrated\n if (!sessionActive && hasState) {\n const checkpoint = await params.checkpoint.get('processManager', name)\n return checkpoint?.metadata?.state ?? clone(initialState)\n }\n return state\n },\n async init(catchUp) {\n idle = false\n const relatedStreamEvents = generateRelatedStreamEvents({\n ...createEventHandlers(null),\n ...createAfterEffects(null),\n })\n\n checkpointApi = createCheckpointApi({\n ...params.checkpoint,\n name,\n relatedStreamEvents,\n type: 'processManager',\n loadEvents: params.loadEvents,\n apply: (event) => apply(event, false, true),\n applyAfter: (event) => apply(event, true, true),\n })\n\n if (resolveRefresh) {\n await refresh()\n return\n }\n if (!catchUp) {\n return\n }\n\n await checkpointApi.rehydrated({\n getMetadata() {\n return { state }\n },\n onMetadata(metadata) {\n state = metadata.state ?? state\n },\n })\n\n // Release state after hydration — it will be loaded\n // from the checkpoint on the next beginSession call\n state = null\n\n resolveHydrated()\n },\n $inferState: null as never,\n } as ProcessManagerApi & InternalProcessManager\n\n async function refresh() {\n await withRefreshing(async () => {\n await api.beginRefresh()\n await checkpointApi.rehydrated({\n getMetadata() {\n return { state }\n },\n onProgress(index, event) {\n params.onRefresh?.progress?.(name, index, event)\n },\n })\n })\n api.endRefresh()\n }\n\n return api\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AA0CA,IAAM,0BAA0B,IAAI;AAEpC,SAAS,sBAAsB,CAC7B,MACA,SACA;AAAA,EACA,OAAO,wBAAwB,IAAI,MAAM,OAAO;AAAA;AAG3C,SAAS,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "81E4DAF885A8BB5964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
ConcurrencyError2,
|
|
3
|
+
ValidationError2,
|
|
4
|
+
EventValidationError2,
|
|
5
|
+
InfiniteLoopError2,
|
|
6
|
+
CommandError2
|
|
7
|
+
} from "./chunk-jw9e0fr0.js";
|
|
4
8
|
import {
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
isRefreshing2
|
|
10
|
+
} from "./chunk-hmvwn02w.js";
|
|
7
11
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
wrappedEventBuilder
|
|
11
|
-
} from "./chunk-z388cd25.js";
|
|
12
|
+
clone2
|
|
13
|
+
} from "./chunk-fpjnk143.js";
|
|
12
14
|
import {
|
|
13
|
-
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
getProcessManagerInfo2
|
|
16
|
+
} from "./chunk-d6jqbrq2.js";
|
|
15
17
|
import {
|
|
16
18
|
createSnapshotGenerator
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-fxb9fbvz.js";
|
|
18
20
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "./chunk-tfy01fah.js";
|
|
21
|
+
getProjectionInfo2
|
|
22
|
+
} from "./chunk-2wqpktzp.js";
|
|
22
23
|
import {
|
|
23
|
-
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
DEFAULT_LOCK_TTL2
|
|
25
|
+
} from "./chunk-j4mwjx07.js";
|
|
25
26
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
normalizeArray2,
|
|
28
|
+
awaitAll2
|
|
29
|
+
} from "./chunk-c7sytwpp.js";
|
|
30
|
+
import {
|
|
31
|
+
unwrapEvent2,
|
|
32
|
+
handleEventError2,
|
|
33
|
+
wrappedEventBuilder2
|
|
34
|
+
} from "./chunk-zkz6ew8c.js";
|
|
32
35
|
|
|
33
36
|
// src/createAggregateRoot.ts
|
|
34
37
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -55,7 +58,7 @@ var snapshotSchema = z.object({
|
|
|
55
58
|
lastEventPosition: z.int(),
|
|
56
59
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
57
60
|
});
|
|
58
|
-
function
|
|
61
|
+
function createAggregateRoot2(type) {
|
|
59
62
|
let initialState = null;
|
|
60
63
|
let createEventHandlers = () => ({});
|
|
61
64
|
let loadEvents;
|
|
@@ -78,7 +81,7 @@ function createAggregateRoot(type) {
|
|
|
78
81
|
if (snapshot) {
|
|
79
82
|
const result = snapshotSchema.safeParse(snapshot);
|
|
80
83
|
if (!result.success) {
|
|
81
|
-
throw new
|
|
84
|
+
throw new ValidationError2("loaded aggregate root snapshot does not comply with schema", result.error);
|
|
82
85
|
}
|
|
83
86
|
snapshotApi.internalApply(result.data);
|
|
84
87
|
position = snapshot.lastEventPosition;
|
|
@@ -108,6 +111,7 @@ function createAggregateRoot(type) {
|
|
|
108
111
|
let downstreamSettledPromise = () => Promise.resolve();
|
|
109
112
|
let releaseLock;
|
|
110
113
|
let extendLock;
|
|
114
|
+
let lockTtl;
|
|
111
115
|
const warnReleaseLock = () => {
|
|
112
116
|
if (releasedAfterChain) {
|
|
113
117
|
console.warn(`aggregate root for ${type}:${streamId} was open too long`);
|
|
@@ -140,21 +144,30 @@ function createAggregateRoot(type) {
|
|
|
140
144
|
let commandChain = createLock([type, streamId]).then((lock) => {
|
|
141
145
|
releaseLock = lock.release;
|
|
142
146
|
extendLock = lock.extend;
|
|
147
|
+
lockTtl = lock.ttl;
|
|
143
148
|
return [];
|
|
144
149
|
});
|
|
150
|
+
const keepLockAlive = async (work) => {
|
|
151
|
+
const timer = setInterval(() => void extendLock().catch(() => false), (lockTtl ?? DEFAULT_LOCK_TTL2) / 4);
|
|
152
|
+
try {
|
|
153
|
+
return await work();
|
|
154
|
+
} finally {
|
|
155
|
+
clearInterval(timer);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
145
158
|
let commandChainTouched = false;
|
|
146
159
|
let releasedAfterChain = false;
|
|
147
160
|
let transactionDelay;
|
|
148
161
|
const stateReady = Promise.withResolvers();
|
|
149
|
-
const rawCommands = createCommands(state,
|
|
162
|
+
const rawCommands = createCommands(state, wrappedEventBuilder2, {
|
|
150
163
|
streamId,
|
|
151
164
|
extendLock
|
|
152
165
|
});
|
|
153
166
|
const commands = Object.fromEntries(Object.entries(rawCommands).map(([commandName, commandFn]) => [
|
|
154
167
|
commandName,
|
|
155
168
|
(...args) => {
|
|
156
|
-
if (
|
|
157
|
-
return
|
|
169
|
+
if (isRefreshing2()) {
|
|
170
|
+
return api.commands;
|
|
158
171
|
}
|
|
159
172
|
dispatchedCommands.push({ name: commandName, args });
|
|
160
173
|
const commandId = crypto.randomUUID();
|
|
@@ -164,11 +177,11 @@ function createAggregateRoot(type) {
|
|
|
164
177
|
if (previousEvents instanceof Error) {
|
|
165
178
|
return previousEvents;
|
|
166
179
|
}
|
|
167
|
-
const events = (await
|
|
180
|
+
const events = (await normalizeArray2(commandFn.apply(rawCommands, args), () => extendLock())).filter((event) => Boolean(event));
|
|
168
181
|
const baseInputEvents = await Promise.all(events.map(async (event) => {
|
|
169
182
|
event.withAdditionalMetadata({ commandName });
|
|
170
183
|
event.withDefaultCausationId(commandId);
|
|
171
|
-
const unwrappedEvent =
|
|
184
|
+
const unwrappedEvent = unwrapEvent2(event);
|
|
172
185
|
const eventVersions = registeredEvents.get(unwrappedEvent.type);
|
|
173
186
|
if (!eventVersions) {
|
|
174
187
|
throw new Error(`cannot append unknown event ${unwrappedEvent.type}`);
|
|
@@ -177,8 +190,8 @@ function createAggregateRoot(type) {
|
|
|
177
190
|
const schema = eventVersions.get(latestVersion);
|
|
178
191
|
const result = schema.safeParse(unwrappedEvent.payload);
|
|
179
192
|
if (!result.success) {
|
|
180
|
-
const error = new
|
|
181
|
-
await
|
|
193
|
+
const error = new EventValidationError2(`payload does not comply with schema for event ${unwrappedEvent.type}@${latestVersion} at stream ${streamId}`, result.error);
|
|
194
|
+
await handleEventError2(event, error);
|
|
182
195
|
throw error;
|
|
183
196
|
}
|
|
184
197
|
unwrappedEvent.payload = result.data;
|
|
@@ -189,10 +202,10 @@ function createAggregateRoot(type) {
|
|
|
189
202
|
}));
|
|
190
203
|
const eventHandlers = createEventHandlers(state);
|
|
191
204
|
for (const event of baseInputEvents) {
|
|
192
|
-
const processManagerInfo =
|
|
205
|
+
const processManagerInfo = getProcessManagerInfo2();
|
|
193
206
|
if (processManagerInfo) {
|
|
194
207
|
if (processManagerInfo.event.streamId === streamId && processManagerInfo.event.streamType === type && processManagerInfo.event.type === event.type && deepEqual(processManagerInfo.event.payload, event.payload)) {
|
|
195
|
-
return new
|
|
208
|
+
return new InfiniteLoopError2(`Infinite loop detected. Process Manager "${processManagerInfo.name}" listens and emits same event "${event.type}" for stream ${type}:${streamId}, causing a recurring event cycle`);
|
|
196
209
|
}
|
|
197
210
|
}
|
|
198
211
|
await eventHandlers[`on${event.type}`]?.({
|
|
@@ -206,10 +219,10 @@ function createAggregateRoot(type) {
|
|
|
206
219
|
if (!(error instanceof Error)) {
|
|
207
220
|
throw error;
|
|
208
221
|
}
|
|
209
|
-
const commandError = new
|
|
222
|
+
const commandError = new CommandError2(type, commandName, streamId, error);
|
|
210
223
|
for (const [ErrorType, handler] of errorHandlers) {
|
|
211
224
|
if (error instanceof ErrorType) {
|
|
212
|
-
return new
|
|
225
|
+
return new CommandError2(type, commandName, streamId, handler(commandError));
|
|
213
226
|
}
|
|
214
227
|
}
|
|
215
228
|
return commandError;
|
|
@@ -241,7 +254,7 @@ function createAggregateRoot(type) {
|
|
|
241
254
|
});
|
|
242
255
|
}
|
|
243
256
|
});
|
|
244
|
-
return
|
|
257
|
+
return api.commands;
|
|
245
258
|
}
|
|
246
259
|
]));
|
|
247
260
|
setImmediate(() => {
|
|
@@ -253,9 +266,9 @@ function createAggregateRoot(type) {
|
|
|
253
266
|
});
|
|
254
267
|
}
|
|
255
268
|
});
|
|
256
|
-
const
|
|
269
|
+
const api = {
|
|
257
270
|
load(filter) {
|
|
258
|
-
commandChain = commandChain.then(() => loadUntil(state, snapshotApi, streamId, undefined, filter)).then((loadedVersion) => {
|
|
271
|
+
commandChain = commandChain.then(() => keepLockAlive(() => loadUntil(state, snapshotApi, streamId, undefined, filter))).then((loadedVersion) => {
|
|
259
272
|
version = loadedVersion;
|
|
260
273
|
return [];
|
|
261
274
|
});
|
|
@@ -265,7 +278,7 @@ function createAggregateRoot(type) {
|
|
|
265
278
|
async state() {
|
|
266
279
|
const maybeError = await Promise.race([
|
|
267
280
|
stateReady.promise,
|
|
268
|
-
|
|
281
|
+
awaitAll2(() => commandChain)
|
|
269
282
|
]);
|
|
270
283
|
if (maybeError) {
|
|
271
284
|
throw maybeError;
|
|
@@ -273,7 +286,7 @@ function createAggregateRoot(type) {
|
|
|
273
286
|
return state;
|
|
274
287
|
},
|
|
275
288
|
async settled(options) {
|
|
276
|
-
const parentProcessManager =
|
|
289
|
+
const parentProcessManager = getProcessManagerInfo2();
|
|
277
290
|
if (parentProcessManager) {
|
|
278
291
|
const error = new Error(`aggregateRoot.settled() cannot be called inside a process manager. Attempted on aggregate stream "${type}" within Process Manager ${parentProcessManager.name}. To configure how dispatched streams settle, pass options to createProcessManager(name, { settled }).`);
|
|
279
292
|
commandChain = commandChain.then(() => error);
|
|
@@ -286,11 +299,11 @@ function createAggregateRoot(type) {
|
|
|
286
299
|
}
|
|
287
300
|
const maxRetries = options?.maxRetries ?? 0;
|
|
288
301
|
try {
|
|
289
|
-
await
|
|
302
|
+
await awaitAll2(() => commandChain);
|
|
290
303
|
} catch (error) {
|
|
291
|
-
if (maxRetries > 0 && error instanceof
|
|
304
|
+
if (maxRetries > 0 && error instanceof ConcurrencyError2) {
|
|
292
305
|
await releaseLock?.();
|
|
293
|
-
const retryStream = dispatchContext(() => transaction.exit(() => downstreamRootsPromiseStore.exit(() => createStream(
|
|
306
|
+
const retryStream = dispatchContext(() => transaction.exit(() => downstreamRootsPromiseStore.exit(() => createStream(clone2(initialState), streamId, {
|
|
294
307
|
load: true,
|
|
295
308
|
filter: config.filter,
|
|
296
309
|
replayCommands: dispatchedCommands,
|
|
@@ -304,30 +317,30 @@ function createAggregateRoot(type) {
|
|
|
304
317
|
throw error;
|
|
305
318
|
}
|
|
306
319
|
await downstreamSettledPromise();
|
|
307
|
-
return
|
|
320
|
+
return api.commands;
|
|
308
321
|
},
|
|
309
322
|
transformError(error, handler) {
|
|
310
323
|
errorHandlers.set(error, handler);
|
|
311
|
-
return
|
|
324
|
+
return api.commands;
|
|
312
325
|
},
|
|
313
326
|
streamId
|
|
314
327
|
}
|
|
315
328
|
};
|
|
316
|
-
const autoSettleOptions =
|
|
317
|
-
registerRootPromiseToUpstream(() =>
|
|
318
|
-
transaction.after(() =>
|
|
329
|
+
const autoSettleOptions = getProcessManagerInfo2()?.settledDefaults ?? transaction.settledDefaults;
|
|
330
|
+
registerRootPromiseToUpstream(() => api.commands.settled(autoSettleOptions));
|
|
331
|
+
transaction.after(() => api.commands.settled(autoSettleOptions));
|
|
319
332
|
if (config.load) {
|
|
320
|
-
|
|
333
|
+
api.load(config.filter);
|
|
321
334
|
}
|
|
322
335
|
for (const { name, args } of config.replayCommands ?? []) {
|
|
323
|
-
|
|
336
|
+
api.commands[name](...args);
|
|
324
337
|
}
|
|
325
|
-
return
|
|
338
|
+
return api;
|
|
326
339
|
};
|
|
327
340
|
const api = {
|
|
328
341
|
type,
|
|
329
342
|
withInitialState: (state) => {
|
|
330
|
-
initialState =
|
|
343
|
+
initialState = clone2(state);
|
|
331
344
|
return api;
|
|
332
345
|
},
|
|
333
346
|
withCommands(handlers) {
|
|
@@ -341,7 +354,7 @@ function createAggregateRoot(type) {
|
|
|
341
354
|
},
|
|
342
355
|
withEvents(handler) {
|
|
343
356
|
const eventMap = typeof handler === "function" ? handler({ version: (version, schema) => ({ version, schema }), z }) : handler;
|
|
344
|
-
for (const [
|
|
357
|
+
for (const [type, config] of Object.entries(eventMap)) {
|
|
345
358
|
const versions = Array.isArray(config) ? config : [config];
|
|
346
359
|
for (const v of versions) {
|
|
347
360
|
let schema;
|
|
@@ -353,13 +366,13 @@ function createAggregateRoot(type) {
|
|
|
353
366
|
schema = v;
|
|
354
367
|
version = 0;
|
|
355
368
|
}
|
|
356
|
-
if (registeredEvents.get(
|
|
357
|
-
throw new Error(`event ${
|
|
369
|
+
if (registeredEvents.get(type)?.has(version)) {
|
|
370
|
+
throw new Error(`event ${type} is already registered in version ${version}`);
|
|
358
371
|
}
|
|
359
|
-
if (!registeredEvents.has(
|
|
360
|
-
registeredEvents.set(
|
|
372
|
+
if (!registeredEvents.has(type)) {
|
|
373
|
+
registeredEvents.set(type, new Map);
|
|
361
374
|
}
|
|
362
|
-
registeredEvents.get(
|
|
375
|
+
registeredEvents.get(type).set(version, schema);
|
|
363
376
|
}
|
|
364
377
|
}
|
|
365
378
|
return api;
|
|
@@ -376,11 +389,11 @@ function createAggregateRoot(type) {
|
|
|
376
389
|
if (!loadEvents) {
|
|
377
390
|
throw new Error(`aggregate root ${type} is not linked to an event store`);
|
|
378
391
|
}
|
|
379
|
-
const parentProjectionName =
|
|
392
|
+
const parentProjectionName = getProjectionInfo2()?.name;
|
|
380
393
|
if (parentProjectionName) {
|
|
381
394
|
throw new Error(`aggregateRoot.newStream() cannot be called inside a projection. Attempted on aggregate stream ${type} within Projection ${parentProjectionName}.`);
|
|
382
395
|
}
|
|
383
|
-
return createStream(
|
|
396
|
+
return createStream(clone2(initialState), id ?? crypto.randomUUID(), {
|
|
384
397
|
load: false
|
|
385
398
|
}).commands;
|
|
386
399
|
},
|
|
@@ -388,11 +401,11 @@ function createAggregateRoot(type) {
|
|
|
388
401
|
if (!loadEvents) {
|
|
389
402
|
throw new Error(`aggregate root ${type} is not linked to an event store`);
|
|
390
403
|
}
|
|
391
|
-
const parentProjectionName =
|
|
404
|
+
const parentProjectionName = getProjectionInfo2()?.name;
|
|
392
405
|
if (parentProjectionName) {
|
|
393
406
|
throw new Error(`aggregateRoot.loadStream("${id}") cannot be called inside a projection. Attempted on aggregate stream ${type} within Projection ${parentProjectionName}.`);
|
|
394
407
|
}
|
|
395
|
-
return createStream(
|
|
408
|
+
return createStream(clone2(initialState), id, {
|
|
396
409
|
load: true,
|
|
397
410
|
filter
|
|
398
411
|
}).commands;
|
|
@@ -416,6 +429,6 @@ function createAggregateRoot(type) {
|
|
|
416
429
|
return api;
|
|
417
430
|
}
|
|
418
431
|
|
|
419
|
-
export {
|
|
432
|
+
export { createAggregateRoot2 };
|
|
420
433
|
|
|
421
|
-
//# debugId=
|
|
434
|
+
//# debugId=3DB62A08BB7BF61F64756E2164756E21
|