@requence/event-sourcing 0.0.1 → 1.0.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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/chunk-24xjphpk.js +51 -0
- package/dist/chunk-24xjphpk.js.map +10 -0
- package/dist/chunk-3t1vzbab.js +88 -0
- package/dist/chunk-3t1vzbab.js.map +10 -0
- package/dist/chunk-6q8mr1sg.js +13 -0
- package/dist/chunk-6q8mr1sg.js.map +10 -0
- package/dist/chunk-9psg50cb.js +22 -0
- package/dist/chunk-9psg50cb.js.map +10 -0
- package/dist/chunk-dgzw1nj9.js +50 -0
- package/dist/chunk-dgzw1nj9.js.map +10 -0
- package/dist/chunk-gq80tgvf.js +70 -0
- package/dist/chunk-gq80tgvf.js.map +10 -0
- package/dist/chunk-jd4q8fcf.js +11 -0
- package/dist/chunk-jd4q8fcf.js.map +10 -0
- package/dist/chunk-m07vf8q9.js +210 -0
- package/dist/chunk-m07vf8q9.js.map +10 -0
- package/dist/chunk-ncpk70p4.js +313 -0
- package/dist/chunk-ncpk70p4.js.map +10 -0
- package/dist/chunk-pmfzjavb.js +49 -0
- package/dist/chunk-pmfzjavb.js.map +10 -0
- package/dist/chunk-qdczt68t.js +84 -0
- package/dist/chunk-qdczt68t.js.map +10 -0
- package/dist/chunk-tfy01fah.js +34 -0
- package/dist/chunk-tfy01fah.js.map +10 -0
- package/dist/chunk-tkvj6x8k.js +389 -0
- package/dist/chunk-tkvj6x8k.js.map +10 -0
- package/dist/chunk-ty9bhdvx.js +53 -0
- package/dist/chunk-ty9bhdvx.js.map +10 -0
- package/dist/chunk-wmz6a1jk.js +233 -0
- package/dist/chunk-wmz6a1jk.js.map +10 -0
- package/dist/chunk-x90g388y.js +22 -0
- package/dist/chunk-x90g388y.js.map +10 -0
- package/dist/chunk-z388cd25.js +91 -0
- package/dist/chunk-z388cd25.js.map +10 -0
- package/dist/chunk-zf3x573k.js +65 -0
- package/dist/chunk-zf3x573k.js.map +10 -0
- package/dist/createAggregateRoot.d.ts +191 -0
- package/dist/createAggregateRoot.d.ts.map +1 -0
- package/dist/createAggregateRoot.js +18 -0
- package/dist/createAggregateRoot.js.map +9 -0
- package/dist/createCheckpointApi.d.ts +38 -0
- package/dist/createCheckpointApi.d.ts.map +1 -0
- package/dist/createCheckpointApi.js +9 -0
- package/dist/createCheckpointApi.js.map +9 -0
- package/dist/createEventListener.d.ts +46 -0
- package/dist/createEventListener.d.ts.map +1 -0
- package/dist/createEventListener.js +12 -0
- package/dist/createEventListener.js.map +9 -0
- package/dist/createEventStore.d.ts +57 -0
- package/dist/createEventStore.d.ts.map +1 -0
- package/dist/createEventStore.js +20 -0
- package/dist/createEventStore.js.map +9 -0
- package/dist/createProcessManager.d.ts +70 -0
- package/dist/createProcessManager.d.ts.map +1 -0
- package/dist/createProcessManager.js +16 -0
- package/dist/createProcessManager.js.map +9 -0
- package/dist/createProjection.d.ts +120 -0
- package/dist/createProjection.d.ts.map +1 -0
- package/dist/createProjection.js +16 -0
- package/dist/createProjection.js.map +9 -0
- package/dist/drizzle/index.d.ts +449 -0
- package/dist/drizzle/index.d.ts.map +1 -0
- package/dist/drizzle/index.js +325 -0
- package/dist/drizzle/index.js.map +10 -0
- package/dist/drizzle/postgres.d.ts +686 -0
- package/dist/drizzle/postgres.d.ts.map +1 -0
- package/dist/drizzle/postgres.js +18 -0
- package/dist/drizzle/postgres.js.map +9 -0
- package/dist/errors.d.ts +53 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +9 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +9 -0
- package/dist/lock.d.ts +6 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +8 -0
- package/dist/lock.js.map +9 -0
- package/dist/memory/index.d.ts +15 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +178 -0
- package/dist/memory/index.js.map +10 -0
- package/dist/redis/index.d.ts +2 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/redis/index.js +8 -0
- package/dist/redis/index.js.map +9 -0
- package/dist/redis/redisLock.d.ts +4 -0
- package/dist/redis/redisLock.d.ts.map +1 -0
- package/dist/redis/redisLock.js +8 -0
- package/dist/redis/redisLock.js.map +9 -0
- package/dist/refresh.d.ts +4 -0
- package/dist/refresh.d.ts.map +1 -0
- package/dist/refresh.js +13 -0
- package/dist/refresh.js.map +9 -0
- package/dist/replay.d.ts +4 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +13 -0
- package/dist/replay.js.map +9 -0
- package/dist/snapshotGenerator.d.ts +79 -0
- package/dist/snapshotGenerator.d.ts.map +1 -0
- package/dist/snapshotGenerator.js +9 -0
- package/dist/snapshotGenerator.js.map +9 -0
- package/dist/superjson.d.ts +5 -0
- package/dist/superjson.d.ts.map +1 -0
- package/dist/superjson.js +12 -0
- package/dist/superjson.js.map +9 -0
- package/dist/transaction.d.ts +13 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +8 -0
- package/dist/transaction.js.map +9 -0
- package/dist/utilityTypes.d.js +2 -0
- package/dist/utilityTypes.d.js.map +9 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +10 -0
- package/dist/utils.js.map +9 -0
- package/dist/wrappedEventBuilder.d.ts +37 -0
- package/dist/wrappedEventBuilder.d.ts.map +1 -0
- package/dist/wrappedEventBuilder.js +12 -0
- package/dist/wrappedEventBuilder.js.map +9 -0
- package/package.json +87 -1
- package/src/createAggregateRoot.ts +902 -0
- package/src/createCheckpointApi.ts +106 -0
- package/src/createEventListener.ts +118 -0
- package/src/createEventStore.ts +446 -0
- package/src/createProcessManager.ts +363 -0
- package/src/createProjection.ts +577 -0
- package/src/drizzle/index.ts +449 -0
- package/src/drizzle/postgres.ts +120 -0
- package/src/errors.ts +85 -0
- package/src/index.ts +45 -0
- package/src/lock.ts +58 -0
- package/src/memory/index.ts +224 -0
- package/src/redis/index.ts +1 -0
- package/src/redis/redisLock.ts +61 -0
- package/src/refresh.ts +19 -0
- package/src/replay.ts +19 -0
- package/src/snapshotGenerator.ts +190 -0
- package/src/superjson.ts +11 -0
- package/src/tests/aggregateRoot.spec.ts +532 -0
- package/src/tests/eventListener.spec.ts +57 -0
- package/src/tests/eventStore.spec.ts +86 -0
- package/src/tests/processManager.spec.ts +185 -0
- package/src/tests/projection.spec.ts +511 -0
- package/src/tests/projectionProcessManager.spec.ts +203 -0
- package/src/tests/setup.ts +230 -0
- package/src/tests/transaction.spec.ts +185 -0
- package/src/transaction.ts +93 -0
- package/src/utilityTypes.d.ts +110 -0
- package/src/utils.ts +43 -0
- package/src/wrappedEventBuilder.ts +181 -0
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
|
|
3
|
+
import { deepEqual } from 'fast-equals'
|
|
4
|
+
import { z } from 'zod/v4'
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
EmitEvents,
|
|
8
|
+
InternalAppendEvents,
|
|
9
|
+
LoadEvents,
|
|
10
|
+
} from './createEventStore.ts'
|
|
11
|
+
import { getProcessManagerInfo } from './createProcessManager.ts'
|
|
12
|
+
import { getProjectionInfo } from './createProjection.ts'
|
|
13
|
+
import {
|
|
14
|
+
CommandError,
|
|
15
|
+
EventValidationError,
|
|
16
|
+
InfiniteLoopError,
|
|
17
|
+
ValidationError,
|
|
18
|
+
} from './errors.ts'
|
|
19
|
+
import type { LockCreator } from './lock.ts'
|
|
20
|
+
import { isRefreshing } from './refresh.ts'
|
|
21
|
+
import createSnapshotGenerator, {
|
|
22
|
+
type CompleteSnapshotGenerator,
|
|
23
|
+
type InternalSnapshotGenerator,
|
|
24
|
+
type SnapshotGenerator,
|
|
25
|
+
} from './snapshotGenerator.ts'
|
|
26
|
+
import { clone } from './superjson.ts'
|
|
27
|
+
import type { Transaction, TransactionDelay } from './transaction.ts'
|
|
28
|
+
import type {
|
|
29
|
+
BaseInputEvent,
|
|
30
|
+
BaseOutputEvent,
|
|
31
|
+
EventTemplate,
|
|
32
|
+
Keys,
|
|
33
|
+
MaybePromise,
|
|
34
|
+
Merge,
|
|
35
|
+
OutputEvents,
|
|
36
|
+
Prettify,
|
|
37
|
+
} from './utilityTypes.ts'
|
|
38
|
+
import { awaitAll, normalizeArray } from './utils.ts'
|
|
39
|
+
import {
|
|
40
|
+
type WrappedEvent,
|
|
41
|
+
type WrappedEventBuilder,
|
|
42
|
+
handleEventError,
|
|
43
|
+
unwrapEvent,
|
|
44
|
+
wrappedEventBuilder,
|
|
45
|
+
} from './wrappedEventBuilder.ts'
|
|
46
|
+
|
|
47
|
+
type UppercaseRecord<T extends Record<string, any>> = {
|
|
48
|
+
[K in keyof T]: K extends Capitalize<K & string>
|
|
49
|
+
? any
|
|
50
|
+
: `Error: Key '${K & string}' must be Capitalized`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type BaseEventMap = Record<
|
|
54
|
+
string,
|
|
55
|
+
z.ZodType | (z.ZodType | { schema: z.ZodType; version: number })[]
|
|
56
|
+
>
|
|
57
|
+
|
|
58
|
+
type DownstreamRootPromise = () => Promise<unknown>
|
|
59
|
+
const downstreamRootsPromiseStore = new AsyncLocalStorage<
|
|
60
|
+
DownstreamRootPromise[]
|
|
61
|
+
>()
|
|
62
|
+
|
|
63
|
+
function registerRootPromiseToUpstream(rootPromise: DownstreamRootPromise) {
|
|
64
|
+
downstreamRootsPromiseStore.getStore()?.push(rootPromise)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function collectDownstreamRootPromise(handler: () => Promise<void>) {
|
|
68
|
+
const rootPromises: DownstreamRootPromise[] = []
|
|
69
|
+
await downstreamRootsPromiseStore.run(rootPromises, handler)
|
|
70
|
+
return async () => {
|
|
71
|
+
await Promise.all(rootPromises.map((promise) => promise()))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type BaseInternalEvent<
|
|
76
|
+
Type extends string,
|
|
77
|
+
Version extends number,
|
|
78
|
+
Payload = unknown,
|
|
79
|
+
> = BaseOutputEvent<Type, Version, Payload>
|
|
80
|
+
|
|
81
|
+
type InternalEvent<Event extends EventTemplate> = Prettify<
|
|
82
|
+
{
|
|
83
|
+
[K in Event as K['version']]: Omit<
|
|
84
|
+
BaseInternalEvent<K['type'], K['version'], z.output<K['schema']>>,
|
|
85
|
+
'createdAt' | 'position' | 'streamVersion'
|
|
86
|
+
>
|
|
87
|
+
}[Event['version']]
|
|
88
|
+
>
|
|
89
|
+
|
|
90
|
+
type InternalEventHandlers<Events extends EventTemplate> = {
|
|
91
|
+
[K in Events as `on${K['type']}`]?: (
|
|
92
|
+
event: InternalEvent<K>,
|
|
93
|
+
) => MaybePromise<void>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
type EventsFromMap<EventMap extends BaseEventMap> = Prettify<
|
|
97
|
+
{
|
|
98
|
+
[K in keyof EventMap]: { type: K & string } & (EventMap[K] extends z.ZodType
|
|
99
|
+
? { schema: EventMap[K]; version: 0 }
|
|
100
|
+
: EventMap[K] extends { schema: infer S; version: infer V }
|
|
101
|
+
? S extends z.ZodType
|
|
102
|
+
? { schema: S; version: V }
|
|
103
|
+
: never
|
|
104
|
+
: EventMap[K] extends Array<infer I>
|
|
105
|
+
? I extends z.ZodType
|
|
106
|
+
? { schema: I; version: 0 }
|
|
107
|
+
: I extends { schema: infer S; version: infer V }
|
|
108
|
+
? { schema: S; version: V }
|
|
109
|
+
: never
|
|
110
|
+
: never)
|
|
111
|
+
}[keyof EventMap]
|
|
112
|
+
>
|
|
113
|
+
|
|
114
|
+
type Constructor<T> = new (...args: any[]) => T
|
|
115
|
+
|
|
116
|
+
type Command = (
|
|
117
|
+
...args: any[]
|
|
118
|
+
) =>
|
|
119
|
+
| MaybePromise<void | WrappedEvent | WrappedEvent[]>
|
|
120
|
+
| Iterable<WrappedEvent>
|
|
121
|
+
| AsyncIterable<WrappedEvent>
|
|
122
|
+
|
|
123
|
+
type Utils = {
|
|
124
|
+
reset: () => void
|
|
125
|
+
replayUntil: (streamVersion: number) => Promise<void>
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type VersionBuilder = <V extends number, S extends z.ZodType>(
|
|
129
|
+
version: V,
|
|
130
|
+
schema: S,
|
|
131
|
+
) => { schema: S; version: V }
|
|
132
|
+
|
|
133
|
+
type Stream<Commands extends Record<string, Command>, State> = {
|
|
134
|
+
[A in keyof Commands]: (
|
|
135
|
+
...args: Parameters<Commands[A]>
|
|
136
|
+
) => Stream<Commands, State>
|
|
137
|
+
} & {
|
|
138
|
+
settled: () => Promise<Stream<Commands, State>>
|
|
139
|
+
state: () => Promise<State>
|
|
140
|
+
transformError: <T extends Error>(
|
|
141
|
+
error: Constructor<T>,
|
|
142
|
+
errorHandler: (commandError: CommandError<T>) => Error,
|
|
143
|
+
) => Stream<Commands, State>
|
|
144
|
+
readonly streamId: string
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type AnyStream = {
|
|
148
|
+
settled: () => Promise<AnyStream>
|
|
149
|
+
state: () => Promise<unknown>
|
|
150
|
+
transformError: <T extends Error>(
|
|
151
|
+
error: Constructor<T>,
|
|
152
|
+
errorHandler: (error: CommandError<T>) => Error,
|
|
153
|
+
) => AnyStream
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type AnyAggregateRoot = {
|
|
157
|
+
type: string
|
|
158
|
+
loadStream: (
|
|
159
|
+
id: string,
|
|
160
|
+
filter?: (event: BaseOutputEvent) => MaybePromise<boolean>,
|
|
161
|
+
) => AnyStream
|
|
162
|
+
newStream: () => AnyStream
|
|
163
|
+
$inferState: any
|
|
164
|
+
$inferEvents: any
|
|
165
|
+
$inferEvent: any
|
|
166
|
+
$inferCommandParameters: any
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type EventHandlerOptions = {
|
|
170
|
+
streamId: string
|
|
171
|
+
extendLock: (ms?: number) => Promise<boolean>
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type AggregateRootApi<
|
|
175
|
+
Name,
|
|
176
|
+
State = never,
|
|
177
|
+
Events extends EventTemplate = never,
|
|
178
|
+
Commands extends Record<string, Command> = Record<string, Command>,
|
|
179
|
+
Flags extends {
|
|
180
|
+
withInitialState: boolean
|
|
181
|
+
withEvents: boolean
|
|
182
|
+
withCommands: boolean
|
|
183
|
+
withEventHandlers: boolean
|
|
184
|
+
withSnapshots: boolean
|
|
185
|
+
} = {
|
|
186
|
+
withInitialState: false
|
|
187
|
+
withEvents: false
|
|
188
|
+
withCommands: true
|
|
189
|
+
withEventHandlers: true
|
|
190
|
+
withSnapshots: false
|
|
191
|
+
},
|
|
192
|
+
> = {
|
|
193
|
+
withInitialState: <NextState extends Record<string, unknown>>(
|
|
194
|
+
state: NextState,
|
|
195
|
+
) => Omit<
|
|
196
|
+
AggregateRootApi<
|
|
197
|
+
Name,
|
|
198
|
+
NextState,
|
|
199
|
+
Events,
|
|
200
|
+
Commands,
|
|
201
|
+
Merge<Flags, { withInitialState: true; withEventHandlers: false }>
|
|
202
|
+
>,
|
|
203
|
+
Keys<Merge<Flags, { withInitialState: true; withEventHandlers: false }>>
|
|
204
|
+
>
|
|
205
|
+
withCommands: <
|
|
206
|
+
NextCommands extends Record<
|
|
207
|
+
Exclude<string, 'state' | 'settled' | 'transformError' | 'streamId'>,
|
|
208
|
+
Command
|
|
209
|
+
>,
|
|
210
|
+
>(
|
|
211
|
+
handlers: [State] extends [never]
|
|
212
|
+
? (
|
|
213
|
+
event: WrappedEventBuilder<Events>,
|
|
214
|
+
options: EventHandlerOptions,
|
|
215
|
+
) => NextCommands
|
|
216
|
+
: (
|
|
217
|
+
state: Readonly<State>,
|
|
218
|
+
event: WrappedEventBuilder<Events>,
|
|
219
|
+
options: EventHandlerOptions,
|
|
220
|
+
) => NextCommands,
|
|
221
|
+
) => Omit<
|
|
222
|
+
AggregateRootApi<
|
|
223
|
+
Name,
|
|
224
|
+
State,
|
|
225
|
+
Events,
|
|
226
|
+
NextCommands,
|
|
227
|
+
Merge<
|
|
228
|
+
Flags,
|
|
229
|
+
{ withInitialState: true; withCommands: true; withEvents: true }
|
|
230
|
+
>
|
|
231
|
+
>,
|
|
232
|
+
Keys<
|
|
233
|
+
Merge<
|
|
234
|
+
Flags,
|
|
235
|
+
{ withInitialState: true; withCommands: true; withEvents: true }
|
|
236
|
+
>
|
|
237
|
+
>
|
|
238
|
+
>
|
|
239
|
+
withEvents: {
|
|
240
|
+
<EventMap extends BaseEventMap>(
|
|
241
|
+
handler: EventMap & UppercaseRecord<EventMap>,
|
|
242
|
+
): Omit<
|
|
243
|
+
AggregateRootApi<
|
|
244
|
+
Name,
|
|
245
|
+
State,
|
|
246
|
+
EventsFromMap<EventMap>,
|
|
247
|
+
Commands,
|
|
248
|
+
Merge<
|
|
249
|
+
Flags,
|
|
250
|
+
{
|
|
251
|
+
withInitialState: true
|
|
252
|
+
withEvents: true
|
|
253
|
+
withCommands: false
|
|
254
|
+
}
|
|
255
|
+
>
|
|
256
|
+
>,
|
|
257
|
+
Keys<
|
|
258
|
+
Merge<
|
|
259
|
+
Flags,
|
|
260
|
+
{
|
|
261
|
+
withInitialState: true
|
|
262
|
+
withEvents: true
|
|
263
|
+
withCommands: false
|
|
264
|
+
}
|
|
265
|
+
>
|
|
266
|
+
>
|
|
267
|
+
>
|
|
268
|
+
<EventMap extends BaseEventMap>(
|
|
269
|
+
handler: (utils: {
|
|
270
|
+
version: VersionBuilder
|
|
271
|
+
z: typeof z
|
|
272
|
+
}) => EventMap & UppercaseRecord<EventMap>,
|
|
273
|
+
): Omit<
|
|
274
|
+
AggregateRootApi<
|
|
275
|
+
Name,
|
|
276
|
+
State,
|
|
277
|
+
EventsFromMap<EventMap>,
|
|
278
|
+
Commands,
|
|
279
|
+
Merge<
|
|
280
|
+
Flags,
|
|
281
|
+
{
|
|
282
|
+
withInitialState: true
|
|
283
|
+
withEvents: true
|
|
284
|
+
withCommands: false
|
|
285
|
+
}
|
|
286
|
+
>
|
|
287
|
+
>,
|
|
288
|
+
Keys<
|
|
289
|
+
Merge<
|
|
290
|
+
Flags,
|
|
291
|
+
{
|
|
292
|
+
withInitialState: true
|
|
293
|
+
withEvents: true
|
|
294
|
+
withCommands: false
|
|
295
|
+
}
|
|
296
|
+
>
|
|
297
|
+
>
|
|
298
|
+
>
|
|
299
|
+
}
|
|
300
|
+
withEventHandlers: (
|
|
301
|
+
handler: (state: State, utils: Utils) => InternalEventHandlers<Events>,
|
|
302
|
+
) => Omit<
|
|
303
|
+
AggregateRootApi<
|
|
304
|
+
Name,
|
|
305
|
+
State,
|
|
306
|
+
Events,
|
|
307
|
+
Commands,
|
|
308
|
+
Merge<
|
|
309
|
+
Flags,
|
|
310
|
+
{ withInitialState: true; withEventHandlers: true; withEvents: true }
|
|
311
|
+
>
|
|
312
|
+
>,
|
|
313
|
+
Keys<
|
|
314
|
+
Merge<
|
|
315
|
+
Flags,
|
|
316
|
+
{ withInitialState: true; withEventHandlers: true; withEvents: true }
|
|
317
|
+
>
|
|
318
|
+
>
|
|
319
|
+
>
|
|
320
|
+
withSnapshots: (
|
|
321
|
+
handler: (
|
|
322
|
+
state: State,
|
|
323
|
+
generate: SnapshotGenerator<Omit<Snapshot, 'schemaVersion' | 'state'>>,
|
|
324
|
+
utils: { z: typeof z },
|
|
325
|
+
) => CompleteSnapshotGenerator,
|
|
326
|
+
) => Omit<
|
|
327
|
+
AggregateRootApi<
|
|
328
|
+
Name,
|
|
329
|
+
State,
|
|
330
|
+
Events,
|
|
331
|
+
Commands,
|
|
332
|
+
Merge<Flags, { withSnapshots: true }>
|
|
333
|
+
>,
|
|
334
|
+
Keys<Merge<Flags, { withSnapshots: true }>>
|
|
335
|
+
>
|
|
336
|
+
loadStream: (
|
|
337
|
+
id: string,
|
|
338
|
+
filter?: (event: OutputEvents<Events>) => MaybePromise<boolean>,
|
|
339
|
+
) => Stream<Commands, State>
|
|
340
|
+
newStream: (id?: string) => Stream<Commands, State>
|
|
341
|
+
|
|
342
|
+
$inferEvents: OutputEvents<Events>
|
|
343
|
+
$inferEvent: OutputEvents<Events> extends BaseOutputEvent
|
|
344
|
+
? { [E in OutputEvents<Events> as E['type']]: E }
|
|
345
|
+
: never
|
|
346
|
+
$inferState: State
|
|
347
|
+
$inferCommandParameters: {
|
|
348
|
+
[CommandName in keyof Commands]: Parameters<Commands[CommandName]>
|
|
349
|
+
}
|
|
350
|
+
readonly type: Name
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export type InternalAggregateRootApi = {
|
|
354
|
+
linkEventStore: (
|
|
355
|
+
loadEvents: LoadEvents,
|
|
356
|
+
appendEvents: InternalAppendEvents,
|
|
357
|
+
emitEvents: EmitEvents,
|
|
358
|
+
transaction: Transaction,
|
|
359
|
+
lock: LockCreator,
|
|
360
|
+
snapshots?: AggregateRootSnapshotMethods,
|
|
361
|
+
) => void
|
|
362
|
+
eventTypes: string[]
|
|
363
|
+
readonly type: string
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const snapshotSchema = z.object({
|
|
367
|
+
streamId: z.string(),
|
|
368
|
+
schemaVersion: z.int(),
|
|
369
|
+
state: z.unknown(),
|
|
370
|
+
createdAt: z.date(),
|
|
371
|
+
streamVersion: z.int(),
|
|
372
|
+
streamType: z.string(),
|
|
373
|
+
lastEventPosition: z.int(),
|
|
374
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
export type Snapshot = z.output<typeof snapshotSchema>
|
|
378
|
+
|
|
379
|
+
export type AggregateRootSnapshotMethods = {
|
|
380
|
+
get: (select: {
|
|
381
|
+
stream: string
|
|
382
|
+
id: string
|
|
383
|
+
}) => MaybePromise<Snapshot | null | undefined>
|
|
384
|
+
put: (snapshot: Snapshot) => MaybePromise<void>
|
|
385
|
+
delete: (
|
|
386
|
+
target: {
|
|
387
|
+
stream: string
|
|
388
|
+
id: string
|
|
389
|
+
},
|
|
390
|
+
fromPosition: number,
|
|
391
|
+
) => MaybePromise<void>
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function createAggregateRoot<Name extends string>(type: Name) {
|
|
395
|
+
let initialState: any = null
|
|
396
|
+
let createEventHandlers: (
|
|
397
|
+
state: any,
|
|
398
|
+
) => InternalEventHandlers<any> = () => ({})
|
|
399
|
+
|
|
400
|
+
let loadEvents!: LoadEvents
|
|
401
|
+
let appendEvents!: InternalAppendEvents
|
|
402
|
+
let emitEvents!: EmitEvents
|
|
403
|
+
let snapshots: AggregateRootSnapshotMethods | undefined
|
|
404
|
+
let createLock!: LockCreator
|
|
405
|
+
let transaction!: Transaction
|
|
406
|
+
|
|
407
|
+
let createCommands: (
|
|
408
|
+
state: any,
|
|
409
|
+
eventBuilder: (name: string, payload: unknown) => WrappedEvent,
|
|
410
|
+
options: EventHandlerOptions,
|
|
411
|
+
) => Record<string, Command> = () => ({})
|
|
412
|
+
|
|
413
|
+
const registeredEvents = new Map<string, Map<number, z.ZodType>>()
|
|
414
|
+
let createSnapshotApi:
|
|
415
|
+
| ((
|
|
416
|
+
state: any,
|
|
417
|
+
generate: SnapshotGenerator<any>,
|
|
418
|
+
utils: { z: typeof z },
|
|
419
|
+
) => CompleteSnapshotGenerator & InternalSnapshotGenerator)
|
|
420
|
+
| null = null
|
|
421
|
+
|
|
422
|
+
const loadUntil = async (
|
|
423
|
+
state: any,
|
|
424
|
+
snapshotApi:
|
|
425
|
+
| (CompleteSnapshotGenerator & InternalSnapshotGenerator)
|
|
426
|
+
| undefined,
|
|
427
|
+
id: string,
|
|
428
|
+
untilPosition?: number,
|
|
429
|
+
filter?: (event: BaseOutputEvent) => MaybePromise<boolean>,
|
|
430
|
+
) => {
|
|
431
|
+
let position = 0
|
|
432
|
+
let version = 0
|
|
433
|
+
if (snapshotApi) {
|
|
434
|
+
const snapshot = await snapshots?.get({
|
|
435
|
+
stream: type,
|
|
436
|
+
id,
|
|
437
|
+
})
|
|
438
|
+
if (snapshot) {
|
|
439
|
+
const result = snapshotSchema.safeParse(snapshot)
|
|
440
|
+
|
|
441
|
+
if (!result.success) {
|
|
442
|
+
throw new ValidationError(
|
|
443
|
+
'loaded aggregate root snapshot does not comply with schema',
|
|
444
|
+
result.error,
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
snapshotApi.internalApply(result.data)
|
|
449
|
+
position = snapshot.lastEventPosition
|
|
450
|
+
version = snapshot.streamVersion
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const events = loadEvents(
|
|
455
|
+
{ stream: type, id },
|
|
456
|
+
{
|
|
457
|
+
from: position,
|
|
458
|
+
to: untilPosition,
|
|
459
|
+
},
|
|
460
|
+
)
|
|
461
|
+
const eventHandlers = createEventHandlers(state)
|
|
462
|
+
for await (const event of events) {
|
|
463
|
+
version += 1
|
|
464
|
+
if ((await filter?.(event)) === false) {
|
|
465
|
+
continue
|
|
466
|
+
}
|
|
467
|
+
await (eventHandlers as any)[`on${event.type}`]?.(event)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return version
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const createStream = (state: any, streamId: string) => {
|
|
474
|
+
let version = 0
|
|
475
|
+
const snapshotApi = createSnapshotApi?.(
|
|
476
|
+
state,
|
|
477
|
+
createSnapshotGenerator() as any,
|
|
478
|
+
{ z },
|
|
479
|
+
)
|
|
480
|
+
const errorHandlers = new Map<
|
|
481
|
+
Constructor<Error>,
|
|
482
|
+
(error: CommandError<Error>) => Error
|
|
483
|
+
>()
|
|
484
|
+
let downstreamSettledPromise = () => Promise.resolve()
|
|
485
|
+
let releaseLock!: () => Promise<void>
|
|
486
|
+
let extendLock!: EventHandlerOptions['extendLock']
|
|
487
|
+
|
|
488
|
+
const warnReleaseLock = () => {
|
|
489
|
+
if (releasedAfterChain) {
|
|
490
|
+
console.warn(`aggregate root for ${type}:${streamId} was open too long`)
|
|
491
|
+
}
|
|
492
|
+
releaseLock()
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const processEvents = async (events: BaseInputEvent[]) => {
|
|
496
|
+
if (!appendEvents) {
|
|
497
|
+
throw new Error(
|
|
498
|
+
`aggregate root ${type} is not linked to an event store`,
|
|
499
|
+
)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const persistedEvents = await appendEvents(
|
|
503
|
+
{ stream: type, id: streamId },
|
|
504
|
+
events,
|
|
505
|
+
version,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
warnReleaseLock()
|
|
509
|
+
|
|
510
|
+
for (const persistedEvent of persistedEvents) {
|
|
511
|
+
const snapshot = await snapshotApi?.internalCapture(
|
|
512
|
+
persistedEvent,
|
|
513
|
+
persistedEvent.streamVersion,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
if (snapshot) {
|
|
517
|
+
await snapshots?.put({
|
|
518
|
+
state: snapshot.state,
|
|
519
|
+
lastEventPosition: persistedEvent.position,
|
|
520
|
+
streamId,
|
|
521
|
+
streamVersion: version,
|
|
522
|
+
streamType: type,
|
|
523
|
+
schemaVersion: snapshot.version,
|
|
524
|
+
createdAt: new Date(),
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
version += events.length
|
|
530
|
+
|
|
531
|
+
downstreamSettledPromise = await collectDownstreamRootPromise(() =>
|
|
532
|
+
emitEvents(persistedEvents),
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let commandChain = createLock([type, streamId]).then((lock) => {
|
|
537
|
+
releaseLock = lock.release
|
|
538
|
+
extendLock = lock.extend
|
|
539
|
+
return [] as Error | BaseInputEvent[]
|
|
540
|
+
})
|
|
541
|
+
let commandChainTouched = false
|
|
542
|
+
let releasedAfterChain = false
|
|
543
|
+
let transactionDelay: TransactionDelay | undefined
|
|
544
|
+
|
|
545
|
+
const stateReady = Promise.withResolvers<Error | undefined>()
|
|
546
|
+
|
|
547
|
+
const rawCommands = createCommands(state, wrappedEventBuilder, {
|
|
548
|
+
streamId,
|
|
549
|
+
extendLock,
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
const commands = Object.fromEntries(
|
|
553
|
+
Object.entries(rawCommands).map(([commandName, commandFn]) => [
|
|
554
|
+
commandName,
|
|
555
|
+
(...args: any[]) => {
|
|
556
|
+
// don't create new events when refreshing the process manager
|
|
557
|
+
if (isRefreshing()) {
|
|
558
|
+
return api.commands
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const commandId = crypto.randomUUID()
|
|
562
|
+
transactionDelay ??= transaction.delay()
|
|
563
|
+
commandChainTouched = true
|
|
564
|
+
commandChain = commandChain
|
|
565
|
+
.then(async (previousEvents) => {
|
|
566
|
+
if (previousEvents instanceof Error) {
|
|
567
|
+
return previousEvents
|
|
568
|
+
}
|
|
569
|
+
const events = (
|
|
570
|
+
await normalizeArray(
|
|
571
|
+
commandFn.apply(rawCommands, args),
|
|
572
|
+
() => extendLock(),
|
|
573
|
+
)
|
|
574
|
+
).filter((event) => Boolean(event))
|
|
575
|
+
|
|
576
|
+
const baseInputEvents = await Promise.all(
|
|
577
|
+
events.map(async (event) => {
|
|
578
|
+
event.withAdditionalMetadata({ commandName })
|
|
579
|
+
event.withDefaultCausationId(commandId)
|
|
580
|
+
|
|
581
|
+
const unwrappedEvent = unwrapEvent(event)
|
|
582
|
+
|
|
583
|
+
const eventVersions = registeredEvents.get(
|
|
584
|
+
unwrappedEvent.type,
|
|
585
|
+
)
|
|
586
|
+
if (!eventVersions) {
|
|
587
|
+
throw new Error(
|
|
588
|
+
`cannot append unknown event ${unwrappedEvent.type}`,
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
const latestVersion = Math.max(
|
|
592
|
+
...Array.from(eventVersions.keys()),
|
|
593
|
+
)
|
|
594
|
+
const schema = eventVersions.get(latestVersion)!
|
|
595
|
+
|
|
596
|
+
const result = schema.safeParse(unwrappedEvent.payload)
|
|
597
|
+
if (!result.success) {
|
|
598
|
+
const error = new EventValidationError(
|
|
599
|
+
`payload does not comply with schema for event ${unwrappedEvent.type}@${latestVersion} at stream ${streamId}`,
|
|
600
|
+
result.error,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
await handleEventError(event, error)
|
|
604
|
+
throw error
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
unwrappedEvent.payload = result.data
|
|
608
|
+
|
|
609
|
+
return {
|
|
610
|
+
...unwrappedEvent,
|
|
611
|
+
schemaVersion: latestVersion,
|
|
612
|
+
}
|
|
613
|
+
}),
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
const eventHandlers = createEventHandlers(state)
|
|
617
|
+
for (const event of baseInputEvents) {
|
|
618
|
+
const processManagerInfo = getProcessManagerInfo()
|
|
619
|
+
if (processManagerInfo) {
|
|
620
|
+
if (
|
|
621
|
+
processManagerInfo.event.streamId === streamId &&
|
|
622
|
+
processManagerInfo.event.streamType === type &&
|
|
623
|
+
processManagerInfo.event.type === event.type &&
|
|
624
|
+
deepEqual(processManagerInfo.event.payload, event.payload)
|
|
625
|
+
) {
|
|
626
|
+
return new InfiniteLoopError(
|
|
627
|
+
`Infinite loop detected. Process Manager "${processManagerInfo.name}" listens and emits same event "${event.type}" for stream ${type}:${streamId}, causing a recurring event cycle`,
|
|
628
|
+
)
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
await eventHandlers[`on${event.type}`]?.({
|
|
633
|
+
...event,
|
|
634
|
+
streamId,
|
|
635
|
+
streamType: type,
|
|
636
|
+
})
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return [...previousEvents, ...baseInputEvents]
|
|
640
|
+
})
|
|
641
|
+
.catch((error) => {
|
|
642
|
+
if (!(error instanceof Error)) {
|
|
643
|
+
throw error
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const commandError = new CommandError(
|
|
647
|
+
type,
|
|
648
|
+
commandName,
|
|
649
|
+
streamId,
|
|
650
|
+
error,
|
|
651
|
+
)
|
|
652
|
+
for (const [ErrorType, handler] of errorHandlers) {
|
|
653
|
+
if (error instanceof ErrorType) {
|
|
654
|
+
return new CommandError(
|
|
655
|
+
type,
|
|
656
|
+
commandName,
|
|
657
|
+
streamId,
|
|
658
|
+
handler(commandError),
|
|
659
|
+
)
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return commandError
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
const currentCommandChain = commandChain
|
|
667
|
+
currentCommandChain.then(async (allEvents) => {
|
|
668
|
+
if (
|
|
669
|
+
// we are the last command
|
|
670
|
+
currentCommandChain === commandChain
|
|
671
|
+
) {
|
|
672
|
+
stateReady.resolve(
|
|
673
|
+
allEvents instanceof Error ? allEvents : undefined,
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
commandChain = commandChain.then(async () => {
|
|
677
|
+
// no Error in chain
|
|
678
|
+
if (Array.isArray(allEvents)) {
|
|
679
|
+
transactionDelay?.resolve()
|
|
680
|
+
try {
|
|
681
|
+
await transactionDelay?.commit()
|
|
682
|
+
} catch (error) {
|
|
683
|
+
if (error instanceof Error) {
|
|
684
|
+
warnReleaseLock()
|
|
685
|
+
return error
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
await processEvents(allEvents)
|
|
690
|
+
return []
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
warnReleaseLock()
|
|
694
|
+
transactionDelay?.reject(allEvents)
|
|
695
|
+
if (transaction.active) {
|
|
696
|
+
return []
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
throw allEvents
|
|
700
|
+
})
|
|
701
|
+
}
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
return api.commands
|
|
705
|
+
},
|
|
706
|
+
]),
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
setImmediate(() => {
|
|
710
|
+
if (!commandChainTouched) {
|
|
711
|
+
releasedAfterChain = true
|
|
712
|
+
commandChain = commandChain.then((v) => {
|
|
713
|
+
releaseLock()
|
|
714
|
+
return v
|
|
715
|
+
})
|
|
716
|
+
}
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
const api = {
|
|
720
|
+
load(filter?: (event: BaseOutputEvent) => MaybePromise<boolean>) {
|
|
721
|
+
// ensure commands are executed after load is finished
|
|
722
|
+
commandChain = commandChain
|
|
723
|
+
.then(() =>
|
|
724
|
+
loadUntil(state, snapshotApi, streamId, undefined, filter),
|
|
725
|
+
)
|
|
726
|
+
.then((loadedVersion) => {
|
|
727
|
+
version = loadedVersion
|
|
728
|
+
return []
|
|
729
|
+
})
|
|
730
|
+
},
|
|
731
|
+
commands: {
|
|
732
|
+
...commands,
|
|
733
|
+
async state() {
|
|
734
|
+
const maybeError = await Promise.race([
|
|
735
|
+
stateReady.promise,
|
|
736
|
+
awaitAll(() => commandChain),
|
|
737
|
+
])
|
|
738
|
+
|
|
739
|
+
if (maybeError) {
|
|
740
|
+
throw maybeError
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return state
|
|
744
|
+
},
|
|
745
|
+
async settled() {
|
|
746
|
+
const parentProcessManager = getProcessManagerInfo()
|
|
747
|
+
if (parentProcessManager) {
|
|
748
|
+
const error = new Error(
|
|
749
|
+
`aggregateRoot.settled() cannot be called inside a process manager. Attempted on aggregate stream "${type}" within Process Manager ${parentProcessManager.name}.`,
|
|
750
|
+
)
|
|
751
|
+
commandChain = commandChain.then(() => error)
|
|
752
|
+
throw error
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (transaction.active) {
|
|
756
|
+
const error = new Error(
|
|
757
|
+
`aggregateRoot.settled() cannot be called inside a transaction. Attempted on aggregate stream "${type}".`,
|
|
758
|
+
)
|
|
759
|
+
commandChain = commandChain.then(() => error)
|
|
760
|
+
throw error
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
await awaitAll(() => commandChain)
|
|
764
|
+
await downstreamSettledPromise()
|
|
765
|
+
return api.commands
|
|
766
|
+
},
|
|
767
|
+
transformError(
|
|
768
|
+
error: Constructor<Error>,
|
|
769
|
+
handler: (error: Error) => never,
|
|
770
|
+
) {
|
|
771
|
+
errorHandlers.set(error, handler)
|
|
772
|
+
return api.commands
|
|
773
|
+
},
|
|
774
|
+
streamId,
|
|
775
|
+
},
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
registerRootPromiseToUpstream(() => api.commands.settled())
|
|
779
|
+
transaction.after(() => api.commands.settled())
|
|
780
|
+
return api
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const api: AggregateRootApi<Name> & InternalAggregateRootApi = {
|
|
784
|
+
type,
|
|
785
|
+
withInitialState: (state) => {
|
|
786
|
+
initialState = clone(state)
|
|
787
|
+
return api as any
|
|
788
|
+
},
|
|
789
|
+
|
|
790
|
+
withCommands(handlers) {
|
|
791
|
+
createCommands = (state, eventBuilder, options) => {
|
|
792
|
+
if (state === null) {
|
|
793
|
+
return (handlers as any)(eventBuilder, options)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return (handlers as any)(state, eventBuilder, options)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return api as any
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
withEvents(handler) {
|
|
803
|
+
const eventMap: BaseEventMap =
|
|
804
|
+
typeof handler === 'function'
|
|
805
|
+
? handler({ version: (version, schema) => ({ version, schema }), z })
|
|
806
|
+
: handler
|
|
807
|
+
|
|
808
|
+
for (const [type, config] of Object.entries(eventMap)) {
|
|
809
|
+
const versions = Array.isArray(config) ? config : [config]
|
|
810
|
+
for (const v of versions) {
|
|
811
|
+
let schema: z.ZodType
|
|
812
|
+
let version: number
|
|
813
|
+
if (typeof v === 'object' && 'version' in v) {
|
|
814
|
+
schema = v.schema
|
|
815
|
+
version = v.version
|
|
816
|
+
} else {
|
|
817
|
+
schema = v
|
|
818
|
+
version = 0
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (registeredEvents.get(type)?.has(version)) {
|
|
822
|
+
throw new Error(
|
|
823
|
+
`event ${type} is already registered in version ${version}`,
|
|
824
|
+
)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (!registeredEvents.has(type)) {
|
|
828
|
+
registeredEvents.set(type, new Map())
|
|
829
|
+
}
|
|
830
|
+
registeredEvents.get(type)!.set(version, schema)
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return api as any
|
|
834
|
+
},
|
|
835
|
+
|
|
836
|
+
withEventHandlers(handler) {
|
|
837
|
+
createEventHandlers = handler as any
|
|
838
|
+
return api as any
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
withSnapshots: (handler) => {
|
|
842
|
+
createSnapshotApi = handler as any
|
|
843
|
+
return api as any
|
|
844
|
+
},
|
|
845
|
+
|
|
846
|
+
newStream(id) {
|
|
847
|
+
if (!loadEvents) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
`aggregate root ${type} is not linked to an event store`,
|
|
850
|
+
)
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const parentProjectionName = getProjectionInfo()?.name
|
|
854
|
+
if (parentProjectionName) {
|
|
855
|
+
throw new Error(
|
|
856
|
+
`aggregateRoot.newStream() cannot be called inside a projection. Attempted on aggregate stream ${type} within Projection ${parentProjectionName}.`,
|
|
857
|
+
)
|
|
858
|
+
}
|
|
859
|
+
return createStream(clone(initialState), id ?? crypto.randomUUID())
|
|
860
|
+
.commands as any
|
|
861
|
+
},
|
|
862
|
+
|
|
863
|
+
loadStream(id, filter) {
|
|
864
|
+
if (!loadEvents) {
|
|
865
|
+
throw new Error(
|
|
866
|
+
`aggregate root ${type} is not linked to an event store`,
|
|
867
|
+
)
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const parentProjectionName = getProjectionInfo()?.name
|
|
871
|
+
if (parentProjectionName) {
|
|
872
|
+
throw new Error(
|
|
873
|
+
`aggregateRoot.loadStream("${id}") cannot be called inside a projection. Attempted on aggregate stream ${type} within Projection ${parentProjectionName}.`,
|
|
874
|
+
)
|
|
875
|
+
}
|
|
876
|
+
const root = createStream(clone(initialState), id)
|
|
877
|
+
root.load(filter as any)
|
|
878
|
+
return root.commands as any
|
|
879
|
+
},
|
|
880
|
+
|
|
881
|
+
linkEventStore(load, append, emit, trans, lock, snap) {
|
|
882
|
+
loadEvents = load
|
|
883
|
+
appendEvents = append
|
|
884
|
+
emitEvents = emit
|
|
885
|
+
transaction = trans
|
|
886
|
+
createLock = lock
|
|
887
|
+
snapshots = snap
|
|
888
|
+
},
|
|
889
|
+
get eventTypes() {
|
|
890
|
+
return Array.from(registeredEvents.keys())
|
|
891
|
+
},
|
|
892
|
+
$inferEvents: null as never,
|
|
893
|
+
$inferEvent: null as never,
|
|
894
|
+
$inferState: null as never,
|
|
895
|
+
$inferCommandParameters: null as never,
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return api as Omit<
|
|
899
|
+
AggregateRootApi<Name>,
|
|
900
|
+
'withCommands' | 'withEventHandlers'
|
|
901
|
+
>
|
|
902
|
+
}
|