@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,449 @@
1
+ import {
2
+ type SQL,
3
+ and,
4
+ asc,
5
+ desc,
6
+ eq,
7
+ gt,
8
+ inArray,
9
+ lte,
10
+ sql,
11
+ } from 'drizzle-orm'
12
+ import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
13
+
14
+ import type { AnyAggregateRoot } from '../createAggregateRoot.ts'
15
+ import {
16
+ ConcurrencyError,
17
+ createEventStore as createBaseEventStore,
18
+ isStreamEvents,
19
+ isStreamId,
20
+ } from '../index.ts'
21
+ import {
22
+ aggregateRootSnapshots,
23
+ checkpoints,
24
+ events,
25
+ projectionAppliedEvents,
26
+ projectionSnapshots,
27
+ } from './postgres.ts'
28
+ import type {
29
+ EventStore,
30
+ EventStoreParamsWithAggregateRootSnapshots,
31
+ EventsFromRoot,
32
+ OnProgress,
33
+ } from '../createEventStore.ts'
34
+ import { superjson } from '../superjson.ts'
35
+ import type {
36
+ BaseOutputEvent,
37
+ MaybePromise,
38
+ OutputEvent,
39
+ } from '../utilityTypes.js'
40
+
41
+ export { createAggregateRoot } from '../index.ts'
42
+ export { extendTypes } from '../superjson.ts'
43
+
44
+ export type OnEventsAppendend = (
45
+ events: BaseOutputEvent[],
46
+ ) => MaybePromise<void>
47
+
48
+ interface DrizzleEventStoreParams<Root extends AnyAggregateRoot> {
49
+ database: NodePgDatabase<any> | (() => NodePgDatabase<any>)
50
+ aggregateRoots: Root[]
51
+ autoInit?: boolean
52
+ postProcessEvent?: EventStoreParamsWithAggregateRootSnapshots<Root>['postProcessEvent']
53
+ onProjectionReplay?: OnProgress
54
+ onProcessManagerRefresh?: OnProgress
55
+ onEventsAppended?: OnEventsAppendend
56
+ }
57
+
58
+ function isPgUniqueViolation(
59
+ err: unknown,
60
+ ): err is { cause: { code: string; constraint?: string } } {
61
+ if (typeof err !== 'object' || err === null) {
62
+ return false
63
+ }
64
+
65
+ const cause = (err as any).cause
66
+ if (typeof cause !== 'object' || cause === null) {
67
+ return false
68
+ }
69
+
70
+ if (cause.code !== '23505') {
71
+ return false
72
+ }
73
+
74
+ return true
75
+ }
76
+
77
+ function createEventsQuery(database: NodePgDatabase<any>) {
78
+ return database
79
+ .select({
80
+ position: events.position,
81
+ type: events.type,
82
+ schemaVersion: events.schemaVersion,
83
+ payload: events.payload,
84
+ streamId: events.streamId,
85
+ streamType: events.streamType,
86
+ streamVersion: events.streamVersion,
87
+ createdAt: events.createdAt,
88
+ actorId: events.actorId,
89
+ correlationId: events.correlationId,
90
+ causationId: events.causationId,
91
+ metadata: events.metadata,
92
+ })
93
+ .from(events)
94
+ .orderBy(asc(events.position))
95
+ .$dynamic()
96
+ }
97
+
98
+ type EventsQuery = ReturnType<typeof createEventsQuery>
99
+
100
+ export async function fetchEvents<E extends OutputEvent<any> = BaseOutputEvent>(
101
+ database: NodePgDatabase<any>,
102
+ modifyQuery: (query: EventsQuery, table: typeof events) => EventsQuery = (
103
+ q,
104
+ ) => q,
105
+ ) {
106
+ const resolvedEvents = await modifyQuery(createEventsQuery(database), events)
107
+
108
+ return resolvedEvents.map((resolvedEvent) => ({
109
+ ...resolvedEvent,
110
+ payload: superjson.parse(resolvedEvent.payload),
111
+ metadata: superjson.deserialize(resolvedEvent.metadata),
112
+ })) as E[]
113
+ }
114
+
115
+ export function createEventStore<const Root extends AnyAggregateRoot>({
116
+ database,
117
+ aggregateRoots,
118
+ postProcessEvent,
119
+ onProjectionReplay,
120
+ onProcessManagerRefresh,
121
+ onEventsAppended,
122
+ autoInit,
123
+ }: DrizzleEventStoreParams<Root>): EventStore<
124
+ EventsFromRoot<Root>,
125
+ Root,
126
+ true
127
+ > {
128
+ const db = () => (typeof database === 'function' ? database() : database)
129
+
130
+ return createBaseEventStore({
131
+ aggregateRoots,
132
+ postProcessEvent,
133
+ onProjectionReplay,
134
+ onProcessManagerRefresh,
135
+ autoInit,
136
+ async *loadEvents(select, range) {
137
+ const conditions = [] as SQL[]
138
+
139
+ if (select) {
140
+ const selects = Array.isArray(select) ? select : [select]
141
+ const streamTypes = selects.map((select) => select.stream)
142
+ const streamIds = selects
143
+ .map((select) => (isStreamId(select) ? select.id : null))
144
+ .filter(Boolean) as string[]
145
+ const eventTypes = selects.flatMap((select) =>
146
+ isStreamEvents(select) ? select.events : [],
147
+ )
148
+ conditions.push(inArray(events.streamType, streamTypes))
149
+
150
+ if (streamIds.length > 0) {
151
+ conditions.push(inArray(events.streamId, streamIds))
152
+ }
153
+
154
+ if (eventTypes.length > 0) {
155
+ conditions.push(inArray(events.type, eventTypes))
156
+ }
157
+ }
158
+ let lastPosition = 0
159
+
160
+ if (range?.from) {
161
+ lastPosition = range.from - 1
162
+ }
163
+
164
+ if (range?.to) {
165
+ conditions.push(lte(events.position, range.to))
166
+ }
167
+
168
+ const chunkSize = 50
169
+
170
+ while (true) {
171
+ const resolvedEvents = await db()
172
+ .select({
173
+ position: events.position,
174
+ type: events.type,
175
+ schemaVersion: events.schemaVersion,
176
+ payload: events.payload,
177
+ streamId: events.streamId,
178
+ streamType: events.streamType,
179
+ streamVersion: events.streamVersion,
180
+ createdAt: events.createdAt,
181
+ actorId: events.actorId,
182
+ correlationId: events.correlationId,
183
+ causationId: events.causationId,
184
+ metadata: events.metadata,
185
+ })
186
+ .from(events)
187
+ .where(and(...conditions, gt(events.position, lastPosition)))
188
+ .limit(chunkSize)
189
+ .orderBy(asc(events.position))
190
+
191
+ if (resolvedEvents.length === 0) {
192
+ break
193
+ }
194
+
195
+ for (const resolvedEvent of resolvedEvents) {
196
+ yield {
197
+ ...resolvedEvent,
198
+ payload: superjson.parse(resolvedEvent.payload),
199
+ metadata: superjson.deserialize(resolvedEvent.metadata),
200
+ }
201
+ lastPosition = resolvedEvent.position
202
+ }
203
+ }
204
+ },
205
+ async appendEvents(streamId, newEvents, expectedVersion) {
206
+ const mappedEvents = newEvents.map((event, index) => ({
207
+ streamId: streamId.id,
208
+ streamType: streamId.stream,
209
+ streamVersion: expectedVersion + 1 + index,
210
+ type: event.type,
211
+ payload: superjson.stringify(event.payload),
212
+ metadata: superjson.serialize(event.metadata),
213
+ causationId: event.causationId,
214
+ actorId: event.actorId,
215
+ correlationId: event.correlationId,
216
+ schemaVersion: event.schemaVersion,
217
+ }))
218
+
219
+ try {
220
+ const persistedEvents = await db()
221
+ .insert(events)
222
+ .values(mappedEvents)
223
+ .returning()
224
+
225
+ const resolvedEvents = persistedEvents
226
+ .map((persistendEvent) => ({
227
+ ...persistendEvent,
228
+ payload: superjson.parse(persistendEvent.payload),
229
+ metadata: superjson.deserialize<
230
+ Record<string, unknown> | undefined
231
+ >(persistendEvent.metadata),
232
+ }))
233
+ .toSorted((eventA, eventB) => eventA.position - eventB.position)
234
+
235
+ onEventsAppended?.(resolvedEvents)
236
+
237
+ return resolvedEvents
238
+ } catch (error) {
239
+ if (isPgUniqueViolation(error)) {
240
+ const [lastStreamEvent] = await db()
241
+ .select({
242
+ streamVersion: events.streamVersion,
243
+ })
244
+ .from(events)
245
+ .where(
246
+ and(
247
+ eq(events.streamId, streamId.id),
248
+ eq(events.streamType, streamId.stream),
249
+ ),
250
+ )
251
+ .orderBy(desc(events.position))
252
+ .limit(1)
253
+
254
+ const currentVersion = lastStreamEvent?.streamVersion ?? 0
255
+ throw new ConcurrencyError(streamId, expectedVersion, currentVersion)
256
+ }
257
+ throw error
258
+ }
259
+ },
260
+ checkpoint: {
261
+ async upsert(checkpoint) {
262
+ await db()
263
+ .insert(checkpoints)
264
+ .values({
265
+ lastEventPosition: checkpoint.lastEventPosition,
266
+ name: checkpoint.name,
267
+ type: checkpoint.type,
268
+ metadata: superjson.stringify(checkpoint.metadata),
269
+ })
270
+ .onConflictDoUpdate({
271
+ target: [checkpoints.type, checkpoints.name],
272
+ set: {
273
+ lastEventPosition: checkpoint.lastEventPosition,
274
+ metadata: superjson.stringify(checkpoint.metadata),
275
+ },
276
+ })
277
+ },
278
+ async get(type, name) {
279
+ const [checkpoint] = await db()
280
+ .select({
281
+ type: checkpoints.type,
282
+ name: checkpoints.name,
283
+ lastEventPosition: checkpoints.lastEventPosition,
284
+ metadata: checkpoints.metadata,
285
+ })
286
+ .from(checkpoints)
287
+ .where(and(eq(checkpoints.type, type), eq(checkpoints.name, name)))
288
+
289
+ if (!checkpoint) {
290
+ return null
291
+ }
292
+
293
+ return {
294
+ ...checkpoint,
295
+ metadata: superjson.parse(checkpoint.metadata),
296
+ }
297
+ },
298
+ async delete(type, name) {
299
+ await db()
300
+ .delete(checkpoints)
301
+ .where(and(eq(checkpoints.type, type), eq(checkpoints.name, name)))
302
+ },
303
+ },
304
+
305
+ projectionSnapshot: {
306
+ async put(snapshot) {
307
+ await db()
308
+ .insert(projectionSnapshots)
309
+ .values({
310
+ lastEventPosition: snapshot.lastEventPosition,
311
+ projectionId: snapshot.projectionId,
312
+ projectionType: snapshot.projectionType,
313
+ schemaVersion: snapshot.schemaVersion,
314
+ state: superjson.stringify(snapshot.state),
315
+ metadata: superjson.stringify(snapshot.metadata),
316
+ })
317
+ },
318
+ async get(select) {
319
+ const [snapshot] = await db()
320
+ .select({
321
+ projectionId: projectionSnapshots.projectionId,
322
+ projectionType: projectionSnapshots.projectionType,
323
+ state: projectionSnapshots.state,
324
+ lastEventPosition: projectionSnapshots.lastEventPosition,
325
+ schemaVersion: projectionSnapshots.schemaVersion,
326
+ createdAt: projectionSnapshots.createdAt,
327
+ metadata: projectionSnapshots.metadata,
328
+ })
329
+ .from(projectionSnapshots)
330
+ .where(
331
+ and(
332
+ eq(projectionSnapshots.projectionId, select.id),
333
+ eq(projectionSnapshots.projectionType, select.projection),
334
+ ),
335
+ )
336
+ .orderBy(desc(projectionSnapshots.lastEventPosition))
337
+
338
+ if (!snapshot) {
339
+ return null
340
+ }
341
+
342
+ return {
343
+ ...snapshot,
344
+ state: superjson.parse(snapshot.state),
345
+ metadata: super.parse(snapshot.metadata),
346
+ }
347
+ },
348
+ async delete(target, fromPosition) {
349
+ const conditions = [
350
+ eq(projectionSnapshots.projectionType, target.projection),
351
+ ]
352
+
353
+ if (target.id) {
354
+ conditions.push(eq(projectionSnapshots.projectionId, target.id))
355
+ }
356
+
357
+ if (fromPosition) {
358
+ conditions.push(
359
+ gt(projectionSnapshots.lastEventPosition, fromPosition),
360
+ )
361
+ }
362
+
363
+ await db()
364
+ .delete(projectionSnapshots)
365
+ .where(and(...conditions))
366
+ },
367
+ async incrementAppliedCount(select) {
368
+ const [applied] = await db()
369
+ .insert(projectionAppliedEvents)
370
+ .values({
371
+ projectionId: select.id,
372
+ projectionType: select.projection,
373
+ appliedEvents: 1,
374
+ })
375
+ .onConflictDoUpdate({
376
+ target: [
377
+ projectionAppliedEvents.projectionId,
378
+ projectionAppliedEvents.projectionType,
379
+ ],
380
+ set: {
381
+ appliedEvents: sql`${projectionAppliedEvents.appliedEvents} + 1`,
382
+ },
383
+ })
384
+ .returning({
385
+ appliedEvents: projectionAppliedEvents.appliedEvents,
386
+ })
387
+
388
+ return applied.appliedEvents
389
+ },
390
+ },
391
+ aggregateRootSnapshot: {
392
+ async put(snapshot) {
393
+ await db()
394
+ .insert(aggregateRootSnapshots)
395
+ .values({
396
+ lastEventPosition: snapshot.lastEventPosition,
397
+ schemaVersion: snapshot.schemaVersion,
398
+ state: superjson.stringify(snapshot.state),
399
+ streamId: snapshot.streamId,
400
+ streamType: snapshot.streamType,
401
+ streamVersion: snapshot.streamVersion,
402
+ metadata: superjson.stringify(snapshot.metadata),
403
+ })
404
+ },
405
+ async get(select) {
406
+ const [snapshot] = await db()
407
+ .select({
408
+ streamId: aggregateRootSnapshots.streamId,
409
+ streamType: aggregateRootSnapshots.streamType,
410
+ streamVersion: aggregateRootSnapshots.streamVersion,
411
+ schemaVersion: aggregateRootSnapshots.schemaVersion,
412
+ state: aggregateRootSnapshots.state,
413
+ createdAt: aggregateRootSnapshots.createdAt,
414
+ lastEventPosition: aggregateRootSnapshots.lastEventPosition,
415
+ metadata: aggregateRootSnapshots.metadata,
416
+ })
417
+ .from(aggregateRootSnapshots)
418
+ .where(
419
+ and(
420
+ eq(aggregateRootSnapshots.streamId, select.id),
421
+ eq(aggregateRootSnapshots.streamType, select.stream),
422
+ ),
423
+ )
424
+ .orderBy(desc(aggregateRootSnapshots.streamVersion))
425
+
426
+ if (!snapshot) {
427
+ return null
428
+ }
429
+
430
+ return {
431
+ ...snapshot,
432
+ state: superjson.parse(snapshot.state),
433
+ metadata: superjson.parse(snapshot.metadata),
434
+ }
435
+ },
436
+ async delete(target, fromStreamVersion) {
437
+ await db()
438
+ .delete(aggregateRootSnapshots)
439
+ .where(
440
+ and(
441
+ eq(aggregateRootSnapshots.streamId, target.id),
442
+ eq(aggregateRootSnapshots.streamType, target.stream),
443
+ gt(aggregateRootSnapshots.streamVersion, fromStreamVersion),
444
+ ),
445
+ )
446
+ },
447
+ },
448
+ }) as any
449
+ }
@@ -0,0 +1,120 @@
1
+ import { sql } from 'drizzle-orm'
2
+ import {
3
+ bigint,
4
+ index,
5
+ integer,
6
+ jsonb,
7
+ pgPolicy,
8
+ pgSchema,
9
+ text,
10
+ timestamp,
11
+ unique,
12
+ uuid,
13
+ } from 'drizzle-orm/pg-core'
14
+ import type { SuperJSONResult } from 'superjson'
15
+
16
+ export const eventSourcing = pgSchema('event_sourcing')
17
+
18
+ export const events = eventSourcing.table(
19
+ 'events',
20
+ {
21
+ position: bigint({ mode: 'number' })
22
+ .generatedByDefaultAsIdentity()
23
+ .primaryKey(),
24
+ id: uuid().unique().notNull().defaultRandom(),
25
+ streamId: text().notNull(),
26
+ streamType: text().notNull(),
27
+ streamVersion: bigint({ mode: 'number' }).notNull(),
28
+ type: text().notNull(),
29
+ createdAt: timestamp().defaultNow().notNull(),
30
+ payload: text().notNull(),
31
+ metadata: jsonb().$type<SuperJSONResult>().notNull(),
32
+ causationId: uuid(),
33
+ correlationId: uuid(),
34
+ actorId: text(),
35
+ schemaVersion: integer().notNull(),
36
+ },
37
+ (table) => [
38
+ unique().on(table.streamId, table.streamVersion, table.streamType),
39
+ index().on(table.streamId, table.streamVersion.desc()),
40
+ index('metadata').using('gin', sql`${table.metadata} jsonb_path_ops`),
41
+ pgPolicy('insert only', {
42
+ for: 'insert',
43
+ to: 'public',
44
+ withCheck: sql`true`,
45
+ }),
46
+ ],
47
+ )
48
+
49
+ export const aggregateRootSnapshots = eventSourcing.table(
50
+ 'aggregate_root_snapshots',
51
+ {
52
+ id: uuid().primaryKey().defaultRandom(),
53
+ streamId: text().notNull(),
54
+ streamType: text().notNull(),
55
+ streamVersion: bigint({ mode: 'number' }).notNull(),
56
+ state: text().notNull(),
57
+ metadata: text().notNull(),
58
+ createdAt: timestamp().defaultNow().notNull(),
59
+ lastEventPosition: bigint({ mode: 'number' }).notNull(),
60
+ schemaVersion: integer().notNull(),
61
+ },
62
+ (table) => [
63
+ unique().on(table.streamType, table.streamId, table.streamVersion),
64
+ index().on(table.streamType, table.streamId, table.streamVersion.desc()),
65
+ ],
66
+ )
67
+
68
+ export const projectionSnapshots = eventSourcing.table(
69
+ 'projection_snapshots',
70
+ {
71
+ id: uuid().primaryKey().defaultRandom(),
72
+ projectionId: text().notNull(),
73
+ projectionType: text().notNull(),
74
+ state: text().notNull(),
75
+ metadata: text().notNull(),
76
+ createdAt: timestamp().defaultNow().notNull(),
77
+ lastEventPosition: bigint({ mode: 'number' }).notNull(),
78
+ schemaVersion: integer().notNull(),
79
+ },
80
+ (table) => [
81
+ unique().on(
82
+ table.projectionId,
83
+ table.projectionType,
84
+ table.lastEventPosition,
85
+ ),
86
+ index().on(
87
+ table.projectionId,
88
+ table.projectionType,
89
+ table.lastEventPosition.desc(),
90
+ ),
91
+ ],
92
+ )
93
+
94
+ export const checkpoints = eventSourcing.table(
95
+ 'checkpoints',
96
+ {
97
+ id: uuid().defaultRandom().primaryKey(),
98
+ type: text({ enum: ['projection', 'processManager'] }).notNull(),
99
+ name: text().notNull(),
100
+ lastEventPosition: bigint({ mode: 'number' }).notNull(),
101
+ metadata: text().notNull(),
102
+ },
103
+ (table) => [
104
+ unique().on(table.type, table.name),
105
+ index().on(table.type, table.name),
106
+ ],
107
+ )
108
+
109
+ export const projectionAppliedEvents = eventSourcing.table(
110
+ 'projection_applied_events',
111
+ {
112
+ projectionId: uuid().notNull(),
113
+ projectionType: text().notNull(),
114
+ appliedEvents: integer().notNull(),
115
+ },
116
+ (table) => [
117
+ unique().on(table.projectionId, table.projectionType),
118
+ index().on(table.projectionId, table.projectionType),
119
+ ],
120
+ )
package/src/errors.ts ADDED
@@ -0,0 +1,85 @@
1
+ import z from 'zod/v4'
2
+
3
+ import type { BaseOutputEvent, OutputEvent } from './utilityTypes.js'
4
+
5
+ export class ConcurrencyError extends Error {
6
+ constructor(
7
+ streamId: { stream: string; id: string },
8
+ expectedVersion: number,
9
+ actualVersion: number,
10
+ ) {
11
+ super(
12
+ `Concurrency error on ${streamId.stream}(${streamId.id}): expected v${expectedVersion}, actual v${actualVersion}`,
13
+ )
14
+ }
15
+ }
16
+
17
+ export class ValidationError extends Error {
18
+ constructor(
19
+ message: string,
20
+ public readonly zodError: z.ZodError,
21
+ ) {
22
+ super(`${message}: ${z.prettifyError(zodError)}`)
23
+ }
24
+ }
25
+
26
+ export class EventValidationError extends ValidationError {}
27
+
28
+ export class InfiniteLoopError extends Error {}
29
+
30
+ export class CommandError<T extends Error> extends Error {
31
+ constructor(
32
+ public readonly aggregateRootType: string,
33
+ public readonly commandName: string,
34
+ public readonly streamId: string,
35
+ public readonly originalError: T,
36
+ ) {
37
+ super(
38
+ `Error executing command "${commandName}" on aggregate root "${aggregateRootType}" for stream id "${streamId}": ${originalError.message}`,
39
+ )
40
+ }
41
+ }
42
+
43
+ export class ProjectionReplayLoopError extends Error {
44
+ constructor(
45
+ message: string,
46
+ public readonly projection: { name: string; eventHandler: string },
47
+ public readonly event: OutputEvent<any>,
48
+ ) {
49
+ super(message)
50
+ }
51
+ }
52
+
53
+ export class RefreshingSkipError extends Error {}
54
+
55
+ export class ReplaySkipError extends Error {}
56
+
57
+ export class ProjectionEventHandlerExecutionError<
58
+ T extends Error,
59
+ > extends Error {
60
+ constructor(
61
+ public readonly projection: string,
62
+ public readonly eventHandler: string,
63
+ public readonly event: BaseOutputEvent,
64
+ public readonly originalError: T,
65
+ ) {
66
+ super(
67
+ `Error execution event handler "${eventHandler}" in projection "${projection}": ${originalError.message}`,
68
+ )
69
+ }
70
+ }
71
+
72
+ export class ProcessManagerEventHandlerExecutionError<
73
+ T extends Error,
74
+ > extends Error {
75
+ constructor(
76
+ public readonly processManager: string,
77
+ public readonly eventHandler: string,
78
+ public readonly event: BaseOutputEvent,
79
+ public readonly originalError: T,
80
+ ) {
81
+ super(
82
+ `Error execution event handler "${eventHandler}" in process manager "${processManager}": ${originalError.message}`,
83
+ )
84
+ }
85
+ }
package/src/index.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type {
2
+ Stream,
3
+ StreamEvents,
4
+ StreamId,
5
+ StreamIdEvents,
6
+ } from './utilityTypes.d.ts'
7
+
8
+ export { createEventStore } from './createEventStore.ts'
9
+ export type { BaseOutputEvent as Event } from './utilityTypes.d.ts'
10
+ export {
11
+ type Snapshot as ProjectionSnapshot,
12
+ isInsideProjection,
13
+ getProjectionInfo,
14
+ } from './createProjection.ts'
15
+ export type { Checkpoint } from './createCheckpointApi.ts'
16
+ export {
17
+ isInsideProcessManager,
18
+ getProcessManagerInfo,
19
+ } from './createProcessManager.ts'
20
+ export {
21
+ isInsideEventListener,
22
+ getEventListenerInfo,
23
+ } from './createEventListener.ts'
24
+ export * from './errors.ts'
25
+ export {
26
+ type Snapshot as AggregateRootSnapshot,
27
+ type AnyAggregateRoot,
28
+ type AnyStream,
29
+ createAggregateRoot,
30
+ } from './createAggregateRoot.ts'
31
+
32
+ export { skipRefreshing } from './refresh.ts'
33
+ export { skipReplay } from './replay.ts'
34
+
35
+ export function isStreamId(
36
+ stream: Stream,
37
+ ): stream is StreamId | StreamIdEvents {
38
+ return 'id' in stream
39
+ }
40
+ export function isStreamEvents(
41
+ stream: Stream,
42
+ ): stream is StreamEvents | StreamIdEvents {
43
+ return 'events' in stream && Array.isArray(stream.events)
44
+ }
45
+ export type { WrappedEvent } from './wrappedEventBuilder.ts'