@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 CHANGED
@@ -1,16 +1,267 @@
1
- # l-etabli/events
1
+ # @l-etabli/events
2
2
 
3
- ${project_description}
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
- This project is a template for creating typscript libraries.
7
+ ## Installation
7
8
 
8
- It uses :
9
+ ```bash
10
+ pnpm add @l-etabli/events
11
+ ```
9
12
 
10
- - Typescript for type checking
11
- - Bun for running tests
12
- - Bun as a package manager
13
- - Biome as a formatter
14
- - Biome as a linter
15
- - Lefthook for pre-commit hooks
16
- - GitHub Actions for CI, which will run typecheck, format, lint, test and than deploy the package to npm
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.ts.mjs";
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.ts.mjs';\nimport type { EventBus } from '../../ports/EventBus.ts.mjs';\nimport type { WithEventsUow } from '../../ports/EventRepository.ts.mjs';\nimport type {\n DefaultContext,\n EventId,\n EventPublication,\n GenericEvent,\n SubscriptionId,\n} from '../../types.ts.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
+ {"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.ts.mjs';\nimport type { DefaultContext, GenericEvent } from '../../types.ts.mjs';\nimport type { InMemoryEventRepositoryHelpers } from './InMemoryEventRepository.ts.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":[]}
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;AAC5B,UAAM,SAAS,cAAc;AAAA,EAC/B;AACA,SAAO,EAAE,QAAQ;AACnB;","names":[]}
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.ts.mjs';\nimport type { DefaultContext, GenericEvent } from '../../types.ts.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 await fn({ eventRepository });\n await options?.afterCommit?.();\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,GAAG,EAAE,gBAAgB,CAAC;AAC5B,UAAM,SAAS,cAAc;AAAA,EAC/B;AACA,SAAO,EAAE,QAAQ;AACnB;","names":[]}
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.ts.mjs";
1
+ import { createInMemoryEventQueries } from "./InMemoryEventQueries.mjs";
2
2
  import {
3
3
  createInMemoryEventRepository,
4
4
  createInMemoryWithUow
5
- } from "./InMemoryEventRepository.ts.mjs";
6
- export * from "./InMemoryEventBus.ts.mjs";
7
- export * from "./InMemoryEventQueries.ts.mjs";
8
- export * from "./InMemoryEventRepository.ts.mjs";
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.ts.mjs';\nimport { createInMemoryEventQueries } from './InMemoryEventQueries.ts.mjs';\nimport {\n createInMemoryEventRepository,\n createInMemoryWithUow,\n} from './InMemoryEventRepository.ts.mjs';\n\nexport * from './InMemoryEventBus.ts.mjs';\nexport * from './InMemoryEventQueries.ts.mjs';\nexport * from './InMemoryEventRepository.ts.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
+ {"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.ts.mjs';\nimport type { DefaultContext, GenericEvent } from '../../types.ts.mjs';\nimport type { EventsTable } from './types.ts.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
+ {"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.ts.mjs';\nimport type { DefaultContext, GenericEvent } from '../../types.ts.mjs';\nimport type { EventsTable } from './types.ts.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
+ {"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.ts.mjs";
2
- import { createKyselyEventRepository } from "./KyselyEventRepository.ts.mjs";
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.ts.mjs';\nexport { createKyselyEventRepository } from './KyselyEventRepository.ts.mjs';\nexport type { EventsTable, TypedEventsTable } from './types.ts.mjs';\n"],"mappings":"AAAA,SAAS,gCAAgC;AACzC,SAAS,mCAAmC;","names":[]}
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.ts.mjs';\nimport type { EventQueries } from './ports/EventQueries.ts.mjs';\nimport type { WithEventsUow } from './ports/EventRepository.ts.mjs';\nimport type { DefaultContext, GenericEvent } from './types.ts.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
+ {"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.ts.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":[]}
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.ts.mjs";
2
- export * from "./createEventCrawler.ts.mjs";
3
- export * from "./createNewEvent.ts.mjs";
1
+ export * from "./adapters/in-memory/index.mjs";
2
+ export * from "./createEventCrawler.mjs";
3
+ export * from "./createNewEvent.mjs";
4
4
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './adapters/in-memory/index.ts.mjs';\nexport * from './createEventCrawler.ts.mjs';\nexport * from './createNewEvent.ts.mjs';\nexport type * from './ports/EventBus.ts.mjs';\nexport type * from './ports/EventQueries.ts.mjs';\nexport type * from './ports/EventRepository.ts.mjs';\nexport type * from './types.ts.mjs';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
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<void>,\n options?: WithEventsUowOptions,\n) => Promise<void>;\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
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<void>, options?: WithEventsUowOptions) => Promise<void>;
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<void>, options?: WithEventsUowOptions) => Promise<void>;
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.3.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<void>,
78
+ > = <T>(
79
+ fn: (uow: EventsUnitOfWork<Event>) => Promise<T>,
79
80
  options?: WithEventsUowOptions,
80
- ) => Promise<void>;
81
+ ) => Promise<T>;