@requence/event-sourcing 1.4.1 → 1.5.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 (133) hide show
  1. package/README.md +6 -5
  2. package/dist/{chunk-24xjphpk.js → chunk-06anxtk6.js} +6 -6
  3. package/dist/{chunk-24xjphpk.js.map → chunk-06anxtk6.js.map} +2 -2
  4. package/dist/{chunk-263c07sn.js → chunk-1mr4cwpw.js} +8 -8
  5. package/dist/{chunk-263c07sn.js.map → chunk-1mr4cwpw.js.map} +2 -2
  6. package/dist/{chunk-3ejt4h7x.js → chunk-2wqpktzp.js} +78 -66
  7. package/dist/chunk-2wqpktzp.js.map +10 -0
  8. package/dist/{chunk-dgzw1nj9.js → chunk-467qtkth.js} +8 -3
  9. package/dist/chunk-467qtkth.js.map +10 -0
  10. package/dist/chunk-6dn6wxg7.js +11 -0
  11. package/dist/{chunk-jd4q8fcf.js.map → chunk-6dn6wxg7.js.map} +2 -2
  12. package/dist/{chunk-pc1q4se9.js → chunk-9qtjz725.js} +2 -2
  13. package/dist/{chunk-pc1q4se9.js.map → chunk-9qtjz725.js.map} +2 -2
  14. package/dist/{chunk-tfy01fah.js → chunk-c7sytwpp.js} +4 -4
  15. package/dist/{chunk-tfy01fah.js.map → chunk-c7sytwpp.js.map} +2 -2
  16. package/dist/{chunk-vvv14qxy.js → chunk-d6jqbrq2.js} +33 -31
  17. package/dist/{chunk-vvv14qxy.js.map → chunk-d6jqbrq2.js.map} +3 -3
  18. package/dist/{chunk-vep2byx4.js → chunk-f95c3da4.js} +77 -64
  19. package/dist/chunk-f95c3da4.js.map +10 -0
  20. package/dist/chunk-fpjnk143.js +13 -0
  21. package/dist/{chunk-6q8mr1sg.js.map → chunk-fpjnk143.js.map} +2 -2
  22. package/dist/{chunk-8qn1nqnh.js → chunk-fxb9fbvz.js} +9 -9
  23. package/dist/{chunk-8qn1nqnh.js.map → chunk-fxb9fbvz.js.map} +2 -2
  24. package/dist/chunk-hmvwn02w.js +22 -0
  25. package/dist/{chunk-6yrb30rx.js.map → chunk-hmvwn02w.js.map} +2 -2
  26. package/dist/{chunk-ty9bhdvx.js → chunk-j4mwjx07.js} +5 -3
  27. package/dist/chunk-j4mwjx07.js.map +10 -0
  28. package/dist/{chunk-391m4mfp.js → chunk-jw9e0fr0.js} +13 -13
  29. package/dist/{chunk-391m4mfp.js.map → chunk-jw9e0fr0.js.map} +2 -2
  30. package/dist/chunk-mbxxvskx.js +67 -0
  31. package/dist/chunk-mbxxvskx.js.map +10 -0
  32. package/dist/{chunk-yn2sjcg3.js → chunk-nrhw3kde.js} +57 -8
  33. package/dist/chunk-nrhw3kde.js.map +10 -0
  34. package/dist/{chunk-57b88rt5.js → chunk-pcggjrxr.js} +32 -32
  35. package/dist/{chunk-57b88rt5.js.map → chunk-pcggjrxr.js.map} +2 -2
  36. package/dist/chunk-tkwe4r0y.js +22 -0
  37. package/dist/{chunk-2fe8jjdc.js.map → chunk-tkwe4r0y.js.map} +2 -2
  38. package/dist/{chunk-z388cd25.js → chunk-zkz6ew8c.js} +5 -5
  39. package/dist/{chunk-z388cd25.js.map → chunk-zkz6ew8c.js.map} +2 -2
  40. package/dist/createAggregateRoot.d.ts +1 -1
  41. package/dist/createAggregateRoot.d.ts.map +1 -1
  42. package/dist/createAggregateRoot.js +15 -14
  43. package/dist/createAggregateRoot.js.map +1 -1
  44. package/dist/createCheckpointApi.d.ts +30 -0
  45. package/dist/createCheckpointApi.d.ts.map +1 -1
  46. package/dist/createCheckpointApi.js +5 -5
  47. package/dist/createCheckpointApi.js.map +1 -1
  48. package/dist/createEventListener.js +8 -8
  49. package/dist/createEventListener.js.map +1 -1
  50. package/dist/createEventStore.js +16 -16
  51. package/dist/createEventStore.js.map +1 -1
  52. package/dist/createProcessManager.d.ts.map +1 -1
  53. package/dist/createProcessManager.js +12 -12
  54. package/dist/createProcessManager.js.map +1 -1
  55. package/dist/createProjection.d.ts.map +1 -1
  56. package/dist/createProjection.js +12 -12
  57. package/dist/createProjection.js.map +1 -1
  58. package/dist/drizzle/index.js +132 -132
  59. package/dist/drizzle/index.js.map +2 -2
  60. package/dist/drizzle/postgres.js +14 -14
  61. package/dist/drizzle/postgres.js.map +1 -1
  62. package/dist/errors.js +24 -24
  63. package/dist/errors.js.map +1 -1
  64. package/dist/index.js +63 -63
  65. package/dist/index.js.map +1 -1
  66. package/dist/lock.d.ts +11 -3
  67. package/dist/lock.d.ts.map +1 -1
  68. package/dist/lock.js +4 -2
  69. package/dist/lock.js.map +1 -1
  70. package/dist/memory/index.d.ts +14 -3
  71. package/dist/memory/index.d.ts.map +1 -1
  72. package/dist/memory/index.js +52 -45
  73. package/dist/memory/index.js.map +3 -3
  74. package/dist/memory/index.spec.d.ts +2 -0
  75. package/dist/memory/index.spec.d.ts.map +1 -0
  76. package/dist/redis/index.js +5 -4
  77. package/dist/redis/index.js.map +1 -1
  78. package/dist/redis/redisLock.d.ts +1 -1
  79. package/dist/redis/redisLock.d.ts.map +1 -1
  80. package/dist/redis/redisLock.js +5 -4
  81. package/dist/redis/redisLock.js.map +1 -1
  82. package/dist/refresh.js +9 -9
  83. package/dist/refresh.js.map +1 -1
  84. package/dist/replay.js +9 -9
  85. package/dist/replay.js.map +1 -1
  86. package/dist/snapshotGenerator.js +2 -2
  87. package/dist/superjson.js +8 -8
  88. package/dist/superjson.js.map +1 -1
  89. package/dist/surreal/index.d.ts +35 -0
  90. package/dist/surreal/index.d.ts.map +1 -0
  91. package/dist/surreal/index.js +415 -0
  92. package/dist/surreal/index.js.map +10 -0
  93. package/dist/surreal/index.spec.d.ts +2 -0
  94. package/dist/surreal/index.spec.d.ts.map +1 -0
  95. package/dist/surreal/schema.d.ts +12 -0
  96. package/dist/surreal/schema.d.ts.map +1 -0
  97. package/dist/surreal/schema.js +12 -0
  98. package/dist/surreal/schema.js.map +9 -0
  99. package/dist/tests/projectionMultiInstance.spec.d.ts +2 -0
  100. package/dist/tests/projectionMultiInstance.spec.d.ts.map +1 -0
  101. package/dist/tests/storageDriverContract.d.ts +19 -0
  102. package/dist/tests/storageDriverContract.d.ts.map +1 -0
  103. package/dist/transaction.js +1 -1
  104. package/dist/utils.js +6 -6
  105. package/dist/utils.js.map +1 -1
  106. package/dist/wrappedEventBuilder.js +8 -8
  107. package/dist/wrappedEventBuilder.js.map +1 -1
  108. package/package.json +19 -10
  109. package/src/createAggregateRoot.ts +24 -5
  110. package/src/createCheckpointApi.ts +98 -0
  111. package/src/createProcessManager.ts +7 -6
  112. package/src/createProjection.ts +91 -65
  113. package/src/lock.ts +17 -3
  114. package/src/memory/index.spec.ts +4 -0
  115. package/src/memory/index.ts +55 -29
  116. package/src/redis/redisLock.ts +6 -2
  117. package/src/surreal/index.spec.ts +152 -0
  118. package/src/surreal/index.ts +604 -0
  119. package/src/surreal/schema.ts +68 -0
  120. package/src/tests/aggregateRoot.spec.ts +48 -3
  121. package/src/tests/eventStore.spec.ts +1 -3
  122. package/src/tests/processManagerMultiInstance.spec.ts +18 -7
  123. package/src/tests/projectionMultiInstance.spec.ts +283 -0
  124. package/src/tests/storageDriverContract.ts +612 -0
  125. package/dist/chunk-2fe8jjdc.js +0 -22
  126. package/dist/chunk-3ejt4h7x.js.map +0 -10
  127. package/dist/chunk-6q8mr1sg.js +0 -13
  128. package/dist/chunk-6yrb30rx.js +0 -22
  129. package/dist/chunk-dgzw1nj9.js.map +0 -10
  130. package/dist/chunk-jd4q8fcf.js +0 -11
  131. package/dist/chunk-ty9bhdvx.js.map +0 -10
  132. package/dist/chunk-vep2byx4.js.map +0 -10
  133. package/dist/chunk-yn2sjcg3.js.map +0 -10
@@ -1,5 +1,9 @@
1
- import type { AnyAggregateRoot } from '../createAggregateRoot.ts'
2
- import type { Checkpoint } from '../createCheckpointApi.ts'
1
+ import type {
2
+ AggregateRootSnapshotMethods,
3
+ AnyAggregateRoot,
4
+ } from '../createAggregateRoot.ts'
5
+ import type { Checkpoint, CheckpointMethods } from '../createCheckpointApi.ts'
6
+ import type { ProjectionSnapshotMethods } from '../createProjection.ts'
3
7
  import {
4
8
  type AggregateRootSnapshot,
5
9
  ConcurrencyError,
@@ -13,16 +17,20 @@ import type {
13
17
  EventStore,
14
18
  EventStoreParamsWithAggregateRootSnapshots,
15
19
  EventsFromRoot,
20
+ LoadEvents,
16
21
  OnProgress,
17
22
  } from '../createEventStore.ts'
18
23
  import type { LockCreator } from '../lock.ts'
19
- import type { BaseOutputEvent, MaybePromise } from '../utilityTypes.js'
24
+ import type {
25
+ BaseInputEvent,
26
+ BaseOutputEvent,
27
+ MaybePromise,
28
+ StreamId,
29
+ } from '../utilityTypes.js'
20
30
 
21
31
  export { createAggregateRoot } from '../index.ts'
22
32
 
23
- export type OnEventsAppended = (
24
- events: BaseOutputEvent[],
25
- ) => MaybePromise<void>
33
+ export type OnEventsAppended = (events: BaseOutputEvent[]) => MaybePromise<void>
26
34
 
27
35
  interface MemoryEventStoreParams<Root extends AnyAggregateRoot> {
28
36
  aggregateRoots: Root[]
@@ -34,33 +42,26 @@ interface MemoryEventStoreParams<Root extends AnyAggregateRoot> {
34
42
  onEventsAppended?: OnEventsAppended
35
43
  }
36
44
 
37
- export function createEventStore<const Root extends AnyAggregateRoot>({
38
- aggregateRoots,
39
- postProcessEvent,
40
- onProjectionReplay,
41
- onProcessManagerRefresh,
42
- onEventsAppended,
43
- autoInit,
44
- lock,
45
- }: MemoryEventStoreParams<Root>): EventStore<
46
- EventsFromRoot<Root>,
47
- Root,
48
- true
49
- > {
45
+ export function createMemoryStorage(params?: {
46
+ onEventsAppended?: OnEventsAppended
47
+ }): {
48
+ loadEvents: LoadEvents
49
+ appendEvents: (
50
+ streamId: StreamId,
51
+ events: BaseInputEvent[],
52
+ expectedVersion: number,
53
+ ) => BaseOutputEvent[]
54
+ checkpoint: CheckpointMethods
55
+ projectionSnapshot: ProjectionSnapshotMethods
56
+ aggregateRootSnapshot: AggregateRootSnapshotMethods
57
+ } {
50
58
  const events: Event[] = []
51
59
  const checkpoints = new Set<Checkpoint>()
52
60
  const aggregateRootSnapshots = new Map<string, AggregateRootSnapshot>()
53
61
  const projectionSnapshots = new Set<ProjectionSnapshot>()
54
62
  const projectionAppliedCount = new Map<string, number>()
55
63
 
56
- return createBaseEventStore({
57
- aggregateRoots,
58
- postProcessEvent,
59
- onProjectionReplay,
60
- onProcessManagerRefresh,
61
- autoInit,
62
- lock,
63
-
64
+ return {
64
65
  async *loadEvents(select, range) {
65
66
  let streamTypes: string[] | null = null
66
67
  let streamIds: string[] | null = null
@@ -131,7 +132,7 @@ export function createEventStore<const Root extends AnyAggregateRoot>({
131
132
  }))
132
133
 
133
134
  events.push(...extendedEvents)
134
- onEventsAppended?.(extendedEvents)
135
+ params?.onEventsAppended?.(extendedEvents)
135
136
  return extendedEvents
136
137
  },
137
138
 
@@ -155,7 +156,10 @@ export function createEventStore<const Root extends AnyAggregateRoot>({
155
156
  if (persisted) {
156
157
  // Compare-and-swap: reject the write when another writer advanced the
157
158
  // checkpoint, or when the caller expected to create a fresh one.
158
- if (expectedVersion === null || persisted.version !== expectedVersion) {
159
+ if (
160
+ expectedVersion === null ||
161
+ persisted.version !== expectedVersion
162
+ ) {
159
163
  return false
160
164
  }
161
165
  persisted.lastEventPosition = checkpoint.lastEventPosition
@@ -232,5 +236,27 @@ export function createEventStore<const Root extends AnyAggregateRoot>({
232
236
  }
233
237
  },
234
238
  },
239
+ }
240
+ }
241
+
242
+ export function createEventStore<const Root extends AnyAggregateRoot>({
243
+ aggregateRoots,
244
+ postProcessEvent,
245
+ onProjectionReplay,
246
+ onProcessManagerRefresh,
247
+ onEventsAppended,
248
+ autoInit,
249
+ lock,
250
+ }: MemoryEventStoreParams<Root>): EventStore<EventsFromRoot<Root>, Root, true> {
251
+ const storage = createMemoryStorage({ onEventsAppended })
252
+
253
+ return createBaseEventStore({
254
+ aggregateRoots,
255
+ postProcessEvent,
256
+ onProjectionReplay,
257
+ onProcessManagerRefresh,
258
+ autoInit,
259
+ lock,
260
+ ...storage,
235
261
  }) as any
236
262
  }
@@ -1,6 +1,6 @@
1
1
  import type { Redis } from 'ioredis'
2
2
 
3
- import type { LockCreator } from '../lock.ts'
3
+ import { DEFAULT_LOCK_TTL, type LockCreator } from '../lock.ts'
4
4
 
5
5
  const extendScript = `
6
6
  if redis.call("get", KEYS[1]) == ARGV[1] then
@@ -18,7 +18,10 @@ const deleteScript = `
18
18
  end
19
19
  `
20
20
 
21
- export function redisLock(client: Redis, defaultTtl = 5000): LockCreator {
21
+ export function redisLock(
22
+ client: Redis,
23
+ defaultTtl = DEFAULT_LOCK_TTL,
24
+ ): LockCreator {
22
25
  return async (rawKey) => {
23
26
  const key = Array.isArray(rawKey) ? rawKey.join(':') : rawKey
24
27
  const token = crypto.randomUUID()
@@ -33,6 +36,7 @@ export function redisLock(client: Redis, defaultTtl = 5000): LockCreator {
33
36
 
34
37
  let isReleased = false
35
38
  return {
39
+ ttl: defaultTtl,
36
40
  async extend(ttl) {
37
41
  const extended = await client.eval(
38
42
  extendScript,
@@ -0,0 +1,152 @@
1
+ import { afterAll, describe, expect, it } from 'bun:test'
2
+ import { createNodeEngines } from '@surrealdb/node'
3
+ import { Surreal } from 'surrealdb'
4
+
5
+ import { ConcurrencyError } from '../errors.ts'
6
+ import { runStorageDriverContract } from '../tests/storageDriverContract.ts'
7
+ import { createSurrealStorage, initSchema } from './index.ts'
8
+
9
+ // One shared embedded engine for the whole file; isolation comes from a
10
+ // fresh logical database per test (tests run sequentially).
11
+ let connection: Surreal | undefined
12
+
13
+ async function makeDatabase() {
14
+ if (!connection) {
15
+ connection = new Surreal({ engines: { ...createNodeEngines() } })
16
+ await connection.connect('mem://')
17
+ }
18
+ await connection.use({ namespace: 'test', database: crypto.randomUUID() })
19
+ return connection
20
+ }
21
+
22
+ afterAll(async () => {
23
+ await connection?.close()
24
+ })
25
+
26
+ runStorageDriverContract('surreal', async () => {
27
+ const database = await makeDatabase()
28
+ return createSurrealStorage({ database })
29
+ })
30
+
31
+ describe('surreal driver specifics', () => {
32
+ it('initializes the schema idempotently', async () => {
33
+ const database = await makeDatabase()
34
+ await initSchema(database)
35
+ await initSchema(database)
36
+
37
+ const storage = createSurrealStorage({ database })
38
+ const persisted = await storage.appendEvents(
39
+ { stream: 'order', id: 'o1' },
40
+ [{ type: 'created', payload: {}, metadata: {}, schemaVersion: 1 }],
41
+ 0,
42
+ )
43
+ expect(persisted[0].position).toBe(1)
44
+ })
45
+
46
+ it('skips schema creation when initSchema is disabled', async () => {
47
+ const database = await makeDatabase()
48
+ const storage = createSurrealStorage({ database, initSchema: false })
49
+
50
+ let error: unknown
51
+ try {
52
+ await storage.appendEvents(
53
+ { stream: 'order', id: 'o1' },
54
+ [{ type: 'created', payload: {}, metadata: {}, schemaVersion: 1 }],
55
+ 0,
56
+ )
57
+ } catch (err) {
58
+ error = err
59
+ }
60
+ // Without the SCHEMAFULL tables the counter arithmetic cannot resolve.
61
+ expect(error).toBeTruthy()
62
+ })
63
+
64
+ it('assigns gapless, monotonically increasing global positions', async () => {
65
+ const database = await makeDatabase()
66
+ const storage = createSurrealStorage({ database })
67
+
68
+ await storage.appendEvents(
69
+ { stream: 'order', id: 'o1' },
70
+ [
71
+ { type: 'created', payload: {}, metadata: {}, schemaVersion: 1 },
72
+ { type: 'updated', payload: {}, metadata: {}, schemaVersion: 1 },
73
+ ],
74
+ 0,
75
+ )
76
+ const second = await storage.appendEvents(
77
+ { stream: 'order', id: 'o2' },
78
+ [{ type: 'created', payload: {}, metadata: {}, schemaVersion: 1 }],
79
+ 0,
80
+ )
81
+
82
+ expect(second[0].position).toBe(3)
83
+ })
84
+
85
+ it('serializes concurrent appends to different streams without losing positions', async () => {
86
+ const database = await makeDatabase()
87
+ const storage = createSurrealStorage({ database })
88
+
89
+ const results = await Promise.all(
90
+ Array.from({ length: 8 }, (_, index) =>
91
+ storage.appendEvents(
92
+ { stream: 'order', id: `o${index}` },
93
+ [{ type: 'created', payload: {}, metadata: {}, schemaVersion: 1 }],
94
+ 0,
95
+ ),
96
+ ),
97
+ )
98
+
99
+ const positions = results
100
+ .map(([event]) => event.position)
101
+ .toSorted((a, b) => a - b)
102
+ expect(positions).toEqual([1, 2, 3, 4, 5, 6, 7, 8])
103
+ })
104
+
105
+ it('reports the current version in ConcurrencyError', async () => {
106
+ const database = await makeDatabase()
107
+ const storage = createSurrealStorage({ database })
108
+
109
+ await storage.appendEvents(
110
+ { stream: 'order', id: 'o1' },
111
+ [
112
+ { type: 'created', payload: {}, metadata: {}, schemaVersion: 1 },
113
+ { type: 'updated', payload: {}, metadata: {}, schemaVersion: 1 },
114
+ ],
115
+ 0,
116
+ )
117
+
118
+ let error: unknown
119
+ try {
120
+ await storage.appendEvents(
121
+ { stream: 'order', id: 'o1' },
122
+ [{ type: 'updated', payload: {}, metadata: {}, schemaVersion: 1 }],
123
+ 1,
124
+ )
125
+ } catch (err) {
126
+ error = err
127
+ }
128
+
129
+ expect(error).toBeInstanceOf(ConcurrencyError)
130
+ expect((error as Error).message).toContain('expected v1, actual v2')
131
+ })
132
+
133
+ it('notifies onEventsAppended with the persisted events', async () => {
134
+ const database = await makeDatabase()
135
+ let appended: unknown[] = []
136
+ const storage = createSurrealStorage({
137
+ database,
138
+ onEventsAppended: (events) => {
139
+ appended = events
140
+ },
141
+ })
142
+
143
+ await storage.appendEvents(
144
+ { stream: 'order', id: 'o1' },
145
+ [{ type: 'created', payload: { n: 1 }, metadata: {}, schemaVersion: 1 }],
146
+ 0,
147
+ )
148
+
149
+ expect(appended).toHaveLength(1)
150
+ expect((appended[0] as any).position).toBe(1)
151
+ })
152
+ })