@l-etabli/events 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -11
- package/dist/adapters/in-memory/InMemoryEventBus.mjs +1 -1
- package/dist/adapters/in-memory/InMemoryEventBus.mjs.map +1 -1
- package/dist/adapters/in-memory/InMemoryEventQueries.mjs.map +1 -1
- package/dist/adapters/in-memory/InMemoryEventRepository.cjs +2 -1
- package/dist/adapters/in-memory/InMemoryEventRepository.cjs.map +1 -1
- package/dist/adapters/in-memory/InMemoryEventRepository.mjs +2 -1
- package/dist/adapters/in-memory/InMemoryEventRepository.mjs.map +1 -1
- package/dist/adapters/in-memory/index.mjs +5 -5
- package/dist/adapters/in-memory/index.mjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventQueries.mjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventRepository.mjs.map +1 -1
- package/dist/adapters/kysely/index.mjs +2 -2
- package/dist/adapters/kysely/index.mjs.map +1 -1
- package/dist/createEventCrawler.mjs.map +1 -1
- package/dist/createNewEvent.mjs.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/dist/ports/EventRepository.cjs.map +1 -1
- package/dist/ports/EventRepository.d.cts +4 -3
- package/dist/ports/EventRepository.d.ts +4 -3
- package/package.json +1 -1
- package/src/adapters/in-memory/InMemoryEventRepository.ts +2 -1
- package/src/ports/EventRepository.ts +6 -5
package/README.md
CHANGED
|
@@ -1,16 +1,267 @@
|
|
|
1
|
-
# l-etabli/events
|
|
1
|
+
# @l-etabli/events
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Event-driven architecture library implementing the **outbox pattern** for TypeScript.
|
|
4
4
|
|
|
5
|
+
Events are persisted in the same transaction as your domain changes, then reliably published asynchronously. No lost events, even on failures.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
## Installation
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @l-etabli/events
|
|
11
|
+
```
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
For Kysely adapter (PostgreSQL):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @l-etabli/events kysely pg
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### 1. Define Your Events
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { GenericEvent } from "@l-etabli/events";
|
|
25
|
+
|
|
26
|
+
type MyEvents =
|
|
27
|
+
| GenericEvent<"UserCreated", { userId: string; email: string }>
|
|
28
|
+
| GenericEvent<"OrderPlaced", { orderId: string; amount: number }>;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Setup Event Infrastructure
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import {
|
|
35
|
+
createInMemoryEventBus,
|
|
36
|
+
createInMemoryEventRepositoryAndQueries,
|
|
37
|
+
createEventCrawler,
|
|
38
|
+
} from "@l-etabli/events";
|
|
39
|
+
|
|
40
|
+
const { eventQueries, withUow } = createInMemoryEventRepositoryAndQueries<MyEvents>();
|
|
41
|
+
const { eventBus, createNewEvent } = createInMemoryEventBus<MyEvents>(withUow);
|
|
42
|
+
|
|
43
|
+
const crawler = createEventCrawler({
|
|
44
|
+
withUow,
|
|
45
|
+
eventQueries,
|
|
46
|
+
eventBus,
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. Subscribe to Events
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
eventBus.subscribe({
|
|
54
|
+
topic: "OrderPlaced",
|
|
55
|
+
subscriptionId: "send-confirmation-email",
|
|
56
|
+
callBack: async (event) => {
|
|
57
|
+
await emailService.sendOrderConfirmation(event.payload.orderId);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. Emit Events (in a use case)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
await withUow(async (uow) => {
|
|
66
|
+
// Save your domain entity
|
|
67
|
+
await orderRepository.save(order);
|
|
68
|
+
|
|
69
|
+
// Emit event in the same transaction
|
|
70
|
+
await uow.eventRepository.saveNewEventsBatch([
|
|
71
|
+
createNewEvent({
|
|
72
|
+
topic: "OrderPlaced",
|
|
73
|
+
payload: { orderId: order.id, amount: order.total },
|
|
74
|
+
triggeredByUserId: currentUserId,
|
|
75
|
+
}),
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 5. Process Events
|
|
81
|
+
|
|
82
|
+
**Traditional server** - start background polling:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
crawler.start();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Serverless** - trigger on-demand after commit:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
await withUow(
|
|
92
|
+
async (uow) => {
|
|
93
|
+
await uow.eventRepository.saveNewEventsBatch([event]);
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
afterCommit: async () => {
|
|
97
|
+
await crawler.triggerProcessing();
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
);
|
|
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
|
+
```
|
|
123
|
+
|
|
124
|
+
## Event Lifecycle
|
|
125
|
+
|
|
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
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Define events with topic, payload, and optional context
|
|
145
|
+
type GenericEvent<Topic, Payload, Context?> = {
|
|
146
|
+
id: EventId;
|
|
147
|
+
topic: Topic;
|
|
148
|
+
payload: Payload;
|
|
149
|
+
status: EventStatus;
|
|
150
|
+
occurredAt: Date;
|
|
151
|
+
triggeredByUserId: UserId;
|
|
152
|
+
publications: EventPublication[];
|
|
153
|
+
context?: Context;
|
|
154
|
+
priority?: number;
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### `makeCreateNewEvent<Events>(options?)`
|
|
159
|
+
|
|
160
|
+
Creates a type-safe event factory. Payload is validated against topic at compile time.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const createEvent = makeCreateNewEvent<MyEvents>({
|
|
164
|
+
getNow: () => new Date(), // optional, for testing
|
|
165
|
+
generateId: () => crypto.randomUUID(), // optional, for testing
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Type-safe: payload must match topic
|
|
169
|
+
createEvent({ topic: "UserCreated", payload: { userId: "1", email: "a@b.com" }, triggeredByUserId: "u1" });
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `createInMemoryEventBus<Events>(withUow, options?)`
|
|
173
|
+
|
|
174
|
+
Creates an in-memory event bus with a typed `createNewEvent` function.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const { eventBus, createNewEvent } = createInMemoryEventBus<MyEvents>(withUow, {
|
|
178
|
+
maxRetries: 3, // default
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `createEventCrawler(config)`
|
|
183
|
+
|
|
184
|
+
Creates a background processor for publishing events.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const crawler = createEventCrawler({
|
|
188
|
+
withUow,
|
|
189
|
+
eventQueries,
|
|
190
|
+
eventBus,
|
|
191
|
+
options: {
|
|
192
|
+
batchSize: 100, // events per batch (default: 100)
|
|
193
|
+
maxParallelProcessing: 1, // parallel publishes (default: 1)
|
|
194
|
+
newEventsIntervalMs: 10000, // polling interval (default: 10s)
|
|
195
|
+
failedEventsIntervalMs: 60000, // retry interval (default: 60s)
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
crawler.start(); // Start background polling
|
|
200
|
+
crawler.processNewEvents(); // Manual: process new events
|
|
201
|
+
crawler.retryFailedEvents(); // Manual: retry failed events
|
|
202
|
+
crawler.triggerProcessing(); // Manual: process new + retry failed
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Database Setup (Kysely/PostgreSQL)
|
|
206
|
+
|
|
207
|
+
### Migration
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import type { Kysely } from "kysely";
|
|
211
|
+
|
|
212
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
213
|
+
await db.schema
|
|
214
|
+
.createTable("events")
|
|
215
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
216
|
+
.addColumn("topic", "text", (col) => col.notNull())
|
|
217
|
+
.addColumn("payload", "jsonb", (col) => col.notNull())
|
|
218
|
+
.addColumn("context", "jsonb")
|
|
219
|
+
.addColumn("status", "text", (col) => col.notNull())
|
|
220
|
+
.addColumn("triggeredByUserId", "text", (col) => col.notNull())
|
|
221
|
+
.addColumn("occurredAt", "timestamptz", (col) => col.notNull())
|
|
222
|
+
.addColumn("publications", "jsonb", (col) => col.notNull().defaultTo("[]"))
|
|
223
|
+
.addColumn("priority", "integer")
|
|
224
|
+
.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
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
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
|
+
## Examples
|
|
258
|
+
|
|
259
|
+
See the [`examples/`](./examples/) directory for complete implementations:
|
|
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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeCreateNewEvent } from "../../createNewEvent.
|
|
1
|
+
import { makeCreateNewEvent } from "../../createNewEvent.mjs";
|
|
2
2
|
const createInMemoryEventBus = (withUow, options = {}) => {
|
|
3
3
|
const maxRetries = options.maxRetries ?? 3;
|
|
4
4
|
const createNewEvent = makeCreateNewEvent({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import { makeCreateNewEvent } from '../../createNewEvent.
|
|
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 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 failures: [],\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,\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 return { eventBus, createNewEvent };\n};\n"],"mappings":"AAAA,SAAS,0BAA0B;AAsB5B,MAAM,yBAAyB,CAGpC,SACA,UAAyC,CAAC,MACvC;AACH,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,iBAAiB,mBAA0B;AAAA,IAC/C,QAAQ,QAAQ;AAAA,IAChB,YAAY,QAAQ;AAAA,EACtB,CAAC;AACD,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,wBAAwB,gBAAgB,SAAS;AAAA,MACrD,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,UACvB,UAAU,CAAC;AAAA,QACb,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;AAAA,QACF;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;AAEA,SAAO,EAAE,UAAU,eAAe;AACpC;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventQueries.ts"],"sourcesContent":["import type { EventQueries } from '../../ports/EventQueries.
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventQueries.ts"],"sourcesContent":["import type { EventQueries } from '../../ports/EventQueries.mjs';\nimport type { DefaultContext, GenericEvent } from '../../types.mjs';\nimport type { InMemoryEventRepositoryHelpers } from './InMemoryEventRepository.mjs';\n\nexport const createInMemoryEventQueries = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n helpers: InMemoryEventRepositoryHelpers<Event>,\n): { eventQueries: EventQueries<Event> } => ({\n eventQueries: {\n getEvents: async ({ filters, limit }) => {\n const matchesContext = (event: Event): boolean => {\n if (!filters.context) return true;\n if (!event.context) return false;\n\n return Object.entries(filters.context).every(\n ([key, value]) => event.context?.[key] === value,\n );\n };\n\n const matchesOccurredAt = (event: Event): boolean => {\n if (!filters.occurredAt) return true;\n\n const { from, to } = filters.occurredAt;\n const eventTime = event.occurredAt.getTime();\n\n if (from && eventTime < from.getTime()) return false;\n if (to && eventTime > to.getTime()) return false;\n\n return true;\n };\n\n return helpers\n .getAllEvents()\n .filter(\n (event) =>\n filters.statuses.includes(event.status) &&\n matchesContext(event) &&\n matchesOccurredAt(event),\n )\n .slice(0, limit);\n },\n },\n});\n"],"mappings":"AAIO,MAAM,6BAA6B,CAGxC,aAC2C;AAAA,EAC3C,cAAc;AAAA,IACZ,WAAW,OAAO,EAAE,SAAS,MAAM,MAAM;AACvC,YAAM,iBAAiB,CAAC,UAA0B;AAChD,YAAI,CAAC,QAAQ,QAAS,QAAO;AAC7B,YAAI,CAAC,MAAM,QAAS,QAAO;AAE3B,eAAO,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAAA,UACrC,CAAC,CAAC,KAAK,KAAK,MAAM,MAAM,UAAU,GAAG,MAAM;AAAA,QAC7C;AAAA,MACF;AAEA,YAAM,oBAAoB,CAAC,UAA0B;AACnD,YAAI,CAAC,QAAQ,WAAY,QAAO;AAEhC,cAAM,EAAE,MAAM,GAAG,IAAI,QAAQ;AAC7B,cAAM,YAAY,MAAM,WAAW,QAAQ;AAE3C,YAAI,QAAQ,YAAY,KAAK,QAAQ,EAAG,QAAO;AAC/C,YAAI,MAAM,YAAY,GAAG,QAAQ,EAAG,QAAO;AAE3C,eAAO;AAAA,MACT;AAEA,aAAO,QACJ,aAAa,EACb;AAAA,QACC,CAAC,UACC,QAAQ,SAAS,SAAS,MAAM,MAAM,KACtC,eAAe,KAAK,KACpB,kBAAkB,KAAK;AAAA,MAC3B,EACC,MAAM,GAAG,KAAK;AAAA,IACnB;AAAA,EACF;AACF;","names":[]}
|
|
@@ -56,8 +56,9 @@ const createInMemoryEventRepository = () => {
|
|
|
56
56
|
};
|
|
57
57
|
const createInMemoryWithUow = (eventRepository) => {
|
|
58
58
|
const withUow = async (fn, options) => {
|
|
59
|
-
await fn({ eventRepository });
|
|
59
|
+
const result = await fn({ eventRepository });
|
|
60
60
|
await options?.afterCommit?.();
|
|
61
|
+
return result;
|
|
61
62
|
};
|
|
62
63
|
return { withUow };
|
|
63
64
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventRepository.ts"],"sourcesContent":["import type {\n EventRepository,\n WithEventsUow,\n} from \"../../ports/EventRepository.ts\";\nimport type { DefaultContext, GenericEvent } from \"../../types.ts\";\n\nexport type InMemoryEventRepositoryHelpers<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = { getAllEvents: () => Event[]; setEvents: (events: Event[]) => void };\n\nexport const createInMemoryEventRepository = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(): {\n eventRepository: EventRepository<Event>;\n helpers: InMemoryEventRepositoryHelpers<Event>;\n} => {\n const eventById: Record<string, Event> = {};\n\n const eventRepository: EventRepository<Event> = {\n save: async (event) => {\n eventById[event.id] = event;\n },\n saveNewEventsBatch: async (events) => {\n events.forEach((event) => {\n eventById[event.id] = event;\n });\n },\n markEventsAsInProcess: async (events) => {\n events.forEach((event) => {\n eventById[event.id] = { ...event, status: \"in-process\" };\n });\n },\n };\n\n return {\n eventRepository,\n helpers: {\n getAllEvents: () => Object.values(eventById),\n setEvents: (events) => {\n Object.keys(eventById).forEach((key) => {\n delete eventById[key];\n });\n\n events.forEach((event) => {\n eventById[event.id] = event;\n });\n },\n },\n };\n};\n\nexport const createInMemoryWithUow = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n eventRepository: EventRepository<Event>,\n): { withUow: WithEventsUow<Event> } => {\n // In-memory adapter awaits afterCommit for predictable test behavior\n const withUow: WithEventsUow<Event> = async (fn, options) => {\n await fn({ eventRepository });\n await options?.afterCommit?.();\n };\n return { withUow };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUO,MAAM,gCAAgC,MAKxC;AACH,QAAM,YAAmC,CAAC;AAE1C,QAAM,kBAA0C;AAAA,IAC9C,MAAM,OAAO,UAAU;AACrB,gBAAU,MAAM,EAAE,IAAI;AAAA,IACxB;AAAA,IACA,oBAAoB,OAAO,WAAW;AACpC,aAAO,QAAQ,CAAC,UAAU;AACxB,kBAAU,MAAM,EAAE,IAAI;AAAA,MACxB,CAAC;AAAA,IACH;AAAA,IACA,uBAAuB,OAAO,WAAW;AACvC,aAAO,QAAQ,CAAC,UAAU;AACxB,kBAAU,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,QAAQ,aAAa;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,MACP,cAAc,MAAM,OAAO,OAAO,SAAS;AAAA,MAC3C,WAAW,CAAC,WAAW;AACrB,eAAO,KAAK,SAAS,EAAE,QAAQ,CAAC,QAAQ;AACtC,iBAAO,UAAU,GAAG;AAAA,QACtB,CAAC;AAED,eAAO,QAAQ,CAAC,UAAU;AACxB,oBAAU,MAAM,EAAE,IAAI;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,wBAAwB,CAGnC,oBACsC;AAEtC,QAAM,UAAgC,OAAO,IAAI,YAAY;AAC3D,UAAM,GAAG,EAAE,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventRepository.ts"],"sourcesContent":["import type {\n EventRepository,\n WithEventsUow,\n} from \"../../ports/EventRepository.ts\";\nimport type { DefaultContext, GenericEvent } from \"../../types.ts\";\n\nexport type InMemoryEventRepositoryHelpers<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = { getAllEvents: () => Event[]; setEvents: (events: Event[]) => void };\n\nexport const createInMemoryEventRepository = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(): {\n eventRepository: EventRepository<Event>;\n helpers: InMemoryEventRepositoryHelpers<Event>;\n} => {\n const eventById: Record<string, Event> = {};\n\n const eventRepository: EventRepository<Event> = {\n save: async (event) => {\n eventById[event.id] = event;\n },\n saveNewEventsBatch: async (events) => {\n events.forEach((event) => {\n eventById[event.id] = event;\n });\n },\n markEventsAsInProcess: async (events) => {\n events.forEach((event) => {\n eventById[event.id] = { ...event, status: \"in-process\" };\n });\n },\n };\n\n return {\n eventRepository,\n helpers: {\n getAllEvents: () => Object.values(eventById),\n setEvents: (events) => {\n Object.keys(eventById).forEach((key) => {\n delete eventById[key];\n });\n\n events.forEach((event) => {\n eventById[event.id] = event;\n });\n },\n },\n };\n};\n\nexport const createInMemoryWithUow = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n eventRepository: EventRepository<Event>,\n): { withUow: WithEventsUow<Event> } => {\n // In-memory adapter awaits afterCommit for predictable test behavior\n const withUow: WithEventsUow<Event> = async (fn, options) => {\n const result = await fn({ eventRepository });\n await options?.afterCommit?.();\n return result;\n };\n return { withUow };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUO,MAAM,gCAAgC,MAKxC;AACH,QAAM,YAAmC,CAAC;AAE1C,QAAM,kBAA0C;AAAA,IAC9C,MAAM,OAAO,UAAU;AACrB,gBAAU,MAAM,EAAE,IAAI;AAAA,IACxB;AAAA,IACA,oBAAoB,OAAO,WAAW;AACpC,aAAO,QAAQ,CAAC,UAAU;AACxB,kBAAU,MAAM,EAAE,IAAI;AAAA,MACxB,CAAC;AAAA,IACH;AAAA,IACA,uBAAuB,OAAO,WAAW;AACvC,aAAO,QAAQ,CAAC,UAAU;AACxB,kBAAU,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,QAAQ,aAAa;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,MACP,cAAc,MAAM,OAAO,OAAO,SAAS;AAAA,MAC3C,WAAW,CAAC,WAAW;AACrB,eAAO,KAAK,SAAS,EAAE,QAAQ,CAAC,QAAQ;AACtC,iBAAO,UAAU,GAAG;AAAA,QACtB,CAAC;AAED,eAAO,QAAQ,CAAC,UAAU;AACxB,oBAAU,MAAM,EAAE,IAAI;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,wBAAwB,CAGnC,oBACsC;AAEtC,QAAM,UAAgC,OAAO,IAAI,YAAY;AAC3D,UAAM,SAAS,MAAM,GAAG,EAAE,gBAAgB,CAAC;AAC3C,UAAM,SAAS,cAAc;AAC7B,WAAO;AAAA,EACT;AACA,SAAO,EAAE,QAAQ;AACnB;","names":[]}
|
|
@@ -32,8 +32,9 @@ const createInMemoryEventRepository = () => {
|
|
|
32
32
|
};
|
|
33
33
|
const createInMemoryWithUow = (eventRepository) => {
|
|
34
34
|
const withUow = async (fn, options) => {
|
|
35
|
-
await fn({ eventRepository });
|
|
35
|
+
const result = await fn({ eventRepository });
|
|
36
36
|
await options?.afterCommit?.();
|
|
37
|
+
return result;
|
|
37
38
|
};
|
|
38
39
|
return { withUow };
|
|
39
40
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventRepository.ts"],"sourcesContent":["import type {\n EventRepository,\n WithEventsUow,\n} from '../../ports/EventRepository.
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventRepository.ts"],"sourcesContent":["import type {\n EventRepository,\n WithEventsUow,\n} from '../../ports/EventRepository.mjs';\nimport type { DefaultContext, GenericEvent } from '../../types.mjs';\n\nexport type InMemoryEventRepositoryHelpers<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = { getAllEvents: () => Event[]; setEvents: (events: Event[]) => void };\n\nexport const createInMemoryEventRepository = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(): {\n eventRepository: EventRepository<Event>;\n helpers: InMemoryEventRepositoryHelpers<Event>;\n} => {\n const eventById: Record<string, Event> = {};\n\n const eventRepository: EventRepository<Event> = {\n save: async (event) => {\n eventById[event.id] = event;\n },\n saveNewEventsBatch: async (events) => {\n events.forEach((event) => {\n eventById[event.id] = event;\n });\n },\n markEventsAsInProcess: async (events) => {\n events.forEach((event) => {\n eventById[event.id] = { ...event, status: \"in-process\" };\n });\n },\n };\n\n return {\n eventRepository,\n helpers: {\n getAllEvents: () => Object.values(eventById),\n setEvents: (events) => {\n Object.keys(eventById).forEach((key) => {\n delete eventById[key];\n });\n\n events.forEach((event) => {\n eventById[event.id] = event;\n });\n },\n },\n };\n};\n\nexport const createInMemoryWithUow = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n eventRepository: EventRepository<Event>,\n): { withUow: WithEventsUow<Event> } => {\n // In-memory adapter awaits afterCommit for predictable test behavior\n const withUow: WithEventsUow<Event> = async (fn, options) => {\n const result = await fn({ eventRepository });\n await options?.afterCommit?.();\n return result;\n };\n return { withUow };\n};\n"],"mappings":"AAUO,MAAM,gCAAgC,MAKxC;AACH,QAAM,YAAmC,CAAC;AAE1C,QAAM,kBAA0C;AAAA,IAC9C,MAAM,OAAO,UAAU;AACrB,gBAAU,MAAM,EAAE,IAAI;AAAA,IACxB;AAAA,IACA,oBAAoB,OAAO,WAAW;AACpC,aAAO,QAAQ,CAAC,UAAU;AACxB,kBAAU,MAAM,EAAE,IAAI;AAAA,MACxB,CAAC;AAAA,IACH;AAAA,IACA,uBAAuB,OAAO,WAAW;AACvC,aAAO,QAAQ,CAAC,UAAU;AACxB,kBAAU,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,QAAQ,aAAa;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,MACP,cAAc,MAAM,OAAO,OAAO,SAAS;AAAA,MAC3C,WAAW,CAAC,WAAW;AACrB,eAAO,KAAK,SAAS,EAAE,QAAQ,CAAC,QAAQ;AACtC,iBAAO,UAAU,GAAG;AAAA,QACtB,CAAC;AAED,eAAO,QAAQ,CAAC,UAAU;AACxB,oBAAU,MAAM,EAAE,IAAI;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,wBAAwB,CAGnC,oBACsC;AAEtC,QAAM,UAAgC,OAAO,IAAI,YAAY;AAC3D,UAAM,SAAS,MAAM,GAAG,EAAE,gBAAgB,CAAC;AAC3C,UAAM,SAAS,cAAc;AAC7B,WAAO;AAAA,EACT;AACA,SAAO,EAAE,QAAQ;AACnB;","names":[]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { createInMemoryEventQueries } from "./InMemoryEventQueries.
|
|
1
|
+
import { createInMemoryEventQueries } from "./InMemoryEventQueries.mjs";
|
|
2
2
|
import {
|
|
3
3
|
createInMemoryEventRepository,
|
|
4
4
|
createInMemoryWithUow
|
|
5
|
-
} from "./InMemoryEventRepository.
|
|
6
|
-
export * from "./InMemoryEventBus.
|
|
7
|
-
export * from "./InMemoryEventQueries.
|
|
8
|
-
export * from "./InMemoryEventRepository.
|
|
5
|
+
} from "./InMemoryEventRepository.mjs";
|
|
6
|
+
export * from "./InMemoryEventBus.mjs";
|
|
7
|
+
export * from "./InMemoryEventQueries.mjs";
|
|
8
|
+
export * from "./InMemoryEventRepository.mjs";
|
|
9
9
|
const createInMemoryEventRepositoryAndQueries = () => {
|
|
10
10
|
const { eventRepository, helpers } = createInMemoryEventRepository();
|
|
11
11
|
const { eventQueries } = createInMemoryEventQueries(helpers);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/index.ts"],"sourcesContent":["import type { DefaultContext, GenericEvent } from '../../types.
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/in-memory/index.ts"],"sourcesContent":["import type { DefaultContext, GenericEvent } from '../../types.mjs';\nimport { createInMemoryEventQueries } from './InMemoryEventQueries.mjs';\nimport {\n createInMemoryEventRepository,\n createInMemoryWithUow,\n} from './InMemoryEventRepository.mjs';\n\nexport * from './InMemoryEventBus.mjs';\nexport * from './InMemoryEventQueries.mjs';\nexport * from './InMemoryEventRepository.mjs';\n\nexport const createInMemoryEventRepositoryAndQueries = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>() => {\n const { eventRepository, helpers } = createInMemoryEventRepository<Event>();\n const { eventQueries } = createInMemoryEventQueries<Event>(helpers);\n const { withUow } = createInMemoryWithUow<Event>(eventRepository);\n return { eventRepository, eventQueries, helpers, withUow };\n};\n"],"mappings":"AACA,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,cAAc;AACd,cAAc;AACd,cAAc;AAEP,MAAM,0CAA0C,MAEhD;AACL,QAAM,EAAE,iBAAiB,QAAQ,IAAI,8BAAqC;AAC1E,QAAM,EAAE,aAAa,IAAI,2BAAkC,OAAO;AAClE,QAAM,EAAE,QAAQ,IAAI,sBAA6B,eAAe;AAChE,SAAO,EAAE,iBAAiB,cAAc,SAAS,QAAQ;AAC3D;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/kysely/KyselyEventQueries.ts"],"sourcesContent":["import type { Kysely, SqlBool } from \"kysely\";\nimport { sql } from \"kysely\";\nimport type { EventQueries } from '../../ports/EventQueries.
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/kysely/KyselyEventQueries.ts"],"sourcesContent":["import type { Kysely, SqlBool } from \"kysely\";\nimport { sql } from \"kysely\";\nimport type { EventQueries } from '../../ports/EventQueries.mjs';\nimport type { DefaultContext, GenericEvent } from '../../types.mjs';\nimport type { EventsTable } from './types.mjs';\n\nexport const createKyselyEventQueries = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n db: Kysely<EventsTable>,\n): EventQueries<Event> => ({\n getEvents: async ({ filters, limit }) => {\n let query = db\n .selectFrom(\"events\")\n .selectAll()\n .where(\"status\", \"in\", filters.statuses)\n .limit(limit);\n\n if (filters.context) {\n for (const [key, value] of Object.entries(filters.context)) {\n query = query.where(sql<SqlBool>`context->>${key} = ${value}`);\n }\n }\n\n if (filters.occurredAt?.from) {\n query = query.where(\"occurredAt\", \">=\", filters.occurredAt.from);\n }\n\n if (filters.occurredAt?.to) {\n query = query.where(\"occurredAt\", \"<=\", filters.occurredAt.to);\n }\n\n const rows = await query.execute();\n return rows.map(\n (row: EventsTable[\"events\"]) =>\n ({\n ...row,\n context: row.context ?? undefined,\n priority: row.priority ?? undefined,\n }) as Event,\n );\n },\n});\n"],"mappings":"AACA,SAAS,WAAW;AAKb,MAAM,2BAA2B,CAGtC,QACyB;AAAA,EACzB,WAAW,OAAO,EAAE,SAAS,MAAM,MAAM;AACvC,QAAI,QAAQ,GACT,WAAW,QAAQ,EACnB,UAAU,EACV,MAAM,UAAU,MAAM,QAAQ,QAAQ,EACtC,MAAM,KAAK;AAEd,QAAI,QAAQ,SAAS;AACnB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,OAAO,GAAG;AAC1D,gBAAQ,MAAM,MAAM,gBAAyB,GAAG,MAAM,KAAK,EAAE;AAAA,MAC/D;AAAA,IACF;AAEA,QAAI,QAAQ,YAAY,MAAM;AAC5B,cAAQ,MAAM,MAAM,cAAc,MAAM,QAAQ,WAAW,IAAI;AAAA,IACjE;AAEA,QAAI,QAAQ,YAAY,IAAI;AAC1B,cAAQ,MAAM,MAAM,cAAc,MAAM,QAAQ,WAAW,EAAE;AAAA,IAC/D;AAEA,UAAM,OAAO,MAAM,MAAM,QAAQ;AACjC,WAAO,KAAK;AAAA,MACV,CAAC,SACE;AAAA,QACC,GAAG;AAAA,QACH,SAAS,IAAI,WAAW;AAAA,QACxB,UAAU,IAAI,YAAY;AAAA,MAC5B;AAAA,IACJ;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/kysely/KyselyEventRepository.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\nimport type { EventRepository } from '../../ports/EventRepository.
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/kysely/KyselyEventRepository.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\nimport type { EventRepository } from '../../ports/EventRepository.mjs';\nimport type { DefaultContext, GenericEvent } from '../../types.mjs';\nimport type { EventsTable } from './types.mjs';\n\nexport const createKyselyEventRepository = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n db: Kysely<EventsTable>,\n): EventRepository<Event> => ({\n save: async (event) => {\n await db\n .insertInto(\"events\")\n .values(event)\n .onConflict((oc) =>\n oc.column(\"id\").doUpdateSet({\n topic: event.topic,\n payload: event.payload,\n context: event.context,\n status: event.status,\n triggeredByUserId: event.triggeredByUserId,\n occurredAt: event.occurredAt,\n publications: event.publications,\n priority: event.priority,\n }),\n )\n .execute();\n },\n\n saveNewEventsBatch: async (events) => {\n if (events.length === 0) return;\n await db.insertInto(\"events\").values(events).execute();\n },\n\n markEventsAsInProcess: async (events) => {\n if (events.length === 0) return;\n const ids = events.map((e) => e.id);\n\n // Lock the rows to prevent concurrent processing\n const lockedRows = await db\n .selectFrom(\"events\")\n .select(\"id\")\n .where(\"id\", \"in\", ids)\n .forUpdate()\n .skipLocked()\n .execute();\n\n if (lockedRows.length === 0) return;\n const lockedIds = lockedRows.map((r) => r.id);\n\n // Update status to in-process (only for locked rows)\n await db\n .updateTable(\"events\")\n .set({ status: \"in-process\" })\n .where(\"id\", \"in\", lockedIds)\n .execute();\n },\n});\n"],"mappings":"AAKO,MAAM,8BAA8B,CAGzC,QAC4B;AAAA,EAC5B,MAAM,OAAO,UAAU;AACrB,UAAM,GACH,WAAW,QAAQ,EACnB,OAAO,KAAK,EACZ;AAAA,MAAW,CAAC,OACX,GAAG,OAAO,IAAI,EAAE,YAAY;AAAA,QAC1B,OAAO,MAAM;AAAA,QACb,SAAS,MAAM;AAAA,QACf,SAAS,MAAM;AAAA,QACf,QAAQ,MAAM;AAAA,QACd,mBAAmB,MAAM;AAAA,QACzB,YAAY,MAAM;AAAA,QAClB,cAAc,MAAM;AAAA,QACpB,UAAU,MAAM;AAAA,MAClB,CAAC;AAAA,IACH,EACC,QAAQ;AAAA,EACb;AAAA,EAEA,oBAAoB,OAAO,WAAW;AACpC,QAAI,OAAO,WAAW,EAAG;AACzB,UAAM,GAAG,WAAW,QAAQ,EAAE,OAAO,MAAM,EAAE,QAAQ;AAAA,EACvD;AAAA,EAEA,uBAAuB,OAAO,WAAW;AACvC,QAAI,OAAO,WAAW,EAAG;AACzB,UAAM,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE;AAGlC,UAAM,aAAa,MAAM,GACtB,WAAW,QAAQ,EACnB,OAAO,IAAI,EACX,MAAM,MAAM,MAAM,GAAG,EACrB,UAAU,EACV,WAAW,EACX,QAAQ;AAEX,QAAI,WAAW,WAAW,EAAG;AAC7B,UAAM,YAAY,WAAW,IAAI,CAAC,MAAM,EAAE,EAAE;AAG5C,UAAM,GACH,YAAY,QAAQ,EACpB,IAAI,EAAE,QAAQ,aAAa,CAAC,EAC5B,MAAM,MAAM,MAAM,SAAS,EAC3B,QAAQ;AAAA,EACb;AACF;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createKyselyEventQueries } from "./KyselyEventQueries.
|
|
2
|
-
import { createKyselyEventRepository } from "./KyselyEventRepository.
|
|
1
|
+
import { createKyselyEventQueries } from "./KyselyEventQueries.mjs";
|
|
2
|
+
import { createKyselyEventRepository } from "./KyselyEventRepository.mjs";
|
|
3
3
|
export {
|
|
4
4
|
createKyselyEventQueries,
|
|
5
5
|
createKyselyEventRepository
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/kysely/index.ts"],"sourcesContent":["export { createKyselyEventQueries } from './KyselyEventQueries.
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/kysely/index.ts"],"sourcesContent":["export { createKyselyEventQueries } from './KyselyEventQueries.mjs';\nexport { createKyselyEventRepository } from './KyselyEventRepository.mjs';\nexport type { EventsTable, TypedEventsTable } from './types.mjs';\n"],"mappings":"AAAA,SAAS,gCAAgC;AACzC,SAAS,mCAAmC;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/createEventCrawler.ts"],"sourcesContent":["import type { EventBus } from './ports/EventBus.
|
|
1
|
+
{"version":3,"sources":["../src/createEventCrawler.ts"],"sourcesContent":["import type { EventBus } from './ports/EventBus.mjs';\nimport type { EventQueries } from './ports/EventQueries.mjs';\nimport type { WithEventsUow } from './ports/EventRepository.mjs';\nimport type { DefaultContext, GenericEvent } from './types.mjs';\n\n/** Configuration options for the event crawler. */\ntype CreateEventCrawlerOptions = {\n /** Max events to fetch per batch (default: 100). */\n batchSize?: number;\n /** Max events to publish in parallel (default: 1). */\n maxParallelProcessing?: number;\n /** Interval for processing new events in ms (default: 10000). */\n newEventsIntervalMs?: number;\n /** Interval for retrying failed events in ms (default: 60000). */\n failedEventsIntervalMs?: number;\n};\n\nconst splitIntoChunks = <T>(array: T[], chunkSize: number): T[][] => {\n const chunks: T[][] = [];\n for (let i = 0; i < array.length; i += chunkSize) {\n chunks.push(array.slice(i, i + chunkSize));\n }\n return chunks;\n};\n\n/**\n * Creates a background event crawler that processes and publishes events.\n *\n * The crawler runs two loops:\n * 1. Process new events: polls for \"never-published\" events and publishes them\n * 2. Retry failed events: polls for failed events and retries them\n *\n * @returns Object with:\n * - `start()`: Start the background polling loops (for traditional server environments)\n * - `processNewEvents()`: Manually trigger new event processing\n * - `retryFailedEvents()`: Manually trigger failed event retry\n * - `triggerProcessing()`: Process both new and failed events (for serverless environments)\n *\n * @example\n * ```typescript\n * const crawler = createEventCrawler({\n * withUow,\n * eventQueries,\n * eventBus,\n * options: { batchSize: 50, newEventsIntervalMs: 5000 },\n * });\n *\n * // Traditional server mode: Start background processing\n * crawler.start();\n *\n * // Serverless mode: Trigger on-demand after saving events\n * await withUow(async (uow) => {\n * await uow.eventRepository.save(event);\n * }, {\n * afterCommit: () => {\n * crawler.triggerProcessing().catch(console.error);\n * }\n * });\n *\n * // Or trigger manually (useful for testing)\n * await crawler.processNewEvents();\n * ```\n */\nexport const createEventCrawler = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>({\n withUow,\n eventQueries,\n eventBus,\n options = {},\n}: {\n withUow: WithEventsUow<Event>;\n eventQueries: EventQueries<Event>;\n eventBus: EventBus<Event>;\n options?: CreateEventCrawlerOptions;\n}) => {\n const batchSize = options.batchSize ?? 100;\n const maxParallelProcessing = options.maxParallelProcessing ?? 1;\n const newEventsIntervalMs = options.newEventsIntervalMs ?? 10_000;\n const failedEventsIntervalMs = options.failedEventsIntervalMs ?? 60_000;\n\n const publishEventsInParallel = async (events: Event[]) => {\n const eventChunks = splitIntoChunks(events, maxParallelProcessing);\n for (const chunk of eventChunks) {\n await Promise.all(chunk.map((event) => eventBus.publish(event)));\n }\n };\n\n const processNewEvents = async (): Promise<void> => {\n const events = await eventQueries.getEvents({\n filters: { statuses: [\"never-published\"] },\n limit: batchSize,\n });\n\n if (events.length === 0) return;\n\n await withUow(async (uow) => {\n await uow.eventRepository.markEventsAsInProcess(events);\n });\n\n await publishEventsInParallel(events);\n };\n\n const retryFailedEvents = async (): Promise<void> => {\n const oneMinuteAgo = new Date(Date.now() - 60_000);\n\n const events = await eventQueries.getEvents({\n filters: {\n statuses: [\"to-republish\", \"failed-but-will-retry\"],\n occurredAt: { to: oneMinuteAgo },\n },\n limit: batchSize,\n });\n\n if (events.length === 0) return;\n\n await publishEventsInParallel(events);\n };\n\n const triggerProcessing = async (): Promise<void> => {\n // Use Promise.allSettled to ensure both processing steps run independently\n // If processNewEvents fails, retryFailedEvents will still execute\n const results = await Promise.allSettled([\n processNewEvents(),\n retryFailedEvents(),\n ]);\n\n // Re-throw if both failed\n const errors = results\n .filter((r) => r.status === \"rejected\")\n .map((r) => (r as PromiseRejectedResult).reason);\n\n if (errors.length > 0) {\n throw new AggregateError(errors, \"Event processing failed\");\n }\n };\n\n const start = () => {\n const scheduleProcessNewEvents = () => {\n setTimeout(async () => {\n try {\n await processNewEvents();\n } catch (error) {\n console.error(\"Error processing new events:\", error);\n } finally {\n scheduleProcessNewEvents();\n }\n }, newEventsIntervalMs);\n };\n\n const scheduleRetryFailedEvents = () => {\n setTimeout(async () => {\n try {\n await retryFailedEvents();\n } catch (error) {\n console.error(\"Error retrying failed events:\", error);\n } finally {\n scheduleRetryFailedEvents();\n }\n }, failedEventsIntervalMs);\n };\n\n scheduleProcessNewEvents();\n scheduleRetryFailedEvents();\n };\n\n return {\n processNewEvents,\n retryFailedEvents,\n triggerProcessing,\n start,\n };\n};\n"],"mappings":"AAiBA,MAAM,kBAAkB,CAAI,OAAY,cAA6B;AACnE,QAAM,SAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;AAChD,WAAO,KAAK,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;AAAA,EAC3C;AACA,SAAO;AACT;AAwCO,MAAM,qBAAqB,CAEhC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU,CAAC;AACb,MAKM;AACJ,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,wBAAwB,QAAQ,yBAAyB;AAC/D,QAAM,sBAAsB,QAAQ,uBAAuB;AAC3D,QAAM,yBAAyB,QAAQ,0BAA0B;AAEjE,QAAM,0BAA0B,OAAO,WAAoB;AACzD,UAAM,cAAc,gBAAgB,QAAQ,qBAAqB;AACjE,eAAW,SAAS,aAAa;AAC/B,YAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,UAAU,SAAS,QAAQ,KAAK,CAAC,CAAC;AAAA,IACjE;AAAA,EACF;AAEA,QAAM,mBAAmB,YAA2B;AAClD,UAAM,SAAS,MAAM,aAAa,UAAU;AAAA,MAC1C,SAAS,EAAE,UAAU,CAAC,iBAAiB,EAAE;AAAA,MACzC,OAAO;AAAA,IACT,CAAC;AAED,QAAI,OAAO,WAAW,EAAG;AAEzB,UAAM,QAAQ,OAAO,QAAQ;AAC3B,YAAM,IAAI,gBAAgB,sBAAsB,MAAM;AAAA,IACxD,CAAC;AAED,UAAM,wBAAwB,MAAM;AAAA,EACtC;AAEA,QAAM,oBAAoB,YAA2B;AACnD,UAAM,eAAe,IAAI,KAAK,KAAK,IAAI,IAAI,GAAM;AAEjD,UAAM,SAAS,MAAM,aAAa,UAAU;AAAA,MAC1C,SAAS;AAAA,QACP,UAAU,CAAC,gBAAgB,uBAAuB;AAAA,QAClD,YAAY,EAAE,IAAI,aAAa;AAAA,MACjC;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAED,QAAI,OAAO,WAAW,EAAG;AAEzB,UAAM,wBAAwB,MAAM;AAAA,EACtC;AAEA,QAAM,oBAAoB,YAA2B;AAGnD,UAAM,UAAU,MAAM,QAAQ,WAAW;AAAA,MACvC,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IACpB,CAAC;AAGD,UAAM,SAAS,QACZ,OAAO,CAAC,MAAM,EAAE,WAAW,UAAU,EACrC,IAAI,CAAC,MAAO,EAA4B,MAAM;AAEjD,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,IAAI,eAAe,QAAQ,yBAAyB;AAAA,IAC5D;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM;AAClB,UAAM,2BAA2B,MAAM;AACrC,iBAAW,YAAY;AACrB,YAAI;AACF,gBAAM,iBAAiB;AAAA,QACzB,SAAS,OAAO;AACd,kBAAQ,MAAM,gCAAgC,KAAK;AAAA,QACrD,UAAE;AACA,mCAAyB;AAAA,QAC3B;AAAA,MACF,GAAG,mBAAmB;AAAA,IACxB;AAEA,UAAM,4BAA4B,MAAM;AACtC,iBAAW,YAAY;AACrB,YAAI;AACF,gBAAM,kBAAkB;AAAA,QAC1B,SAAS,OAAO;AACd,kBAAQ,MAAM,iCAAiC,KAAK;AAAA,QACtD,UAAE;AACA,oCAA0B;AAAA,QAC5B;AAAA,MACF,GAAG,sBAAsB;AAAA,IAC3B;AAEA,6BAAyB;AACzB,8BAA0B;AAAA,EAC5B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/createNewEvent.ts"],"sourcesContent":["import type { DefaultContext, EventId, GenericEvent, UserId } from './types.
|
|
1
|
+
{"version":3,"sources":["../src/createNewEvent.ts"],"sourcesContent":["import type { DefaultContext, EventId, GenericEvent, UserId } from './types.mjs';\n\ntype MakeCreateNewEventOptions = {\n getNow?: () => Date;\n generateId?: () => EventId;\n};\n\ntype ContextParam<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n Topic extends Event[\"topic\"],\n> = Extract<Event, { topic: Topic }>[\"context\"] extends undefined\n ? { context?: undefined }\n : { context: Extract<Event, { topic: Topic }>[\"context\"] };\n\ntype CreateNewEventParams<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n Topic extends Event[\"topic\"],\n> = {\n topic: Topic;\n payload: Extract<Event, { topic: Topic }>[\"payload\"];\n triggeredByUserId: UserId;\n id?: EventId;\n occurredAt?: Date;\n priority?: number;\n} & ContextParam<Event, Topic>;\n\n/**\n * Creates a typed event creator factory for your event union.\n * Provides type-safe event creation where topic constrains payload type.\n *\n * @param options.getNow - Function to get current time (default: `() => new Date()`)\n * @param options.generateId - Function to generate event IDs (default: `() => crypto.randomUUID()`)\n *\n * @example\n * ```typescript\n * type MyEvents =\n * | GenericEvent<\"UserCreated\", { email: string }>\n * | GenericEvent<\"OrderPlaced\", { orderId: string }>;\n *\n * // Standalone usage:\n * const createEvent = makeCreateNewEvent<MyEvents>();\n *\n * // Or get it from createInMemoryEventBus (recommended):\n * const { eventBus, createEvent } = createInMemoryEventBus<MyEvents>(withUow);\n *\n * // Type-safe: payload must match topic\n * createEvent({ topic: \"UserCreated\", payload: { email: \"a@b.com\" }, triggeredByUserId: \"u1\" }); // OK\n * createEvent({ topic: \"UserCreated\", payload: { orderId: \"123\" }, triggeredByUserId: \"u1\" }); // Error!\n *\n * // For testing, inject deterministic functions:\n * const createEvent = makeCreateNewEvent<MyEvents>({\n * getNow: () => new Date(\"2024-01-01\"),\n * generateId: () => \"test-id\",\n * });\n * ```\n */\n\nexport type CreateNewEvent<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = <Topic extends Event[\"topic\"]>(\n params: CreateNewEventParams<Event, Topic>,\n) => Extract<Event, { topic: Topic }>;\n\nexport const makeCreateNewEvent = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n options: MakeCreateNewEventOptions = {},\n): CreateNewEvent<Event> => {\n const getNow = options.getNow ?? (() => new Date());\n const generateId =\n options.generateId ?? (() => crypto.randomUUID() as EventId);\n\n return <Topic extends Event[\"topic\"]>(\n params: CreateNewEventParams<Event, Topic>,\n ): Extract<Event, { topic: Topic }> =>\n ({\n id: params.id ?? generateId(),\n topic: params.topic,\n payload: params.payload,\n triggeredByUserId: params.triggeredByUserId,\n occurredAt: params.occurredAt ?? getNow(),\n status: \"never-published\",\n publications: [],\n priority: params.priority,\n context: params.context,\n }) as unknown as Extract<Event, { topic: Topic }>;\n};\n"],"mappings":"AA+DO,MAAM,qBAAqB,CAGhC,UAAqC,CAAC,MACZ;AAC1B,QAAM,SAAS,QAAQ,WAAW,MAAM,oBAAI,KAAK;AACjD,QAAM,aACJ,QAAQ,eAAe,MAAM,OAAO,WAAW;AAEjD,SAAO,CACL,YAEC;AAAA,IACC,IAAI,OAAO,MAAM,WAAW;AAAA,IAC5B,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,IAChB,mBAAmB,OAAO;AAAA,IAC1B,YAAY,OAAO,cAAc,OAAO;AAAA,IACxC,QAAQ;AAAA,IACR,cAAc,CAAC;AAAA,IACf,UAAU,OAAO;AAAA,IACjB,SAAS,OAAO;AAAA,EAClB;AACJ;","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from "./adapters/in-memory/index.
|
|
2
|
-
export * from "./createEventCrawler.
|
|
3
|
-
export * from "./createNewEvent.
|
|
1
|
+
export * from "./adapters/in-memory/index.mjs";
|
|
2
|
+
export * from "./createEventCrawler.mjs";
|
|
3
|
+
export * from "./createNewEvent.mjs";
|
|
4
4
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './adapters/in-memory/index.
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './adapters/in-memory/index.mjs';\nexport * from './createEventCrawler.mjs';\nexport * from './createNewEvent.mjs';\nexport type * from './ports/EventBus.mjs';\nexport type * from './ports/EventQueries.mjs';\nexport type * from './ports/EventRepository.mjs';\nexport type * from './types.mjs';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ports/EventRepository.ts"],"sourcesContent":["import type { DefaultContext, GenericEvent } from \"../types.ts\";\n\n/**\n * Repository interface for persisting events.\n * Implement this to store events in your database (e.g., PostgreSQL, MongoDB).\n * Events should be saved in the same transaction as your domain changes.\n */\nexport type EventRepository<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n /** Persist a single event (typically after publication status update). */\n save: (event: Event) => Promise<void>;\n /** Persist multiple new events in a batch. */\n saveNewEventsBatch: (events: Event[]) => Promise<void>;\n /** Mark events as \"in-process\" before publishing (prevents duplicate processing). */\n markEventsAsInProcess: (events: Event[]) => Promise<void>;\n};\n\n/**\n * Unit of work containing the event repository.\n * Extend this with your own repositories for transactional consistency.\n */\nexport type EventsUnitOfWork<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n eventRepository: EventRepository<Event>;\n};\n\n/**\n * Options for unit of work execution.\n */\nexport type WithEventsUowOptions = {\n /**\n * Callback executed after successful transaction commit.\n * Useful for triggering event processing in serverless environments.\n *\n * The callback should return a Promise. Whether it's awaited depends on\n * the withUow implementation:\n * - Serverless (Lambda): await to ensure completion before runtime freezes\n * - Long-running servers: fire-and-forget for faster response times\n *\n * @example\n * ```typescript\n * await withUow(async (uow) => {\n * await uow.eventRepository.save(event);\n * }, {\n * afterCommit: async () => {\n * await eventCrawler.triggerProcessing();\n * }\n * });\n * ```\n */\n afterCommit?: () => Promise<void>;\n};\n\n/**\n * Higher-order function that provides a unit of work for transactional operations.\n * Your implementation should handle transaction begin/commit/rollback.\n *\n * @example\n * ```typescript\n * const withUow: WithEventsUow<MyEvent> = async (fn, options) => {\n * const tx = await db.beginTransaction();\n * try {\n * await fn({ eventRepository: createEventRepo(tx) });\n * await tx.commit();\n * options?.afterCommit?.();\n * } catch (e) {\n * await tx.rollback();\n * throw e;\n * }\n * };\n * ```\n */\nexport type WithEventsUow<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = (\n fn: (uow: EventsUnitOfWork<Event>) => Promise<
|
|
1
|
+
{"version":3,"sources":["../../src/ports/EventRepository.ts"],"sourcesContent":["import type { DefaultContext, GenericEvent } from \"../types.ts\";\n\n/**\n * Repository interface for persisting events.\n * Implement this to store events in your database (e.g., PostgreSQL, MongoDB).\n * Events should be saved in the same transaction as your domain changes.\n */\nexport type EventRepository<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n /** Persist a single event (typically after publication status update). */\n save: (event: Event) => Promise<void>;\n /** Persist multiple new events in a batch. */\n saveNewEventsBatch: (events: Event[]) => Promise<void>;\n /** Mark events as \"in-process\" before publishing (prevents duplicate processing). */\n markEventsAsInProcess: (events: Event[]) => Promise<void>;\n};\n\n/**\n * Unit of work containing the event repository.\n * Extend this with your own repositories for transactional consistency.\n */\nexport type EventsUnitOfWork<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n eventRepository: EventRepository<Event>;\n};\n\n/**\n * Options for unit of work execution.\n */\nexport type WithEventsUowOptions = {\n /**\n * Callback executed after successful transaction commit.\n * Useful for triggering event processing in serverless environments.\n *\n * The callback should return a Promise. Whether it's awaited depends on\n * the withUow implementation:\n * - Serverless (Lambda): await to ensure completion before runtime freezes\n * - Long-running servers: fire-and-forget for faster response times\n *\n * @example\n * ```typescript\n * await withUow(async (uow) => {\n * await uow.eventRepository.save(event);\n * }, {\n * afterCommit: async () => {\n * await eventCrawler.triggerProcessing();\n * }\n * });\n * ```\n */\n afterCommit?: () => Promise<void>;\n};\n\n/**\n * Higher-order function that provides a unit of work for transactional operations.\n * Your implementation should handle transaction begin/commit/rollback.\n *\n * @example\n * ```typescript\n * const withUow: WithEventsUow<MyEvent> = async (fn, options) => {\n * const tx = await db.beginTransaction();\n * try {\n * const result = await fn({ eventRepository: createEventRepo(tx) });\n * await tx.commit();\n * await options?.afterCommit?.();\n * return result;\n * } catch (e) {\n * await tx.rollback();\n * throw e;\n * }\n * };\n * ```\n */\nexport type WithEventsUow<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = <T>(\n fn: (uow: EventsUnitOfWork<Event>) => Promise<T>,\n options?: WithEventsUowOptions,\n) => Promise<T>;\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
@@ -55,9 +55,10 @@ type WithEventsUowOptions = {
|
|
|
55
55
|
* const withUow: WithEventsUow<MyEvent> = async (fn, options) => {
|
|
56
56
|
* const tx = await db.beginTransaction();
|
|
57
57
|
* try {
|
|
58
|
-
* await fn({ eventRepository: createEventRepo(tx) });
|
|
58
|
+
* const result = await fn({ eventRepository: createEventRepo(tx) });
|
|
59
59
|
* await tx.commit();
|
|
60
|
-
* options?.afterCommit?.();
|
|
60
|
+
* await options?.afterCommit?.();
|
|
61
|
+
* return result;
|
|
61
62
|
* } catch (e) {
|
|
62
63
|
* await tx.rollback();
|
|
63
64
|
* throw e;
|
|
@@ -65,6 +66,6 @@ type WithEventsUowOptions = {
|
|
|
65
66
|
* };
|
|
66
67
|
* ```
|
|
67
68
|
*/
|
|
68
|
-
type WithEventsUow<Event extends GenericEvent<string, unknown, DefaultContext>> = (fn: (uow: EventsUnitOfWork<Event>) => Promise<
|
|
69
|
+
type WithEventsUow<Event extends GenericEvent<string, unknown, DefaultContext>> = <T>(fn: (uow: EventsUnitOfWork<Event>) => Promise<T>, options?: WithEventsUowOptions) => Promise<T>;
|
|
69
70
|
|
|
70
71
|
export type { EventRepository, EventsUnitOfWork, WithEventsUow, WithEventsUowOptions };
|
|
@@ -55,9 +55,10 @@ type WithEventsUowOptions = {
|
|
|
55
55
|
* const withUow: WithEventsUow<MyEvent> = async (fn, options) => {
|
|
56
56
|
* const tx = await db.beginTransaction();
|
|
57
57
|
* try {
|
|
58
|
-
* await fn({ eventRepository: createEventRepo(tx) });
|
|
58
|
+
* const result = await fn({ eventRepository: createEventRepo(tx) });
|
|
59
59
|
* await tx.commit();
|
|
60
|
-
* options?.afterCommit?.();
|
|
60
|
+
* await options?.afterCommit?.();
|
|
61
|
+
* return result;
|
|
61
62
|
* } catch (e) {
|
|
62
63
|
* await tx.rollback();
|
|
63
64
|
* throw e;
|
|
@@ -65,6 +66,6 @@ type WithEventsUowOptions = {
|
|
|
65
66
|
* };
|
|
66
67
|
* ```
|
|
67
68
|
*/
|
|
68
|
-
type WithEventsUow<Event extends GenericEvent<string, unknown, DefaultContext>> = (fn: (uow: EventsUnitOfWork<Event>) => Promise<
|
|
69
|
+
type WithEventsUow<Event extends GenericEvent<string, unknown, DefaultContext>> = <T>(fn: (uow: EventsUnitOfWork<Event>) => Promise<T>, options?: WithEventsUowOptions) => Promise<T>;
|
|
69
70
|
|
|
70
71
|
export type { EventRepository, EventsUnitOfWork, WithEventsUow, WithEventsUowOptions };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "The purpose of this repository is to make it easy to setup event driven architecture using outbox pattern",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.4.1",
|
|
7
7
|
"main": "./dist/index.mjs",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
@@ -56,8 +56,9 @@ export const createInMemoryWithUow = <
|
|
|
56
56
|
): { withUow: WithEventsUow<Event> } => {
|
|
57
57
|
// In-memory adapter awaits afterCommit for predictable test behavior
|
|
58
58
|
const withUow: WithEventsUow<Event> = async (fn, options) => {
|
|
59
|
-
await fn({ eventRepository });
|
|
59
|
+
const result = await fn({ eventRepository });
|
|
60
60
|
await options?.afterCommit?.();
|
|
61
|
+
return result;
|
|
61
62
|
};
|
|
62
63
|
return { withUow };
|
|
63
64
|
};
|
|
@@ -62,9 +62,10 @@ export type WithEventsUowOptions = {
|
|
|
62
62
|
* const withUow: WithEventsUow<MyEvent> = async (fn, options) => {
|
|
63
63
|
* const tx = await db.beginTransaction();
|
|
64
64
|
* try {
|
|
65
|
-
* await fn({ eventRepository: createEventRepo(tx) });
|
|
65
|
+
* const result = await fn({ eventRepository: createEventRepo(tx) });
|
|
66
66
|
* await tx.commit();
|
|
67
|
-
* options?.afterCommit?.();
|
|
67
|
+
* await options?.afterCommit?.();
|
|
68
|
+
* return result;
|
|
68
69
|
* } catch (e) {
|
|
69
70
|
* await tx.rollback();
|
|
70
71
|
* throw e;
|
|
@@ -74,7 +75,7 @@ export type WithEventsUowOptions = {
|
|
|
74
75
|
*/
|
|
75
76
|
export type WithEventsUow<
|
|
76
77
|
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
77
|
-
> = (
|
|
78
|
-
fn: (uow: EventsUnitOfWork<Event>) => Promise<
|
|
78
|
+
> = <T>(
|
|
79
|
+
fn: (uow: EventsUnitOfWork<Event>) => Promise<T>,
|
|
79
80
|
options?: WithEventsUowOptions,
|
|
80
|
-
) => Promise<
|
|
81
|
+
) => Promise<T>;
|