@requence/event-sourcing 1.4.0 → 1.5.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 (65) hide show
  1. package/README.md +6 -5
  2. package/dist/{chunk-yn2sjcg3.js → chunk-3251p253.js} +50 -1
  3. package/dist/chunk-3251p253.js.map +10 -0
  4. package/dist/{chunk-vep2byx4.js → chunk-7akf01zj.js} +11 -11
  5. package/dist/{chunk-vep2byx4.js.map → chunk-7akf01zj.js.map} +3 -3
  6. package/dist/{chunk-57b88rt5.js → chunk-8edc30gz.js} +11 -11
  7. package/dist/{chunk-57b88rt5.js.map → chunk-8edc30gz.js.map} +1 -1
  8. package/dist/chunk-ak901ckt.js +67 -0
  9. package/dist/chunk-ak901ckt.js.map +10 -0
  10. package/dist/{chunk-vvv14qxy.js → chunk-kqh3q3fj.js} +10 -8
  11. package/dist/{chunk-vvv14qxy.js.map → chunk-kqh3q3fj.js.map} +3 -3
  12. package/dist/{chunk-3ejt4h7x.js → chunk-zznvzvnv.js} +58 -46
  13. package/dist/chunk-zznvzvnv.js.map +10 -0
  14. package/dist/createAggregateRoot.d.ts.map +1 -1
  15. package/dist/createAggregateRoot.js +7 -7
  16. package/dist/createCheckpointApi.d.ts +30 -0
  17. package/dist/createCheckpointApi.d.ts.map +1 -1
  18. package/dist/createCheckpointApi.js +1 -1
  19. package/dist/createEventStore.js +8 -8
  20. package/dist/createProcessManager.d.ts.map +1 -1
  21. package/dist/createProcessManager.js +3 -3
  22. package/dist/createProjection.d.ts.map +1 -1
  23. package/dist/createProjection.js +2 -2
  24. package/dist/drizzle/index.js +15 -15
  25. package/dist/drizzle/index.js.map +3 -3
  26. package/dist/index.js +18 -18
  27. package/dist/index.js.map +1 -1
  28. package/dist/memory/index.d.ts +14 -3
  29. package/dist/memory/index.d.ts.map +1 -1
  30. package/dist/memory/index.js +37 -30
  31. package/dist/memory/index.js.map +3 -3
  32. package/dist/memory/index.spec.d.ts +2 -0
  33. package/dist/memory/index.spec.d.ts.map +1 -0
  34. package/dist/surreal/index.d.ts +35 -0
  35. package/dist/surreal/index.d.ts.map +1 -0
  36. package/dist/surreal/index.js +415 -0
  37. package/dist/surreal/index.js.map +10 -0
  38. package/dist/surreal/index.spec.d.ts +2 -0
  39. package/dist/surreal/index.spec.d.ts.map +1 -0
  40. package/dist/surreal/schema.d.ts +12 -0
  41. package/dist/surreal/schema.d.ts.map +1 -0
  42. package/dist/surreal/schema.js +12 -0
  43. package/dist/surreal/schema.js.map +9 -0
  44. package/dist/tests/projectionMultiInstance.spec.d.ts +2 -0
  45. package/dist/tests/projectionMultiInstance.spec.d.ts.map +1 -0
  46. package/dist/tests/storageDriverContract.d.ts +19 -0
  47. package/dist/tests/storageDriverContract.d.ts.map +1 -0
  48. package/package.json +19 -10
  49. package/src/createAggregateRoot.ts +2 -3
  50. package/src/createCheckpointApi.ts +98 -0
  51. package/src/createProcessManager.ts +7 -6
  52. package/src/createProjection.ts +91 -65
  53. package/src/drizzle/index.ts +1 -1
  54. package/src/memory/index.spec.ts +4 -0
  55. package/src/memory/index.ts +55 -29
  56. package/src/surreal/index.spec.ts +152 -0
  57. package/src/surreal/index.ts +604 -0
  58. package/src/surreal/schema.ts +68 -0
  59. package/src/tests/aggregateRoot.spec.ts +6 -3
  60. package/src/tests/eventStore.spec.ts +1 -3
  61. package/src/tests/processManagerMultiInstance.spec.ts +18 -7
  62. package/src/tests/projectionMultiInstance.spec.ts +283 -0
  63. package/src/tests/storageDriverContract.ts +612 -0
  64. package/dist/chunk-3ejt4h7x.js.map +0 -10
  65. package/dist/chunk-yn2sjcg3.js.map +0 -10
@@ -0,0 +1,612 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+
3
+ import type {
4
+ Snapshot as AggregateRootSnapshot,
5
+ AggregateRootSnapshotMethods,
6
+ } from '../createAggregateRoot.ts'
7
+ import type { Checkpoint, CheckpointMethods } from '../createCheckpointApi.ts'
8
+ import type { LoadEvents } from '../createEventStore.ts'
9
+ import type {
10
+ Snapshot as ProjectionSnapshot,
11
+ ProjectionSnapshotMethods,
12
+ } from '../createProjection.ts'
13
+ import { ConcurrencyError } from '../errors.ts'
14
+ import type {
15
+ BaseInputEvent,
16
+ BaseOutputEvent,
17
+ MaybePromise,
18
+ StreamId,
19
+ } from '../utilityTypes.ts'
20
+
21
+ export type StorageDriver = {
22
+ loadEvents: LoadEvents
23
+ appendEvents: (
24
+ streamId: StreamId,
25
+ events: BaseInputEvent[],
26
+ expectedVersion: number,
27
+ ) => MaybePromise<BaseOutputEvent[]>
28
+ checkpoint: CheckpointMethods
29
+ projectionSnapshot: ProjectionSnapshotMethods
30
+ aggregateRootSnapshot: AggregateRootSnapshotMethods
31
+ }
32
+
33
+ function inputEvent(
34
+ type: string,
35
+ payload: unknown,
36
+ overrides?: Partial<BaseInputEvent>,
37
+ ): BaseInputEvent {
38
+ return {
39
+ type,
40
+ payload,
41
+ metadata: {},
42
+ schemaVersion: 1,
43
+ ...overrides,
44
+ } as BaseInputEvent
45
+ }
46
+
47
+ // Works for drivers that throw synchronously (memory) and asynchronously.
48
+ async function expectConcurrencyError(run: () => MaybePromise<unknown>) {
49
+ let error: unknown
50
+ try {
51
+ await run()
52
+ } catch (err) {
53
+ error = err
54
+ }
55
+ expect(error).toBeInstanceOf(ConcurrencyError)
56
+ }
57
+
58
+ async function collect(events: AsyncGenerator<BaseOutputEvent>) {
59
+ const resolved: BaseOutputEvent[] = []
60
+ for await (const event of events) {
61
+ resolved.push(event)
62
+ }
63
+ return resolved
64
+ }
65
+
66
+ function checkpointFixture(overrides?: Partial<Checkpoint>): Checkpoint {
67
+ return {
68
+ type: 'projection',
69
+ name: 'test',
70
+ lastEventPosition: 1,
71
+ metadata: {},
72
+ version: 1,
73
+ ...overrides,
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Reusable contract suite exercising the raw storage methods every driver
79
+ * must supply to the core `createEventStore`. Call it once per driver with a
80
+ * factory producing a fresh, empty storage instance.
81
+ */
82
+ export function runStorageDriverContract(
83
+ name: string,
84
+ makeStorage: () => MaybePromise<StorageDriver>,
85
+ ) {
86
+ describe(`storage driver contract: ${name}`, () => {
87
+ describe('appendEvents', () => {
88
+ it('persists events with stream versions, positions and createdAt', async () => {
89
+ const storage = await makeStorage()
90
+ const persisted = await storage.appendEvents(
91
+ { stream: 'order', id: 'o1' },
92
+ [
93
+ inputEvent('created', { total: 1 }),
94
+ inputEvent('updated', { total: 2 }),
95
+ ],
96
+ 0,
97
+ )
98
+
99
+ expect(persisted).toHaveLength(2)
100
+ expect(persisted[0].streamVersion).toBe(1)
101
+ expect(persisted[1].streamVersion).toBe(2)
102
+ expect(persisted[0].streamId).toBe('o1')
103
+ expect(persisted[0].streamType).toBe('order')
104
+ expect(persisted[1].position).toBeGreaterThan(persisted[0].position)
105
+ expect(persisted[0].createdAt).toBeInstanceOf(Date)
106
+ expect(persisted[0].payload).toEqual({ total: 1 })
107
+ })
108
+
109
+ it('continues stream versions across appends', async () => {
110
+ const storage = await makeStorage()
111
+ await storage.appendEvents(
112
+ { stream: 'order', id: 'o1' },
113
+ [inputEvent('created', {})],
114
+ 0,
115
+ )
116
+ const second = await storage.appendEvents(
117
+ { stream: 'order', id: 'o1' },
118
+ [inputEvent('updated', {})],
119
+ 1,
120
+ )
121
+
122
+ expect(second[0].streamVersion).toBe(2)
123
+ })
124
+
125
+ it('throws ConcurrencyError when the expected version is stale', async () => {
126
+ const storage = await makeStorage()
127
+ await storage.appendEvents(
128
+ { stream: 'order', id: 'o1' },
129
+ [inputEvent('created', {})],
130
+ 0,
131
+ )
132
+
133
+ await expectConcurrencyError(() =>
134
+ storage.appendEvents(
135
+ { stream: 'order', id: 'o1' },
136
+ [inputEvent('updated', {})],
137
+ 0,
138
+ ),
139
+ )
140
+ })
141
+
142
+ it('throws ConcurrencyError when expecting events on a fresh stream', async () => {
143
+ const storage = await makeStorage()
144
+
145
+ await expectConcurrencyError(() =>
146
+ storage.appendEvents(
147
+ { stream: 'order', id: 'missing' },
148
+ [inputEvent('updated', {})],
149
+ 3,
150
+ ),
151
+ )
152
+ })
153
+
154
+ it('tracks versions per stream independently', async () => {
155
+ const storage = await makeStorage()
156
+ await storage.appendEvents(
157
+ { stream: 'order', id: 'o1' },
158
+ [inputEvent('created', {})],
159
+ 0,
160
+ )
161
+ const other = await storage.appendEvents(
162
+ { stream: 'order', id: 'o2' },
163
+ [inputEvent('created', {})],
164
+ 0,
165
+ )
166
+
167
+ expect(other[0].streamVersion).toBe(1)
168
+ })
169
+
170
+ it('round-trips complex payloads and metadata through superjson types', async () => {
171
+ const storage = await makeStorage()
172
+ const createdAt = new Date('2024-05-04T03:02:01.000Z')
173
+ const payload = {
174
+ when: createdAt,
175
+ tags: new Map([['a', 1]]),
176
+ missing: undefined,
177
+ nested: { deep: [1, 2, 3] },
178
+ }
179
+
180
+ await storage.appendEvents(
181
+ { stream: 'order', id: 'o1' },
182
+ [inputEvent('created', payload, { metadata: { at: createdAt } })],
183
+ 0,
184
+ )
185
+
186
+ const [event] = await collect(
187
+ storage.loadEvents({ stream: 'order', id: 'o1' }),
188
+ )
189
+ expect(event.payload).toEqual(payload)
190
+ expect((event.metadata as any).at).toEqual(createdAt)
191
+ })
192
+ })
193
+
194
+ describe('loadEvents', () => {
195
+ it('yields all events globally ordered by position', async () => {
196
+ const storage = await makeStorage()
197
+ await storage.appendEvents(
198
+ { stream: 'order', id: 'o1' },
199
+ [inputEvent('created', { n: 1 })],
200
+ 0,
201
+ )
202
+ await storage.appendEvents(
203
+ { stream: 'invoice', id: 'i1' },
204
+ [inputEvent('issued', { n: 2 })],
205
+ 0,
206
+ )
207
+ await storage.appendEvents(
208
+ { stream: 'order', id: 'o1' },
209
+ [inputEvent('updated', { n: 3 })],
210
+ 1,
211
+ )
212
+
213
+ const events = await collect(storage.loadEvents(null))
214
+ expect(events).toHaveLength(3)
215
+ expect(events.map((event) => (event.payload as any).n)).toEqual([
216
+ 1, 2, 3,
217
+ ])
218
+ for (let index = 1; index < events.length; index++) {
219
+ expect(events[index].position).toBeGreaterThan(
220
+ events[index - 1].position,
221
+ )
222
+ }
223
+ })
224
+
225
+ it('paginates beyond the chunk size without gaps or duplicates', async () => {
226
+ const storage = await makeStorage()
227
+ const total = 120
228
+ for (let index = 0; index < total; index += 20) {
229
+ await storage.appendEvents(
230
+ { stream: 'order', id: 'o1' },
231
+ Array.from({ length: 20 }, (_, offset) =>
232
+ inputEvent('created', { n: index + offset }),
233
+ ),
234
+ index,
235
+ )
236
+ }
237
+
238
+ const events = await collect(storage.loadEvents(null))
239
+ expect(events).toHaveLength(total)
240
+ expect(events.map((event) => (event.payload as any).n)).toEqual(
241
+ Array.from({ length: total }, (_, n) => n),
242
+ )
243
+ })
244
+
245
+ it('filters by stream type', async () => {
246
+ const storage = await makeStorage()
247
+ await storage.appendEvents(
248
+ { stream: 'order', id: 'o1' },
249
+ [inputEvent('created', {})],
250
+ 0,
251
+ )
252
+ await storage.appendEvents(
253
+ { stream: 'invoice', id: 'i1' },
254
+ [inputEvent('issued', {})],
255
+ 0,
256
+ )
257
+
258
+ const events = await collect(storage.loadEvents({ stream: 'order' }))
259
+ expect(events).toHaveLength(1)
260
+ expect(events[0].streamType).toBe('order')
261
+ })
262
+
263
+ it('filters by stream type and id', async () => {
264
+ const storage = await makeStorage()
265
+ await storage.appendEvents(
266
+ { stream: 'order', id: 'o1' },
267
+ [inputEvent('created', {})],
268
+ 0,
269
+ )
270
+ await storage.appendEvents(
271
+ { stream: 'order', id: 'o2' },
272
+ [inputEvent('created', {})],
273
+ 0,
274
+ )
275
+
276
+ const events = await collect(
277
+ storage.loadEvents({ stream: 'order', id: 'o2' }),
278
+ )
279
+ expect(events).toHaveLength(1)
280
+ expect(events[0].streamId).toBe('o2')
281
+ })
282
+
283
+ it('filters by stream events', async () => {
284
+ const storage = await makeStorage()
285
+ await storage.appendEvents(
286
+ { stream: 'order', id: 'o1' },
287
+ [
288
+ inputEvent('created', {}),
289
+ inputEvent('updated', {}),
290
+ inputEvent('shipped', {}),
291
+ ],
292
+ 0,
293
+ )
294
+
295
+ const events = await collect(
296
+ storage.loadEvents({
297
+ stream: 'order',
298
+ events: ['created', 'shipped'],
299
+ }),
300
+ )
301
+ expect(events.map((event) => event.type)).toEqual([
302
+ 'created',
303
+ 'shipped',
304
+ ])
305
+ })
306
+
307
+ it('honors range.from and range.to inclusively', async () => {
308
+ const storage = await makeStorage()
309
+ const persisted = await storage.appendEvents(
310
+ { stream: 'order', id: 'o1' },
311
+ Array.from({ length: 5 }, (_, n) => inputEvent('created', { n })),
312
+ 0,
313
+ )
314
+
315
+ const from = persisted[1].position
316
+ const to = persisted[3].position
317
+ const events = await collect(storage.loadEvents(null, { from, to }))
318
+
319
+ expect(events.map((event) => event.position)).toEqual([
320
+ persisted[1].position,
321
+ persisted[2].position,
322
+ persisted[3].position,
323
+ ])
324
+ })
325
+ })
326
+
327
+ describe('checkpoint', () => {
328
+ it('inserts with a null expected version and reads it back', async () => {
329
+ const storage = await makeStorage()
330
+ const checkpoint = checkpointFixture({ metadata: { cursor: 'abc' } })
331
+
332
+ expect(await storage.checkpoint.upsert(checkpoint, null)).toBe(true)
333
+
334
+ const stored = await storage.checkpoint.get('projection', 'test')
335
+ expect(stored).toBeTruthy()
336
+ expect(stored!.lastEventPosition).toBe(1)
337
+ expect(stored!.version).toBe(1)
338
+ expect(stored!.metadata).toEqual({ cursor: 'abc' })
339
+ })
340
+
341
+ it('rejects an insert-only upsert when the checkpoint already exists', async () => {
342
+ const storage = await makeStorage()
343
+ await storage.checkpoint.upsert(checkpointFixture(), null)
344
+
345
+ expect(await storage.checkpoint.upsert(checkpointFixture(), null)).toBe(
346
+ false,
347
+ )
348
+ })
349
+
350
+ it('applies the write when the expected version matches', async () => {
351
+ const storage = await makeStorage()
352
+ await storage.checkpoint.upsert(checkpointFixture({ version: 1 }), null)
353
+
354
+ expect(
355
+ await storage.checkpoint.upsert(
356
+ checkpointFixture({ lastEventPosition: 9, version: 2 }),
357
+ 1,
358
+ ),
359
+ ).toBe(true)
360
+
361
+ const stored = await storage.checkpoint.get('projection', 'test')
362
+ expect(stored!.lastEventPosition).toBe(9)
363
+ expect(stored!.version).toBe(2)
364
+ })
365
+
366
+ it('rejects the write when the expected version is stale', async () => {
367
+ const storage = await makeStorage()
368
+ await storage.checkpoint.upsert(checkpointFixture({ version: 2 }), null)
369
+
370
+ expect(
371
+ await storage.checkpoint.upsert(
372
+ checkpointFixture({ lastEventPosition: 9, version: 3 }),
373
+ 1,
374
+ ),
375
+ ).toBe(false)
376
+
377
+ const stored = await storage.checkpoint.get('projection', 'test')
378
+ expect(stored!.lastEventPosition).toBe(1)
379
+ expect(stored!.version).toBe(2)
380
+ })
381
+
382
+ it('inserts when expecting a version on a missing checkpoint', async () => {
383
+ const storage = await makeStorage()
384
+
385
+ expect(
386
+ await storage.checkpoint.upsert(checkpointFixture({ version: 5 }), 4),
387
+ ).toBe(true)
388
+
389
+ const stored = await storage.checkpoint.get('projection', 'test')
390
+ expect(stored!.version).toBe(5)
391
+ })
392
+
393
+ it('keeps checkpoints of different types and names separate', async () => {
394
+ const storage = await makeStorage()
395
+ await storage.checkpoint.upsert(
396
+ checkpointFixture({ type: 'projection', lastEventPosition: 1 }),
397
+ null,
398
+ )
399
+ await storage.checkpoint.upsert(
400
+ checkpointFixture({ type: 'processManager', lastEventPosition: 2 }),
401
+ null,
402
+ )
403
+
404
+ const projection = await storage.checkpoint.get('projection', 'test')
405
+ const processManager = await storage.checkpoint.get(
406
+ 'processManager',
407
+ 'test',
408
+ )
409
+ expect(projection!.lastEventPosition).toBe(1)
410
+ expect(processManager!.lastEventPosition).toBe(2)
411
+ expect(await storage.checkpoint.get('projection', 'other')).toBeFalsy()
412
+ })
413
+
414
+ it('deletes a checkpoint', async () => {
415
+ const storage = await makeStorage()
416
+ await storage.checkpoint.upsert(checkpointFixture(), null)
417
+ await storage.checkpoint.delete('projection', 'test')
418
+
419
+ expect(await storage.checkpoint.get('projection', 'test')).toBeFalsy()
420
+ })
421
+ })
422
+
423
+ describe('projectionSnapshot', () => {
424
+ const snapshotFixture = (
425
+ overrides?: Partial<ProjectionSnapshot>,
426
+ ): ProjectionSnapshot => ({
427
+ projectionId: 'p1',
428
+ projectionType: 'orders',
429
+ lastEventPosition: 1,
430
+ state: { count: 1 },
431
+ metadata: { origin: 'test' },
432
+ schemaVersion: 1,
433
+ createdAt: new Date(),
434
+ ...overrides,
435
+ })
436
+
437
+ it('returns the snapshot with the highest event position', async () => {
438
+ const storage = await makeStorage()
439
+ await storage.projectionSnapshot.put(snapshotFixture())
440
+ await storage.projectionSnapshot.put(
441
+ snapshotFixture({ lastEventPosition: 7, state: { count: 7 } }),
442
+ )
443
+
444
+ const snapshot = await storage.projectionSnapshot.get({
445
+ projection: 'orders',
446
+ id: 'p1',
447
+ })
448
+ expect(snapshot!.lastEventPosition).toBe(7)
449
+ expect(snapshot!.state).toEqual({ count: 7 })
450
+ expect(snapshot!.metadata).toEqual({ origin: 'test' })
451
+ expect(snapshot!.createdAt).toBeInstanceOf(Date)
452
+ })
453
+
454
+ it('returns nothing for unknown projections', async () => {
455
+ const storage = await makeStorage()
456
+
457
+ expect(
458
+ await storage.projectionSnapshot.get({
459
+ projection: 'orders',
460
+ id: 'missing',
461
+ }),
462
+ ).toBeFalsy()
463
+ })
464
+
465
+ it('deletes snapshots above a position', async () => {
466
+ const storage = await makeStorage()
467
+ await storage.projectionSnapshot.put(
468
+ snapshotFixture({ lastEventPosition: 2 }),
469
+ )
470
+ await storage.projectionSnapshot.put(
471
+ snapshotFixture({ lastEventPosition: 9 }),
472
+ )
473
+
474
+ await storage.projectionSnapshot.delete(
475
+ { projection: 'orders', id: 'p1' },
476
+ 5,
477
+ )
478
+
479
+ const snapshot = await storage.projectionSnapshot.get({
480
+ projection: 'orders',
481
+ id: 'p1',
482
+ })
483
+ expect(snapshot!.lastEventPosition).toBe(2)
484
+ })
485
+
486
+ it('deletes all snapshots of a projection', async () => {
487
+ const storage = await makeStorage()
488
+ await storage.projectionSnapshot.put(snapshotFixture())
489
+ await storage.projectionSnapshot.put(
490
+ snapshotFixture({ projectionId: 'p2' }),
491
+ )
492
+
493
+ await storage.projectionSnapshot.delete({ projection: 'orders' })
494
+
495
+ expect(
496
+ await storage.projectionSnapshot.get({
497
+ projection: 'orders',
498
+ id: 'p1',
499
+ }),
500
+ ).toBeFalsy()
501
+ expect(
502
+ await storage.projectionSnapshot.get({
503
+ projection: 'orders',
504
+ id: 'p2',
505
+ }),
506
+ ).toBeFalsy()
507
+ })
508
+
509
+ it('increments applied counts per projection instance', async () => {
510
+ const storage = await makeStorage()
511
+ const target = { projection: 'orders', id: 'p1' }
512
+
513
+ expect(
514
+ await storage.projectionSnapshot.incrementAppliedCount(target),
515
+ ).toBe(1)
516
+ expect(
517
+ await storage.projectionSnapshot.incrementAppliedCount(target),
518
+ ).toBe(2)
519
+ expect(
520
+ await storage.projectionSnapshot.incrementAppliedCount({
521
+ projection: 'orders',
522
+ id: 'p2',
523
+ }),
524
+ ).toBe(1)
525
+ })
526
+ })
527
+
528
+ describe('aggregateRootSnapshot', () => {
529
+ const snapshotFixture = (
530
+ overrides?: Partial<AggregateRootSnapshot>,
531
+ ): AggregateRootSnapshot => ({
532
+ streamId: 's1',
533
+ streamType: 'order',
534
+ streamVersion: 1,
535
+ lastEventPosition: 1,
536
+ state: { total: 1 },
537
+ metadata: { origin: 'test' },
538
+ schemaVersion: 1,
539
+ createdAt: new Date(),
540
+ ...overrides,
541
+ })
542
+
543
+ it('returns the snapshot with the highest stream version', async () => {
544
+ const storage = await makeStorage()
545
+ await storage.aggregateRootSnapshot.put(snapshotFixture())
546
+ await storage.aggregateRootSnapshot.put(
547
+ snapshotFixture({
548
+ streamVersion: 4,
549
+ lastEventPosition: 9,
550
+ state: { total: 4 },
551
+ }),
552
+ )
553
+
554
+ const snapshot = await storage.aggregateRootSnapshot.get({
555
+ stream: 'order',
556
+ id: 's1',
557
+ })
558
+ expect(snapshot!.streamVersion).toBe(4)
559
+ expect(snapshot!.state).toEqual({ total: 4 })
560
+ expect(snapshot!.createdAt).toBeInstanceOf(Date)
561
+ })
562
+
563
+ it('returns nothing for unknown streams', async () => {
564
+ const storage = await makeStorage()
565
+
566
+ expect(
567
+ await storage.aggregateRootSnapshot.get({
568
+ stream: 'order',
569
+ id: 'missing',
570
+ }),
571
+ ).toBeFalsy()
572
+ })
573
+
574
+ it('deletes snapshots above a stream version', async () => {
575
+ const storage = await makeStorage()
576
+ await storage.aggregateRootSnapshot.put(
577
+ snapshotFixture({ streamVersion: 4 }),
578
+ )
579
+
580
+ await storage.aggregateRootSnapshot.delete(
581
+ { stream: 'order', id: 's1' },
582
+ 2,
583
+ )
584
+
585
+ expect(
586
+ await storage.aggregateRootSnapshot.get({
587
+ stream: 'order',
588
+ id: 's1',
589
+ }),
590
+ ).toBeFalsy()
591
+ })
592
+
593
+ it('keeps snapshots at or below the deletion version', async () => {
594
+ const storage = await makeStorage()
595
+ await storage.aggregateRootSnapshot.put(
596
+ snapshotFixture({ streamVersion: 2 }),
597
+ )
598
+
599
+ await storage.aggregateRootSnapshot.delete(
600
+ { stream: 'order', id: 's1' },
601
+ 2,
602
+ )
603
+
604
+ const snapshot = await storage.aggregateRootSnapshot.get({
605
+ stream: 'order',
606
+ id: 's1',
607
+ })
608
+ expect(snapshot!.streamVersion).toBe(2)
609
+ })
610
+ })
611
+ })
612
+ }
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/createProjection.ts"],
4
- "sourcesContent": [
5
- "import { AsyncLocalStorage } from 'node:async_hooks'\n\nimport z from 'zod/v4'\n\nimport {\n type CheckpointApi,\n type CheckpointMethods,\n createCheckpointApi,\n} from './createCheckpointApi.ts'\nimport type { LoadEvents, OnProgress } from './createEventStore.ts'\nimport {\n ProjectionEventHandlerExecutionError,\n ProjectionReplayLoopError,\n ReplaySkipError,\n ValidationError,\n} from './errors.ts'\nimport { isReplaying, withReplaying } from './replay.ts'\nimport type {\n CompleteSnapshotGenerator,\n InternalSnapshotGenerator,\n SnapshotGenerator,\n} from './snapshotGenerator.ts'\nimport createSnapshotGenerator from './snapshotGenerator.ts'\nimport type {\n AllEvents,\n BaseOutputEvent,\n EventHandlers,\n EventTemplate,\n Keys,\n MaybePromise,\n Merge,\n OutputEvent,\n StreamEvents,\n} from './utilityTypes.d.ts'\n\nconst hydratingStore = new AsyncLocalStorage<boolean>()\n\nfunction isHydrating() {\n return hydratingStore.getStore() ?? false\n}\n\nasync function withHydrating(handler: () => Promise<void>) {\n await hydratingStore.run(true, handler)\n}\n\ntype ProjectionInfo = {\n name: string\n event: BaseOutputEvent\n}\nconst projectionInfoStore = new AsyncLocalStorage<ProjectionInfo>()\n\nfunction withProjectionInfo(\n info: ProjectionInfo,\n handler: () => Promise<void>,\n) {\n return projectionInfoStore.run(info, handler)\n}\n\nexport function getProjectionInfo() {\n return projectionInfoStore.getStore()\n}\n\nexport function isInsideProjection() {\n return Boolean(getProjectionInfo())\n}\n\ntype ReplayConfig = {\n deleteOne?: (id: string) => MaybePromise<void>\n deleteAll?: () => MaybePromise<void>\n}\n\ntype ProjectionEventHandlers<Events extends EventTemplate> = EventHandlers<\n Events,\n {\n replaying: boolean\n replayUntil: (\n position: number,\n filterEvents?: (event: OutputEvent<Events>) => boolean,\n ) => Promise<void>\n }\n>\n\ntype ReplayProjection<Events extends EventTemplate> = {\n replayOne: (streamId: string) => Promise<void>\n replayUntil: (\n streamId: string,\n position: number,\n filterEvents?: (event: OutputEvent<Events>) => boolean,\n ) => Promise<void>\n replay: () => Promise<void>\n}\n\nexport type InternalProjection = {\n apply: (event: BaseOutputEvent) => Promise<void>\n setRelatedStreamEvents: (\n generateRelatedStreamEvents: (\n eventHandlers: EventHandlers<EventTemplate>,\n ) => StreamEvents[],\n ) => void\n isReady(): Promise<void>\n canReplay(): boolean\n beginReplay(): Promise<void>\n progressReplay(index: number, event: BaseOutputEvent): Promise<void>\n endReplay(): void\n init(catchUp: boolean): Promise<void>\n}\n\nexport type ProjectionApi<\n Events extends EventTemplate = never,\n RConfig extends ReplayConfig = never,\n Flags extends {\n withEventHandlers: boolean\n withGlobalEventHandler: boolean\n concurrent: boolean\n withReplay: boolean\n withSnapshots: boolean\n } = {\n withEventHandlers: false\n withGlobalEventHandler: false\n concurrent: false\n withReplay: false\n withSnapshots: true\n },\n> = {\n withEventHandlers: (\n handlers: ProjectionEventHandlers<Events>,\n ) => Omit<\n ProjectionApi<Events, RConfig, Merge<Flags, { withEventHandlers: true }>>,\n Keys<Merge<Flags, { withEventHandlers: true }>>\n >\n withGlobalEventHandler: (\n handler: (\n event: AllEvents<Events>,\n methods: ReplayProjection<Events>,\n ) => MaybePromise<void>,\n ) => Omit<\n ProjectionApi<\n Events,\n RConfig,\n Merge<Flags, { withGlobalEventHandler: true }>\n >,\n Keys<Merge<Flags, { withGlobalEventHandler: true }>>\n >\n concurrent: () => Omit<\n ProjectionApi<Events, RConfig, Merge<Flags, { concurrent: true }>>,\n Keys<Merge<Flags, { concurrent: true }>>\n >\n withReplay: <Config extends ReplayConfig>(\n config: Config,\n ) => Omit<\n ProjectionApi<Events, Config, Merge<Flags, { withReplay: true }>>,\n Keys<Merge<Flags, { withReplay: true }>>\n >\n withSnapshots: (\n handler: (\n generator: SnapshotGenerator<Omit<Snapshot, 'schemaVersion' | 'state'>>,\n ) => CompleteSnapshotGenerator,\n ) => Omit<\n ProjectionApi<Events, RConfig, Merge<Flags, { withSnapshots: true }>>,\n Keys<Merge<Flags, { withSnapshots: true }>>\n >\n isReady(): Promise<void>\n} & ('deleteOne' extends keyof RConfig\n ? Pick<ReplayProjection<Events>, 'replayOne' | 'replayUntil'>\n : {}) &\n ('deleteAll' extends keyof RConfig\n ? Pick<ReplayProjection<Events>, 'replay'>\n : {})\n\nexport type ProjectionApiWithSnapshots<\n Events extends EventTemplate = never,\n RConfig extends ReplayConfig = never,\n Flags extends {\n withEventHandlers: boolean\n withGlobalEventHandler: boolean\n concurrent: boolean\n withReplay: boolean\n withSnapshots: boolean\n } = {\n withEventHandlers: false\n withGlobalEventHandler: false\n concurrent: false\n withReplay: false\n withSnapshots: false\n },\n> = ProjectionApi<Events, RConfig, Flags>\n\nconst snapshotSchema = z.object({\n projectionId: z.string(),\n projectionType: z.string(),\n lastEventPosition: z.int(),\n state: z.unknown(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n schemaVersion: z.int(),\n createdAt: z.date(),\n})\n\nexport type Snapshot = z.output<typeof snapshotSchema>\n\nexport type ProjectionSnapshotMethods = {\n get: (select: {\n projection: string\n id: string\n }) => MaybePromise<Snapshot | null | undefined>\n put: (snapshot: Snapshot) => MaybePromise<void>\n delete: (\n target: { id?: string; projection: string },\n fromPosition?: number,\n ) => MaybePromise<void>\n incrementAppliedCount: (target: {\n projection: string\n id: string\n }) => MaybePromise<number>\n}\n\nexport function buildProjectionCreator(params: {\n loadEvents: LoadEvents\n checkpoint: CheckpointMethods\n onReplay?: OnProgress\n snapshot?: ProjectionSnapshotMethods\n}): (name: string) => ProjectionApi {\n return (name) => {\n let idle = true\n let replaying = false\n let handlers: ProjectionEventHandlers<any> = {}\n let globalEventHandler:\n | ((event: BaseOutputEvent) => MaybePromise<void>)\n | null = null\n let generateRelatedStreamEvents: (\n eventHandlers: EventHandlers<any>,\n ) => StreamEvents[]\n let relatedStreamEvents: StreamEvents[] | null = null\n let exclusive = true\n let checkpointApi: CheckpointApi\n let lastEventPosition = -1\n\n let replayConfig: ReplayConfig | null = null\n\n let snapshotting:\n | (CompleteSnapshotGenerator & InternalSnapshotGenerator)\n | null = null\n\n const { promise: hydrated, resolve: resolveHydrated } =\n Promise.withResolvers<void>()\n\n const locks = new Map<string, Promise<void>>()\n const apply = async (event: BaseOutputEvent) => {\n const handler = handlers[`on${event.type}`]\n if (!handler && !globalEventHandler) {\n return Promise.resolve()\n }\n\n if (isHydrating()) {\n lastEventPosition = event.position\n } else {\n await hydrated\n // event was already applied through hydration\n if (event.position <= lastEventPosition) {\n return\n }\n }\n\n const unlocked = !exclusive\n ? Promise.resolve()\n : (locks.get(event.streamId)?.catch(() => {}) ?? Promise.resolve())\n\n const runner = (async () => {\n await unlocked\n await withProjectionInfo({ name, event }, async () => {\n try {\n await handler?.(event, {\n replaying,\n async replayUntil(position, filterEvents) {\n if (position >= event.position) {\n throw new ProjectionReplayLoopError(\n `Event handler on${event.type} of projection ${name} tried to replay to the future`,\n { name, eventHandler: `on${event.type}` },\n event,\n )\n }\n\n locks.delete(event.streamId)\n return api.replayUntil(event.streamId, position, filterEvents)\n },\n })\n } catch (error) {\n if (error instanceof ReplaySkipError) {\n if (isReplaying()) {\n return\n }\n\n throw new Error(\n 'ReplaySkipError should never be thrown outside replay',\n )\n }\n\n if (\n !(error instanceof Error) ||\n error instanceof ProjectionReplayLoopError\n ) {\n throw error\n }\n throw new ProjectionEventHandlerExecutionError(\n name,\n `on${event.type}`,\n event,\n error,\n )\n }\n try {\n await globalEventHandler?.(event)\n } catch (error) {\n if (!(error instanceof Error)) {\n throw error\n }\n\n throw new ProjectionEventHandlerExecutionError(\n name,\n 'GLOBAL',\n event,\n error,\n )\n }\n })\n\n if (params.snapshot && snapshotting) {\n const appliedEvents = await params.snapshot.incrementAppliedCount({\n projection: name,\n id: event.streamId,\n })\n const snapshot = await snapshotting.internalCapture(\n event,\n appliedEvents,\n )\n\n if (snapshot) {\n await params.snapshot.put({\n projectionType: name,\n projectionId: event.streamId,\n lastEventPosition: event.position,\n state: snapshot.state,\n schemaVersion: snapshot.version,\n createdAt: new Date(),\n })\n }\n }\n\n await checkpointApi.upsert(event)\n })()\n\n if (exclusive) {\n locks.set(event.streamId, runner)\n\n runner\n .finally(() => {\n if (locks.get(event.streamId) === runner) {\n locks.delete(event.streamId)\n }\n })\n .catch(() => {})\n return runner\n }\n\n return Promise.resolve()\n }\n\n let resolveReplay: null | (() => void) = null\n let resolveReplayOne: null | (() => void) = null\n let replayOneStreamId: null | string = null\n\n const api = {\n name: 'projection ' + name,\n async isReady() {\n await hydrated\n },\n withEventHandlers(h) {\n handlers = h\n return api\n },\n withGlobalEventHandler(handler) {\n globalEventHandler = handler as any\n return api\n },\n concurrent() {\n exclusive = false\n return api\n },\n setRelatedStreamEvents(generate) {\n generateRelatedStreamEvents = generate\n },\n withReplay(config) {\n replayConfig = config\n return api as any\n },\n\n withSnapshots(handler) {\n snapshotting = handler(createSnapshotGenerator() as any) as any\n return api\n },\n\n async apply(event) {\n await apply(event)\n },\n\n async replayOne(streamId: string) {\n if (!idle) {\n await replayOne(streamId)\n return\n }\n\n const { promise, resolve } = Promise.withResolvers<void>()\n resolveReplayOne = resolve\n replayOneStreamId = streamId\n return promise\n },\n\n async replay() {\n if (!idle) {\n await replay()\n return\n }\n const { promise, resolve } = Promise.withResolvers<void>()\n resolveReplay = resolve\n return promise\n },\n\n canReplay() {\n return Boolean(replayConfig)\n },\n\n async beginReplay() {\n if (!replayConfig?.deleteAll) {\n throw new Error(\n 'this projection does not support replay all. withReplay({ deleteAll }) not implemented.',\n )\n }\n\n lastEventPosition = -1\n params.onReplay?.begin?.(name)\n await params.snapshot?.delete({ projection: name })\n await replayConfig.deleteAll()\n },\n\n async progressReplay(index, event) {\n await withReplaying(() => withHydrating(() => apply(event)))\n if (\n !checkpointApi.relatedStreamEvents ||\n checkpointApi.relatedStreamEvents.some((streamEvent) =>\n streamEvent.events.includes(event.type),\n )\n ) {\n params.onReplay?.progress?.(name, index, event)\n }\n },\n\n endReplay() {\n params.onReplay?.end?.(name)\n resolveHydrated()\n },\n\n async replayUntil(id, position, filterEvents = () => true) {\n let startPosition = 0\n if (snapshotting) {\n await params.snapshot?.delete({ id, projection: name }, position)\n const snapshot = await params.snapshot?.get({ id, projection: name })\n if (snapshot) {\n const result = snapshotSchema.safeParse(snapshot)\n\n if (!result.success) {\n throw new ValidationError(\n 'loaded projection snapshot does not comply with schema',\n result.error,\n )\n }\n snapshotting.internalApply(result.data)\n startPosition = result.data.lastEventPosition + 1\n }\n }\n\n const events = params.loadEvents(\n relatedStreamEvents?.map((relatedStreamEvent) => ({\n ...relatedStreamEvent,\n id,\n })) ?? null,\n {\n from: startPosition,\n to: position,\n },\n )\n\n replaying = true\n await withReplaying(async () => {\n for await (const event of events) {\n if ((filterEvents as (event: BaseOutputEvent) => boolean)(event)) {\n await apply(event)\n }\n }\n })\n replaying = false\n },\n async init(catchUp) {\n idle = false\n relatedStreamEvents = globalEventHandler\n ? null\n : generateRelatedStreamEvents(handlers as EventHandlers<any>)\n checkpointApi = createCheckpointApi({\n ...params.checkpoint,\n name,\n relatedStreamEvents,\n type: 'projection',\n loadEvents: params.loadEvents,\n apply: (event) => withHydrating(() => apply(event)),\n })\n\n if (resolveReplay) {\n await replay()\n resolveReplay()\n return\n }\n\n if (resolveReplayOne && replayOneStreamId) {\n await replayOne(replayOneStreamId)\n }\n\n if (!catchUp) {\n return\n }\n\n await checkpointApi.rehydrated()\n resolveHydrated()\n resolveReplayOne?.()\n },\n } as ProjectionApi & InternalProjection & ReplayProjection<any>\n\n async function replayOne(id: string) {\n if (!replayConfig?.deleteOne) {\n throw new Error(\n 'this projection does not support replay one. withReplay({ deleteOne }) not implemented.',\n )\n }\n await params.snapshot?.delete({ id, projection: name })\n await replayConfig.deleteOne(id)\n const streamIds = relatedStreamEvents\n ? relatedStreamEvents.map((relatedStreamEvent) => ({\n ...relatedStreamEvent,\n id,\n }))\n : null\n const events = params.loadEvents(streamIds)\n\n replaying = true\n await withReplaying(async () => {\n for await (const event of events) {\n await withHydrating(() => apply(event))\n }\n })\n replaying = false\n }\n\n async function replay() {\n await api.beginReplay()\n const events = params.loadEvents(relatedStreamEvents)\n let index = 0\n replaying = true\n await withReplaying(async () => {\n for await (const event of events) {\n await api.progressReplay(index, event)\n index++\n }\n })\n replaying = false\n api.endReplay()\n }\n\n return api\n }\n}\n"
6
- ],
7
- "mappings": "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",
8
- "debugId": "059753BED2CE852D64756E2164756E21",
9
- "names": []
10
- }