@requence/event-sourcing 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-24xjphpk.js +51 -0
  3. package/dist/chunk-24xjphpk.js.map +10 -0
  4. package/dist/chunk-3t1vzbab.js +88 -0
  5. package/dist/chunk-3t1vzbab.js.map +10 -0
  6. package/dist/chunk-6q8mr1sg.js +13 -0
  7. package/dist/chunk-6q8mr1sg.js.map +10 -0
  8. package/dist/chunk-9psg50cb.js +22 -0
  9. package/dist/chunk-9psg50cb.js.map +10 -0
  10. package/dist/chunk-dgzw1nj9.js +50 -0
  11. package/dist/chunk-dgzw1nj9.js.map +10 -0
  12. package/dist/chunk-gq80tgvf.js +70 -0
  13. package/dist/chunk-gq80tgvf.js.map +10 -0
  14. package/dist/chunk-jd4q8fcf.js +11 -0
  15. package/dist/chunk-jd4q8fcf.js.map +10 -0
  16. package/dist/chunk-m07vf8q9.js +210 -0
  17. package/dist/chunk-m07vf8q9.js.map +10 -0
  18. package/dist/chunk-ncpk70p4.js +313 -0
  19. package/dist/chunk-ncpk70p4.js.map +10 -0
  20. package/dist/chunk-pmfzjavb.js +49 -0
  21. package/dist/chunk-pmfzjavb.js.map +10 -0
  22. package/dist/chunk-qdczt68t.js +84 -0
  23. package/dist/chunk-qdczt68t.js.map +10 -0
  24. package/dist/chunk-tfy01fah.js +34 -0
  25. package/dist/chunk-tfy01fah.js.map +10 -0
  26. package/dist/chunk-tkvj6x8k.js +389 -0
  27. package/dist/chunk-tkvj6x8k.js.map +10 -0
  28. package/dist/chunk-ty9bhdvx.js +53 -0
  29. package/dist/chunk-ty9bhdvx.js.map +10 -0
  30. package/dist/chunk-wmz6a1jk.js +233 -0
  31. package/dist/chunk-wmz6a1jk.js.map +10 -0
  32. package/dist/chunk-x90g388y.js +22 -0
  33. package/dist/chunk-x90g388y.js.map +10 -0
  34. package/dist/chunk-z388cd25.js +91 -0
  35. package/dist/chunk-z388cd25.js.map +10 -0
  36. package/dist/chunk-zf3x573k.js +65 -0
  37. package/dist/chunk-zf3x573k.js.map +10 -0
  38. package/dist/createAggregateRoot.d.ts +191 -0
  39. package/dist/createAggregateRoot.d.ts.map +1 -0
  40. package/dist/createAggregateRoot.js +18 -0
  41. package/dist/createAggregateRoot.js.map +9 -0
  42. package/dist/createCheckpointApi.d.ts +38 -0
  43. package/dist/createCheckpointApi.d.ts.map +1 -0
  44. package/dist/createCheckpointApi.js +9 -0
  45. package/dist/createCheckpointApi.js.map +9 -0
  46. package/dist/createEventListener.d.ts +46 -0
  47. package/dist/createEventListener.d.ts.map +1 -0
  48. package/dist/createEventListener.js +12 -0
  49. package/dist/createEventListener.js.map +9 -0
  50. package/dist/createEventStore.d.ts +57 -0
  51. package/dist/createEventStore.d.ts.map +1 -0
  52. package/dist/createEventStore.js +20 -0
  53. package/dist/createEventStore.js.map +9 -0
  54. package/dist/createProcessManager.d.ts +70 -0
  55. package/dist/createProcessManager.d.ts.map +1 -0
  56. package/dist/createProcessManager.js +16 -0
  57. package/dist/createProcessManager.js.map +9 -0
  58. package/dist/createProjection.d.ts +120 -0
  59. package/dist/createProjection.d.ts.map +1 -0
  60. package/dist/createProjection.js +16 -0
  61. package/dist/createProjection.js.map +9 -0
  62. package/dist/drizzle/index.d.ts +449 -0
  63. package/dist/drizzle/index.d.ts.map +1 -0
  64. package/dist/drizzle/index.js +325 -0
  65. package/dist/drizzle/index.js.map +10 -0
  66. package/dist/drizzle/postgres.d.ts +686 -0
  67. package/dist/drizzle/postgres.d.ts.map +1 -0
  68. package/dist/drizzle/postgres.js +18 -0
  69. package/dist/drizzle/postgres.js.map +9 -0
  70. package/dist/errors.d.ts +53 -0
  71. package/dist/errors.d.ts.map +1 -0
  72. package/dist/errors.js +26 -0
  73. package/dist/errors.js.map +9 -0
  74. package/dist/index.d.ts +15 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +73 -0
  77. package/dist/index.js.map +9 -0
  78. package/dist/lock.d.ts +6 -0
  79. package/dist/lock.d.ts.map +1 -0
  80. package/dist/lock.js +8 -0
  81. package/dist/lock.js.map +9 -0
  82. package/dist/memory/index.d.ts +15 -0
  83. package/dist/memory/index.d.ts.map +1 -0
  84. package/dist/memory/index.js +178 -0
  85. package/dist/memory/index.js.map +10 -0
  86. package/dist/redis/index.d.ts +2 -0
  87. package/dist/redis/index.d.ts.map +1 -0
  88. package/dist/redis/index.js +8 -0
  89. package/dist/redis/index.js.map +9 -0
  90. package/dist/redis/redisLock.d.ts +4 -0
  91. package/dist/redis/redisLock.d.ts.map +1 -0
  92. package/dist/redis/redisLock.js +8 -0
  93. package/dist/redis/redisLock.js.map +9 -0
  94. package/dist/refresh.d.ts +4 -0
  95. package/dist/refresh.d.ts.map +1 -0
  96. package/dist/refresh.js +13 -0
  97. package/dist/refresh.js.map +9 -0
  98. package/dist/replay.d.ts +4 -0
  99. package/dist/replay.d.ts.map +1 -0
  100. package/dist/replay.js +13 -0
  101. package/dist/replay.js.map +9 -0
  102. package/dist/snapshotGenerator.d.ts +79 -0
  103. package/dist/snapshotGenerator.d.ts.map +1 -0
  104. package/dist/snapshotGenerator.js +9 -0
  105. package/dist/snapshotGenerator.js.map +9 -0
  106. package/dist/superjson.d.ts +5 -0
  107. package/dist/superjson.d.ts.map +1 -0
  108. package/dist/superjson.js +12 -0
  109. package/dist/superjson.js.map +9 -0
  110. package/dist/transaction.d.ts +13 -0
  111. package/dist/transaction.d.ts.map +1 -0
  112. package/dist/transaction.js +8 -0
  113. package/dist/transaction.js.map +9 -0
  114. package/dist/utilityTypes.d.js +2 -0
  115. package/dist/utilityTypes.d.js.map +9 -0
  116. package/dist/utils.d.ts +4 -0
  117. package/dist/utils.d.ts.map +1 -0
  118. package/dist/utils.js +10 -0
  119. package/dist/utils.js.map +9 -0
  120. package/dist/wrappedEventBuilder.d.ts +37 -0
  121. package/dist/wrappedEventBuilder.d.ts.map +1 -0
  122. package/dist/wrappedEventBuilder.js +12 -0
  123. package/dist/wrappedEventBuilder.js.map +9 -0
  124. package/package.json +85 -1
  125. package/src/createAggregateRoot.ts +902 -0
  126. package/src/createCheckpointApi.ts +106 -0
  127. package/src/createEventListener.ts +118 -0
  128. package/src/createEventStore.ts +446 -0
  129. package/src/createProcessManager.ts +363 -0
  130. package/src/createProjection.ts +577 -0
  131. package/src/drizzle/index.ts +449 -0
  132. package/src/drizzle/postgres.ts +120 -0
  133. package/src/errors.ts +85 -0
  134. package/src/index.ts +45 -0
  135. package/src/lock.ts +58 -0
  136. package/src/memory/index.ts +224 -0
  137. package/src/redis/index.ts +1 -0
  138. package/src/redis/redisLock.ts +61 -0
  139. package/src/refresh.ts +19 -0
  140. package/src/replay.ts +19 -0
  141. package/src/snapshotGenerator.ts +190 -0
  142. package/src/superjson.ts +11 -0
  143. package/src/tests/aggregateRoot.spec.ts +532 -0
  144. package/src/tests/eventListener.spec.ts +57 -0
  145. package/src/tests/eventStore.spec.ts +86 -0
  146. package/src/tests/processManager.spec.ts +185 -0
  147. package/src/tests/projection.spec.ts +511 -0
  148. package/src/tests/projectionProcessManager.spec.ts +203 -0
  149. package/src/tests/setup.ts +230 -0
  150. package/src/tests/transaction.spec.ts +185 -0
  151. package/src/transaction.ts +93 -0
  152. package/src/utilityTypes.d.ts +110 -0
  153. package/src/utils.ts +43 -0
  154. package/src/wrappedEventBuilder.ts +181 -0
@@ -0,0 +1,363 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
+
3
+ import {
4
+ type CheckpointApi,
5
+ type CheckpointMethods,
6
+ createCheckpointApi,
7
+ } from './createCheckpointApi.ts'
8
+ import type { LoadEvents, OnProgress } from './createEventStore.ts'
9
+ import {
10
+ ProcessManagerEventHandlerExecutionError,
11
+ RefreshingSkipError,
12
+ } from './errors.ts'
13
+ import { isRefreshing, withRefreshing } from './refresh.ts'
14
+ import { clone } from './superjson.ts'
15
+ import type {
16
+ AfterEffectHandlers,
17
+ BaseOutputEvent,
18
+ EventHandlers,
19
+ EventTemplate,
20
+ Keys,
21
+ Merge,
22
+ StreamEvents,
23
+ } from './utilityTypes.js'
24
+
25
+ type ProcessManagerInfo = {
26
+ name: string
27
+ event: BaseOutputEvent
28
+ }
29
+ const processManagerInfoStore = new AsyncLocalStorage<ProcessManagerInfo>()
30
+
31
+ function withProcessManagerInfo(
32
+ info: ProcessManagerInfo,
33
+ handler: () => Promise<void>,
34
+ ) {
35
+ return processManagerInfoStore.run(info, handler)
36
+ }
37
+
38
+ export function getProcessManagerInfo() {
39
+ return processManagerInfoStore.getStore()
40
+ }
41
+
42
+ export function isInsideProcessManager() {
43
+ return Boolean(getProcessManagerInfo())
44
+ }
45
+
46
+ type ProcessManagerEventHandlers<Events extends EventTemplate> = EventHandlers<
47
+ Events,
48
+ { refreshing: boolean }
49
+ >
50
+
51
+ type ProcessManagerAfterEffectHandlers<Events extends EventTemplate> =
52
+ AfterEffectHandlers<Events, { refreshing: boolean }>
53
+
54
+ export type InternalProcessManager = {
55
+ apply: (event: BaseOutputEvent) => Promise<void>
56
+ applyAfter: (event: BaseOutputEvent) => Promise<void>
57
+ setRelatedStreamEvents: (
58
+ generateRelatedStreamEvents: (
59
+ eventHandlers: EventHandlers<EventTemplate>,
60
+ ) => StreamEvents[],
61
+ ) => void
62
+ isReady(): Promise<void>
63
+ beginRefresh(): Promise<void>
64
+ progressRefresh(
65
+ index: number,
66
+ event: BaseOutputEvent,
67
+ after: boolean,
68
+ ): Promise<void>
69
+ endRefresh(): void
70
+ init(catchUp: boolean): Promise<void>
71
+ hasState(): boolean
72
+ }
73
+
74
+ export type ProcessManagerApi<
75
+ State = never,
76
+ Events extends EventTemplate = never,
77
+ Flags extends {
78
+ withState: boolean
79
+ withEventHandlers: boolean
80
+ concurrent: boolean
81
+ withAfterEffects: boolean
82
+ } = {
83
+ withState: false
84
+ withEventHandlers: false
85
+ concurrent: false
86
+ withAfterEffects: false
87
+ },
88
+ > = {
89
+ withState: <NextState>(
90
+ state: NextState,
91
+ ) => Omit<
92
+ ProcessManagerApi<NextState, Events, Merge<Flags, { withState: true }>>,
93
+ Keys<Merge<Flags, { withState: true }>>
94
+ >
95
+ withEventHandlers: (
96
+ handlers: [State] extends [never]
97
+ ? ProcessManagerEventHandlers<Events>
98
+ : (state: State) => ProcessManagerEventHandlers<Events>,
99
+ ) => Omit<
100
+ ProcessManagerApi<State, Events, Merge<Flags, { withEventHandlers: true }>>,
101
+ Keys<Merge<Flags, { withEventHandlers: true }>>
102
+ >
103
+ withAfterEffects: (
104
+ handlers: [State] extends [never]
105
+ ? ProcessManagerAfterEffectHandlers<Events>
106
+ : (state: State) => ProcessManagerAfterEffectHandlers<Events>,
107
+ ) => Omit<
108
+ ProcessManagerApi<State, Events, Merge<Flags, { withAfterEffects: true }>>,
109
+ Keys<Merge<Flags, { withAfterEffects: true }>>
110
+ >
111
+ concurrent: () => Omit<
112
+ ProcessManagerApi<State, Events, Merge<Flags, { concurrent: true }>>,
113
+ Keys<Merge<Flags, { concurrent: true }>>
114
+ >
115
+ isReady(): Promise<void>
116
+ $inferState: State
117
+ } & (Flags['withState'] extends true
118
+ ? {
119
+ refreshState: () => Promise<void>
120
+ state: () => Promise<State>
121
+ }
122
+ : {})
123
+
124
+ export function buildProcessManagerCreator(params: {
125
+ loadEvents: LoadEvents
126
+ checkpoint: CheckpointMethods
127
+ onRefresh?: OnProgress
128
+ }) {
129
+ return (name: string): ProcessManagerApi => {
130
+ let idle = true
131
+ let createEventHandlers: (
132
+ state: any,
133
+ ) => ProcessManagerEventHandlers<any> = () => ({})
134
+ let createAfterEffects: (
135
+ state: any,
136
+ ) => ProcessManagerAfterEffectHandlers<any> = () => ({})
137
+
138
+ let hasState = false
139
+ let state: any = null
140
+ let initialState: any = null
141
+ let exclusive = true
142
+ let generateRelatedStreamEvents: (
143
+ eventHandlers: EventHandlers<any>,
144
+ ) => StreamEvents[]
145
+ let checkpointApi: CheckpointApi
146
+ let lastEventPosition = -1
147
+ const { promise: hydrated, resolve: resolveHydrated } =
148
+ Promise.withResolvers<void>()
149
+
150
+ const locks = new Map<string, Promise<void>>()
151
+ const apply = async (
152
+ event: BaseOutputEvent,
153
+ after: boolean,
154
+ hydrating = false,
155
+ ) => {
156
+ const handler = after
157
+ ? createAfterEffects(state)[`after${event.type}`]
158
+ : createEventHandlers(state)[`on${event.type}`]
159
+
160
+ if (!handler) {
161
+ return Promise.resolve()
162
+ }
163
+
164
+ if (hydrating) {
165
+ lastEventPosition = event.position
166
+ } else {
167
+ await hydrated
168
+ // event was already applied through hydration
169
+ if (event.position <= lastEventPosition) {
170
+ return
171
+ }
172
+ }
173
+ const unlocked = !exclusive
174
+ ? Promise.resolve()
175
+ : (locks.get(event.streamId)?.catch(() => {}) ?? Promise.resolve())
176
+
177
+ const runner = (async () => {
178
+ await unlocked
179
+ const refreshing = isRefreshing()
180
+ await withProcessManagerInfo(
181
+ {
182
+ name,
183
+ event,
184
+ },
185
+ async () => {
186
+ try {
187
+ await handler(event, { refreshing })
188
+ } catch (error) {
189
+ if (error instanceof RefreshingSkipError) {
190
+ if (refreshing) {
191
+ return
192
+ }
193
+
194
+ throw new Error(
195
+ 'RefreshingSkipError should never be thrown outside refresh',
196
+ )
197
+ }
198
+
199
+ if (!(error instanceof Error)) {
200
+ throw error
201
+ }
202
+
203
+ throw new ProcessManagerEventHandlerExecutionError(
204
+ name,
205
+ after ? `after${event.type}` : `on${event.type}`,
206
+ event,
207
+ error,
208
+ )
209
+ }
210
+ },
211
+ )
212
+ await checkpointApi.upsert(event, { state })
213
+ })()
214
+
215
+ if (exclusive) {
216
+ locks.set(event.streamId, runner)
217
+
218
+ runner
219
+ .finally(() => {
220
+ if (locks.get(event.streamId) === runner) {
221
+ locks.delete(event.streamId)
222
+ }
223
+ })
224
+ .catch(() => {})
225
+ return runner
226
+ }
227
+
228
+ return Promise.resolve()
229
+ }
230
+
231
+ let resolveRefresh: null | (() => void) = null
232
+
233
+ const api = {
234
+ name,
235
+ hasState() {
236
+ return hasState
237
+ },
238
+ async isReady() {
239
+ await hydrated
240
+ },
241
+ async apply(event) {
242
+ await apply(event, false)
243
+ },
244
+ async applyAfter(event) {
245
+ await apply(event, true)
246
+ },
247
+ withState(nextState) {
248
+ state = clone(nextState)
249
+ initialState = clone(nextState)
250
+ hasState = true
251
+ return api as any
252
+ },
253
+ concurrent() {
254
+ exclusive = false
255
+ return api
256
+ },
257
+ withEventHandlers(handlers) {
258
+ createEventHandlers =
259
+ typeof handlers === 'function' ? (handlers as any) : () => handlers
260
+
261
+ return api
262
+ },
263
+ withAfterEffects(handlers) {
264
+ createAfterEffects =
265
+ typeof handlers === 'function' ? (handlers as any) : () => handlers
266
+
267
+ return api
268
+ },
269
+ setRelatedStreamEvents(generate) {
270
+ generateRelatedStreamEvents = generate
271
+ },
272
+ async beginRefresh() {
273
+ params.onRefresh?.begin?.(name)
274
+ await checkpointApi.clear()
275
+ lastEventPosition = -1
276
+ state = initialState
277
+ },
278
+ async progressRefresh(index, event, after) {
279
+ await apply(event, after, true)
280
+
281
+ if (
282
+ !checkpointApi.relatedStreamEvents ||
283
+ checkpointApi.relatedStreamEvents.some((streamEvent) =>
284
+ streamEvent.events.includes(event.type),
285
+ )
286
+ ) {
287
+ params.onRefresh?.progress?.(name, index, event)
288
+ }
289
+ },
290
+ endRefresh() {
291
+ params.onRefresh?.end?.(name)
292
+ resolveHydrated()
293
+ },
294
+ async refreshState() {
295
+ if (!idle) {
296
+ await refresh()
297
+ return
298
+ }
299
+ const { promise, resolve } = Promise.withResolvers<void>()
300
+ resolveRefresh = resolve
301
+ return promise
302
+ },
303
+ async state() {
304
+ await hydrated
305
+ return state
306
+ },
307
+ async init(catchUp) {
308
+ idle = false
309
+ const relatedStreamEvents = generateRelatedStreamEvents({
310
+ ...createEventHandlers(null),
311
+ ...createAfterEffects(null),
312
+ })
313
+
314
+ checkpointApi = createCheckpointApi({
315
+ ...params.checkpoint,
316
+ name,
317
+ relatedStreamEvents,
318
+ type: 'processManager',
319
+ loadEvents: params.loadEvents,
320
+ apply: (event) => apply(event, false, true),
321
+ applyAfter: (event) => apply(event, true, true),
322
+ })
323
+
324
+ if (resolveRefresh) {
325
+ await refresh()
326
+ return
327
+ }
328
+ if (!catchUp) {
329
+ return
330
+ }
331
+
332
+ await checkpointApi.rehydrated({
333
+ getMetadata() {
334
+ return { state }
335
+ },
336
+ onMetadata(metadata) {
337
+ state = metadata.state ?? state
338
+ },
339
+ })
340
+
341
+ resolveHydrated()
342
+ },
343
+ $inferState: null as never,
344
+ } as ProcessManagerApi & InternalProcessManager
345
+
346
+ async function refresh() {
347
+ await withRefreshing(async () => {
348
+ await api.beginRefresh()
349
+ await checkpointApi.rehydrated({
350
+ getMetadata() {
351
+ return { state }
352
+ },
353
+ onProgress(index, event) {
354
+ params.onRefresh?.progress?.(name, index, event)
355
+ },
356
+ })
357
+ })
358
+ api.endRefresh()
359
+ }
360
+
361
+ return api
362
+ }
363
+ }