@l-etabli/events 0.5.0 → 0.6.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 +146 -155
- package/dist/adapters/in-memory/InMemoryEventBus.cjs +8 -3
- package/dist/adapters/in-memory/InMemoryEventBus.cjs.map +1 -1
- package/dist/adapters/in-memory/InMemoryEventBus.d.cts +10 -2
- package/dist/adapters/in-memory/InMemoryEventBus.d.ts +10 -2
- package/dist/adapters/in-memory/InMemoryEventBus.mjs +11 -4
- package/dist/adapters/in-memory/InMemoryEventBus.mjs.map +1 -1
- package/dist/adapters/in-memory/index.d.cts +1 -0
- package/dist/adapters/in-memory/index.d.ts +1 -0
- package/dist/adapters/kysely/KyselyEventQueries.cjs +2 -0
- package/dist/adapters/kysely/KyselyEventQueries.cjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventQueries.mjs +2 -0
- package/dist/adapters/kysely/KyselyEventQueries.mjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventRepository.cjs +5 -1
- package/dist/adapters/kysely/KyselyEventRepository.cjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventRepository.mjs +5 -1
- package/dist/adapters/kysely/KyselyEventRepository.mjs.map +1 -1
- package/dist/adapters/kysely/migration.cjs +1 -1
- package/dist/adapters/kysely/migration.cjs.map +1 -1
- package/dist/adapters/kysely/migration.mjs +1 -1
- package/dist/adapters/kysely/migration.mjs.map +1 -1
- package/dist/adapters/kysely/types.cjs.map +1 -1
- package/dist/adapters/kysely/types.d.cts +7 -3
- package/dist/adapters/kysely/types.d.ts +7 -3
- package/dist/createNewEvent.cjs +19 -12
- package/dist/createNewEvent.cjs.map +1 -1
- package/dist/createNewEvent.d.cts +43 -39
- package/dist/createNewEvent.d.ts +43 -39
- package/dist/createNewEvent.mjs +19 -12
- package/dist/createNewEvent.mjs.map +1 -1
- package/dist/eventDefinitions.cjs +32 -0
- package/dist/eventDefinitions.cjs.map +1 -0
- package/dist/eventDefinitions.d.cts +20 -0
- package/dist/eventDefinitions.d.ts +20 -0
- package/dist/eventDefinitions.mjs +7 -0
- package/dist/eventDefinitions.mjs.map +1 -0
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/types.cjs +37 -0
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +42 -3
- package/dist/types.d.ts +42 -3
- package/dist/types.mjs +25 -0
- package/dist/types.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/in-memory/InMemoryEventBus.ts +63 -9
- package/src/adapters/kysely/KyselyEventQueries.ts +2 -0
- package/src/adapters/kysely/KyselyEventRepository.ts +5 -1
- package/src/adapters/kysely/migration.ts +3 -1
- package/src/adapters/kysely/types.ts +7 -2
- package/src/createNewEvent.ts +116 -48
- package/src/eventDefinitions.ts +53 -0
- package/src/index.ts +2 -1
- package/src/types.ts +74 -2
package/README.md
CHANGED
|
@@ -1,44 +1,80 @@
|
|
|
1
1
|
# @l-etabli/events
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript library for event-driven architecture with the **outbox pattern**.
|
|
4
4
|
|
|
5
|
-
Events are persisted in the same transaction as your domain changes, then
|
|
5
|
+
Events are persisted in the same transaction as your domain changes, then published asynchronously with retry support.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
bun add @l-etabli/events
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
For Kysely
|
|
13
|
+
For Kysely/PostgreSQL:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
16
|
+
bun add @l-etabli/events kysely pg
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Recommended API
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
The recommended way to model events is now a single canonical definitions object.
|
|
22
22
|
|
|
23
|
-
```
|
|
24
|
-
import {
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
defineEvent,
|
|
26
|
+
defineEvents,
|
|
27
|
+
type InferEventsFromDefinitions,
|
|
28
|
+
} from "@l-etabli/events";
|
|
25
29
|
|
|
26
|
-
type
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
type Project = { id: string; name: string };
|
|
31
|
+
type ProjectContext = { projectId: string };
|
|
32
|
+
type ProjectMemberContext = { projectId: string; memberId: string };
|
|
33
|
+
type ProjectRole = "admin" | "editor";
|
|
34
|
+
|
|
35
|
+
const eventDefinitions = defineEvents({
|
|
36
|
+
ProjectCreated: defineEvent<{ project: Project }, ProjectContext>({
|
|
37
|
+
priority: 1,
|
|
38
|
+
}),
|
|
39
|
+
ProjectUpdated: defineEvent<{ project: Project }, ProjectContext>(),
|
|
40
|
+
UserAddedToProject: defineEvent<
|
|
41
|
+
{ projectId: string; userId: string; role: ProjectRole },
|
|
42
|
+
ProjectMemberContext
|
|
43
|
+
>({
|
|
44
|
+
priority: 20,
|
|
45
|
+
}),
|
|
46
|
+
PingSent: defineEvent<{ at: Date }>(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
type AppEvents = InferEventsFromDefinitions<typeof eventDefinitions>;
|
|
29
50
|
```
|
|
30
51
|
|
|
31
|
-
|
|
52
|
+
Benefits:
|
|
53
|
+
|
|
54
|
+
- one object to maintain
|
|
55
|
+
- topics derived from object keys
|
|
56
|
+
- `priority` lives in the same canonical definition
|
|
57
|
+
- no handwritten `GenericEvent<...> | ...` union in the app
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
### 1. Setup infrastructure
|
|
32
62
|
|
|
33
|
-
```
|
|
63
|
+
```ts
|
|
34
64
|
import {
|
|
65
|
+
createUserActor,
|
|
66
|
+
createWorkerActor,
|
|
67
|
+
createEventCrawler,
|
|
35
68
|
createInMemoryEventBus,
|
|
36
69
|
createInMemoryEventRepositoryAndQueries,
|
|
37
|
-
createEventCrawler,
|
|
38
70
|
} from "@l-etabli/events";
|
|
39
71
|
|
|
40
|
-
const { eventQueries, withUow } =
|
|
41
|
-
|
|
72
|
+
const { eventQueries, withUow } =
|
|
73
|
+
createInMemoryEventRepositoryAndQueries<AppEvents>();
|
|
74
|
+
|
|
75
|
+
const { eventBus, createNewEvent } = createInMemoryEventBus(withUow, {
|
|
76
|
+
eventDefinitions,
|
|
77
|
+
});
|
|
42
78
|
|
|
43
79
|
const crawler = createEventCrawler({
|
|
44
80
|
withUow,
|
|
@@ -47,47 +83,47 @@ const crawler = createEventCrawler({
|
|
|
47
83
|
});
|
|
48
84
|
```
|
|
49
85
|
|
|
50
|
-
###
|
|
86
|
+
### 2. Subscribe to events
|
|
51
87
|
|
|
52
|
-
```
|
|
88
|
+
```ts
|
|
53
89
|
eventBus.subscribe({
|
|
54
|
-
topic: "
|
|
55
|
-
subscriptionId: "send-
|
|
90
|
+
topic: "ProjectCreated",
|
|
91
|
+
subscriptionId: "send-project-created-email",
|
|
56
92
|
callBack: async (event) => {
|
|
57
|
-
await emailService.
|
|
93
|
+
await emailService.send(event.payload.project.id);
|
|
58
94
|
},
|
|
59
95
|
});
|
|
60
96
|
```
|
|
61
97
|
|
|
62
|
-
###
|
|
98
|
+
### 3. Emit events
|
|
63
99
|
|
|
64
|
-
```
|
|
100
|
+
```ts
|
|
65
101
|
await withUow(async (uow) => {
|
|
66
|
-
|
|
67
|
-
await orderRepository.save(order);
|
|
102
|
+
await projectRepository.save(project);
|
|
68
103
|
|
|
69
|
-
// Emit event in the same transaction
|
|
70
104
|
await uow.eventRepository.saveNewEventsBatch([
|
|
71
105
|
createNewEvent({
|
|
72
|
-
topic: "
|
|
73
|
-
payload: {
|
|
74
|
-
|
|
106
|
+
topic: "ProjectCreated",
|
|
107
|
+
payload: { project },
|
|
108
|
+
context: { projectId: project.id },
|
|
109
|
+
triggeredByActor: createUserActor(currentUserId),
|
|
110
|
+
flowId: requestId,
|
|
75
111
|
}),
|
|
76
112
|
]);
|
|
77
113
|
});
|
|
78
114
|
```
|
|
79
115
|
|
|
80
|
-
###
|
|
116
|
+
### 4. Process events
|
|
81
117
|
|
|
82
|
-
|
|
118
|
+
Traditional server:
|
|
83
119
|
|
|
84
|
-
```
|
|
120
|
+
```ts
|
|
85
121
|
crawler.start();
|
|
86
122
|
```
|
|
87
123
|
|
|
88
|
-
|
|
124
|
+
Serverless:
|
|
89
125
|
|
|
90
|
-
```
|
|
126
|
+
```ts
|
|
91
127
|
await withUow(
|
|
92
128
|
async (uow) => {
|
|
93
129
|
await uow.eventRepository.saveNewEventsBatch([event]);
|
|
@@ -96,117 +132,107 @@ await withUow(
|
|
|
96
132
|
afterCommit: async () => {
|
|
97
133
|
await crawler.triggerProcessing();
|
|
98
134
|
},
|
|
99
|
-
}
|
|
135
|
+
},
|
|
100
136
|
);
|
|
101
|
-
|
|
102
|
-
### Returning Values from Transactions
|
|
103
|
-
|
|
104
|
-
The `withUow` function supports returning values from your transaction callback:
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
const result = await withUow(async (uow) => {
|
|
108
|
-
const order = await orderRepository.save(newOrder);
|
|
109
|
-
|
|
110
|
-
await uow.eventRepository.saveNewEventsBatch([
|
|
111
|
-
createNewEvent({
|
|
112
|
-
topic: "OrderPlaced",
|
|
113
|
-
payload: { orderId: order.id, amount: order.total },
|
|
114
|
-
triggeredByUserId: currentUserId,
|
|
115
|
-
}),
|
|
116
|
-
]);
|
|
117
|
-
|
|
118
|
-
return { orderId: order.id, createdAt: order.createdAt };
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
console.log(result.orderId); // Access the returned value
|
|
122
137
|
```
|
|
123
138
|
|
|
124
|
-
## Event
|
|
139
|
+
## Event shape
|
|
125
140
|
|
|
126
|
-
|
|
127
|
-
never-published → in-process → published
|
|
128
|
-
↘ failed-but-will-retry → published
|
|
129
|
-
↘ quarantined (after maxRetries)
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
- `never-published` - New event, not yet processed
|
|
133
|
-
- `in-process` - Currently being published
|
|
134
|
-
- `published` - Successfully delivered to all subscribers
|
|
135
|
-
- `failed-but-will-retry` - Some subscribers failed, will retry
|
|
136
|
-
- `quarantined` - Exceeded max retries, requires manual intervention
|
|
137
|
-
- `to-republish` - Force republish to all subscribers
|
|
138
|
-
|
|
139
|
-
## API Reference
|
|
140
|
-
|
|
141
|
-
### Types
|
|
141
|
+
`GenericEvent` remains the base type.
|
|
142
142
|
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
type GenericEvent<Topic, Payload, Context?> = {
|
|
143
|
+
```ts
|
|
144
|
+
type GenericEvent<Topic, Payload, Context = undefined> = {
|
|
146
145
|
id: EventId;
|
|
146
|
+
occurredAt: Date;
|
|
147
147
|
topic: Topic;
|
|
148
148
|
payload: Payload;
|
|
149
149
|
status: EventStatus;
|
|
150
|
-
occurredAt: Date;
|
|
151
|
-
triggeredByUserId: UserId;
|
|
152
150
|
publications: EventPublication[];
|
|
153
|
-
|
|
151
|
+
triggeredByActor: Actor;
|
|
152
|
+
flowId?: string;
|
|
153
|
+
causedByEventId?: EventId;
|
|
154
154
|
priority?: number;
|
|
155
|
+
context?: Context;
|
|
155
156
|
};
|
|
157
|
+
|
|
158
|
+
type Actor =
|
|
159
|
+
| UserActor
|
|
160
|
+
| SystemActor
|
|
161
|
+
| WorkerActor
|
|
162
|
+
| ApiKeyActor
|
|
163
|
+
| AnonymousActor;
|
|
164
|
+
type UserActor<Id extends string = string> = { kind: "user"; id: Id };
|
|
165
|
+
type SystemActor = { kind: "system" };
|
|
166
|
+
type WorkerActor<Id extends string = string> = { kind: "worker"; id?: Id };
|
|
167
|
+
type ApiKeyActor<Id extends string = string> = { kind: "api-key"; id: Id };
|
|
168
|
+
type AnonymousActor = { kind: "anonymous" };
|
|
156
169
|
```
|
|
157
170
|
|
|
158
|
-
|
|
171
|
+
Rules:
|
|
159
172
|
|
|
160
|
-
|
|
173
|
+
- `payload` is always required
|
|
174
|
+
- `context` is only required at the call site for topics that declare one
|
|
175
|
+
- `priority` is injected automatically from `eventDefinitions` when provided
|
|
161
176
|
|
|
162
|
-
|
|
163
|
-
const createEvent = makeCreateNewEvent<MyEvents>({
|
|
164
|
-
getNow: () => new Date(), // optional, for testing
|
|
165
|
-
generateId: () => crypto.randomUUID(), // optional, for testing
|
|
166
|
-
});
|
|
177
|
+
## Creating events
|
|
167
178
|
|
|
168
|
-
|
|
169
|
-
createEvent({ topic: "UserCreated", payload: { userId: "1", email: "a@b.com" }, triggeredByUserId: "u1" });
|
|
170
|
-
```
|
|
179
|
+
### From event definitions
|
|
171
180
|
|
|
172
|
-
|
|
181
|
+
```ts
|
|
182
|
+
import { createUserActor, makeCreateNewEvent } from "@l-etabli/events";
|
|
173
183
|
|
|
174
|
-
|
|
184
|
+
const createNewEvent = makeCreateNewEvent({
|
|
185
|
+
eventDefinitions,
|
|
186
|
+
});
|
|
175
187
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
188
|
+
createNewEvent({
|
|
189
|
+
topic: "ProjectCreated",
|
|
190
|
+
payload: { project },
|
|
191
|
+
context: { projectId: project.id },
|
|
192
|
+
triggeredByActor: createUserActor(currentUserId),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
createNewEvent({
|
|
196
|
+
topic: "PingSent",
|
|
197
|
+
payload: { at: new Date() },
|
|
198
|
+
triggeredByActor: createWorkerActor("nightly-sync"),
|
|
179
199
|
});
|
|
180
200
|
```
|
|
181
201
|
|
|
182
|
-
###
|
|
202
|
+
### From an existing union
|
|
183
203
|
|
|
184
|
-
|
|
204
|
+
The previous union-based approach still works.
|
|
185
205
|
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
});
|
|
206
|
+
```ts
|
|
207
|
+
import { type GenericEvent, makeCreateNewEvent } from "@l-etabli/events";
|
|
208
|
+
|
|
209
|
+
type LegacyEvents =
|
|
210
|
+
| GenericEvent<"UserCreated", { userId: string; email: string }>
|
|
211
|
+
| GenericEvent<"OrderPlaced", { orderId: string }, { tenantId: string }>;
|
|
212
|
+
|
|
213
|
+
const createNewEvent = makeCreateNewEvent<LegacyEvents>();
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Event lifecycle
|
|
198
217
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
```text
|
|
219
|
+
never-published -> in-process -> published
|
|
220
|
+
\-> failed-but-will-retry -> published
|
|
221
|
+
\-> quarantined
|
|
203
222
|
```
|
|
204
223
|
|
|
205
|
-
|
|
224
|
+
Statuses:
|
|
206
225
|
|
|
207
|
-
|
|
226
|
+
- `never-published`
|
|
227
|
+
- `to-republish`
|
|
228
|
+
- `in-process`
|
|
229
|
+
- `published`
|
|
230
|
+
- `failed-but-will-retry`
|
|
231
|
+
- `quarantined`
|
|
208
232
|
|
|
209
|
-
|
|
233
|
+
## Kysely migration helper
|
|
234
|
+
|
|
235
|
+
```ts
|
|
210
236
|
import type { Kysely } from "kysely";
|
|
211
237
|
|
|
212
238
|
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
@@ -217,51 +243,16 @@ export async function up(db: Kysely<unknown>): Promise<void> {
|
|
|
217
243
|
.addColumn("payload", "jsonb", (col) => col.notNull())
|
|
218
244
|
.addColumn("context", "jsonb")
|
|
219
245
|
.addColumn("status", "text", (col) => col.notNull())
|
|
220
|
-
.addColumn("
|
|
246
|
+
.addColumn("triggeredByActor", "jsonb", (col) => col.notNull())
|
|
247
|
+
.addColumn("flowId", "text")
|
|
248
|
+
.addColumn("causedByEventId", "text")
|
|
221
249
|
.addColumn("occurredAt", "timestamptz", (col) => col.notNull())
|
|
222
250
|
.addColumn("publications", "jsonb", (col) => col.notNull().defaultTo("[]"))
|
|
223
251
|
.addColumn("priority", "integer")
|
|
224
252
|
.execute();
|
|
225
|
-
|
|
226
|
-
await db.schema
|
|
227
|
-
.createIndex("events_status_idx")
|
|
228
|
-
.on("events")
|
|
229
|
-
.column("status")
|
|
230
|
-
.execute();
|
|
231
|
-
|
|
232
|
-
await db.schema
|
|
233
|
-
.createIndex("events_topic_idx")
|
|
234
|
-
.on("events")
|
|
235
|
-
.column("topic")
|
|
236
|
-
.execute();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
240
|
-
await db.schema.dropTable("events").execute();
|
|
241
253
|
}
|
|
242
254
|
```
|
|
243
255
|
|
|
244
|
-
### Usage with Kysely
|
|
245
|
-
|
|
246
|
-
```typescript
|
|
247
|
-
import { createInMemoryEventBus, createEventCrawler } from "@l-etabli/events";
|
|
248
|
-
import {
|
|
249
|
-
KyselyEventRepository,
|
|
250
|
-
KyselyEventQueries,
|
|
251
|
-
createKyselyMigration,
|
|
252
|
-
} from "@l-etabli/events/kysely";
|
|
253
|
-
|
|
254
|
-
// See examples/kysely/ for complete implementation
|
|
255
|
-
```
|
|
256
|
-
|
|
257
256
|
## Examples
|
|
258
257
|
|
|
259
|
-
See
|
|
260
|
-
|
|
261
|
-
- **[kysely-adapter.ts](./examples/kysely/kysely-adapter.ts)** - Kysely adapter with transaction support
|
|
262
|
-
- **[serverless-usage.ts](./examples/kysely/serverless-usage.ts)** - AWS Lambda / serverless deployment
|
|
263
|
-
- **[cascading-events.ts](./examples/kysely/cascading-events.ts)** - Transactional event chains
|
|
264
|
-
|
|
265
|
-
## License
|
|
266
|
-
|
|
267
|
-
MIT
|
|
258
|
+
See [`examples/`](./examples/) for Kysely integration, cascading events and serverless usage.
|
|
@@ -23,9 +23,14 @@ __export(InMemoryEventBus_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(InMemoryEventBus_exports);
|
|
24
24
|
var import_createNewEvent = require("../../createNewEvent.ts");
|
|
25
25
|
var import_subscriptions = require("../../subscriptions.ts");
|
|
26
|
-
|
|
26
|
+
function createInMemoryEventBus(withUow, options = {}) {
|
|
27
27
|
const maxRetries = options.maxRetries ?? 3;
|
|
28
|
-
const
|
|
28
|
+
const eventDefinitions = "eventDefinitions" in options ? options.eventDefinitions : void 0;
|
|
29
|
+
const createNewEvent = eventDefinitions ? (0, import_createNewEvent.makeCreateNewEvent)({
|
|
30
|
+
getNow: options.getNow,
|
|
31
|
+
generateId: options.generateId,
|
|
32
|
+
eventDefinitions
|
|
33
|
+
}) : (0, import_createNewEvent.makeCreateNewEvent)({
|
|
29
34
|
getNow: options.getNow,
|
|
30
35
|
generateId: options.generateId
|
|
31
36
|
});
|
|
@@ -133,7 +138,7 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
133
138
|
subscribeAll,
|
|
134
139
|
subscribeGlobal
|
|
135
140
|
};
|
|
136
|
-
}
|
|
141
|
+
}
|
|
137
142
|
// Annotate the CommonJS export names for ESM import in node:
|
|
138
143
|
0 && (module.exports = {
|
|
139
144
|
createInMemoryEventBus
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import { makeCreateNewEvent } from \"../../createNewEvent.ts\";\nimport type { EventBus } from \"../../ports/EventBus.ts\";\nimport type { WithEventsUow } from \"../../ports/EventRepository.ts\";\nimport {\n type GlobalSubscriberConfig,\n subscribeByTopic,\n subscribeGlobalToTopics,\n type TopicSubscriptions,\n} from \"../../subscriptions.ts\";\nimport type {\n DefaultContext,\n EventId,\n EventPublication,\n GenericEvent,\n SubscriptionId,\n} from \"../../types.ts\";\n\ntype SubscriptionsForTopic = Record<\n string,\n (event: GenericEvent<string, unknown, DefaultContext>) => Promise<void>\n>;\n\ntype CreateInMemoryEventBusOptions = {\n maxRetries?: number;\n getNow?: () => Date;\n generateId?: () => EventId;\n};\n\nexport const createInMemoryEventBus = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options: CreateInMemoryEventBusOptions = {},\n) => {\n const maxRetries = options.maxRetries ?? 3;\n const createNewEvent = makeCreateNewEvent<Event>({\n getNow: options.getNow,\n generateId: options.generateId,\n });\n const subscriptions: Partial<Record<string, SubscriptionsForTopic>> = {};\n\n const executeCallback = async (\n event: Event,\n subscriptionId: string,\n callback: (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>,\n ): Promise<\n { subscriptionId: string; errorMessage: string; stack?: string } | undefined\n > => {\n try {\n await callback(event);\n } catch (error) {\n return {\n subscriptionId,\n errorMessage: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n }\n };\n\n const getSubscriptionIdsToPublish = (\n event: Event,\n callbacksBySubscriptionId: SubscriptionsForTopic,\n ): string[] => {\n const allSubscriptionIds = Object.keys(callbacksBySubscriptionId);\n\n if (event.publications.length === 0 || event.status === \"to-republish\") {\n return allSubscriptionIds;\n }\n\n const lastPublication = event.publications.reduce((latest, current) =>\n current.publishedAt > latest.publishedAt ? current : latest,\n );\n const failedSubscriptionIds = (lastPublication.failures ?? []).map(\n (failure) => failure.subscriptionId,\n );\n\n return allSubscriptionIds.filter((id) =>\n failedSubscriptionIds.includes(id),\n );\n };\n\n const eventBus: EventBus<Event> = {\n publish: async (event) => {\n const publishedAt = new Date();\n const topic = event.topic;\n\n const callbacksBySubscriptionSlug = subscriptions[topic];\n\n if (!callbacksBySubscriptionSlug) {\n event.publications.push({\n publishedAt,\n publishedSubscribers: [],\n });\n event.status = \"published\";\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n return;\n }\n\n const subscriptionIdsToPublish = getSubscriptionIdsToPublish(\n event,\n callbacksBySubscriptionSlug,\n );\n\n const failuresOrUndefined = await Promise.all(\n subscriptionIdsToPublish.map((subscriptionId) =>\n executeCallback(\n event,\n subscriptionId,\n callbacksBySubscriptionSlug[subscriptionId],\n ),\n ),\n );\n\n const failures = failuresOrUndefined.filter(\n (\n f,\n ): f is {\n subscriptionId: string;\n errorMessage: string;\n stack?: string;\n } => f !== undefined,\n );\n\n const publications: EventPublication[] = [\n ...event.publications,\n {\n publishedAt,\n publishedSubscribers: subscriptionIdsToPublish.map(\n (id) => id as SubscriptionId,\n ),\n ...(failures.length > 0 && { failures }),\n },\n ];\n\n if (failures.length === 0) {\n event.status = \"published\";\n } else {\n const wasMaxNumberOfErrorsReached = publications.length >= maxRetries;\n event.status = wasMaxNumberOfErrorsReached\n ? \"quarantined\"\n : \"failed-but-will-retry\";\n }\n\n event.publications = publications;\n\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n },\n\n subscribe: ({ topic, subscriptionId, callBack }) => {\n if (!subscriptions[topic]) {\n subscriptions[topic] = {};\n }\n\n const subscriptionsForTopic = subscriptions[topic];\n if (subscriptionsForTopic) {\n subscriptionsForTopic[subscriptionId] = callBack as (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>;\n }\n },\n };\n\n /**\n * Identity function for type inference when defining subscription maps.\n * Ensures all topics are covered and handlers have correct payload types.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({\n * OrderCreated: [{ subscriptionId: \"notify\", handler: async (e) => {...} }],\n * OrderShipped: [], // Required even if empty\n * });\n * ```\n */\n const defineSubscriptions = (\n subscriptions: TopicSubscriptions<Event>,\n ): TopicSubscriptions<Event> => subscriptions;\n\n /**\n * Subscribe all handlers from a topic subscription map to this event bus.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({...});\n * subscribeAll(subscriptions);\n * ```\n */\n const subscribeAll = (subscriptions: TopicSubscriptions<Event>): void => {\n subscribeByTopic(eventBus, subscriptions);\n };\n\n /**\n * Subscribe a global handler to multiple topics with optional filtering.\n *\n * @example\n * ```typescript\n * subscribeGlobal(subscriptions, {\n * subscriptionId: \"audit-log\",\n * handler: async (event) => auditLog.record(event),\n * filter: { exclude: [\"NotificationAdded\"] },\n * });\n * ```\n */\n const subscribeGlobal = (\n subscriptions: TopicSubscriptions<Event>,\n config: GlobalSubscriberConfig<Event>,\n ): void => {\n subscribeGlobalToTopics(eventBus, subscriptions, config);\n };\n\n return {\n eventBus,\n createNewEvent,\n defineSubscriptions,\n subscribeAll,\n subscribeGlobal,\n };\n};\n"],"mappings":"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","names":["subscriptions"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import {\n type CreateNewEvent,\n type CreateNewEventFromDefinitions,\n makeCreateNewEvent,\n} from \"../../createNewEvent.ts\";\nimport type {\n EventDefinitions,\n InferEventsFromDefinitions,\n} from \"../../eventDefinitions.ts\";\nimport type { EventBus } from \"../../ports/EventBus.ts\";\nimport type { WithEventsUow } from \"../../ports/EventRepository.ts\";\nimport {\n type GlobalSubscriberConfig,\n subscribeByTopic,\n subscribeGlobalToTopics,\n type TopicSubscriptions,\n} from \"../../subscriptions.ts\";\nimport type {\n DefaultContext,\n EventId,\n EventPublication,\n GenericEvent,\n SubscriptionId,\n} from \"../../types.ts\";\n\ntype SubscriptionsForTopic = Record<\n string,\n (event: GenericEvent<string, unknown, DefaultContext>) => Promise<void>\n>;\n\ntype CreateInMemoryEventBusOptions = {\n maxRetries?: number;\n getNow?: () => Date;\n generateId?: () => EventId;\n};\n\ntype CreateInMemoryEventBusFromDefinitionsOptions<\n Definitions extends EventDefinitions,\n> = CreateInMemoryEventBusOptions & {\n eventDefinitions: Definitions;\n};\n\ntype CreateInMemoryEventBusResult<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n eventBus: EventBus<Event>;\n createNewEvent: CreateNewEvent<Event>;\n defineSubscriptions: (\n subscriptions: TopicSubscriptions<Event>,\n ) => TopicSubscriptions<Event>;\n subscribeAll: (subscriptions: TopicSubscriptions<Event>) => void;\n subscribeGlobal: (\n subscriptions: TopicSubscriptions<Event>,\n config: GlobalSubscriberConfig<Event>,\n ) => void;\n};\n\nexport function createInMemoryEventBus<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options?: CreateInMemoryEventBusOptions,\n): CreateInMemoryEventBusResult<Event>;\nexport function createInMemoryEventBus<Definitions extends EventDefinitions>(\n withUow: WithEventsUow<InferEventsFromDefinitions<Definitions>>,\n options: CreateInMemoryEventBusFromDefinitionsOptions<Definitions>,\n): Omit<\n CreateInMemoryEventBusResult<InferEventsFromDefinitions<Definitions>>,\n \"createNewEvent\"\n> & {\n createNewEvent: CreateNewEventFromDefinitions<Definitions>;\n};\nexport function createInMemoryEventBus<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options:\n | CreateInMemoryEventBusOptions\n | CreateInMemoryEventBusFromDefinitionsOptions<EventDefinitions> = {},\n) {\n const maxRetries = options.maxRetries ?? 3;\n const eventDefinitions =\n \"eventDefinitions\" in options ? options.eventDefinitions : undefined;\n const createNewEvent = eventDefinitions\n ? makeCreateNewEvent({\n getNow: options.getNow,\n generateId: options.generateId,\n eventDefinitions,\n })\n : makeCreateNewEvent<Event>({\n getNow: options.getNow,\n generateId: options.generateId,\n });\n const subscriptions: Partial<Record<string, SubscriptionsForTopic>> = {};\n\n const executeCallback = async (\n event: Event,\n subscriptionId: string,\n callback: (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>,\n ): Promise<\n { subscriptionId: string; errorMessage: string; stack?: string } | undefined\n > => {\n try {\n await callback(event);\n } catch (error) {\n return {\n subscriptionId,\n errorMessage: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n }\n };\n\n const getSubscriptionIdsToPublish = (\n event: Event,\n callbacksBySubscriptionId: SubscriptionsForTopic,\n ): string[] => {\n const allSubscriptionIds = Object.keys(callbacksBySubscriptionId);\n\n if (event.publications.length === 0 || event.status === \"to-republish\") {\n return allSubscriptionIds;\n }\n\n const lastPublication = event.publications.reduce((latest, current) =>\n current.publishedAt > latest.publishedAt ? current : latest,\n );\n const failedSubscriptionIds = (lastPublication.failures ?? []).map(\n (failure) => failure.subscriptionId,\n );\n\n return allSubscriptionIds.filter((id) =>\n failedSubscriptionIds.includes(id),\n );\n };\n\n const eventBus: EventBus<Event> = {\n publish: async (event) => {\n const publishedAt = new Date();\n const topic = event.topic;\n\n const callbacksBySubscriptionSlug = subscriptions[topic];\n\n if (!callbacksBySubscriptionSlug) {\n event.publications.push({\n publishedAt,\n publishedSubscribers: [],\n });\n event.status = \"published\";\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n return;\n }\n\n const subscriptionIdsToPublish = getSubscriptionIdsToPublish(\n event,\n callbacksBySubscriptionSlug,\n );\n\n const failuresOrUndefined = await Promise.all(\n subscriptionIdsToPublish.map((subscriptionId) =>\n executeCallback(\n event,\n subscriptionId,\n callbacksBySubscriptionSlug[subscriptionId],\n ),\n ),\n );\n\n const failures = failuresOrUndefined.filter(\n (\n f,\n ): f is {\n subscriptionId: string;\n errorMessage: string;\n stack?: string;\n } => f !== undefined,\n );\n\n const publications: EventPublication[] = [\n ...event.publications,\n {\n publishedAt,\n publishedSubscribers: subscriptionIdsToPublish.map(\n (id) => id as SubscriptionId,\n ),\n ...(failures.length > 0 && { failures }),\n },\n ];\n\n if (failures.length === 0) {\n event.status = \"published\";\n } else {\n const wasMaxNumberOfErrorsReached = publications.length >= maxRetries;\n event.status = wasMaxNumberOfErrorsReached\n ? \"quarantined\"\n : \"failed-but-will-retry\";\n }\n\n event.publications = publications;\n\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n },\n\n subscribe: ({ topic, subscriptionId, callBack }) => {\n if (!subscriptions[topic]) {\n subscriptions[topic] = {};\n }\n\n const subscriptionsForTopic = subscriptions[topic];\n if (subscriptionsForTopic) {\n subscriptionsForTopic[subscriptionId] = callBack as (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>;\n }\n },\n };\n\n /**\n * Identity function for type inference when defining subscription maps.\n * Ensures all topics are covered and handlers have correct payload types.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({\n * OrderCreated: [{ subscriptionId: \"notify\", handler: async (e) => {...} }],\n * OrderShipped: [], // Required even if empty\n * });\n * ```\n */\n const defineSubscriptions = (\n subscriptions: TopicSubscriptions<Event>,\n ): TopicSubscriptions<Event> => subscriptions;\n\n /**\n * Subscribe all handlers from a topic subscription map to this event bus.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({...});\n * subscribeAll(subscriptions);\n * ```\n */\n const subscribeAll = (subscriptions: TopicSubscriptions<Event>): void => {\n subscribeByTopic(eventBus, subscriptions);\n };\n\n /**\n * Subscribe a global handler to multiple topics with optional filtering.\n *\n * @example\n * ```typescript\n * subscribeGlobal(subscriptions, {\n * subscriptionId: \"audit-log\",\n * handler: async (event) => auditLog.record(event),\n * filter: { exclude: [\"NotificationAdded\"] },\n * });\n * ```\n */\n const subscribeGlobal = (\n subscriptions: TopicSubscriptions<Event>,\n config: GlobalSubscriberConfig<Event>,\n ): void => {\n subscribeGlobalToTopics(eventBus, subscriptions, config);\n };\n\n return {\n eventBus,\n createNewEvent,\n defineSubscriptions,\n subscribeAll,\n subscribeGlobal,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAIO;AAOP,2BAKO;AAwDA,SAAS,uBAGd,SACA,UAEqE,CAAC,GACtE;AACA,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,mBACJ,sBAAsB,UAAU,QAAQ,mBAAmB;AAC7D,QAAM,iBAAiB,uBACnB,0CAAmB;AAAA,IACjB,QAAQ,QAAQ;AAAA,IAChB,YAAY,QAAQ;AAAA,IACpB;AAAA,EACF,CAAC,QACD,0CAA0B;AAAA,IACxB,QAAQ,QAAQ;AAAA,IAChB,YAAY,QAAQ;AAAA,EACtB,CAAC;AACL,QAAM,gBAAgE,CAAC;AAEvE,QAAM,kBAAkB,OACtB,OACA,gBACA,aAKG;AACH,QAAI;AACF,YAAM,SAAS,KAAK;AAAA,IACtB,SAAS,OAAO;AACd,aAAO;AAAA,QACL;AAAA,QACA,cAAc,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QACnE,OAAO,iBAAiB,QAAQ,MAAM,QAAQ;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,8BAA8B,CAClC,OACA,8BACa;AACb,UAAM,qBAAqB,OAAO,KAAK,yBAAyB;AAEhE,QAAI,MAAM,aAAa,WAAW,KAAK,MAAM,WAAW,gBAAgB;AACtE,aAAO;AAAA,IACT;AAEA,UAAM,kBAAkB,MAAM,aAAa;AAAA,MAAO,CAAC,QAAQ,YACzD,QAAQ,cAAc,OAAO,cAAc,UAAU;AAAA,IACvD;AACA,UAAM,yBAAyB,gBAAgB,YAAY,CAAC,GAAG;AAAA,MAC7D,CAAC,YAAY,QAAQ;AAAA,IACvB;AAEA,WAAO,mBAAmB;AAAA,MAAO,CAAC,OAChC,sBAAsB,SAAS,EAAE;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,WAA4B;AAAA,IAChC,SAAS,OAAO,UAAU;AACxB,YAAM,cAAc,oBAAI,KAAK;AAC7B,YAAM,QAAQ,MAAM;AAEpB,YAAM,8BAA8B,cAAc,KAAK;AAEvD,UAAI,CAAC,6BAA6B;AAChC,cAAM,aAAa,KAAK;AAAA,UACtB;AAAA,UACA,sBAAsB,CAAC;AAAA,QACzB,CAAC;AACD,cAAM,SAAS;AACf,cAAM,QAAQ,OAAO,QAAQ;AAC3B,gBAAM,IAAI,gBAAgB,KAAK,KAAK;AAAA,QACtC,CAAC;AACD;AAAA,MACF;AAEA,YAAM,2BAA2B;AAAA,QAC/B;AAAA,QACA;AAAA,MACF;AAEA,YAAM,sBAAsB,MAAM,QAAQ;AAAA,QACxC,yBAAyB;AAAA,UAAI,CAAC,mBAC5B;AAAA,YACE;AAAA,YACA;AAAA,YACA,4BAA4B,cAAc;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AAEA,YAAM,WAAW,oBAAoB;AAAA,QACnC,CACE,MAKG,MAAM;AAAA,MACb;AAEA,YAAM,eAAmC;AAAA,QACvC,GAAG,MAAM;AAAA,QACT;AAAA,UACE;AAAA,UACA,sBAAsB,yBAAyB;AAAA,YAC7C,CAAC,OAAO;AAAA,UACV;AAAA,UACA,GAAI,SAAS,SAAS,KAAK,EAAE,SAAS;AAAA,QACxC;AAAA,MACF;AAEA,UAAI,SAAS,WAAW,GAAG;AACzB,cAAM,SAAS;AAAA,MACjB,OAAO;AACL,cAAM,8BAA8B,aAAa,UAAU;AAC3D,cAAM,SAAS,8BACX,gBACA;AAAA,MACN;AAEA,YAAM,eAAe;AAErB,YAAM,QAAQ,OAAO,QAAQ;AAC3B,cAAM,IAAI,gBAAgB,KAAK,KAAK;AAAA,MACtC,CAAC;AAAA,IACH;AAAA,IAEA,WAAW,CAAC,EAAE,OAAO,gBAAgB,SAAS,MAAM;AAClD,UAAI,CAAC,cAAc,KAAK,GAAG;AACzB,sBAAc,KAAK,IAAI,CAAC;AAAA,MAC1B;AAEA,YAAM,wBAAwB,cAAc,KAAK;AACjD,UAAI,uBAAuB;AACzB,8BAAsB,cAAc,IAAI;AAAA,MAG1C;AAAA,IACF;AAAA,EACF;AAcA,QAAM,sBAAsB,CAC1BA,mBAC8BA;AAWhC,QAAM,eAAe,CAACA,mBAAmD;AACvE,+CAAiB,UAAUA,cAAa;AAAA,EAC1C;AAcA,QAAM,kBAAkB,CACtBA,gBACA,WACS;AACT,sDAAwB,UAAUA,gBAAe,MAAM;AAAA,EACzD;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["subscriptions"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { CreateNewEvent } from '../../createNewEvent.cjs';
|
|
1
|
+
import { CreateNewEvent, CreateNewEventFromDefinitions } from '../../createNewEvent.cjs';
|
|
2
|
+
import { EventDefinitions, InferEventsFromDefinitions } from '../../eventDefinitions.cjs';
|
|
2
3
|
import { EventBus } from '../../ports/EventBus.cjs';
|
|
3
4
|
import { WithEventsUow } from '../../ports/EventRepository.cjs';
|
|
4
5
|
import { TopicSubscriptions, GlobalSubscriberConfig } from '../../subscriptions.cjs';
|
|
@@ -9,12 +10,19 @@ type CreateInMemoryEventBusOptions = {
|
|
|
9
10
|
getNow?: () => Date;
|
|
10
11
|
generateId?: () => EventId;
|
|
11
12
|
};
|
|
12
|
-
|
|
13
|
+
type CreateInMemoryEventBusFromDefinitionsOptions<Definitions extends EventDefinitions> = CreateInMemoryEventBusOptions & {
|
|
14
|
+
eventDefinitions: Definitions;
|
|
15
|
+
};
|
|
16
|
+
type CreateInMemoryEventBusResult<Event extends GenericEvent<string, unknown, DefaultContext>> = {
|
|
13
17
|
eventBus: EventBus<Event>;
|
|
14
18
|
createNewEvent: CreateNewEvent<Event>;
|
|
15
19
|
defineSubscriptions: (subscriptions: TopicSubscriptions<Event>) => TopicSubscriptions<Event>;
|
|
16
20
|
subscribeAll: (subscriptions: TopicSubscriptions<Event>) => void;
|
|
17
21
|
subscribeGlobal: (subscriptions: TopicSubscriptions<Event>, config: GlobalSubscriberConfig<Event>) => void;
|
|
18
22
|
};
|
|
23
|
+
declare function createInMemoryEventBus<Event extends GenericEvent<string, unknown, DefaultContext>>(withUow: WithEventsUow<Event>, options?: CreateInMemoryEventBusOptions): CreateInMemoryEventBusResult<Event>;
|
|
24
|
+
declare function createInMemoryEventBus<Definitions extends EventDefinitions>(withUow: WithEventsUow<InferEventsFromDefinitions<Definitions>>, options: CreateInMemoryEventBusFromDefinitionsOptions<Definitions>): Omit<CreateInMemoryEventBusResult<InferEventsFromDefinitions<Definitions>>, "createNewEvent"> & {
|
|
25
|
+
createNewEvent: CreateNewEventFromDefinitions<Definitions>;
|
|
26
|
+
};
|
|
19
27
|
|
|
20
28
|
export { createInMemoryEventBus };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { CreateNewEvent } from '../../createNewEvent.js';
|
|
1
|
+
import { CreateNewEvent, CreateNewEventFromDefinitions } from '../../createNewEvent.js';
|
|
2
|
+
import { EventDefinitions, InferEventsFromDefinitions } from '../../eventDefinitions.js';
|
|
2
3
|
import { EventBus } from '../../ports/EventBus.js';
|
|
3
4
|
import { WithEventsUow } from '../../ports/EventRepository.js';
|
|
4
5
|
import { TopicSubscriptions, GlobalSubscriberConfig } from '../../subscriptions.js';
|
|
@@ -9,12 +10,19 @@ type CreateInMemoryEventBusOptions = {
|
|
|
9
10
|
getNow?: () => Date;
|
|
10
11
|
generateId?: () => EventId;
|
|
11
12
|
};
|
|
12
|
-
|
|
13
|
+
type CreateInMemoryEventBusFromDefinitionsOptions<Definitions extends EventDefinitions> = CreateInMemoryEventBusOptions & {
|
|
14
|
+
eventDefinitions: Definitions;
|
|
15
|
+
};
|
|
16
|
+
type CreateInMemoryEventBusResult<Event extends GenericEvent<string, unknown, DefaultContext>> = {
|
|
13
17
|
eventBus: EventBus<Event>;
|
|
14
18
|
createNewEvent: CreateNewEvent<Event>;
|
|
15
19
|
defineSubscriptions: (subscriptions: TopicSubscriptions<Event>) => TopicSubscriptions<Event>;
|
|
16
20
|
subscribeAll: (subscriptions: TopicSubscriptions<Event>) => void;
|
|
17
21
|
subscribeGlobal: (subscriptions: TopicSubscriptions<Event>, config: GlobalSubscriberConfig<Event>) => void;
|
|
18
22
|
};
|
|
23
|
+
declare function createInMemoryEventBus<Event extends GenericEvent<string, unknown, DefaultContext>>(withUow: WithEventsUow<Event>, options?: CreateInMemoryEventBusOptions): CreateInMemoryEventBusResult<Event>;
|
|
24
|
+
declare function createInMemoryEventBus<Definitions extends EventDefinitions>(withUow: WithEventsUow<InferEventsFromDefinitions<Definitions>>, options: CreateInMemoryEventBusFromDefinitionsOptions<Definitions>): Omit<CreateInMemoryEventBusResult<InferEventsFromDefinitions<Definitions>>, "createNewEvent"> & {
|
|
25
|
+
createNewEvent: CreateNewEventFromDefinitions<Definitions>;
|
|
26
|
+
};
|
|
19
27
|
|
|
20
28
|
export { createInMemoryEventBus };
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
makeCreateNewEvent
|
|
3
|
+
} from "../../createNewEvent.mjs";
|
|
2
4
|
import {
|
|
3
5
|
subscribeByTopic,
|
|
4
6
|
subscribeGlobalToTopics
|
|
5
7
|
} from "../../subscriptions.mjs";
|
|
6
|
-
|
|
8
|
+
function createInMemoryEventBus(withUow, options = {}) {
|
|
7
9
|
const maxRetries = options.maxRetries ?? 3;
|
|
8
|
-
const
|
|
10
|
+
const eventDefinitions = "eventDefinitions" in options ? options.eventDefinitions : void 0;
|
|
11
|
+
const createNewEvent = eventDefinitions ? makeCreateNewEvent({
|
|
12
|
+
getNow: options.getNow,
|
|
13
|
+
generateId: options.generateId,
|
|
14
|
+
eventDefinitions
|
|
15
|
+
}) : makeCreateNewEvent({
|
|
9
16
|
getNow: options.getNow,
|
|
10
17
|
generateId: options.generateId
|
|
11
18
|
});
|
|
@@ -113,7 +120,7 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
113
120
|
subscribeAll,
|
|
114
121
|
subscribeGlobal
|
|
115
122
|
};
|
|
116
|
-
}
|
|
123
|
+
}
|
|
117
124
|
export {
|
|
118
125
|
createInMemoryEventBus
|
|
119
126
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import { makeCreateNewEvent } from '../../createNewEvent.mjs';\nimport type { EventBus } from '../../ports/EventBus.mjs';\nimport type { WithEventsUow } from '../../ports/EventRepository.mjs';\nimport {\n type GlobalSubscriberConfig,\n subscribeByTopic,\n subscribeGlobalToTopics,\n type TopicSubscriptions,\n} from '../../subscriptions.mjs';\nimport type {\n DefaultContext,\n EventId,\n EventPublication,\n GenericEvent,\n SubscriptionId,\n} from '../../types.mjs';\n\ntype SubscriptionsForTopic = Record<\n string,\n (event: GenericEvent<string, unknown, DefaultContext>) => Promise<void>\n>;\n\ntype CreateInMemoryEventBusOptions = {\n maxRetries?: number;\n getNow?: () => Date;\n generateId?: () => EventId;\n};\n\nexport const createInMemoryEventBus = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options: CreateInMemoryEventBusOptions = {},\n) => {\n const maxRetries = options.maxRetries ?? 3;\n const createNewEvent = makeCreateNewEvent<Event>({\n getNow: options.getNow,\n generateId: options.generateId,\n });\n const subscriptions: Partial<Record<string, SubscriptionsForTopic>> = {};\n\n const executeCallback = async (\n event: Event,\n subscriptionId: string,\n callback: (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>,\n ): Promise<\n { subscriptionId: string; errorMessage: string; stack?: string } | undefined\n > => {\n try {\n await callback(event);\n } catch (error) {\n return {\n subscriptionId,\n errorMessage: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n }\n };\n\n const getSubscriptionIdsToPublish = (\n event: Event,\n callbacksBySubscriptionId: SubscriptionsForTopic,\n ): string[] => {\n const allSubscriptionIds = Object.keys(callbacksBySubscriptionId);\n\n if (event.publications.length === 0 || event.status === \"to-republish\") {\n return allSubscriptionIds;\n }\n\n const lastPublication = event.publications.reduce((latest, current) =>\n current.publishedAt > latest.publishedAt ? current : latest,\n );\n const failedSubscriptionIds = (lastPublication.failures ?? []).map(\n (failure) => failure.subscriptionId,\n );\n\n return allSubscriptionIds.filter((id) =>\n failedSubscriptionIds.includes(id),\n );\n };\n\n const eventBus: EventBus<Event> = {\n publish: async (event) => {\n const publishedAt = new Date();\n const topic = event.topic;\n\n const callbacksBySubscriptionSlug = subscriptions[topic];\n\n if (!callbacksBySubscriptionSlug) {\n event.publications.push({\n publishedAt,\n publishedSubscribers: [],\n });\n event.status = \"published\";\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n return;\n }\n\n const subscriptionIdsToPublish = getSubscriptionIdsToPublish(\n event,\n callbacksBySubscriptionSlug,\n );\n\n const failuresOrUndefined = await Promise.all(\n subscriptionIdsToPublish.map((subscriptionId) =>\n executeCallback(\n event,\n subscriptionId,\n callbacksBySubscriptionSlug[subscriptionId],\n ),\n ),\n );\n\n const failures = failuresOrUndefined.filter(\n (\n f,\n ): f is {\n subscriptionId: string;\n errorMessage: string;\n stack?: string;\n } => f !== undefined,\n );\n\n const publications: EventPublication[] = [\n ...event.publications,\n {\n publishedAt,\n publishedSubscribers: subscriptionIdsToPublish.map(\n (id) => id as SubscriptionId,\n ),\n ...(failures.length > 0 && { failures }),\n },\n ];\n\n if (failures.length === 0) {\n event.status = \"published\";\n } else {\n const wasMaxNumberOfErrorsReached = publications.length >= maxRetries;\n event.status = wasMaxNumberOfErrorsReached\n ? \"quarantined\"\n : \"failed-but-will-retry\";\n }\n\n event.publications = publications;\n\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n },\n\n subscribe: ({ topic, subscriptionId, callBack }) => {\n if (!subscriptions[topic]) {\n subscriptions[topic] = {};\n }\n\n const subscriptionsForTopic = subscriptions[topic];\n if (subscriptionsForTopic) {\n subscriptionsForTopic[subscriptionId] = callBack as (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>;\n }\n },\n };\n\n /**\n * Identity function for type inference when defining subscription maps.\n * Ensures all topics are covered and handlers have correct payload types.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({\n * OrderCreated: [{ subscriptionId: \"notify\", handler: async (e) => {...} }],\n * OrderShipped: [], // Required even if empty\n * });\n * ```\n */\n const defineSubscriptions = (\n subscriptions: TopicSubscriptions<Event>,\n ): TopicSubscriptions<Event> => subscriptions;\n\n /**\n * Subscribe all handlers from a topic subscription map to this event bus.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({...});\n * subscribeAll(subscriptions);\n * ```\n */\n const subscribeAll = (subscriptions: TopicSubscriptions<Event>): void => {\n subscribeByTopic(eventBus, subscriptions);\n };\n\n /**\n * Subscribe a global handler to multiple topics with optional filtering.\n *\n * @example\n * ```typescript\n * subscribeGlobal(subscriptions, {\n * subscriptionId: \"audit-log\",\n * handler: async (event) => auditLog.record(event),\n * filter: { exclude: [\"NotificationAdded\"] },\n * });\n * ```\n */\n const subscribeGlobal = (\n subscriptions: TopicSubscriptions<Event>,\n config: GlobalSubscriberConfig<Event>,\n ): void => {\n subscribeGlobalToTopics(eventBus, subscriptions, config);\n };\n\n return {\n eventBus,\n createNewEvent,\n defineSubscriptions,\n subscribeAll,\n subscribeGlobal,\n };\n};\n"],"mappings":"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","names":["subscriptions"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import {\n type CreateNewEvent,\n type CreateNewEventFromDefinitions,\n makeCreateNewEvent,\n} from '../../createNewEvent.mjs';\nimport type {\n EventDefinitions,\n InferEventsFromDefinitions,\n} from '../../eventDefinitions.mjs';\nimport type { EventBus } from '../../ports/EventBus.mjs';\nimport type { WithEventsUow } from '../../ports/EventRepository.mjs';\nimport {\n type GlobalSubscriberConfig,\n subscribeByTopic,\n subscribeGlobalToTopics,\n type TopicSubscriptions,\n} from '../../subscriptions.mjs';\nimport type {\n DefaultContext,\n EventId,\n EventPublication,\n GenericEvent,\n SubscriptionId,\n} from '../../types.mjs';\n\ntype SubscriptionsForTopic = Record<\n string,\n (event: GenericEvent<string, unknown, DefaultContext>) => Promise<void>\n>;\n\ntype CreateInMemoryEventBusOptions = {\n maxRetries?: number;\n getNow?: () => Date;\n generateId?: () => EventId;\n};\n\ntype CreateInMemoryEventBusFromDefinitionsOptions<\n Definitions extends EventDefinitions,\n> = CreateInMemoryEventBusOptions & {\n eventDefinitions: Definitions;\n};\n\ntype CreateInMemoryEventBusResult<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n eventBus: EventBus<Event>;\n createNewEvent: CreateNewEvent<Event>;\n defineSubscriptions: (\n subscriptions: TopicSubscriptions<Event>,\n ) => TopicSubscriptions<Event>;\n subscribeAll: (subscriptions: TopicSubscriptions<Event>) => void;\n subscribeGlobal: (\n subscriptions: TopicSubscriptions<Event>,\n config: GlobalSubscriberConfig<Event>,\n ) => void;\n};\n\nexport function createInMemoryEventBus<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options?: CreateInMemoryEventBusOptions,\n): CreateInMemoryEventBusResult<Event>;\nexport function createInMemoryEventBus<Definitions extends EventDefinitions>(\n withUow: WithEventsUow<InferEventsFromDefinitions<Definitions>>,\n options: CreateInMemoryEventBusFromDefinitionsOptions<Definitions>,\n): Omit<\n CreateInMemoryEventBusResult<InferEventsFromDefinitions<Definitions>>,\n \"createNewEvent\"\n> & {\n createNewEvent: CreateNewEventFromDefinitions<Definitions>;\n};\nexport function createInMemoryEventBus<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options:\n | CreateInMemoryEventBusOptions\n | CreateInMemoryEventBusFromDefinitionsOptions<EventDefinitions> = {},\n) {\n const maxRetries = options.maxRetries ?? 3;\n const eventDefinitions =\n \"eventDefinitions\" in options ? options.eventDefinitions : undefined;\n const createNewEvent = eventDefinitions\n ? makeCreateNewEvent({\n getNow: options.getNow,\n generateId: options.generateId,\n eventDefinitions,\n })\n : makeCreateNewEvent<Event>({\n getNow: options.getNow,\n generateId: options.generateId,\n });\n const subscriptions: Partial<Record<string, SubscriptionsForTopic>> = {};\n\n const executeCallback = async (\n event: Event,\n subscriptionId: string,\n callback: (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>,\n ): Promise<\n { subscriptionId: string; errorMessage: string; stack?: string } | undefined\n > => {\n try {\n await callback(event);\n } catch (error) {\n return {\n subscriptionId,\n errorMessage: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n }\n };\n\n const getSubscriptionIdsToPublish = (\n event: Event,\n callbacksBySubscriptionId: SubscriptionsForTopic,\n ): string[] => {\n const allSubscriptionIds = Object.keys(callbacksBySubscriptionId);\n\n if (event.publications.length === 0 || event.status === \"to-republish\") {\n return allSubscriptionIds;\n }\n\n const lastPublication = event.publications.reduce((latest, current) =>\n current.publishedAt > latest.publishedAt ? current : latest,\n );\n const failedSubscriptionIds = (lastPublication.failures ?? []).map(\n (failure) => failure.subscriptionId,\n );\n\n return allSubscriptionIds.filter((id) =>\n failedSubscriptionIds.includes(id),\n );\n };\n\n const eventBus: EventBus<Event> = {\n publish: async (event) => {\n const publishedAt = new Date();\n const topic = event.topic;\n\n const callbacksBySubscriptionSlug = subscriptions[topic];\n\n if (!callbacksBySubscriptionSlug) {\n event.publications.push({\n publishedAt,\n publishedSubscribers: [],\n });\n event.status = \"published\";\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n return;\n }\n\n const subscriptionIdsToPublish = getSubscriptionIdsToPublish(\n event,\n callbacksBySubscriptionSlug,\n );\n\n const failuresOrUndefined = await Promise.all(\n subscriptionIdsToPublish.map((subscriptionId) =>\n executeCallback(\n event,\n subscriptionId,\n callbacksBySubscriptionSlug[subscriptionId],\n ),\n ),\n );\n\n const failures = failuresOrUndefined.filter(\n (\n f,\n ): f is {\n subscriptionId: string;\n errorMessage: string;\n stack?: string;\n } => f !== undefined,\n );\n\n const publications: EventPublication[] = [\n ...event.publications,\n {\n publishedAt,\n publishedSubscribers: subscriptionIdsToPublish.map(\n (id) => id as SubscriptionId,\n ),\n ...(failures.length > 0 && { failures }),\n },\n ];\n\n if (failures.length === 0) {\n event.status = \"published\";\n } else {\n const wasMaxNumberOfErrorsReached = publications.length >= maxRetries;\n event.status = wasMaxNumberOfErrorsReached\n ? \"quarantined\"\n : \"failed-but-will-retry\";\n }\n\n event.publications = publications;\n\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n },\n\n subscribe: ({ topic, subscriptionId, callBack }) => {\n if (!subscriptions[topic]) {\n subscriptions[topic] = {};\n }\n\n const subscriptionsForTopic = subscriptions[topic];\n if (subscriptionsForTopic) {\n subscriptionsForTopic[subscriptionId] = callBack as (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>;\n }\n },\n };\n\n /**\n * Identity function for type inference when defining subscription maps.\n * Ensures all topics are covered and handlers have correct payload types.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({\n * OrderCreated: [{ subscriptionId: \"notify\", handler: async (e) => {...} }],\n * OrderShipped: [], // Required even if empty\n * });\n * ```\n */\n const defineSubscriptions = (\n subscriptions: TopicSubscriptions<Event>,\n ): TopicSubscriptions<Event> => subscriptions;\n\n /**\n * Subscribe all handlers from a topic subscription map to this event bus.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({...});\n * subscribeAll(subscriptions);\n * ```\n */\n const subscribeAll = (subscriptions: TopicSubscriptions<Event>): void => {\n subscribeByTopic(eventBus, subscriptions);\n };\n\n /**\n * Subscribe a global handler to multiple topics with optional filtering.\n *\n * @example\n * ```typescript\n * subscribeGlobal(subscriptions, {\n * subscriptionId: \"audit-log\",\n * handler: async (event) => auditLog.record(event),\n * filter: { exclude: [\"NotificationAdded\"] },\n * });\n * ```\n */\n const subscribeGlobal = (\n subscriptions: TopicSubscriptions<Event>,\n config: GlobalSubscriberConfig<Event>,\n ): void => {\n subscribeGlobalToTopics(eventBus, subscriptions, config);\n };\n\n return {\n eventBus,\n createNewEvent,\n defineSubscriptions,\n subscribeAll,\n subscribeGlobal,\n };\n}\n"],"mappings":"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","names":["subscriptions"]}
|
|
@@ -6,6 +6,7 @@ import { EventRepository, WithEventsUow } from '../../ports/EventRepository.cjs'
|
|
|
6
6
|
export { createInMemoryEventBus } from './InMemoryEventBus.cjs';
|
|
7
7
|
export { createInMemoryEventQueries } from './InMemoryEventQueries.cjs';
|
|
8
8
|
import '../../createNewEvent.cjs';
|
|
9
|
+
import '../../eventDefinitions.cjs';
|
|
9
10
|
import '../../ports/EventBus.cjs';
|
|
10
11
|
import '../../subscriptions.cjs';
|
|
11
12
|
|
|
@@ -6,6 +6,7 @@ import { EventRepository, WithEventsUow } from '../../ports/EventRepository.js';
|
|
|
6
6
|
export { createInMemoryEventBus } from './InMemoryEventBus.js';
|
|
7
7
|
export { createInMemoryEventQueries } from './InMemoryEventQueries.js';
|
|
8
8
|
import '../../createNewEvent.js';
|
|
9
|
+
import '../../eventDefinitions.js';
|
|
9
10
|
import '../../ports/EventBus.js';
|
|
10
11
|
import '../../subscriptions.js';
|
|
11
12
|
|