@requence/event-sourcing 1.0.3 → 1.1.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/README.md +1 -1
- package/dist/{chunk-8pchcyvm.js → chunk-04wqxaqs.js} +28 -18
- package/dist/chunk-04wqxaqs.js.map +10 -0
- package/dist/{chunk-3t1vzbab.js → chunk-263c07sn.js} +3 -2
- package/dist/{chunk-3t1vzbab.js.map → chunk-263c07sn.js.map} +3 -3
- package/dist/{chunk-x90g388y.js → chunk-2fe8jjdc.js} +1 -1
- package/dist/{chunk-qdczt68t.js → chunk-391m4mfp.js} +14 -2
- package/dist/chunk-391m4mfp.js.map +10 -0
- package/dist/{chunk-9psg50cb.js → chunk-6yrb30rx.js} +1 -1
- package/dist/{chunk-pmfzjavb.js → chunk-8qn1nqnh.js} +1 -1
- package/dist/{chunk-6n3jfg84.js → chunk-r601d5jm.js} +18 -18
- package/dist/{chunk-6n3jfg84.js.map → chunk-r601d5jm.js.map} +1 -1
- package/dist/{chunk-13wtyj2h.js → chunk-smy8mxvr.js} +37 -23
- package/dist/chunk-smy8mxvr.js.map +10 -0
- package/dist/{chunk-8ev7sb2v.js → chunk-th14wxh8.js} +7 -7
- package/dist/{chunk-8ev7sb2v.js.map → chunk-th14wxh8.js.map} +1 -1
- package/dist/{chunk-zf3x573k.js → chunk-yn2sjcg3.js} +36 -11
- package/dist/chunk-yn2sjcg3.js.map +10 -0
- package/dist/createAggregateRoot.js +9 -9
- package/dist/createCheckpointApi.d.ts +37 -2
- package/dist/createCheckpointApi.d.ts.map +1 -1
- package/dist/createCheckpointApi.js +2 -2
- package/dist/createEventStore.js +11 -11
- package/dist/createProcessManager.d.ts.map +1 -1
- package/dist/createProcessManager.js +4 -4
- package/dist/createProjection.js +5 -5
- package/dist/drizzle/index.d.ts.map +1 -1
- package/dist/drizzle/index.js +34 -28
- package/dist/drizzle/index.js.map +3 -3
- package/dist/drizzle/postgres.d.ts +17 -0
- package/dist/drizzle/postgres.d.ts.map +1 -1
- package/dist/drizzle/postgres.js +1 -1
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +5 -3
- package/dist/errors.js.map +1 -1
- package/dist/index.js +25 -23
- package/dist/index.js.map +1 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +21 -16
- package/dist/memory/index.js.map +3 -3
- package/dist/refresh.js +2 -2
- package/dist/replay.js +2 -2
- package/dist/snapshotGenerator.js +2 -2
- package/dist/tests/setup.d.ts +1 -0
- package/dist/tests/setup.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/createCheckpointApi.ts +86 -10
- package/src/createEventStore.ts +23 -4
- package/src/createProcessManager.ts +71 -32
- package/src/drizzle/index.ts +19 -4
- package/src/drizzle/postgres.ts +3 -0
- package/src/errors.ts +12 -0
- package/src/memory/index.ts +10 -2
- package/src/tests/eventStore.spec.ts +28 -0
- package/src/tests/processManagerMultiInstance.spec.ts +86 -3
- package/src/tests/setup.ts +12 -2
- package/dist/chunk-13wtyj2h.js.map +0 -10
- package/dist/chunk-8pchcyvm.js.map +0 -10
- package/dist/chunk-qdczt68t.js.map +0 -10
- package/dist/chunk-zf3x573k.js.map +0 -10
- /package/dist/{chunk-x90g388y.js.map → chunk-2fe8jjdc.js.map} +0 -0
- /package/dist/{chunk-9psg50cb.js.map → chunk-6yrb30rx.js.map} +0 -0
- /package/dist/{chunk-pmfzjavb.js.map → chunk-8qn1nqnh.js.map} +0 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from './createCheckpointApi.ts'
|
|
8
8
|
import type { LoadEvents, OnProgress } from './createEventStore.ts'
|
|
9
9
|
import {
|
|
10
|
+
CheckpointConcurrencyError,
|
|
10
11
|
ProcessManagerEventHandlerExecutionError,
|
|
11
12
|
RefreshingSkipError,
|
|
12
13
|
} from './errors.ts'
|
|
@@ -180,39 +181,75 @@ export function buildProcessManagerCreator(params: {
|
|
|
180
181
|
const runner = (async () => {
|
|
181
182
|
await unlocked
|
|
182
183
|
const refreshing = isRefreshing()
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
184
|
+
// Fold state into the process-manager checkpoint with optimistic
|
|
185
|
+
// concurrency control. Only the stateful `on<Event>` fold needs
|
|
186
|
+
// compare-and-swap — after-effects and stateless managers persist a
|
|
187
|
+
// plain cursor, which is safe to write monotonically. When a concurrent
|
|
188
|
+
// instance advances the checkpoint between our read and write, reload
|
|
189
|
+
// the latest state and re-apply this event on top of it.
|
|
190
|
+
const useCas = hasState && !after && !hydrating && !refreshing
|
|
191
|
+
while (true) {
|
|
192
|
+
const currentHandler = after
|
|
193
|
+
? createAfterEffects(state)[`after${event.type}`]
|
|
194
|
+
: createEventHandlers(state)[`on${event.type}`]
|
|
195
|
+
await withProcessManagerInfo(
|
|
196
|
+
{
|
|
197
|
+
name,
|
|
198
|
+
event,
|
|
199
|
+
},
|
|
200
|
+
async () => {
|
|
201
|
+
try {
|
|
202
|
+
await currentHandler!(event, { refreshing })
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error instanceof RefreshingSkipError) {
|
|
205
|
+
if (refreshing) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
throw new Error(
|
|
210
|
+
'RefreshingSkipError should never be thrown outside refresh',
|
|
211
|
+
)
|
|
195
212
|
}
|
|
196
213
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
214
|
+
if (!(error instanceof Error)) {
|
|
215
|
+
throw error
|
|
216
|
+
}
|
|
201
217
|
|
|
202
|
-
|
|
203
|
-
|
|
218
|
+
throw new ProcessManagerEventHandlerExecutionError(
|
|
219
|
+
name,
|
|
220
|
+
after ? `after${event.type}` : `on${event.type}`,
|
|
221
|
+
event,
|
|
222
|
+
error,
|
|
223
|
+
)
|
|
204
224
|
}
|
|
225
|
+
},
|
|
226
|
+
)
|
|
205
227
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
228
|
+
try {
|
|
229
|
+
await checkpointApi.upsert(
|
|
230
|
+
event,
|
|
231
|
+
{ state },
|
|
232
|
+
useCas ? 'cas' : 'monotonic',
|
|
233
|
+
)
|
|
234
|
+
break
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (!(error instanceof CheckpointConcurrencyError)) {
|
|
237
|
+
throw error
|
|
212
238
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
239
|
+
|
|
240
|
+
// A concurrent writer advanced the checkpoint. Reload the latest
|
|
241
|
+
// folded state (and version) and re-apply this event on top of it.
|
|
242
|
+
// This event is emitted only by this instance, so it is never
|
|
243
|
+
// already part of the reloaded state — we must always re-fold it
|
|
244
|
+
// (do not skip on position: events are processed out of global
|
|
245
|
+
// order across instances).
|
|
246
|
+
const checkpoint = await checkpointApi.load()
|
|
247
|
+
state =
|
|
248
|
+
checkpoint?.metadata?.state != null
|
|
249
|
+
? clone(checkpoint.metadata.state)
|
|
250
|
+
: clone(initialState)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
216
253
|
})()
|
|
217
254
|
|
|
218
255
|
if (exclusive) {
|
|
@@ -243,12 +280,14 @@ export function buildProcessManagerCreator(params: {
|
|
|
243
280
|
},
|
|
244
281
|
async beginSession() {
|
|
245
282
|
if (!hasState) return
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
)
|
|
283
|
+
// Load through the checkpoint API so the local version is synced with
|
|
284
|
+
// the store; the subsequent fold relies on it for compare-and-swap.
|
|
285
|
+
const checkpoint = await checkpointApi.load()
|
|
250
286
|
if (checkpoint) {
|
|
251
|
-
state =
|
|
287
|
+
state =
|
|
288
|
+
checkpoint.metadata?.state != null
|
|
289
|
+
? clone(checkpoint.metadata.state)
|
|
290
|
+
: clone(initialState)
|
|
252
291
|
lastEventPosition = checkpoint.lastEventPosition
|
|
253
292
|
} else {
|
|
254
293
|
state = clone(initialState)
|
package/src/drizzle/index.ts
CHANGED
|
@@ -258,22 +258,36 @@ export function createEventStore<const Root extends AnyAggregateRoot>({
|
|
|
258
258
|
}
|
|
259
259
|
},
|
|
260
260
|
checkpoint: {
|
|
261
|
-
async upsert(checkpoint) {
|
|
262
|
-
|
|
261
|
+
async upsert(checkpoint, expectedVersion) {
|
|
262
|
+
const metadata = superjson.stringify(checkpoint.metadata)
|
|
263
|
+
// Compare-and-swap: on an existing row the DO UPDATE only fires when
|
|
264
|
+
// the stored version still matches `expectedVersion`. A `null`
|
|
265
|
+
// expectation means "insert only" — if the row already exists the
|
|
266
|
+
// guard fails and no row is returned, signalling a conflict.
|
|
267
|
+
const committed = await db()
|
|
263
268
|
.insert(checkpoints)
|
|
264
269
|
.values({
|
|
265
270
|
lastEventPosition: checkpoint.lastEventPosition,
|
|
266
271
|
name: checkpoint.name,
|
|
267
272
|
type: checkpoint.type,
|
|
268
|
-
metadata
|
|
273
|
+
metadata,
|
|
274
|
+
version: checkpoint.version,
|
|
269
275
|
})
|
|
270
276
|
.onConflictDoUpdate({
|
|
271
277
|
target: [checkpoints.type, checkpoints.name],
|
|
272
278
|
set: {
|
|
273
279
|
lastEventPosition: checkpoint.lastEventPosition,
|
|
274
|
-
metadata
|
|
280
|
+
metadata,
|
|
281
|
+
version: checkpoint.version,
|
|
275
282
|
},
|
|
283
|
+
setWhere:
|
|
284
|
+
expectedVersion === null
|
|
285
|
+
? sql`false`
|
|
286
|
+
: eq(checkpoints.version, expectedVersion),
|
|
276
287
|
})
|
|
288
|
+
.returning({ version: checkpoints.version })
|
|
289
|
+
|
|
290
|
+
return committed.length > 0
|
|
277
291
|
},
|
|
278
292
|
async get(type, name) {
|
|
279
293
|
const [checkpoint] = await db()
|
|
@@ -282,6 +296,7 @@ export function createEventStore<const Root extends AnyAggregateRoot>({
|
|
|
282
296
|
name: checkpoints.name,
|
|
283
297
|
lastEventPosition: checkpoints.lastEventPosition,
|
|
284
298
|
metadata: checkpoints.metadata,
|
|
299
|
+
version: checkpoints.version,
|
|
285
300
|
})
|
|
286
301
|
.from(checkpoints)
|
|
287
302
|
.where(and(eq(checkpoints.type, type), eq(checkpoints.name, name)))
|
package/src/drizzle/postgres.ts
CHANGED
|
@@ -99,6 +99,9 @@ export const checkpoints = eventSourcing.table(
|
|
|
99
99
|
name: text().notNull(),
|
|
100
100
|
lastEventPosition: bigint({ mode: 'number' }).notNull(),
|
|
101
101
|
metadata: text().notNull(),
|
|
102
|
+
// Optimistic-concurrency guard: bumped on every write so concurrent
|
|
103
|
+
// instances can detect when they raced on the same checkpoint.
|
|
104
|
+
version: bigint({ mode: 'number' }).notNull().default(0),
|
|
102
105
|
},
|
|
103
106
|
(table) => [
|
|
104
107
|
unique().on(table.type, table.name),
|
package/src/errors.ts
CHANGED
|
@@ -14,6 +14,18 @@ export class ConcurrencyError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export class CheckpointConcurrencyError extends Error {
|
|
18
|
+
constructor(
|
|
19
|
+
public readonly checkpointType: 'projection' | 'processManager',
|
|
20
|
+
public readonly name: string,
|
|
21
|
+
public readonly expectedVersion: number | null,
|
|
22
|
+
) {
|
|
23
|
+
super(
|
|
24
|
+
`Concurrency error on ${checkpointType} checkpoint "${name}": expected version ${expectedVersion}, but it was modified by another writer`,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
export class ValidationError extends Error {
|
|
18
30
|
constructor(
|
|
19
31
|
message: string,
|
package/src/memory/index.ts
CHANGED
|
@@ -139,7 +139,7 @@ export function createEventStore<const Root extends AnyAggregateRoot>({
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
},
|
|
142
|
-
upsert(checkpoint) {
|
|
142
|
+
upsert(checkpoint, expectedVersion) {
|
|
143
143
|
let persisted: Checkpoint | undefined
|
|
144
144
|
for (const c of checkpoints) {
|
|
145
145
|
if (c.type === checkpoint.type && c.name === checkpoint.name) {
|
|
@@ -149,11 +149,19 @@ export function createEventStore<const Root extends AnyAggregateRoot>({
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
if (persisted) {
|
|
152
|
+
// Compare-and-swap: reject the write when another writer advanced the
|
|
153
|
+
// checkpoint, or when the caller expected to create a fresh one.
|
|
154
|
+
if (expectedVersion === null || persisted.version !== expectedVersion) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
152
157
|
persisted.lastEventPosition = checkpoint.lastEventPosition
|
|
153
158
|
persisted.metadata = checkpoint.metadata
|
|
159
|
+
persisted.version = checkpoint.version
|
|
154
160
|
} else {
|
|
155
|
-
checkpoints.add(checkpoint)
|
|
161
|
+
checkpoints.add({ ...checkpoint })
|
|
156
162
|
}
|
|
163
|
+
|
|
164
|
+
return true
|
|
157
165
|
},
|
|
158
166
|
delete(type, name) {
|
|
159
167
|
for (const checkpoint of checkpoints) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test'
|
|
2
2
|
|
|
3
|
+
import { ConcurrencyError } from '../index.ts'
|
|
3
4
|
import { createUserAggregate, setupEventStore } from './setup.ts'
|
|
4
5
|
|
|
5
6
|
describe('Event Store', () => {
|
|
@@ -67,6 +68,33 @@ describe('Event Store', () => {
|
|
|
67
68
|
expect(knowsUser).toBeTrue()
|
|
68
69
|
})
|
|
69
70
|
|
|
71
|
+
it('does not poison unrelated writes when one append fails', async () => {
|
|
72
|
+
const usersAggregate = createUserAggregate()
|
|
73
|
+
const { eventStore, pseudoEventStore } = setupEventStore(usersAggregate)
|
|
74
|
+
await eventStore.init()
|
|
75
|
+
|
|
76
|
+
const userA = crypto.randomUUID()
|
|
77
|
+
const userB = crypto.randomUUID()
|
|
78
|
+
|
|
79
|
+
// First create succeeds → stream A is now at version 1.
|
|
80
|
+
await usersAggregate.loadStream(userA).create('A').settled()
|
|
81
|
+
|
|
82
|
+
// A duplicate create builds a fresh stream at expected version 0, but the
|
|
83
|
+
// store is at version 1 → ConcurrencyError (the Tillhub-duplicate scenario).
|
|
84
|
+
await expect(
|
|
85
|
+
usersAggregate.newStream(userA).create('A duplicate').settled(),
|
|
86
|
+
).rejects.toBeInstanceOf(ConcurrencyError)
|
|
87
|
+
|
|
88
|
+
// An unrelated stream must still be writable. Before the fix, the shared
|
|
89
|
+
// append chain was left in a rejected state by the failure above, so this
|
|
90
|
+
// write re-propagated stream A's stale ConcurrencyError instead of running.
|
|
91
|
+
await usersAggregate.newStream(userB).create('B').settled()
|
|
92
|
+
|
|
93
|
+
expect(
|
|
94
|
+
pseudoEventStore.some((e) => e.streamId === userB),
|
|
95
|
+
).toBeTrue()
|
|
96
|
+
})
|
|
97
|
+
|
|
70
98
|
it('throws on interaction with posponed init', async () => {
|
|
71
99
|
const usersAggregate = createUserAggregate()
|
|
72
100
|
const { eventStore } = setupEventStore(usersAggregate, { autoInit: false })
|
|
@@ -88,7 +88,7 @@ function createSharedDatabase() {
|
|
|
88
88
|
if (cp.type === type && cp.name === name) return cp
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
|
-
upsert(checkpoint: Checkpoint) {
|
|
91
|
+
upsert(checkpoint: Checkpoint, expectedVersion: number | null) {
|
|
92
92
|
let persisted: Checkpoint | undefined
|
|
93
93
|
for (const c of checkpoints) {
|
|
94
94
|
if (c.type === checkpoint.type && c.name === checkpoint.name) {
|
|
@@ -97,11 +97,20 @@ function createSharedDatabase() {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
if (persisted) {
|
|
100
|
+
// Optimistic-concurrency guard — reject stale writes.
|
|
101
|
+
if (
|
|
102
|
+
expectedVersion === null ||
|
|
103
|
+
persisted.version !== expectedVersion
|
|
104
|
+
) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
100
107
|
persisted.lastEventPosition = checkpoint.lastEventPosition
|
|
101
108
|
persisted.metadata = checkpoint.metadata
|
|
109
|
+
persisted.version = checkpoint.version
|
|
102
110
|
} else {
|
|
103
|
-
checkpoints.add(checkpoint)
|
|
111
|
+
checkpoints.add({ ...checkpoint })
|
|
104
112
|
}
|
|
113
|
+
return true
|
|
105
114
|
},
|
|
106
115
|
delete(type: string, name: string) {
|
|
107
116
|
for (const cp of checkpoints) {
|
|
@@ -123,6 +132,9 @@ function createInstance(
|
|
|
123
132
|
sharedDb: ReturnType<typeof createSharedDatabase>,
|
|
124
133
|
opts?: {
|
|
125
134
|
onEventsEmitted?: (events: BaseOutputEvent[]) => MaybePromise<void>
|
|
135
|
+
// Hook invoked before every checkpoint write — lets a test pause one
|
|
136
|
+
// instance mid-flight to force a concurrent-write interleaving.
|
|
137
|
+
beforeCheckpointUpsert?: () => MaybePromise<void>
|
|
126
138
|
},
|
|
127
139
|
) {
|
|
128
140
|
const aggregate = createAggregateRoot('counters')
|
|
@@ -135,6 +147,14 @@ function createInstance(
|
|
|
135
147
|
},
|
|
136
148
|
}))
|
|
137
149
|
|
|
150
|
+
const checkpoint = {
|
|
151
|
+
...sharedDb.checkpoint,
|
|
152
|
+
async upsert(cp: Checkpoint, expectedVersion: number | null) {
|
|
153
|
+
await opts?.beforeCheckpointUpsert?.()
|
|
154
|
+
return sharedDb.checkpoint.upsert(cp, expectedVersion)
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
138
158
|
const eventStore = createEventStore({
|
|
139
159
|
aggregateRoots: [aggregate],
|
|
140
160
|
autoInit: false,
|
|
@@ -145,7 +165,7 @@ function createInstance(
|
|
|
145
165
|
return appended
|
|
146
166
|
},
|
|
147
167
|
loadEvents: sharedDb.loadEvents.bind(sharedDb),
|
|
148
|
-
checkpoint
|
|
168
|
+
checkpoint,
|
|
149
169
|
})
|
|
150
170
|
|
|
151
171
|
return { aggregate, eventStore }
|
|
@@ -211,4 +231,67 @@ describe('ProcessManager Multi-Instance', () => {
|
|
|
211
231
|
// Instance A also sees the correct state (loaded from shared checkpoint)
|
|
212
232
|
expect(await pmA.state()).toEqual({ total: 15 })
|
|
213
233
|
})
|
|
234
|
+
|
|
235
|
+
it('does not lose updates when two instances fold state concurrently', async () => {
|
|
236
|
+
const sharedDb = createSharedDatabase()
|
|
237
|
+
|
|
238
|
+
// Gate instance A's checkpoint write so we can deterministically force the
|
|
239
|
+
// race: A reads the (empty) checkpoint and folds its event, then parks
|
|
240
|
+
// right before persisting. While it is parked, B reads the same empty
|
|
241
|
+
// checkpoint, folds its own event and commits. When A is released its write
|
|
242
|
+
// is now stale — without optimistic concurrency it would clobber B's
|
|
243
|
+
// update and lose it.
|
|
244
|
+
const aAtGate = Promise.withResolvers<void>()
|
|
245
|
+
const releaseA = Promise.withResolvers<void>()
|
|
246
|
+
let aParked = false
|
|
247
|
+
|
|
248
|
+
const instanceA = createInstance(sharedDb, {
|
|
249
|
+
async beforeCheckpointUpsert() {
|
|
250
|
+
if (!aParked) {
|
|
251
|
+
aParked = true
|
|
252
|
+
aAtGate.resolve()
|
|
253
|
+
await releaseA.promise
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
const pmA = instanceA.eventStore
|
|
258
|
+
.createProcessManager('counter-pm')
|
|
259
|
+
.withState({ total: 0 })
|
|
260
|
+
.withEventHandlers((state) => ({
|
|
261
|
+
async onCounterIncremented({ payload }) {
|
|
262
|
+
state.total += payload.amount
|
|
263
|
+
},
|
|
264
|
+
}))
|
|
265
|
+
await instanceA.eventStore.init()
|
|
266
|
+
await instanceA.eventStore.isReady()
|
|
267
|
+
|
|
268
|
+
const instanceB = createInstance(sharedDb)
|
|
269
|
+
const pmB = instanceB.eventStore
|
|
270
|
+
.createProcessManager('counter-pm')
|
|
271
|
+
.withState({ total: 0 })
|
|
272
|
+
.withEventHandlers((state) => ({
|
|
273
|
+
async onCounterIncremented({ payload }) {
|
|
274
|
+
state.total += payload.amount
|
|
275
|
+
},
|
|
276
|
+
}))
|
|
277
|
+
await instanceB.eventStore.init()
|
|
278
|
+
await instanceB.eventStore.isReady()
|
|
279
|
+
|
|
280
|
+
// A starts processing but parks before persisting its checkpoint.
|
|
281
|
+
const aSettled = instanceA.aggregate.newStream().increment(10).settled()
|
|
282
|
+
await aAtGate.promise
|
|
283
|
+
|
|
284
|
+
// B reads the still-empty checkpoint and commits { total: 5 }.
|
|
285
|
+
await instanceB.aggregate.newStream().increment(5).settled()
|
|
286
|
+
|
|
287
|
+
// Release A — its stale write must be rejected, forcing it to reload B's
|
|
288
|
+
// state and re-fold on top of it.
|
|
289
|
+
releaseA.resolve()
|
|
290
|
+
await aSettled
|
|
291
|
+
|
|
292
|
+
const checkpoint = sharedDb.checkpoint.get('processManager', 'counter-pm')!
|
|
293
|
+
expect(checkpoint.metadata).toEqual({ state: { total: 15 } })
|
|
294
|
+
expect(await pmA.state()).toEqual({ total: 15 })
|
|
295
|
+
expect(await pmB.state()).toEqual({ total: 15 })
|
|
296
|
+
})
|
|
214
297
|
})
|
package/src/tests/setup.ts
CHANGED
|
@@ -142,7 +142,7 @@ export function setupEventStore<const Root extends AnyAggregateRoot>(
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
|
-
upsert(checkpoint) {
|
|
145
|
+
upsert(checkpoint, expectedVersion) {
|
|
146
146
|
let persistedCheckpoint: Checkpoint | undefined
|
|
147
147
|
for (const c of pseudoCheckpointStore) {
|
|
148
148
|
if (c.type === checkpoint.type && c.name === checkpoint.name) {
|
|
@@ -152,10 +152,20 @@ export function setupEventStore<const Root extends AnyAggregateRoot>(
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
if (persistedCheckpoint) {
|
|
155
|
+
if (
|
|
156
|
+
expectedVersion === null ||
|
|
157
|
+
persistedCheckpoint.version !== expectedVersion
|
|
158
|
+
) {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
155
161
|
persistedCheckpoint.lastEventPosition = checkpoint.lastEventPosition
|
|
162
|
+
persistedCheckpoint.metadata = checkpoint.metadata
|
|
163
|
+
persistedCheckpoint.version = checkpoint.version
|
|
156
164
|
} else {
|
|
157
|
-
pseudoCheckpointStore.add(checkpoint)
|
|
165
|
+
pseudoCheckpointStore.add({ ...checkpoint })
|
|
158
166
|
}
|
|
167
|
+
|
|
168
|
+
return true
|
|
159
169
|
},
|
|
160
170
|
delete(type, name) {
|
|
161
171
|
for (const checkpoint of pseudoCheckpointStore) {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/createProcessManager.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { AsyncLocalStorage } from 'node:async_hooks'\n\nimport {\n type CheckpointApi,\n type CheckpointMethods,\n createCheckpointApi,\n} from './createCheckpointApi.ts'\nimport type { LoadEvents, OnProgress } from './createEventStore.ts'\nimport {\n ProcessManagerEventHandlerExecutionError,\n RefreshingSkipError,\n} from './errors.ts'\nimport { isRefreshing, withRefreshing } from './refresh.ts'\nimport { clone } from './superjson.ts'\nimport type {\n AfterEffectHandlers,\n BaseOutputEvent,\n EventHandlers,\n EventTemplate,\n Keys,\n Merge,\n StreamEvents,\n} from './utilityTypes.js'\n\ntype ProcessManagerInfo = {\n name: string\n event: BaseOutputEvent\n}\nconst processManagerInfoStore = new AsyncLocalStorage<ProcessManagerInfo>()\n\nfunction withProcessManagerInfo(\n info: ProcessManagerInfo,\n handler: () => Promise<void>,\n) {\n return processManagerInfoStore.run(info, handler)\n}\n\nexport function getProcessManagerInfo() {\n return processManagerInfoStore.getStore()\n}\n\nexport function isInsideProcessManager() {\n return Boolean(getProcessManagerInfo())\n}\n\ntype ProcessManagerEventHandlers<Events extends EventTemplate> = EventHandlers<\n Events,\n { refreshing: boolean }\n>\n\ntype ProcessManagerAfterEffectHandlers<Events extends EventTemplate> =\n AfterEffectHandlers<Events, { refreshing: boolean }>\n\nexport type InternalProcessManager = {\n apply: (event: BaseOutputEvent) => Promise<void>\n applyAfter: (event: BaseOutputEvent) => Promise<void>\n beginSession: () => Promise<void>\n endSession: () => void\n setRelatedStreamEvents: (\n generateRelatedStreamEvents: (\n eventHandlers: EventHandlers<EventTemplate>,\n ) => StreamEvents[],\n ) => void\n isReady(): Promise<void>\n beginRefresh(): Promise<void>\n progressRefresh(\n index: number,\n event: BaseOutputEvent,\n after: boolean,\n ): Promise<void>\n endRefresh(): void\n init(catchUp: boolean): Promise<void>\n hasState(): boolean\n}\n\nexport type ProcessManagerApi<\n State = never,\n Events extends EventTemplate = never,\n Flags extends {\n withState: boolean\n withEventHandlers: boolean\n concurrent: boolean\n withAfterEffects: boolean\n } = {\n withState: false\n withEventHandlers: false\n concurrent: false\n withAfterEffects: false\n },\n> = {\n withState: <NextState>(\n state: NextState,\n ) => Omit<\n ProcessManagerApi<NextState, Events, Merge<Flags, { withState: true }>>,\n Keys<Merge<Flags, { withState: true }>>\n >\n withEventHandlers: (\n handlers: [State] extends [never]\n ? ProcessManagerEventHandlers<Events>\n : (state: State) => ProcessManagerEventHandlers<Events>,\n ) => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { withEventHandlers: true }>>,\n Keys<Merge<Flags, { withEventHandlers: true }>>\n >\n withAfterEffects: (\n handlers: [State] extends [never]\n ? ProcessManagerAfterEffectHandlers<Events>\n : (state: State) => ProcessManagerAfterEffectHandlers<Events>,\n ) => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { withAfterEffects: true }>>,\n Keys<Merge<Flags, { withAfterEffects: true }>>\n >\n concurrent: () => Omit<\n ProcessManagerApi<State, Events, Merge<Flags, { concurrent: true }>>,\n Keys<Merge<Flags, { concurrent: true }>>\n >\n isReady(): Promise<void>\n $inferState: State\n} & (Flags['withState'] extends true\n ? {\n refreshState: () => Promise<void>\n state: () => Promise<State>\n }\n : {})\n\nexport function buildProcessManagerCreator(params: {\n loadEvents: LoadEvents\n checkpoint: CheckpointMethods\n onRefresh?: OnProgress\n}) {\n return (name: string): ProcessManagerApi => {\n let idle = true\n let createEventHandlers: (\n state: any,\n ) => ProcessManagerEventHandlers<any> = () => ({})\n let createAfterEffects: (\n state: any,\n ) => ProcessManagerAfterEffectHandlers<any> = () => ({})\n\n let hasState = false\n let state: any = null\n let initialState: any = null\n let exclusive = true\n let sessionActive = false\n let generateRelatedStreamEvents: (\n eventHandlers: EventHandlers<any>,\n ) => StreamEvents[]\n let checkpointApi: CheckpointApi\n let lastEventPosition = -1\n const { promise: hydrated, resolve: resolveHydrated } =\n Promise.withResolvers<void>()\n\n const locks = new Map<string, Promise<void>>()\n const apply = async (\n event: BaseOutputEvent,\n after: boolean,\n hydrating = false,\n ) => {\n const handler = after\n ? createAfterEffects(state)[`after${event.type}`]\n : createEventHandlers(state)[`on${event.type}`]\n\n if (!handler) {\n return Promise.resolve()\n }\n\n if (hydrating) {\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 const unlocked = !exclusive\n ? Promise.resolve()\n : (locks.get(event.streamId)?.catch(() => {}) ?? Promise.resolve())\n\n const runner = (async () => {\n await unlocked\n const refreshing = isRefreshing()\n await withProcessManagerInfo(\n {\n name,\n event,\n },\n async () => {\n try {\n await handler(event, { refreshing })\n } catch (error) {\n if (error instanceof RefreshingSkipError) {\n if (refreshing) {\n return\n }\n\n throw new Error(\n 'RefreshingSkipError should never be thrown outside refresh',\n )\n }\n\n if (!(error instanceof Error)) {\n throw error\n }\n\n throw new ProcessManagerEventHandlerExecutionError(\n name,\n after ? `after${event.type}` : `on${event.type}`,\n event,\n error,\n )\n }\n },\n )\n await checkpointApi.upsert(event, { state })\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 resolveRefresh: null | (() => void) = null\n\n const api = {\n name,\n hasState() {\n return hasState\n },\n async isReady() {\n await hydrated\n },\n async beginSession() {\n if (!hasState) return\n const checkpoint = await params.checkpoint.get(\n 'processManager',\n name,\n )\n if (checkpoint) {\n state = checkpoint.metadata?.state ?? clone(initialState)\n lastEventPosition = checkpoint.lastEventPosition\n } else {\n state = clone(initialState)\n lastEventPosition = -1\n }\n sessionActive = true\n },\n endSession() {\n if (!hasState) return\n state = null\n sessionActive = false\n },\n async apply(event) {\n await apply(event, false)\n },\n async applyAfter(event) {\n await apply(event, true)\n },\n withState(nextState) {\n state = clone(nextState)\n initialState = clone(nextState)\n hasState = true\n return api as any\n },\n concurrent() {\n exclusive = false\n return api\n },\n withEventHandlers(handlers) {\n createEventHandlers =\n typeof handlers === 'function' ? (handlers as any) : () => handlers\n\n return api\n },\n withAfterEffects(handlers) {\n createAfterEffects =\n typeof handlers === 'function' ? (handlers as any) : () => handlers\n\n return api\n },\n setRelatedStreamEvents(generate) {\n generateRelatedStreamEvents = generate\n },\n async beginRefresh() {\n params.onRefresh?.begin?.(name)\n await checkpointApi.clear()\n lastEventPosition = -1\n state = initialState\n },\n async progressRefresh(index, event, after) {\n await apply(event, after, true)\n\n if (\n !checkpointApi.relatedStreamEvents ||\n checkpointApi.relatedStreamEvents.some((streamEvent) =>\n streamEvent.events.includes(event.type),\n )\n ) {\n params.onRefresh?.progress?.(name, index, event)\n }\n },\n endRefresh() {\n params.onRefresh?.end?.(name)\n resolveHydrated()\n },\n async refreshState() {\n if (!idle) {\n await refresh()\n return\n }\n const { promise, resolve } = Promise.withResolvers<void>()\n resolveRefresh = resolve\n return promise\n },\n async state() {\n await hydrated\n if (!sessionActive && hasState) {\n const checkpoint = await params.checkpoint.get(\n 'processManager',\n name,\n )\n return checkpoint?.metadata?.state ?? clone(initialState)\n }\n return state\n },\n async init(catchUp) {\n idle = false\n const relatedStreamEvents = generateRelatedStreamEvents({\n ...createEventHandlers(null),\n ...createAfterEffects(null),\n })\n\n checkpointApi = createCheckpointApi({\n ...params.checkpoint,\n name,\n relatedStreamEvents,\n type: 'processManager',\n loadEvents: params.loadEvents,\n apply: (event) => apply(event, false, true),\n applyAfter: (event) => apply(event, true, true),\n })\n\n if (resolveRefresh) {\n await refresh()\n return\n }\n if (!catchUp) {\n return\n }\n\n await checkpointApi.rehydrated({\n getMetadata() {\n return { state }\n },\n onMetadata(metadata) {\n state = metadata.state ?? state\n },\n })\n\n // Release state after hydration — it will be loaded\n // from the checkpoint on the next beginSession call\n state = null\n\n resolveHydrated()\n },\n $inferState: null as never,\n } as ProcessManagerApi & InternalProcessManager\n\n async function refresh() {\n await withRefreshing(async () => {\n await api.beginRefresh()\n await checkpointApi.rehydrated({\n getMetadata() {\n return { state }\n },\n onProgress(index, event) {\n params.onRefresh?.progress?.(name, index, event)\n },\n })\n })\n api.endRefresh()\n }\n\n return api\n }\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "1BE5E249A230C5FD64756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/createEventStore.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import type {\n AggregateRootApi,\n AggregateRootSnapshotMethods,\n AnyAggregateRoot,\n InternalAggregateRootApi,\n} from './createAggregateRoot.ts'\nimport type { CheckpointMethods } from './createCheckpointApi.ts'\nimport {\n type EventListenerApi,\n type InternalEventListener,\n createEventListener,\n} from './createEventListener.ts'\nimport {\n type InternalProcessManager,\n type ProcessManagerApi,\n buildProcessManagerCreator,\n} from './createProcessManager.ts'\nimport {\n type InternalProjection,\n type ProjectionApi,\n type ProjectionApiWithSnapshots,\n type ProjectionSnapshotMethods,\n buildProjectionCreator,\n} from './createProjection.ts'\nimport lock, { type LockCreator } from './lock.ts'\nimport { withRefreshing } from './refresh.ts'\nimport createTransaction from './transaction.ts'\nimport type {\n BaseInputEvent,\n BaseOutputEvent,\n EventHandlers,\n EventTemplate,\n MaybePromise,\n Stream,\n StreamEvents,\n StreamId,\n StreamIdEvents,\n} from './utilityTypes.ts'\nimport {\n type WrappedEvent,\n unwrapEvent,\n wrappedEventBuilder,\n} from './wrappedEventBuilder.ts'\n\nexport type LoadEvents = (\n select:\n | Stream\n | StreamId\n | Stream[]\n | StreamId[]\n | StreamEvents\n | StreamEvents[]\n | StreamIdEvents\n | StreamIdEvents[]\n | null,\n range?: { from: number; to?: number },\n) => AsyncGenerator<BaseOutputEvent>\n\ntype AppendEvents = (\n streamId: StreamId,\n events: BaseInputEvent[],\n expectedVersion: number,\n) => MaybePromise<BaseOutputEvent[]>\n\nexport type InternalAppendEvents = (\n streamId: StreamId,\n events: BaseInputEvent[],\n expectedVersion: number,\n) => MaybePromise<BaseOutputEvent[]>\n\nexport type EmitEvents = (events: BaseOutputEvent[]) => Promise<void>\n\nexport type EventsFromRoot<Root> =\n Root extends Omit<AggregateRootApi<any, any, infer E>, any> ? E : never\n\nexport type OnProgress = {\n begin?: (projectionName: string) => void\n progress?: (\n projectionName: string,\n index: number,\n event: BaseOutputEvent,\n ) => void\n end?: (projectionName: string) => void\n}\n\ninterface BaseEventStoreParams<Root extends AnyAggregateRoot> {\n loadEvents: LoadEvents\n appendEvents: AppendEvents\n aggregateRoots: Root[]\n checkpoint: CheckpointMethods\n lock?: LockCreator\n projectionSnapshot?: ProjectionSnapshotMethods\n onProjectionReplay?: OnProgress\n onProcessManagerRefresh?: OnProgress\n autoInit?: boolean\n}\n\nexport interface EventStoreParamsWithAggregateRootSnapshots<\n Root extends AnyAggregateRoot,\n> extends BaseEventStoreParams<Root> {\n aggregateRootSnapshot: AggregateRootSnapshotMethods\n postProcessEvent?: (\n event: WrappedEvent<EventsFromRoot<Root>>,\n aggregateRoot: Root,\n ) => WrappedEvent\n}\n\nexport interface EventStoreParamsWithoutAggregateRootSnapshots<\n Root extends AnyAggregateRoot,\n> extends BaseEventStoreParams<Root> {\n aggregateRoots: Root[]\n aggregateRootSnapshot?: never\n postProcessEvent?: (\n event: WrappedEvent<EventsFromRoot<Root>>,\n aggregateRoot: Root,\n ) => WrappedEvent\n}\n\nexport type EventStore<\n Event extends EventTemplate,\n Root extends AnyAggregateRoot,\n SupportsProjectionSnapshots = boolean,\n> = {\n createProjection(\n name: string,\n ): SupportsProjectionSnapshots extends true\n ? ProjectionApiWithSnapshots<Event>\n : ProjectionApi<Event>\n createProcessManager(name: string): ProcessManagerApi<never, Event>\n createEventListener(name: string): EventListenerApi<Event>\n rebuild: () => Promise<void>\n isReady: () => Promise<void>\n init: () => Promise<EventStore<Event, Root, SupportsProjectionSnapshots>>\n getAggregateRoot<S extends Root['type']>(\n type: S,\n ): Root extends { type: S } ? Root : never\n transaction<T extends () => any>(handler: T): Promise<Awaited<ReturnType<T>>>\n}\n\nexport function createEventStore<const Root extends AnyAggregateRoot>(\n params: EventStoreParamsWithoutAggregateRootSnapshots<Root>,\n): EventStore<EventsFromRoot<Root>, Root, false>\nexport function createEventStore<const Root extends AnyAggregateRoot>(\n params: EventStoreParamsWithAggregateRootSnapshots<Root>,\n): EventStore<EventsFromRoot<Root>, Root, true>\nexport function createEventStore<const Root extends AnyAggregateRoot>(\n params:\n | EventStoreParamsWithoutAggregateRootSnapshots<Root>\n | EventStoreParamsWithAggregateRootSnapshots<Root>,\n) {\n const transaction = createTransaction()\n const { promise: initialized, resolve: resolveInitialized } =\n Promise.withResolvers<void>()\n const projections = new Map<string, InternalProjection>()\n const processManagers = new Map<string, InternalProcessManager>()\n const eventListeners = new Map<string, InternalEventListener>()\n const aggregatRootsByEvents = new Map<\n string,\n { aggregateRoot: InternalAggregateRootApi; eventType: string }\n >()\n\n let allReplaying = false\n let initializing = false\n\n const assertInitializing = () => {\n if (!initializing && params.autoInit === false) {\n throw new Error(\n 'Cannot emit events because the event store is not initialized. Set autoInit to true, or explicitly call .init() before emitting events.',\n )\n }\n }\n\n let exclusiveEmitEventsChain: Promise<any> = Promise.resolve()\n const emitEvents: EmitEvents = async (events) => {\n exclusiveEmitEventsChain = exclusiveEmitEventsChain.then(async () => {\n await Promise.all(processManagers.values().map((pm) => pm.beginSession()))\n\n try {\n for (const event of events) {\n await Promise.all(\n [\n ...processManagers.values(),\n ...projections.values(),\n ...eventListeners.values(),\n ].map((listener) => listener.apply(event)),\n )\n }\n\n for (const event of events) {\n await Promise.all(\n [...processManagers.values(), ...eventListeners.values()].map(\n (processManager) => processManager.applyAfter(event),\n ),\n )\n }\n } finally {\n for (const pm of processManagers.values()) {\n pm.endSession()\n }\n }\n })\n\n return exclusiveEmitEventsChain\n }\n\n const loadEvents: LoadEvents = (select, range) => {\n assertInitializing()\n return params.loadEvents(select, range)\n }\n\n let exclusiveAppendEventsChain: Promise<any> = initialized\n const appendEvents: InternalAppendEvents = async (\n streamId,\n events,\n expectedVersion,\n ) => {\n assertInitializing()\n exclusiveAppendEventsChain = exclusiveAppendEventsChain.then(async () => {\n if (events.length === 0) {\n return []\n }\n await new Promise((resolve) => setImmediate(resolve)) // edge case for sync setup, see 'can execute after effect' test\n\n const processedEvents = events.map((rawEvent) => {\n const event = structuredClone(rawEvent)\n if (params.postProcessEvent) {\n const wrappedEvent = wrappedEventBuilder(event) as WrappedEvent<any>\n const { aggregateRoot } = aggregatRootsByEvents.get(event.type)!\n const processedEvent = unwrapEvent(\n params.postProcessEvent(\n wrappedEvent,\n aggregateRoot as unknown as Root,\n ),\n )\n Object.assign(event, processedEvent)\n }\n\n return event\n })\n\n return params.appendEvents(streamId, processedEvents, expectedVersion)\n })\n\n return exclusiveAppendEventsChain\n }\n\n for (const aggregateRoot of params.aggregateRoots as unknown as InternalAggregateRootApi[]) {\n aggregateRoot.eventTypes.forEach((eventType) => {\n aggregatRootsByEvents.set(eventType, {\n aggregateRoot,\n eventType,\n })\n })\n\n aggregateRoot.linkEventStore(\n loadEvents,\n appendEvents,\n emitEvents,\n transaction,\n params.lock ?? lock(5000),\n params.aggregateRootSnapshot,\n )\n }\n\n const baseCreateProjection = buildProjectionCreator({\n loadEvents,\n checkpoint: params.checkpoint,\n snapshot: params.projectionSnapshot,\n onReplay: params.onProjectionReplay,\n })\n\n const baseCreateProcessManager = buildProcessManagerCreator({\n loadEvents,\n checkpoint: params.checkpoint,\n onRefresh: params.onProcessManagerRefresh,\n })\n\n const getRelatedStreamEvents = (\n handlers: EventHandlers<any>,\n ): StreamEvents[] => {\n const relatedStreamEvents = new Map<string, Set<string>>()\n for (const handlerName of Object.keys(handlers)) {\n const sanitizedHandlerName = handlerName.replace(/^(on|after)/, '')\n if (!aggregatRootsByEvents.has(sanitizedHandlerName)) {\n throw new Error(\n `event handler ${handlerName} has no corresponding event in any aggregate root`,\n )\n }\n const {\n aggregateRoot: { type: aggregateRootName },\n eventType,\n } = aggregatRootsByEvents.get(sanitizedHandlerName)!\n if (!relatedStreamEvents.has(aggregateRootName)) {\n relatedStreamEvents.set(aggregateRootName, new Set())\n }\n relatedStreamEvents.get(aggregateRootName)!.add(eventType)\n }\n\n return Array.from(relatedStreamEvents, ([stream, events]) => ({\n stream,\n events: Array.from(events),\n }))\n }\n\n const initialize = async () => {\n initializing = true\n await Promise.all(\n projections.values().map((projection) => projection.init(true)),\n )\n await Promise.all(\n processManagers\n .values()\n .map((processManager) => processManager.init(true)),\n )\n resolveInitialized()\n }\n setImmediate(() => {\n if (allReplaying || params.autoInit === false) {\n return\n }\n\n initialize()\n })\n\n const api = {\n createProjection(name) {\n if (projections.has(name)) {\n throw new Error(`Projection ${name} is already registered`)\n }\n const api = baseCreateProjection(name)\n const internalApi = api as unknown as InternalProjection\n internalApi.setRelatedStreamEvents((handlers) =>\n getRelatedStreamEvents(handlers),\n )\n projections.set(name, internalApi)\n if (initializing) {\n internalApi.init(true)\n }\n return api\n },\n createProcessManager(name) {\n if (processManagers.has(name)) {\n throw new Error(`Process Manager ${name} is already registered`)\n }\n const api = baseCreateProcessManager(name)\n\n const internalApi = api as unknown as InternalProcessManager\n internalApi.setRelatedStreamEvents((handlers) =>\n getRelatedStreamEvents(handlers),\n )\n processManagers.set(name, internalApi)\n if (initializing) {\n internalApi.init(true)\n }\n return api\n },\n createEventListener(name) {\n if (eventListeners.has(name)) {\n throw new Error(`Event Listener ${name} is already registered`)\n }\n\n const api = createEventListener(name)\n\n const internalApi = api as unknown as InternalEventListener\n eventListeners.set(name, internalApi)\n return api\n },\n async isReady() {\n await initialized\n await Promise.all([\n ...projections.values().map((projection) => projection.isReady()),\n ...processManagers.values().map((projection) => projection.isReady()),\n ])\n },\n async rebuild() {\n allReplaying = true\n initializing = true\n const replayProjections = Array.from(projections.values()).filter(\n (projection) => projection.canReplay(),\n )\n const replayProcessManagers = Array.from(processManagers.values()).filter(\n (processManager) => processManager.hasState(),\n )\n\n await Promise.all([\n ...replayProjections.map((projection) => projection.init(false)),\n ...replayProcessManagers.map((projection) => projection.init(false)),\n ...replayProjections.map((projection) => projection.beginReplay()),\n ...replayProcessManagers.map((processManager) =>\n processManager.beginRefresh(),\n ),\n ])\n\n await withRefreshing(async () => {\n let index = 0\n for await (const event of loadEvents(null)) {\n index++\n await Promise.all([\n ...replayProjections.map((projection) =>\n projection.progressReplay(index, event),\n ),\n ...replayProcessManagers.map((processManager) =>\n processManager.progressRefresh(index, event, false),\n ),\n ...Array.from(eventListeners.values(), (listener) =>\n listener.apply(event),\n ),\n ])\n await Promise.all([\n ...replayProcessManagers.map((processManager) =>\n processManager.progressRefresh(index, event, true),\n ),\n ...Array.from(eventListeners.values(), (listener) =>\n listener.applyAfter(event),\n ),\n ])\n }\n })\n\n await Promise.all([\n ...replayProjections.map((projection) => projection.endReplay()),\n ...replayProcessManagers.map((processManager) =>\n processManager.endRefresh(),\n ),\n ])\n\n await Promise.all(\n projections.values().map(async (projection) => {\n if (!projection.canReplay()) {\n await projection.init(true)\n }\n }),\n )\n resolveInitialized()\n },\n async init() {\n if (initializing) {\n return api\n }\n await initialize()\n return api\n },\n getAggregateRoot(type) {\n return params.aggregateRoots.find(\n (aggregateRoot) => aggregateRoot.type === type,\n ) as any\n },\n async transaction(handler) {\n return transaction.run(handler)\n },\n } as EventStore<any, Root>\n\n return api\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "E51438C1FD14F00B64756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/errors.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import z from 'zod/v4'\n\nimport type { BaseOutputEvent, OutputEvent } from './utilityTypes.js'\n\nexport class ConcurrencyError extends Error {\n constructor(\n streamId: { stream: string; id: string },\n expectedVersion: number,\n actualVersion: number,\n ) {\n super(\n `Concurrency error on ${streamId.stream}(${streamId.id}): expected v${expectedVersion}, actual v${actualVersion}`,\n )\n }\n}\n\nexport class ValidationError extends Error {\n constructor(\n message: string,\n public readonly zodError: z.ZodError,\n ) {\n super(`${message}: ${z.prettifyError(zodError)}`)\n }\n}\n\nexport class EventValidationError extends ValidationError {}\n\nexport class InfiniteLoopError extends Error {}\n\nexport class CommandError<T extends Error> extends Error {\n constructor(\n public readonly aggregateRootType: string,\n public readonly commandName: string,\n public readonly streamId: string,\n public readonly originalError: T,\n ) {\n super(\n `Error executing command \"${commandName}\" on aggregate root \"${aggregateRootType}\" for stream id \"${streamId}\": ${originalError.message}`,\n )\n }\n}\n\nexport class ProjectionReplayLoopError extends Error {\n constructor(\n message: string,\n public readonly projection: { name: string; eventHandler: string },\n public readonly event: OutputEvent<any>,\n ) {\n super(message)\n }\n}\n\nexport class RefreshingSkipError extends Error {}\n\nexport class ReplaySkipError extends Error {}\n\nexport class ProjectionEventHandlerExecutionError<\n T extends Error,\n> extends Error {\n constructor(\n public readonly projection: string,\n public readonly eventHandler: string,\n public readonly event: BaseOutputEvent,\n public readonly originalError: T,\n ) {\n super(\n `Error execution event handler \"${eventHandler}\" in projection \"${projection}\": ${originalError.message}`,\n )\n }\n}\n\nexport class ProcessManagerEventHandlerExecutionError<\n T extends Error,\n> extends Error {\n constructor(\n public readonly processManager: string,\n public readonly eventHandler: string,\n public readonly event: BaseOutputEvent,\n public readonly originalError: T,\n ) {\n super(\n `Error execution event handler \"${eventHandler}\" in process manager \"${processManager}\": ${originalError.message}`,\n )\n }\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": ";AAAA;AAAA;AAIO,MAAM,yBAAyB,MAAM;AAAA,EAC1C,WAAW,CACT,UACA,iBACA,eACA;AAAA,IACA,MACE,wBAAwB,SAAS,UAAU,SAAS,kBAAkB,4BAA4B,eACpG;AAAA;AAEJ;AAAA;AAEO,MAAM,wBAAwB,MAAM;AAAA,EAGvB;AAAA,EAFlB,WAAW,CACT,SACgB,UAChB;AAAA,IACA,MAAM,GAAG,YAAY,EAAE,cAAc,QAAQ,GAAG;AAAA,IAFhC;AAAA;AAIpB;AAAA;AAEO,MAAM,6BAA6B,gBAAgB;AAAC;AAAA;AAEpD,MAAM,0BAA0B,MAAM;AAAC;AAAA;AAEvC,MAAM,qBAAsC,MAAM;AAAA,EAErC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAJlB,WAAW,CACO,mBACA,aACA,UACA,eAChB;AAAA,IACA,MACE,4BAA4B,mCAAmC,qCAAqC,cAAc,cAAc,SAClI;AAAA,IAPgB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAMpB;AAAA;AAEO,MAAM,kCAAkC,MAAM;AAAA,EAGjC;AAAA,EACA;AAAA,EAHlB,WAAW,CACT,SACgB,YACA,OAChB;AAAA,IACA,MAAM,OAAO;AAAA,IAHG;AAAA,IACA;AAAA;AAIpB;AAAA;AAEO,MAAM,4BAA4B,MAAM;AAAC;AAAA;AAEzC,MAAM,wBAAwB,MAAM;AAAC;AAAA;AAErC,MAAM,6CAEH,MAAM;AAAA,EAEI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAJlB,WAAW,CACO,YACA,cACA,OACA,eAChB;AAAA,IACA,MACE,kCAAkC,gCAAgC,gBAAgB,cAAc,SAClG;AAAA,IAPgB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAMpB;AAAA;AAEO,MAAM,iDAEH,MAAM;AAAA,EAEI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAJlB,WAAW,CACO,gBACA,cACA,OACA,eAChB;AAAA,IACA,MACE,kCAAkC,qCAAqC,oBAAoB,cAAc,SAC3G;AAAA,IAPgB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAMpB;",
|
|
8
|
-
"debugId": "7F0D85CC4F4ACE1E64756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/createCheckpointApi.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import z from 'zod/v4'\n\nimport type { LoadEvents } from './createEventStore.ts'\nimport { ValidationError } from './errors.ts'\nimport type {\n BaseOutputEvent,\n MaybePromise,\n StreamEvents,\n} from './utilityTypes.ts'\n\nconst checkpointSchema = z.object({\n lastEventPosition: z.int(),\n type: z.enum(['projection', 'processManager']),\n name: z.string(),\n metadata: z.record(z.string(), z.any()),\n})\n\nexport type Checkpoint = z.output<typeof checkpointSchema>\n\nexport type CheckpointMethods = {\n get: (\n type: 'projection' | 'processManager',\n name: string,\n ) => MaybePromise<Checkpoint | null | undefined>\n delete: (\n type: 'projection' | 'processManager',\n name: string,\n ) => MaybePromise<void>\n upsert: (checkpoint: Checkpoint) => MaybePromise<void>\n}\n\nexport function createCheckpointApi(\n params: CheckpointMethods & {\n loadEvents: LoadEvents\n relatedStreamEvents: StreamEvents[] | null\n name: string\n type: 'processManager' | 'projection'\n apply: (event: BaseOutputEvent) => MaybePromise<void>\n applyAfter?: (event: BaseOutputEvent) => MaybePromise<void>\n },\n) {\n const api = {\n relatedStreamEvents: params.relatedStreamEvents,\n async clear() {\n await params.delete(params.type, params.name)\n },\n async upsert(event: BaseOutputEvent, metadata: Record<string, any> = {}) {\n await params.upsert({\n lastEventPosition: event.position,\n type: params.type,\n name: params.name,\n metadata,\n })\n },\n async rehydrated(\n options: {\n onMetadata?: (metadata: Record<string, any>) => unknown\n getMetadata?: () => Record<string, any>\n onProgress?: (index: number, event: BaseOutputEvent) => void\n } = {},\n ) {\n const checkpoint = await params.get(params.type, params.name)\n let startPosition = 0\n if (checkpoint) {\n const result = checkpointSchema.safeParse(checkpoint)\n if (!result.success) {\n throw new ValidationError(\n `loaded checkpoint for ${params.type} ${params.name} does not comply with schema`,\n result.error,\n )\n }\n startPosition = checkpoint.lastEventPosition + 1\n options.onMetadata?.(checkpoint.metadata)\n }\n\n const events = params.loadEvents(params.relatedStreamEvents, {\n from: startPosition,\n })\n\n let index = 0\n let lastEvent: BaseOutputEvent | null = null\n for await (const event of events) {\n lastEvent = event\n await params.apply(event)\n index++\n options.onProgress?.(index, event)\n }\n\n if (!lastEvent) {\n return // up to date\n }\n\n if (params.applyAfter) {\n for await (const event of events) {\n await params.applyAfter(event)\n }\n }\n\n await api.upsert(lastEvent, options.getMetadata?.())\n },\n }\n\n return api\n}\n\nexport type CheckpointApi = ReturnType<typeof createCheckpointApi>\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": ";;;;;AAAA;AAUA,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,mBAAmB,EAAE,IAAI;AAAA,EACzB,MAAM,EAAE,KAAK,CAAC,cAAc,gBAAgB,CAAC;AAAA,EAC7C,MAAM,EAAE,OAAO;AAAA,EACf,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,IAAI,CAAC;AACxC,CAAC;AAgBM,SAAS,mBAAmB,CACjC,QAQA;AAAA,EACA,MAAM,MAAM;AAAA,IACV,qBAAqB,OAAO;AAAA,SACtB,MAAK,GAAG;AAAA,MACZ,MAAM,OAAO,OAAO,OAAO,MAAM,OAAO,IAAI;AAAA;AAAA,SAExC,OAAM,CAAC,OAAwB,WAAgC,CAAC,GAAG;AAAA,MACvE,MAAM,OAAO,OAAO;AAAA,QAClB,mBAAmB,MAAM;AAAA,QACzB,MAAM,OAAO;AAAA,QACb,MAAM,OAAO;AAAA,QACb;AAAA,MACF,CAAC;AAAA;AAAA,SAEG,WAAU,CACd,UAII,CAAC,GACL;AAAA,MACA,MAAM,aAAa,MAAM,OAAO,IAAI,OAAO,MAAM,OAAO,IAAI;AAAA,MAC5D,IAAI,gBAAgB;AAAA,MACpB,IAAI,YAAY;AAAA,QACd,MAAM,SAAS,iBAAiB,UAAU,UAAU;AAAA,QACpD,IAAI,CAAC,OAAO,SAAS;AAAA,UACnB,MAAM,IAAI,gBACR,yBAAyB,OAAO,QAAQ,OAAO,oCAC/C,OAAO,KACT;AAAA,QACF;AAAA,QACA,gBAAgB,WAAW,oBAAoB;AAAA,QAC/C,QAAQ,aAAa,WAAW,QAAQ;AAAA,MAC1C;AAAA,MAEA,MAAM,SAAS,OAAO,WAAW,OAAO,qBAAqB;AAAA,QAC3D,MAAM;AAAA,MACR,CAAC;AAAA,MAED,IAAI,QAAQ;AAAA,MACZ,IAAI,YAAoC;AAAA,MACxC,iBAAiB,SAAS,QAAQ;AAAA,QAChC,YAAY;AAAA,QACZ,MAAM,OAAO,MAAM,KAAK;AAAA,QACxB;AAAA,QACA,QAAQ,aAAa,OAAO,KAAK;AAAA,MACnC;AAAA,MAEA,IAAI,CAAC,WAAW;AAAA,QACd;AAAA,MACF;AAAA,MAEA,IAAI,OAAO,YAAY;AAAA,QACrB,iBAAiB,SAAS,QAAQ;AAAA,UAChC,MAAM,OAAO,WAAW,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,MAEA,MAAM,IAAI,OAAO,WAAW,QAAQ,cAAc,CAAC;AAAA;AAAA,EAEvD;AAAA,EAEA,OAAO;AAAA;",
|
|
8
|
-
"debugId": "EDA59BA1E9DE8B8764756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|