@l-etabli/events 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/dist/adapters/in-memory/InMemoryEventBus.cjs +128 -0
- package/dist/adapters/in-memory/InMemoryEventBus.cjs.map +1 -0
- package/dist/adapters/in-memory/InMemoryEventBus.d.cts +16 -0
- package/dist/adapters/in-memory/InMemoryEventBus.d.ts +16 -0
- package/dist/adapters/in-memory/InMemoryEventBus.mjs +104 -0
- package/dist/adapters/in-memory/InMemoryEventBus.mjs.map +1 -0
- package/dist/adapters/in-memory/InMemoryEventQueries.cjs +44 -0
- package/dist/adapters/in-memory/InMemoryEventQueries.cjs.map +1 -0
- package/dist/adapters/in-memory/InMemoryEventQueries.d.cts +10 -0
- package/dist/adapters/in-memory/InMemoryEventQueries.d.ts +10 -0
- package/dist/adapters/in-memory/InMemoryEventQueries.mjs +20 -0
- package/dist/adapters/in-memory/InMemoryEventQueries.mjs.map +1 -0
- package/dist/adapters/in-memory/InMemoryEventRepository.cjs +68 -0
- package/dist/adapters/in-memory/InMemoryEventRepository.cjs.map +1 -0
- package/dist/adapters/in-memory/InMemoryEventRepository.d.cts +16 -0
- package/dist/adapters/in-memory/InMemoryEventRepository.d.ts +16 -0
- package/dist/adapters/in-memory/InMemoryEventRepository.mjs +43 -0
- package/dist/adapters/in-memory/InMemoryEventRepository.mjs.map +1 -0
- package/dist/adapters/in-memory/index.cjs +43 -0
- package/dist/adapters/in-memory/index.cjs.map +1 -0
- package/dist/adapters/in-memory/index.d.cts +18 -0
- package/dist/adapters/in-memory/index.d.ts +18 -0
- package/dist/adapters/in-memory/index.mjs +18 -0
- package/dist/adapters/in-memory/index.mjs.map +1 -0
- package/dist/adapters/kysely/KyselyEventQueries.cjs +47 -0
- package/dist/adapters/kysely/KyselyEventQueries.cjs.map +1 -0
- package/dist/adapters/kysely/KyselyEventQueries.d.cts +8 -0
- package/dist/adapters/kysely/KyselyEventQueries.d.ts +8 -0
- package/dist/adapters/kysely/KyselyEventQueries.mjs +23 -0
- package/dist/adapters/kysely/KyselyEventQueries.mjs.map +1 -0
- package/dist/adapters/kysely/KyselyEventRepository.cjs +53 -0
- package/dist/adapters/kysely/KyselyEventRepository.cjs.map +1 -0
- package/dist/adapters/kysely/KyselyEventRepository.d.cts +8 -0
- package/dist/adapters/kysely/KyselyEventRepository.d.ts +8 -0
- package/dist/adapters/kysely/KyselyEventRepository.mjs +29 -0
- package/dist/adapters/kysely/KyselyEventRepository.mjs.map +1 -0
- package/dist/adapters/kysely/index.cjs +32 -0
- package/dist/adapters/kysely/index.cjs.map +1 -0
- package/dist/adapters/kysely/index.d.cts +7 -0
- package/dist/adapters/kysely/index.d.ts +7 -0
- package/dist/adapters/kysely/index.mjs +7 -0
- package/dist/adapters/kysely/index.mjs.map +1 -0
- package/dist/adapters/kysely/migration.cjs +38 -0
- package/dist/adapters/kysely/migration.cjs.map +1 -0
- package/dist/adapters/kysely/migration.d.cts +6 -0
- package/dist/adapters/kysely/migration.d.ts +6 -0
- package/dist/adapters/kysely/migration.mjs +13 -0
- package/dist/adapters/kysely/migration.mjs.map +1 -0
- package/dist/adapters/kysely/types.cjs +17 -0
- package/dist/adapters/kysely/types.cjs.map +1 -0
- package/dist/adapters/kysely/types.d.cts +34 -0
- package/dist/adapters/kysely/types.d.ts +34 -0
- package/dist/adapters/kysely/types.mjs +1 -0
- package/dist/adapters/kysely/types.mjs.map +1 -0
- package/dist/createEventCrawler.cjs +102 -0
- package/dist/createEventCrawler.cjs.map +1 -0
- package/dist/createEventCrawler.d.cts +56 -0
- package/dist/createEventCrawler.d.ts +56 -0
- package/dist/createEventCrawler.mjs +78 -0
- package/dist/createEventCrawler.mjs.map +1 -0
- package/dist/createNewEvent.cjs +43 -0
- package/dist/createNewEvent.cjs.map +1 -0
- package/dist/createNewEvent.d.cts +61 -0
- package/dist/createNewEvent.d.ts +61 -0
- package/dist/createNewEvent.mjs +19 -0
- package/dist/createNewEvent.mjs.map +1 -0
- package/dist/index.cjs +27 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ports/EventBus.cjs +17 -0
- package/dist/ports/EventBus.cjs.map +1 -0
- package/dist/ports/EventBus.d.cts +35 -0
- package/dist/ports/EventBus.d.ts +35 -0
- package/dist/ports/EventBus.mjs +1 -0
- package/dist/ports/EventBus.mjs.map +1 -0
- package/dist/ports/EventQueries.cjs +17 -0
- package/dist/ports/EventQueries.cjs.map +1 -0
- package/dist/ports/EventQueries.d.cts +24 -0
- package/dist/ports/EventQueries.d.ts +24 -0
- package/dist/ports/EventQueries.mjs +1 -0
- package/dist/ports/EventQueries.mjs.map +1 -0
- package/dist/ports/EventRepository.cjs +17 -0
- package/dist/ports/EventRepository.cjs.map +1 -0
- package/dist/ports/EventRepository.d.cts +43 -0
- package/dist/ports/EventRepository.d.ts +43 -0
- package/dist/ports/EventRepository.mjs +1 -0
- package/dist/ports/EventRepository.mjs.map +1 -0
- package/dist/types.cjs +17 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +84 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.mjs +1 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +68 -0
- package/src/adapters/in-memory/InMemoryEventBus.ts +165 -0
- package/src/adapters/in-memory/InMemoryEventQueries.ts +30 -0
- package/src/adapters/in-memory/InMemoryEventRepository.ts +61 -0
- package/src/adapters/in-memory/index.ts +19 -0
- package/src/adapters/kysely/KyselyEventQueries.ts +35 -0
- package/src/adapters/kysely/KyselyEventRepository.ts +44 -0
- package/src/adapters/kysely/index.ts +3 -0
- package/src/adapters/kysely/migration.ts +39 -0
- package/src/adapters/kysely/types.ts +37 -0
- package/src/createEventCrawler.ts +139 -0
- package/src/createNewEvent.ts +87 -0
- package/src/index.ts +7 -0
- package/src/ports/EventBus.ts +37 -0
- package/src/ports/EventQueries.ts +25 -0
- package/src/ports/EventRepository.ts +49 -0
- package/src/types.ts +100 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var createEventCrawler_exports = {};
|
|
20
|
+
__export(createEventCrawler_exports, {
|
|
21
|
+
createEventCrawler: () => createEventCrawler
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(createEventCrawler_exports);
|
|
24
|
+
const splitIntoChunks = (array, chunkSize) => {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
27
|
+
chunks.push(array.slice(i, i + chunkSize));
|
|
28
|
+
}
|
|
29
|
+
return chunks;
|
|
30
|
+
};
|
|
31
|
+
const createEventCrawler = ({
|
|
32
|
+
withUow,
|
|
33
|
+
eventQueries,
|
|
34
|
+
eventBus,
|
|
35
|
+
options = {}
|
|
36
|
+
}) => {
|
|
37
|
+
const batchSize = options.batchSize ?? 100;
|
|
38
|
+
const maxParallelProcessing = options.maxParallelProcessing ?? 1;
|
|
39
|
+
const newEventsIntervalMs = options.newEventsIntervalMs ?? 1e4;
|
|
40
|
+
const failedEventsIntervalMs = options.failedEventsIntervalMs ?? 6e4;
|
|
41
|
+
const publishEventsInParallel = async (events) => {
|
|
42
|
+
const eventChunks = splitIntoChunks(events, maxParallelProcessing);
|
|
43
|
+
for (const chunk of eventChunks) {
|
|
44
|
+
await Promise.all(chunk.map((event) => eventBus.publish(event)));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const processNewEvents = async () => {
|
|
48
|
+
const events = await eventQueries.getEvents({
|
|
49
|
+
filters: { statuses: ["never-published"] },
|
|
50
|
+
limit: batchSize
|
|
51
|
+
});
|
|
52
|
+
if (events.length === 0) return;
|
|
53
|
+
await withUow(async (uow) => {
|
|
54
|
+
await uow.eventRepository.markEventsAsInProcess(events);
|
|
55
|
+
});
|
|
56
|
+
await publishEventsInParallel(events);
|
|
57
|
+
};
|
|
58
|
+
const retryFailedEvents = async () => {
|
|
59
|
+
const events = await eventQueries.getEvents({
|
|
60
|
+
filters: { statuses: ["to-republish", "failed-but-will-retry"] },
|
|
61
|
+
limit: batchSize
|
|
62
|
+
});
|
|
63
|
+
if (events.length === 0) return;
|
|
64
|
+
await publishEventsInParallel(events);
|
|
65
|
+
};
|
|
66
|
+
const start = () => {
|
|
67
|
+
const scheduleProcessNewEvents = () => {
|
|
68
|
+
setTimeout(async () => {
|
|
69
|
+
try {
|
|
70
|
+
await processNewEvents();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("Error processing new events:", error);
|
|
73
|
+
} finally {
|
|
74
|
+
scheduleProcessNewEvents();
|
|
75
|
+
}
|
|
76
|
+
}, newEventsIntervalMs);
|
|
77
|
+
};
|
|
78
|
+
const scheduleRetryFailedEvents = () => {
|
|
79
|
+
setTimeout(async () => {
|
|
80
|
+
try {
|
|
81
|
+
await retryFailedEvents();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("Error retrying failed events:", error);
|
|
84
|
+
} finally {
|
|
85
|
+
scheduleRetryFailedEvents();
|
|
86
|
+
}
|
|
87
|
+
}, failedEventsIntervalMs);
|
|
88
|
+
};
|
|
89
|
+
scheduleProcessNewEvents();
|
|
90
|
+
scheduleRetryFailedEvents();
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
processNewEvents,
|
|
94
|
+
retryFailedEvents,
|
|
95
|
+
start
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
99
|
+
0 && (module.exports = {
|
|
100
|
+
createEventCrawler
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=createEventCrawler.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/createEventCrawler.ts"],"sourcesContent":["import type { EventBus } from \"./ports/EventBus.ts\";\nimport type { EventQueries } from \"./ports/EventQueries.ts\";\nimport type { WithEventsUow } from \"./ports/EventRepository.ts\";\nimport type { DefaultContext, GenericEvent } from \"./types.ts\";\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\n * - `processNewEvents()`: Manually trigger new event processing\n * - `retryFailedEvents()`: Manually trigger failed event retry\n *\n * @example\n * ```typescript\n * const crawler = createEventCrawler({\n * withUow,\n * eventQueries,\n * eventBus,\n * options: { batchSize: 50, newEventsIntervalMs: 5000 },\n * });\n *\n * // Start background processing\n * crawler.start();\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 events = await eventQueries.getEvents({\n filters: { statuses: [\"to-republish\", \"failed-but-will-retry\"] },\n limit: batchSize,\n });\n\n if (events.length === 0) return;\n\n await publishEventsInParallel(events);\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 start,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;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;AA8BO,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,SAAS,MAAM,aAAa,UAAU;AAAA,MAC1C,SAAS,EAAE,UAAU,CAAC,gBAAgB,uBAAuB,EAAE;AAAA,MAC/D,OAAO;AAAA,IACT,CAAC;AAED,QAAI,OAAO,WAAW,EAAG;AAEzB,UAAM,wBAAwB,MAAM;AAAA,EACtC;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,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EventBus } from './ports/EventBus.cjs';
|
|
2
|
+
import { EventQueries } from './ports/EventQueries.cjs';
|
|
3
|
+
import { WithEventsUow } from './ports/EventRepository.cjs';
|
|
4
|
+
import { GenericEvent, DefaultContext } from './types.cjs';
|
|
5
|
+
|
|
6
|
+
/** Configuration options for the event crawler. */
|
|
7
|
+
type CreateEventCrawlerOptions = {
|
|
8
|
+
/** Max events to fetch per batch (default: 100). */
|
|
9
|
+
batchSize?: number;
|
|
10
|
+
/** Max events to publish in parallel (default: 1). */
|
|
11
|
+
maxParallelProcessing?: number;
|
|
12
|
+
/** Interval for processing new events in ms (default: 10000). */
|
|
13
|
+
newEventsIntervalMs?: number;
|
|
14
|
+
/** Interval for retrying failed events in ms (default: 60000). */
|
|
15
|
+
failedEventsIntervalMs?: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Creates a background event crawler that processes and publishes events.
|
|
19
|
+
*
|
|
20
|
+
* The crawler runs two loops:
|
|
21
|
+
* 1. Process new events: polls for "never-published" events and publishes them
|
|
22
|
+
* 2. Retry failed events: polls for failed events and retries them
|
|
23
|
+
*
|
|
24
|
+
* @returns Object with:
|
|
25
|
+
* - `start()`: Start the background polling loops
|
|
26
|
+
* - `processNewEvents()`: Manually trigger new event processing
|
|
27
|
+
* - `retryFailedEvents()`: Manually trigger failed event retry
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const crawler = createEventCrawler({
|
|
32
|
+
* withUow,
|
|
33
|
+
* eventQueries,
|
|
34
|
+
* eventBus,
|
|
35
|
+
* options: { batchSize: 50, newEventsIntervalMs: 5000 },
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* // Start background processing
|
|
39
|
+
* crawler.start();
|
|
40
|
+
*
|
|
41
|
+
* // Or trigger manually (useful for testing)
|
|
42
|
+
* await crawler.processNewEvents();
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare const createEventCrawler: <Event extends GenericEvent<string, unknown, DefaultContext>>({ withUow, eventQueries, eventBus, options, }: {
|
|
46
|
+
withUow: WithEventsUow<Event>;
|
|
47
|
+
eventQueries: EventQueries<Event>;
|
|
48
|
+
eventBus: EventBus<Event>;
|
|
49
|
+
options?: CreateEventCrawlerOptions;
|
|
50
|
+
}) => {
|
|
51
|
+
processNewEvents: () => Promise<void>;
|
|
52
|
+
retryFailedEvents: () => Promise<void>;
|
|
53
|
+
start: () => void;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { createEventCrawler };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EventBus } from './ports/EventBus.js';
|
|
2
|
+
import { EventQueries } from './ports/EventQueries.js';
|
|
3
|
+
import { WithEventsUow } from './ports/EventRepository.js';
|
|
4
|
+
import { GenericEvent, DefaultContext } from './types.js';
|
|
5
|
+
|
|
6
|
+
/** Configuration options for the event crawler. */
|
|
7
|
+
type CreateEventCrawlerOptions = {
|
|
8
|
+
/** Max events to fetch per batch (default: 100). */
|
|
9
|
+
batchSize?: number;
|
|
10
|
+
/** Max events to publish in parallel (default: 1). */
|
|
11
|
+
maxParallelProcessing?: number;
|
|
12
|
+
/** Interval for processing new events in ms (default: 10000). */
|
|
13
|
+
newEventsIntervalMs?: number;
|
|
14
|
+
/** Interval for retrying failed events in ms (default: 60000). */
|
|
15
|
+
failedEventsIntervalMs?: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Creates a background event crawler that processes and publishes events.
|
|
19
|
+
*
|
|
20
|
+
* The crawler runs two loops:
|
|
21
|
+
* 1. Process new events: polls for "never-published" events and publishes them
|
|
22
|
+
* 2. Retry failed events: polls for failed events and retries them
|
|
23
|
+
*
|
|
24
|
+
* @returns Object with:
|
|
25
|
+
* - `start()`: Start the background polling loops
|
|
26
|
+
* - `processNewEvents()`: Manually trigger new event processing
|
|
27
|
+
* - `retryFailedEvents()`: Manually trigger failed event retry
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const crawler = createEventCrawler({
|
|
32
|
+
* withUow,
|
|
33
|
+
* eventQueries,
|
|
34
|
+
* eventBus,
|
|
35
|
+
* options: { batchSize: 50, newEventsIntervalMs: 5000 },
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* // Start background processing
|
|
39
|
+
* crawler.start();
|
|
40
|
+
*
|
|
41
|
+
* // Or trigger manually (useful for testing)
|
|
42
|
+
* await crawler.processNewEvents();
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare const createEventCrawler: <Event extends GenericEvent<string, unknown, DefaultContext>>({ withUow, eventQueries, eventBus, options, }: {
|
|
46
|
+
withUow: WithEventsUow<Event>;
|
|
47
|
+
eventQueries: EventQueries<Event>;
|
|
48
|
+
eventBus: EventBus<Event>;
|
|
49
|
+
options?: CreateEventCrawlerOptions;
|
|
50
|
+
}) => {
|
|
51
|
+
processNewEvents: () => Promise<void>;
|
|
52
|
+
retryFailedEvents: () => Promise<void>;
|
|
53
|
+
start: () => void;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { createEventCrawler };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const splitIntoChunks = (array, chunkSize) => {
|
|
2
|
+
const chunks = [];
|
|
3
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
4
|
+
chunks.push(array.slice(i, i + chunkSize));
|
|
5
|
+
}
|
|
6
|
+
return chunks;
|
|
7
|
+
};
|
|
8
|
+
const createEventCrawler = ({
|
|
9
|
+
withUow,
|
|
10
|
+
eventQueries,
|
|
11
|
+
eventBus,
|
|
12
|
+
options = {}
|
|
13
|
+
}) => {
|
|
14
|
+
const batchSize = options.batchSize ?? 100;
|
|
15
|
+
const maxParallelProcessing = options.maxParallelProcessing ?? 1;
|
|
16
|
+
const newEventsIntervalMs = options.newEventsIntervalMs ?? 1e4;
|
|
17
|
+
const failedEventsIntervalMs = options.failedEventsIntervalMs ?? 6e4;
|
|
18
|
+
const publishEventsInParallel = async (events) => {
|
|
19
|
+
const eventChunks = splitIntoChunks(events, maxParallelProcessing);
|
|
20
|
+
for (const chunk of eventChunks) {
|
|
21
|
+
await Promise.all(chunk.map((event) => eventBus.publish(event)));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const processNewEvents = async () => {
|
|
25
|
+
const events = await eventQueries.getEvents({
|
|
26
|
+
filters: { statuses: ["never-published"] },
|
|
27
|
+
limit: batchSize
|
|
28
|
+
});
|
|
29
|
+
if (events.length === 0) return;
|
|
30
|
+
await withUow(async (uow) => {
|
|
31
|
+
await uow.eventRepository.markEventsAsInProcess(events);
|
|
32
|
+
});
|
|
33
|
+
await publishEventsInParallel(events);
|
|
34
|
+
};
|
|
35
|
+
const retryFailedEvents = async () => {
|
|
36
|
+
const events = await eventQueries.getEvents({
|
|
37
|
+
filters: { statuses: ["to-republish", "failed-but-will-retry"] },
|
|
38
|
+
limit: batchSize
|
|
39
|
+
});
|
|
40
|
+
if (events.length === 0) return;
|
|
41
|
+
await publishEventsInParallel(events);
|
|
42
|
+
};
|
|
43
|
+
const start = () => {
|
|
44
|
+
const scheduleProcessNewEvents = () => {
|
|
45
|
+
setTimeout(async () => {
|
|
46
|
+
try {
|
|
47
|
+
await processNewEvents();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Error processing new events:", error);
|
|
50
|
+
} finally {
|
|
51
|
+
scheduleProcessNewEvents();
|
|
52
|
+
}
|
|
53
|
+
}, newEventsIntervalMs);
|
|
54
|
+
};
|
|
55
|
+
const scheduleRetryFailedEvents = () => {
|
|
56
|
+
setTimeout(async () => {
|
|
57
|
+
try {
|
|
58
|
+
await retryFailedEvents();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error("Error retrying failed events:", error);
|
|
61
|
+
} finally {
|
|
62
|
+
scheduleRetryFailedEvents();
|
|
63
|
+
}
|
|
64
|
+
}, failedEventsIntervalMs);
|
|
65
|
+
};
|
|
66
|
+
scheduleProcessNewEvents();
|
|
67
|
+
scheduleRetryFailedEvents();
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
processNewEvents,
|
|
71
|
+
retryFailedEvents,
|
|
72
|
+
start
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
export {
|
|
76
|
+
createEventCrawler
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=createEventCrawler.mjs.map
|
|
@@ -0,0 +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\n * - `processNewEvents()`: Manually trigger new event processing\n * - `retryFailedEvents()`: Manually trigger failed event retry\n *\n * @example\n * ```typescript\n * const crawler = createEventCrawler({\n * withUow,\n * eventQueries,\n * eventBus,\n * options: { batchSize: 50, newEventsIntervalMs: 5000 },\n * });\n *\n * // Start background processing\n * crawler.start();\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 events = await eventQueries.getEvents({\n filters: { statuses: [\"to-republish\", \"failed-but-will-retry\"] },\n limit: batchSize,\n });\n\n if (events.length === 0) return;\n\n await publishEventsInParallel(events);\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 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;AA8BO,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,SAAS,MAAM,aAAa,UAAU;AAAA,MAC1C,SAAS,EAAE,UAAU,CAAC,gBAAgB,uBAAuB,EAAE;AAAA,MAC/D,OAAO;AAAA,IACT,CAAC;AAED,QAAI,OAAO,WAAW,EAAG;AAEzB,UAAM,wBAAwB,MAAM;AAAA,EACtC;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,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var createNewEvent_exports = {};
|
|
20
|
+
__export(createNewEvent_exports, {
|
|
21
|
+
makeCreateNewEvent: () => makeCreateNewEvent
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(createNewEvent_exports);
|
|
24
|
+
const makeCreateNewEvent = (options = {}) => {
|
|
25
|
+
const getNow = options.getNow ?? (() => /* @__PURE__ */ new Date());
|
|
26
|
+
const generateId = options.generateId ?? (() => crypto.randomUUID());
|
|
27
|
+
return (params) => ({
|
|
28
|
+
id: params.id ?? generateId(),
|
|
29
|
+
topic: params.topic,
|
|
30
|
+
payload: params.payload,
|
|
31
|
+
triggeredByUserId: params.triggeredByUserId,
|
|
32
|
+
occurredAt: params.occurredAt ?? getNow(),
|
|
33
|
+
status: "never-published",
|
|
34
|
+
publications: [],
|
|
35
|
+
priority: params.priority,
|
|
36
|
+
context: params.context
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
40
|
+
0 && (module.exports = {
|
|
41
|
+
makeCreateNewEvent
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=createNewEvent.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/createNewEvent.ts"],"sourcesContent":["import type { DefaultContext, EventId, GenericEvent, UserId } from \"./types.ts\";\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":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;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":[]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { GenericEvent, DefaultContext, UserId, EventId } from './types.cjs';
|
|
2
|
+
|
|
3
|
+
type MakeCreateNewEventOptions = {
|
|
4
|
+
getNow?: () => Date;
|
|
5
|
+
generateId?: () => EventId;
|
|
6
|
+
};
|
|
7
|
+
type ContextParam<Event extends GenericEvent<string, unknown, DefaultContext>, Topic extends Event["topic"]> = Extract<Event, {
|
|
8
|
+
topic: Topic;
|
|
9
|
+
}>["context"] extends undefined ? {
|
|
10
|
+
context?: undefined;
|
|
11
|
+
} : {
|
|
12
|
+
context: Extract<Event, {
|
|
13
|
+
topic: Topic;
|
|
14
|
+
}>["context"];
|
|
15
|
+
};
|
|
16
|
+
type CreateNewEventParams<Event extends GenericEvent<string, unknown, DefaultContext>, Topic extends Event["topic"]> = {
|
|
17
|
+
topic: Topic;
|
|
18
|
+
payload: Extract<Event, {
|
|
19
|
+
topic: Topic;
|
|
20
|
+
}>["payload"];
|
|
21
|
+
triggeredByUserId: UserId;
|
|
22
|
+
id?: EventId;
|
|
23
|
+
occurredAt?: Date;
|
|
24
|
+
priority?: number;
|
|
25
|
+
} & ContextParam<Event, Topic>;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a typed event creator factory for your event union.
|
|
28
|
+
* Provides type-safe event creation where topic constrains payload type.
|
|
29
|
+
*
|
|
30
|
+
* @param options.getNow - Function to get current time (default: `() => new Date()`)
|
|
31
|
+
* @param options.generateId - Function to generate event IDs (default: `() => crypto.randomUUID()`)
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* type MyEvents =
|
|
36
|
+
* | GenericEvent<"UserCreated", { email: string }>
|
|
37
|
+
* | GenericEvent<"OrderPlaced", { orderId: string }>;
|
|
38
|
+
*
|
|
39
|
+
* // Standalone usage:
|
|
40
|
+
* const createEvent = makeCreateNewEvent<MyEvents>();
|
|
41
|
+
*
|
|
42
|
+
* // Or get it from createInMemoryEventBus (recommended):
|
|
43
|
+
* const { eventBus, createEvent } = createInMemoryEventBus<MyEvents>(withUow);
|
|
44
|
+
*
|
|
45
|
+
* // Type-safe: payload must match topic
|
|
46
|
+
* createEvent({ topic: "UserCreated", payload: { email: "a@b.com" }, triggeredByUserId: "u1" }); // OK
|
|
47
|
+
* createEvent({ topic: "UserCreated", payload: { orderId: "123" }, triggeredByUserId: "u1" }); // Error!
|
|
48
|
+
*
|
|
49
|
+
* // For testing, inject deterministic functions:
|
|
50
|
+
* const createEvent = makeCreateNewEvent<MyEvents>({
|
|
51
|
+
* getNow: () => new Date("2024-01-01"),
|
|
52
|
+
* generateId: () => "test-id",
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
type CreateNewEvent<Event extends GenericEvent<string, unknown, DefaultContext>> = <Topic extends Event["topic"]>(params: CreateNewEventParams<Event, Topic>) => Extract<Event, {
|
|
57
|
+
topic: Topic;
|
|
58
|
+
}>;
|
|
59
|
+
declare const makeCreateNewEvent: <Event extends GenericEvent<string, unknown, DefaultContext>>(options?: MakeCreateNewEventOptions) => CreateNewEvent<Event>;
|
|
60
|
+
|
|
61
|
+
export { type CreateNewEvent, makeCreateNewEvent };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { GenericEvent, DefaultContext, UserId, EventId } from './types.js';
|
|
2
|
+
|
|
3
|
+
type MakeCreateNewEventOptions = {
|
|
4
|
+
getNow?: () => Date;
|
|
5
|
+
generateId?: () => EventId;
|
|
6
|
+
};
|
|
7
|
+
type ContextParam<Event extends GenericEvent<string, unknown, DefaultContext>, Topic extends Event["topic"]> = Extract<Event, {
|
|
8
|
+
topic: Topic;
|
|
9
|
+
}>["context"] extends undefined ? {
|
|
10
|
+
context?: undefined;
|
|
11
|
+
} : {
|
|
12
|
+
context: Extract<Event, {
|
|
13
|
+
topic: Topic;
|
|
14
|
+
}>["context"];
|
|
15
|
+
};
|
|
16
|
+
type CreateNewEventParams<Event extends GenericEvent<string, unknown, DefaultContext>, Topic extends Event["topic"]> = {
|
|
17
|
+
topic: Topic;
|
|
18
|
+
payload: Extract<Event, {
|
|
19
|
+
topic: Topic;
|
|
20
|
+
}>["payload"];
|
|
21
|
+
triggeredByUserId: UserId;
|
|
22
|
+
id?: EventId;
|
|
23
|
+
occurredAt?: Date;
|
|
24
|
+
priority?: number;
|
|
25
|
+
} & ContextParam<Event, Topic>;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a typed event creator factory for your event union.
|
|
28
|
+
* Provides type-safe event creation where topic constrains payload type.
|
|
29
|
+
*
|
|
30
|
+
* @param options.getNow - Function to get current time (default: `() => new Date()`)
|
|
31
|
+
* @param options.generateId - Function to generate event IDs (default: `() => crypto.randomUUID()`)
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* type MyEvents =
|
|
36
|
+
* | GenericEvent<"UserCreated", { email: string }>
|
|
37
|
+
* | GenericEvent<"OrderPlaced", { orderId: string }>;
|
|
38
|
+
*
|
|
39
|
+
* // Standalone usage:
|
|
40
|
+
* const createEvent = makeCreateNewEvent<MyEvents>();
|
|
41
|
+
*
|
|
42
|
+
* // Or get it from createInMemoryEventBus (recommended):
|
|
43
|
+
* const { eventBus, createEvent } = createInMemoryEventBus<MyEvents>(withUow);
|
|
44
|
+
*
|
|
45
|
+
* // Type-safe: payload must match topic
|
|
46
|
+
* createEvent({ topic: "UserCreated", payload: { email: "a@b.com" }, triggeredByUserId: "u1" }); // OK
|
|
47
|
+
* createEvent({ topic: "UserCreated", payload: { orderId: "123" }, triggeredByUserId: "u1" }); // Error!
|
|
48
|
+
*
|
|
49
|
+
* // For testing, inject deterministic functions:
|
|
50
|
+
* const createEvent = makeCreateNewEvent<MyEvents>({
|
|
51
|
+
* getNow: () => new Date("2024-01-01"),
|
|
52
|
+
* generateId: () => "test-id",
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
type CreateNewEvent<Event extends GenericEvent<string, unknown, DefaultContext>> = <Topic extends Event["topic"]>(params: CreateNewEventParams<Event, Topic>) => Extract<Event, {
|
|
57
|
+
topic: Topic;
|
|
58
|
+
}>;
|
|
59
|
+
declare const makeCreateNewEvent: <Event extends GenericEvent<string, unknown, DefaultContext>>(options?: MakeCreateNewEventOptions) => CreateNewEvent<Event>;
|
|
60
|
+
|
|
61
|
+
export { type CreateNewEvent, makeCreateNewEvent };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const makeCreateNewEvent = (options = {}) => {
|
|
2
|
+
const getNow = options.getNow ?? (() => /* @__PURE__ */ new Date());
|
|
3
|
+
const generateId = options.generateId ?? (() => crypto.randomUUID());
|
|
4
|
+
return (params) => ({
|
|
5
|
+
id: params.id ?? generateId(),
|
|
6
|
+
topic: params.topic,
|
|
7
|
+
payload: params.payload,
|
|
8
|
+
triggeredByUserId: params.triggeredByUserId,
|
|
9
|
+
occurredAt: params.occurredAt ?? getNow(),
|
|
10
|
+
status: "never-published",
|
|
11
|
+
publications: [],
|
|
12
|
+
priority: params.priority,
|
|
13
|
+
context: params.context
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
makeCreateNewEvent
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=createNewEvent.mjs.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
15
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
16
|
+
var index_exports = {};
|
|
17
|
+
module.exports = __toCommonJS(index_exports);
|
|
18
|
+
__reExport(index_exports, require("./adapters/in-memory/index.ts"), module.exports);
|
|
19
|
+
__reExport(index_exports, require("./createEventCrawler.ts"), module.exports);
|
|
20
|
+
__reExport(index_exports, require("./createNewEvent.ts"), module.exports);
|
|
21
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
22
|
+
0 && (module.exports = {
|
|
23
|
+
...require("./adapters/in-memory/index.ts"),
|
|
24
|
+
...require("./createEventCrawler.ts"),
|
|
25
|
+
...require("./createNewEvent.ts")
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./adapters/in-memory/index.ts\";\nexport * from \"./createEventCrawler.ts\";\nexport * from \"./createNewEvent.ts\";\nexport type * from \"./ports/EventBus.ts\";\nexport type * from \"./ports/EventQueries.ts\";\nexport type * from \"./ports/EventRepository.ts\";\nexport type * from \"./types.ts\";\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAAA,0BAAc,0CAAd;AACA,0BAAc,oCADd;AAEA,0BAAc,gCAFd;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createInMemoryEventRepositoryAndQueries } from './adapters/in-memory/index.cjs';
|
|
2
|
+
export { createEventCrawler } from './createEventCrawler.cjs';
|
|
3
|
+
export { CreateNewEvent, makeCreateNewEvent } from './createNewEvent.cjs';
|
|
4
|
+
export { EventBus } from './ports/EventBus.cjs';
|
|
5
|
+
export { EventQueries } from './ports/EventQueries.cjs';
|
|
6
|
+
export { EventRepository, EventsUnitOfWork, WithEventsUow } from './ports/EventRepository.cjs';
|
|
7
|
+
export { DefaultContext, EventFailure, EventId, EventPublication, EventStatus, Flavor, GenericEvent, SubscriptionId, UserId } from './types.cjs';
|
|
8
|
+
export { createInMemoryEventBus } from './adapters/in-memory/InMemoryEventBus.cjs';
|
|
9
|
+
export { createInMemoryEventQueries } from './adapters/in-memory/InMemoryEventQueries.cjs';
|
|
10
|
+
export { InMemoryEventRepositoryHelpers, createInMemoryEventRepository, createInMemoryWithUow } from './adapters/in-memory/InMemoryEventRepository.cjs';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createInMemoryEventRepositoryAndQueries } from './adapters/in-memory/index.js';
|
|
2
|
+
export { createEventCrawler } from './createEventCrawler.js';
|
|
3
|
+
export { CreateNewEvent, makeCreateNewEvent } from './createNewEvent.js';
|
|
4
|
+
export { EventBus } from './ports/EventBus.js';
|
|
5
|
+
export { EventQueries } from './ports/EventQueries.js';
|
|
6
|
+
export { EventRepository, EventsUnitOfWork, WithEventsUow } from './ports/EventRepository.js';
|
|
7
|
+
export { DefaultContext, EventFailure, EventId, EventPublication, EventStatus, Flavor, GenericEvent, SubscriptionId, UserId } from './types.js';
|
|
8
|
+
export { createInMemoryEventBus } from './adapters/in-memory/InMemoryEventBus.js';
|
|
9
|
+
export { createInMemoryEventQueries } from './adapters/in-memory/InMemoryEventQueries.js';
|
|
10
|
+
export { InMemoryEventRepositoryHelpers, createInMemoryEventRepository, createInMemoryWithUow } from './adapters/in-memory/InMemoryEventRepository.js';
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +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":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var EventBus_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(EventBus_exports);
|
|
17
|
+
//# sourceMappingURL=EventBus.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/ports/EventBus.ts"],"sourcesContent":["import type { DefaultContext, GenericEvent, SubscriptionId } from \"../types.ts\";\n\n/**\n * Event bus for publishing events to subscribers.\n * Handles delivery, failure tracking, and retry logic.\n *\n * @example\n * ```typescript\n * eventBus.subscribe({\n * topic: \"OrderPlaced\",\n * subscriptionId: \"send-confirmation-email\",\n * callBack: async (event) => {\n * await emailService.sendOrderConfirmation(event.payload);\n * },\n * });\n * ```\n */\nexport type EventBus<\n Event extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n /**\n * Publish an event to all subscribers of its topic.\n * Updates event status and records publication results.\n */\n publish: (event: Event) => Promise<void>;\n /**\n * Register a subscriber for a specific topic.\n * The subscriptionId must be unique per topic and is used for retry tracking.\n */\n subscribe: <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n >(params: {\n topic: Event[\"topic\"];\n subscriptionId: SubscriptionId;\n callBack: (e: Event) => Promise<void>;\n }) => void;\n};\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|