@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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/chunk-24xjphpk.js +51 -0
  4. package/dist/chunk-24xjphpk.js.map +10 -0
  5. package/dist/chunk-3t1vzbab.js +88 -0
  6. package/dist/chunk-3t1vzbab.js.map +10 -0
  7. package/dist/chunk-6q8mr1sg.js +13 -0
  8. package/dist/chunk-6q8mr1sg.js.map +10 -0
  9. package/dist/chunk-9psg50cb.js +22 -0
  10. package/dist/chunk-9psg50cb.js.map +10 -0
  11. package/dist/chunk-dgzw1nj9.js +50 -0
  12. package/dist/chunk-dgzw1nj9.js.map +10 -0
  13. package/dist/chunk-gq80tgvf.js +70 -0
  14. package/dist/chunk-gq80tgvf.js.map +10 -0
  15. package/dist/chunk-jd4q8fcf.js +11 -0
  16. package/dist/chunk-jd4q8fcf.js.map +10 -0
  17. package/dist/chunk-m07vf8q9.js +210 -0
  18. package/dist/chunk-m07vf8q9.js.map +10 -0
  19. package/dist/chunk-ncpk70p4.js +313 -0
  20. package/dist/chunk-ncpk70p4.js.map +10 -0
  21. package/dist/chunk-pmfzjavb.js +49 -0
  22. package/dist/chunk-pmfzjavb.js.map +10 -0
  23. package/dist/chunk-qdczt68t.js +84 -0
  24. package/dist/chunk-qdczt68t.js.map +10 -0
  25. package/dist/chunk-tfy01fah.js +34 -0
  26. package/dist/chunk-tfy01fah.js.map +10 -0
  27. package/dist/chunk-tkvj6x8k.js +389 -0
  28. package/dist/chunk-tkvj6x8k.js.map +10 -0
  29. package/dist/chunk-ty9bhdvx.js +53 -0
  30. package/dist/chunk-ty9bhdvx.js.map +10 -0
  31. package/dist/chunk-wmz6a1jk.js +233 -0
  32. package/dist/chunk-wmz6a1jk.js.map +10 -0
  33. package/dist/chunk-x90g388y.js +22 -0
  34. package/dist/chunk-x90g388y.js.map +10 -0
  35. package/dist/chunk-z388cd25.js +91 -0
  36. package/dist/chunk-z388cd25.js.map +10 -0
  37. package/dist/chunk-zf3x573k.js +65 -0
  38. package/dist/chunk-zf3x573k.js.map +10 -0
  39. package/dist/createAggregateRoot.d.ts +191 -0
  40. package/dist/createAggregateRoot.d.ts.map +1 -0
  41. package/dist/createAggregateRoot.js +18 -0
  42. package/dist/createAggregateRoot.js.map +9 -0
  43. package/dist/createCheckpointApi.d.ts +38 -0
  44. package/dist/createCheckpointApi.d.ts.map +1 -0
  45. package/dist/createCheckpointApi.js +9 -0
  46. package/dist/createCheckpointApi.js.map +9 -0
  47. package/dist/createEventListener.d.ts +46 -0
  48. package/dist/createEventListener.d.ts.map +1 -0
  49. package/dist/createEventListener.js +12 -0
  50. package/dist/createEventListener.js.map +9 -0
  51. package/dist/createEventStore.d.ts +57 -0
  52. package/dist/createEventStore.d.ts.map +1 -0
  53. package/dist/createEventStore.js +20 -0
  54. package/dist/createEventStore.js.map +9 -0
  55. package/dist/createProcessManager.d.ts +70 -0
  56. package/dist/createProcessManager.d.ts.map +1 -0
  57. package/dist/createProcessManager.js +16 -0
  58. package/dist/createProcessManager.js.map +9 -0
  59. package/dist/createProjection.d.ts +120 -0
  60. package/dist/createProjection.d.ts.map +1 -0
  61. package/dist/createProjection.js +16 -0
  62. package/dist/createProjection.js.map +9 -0
  63. package/dist/drizzle/index.d.ts +449 -0
  64. package/dist/drizzle/index.d.ts.map +1 -0
  65. package/dist/drizzle/index.js +325 -0
  66. package/dist/drizzle/index.js.map +10 -0
  67. package/dist/drizzle/postgres.d.ts +686 -0
  68. package/dist/drizzle/postgres.d.ts.map +1 -0
  69. package/dist/drizzle/postgres.js +18 -0
  70. package/dist/drizzle/postgres.js.map +9 -0
  71. package/dist/errors.d.ts +53 -0
  72. package/dist/errors.d.ts.map +1 -0
  73. package/dist/errors.js +26 -0
  74. package/dist/errors.js.map +9 -0
  75. package/dist/index.d.ts +15 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +73 -0
  78. package/dist/index.js.map +9 -0
  79. package/dist/lock.d.ts +6 -0
  80. package/dist/lock.d.ts.map +1 -0
  81. package/dist/lock.js +8 -0
  82. package/dist/lock.js.map +9 -0
  83. package/dist/memory/index.d.ts +15 -0
  84. package/dist/memory/index.d.ts.map +1 -0
  85. package/dist/memory/index.js +178 -0
  86. package/dist/memory/index.js.map +10 -0
  87. package/dist/redis/index.d.ts +2 -0
  88. package/dist/redis/index.d.ts.map +1 -0
  89. package/dist/redis/index.js +8 -0
  90. package/dist/redis/index.js.map +9 -0
  91. package/dist/redis/redisLock.d.ts +4 -0
  92. package/dist/redis/redisLock.d.ts.map +1 -0
  93. package/dist/redis/redisLock.js +8 -0
  94. package/dist/redis/redisLock.js.map +9 -0
  95. package/dist/refresh.d.ts +4 -0
  96. package/dist/refresh.d.ts.map +1 -0
  97. package/dist/refresh.js +13 -0
  98. package/dist/refresh.js.map +9 -0
  99. package/dist/replay.d.ts +4 -0
  100. package/dist/replay.d.ts.map +1 -0
  101. package/dist/replay.js +13 -0
  102. package/dist/replay.js.map +9 -0
  103. package/dist/snapshotGenerator.d.ts +79 -0
  104. package/dist/snapshotGenerator.d.ts.map +1 -0
  105. package/dist/snapshotGenerator.js +9 -0
  106. package/dist/snapshotGenerator.js.map +9 -0
  107. package/dist/superjson.d.ts +5 -0
  108. package/dist/superjson.d.ts.map +1 -0
  109. package/dist/superjson.js +12 -0
  110. package/dist/superjson.js.map +9 -0
  111. package/dist/transaction.d.ts +13 -0
  112. package/dist/transaction.d.ts.map +1 -0
  113. package/dist/transaction.js +8 -0
  114. package/dist/transaction.js.map +9 -0
  115. package/dist/utilityTypes.d.js +2 -0
  116. package/dist/utilityTypes.d.js.map +9 -0
  117. package/dist/utils.d.ts +4 -0
  118. package/dist/utils.d.ts.map +1 -0
  119. package/dist/utils.js +10 -0
  120. package/dist/utils.js.map +9 -0
  121. package/dist/wrappedEventBuilder.d.ts +37 -0
  122. package/dist/wrappedEventBuilder.d.ts.map +1 -0
  123. package/dist/wrappedEventBuilder.js +12 -0
  124. package/dist/wrappedEventBuilder.js.map +9 -0
  125. package/package.json +87 -1
  126. package/src/createAggregateRoot.ts +902 -0
  127. package/src/createCheckpointApi.ts +106 -0
  128. package/src/createEventListener.ts +118 -0
  129. package/src/createEventStore.ts +446 -0
  130. package/src/createProcessManager.ts +363 -0
  131. package/src/createProjection.ts +577 -0
  132. package/src/drizzle/index.ts +449 -0
  133. package/src/drizzle/postgres.ts +120 -0
  134. package/src/errors.ts +85 -0
  135. package/src/index.ts +45 -0
  136. package/src/lock.ts +58 -0
  137. package/src/memory/index.ts +224 -0
  138. package/src/redis/index.ts +1 -0
  139. package/src/redis/redisLock.ts +61 -0
  140. package/src/refresh.ts +19 -0
  141. package/src/replay.ts +19 -0
  142. package/src/snapshotGenerator.ts +190 -0
  143. package/src/superjson.ts +11 -0
  144. package/src/tests/aggregateRoot.spec.ts +532 -0
  145. package/src/tests/eventListener.spec.ts +57 -0
  146. package/src/tests/eventStore.spec.ts +86 -0
  147. package/src/tests/processManager.spec.ts +185 -0
  148. package/src/tests/projection.spec.ts +511 -0
  149. package/src/tests/projectionProcessManager.spec.ts +203 -0
  150. package/src/tests/setup.ts +230 -0
  151. package/src/tests/transaction.spec.ts +185 -0
  152. package/src/transaction.ts +93 -0
  153. package/src/utilityTypes.d.ts +110 -0
  154. package/src/utils.ts +43 -0
  155. package/src/wrappedEventBuilder.ts +181 -0
@@ -0,0 +1,106 @@
1
+ import z from 'zod/v4'
2
+
3
+ import type { LoadEvents } from './createEventStore.ts'
4
+ import { ValidationError } from './errors.ts'
5
+ import type {
6
+ BaseOutputEvent,
7
+ MaybePromise,
8
+ StreamEvents,
9
+ } from './utilityTypes.js'
10
+
11
+ const checkpointSchema = z.object({
12
+ lastEventPosition: z.int(),
13
+ type: z.enum(['projection', 'processManager']),
14
+ name: z.string(),
15
+ metadata: z.record(z.string(), z.any()),
16
+ })
17
+
18
+ export type Checkpoint = z.output<typeof checkpointSchema>
19
+
20
+ export type CheckpointMethods = {
21
+ get: (
22
+ type: 'projection' | 'processManager',
23
+ name: string,
24
+ ) => MaybePromise<Checkpoint | null | undefined>
25
+ delete: (
26
+ type: 'projection' | 'processManager',
27
+ name: string,
28
+ ) => MaybePromise<void>
29
+ upsert: (checkpoint: Checkpoint) => MaybePromise<void>
30
+ }
31
+
32
+ export function createCheckpointApi(
33
+ params: CheckpointMethods & {
34
+ loadEvents: LoadEvents
35
+ relatedStreamEvents: StreamEvents[] | null
36
+ name: string
37
+ type: 'processManager' | 'projection'
38
+ apply: (event: BaseOutputEvent) => MaybePromise<void>
39
+ applyAfter?: (event: BaseOutputEvent) => MaybePromise<void>
40
+ },
41
+ ) {
42
+ const api = {
43
+ relatedStreamEvents: params.relatedStreamEvents,
44
+ async clear() {
45
+ await params.delete(params.type, params.name)
46
+ },
47
+ async upsert(event: BaseOutputEvent, metadata: Record<string, any> = {}) {
48
+ await params.upsert({
49
+ lastEventPosition: event.position,
50
+ type: params.type,
51
+ name: params.name,
52
+ metadata,
53
+ })
54
+ },
55
+ async rehydrated(
56
+ options: {
57
+ onMetadata?: (metadata: Record<string, any>) => unknown
58
+ getMetadata?: () => Record<string, any>
59
+ onProgress?: (index: number, event: BaseOutputEvent) => void
60
+ } = {},
61
+ ) {
62
+ const checkpoint = await params.get(params.type, params.name)
63
+ let startPosition = 0
64
+ if (checkpoint) {
65
+ const result = checkpointSchema.safeParse(checkpoint)
66
+ if (!result.success) {
67
+ throw new ValidationError(
68
+ `loaded checkpoint for ${params.type} ${params.name} does not comply with schema`,
69
+ result.error,
70
+ )
71
+ }
72
+ startPosition = checkpoint.lastEventPosition + 1
73
+ options.onMetadata?.(checkpoint.metadata)
74
+ }
75
+
76
+ const events = params.loadEvents(params.relatedStreamEvents, {
77
+ from: startPosition,
78
+ })
79
+
80
+ let index = 0
81
+ let lastEvent: BaseOutputEvent | null = null
82
+ for await (const event of events) {
83
+ lastEvent = event
84
+ await params.apply(event)
85
+ index++
86
+ options.onProgress?.(index, event)
87
+ }
88
+
89
+ if (!lastEvent) {
90
+ return // up to date
91
+ }
92
+
93
+ if (params.applyAfter) {
94
+ for await (const event of events) {
95
+ await params.applyAfter(event)
96
+ }
97
+ }
98
+
99
+ await api.upsert(lastEvent, options.getMetadata?.())
100
+ },
101
+ }
102
+
103
+ return api
104
+ }
105
+
106
+ export type CheckpointApi = ReturnType<typeof createCheckpointApi>
@@ -0,0 +1,118 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
+
3
+ import type {
4
+ AfterEffectHandlers,
5
+ AllEvents,
6
+ BaseOutputEvent,
7
+ EventHandlers,
8
+ EventTemplate,
9
+ Keys,
10
+ MaybePromise,
11
+ Merge,
12
+ } from './utilityTypes.js'
13
+
14
+ export type InternalEventListener = {
15
+ apply: (event: BaseOutputEvent) => Promise<void>
16
+ applyAfter: (event: BaseOutputEvent) => Promise<void>
17
+ }
18
+
19
+ type EventListenerInfo = {
20
+ name: string | null
21
+ event: BaseOutputEvent
22
+ }
23
+ const eventListenerInfoStore = new AsyncLocalStorage<EventListenerInfo>()
24
+
25
+ function withEventListenerInfo(
26
+ info: EventListenerInfo,
27
+ handler: () => Promise<void>,
28
+ ) {
29
+ return eventListenerInfoStore.run(info, handler)
30
+ }
31
+
32
+ export function getEventListenerInfo() {
33
+ return eventListenerInfoStore.getStore()
34
+ }
35
+
36
+ export function isInsideEventListener() {
37
+ return Boolean(getEventListenerInfo())
38
+ }
39
+
40
+ export type EventListenerApi<
41
+ Events extends EventTemplate = never,
42
+ Flags extends {
43
+ withEventHandlers: boolean
44
+ withGlobalEventHandler: boolean
45
+ withAfterEffects: boolean
46
+ withGlobalAfterEffect: boolean
47
+ } = {
48
+ withEventHandlers: false
49
+ withGlobalEventHandler: false
50
+ withAfterEffects: false
51
+ withGlobalAfterEffect: false
52
+ },
53
+ > = {
54
+ withEventHandlers: (
55
+ handlers: EventHandlers<Events>,
56
+ ) => Omit<
57
+ EventListenerApi<Events, Merge<Flags, { withEventHandlers: true }>>,
58
+ Keys<Merge<Flags, { withEventHandlers: true }>>
59
+ >
60
+ withGlobalEventHandler: (
61
+ handler: (event: AllEvents<Events>) => MaybePromise<void>,
62
+ ) => Omit<
63
+ EventListenerApi<Events, Merge<Flags, { withGlobalEventHandler: true }>>,
64
+ Keys<Merge<Flags, { withGlobalEventHandler: true }>>
65
+ >
66
+ withAfterEffects: (
67
+ handlers: AfterEffectHandlers<Events>,
68
+ ) => Omit<
69
+ EventListenerApi<Events, Merge<Flags, { withAfterEffects: true }>>,
70
+ Keys<Merge<Flags, { withAfterEffects: true }>>
71
+ >
72
+ withGlobalAfterEffect: (
73
+ handler: (event: AllEvents<Events>) => MaybePromise<void>,
74
+ ) => Omit<
75
+ EventListenerApi<Events, Merge<Flags, { withGlobalAfterEffect: true }>>,
76
+ Keys<Merge<Flags, { withGlobalAfterEffect: true }>>
77
+ >
78
+ }
79
+
80
+ export function createEventListener(
81
+ name: string | null = null,
82
+ ): EventListenerApi {
83
+ let eventHandlers: EventHandlers<any> = {}
84
+ let afterEffectHandlers: AfterEffectHandlers<any> = {}
85
+ let globalEventHandler: (event: BaseOutputEvent) => MaybePromise<void>
86
+ let globalAfterEffect: (event: BaseOutputEvent) => MaybePromise<void>
87
+ const api = {
88
+ async apply(event) {
89
+ withEventListenerInfo({ name, event }, async () => {
90
+ await eventHandlers[`on${event.type}`]?.(event)
91
+ await globalEventHandler?.(event)
92
+ })
93
+ },
94
+
95
+ async applyAfter(event) {
96
+ await afterEffectHandlers[`after${event.type}`]?.(event)
97
+ await globalAfterEffect?.(event)
98
+ },
99
+ withEventHandlers(handlers) {
100
+ eventHandlers = handlers
101
+ return api
102
+ },
103
+ withGlobalEventHandler(handler) {
104
+ globalEventHandler = handler as any
105
+ return api
106
+ },
107
+ withAfterEffects(handlers) {
108
+ afterEffectHandlers = handlers
109
+ return api
110
+ },
111
+ withGlobalAfterEffect(handler) {
112
+ globalAfterEffect = handler as any
113
+ return api
114
+ },
115
+ } as EventListenerApi & InternalEventListener
116
+
117
+ return api
118
+ }
@@ -0,0 +1,446 @@
1
+ import type {
2
+ AggregateRootApi,
3
+ AggregateRootSnapshotMethods,
4
+ AnyAggregateRoot,
5
+ InternalAggregateRootApi,
6
+ } from './createAggregateRoot.ts'
7
+ import type { CheckpointMethods } from './createCheckpointApi.ts'
8
+ import {
9
+ type EventListenerApi,
10
+ type InternalEventListener,
11
+ createEventListener,
12
+ } from './createEventListener.ts'
13
+ import {
14
+ type InternalProcessManager,
15
+ type ProcessManagerApi,
16
+ buildProcessManagerCreator,
17
+ } from './createProcessManager.ts'
18
+ import {
19
+ type InternalProjection,
20
+ type ProjectionApi,
21
+ type ProjectionApiWithSnapshots,
22
+ type ProjectionSnapshotMethods,
23
+ buildProjectionCreator,
24
+ } from './createProjection.ts'
25
+ import lock, { type LockCreator } from './lock.ts'
26
+ import { withRefreshing } from './refresh.ts'
27
+ import createTransaction from './transaction.ts'
28
+ import type {
29
+ BaseInputEvent,
30
+ BaseOutputEvent,
31
+ EventHandlers,
32
+ EventTemplate,
33
+ MaybePromise,
34
+ Stream,
35
+ StreamEvents,
36
+ StreamId,
37
+ StreamIdEvents,
38
+ } from './utilityTypes.js'
39
+ import {
40
+ type WrappedEvent,
41
+ unwrapEvent,
42
+ wrappedEventBuilder,
43
+ } from './wrappedEventBuilder.ts'
44
+
45
+ export type LoadEvents = (
46
+ select:
47
+ | Stream
48
+ | StreamId
49
+ | Stream[]
50
+ | StreamId[]
51
+ | StreamEvents
52
+ | StreamEvents[]
53
+ | StreamIdEvents
54
+ | StreamIdEvents[]
55
+ | null,
56
+ range?: { from: number; to?: number },
57
+ ) => AsyncGenerator<BaseOutputEvent>
58
+
59
+ type AppendEvents = (
60
+ streamId: StreamId,
61
+ events: BaseInputEvent[],
62
+ expectedVersion: number,
63
+ ) => MaybePromise<BaseOutputEvent[]>
64
+
65
+ export type InternalAppendEvents = (
66
+ streamId: StreamId,
67
+ events: BaseInputEvent[],
68
+ expectedVersion: number,
69
+ ) => MaybePromise<BaseOutputEvent[]>
70
+
71
+ export type EmitEvents = (events: BaseOutputEvent[]) => Promise<void>
72
+
73
+ export type EventsFromRoot<Root> =
74
+ Root extends Omit<AggregateRootApi<any, any, infer E>, any> ? E : never
75
+
76
+ export type OnProgress = {
77
+ begin?: (projectionName: string) => void
78
+ progress?: (
79
+ projectionName: string,
80
+ index: number,
81
+ event: BaseOutputEvent,
82
+ ) => void
83
+ end?: (projectionName: string) => void
84
+ }
85
+
86
+ interface BaseEventStoreParams<Root extends AnyAggregateRoot> {
87
+ loadEvents: LoadEvents
88
+ appendEvents: AppendEvents
89
+ aggregateRoots: Root[]
90
+ checkpoint: CheckpointMethods
91
+ lock?: LockCreator
92
+ projectionSnapshot?: ProjectionSnapshotMethods
93
+ onProjectionReplay?: OnProgress
94
+ onProcessManagerRefresh?: OnProgress
95
+ autoInit?: boolean
96
+ }
97
+
98
+ export interface EventStoreParamsWithAggregateRootSnapshots<
99
+ Root extends AnyAggregateRoot,
100
+ > extends BaseEventStoreParams<Root> {
101
+ aggregateRootSnapshot: AggregateRootSnapshotMethods
102
+ postProcessEvent?: (
103
+ event: WrappedEvent<EventsFromRoot<Root>>,
104
+ aggregateRoot: Root,
105
+ ) => WrappedEvent
106
+ }
107
+
108
+ export interface EventStoreParamsWithoutAggregateRootSnapshots<
109
+ Root extends AnyAggregateRoot,
110
+ > extends BaseEventStoreParams<Root> {
111
+ aggregateRoots: Root[]
112
+ aggregateRootSnapshot?: never
113
+ postProcessEvent?: (
114
+ event: WrappedEvent<EventsFromRoot<Root>>,
115
+ aggregateRoot: Root,
116
+ ) => WrappedEvent
117
+ }
118
+
119
+ export type EventStore<
120
+ Event extends EventTemplate,
121
+ Root extends AnyAggregateRoot,
122
+ SupportsProjectionSnapshots = boolean,
123
+ > = {
124
+ createProjection(
125
+ name: string,
126
+ ): SupportsProjectionSnapshots extends true
127
+ ? ProjectionApiWithSnapshots<Event>
128
+ : ProjectionApi<Event>
129
+ createProcessManager(name: string): ProcessManagerApi<never, Event>
130
+ createEventListener(name: string): EventListenerApi<Event>
131
+ rebuild: () => Promise<void>
132
+ isReady: () => Promise<void>
133
+ init: () => Promise<EventStore<Event, Root, SupportsProjectionSnapshots>>
134
+ getAggregateRoot<S extends Root['type']>(
135
+ type: S,
136
+ ): Root extends { type: S } ? Root : never
137
+ transaction<T extends () => any>(handler: T): Promise<Awaited<ReturnType<T>>>
138
+ }
139
+
140
+ export function createEventStore<const Root extends AnyAggregateRoot>(
141
+ params: EventStoreParamsWithoutAggregateRootSnapshots<Root>,
142
+ ): EventStore<EventsFromRoot<Root>, Root, false>
143
+ export function createEventStore<const Root extends AnyAggregateRoot>(
144
+ params: EventStoreParamsWithAggregateRootSnapshots<Root>,
145
+ ): EventStore<EventsFromRoot<Root>, Root, true>
146
+ export function createEventStore<const Root extends AnyAggregateRoot>(
147
+ params:
148
+ | EventStoreParamsWithoutAggregateRootSnapshots<Root>
149
+ | EventStoreParamsWithAggregateRootSnapshots<Root>,
150
+ ) {
151
+ const transaction = createTransaction()
152
+ const { promise: initialized, resolve: resolveInitialized } =
153
+ Promise.withResolvers<void>()
154
+ const projections = new Map<string, InternalProjection>()
155
+ const processManagers = new Map<string, InternalProcessManager>()
156
+ const eventListeners = new Map<string, InternalEventListener>()
157
+ const aggregatRootsByEvents = new Map<
158
+ string,
159
+ { aggregateRoot: InternalAggregateRootApi; eventType: string }
160
+ >()
161
+
162
+ let allReplaying = false
163
+ let initializing = false
164
+
165
+ const assertInitializing = () => {
166
+ if (!initializing && params.autoInit === false) {
167
+ throw new Error(
168
+ 'Cannot emit events because the event store is not initialized. Set autoInit to true, or explicitly call .init() before emitting events.',
169
+ )
170
+ }
171
+ }
172
+
173
+ let exclusiveEmitEventsChain: Promise<any> = Promise.resolve()
174
+ const emitEvents: EmitEvents = async (events) => {
175
+ exclusiveEmitEventsChain = exclusiveEmitEventsChain.then(async () => {
176
+ for (const event of events) {
177
+ await Promise.all(
178
+ [
179
+ ...processManagers.values(),
180
+ ...projections.values(),
181
+ ...eventListeners.values(),
182
+ ].map((listener) => listener.apply(event)),
183
+ )
184
+ }
185
+
186
+ for (const event of events) {
187
+ await Promise.all(
188
+ [...processManagers.values(), ...eventListeners.values()].map(
189
+ (processManager) => processManager.applyAfter(event),
190
+ ),
191
+ )
192
+ }
193
+ })
194
+
195
+ return exclusiveEmitEventsChain
196
+ }
197
+
198
+ const loadEvents: LoadEvents = (select, range) => {
199
+ assertInitializing()
200
+ return params.loadEvents(select, range)
201
+ }
202
+
203
+ let exclusiveAppendEventsChain: Promise<any> = initialized
204
+ const appendEvents: InternalAppendEvents = async (
205
+ streamId,
206
+ events,
207
+ expectedVersion,
208
+ ) => {
209
+ assertInitializing()
210
+ exclusiveAppendEventsChain = exclusiveAppendEventsChain.then(async () => {
211
+ if (events.length === 0) {
212
+ return []
213
+ }
214
+ await new Promise((resolve) => setImmediate(resolve)) // edge case for sync setup, see 'can execute after effect' test
215
+
216
+ const processedEvents = events.map((rawEvent) => {
217
+ const event = structuredClone(rawEvent)
218
+ if (params.postProcessEvent) {
219
+ const wrappedEvent = wrappedEventBuilder(event) as WrappedEvent<any>
220
+ const { aggregateRoot } = aggregatRootsByEvents.get(event.type)!
221
+ const processedEvent = unwrapEvent(
222
+ params.postProcessEvent(
223
+ wrappedEvent,
224
+ aggregateRoot as unknown as Root,
225
+ ),
226
+ )
227
+ Object.assign(event, processedEvent)
228
+ }
229
+
230
+ return event
231
+ })
232
+
233
+ return params.appendEvents(streamId, processedEvents, expectedVersion)
234
+ })
235
+
236
+ return exclusiveAppendEventsChain
237
+ }
238
+
239
+ for (const aggregateRoot of params.aggregateRoots as unknown as InternalAggregateRootApi[]) {
240
+ aggregateRoot.eventTypes.forEach((eventType) => {
241
+ aggregatRootsByEvents.set(eventType, {
242
+ aggregateRoot,
243
+ eventType,
244
+ })
245
+ })
246
+
247
+ aggregateRoot.linkEventStore(
248
+ loadEvents,
249
+ appendEvents,
250
+ emitEvents,
251
+ transaction,
252
+ params.lock ?? lock(5000),
253
+ params.aggregateRootSnapshot,
254
+ )
255
+ }
256
+
257
+ const baseCreateProjection = buildProjectionCreator({
258
+ loadEvents,
259
+ checkpoint: params.checkpoint,
260
+ snapshot: params.projectionSnapshot,
261
+ onReplay: params.onProjectionReplay,
262
+ })
263
+
264
+ const baseCreateProcessManager = buildProcessManagerCreator({
265
+ loadEvents,
266
+ checkpoint: params.checkpoint,
267
+ onRefresh: params.onProcessManagerRefresh,
268
+ })
269
+
270
+ const getRelatedStreamEvents = (
271
+ handlers: EventHandlers<any>,
272
+ ): StreamEvents[] => {
273
+ const relatedStreamEvents = new Map<string, Set<string>>()
274
+ for (const handlerName of Object.keys(handlers)) {
275
+ const sanitizedHandlerName = handlerName.replace(/^(on|after)/, '')
276
+ if (!aggregatRootsByEvents.has(sanitizedHandlerName)) {
277
+ throw new Error(
278
+ `event handler ${handlerName} has no corresponding event in any aggregate root`,
279
+ )
280
+ }
281
+ const {
282
+ aggregateRoot: { type: aggregateRootName },
283
+ eventType,
284
+ } = aggregatRootsByEvents.get(sanitizedHandlerName)!
285
+ if (!relatedStreamEvents.has(aggregateRootName)) {
286
+ relatedStreamEvents.set(aggregateRootName, new Set())
287
+ }
288
+ relatedStreamEvents.get(aggregateRootName)!.add(eventType)
289
+ }
290
+
291
+ return Array.from(relatedStreamEvents, ([stream, events]) => ({
292
+ stream,
293
+ events: Array.from(events),
294
+ }))
295
+ }
296
+
297
+ const initialize = async () => {
298
+ initializing = true
299
+ await Promise.all(
300
+ projections.values().map((projection) => projection.init(true)),
301
+ )
302
+ await Promise.all(
303
+ processManagers
304
+ .values()
305
+ .map((processManager) => processManager.init(true)),
306
+ )
307
+ resolveInitialized()
308
+ }
309
+ setImmediate(() => {
310
+ if (allReplaying || params.autoInit === false) {
311
+ return
312
+ }
313
+
314
+ initialize()
315
+ })
316
+
317
+ const api = {
318
+ createProjection(name) {
319
+ if (projections.has(name)) {
320
+ throw new Error(`Projection ${name} is already registered`)
321
+ }
322
+ const api = baseCreateProjection(name)
323
+ const internalApi = api as unknown as InternalProjection
324
+ internalApi.setRelatedStreamEvents((handlers) =>
325
+ getRelatedStreamEvents(handlers),
326
+ )
327
+ projections.set(name, internalApi)
328
+ if (initializing) {
329
+ internalApi.init(true)
330
+ }
331
+ return api
332
+ },
333
+ createProcessManager(name) {
334
+ if (processManagers.has(name)) {
335
+ throw new Error(`Process Manager ${name} is already registered`)
336
+ }
337
+ const api = baseCreateProcessManager(name)
338
+
339
+ const internalApi = api as unknown as InternalProcessManager
340
+ internalApi.setRelatedStreamEvents((handlers) =>
341
+ getRelatedStreamEvents(handlers),
342
+ )
343
+ processManagers.set(name, internalApi)
344
+ if (initializing) {
345
+ internalApi.init(true)
346
+ }
347
+ return api
348
+ },
349
+ createEventListener(name) {
350
+ if (eventListeners.has(name)) {
351
+ throw new Error(`Event Listener ${name} is already registered`)
352
+ }
353
+
354
+ const api = createEventListener(name)
355
+
356
+ const internalApi = api as unknown as InternalEventListener
357
+ eventListeners.set(name, internalApi)
358
+ return api
359
+ },
360
+ async isReady() {
361
+ await initialized
362
+ await Promise.all([
363
+ ...projections.values().map((projection) => projection.isReady()),
364
+ ...processManagers.values().map((projection) => projection.isReady()),
365
+ ])
366
+ },
367
+ async rebuild() {
368
+ allReplaying = true
369
+ initializing = true
370
+ const replayProjections = Array.from(projections.values()).filter(
371
+ (projection) => projection.canReplay(),
372
+ )
373
+ const replayProcessManagers = Array.from(processManagers.values()).filter(
374
+ (processManager) => processManager.hasState(),
375
+ )
376
+
377
+ await Promise.all([
378
+ ...replayProjections.map((projection) => projection.init(false)),
379
+ ...replayProcessManagers.map((projection) => projection.init(false)),
380
+ ...replayProjections.map((projection) => projection.beginReplay()),
381
+ ...replayProcessManagers.map((processManager) =>
382
+ processManager.beginRefresh(),
383
+ ),
384
+ ])
385
+
386
+ await withRefreshing(async () => {
387
+ let index = 0
388
+ for await (const event of loadEvents(null)) {
389
+ index++
390
+ await Promise.all([
391
+ ...replayProjections.map((projection) =>
392
+ projection.progressReplay(index, event),
393
+ ),
394
+ ...replayProcessManagers.map((processManager) =>
395
+ processManager.progressRefresh(index, event, false),
396
+ ),
397
+ ...Array.from(eventListeners.values(), (listener) =>
398
+ listener.apply(event),
399
+ ),
400
+ ])
401
+ await Promise.all([
402
+ ...replayProcessManagers.map((processManager) =>
403
+ processManager.progressRefresh(index, event, true),
404
+ ),
405
+ ...Array.from(eventListeners.values(), (listener) =>
406
+ listener.applyAfter(event),
407
+ ),
408
+ ])
409
+ }
410
+ })
411
+
412
+ await Promise.all([
413
+ ...replayProjections.map((projection) => projection.endReplay()),
414
+ ...replayProcessManagers.map((processManager) =>
415
+ processManager.endRefresh(),
416
+ ),
417
+ ])
418
+
419
+ await Promise.all(
420
+ projections.values().map(async (projection) => {
421
+ if (!projection.canReplay()) {
422
+ await projection.init(true)
423
+ }
424
+ }),
425
+ )
426
+ resolveInitialized()
427
+ },
428
+ async init() {
429
+ if (initializing) {
430
+ return api
431
+ }
432
+ await initialize()
433
+ return api
434
+ },
435
+ getAggregateRoot(type) {
436
+ return params.aggregateRoots.find(
437
+ (aggregateRoot) => aggregateRoot.type === type,
438
+ ) as any
439
+ },
440
+ async transaction(handler) {
441
+ return transaction.run(handler)
442
+ },
443
+ } as EventStore<any, Root>
444
+
445
+ return api
446
+ }