@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.
- package/LICENSE +21 -0
- package/dist/chunk-24xjphpk.js +51 -0
- package/dist/chunk-24xjphpk.js.map +10 -0
- package/dist/chunk-3t1vzbab.js +88 -0
- package/dist/chunk-3t1vzbab.js.map +10 -0
- package/dist/chunk-6q8mr1sg.js +13 -0
- package/dist/chunk-6q8mr1sg.js.map +10 -0
- package/dist/chunk-9psg50cb.js +22 -0
- package/dist/chunk-9psg50cb.js.map +10 -0
- package/dist/chunk-dgzw1nj9.js +50 -0
- package/dist/chunk-dgzw1nj9.js.map +10 -0
- package/dist/chunk-gq80tgvf.js +70 -0
- package/dist/chunk-gq80tgvf.js.map +10 -0
- package/dist/chunk-jd4q8fcf.js +11 -0
- package/dist/chunk-jd4q8fcf.js.map +10 -0
- package/dist/chunk-m07vf8q9.js +210 -0
- package/dist/chunk-m07vf8q9.js.map +10 -0
- package/dist/chunk-ncpk70p4.js +313 -0
- package/dist/chunk-ncpk70p4.js.map +10 -0
- package/dist/chunk-pmfzjavb.js +49 -0
- package/dist/chunk-pmfzjavb.js.map +10 -0
- package/dist/chunk-qdczt68t.js +84 -0
- package/dist/chunk-qdczt68t.js.map +10 -0
- package/dist/chunk-tfy01fah.js +34 -0
- package/dist/chunk-tfy01fah.js.map +10 -0
- package/dist/chunk-tkvj6x8k.js +389 -0
- package/dist/chunk-tkvj6x8k.js.map +10 -0
- package/dist/chunk-ty9bhdvx.js +53 -0
- package/dist/chunk-ty9bhdvx.js.map +10 -0
- package/dist/chunk-wmz6a1jk.js +233 -0
- package/dist/chunk-wmz6a1jk.js.map +10 -0
- package/dist/chunk-x90g388y.js +22 -0
- package/dist/chunk-x90g388y.js.map +10 -0
- package/dist/chunk-z388cd25.js +91 -0
- package/dist/chunk-z388cd25.js.map +10 -0
- package/dist/chunk-zf3x573k.js +65 -0
- package/dist/chunk-zf3x573k.js.map +10 -0
- package/dist/createAggregateRoot.d.ts +191 -0
- package/dist/createAggregateRoot.d.ts.map +1 -0
- package/dist/createAggregateRoot.js +18 -0
- package/dist/createAggregateRoot.js.map +9 -0
- package/dist/createCheckpointApi.d.ts +38 -0
- package/dist/createCheckpointApi.d.ts.map +1 -0
- package/dist/createCheckpointApi.js +9 -0
- package/dist/createCheckpointApi.js.map +9 -0
- package/dist/createEventListener.d.ts +46 -0
- package/dist/createEventListener.d.ts.map +1 -0
- package/dist/createEventListener.js +12 -0
- package/dist/createEventListener.js.map +9 -0
- package/dist/createEventStore.d.ts +57 -0
- package/dist/createEventStore.d.ts.map +1 -0
- package/dist/createEventStore.js +20 -0
- package/dist/createEventStore.js.map +9 -0
- package/dist/createProcessManager.d.ts +70 -0
- package/dist/createProcessManager.d.ts.map +1 -0
- package/dist/createProcessManager.js +16 -0
- package/dist/createProcessManager.js.map +9 -0
- package/dist/createProjection.d.ts +120 -0
- package/dist/createProjection.d.ts.map +1 -0
- package/dist/createProjection.js +16 -0
- package/dist/createProjection.js.map +9 -0
- package/dist/drizzle/index.d.ts +449 -0
- package/dist/drizzle/index.d.ts.map +1 -0
- package/dist/drizzle/index.js +325 -0
- package/dist/drizzle/index.js.map +10 -0
- package/dist/drizzle/postgres.d.ts +686 -0
- package/dist/drizzle/postgres.d.ts.map +1 -0
- package/dist/drizzle/postgres.js +18 -0
- package/dist/drizzle/postgres.js.map +9 -0
- package/dist/errors.d.ts +53 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +9 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +9 -0
- package/dist/lock.d.ts +6 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +8 -0
- package/dist/lock.js.map +9 -0
- package/dist/memory/index.d.ts +15 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +178 -0
- package/dist/memory/index.js.map +10 -0
- package/dist/redis/index.d.ts +2 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/redis/index.js +8 -0
- package/dist/redis/index.js.map +9 -0
- package/dist/redis/redisLock.d.ts +4 -0
- package/dist/redis/redisLock.d.ts.map +1 -0
- package/dist/redis/redisLock.js +8 -0
- package/dist/redis/redisLock.js.map +9 -0
- package/dist/refresh.d.ts +4 -0
- package/dist/refresh.d.ts.map +1 -0
- package/dist/refresh.js +13 -0
- package/dist/refresh.js.map +9 -0
- package/dist/replay.d.ts +4 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +13 -0
- package/dist/replay.js.map +9 -0
- package/dist/snapshotGenerator.d.ts +79 -0
- package/dist/snapshotGenerator.d.ts.map +1 -0
- package/dist/snapshotGenerator.js +9 -0
- package/dist/snapshotGenerator.js.map +9 -0
- package/dist/superjson.d.ts +5 -0
- package/dist/superjson.d.ts.map +1 -0
- package/dist/superjson.js +12 -0
- package/dist/superjson.js.map +9 -0
- package/dist/transaction.d.ts +13 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +8 -0
- package/dist/transaction.js.map +9 -0
- package/dist/utilityTypes.d.js +2 -0
- package/dist/utilityTypes.d.js.map +9 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +10 -0
- package/dist/utils.js.map +9 -0
- package/dist/wrappedEventBuilder.d.ts +37 -0
- package/dist/wrappedEventBuilder.d.ts.map +1 -0
- package/dist/wrappedEventBuilder.js +12 -0
- package/dist/wrappedEventBuilder.js.map +9 -0
- package/package.json +85 -1
- package/src/createAggregateRoot.ts +902 -0
- package/src/createCheckpointApi.ts +106 -0
- package/src/createEventListener.ts +118 -0
- package/src/createEventStore.ts +446 -0
- package/src/createProcessManager.ts +363 -0
- package/src/createProjection.ts +577 -0
- package/src/drizzle/index.ts +449 -0
- package/src/drizzle/postgres.ts +120 -0
- package/src/errors.ts +85 -0
- package/src/index.ts +45 -0
- package/src/lock.ts +58 -0
- package/src/memory/index.ts +224 -0
- package/src/redis/index.ts +1 -0
- package/src/redis/redisLock.ts +61 -0
- package/src/refresh.ts +19 -0
- package/src/replay.ts +19 -0
- package/src/snapshotGenerator.ts +190 -0
- package/src/superjson.ts +11 -0
- package/src/tests/aggregateRoot.spec.ts +532 -0
- package/src/tests/eventListener.spec.ts +57 -0
- package/src/tests/eventStore.spec.ts +86 -0
- package/src/tests/processManager.spec.ts +185 -0
- package/src/tests/projection.spec.ts +511 -0
- package/src/tests/projectionProcessManager.spec.ts +203 -0
- package/src/tests/setup.ts +230 -0
- package/src/tests/transaction.spec.ts +185 -0
- package/src/transaction.ts +93 -0
- package/src/utilityTypes.d.ts +110 -0
- package/src/utils.ts +43 -0
- 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'
|