@l-etabli/events 0.4.2 → 0.5.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/dist/adapters/in-memory/InMemoryEventBus.cjs +18 -5
- package/dist/adapters/in-memory/InMemoryEventBus.cjs.map +1 -1
- package/dist/adapters/in-memory/InMemoryEventBus.d.cts +4 -0
- package/dist/adapters/in-memory/InMemoryEventBus.d.ts +4 -0
- package/dist/adapters/in-memory/InMemoryEventBus.mjs +21 -5
- package/dist/adapters/in-memory/InMemoryEventBus.mjs.map +1 -1
- package/dist/adapters/in-memory/index.d.cts +1 -0
- package/dist/adapters/in-memory/index.d.ts +1 -0
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/subscriptions.cjs +61 -0
- package/dist/subscriptions.cjs.map +1 -0
- package/dist/subscriptions.d.cts +107 -0
- package/dist/subscriptions.d.ts +107 -0
- package/dist/subscriptions.mjs +36 -0
- package/dist/subscriptions.mjs.map +1 -0
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/adapters/in-memory/InMemoryEventBus.ts +63 -4
- package/src/index.ts +1 -0
- package/src/subscriptions.ts +159 -0
- package/src/types.ts +1 -1
|
@@ -22,6 +22,7 @@ __export(InMemoryEventBus_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(InMemoryEventBus_exports);
|
|
24
24
|
var import_createNewEvent = require("../../createNewEvent.ts");
|
|
25
|
+
var import_subscriptions = require("../../subscriptions.ts");
|
|
25
26
|
const createInMemoryEventBus = (withUow, options = {}) => {
|
|
26
27
|
const maxRetries = options.maxRetries ?? 3;
|
|
27
28
|
const createNewEvent = (0, import_createNewEvent.makeCreateNewEvent)({
|
|
@@ -48,7 +49,7 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
48
49
|
const lastPublication = event.publications.reduce(
|
|
49
50
|
(latest, current) => current.publishedAt > latest.publishedAt ? current : latest
|
|
50
51
|
);
|
|
51
|
-
const failedSubscriptionIds = lastPublication.failures.map(
|
|
52
|
+
const failedSubscriptionIds = (lastPublication.failures ?? []).map(
|
|
52
53
|
(failure) => failure.subscriptionId
|
|
53
54
|
);
|
|
54
55
|
return allSubscriptionIds.filter(
|
|
@@ -63,8 +64,7 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
63
64
|
if (!callbacksBySubscriptionSlug) {
|
|
64
65
|
event.publications.push({
|
|
65
66
|
publishedAt,
|
|
66
|
-
publishedSubscribers: []
|
|
67
|
-
failures: []
|
|
67
|
+
publishedSubscribers: []
|
|
68
68
|
});
|
|
69
69
|
event.status = "published";
|
|
70
70
|
await withUow(async (uow) => {
|
|
@@ -95,7 +95,7 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
95
95
|
publishedSubscribers: subscriptionIdsToPublish.map(
|
|
96
96
|
(id) => id
|
|
97
97
|
),
|
|
98
|
-
failures
|
|
98
|
+
...failures.length > 0 && { failures }
|
|
99
99
|
}
|
|
100
100
|
];
|
|
101
101
|
if (failures.length === 0) {
|
|
@@ -119,7 +119,20 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
|
-
|
|
122
|
+
const defineSubscriptions = (subscriptions2) => subscriptions2;
|
|
123
|
+
const subscribeAll = (subscriptions2) => {
|
|
124
|
+
(0, import_subscriptions.subscribeByTopic)(eventBus, subscriptions2);
|
|
125
|
+
};
|
|
126
|
+
const subscribeGlobal = (subscriptions2, config) => {
|
|
127
|
+
(0, import_subscriptions.subscribeGlobalToTopics)(eventBus, subscriptions2, config);
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
eventBus,
|
|
131
|
+
createNewEvent,
|
|
132
|
+
defineSubscriptions,
|
|
133
|
+
subscribeAll,
|
|
134
|
+
subscribeGlobal
|
|
135
|
+
};
|
|
123
136
|
};
|
|
124
137
|
// Annotate the CommonJS export names for ESM import in node:
|
|
125
138
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import { makeCreateNewEvent } from \"../../createNewEvent.ts\";\nimport type { EventBus } from \"../../ports/EventBus.ts\";\nimport type { WithEventsUow } from \"../../ports/EventRepository.ts\";\nimport type {\n DefaultContext,\n EventId,\n EventPublication,\n GenericEvent,\n SubscriptionId,\n} from \"../../types.ts\";\n\ntype SubscriptionsForTopic = Record<\n string,\n (event: GenericEvent<string, unknown, DefaultContext>) => Promise<void>\n>;\n\ntype CreateInMemoryEventBusOptions = {\n maxRetries?: number;\n getNow?: () => Date;\n generateId?: () => EventId;\n};\n\nexport const createInMemoryEventBus = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options: CreateInMemoryEventBusOptions = {},\n) => {\n const maxRetries = options.maxRetries ?? 3;\n const createNewEvent = makeCreateNewEvent<Event>({\n getNow: options.getNow,\n generateId: options.generateId,\n });\n const subscriptions: Partial<Record<string, SubscriptionsForTopic>> = {};\n\n const executeCallback = async (\n event: Event,\n subscriptionId: string,\n callback: (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>,\n ): Promise<\n { subscriptionId: string; errorMessage: string; stack?: string } | undefined\n > => {\n try {\n await callback(event);\n } catch (error) {\n return {\n subscriptionId,\n errorMessage: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n }\n };\n\n const getSubscriptionIdsToPublish = (\n event: Event,\n callbacksBySubscriptionId: SubscriptionsForTopic,\n ): string[] => {\n const allSubscriptionIds = Object.keys(callbacksBySubscriptionId);\n\n if (event.publications.length === 0 || event.status === \"to-republish\") {\n return allSubscriptionIds;\n }\n\n const lastPublication = event.publications.reduce((latest, current) =>\n current.publishedAt > latest.publishedAt ? current : latest,\n );\n const failedSubscriptionIds = lastPublication.failures.map(\n (failure) => failure.subscriptionId,\n );\n\n return allSubscriptionIds.filter((id) =>\n failedSubscriptionIds.includes(id),\n );\n };\n\n const eventBus: EventBus<Event> = {\n publish: async (event) => {\n const publishedAt = new Date();\n const topic = event.topic;\n\n const callbacksBySubscriptionSlug = subscriptions[topic];\n\n if (!callbacksBySubscriptionSlug) {\n event.publications.push({\n publishedAt,\n publishedSubscribers: [],\n
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import { makeCreateNewEvent } from \"../../createNewEvent.ts\";\nimport type { EventBus } from \"../../ports/EventBus.ts\";\nimport type { WithEventsUow } from \"../../ports/EventRepository.ts\";\nimport {\n type GlobalSubscriberConfig,\n subscribeByTopic,\n subscribeGlobalToTopics,\n type TopicSubscriptions,\n} from \"../../subscriptions.ts\";\nimport type {\n DefaultContext,\n EventId,\n EventPublication,\n GenericEvent,\n SubscriptionId,\n} from \"../../types.ts\";\n\ntype SubscriptionsForTopic = Record<\n string,\n (event: GenericEvent<string, unknown, DefaultContext>) => Promise<void>\n>;\n\ntype CreateInMemoryEventBusOptions = {\n maxRetries?: number;\n getNow?: () => Date;\n generateId?: () => EventId;\n};\n\nexport const createInMemoryEventBus = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options: CreateInMemoryEventBusOptions = {},\n) => {\n const maxRetries = options.maxRetries ?? 3;\n const createNewEvent = makeCreateNewEvent<Event>({\n getNow: options.getNow,\n generateId: options.generateId,\n });\n const subscriptions: Partial<Record<string, SubscriptionsForTopic>> = {};\n\n const executeCallback = async (\n event: Event,\n subscriptionId: string,\n callback: (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>,\n ): Promise<\n { subscriptionId: string; errorMessage: string; stack?: string } | undefined\n > => {\n try {\n await callback(event);\n } catch (error) {\n return {\n subscriptionId,\n errorMessage: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n }\n };\n\n const getSubscriptionIdsToPublish = (\n event: Event,\n callbacksBySubscriptionId: SubscriptionsForTopic,\n ): string[] => {\n const allSubscriptionIds = Object.keys(callbacksBySubscriptionId);\n\n if (event.publications.length === 0 || event.status === \"to-republish\") {\n return allSubscriptionIds;\n }\n\n const lastPublication = event.publications.reduce((latest, current) =>\n current.publishedAt > latest.publishedAt ? current : latest,\n );\n const failedSubscriptionIds = (lastPublication.failures ?? []).map(\n (failure) => failure.subscriptionId,\n );\n\n return allSubscriptionIds.filter((id) =>\n failedSubscriptionIds.includes(id),\n );\n };\n\n const eventBus: EventBus<Event> = {\n publish: async (event) => {\n const publishedAt = new Date();\n const topic = event.topic;\n\n const callbacksBySubscriptionSlug = subscriptions[topic];\n\n if (!callbacksBySubscriptionSlug) {\n event.publications.push({\n publishedAt,\n publishedSubscribers: [],\n });\n event.status = \"published\";\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n return;\n }\n\n const subscriptionIdsToPublish = getSubscriptionIdsToPublish(\n event,\n callbacksBySubscriptionSlug,\n );\n\n const failuresOrUndefined = await Promise.all(\n subscriptionIdsToPublish.map((subscriptionId) =>\n executeCallback(\n event,\n subscriptionId,\n callbacksBySubscriptionSlug[subscriptionId],\n ),\n ),\n );\n\n const failures = failuresOrUndefined.filter(\n (\n f,\n ): f is {\n subscriptionId: string;\n errorMessage: string;\n stack?: string;\n } => f !== undefined,\n );\n\n const publications: EventPublication[] = [\n ...event.publications,\n {\n publishedAt,\n publishedSubscribers: subscriptionIdsToPublish.map(\n (id) => id as SubscriptionId,\n ),\n ...(failures.length > 0 && { failures }),\n },\n ];\n\n if (failures.length === 0) {\n event.status = \"published\";\n } else {\n const wasMaxNumberOfErrorsReached = publications.length >= maxRetries;\n event.status = wasMaxNumberOfErrorsReached\n ? \"quarantined\"\n : \"failed-but-will-retry\";\n }\n\n event.publications = publications;\n\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n },\n\n subscribe: ({ topic, subscriptionId, callBack }) => {\n if (!subscriptions[topic]) {\n subscriptions[topic] = {};\n }\n\n const subscriptionsForTopic = subscriptions[topic];\n if (subscriptionsForTopic) {\n subscriptionsForTopic[subscriptionId] = callBack as (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>;\n }\n },\n };\n\n /**\n * Identity function for type inference when defining subscription maps.\n * Ensures all topics are covered and handlers have correct payload types.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({\n * OrderCreated: [{ subscriptionId: \"notify\", handler: async (e) => {...} }],\n * OrderShipped: [], // Required even if empty\n * });\n * ```\n */\n const defineSubscriptions = (\n subscriptions: TopicSubscriptions<Event>,\n ): TopicSubscriptions<Event> => subscriptions;\n\n /**\n * Subscribe all handlers from a topic subscription map to this event bus.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({...});\n * subscribeAll(subscriptions);\n * ```\n */\n const subscribeAll = (subscriptions: TopicSubscriptions<Event>): void => {\n subscribeByTopic(eventBus, subscriptions);\n };\n\n /**\n * Subscribe a global handler to multiple topics with optional filtering.\n *\n * @example\n * ```typescript\n * subscribeGlobal(subscriptions, {\n * subscriptionId: \"audit-log\",\n * handler: async (event) => auditLog.record(event),\n * filter: { exclude: [\"NotificationAdded\"] },\n * });\n * ```\n */\n const subscribeGlobal = (\n subscriptions: TopicSubscriptions<Event>,\n config: GlobalSubscriberConfig<Event>,\n ): void => {\n subscribeGlobalToTopics(eventBus, subscriptions, config);\n };\n\n return {\n eventBus,\n createNewEvent,\n defineSubscriptions,\n subscribeAll,\n subscribeGlobal,\n };\n};\n"],"mappings":"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","names":["subscriptions"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CreateNewEvent } from '../../createNewEvent.cjs';
|
|
2
2
|
import { EventBus } from '../../ports/EventBus.cjs';
|
|
3
3
|
import { WithEventsUow } from '../../ports/EventRepository.cjs';
|
|
4
|
+
import { TopicSubscriptions, GlobalSubscriberConfig } from '../../subscriptions.cjs';
|
|
4
5
|
import { GenericEvent, DefaultContext, EventId } from '../../types.cjs';
|
|
5
6
|
|
|
6
7
|
type CreateInMemoryEventBusOptions = {
|
|
@@ -11,6 +12,9 @@ type CreateInMemoryEventBusOptions = {
|
|
|
11
12
|
declare const createInMemoryEventBus: <Event extends GenericEvent<string, unknown, DefaultContext>>(withUow: WithEventsUow<Event>, options?: CreateInMemoryEventBusOptions) => {
|
|
12
13
|
eventBus: EventBus<Event>;
|
|
13
14
|
createNewEvent: CreateNewEvent<Event>;
|
|
15
|
+
defineSubscriptions: (subscriptions: TopicSubscriptions<Event>) => TopicSubscriptions<Event>;
|
|
16
|
+
subscribeAll: (subscriptions: TopicSubscriptions<Event>) => void;
|
|
17
|
+
subscribeGlobal: (subscriptions: TopicSubscriptions<Event>, config: GlobalSubscriberConfig<Event>) => void;
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
export { createInMemoryEventBus };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CreateNewEvent } from '../../createNewEvent.js';
|
|
2
2
|
import { EventBus } from '../../ports/EventBus.js';
|
|
3
3
|
import { WithEventsUow } from '../../ports/EventRepository.js';
|
|
4
|
+
import { TopicSubscriptions, GlobalSubscriberConfig } from '../../subscriptions.js';
|
|
4
5
|
import { GenericEvent, DefaultContext, EventId } from '../../types.js';
|
|
5
6
|
|
|
6
7
|
type CreateInMemoryEventBusOptions = {
|
|
@@ -11,6 +12,9 @@ type CreateInMemoryEventBusOptions = {
|
|
|
11
12
|
declare const createInMemoryEventBus: <Event extends GenericEvent<string, unknown, DefaultContext>>(withUow: WithEventsUow<Event>, options?: CreateInMemoryEventBusOptions) => {
|
|
12
13
|
eventBus: EventBus<Event>;
|
|
13
14
|
createNewEvent: CreateNewEvent<Event>;
|
|
15
|
+
defineSubscriptions: (subscriptions: TopicSubscriptions<Event>) => TopicSubscriptions<Event>;
|
|
16
|
+
subscribeAll: (subscriptions: TopicSubscriptions<Event>) => void;
|
|
17
|
+
subscribeGlobal: (subscriptions: TopicSubscriptions<Event>, config: GlobalSubscriberConfig<Event>) => void;
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
export { createInMemoryEventBus };
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { makeCreateNewEvent } from "../../createNewEvent.mjs";
|
|
2
|
+
import {
|
|
3
|
+
subscribeByTopic,
|
|
4
|
+
subscribeGlobalToTopics
|
|
5
|
+
} from "../../subscriptions.mjs";
|
|
2
6
|
const createInMemoryEventBus = (withUow, options = {}) => {
|
|
3
7
|
const maxRetries = options.maxRetries ?? 3;
|
|
4
8
|
const createNewEvent = makeCreateNewEvent({
|
|
@@ -25,7 +29,7 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
25
29
|
const lastPublication = event.publications.reduce(
|
|
26
30
|
(latest, current) => current.publishedAt > latest.publishedAt ? current : latest
|
|
27
31
|
);
|
|
28
|
-
const failedSubscriptionIds = lastPublication.failures.map(
|
|
32
|
+
const failedSubscriptionIds = (lastPublication.failures ?? []).map(
|
|
29
33
|
(failure) => failure.subscriptionId
|
|
30
34
|
);
|
|
31
35
|
return allSubscriptionIds.filter(
|
|
@@ -40,8 +44,7 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
40
44
|
if (!callbacksBySubscriptionSlug) {
|
|
41
45
|
event.publications.push({
|
|
42
46
|
publishedAt,
|
|
43
|
-
publishedSubscribers: []
|
|
44
|
-
failures: []
|
|
47
|
+
publishedSubscribers: []
|
|
45
48
|
});
|
|
46
49
|
event.status = "published";
|
|
47
50
|
await withUow(async (uow) => {
|
|
@@ -72,7 +75,7 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
72
75
|
publishedSubscribers: subscriptionIdsToPublish.map(
|
|
73
76
|
(id) => id
|
|
74
77
|
),
|
|
75
|
-
failures
|
|
78
|
+
...failures.length > 0 && { failures }
|
|
76
79
|
}
|
|
77
80
|
];
|
|
78
81
|
if (failures.length === 0) {
|
|
@@ -96,7 +99,20 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
};
|
|
99
|
-
|
|
102
|
+
const defineSubscriptions = (subscriptions2) => subscriptions2;
|
|
103
|
+
const subscribeAll = (subscriptions2) => {
|
|
104
|
+
subscribeByTopic(eventBus, subscriptions2);
|
|
105
|
+
};
|
|
106
|
+
const subscribeGlobal = (subscriptions2, config) => {
|
|
107
|
+
subscribeGlobalToTopics(eventBus, subscriptions2, config);
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
eventBus,
|
|
111
|
+
createNewEvent,
|
|
112
|
+
defineSubscriptions,
|
|
113
|
+
subscribeAll,
|
|
114
|
+
subscribeGlobal
|
|
115
|
+
};
|
|
100
116
|
};
|
|
101
117
|
export {
|
|
102
118
|
createInMemoryEventBus
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import { makeCreateNewEvent } from '../../createNewEvent.mjs';\nimport type { EventBus } from '../../ports/EventBus.mjs';\nimport type { WithEventsUow } from '../../ports/EventRepository.mjs';\nimport 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
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import { makeCreateNewEvent } from '../../createNewEvent.mjs';\nimport type { EventBus } from '../../ports/EventBus.mjs';\nimport type { WithEventsUow } from '../../ports/EventRepository.mjs';\nimport {\n type GlobalSubscriberConfig,\n subscribeByTopic,\n subscribeGlobalToTopics,\n type TopicSubscriptions,\n} from '../../subscriptions.mjs';\nimport type {\n DefaultContext,\n EventId,\n EventPublication,\n GenericEvent,\n SubscriptionId,\n} from '../../types.mjs';\n\ntype SubscriptionsForTopic = Record<\n string,\n (event: GenericEvent<string, unknown, DefaultContext>) => Promise<void>\n>;\n\ntype CreateInMemoryEventBusOptions = {\n maxRetries?: number;\n getNow?: () => Date;\n generateId?: () => EventId;\n};\n\nexport const createInMemoryEventBus = <\n Event extends GenericEvent<string, unknown, DefaultContext>,\n>(\n withUow: WithEventsUow<Event>,\n options: CreateInMemoryEventBusOptions = {},\n) => {\n const maxRetries = options.maxRetries ?? 3;\n const createNewEvent = makeCreateNewEvent<Event>({\n getNow: options.getNow,\n generateId: options.generateId,\n });\n const subscriptions: Partial<Record<string, SubscriptionsForTopic>> = {};\n\n const executeCallback = async (\n event: Event,\n subscriptionId: string,\n callback: (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>,\n ): Promise<\n { subscriptionId: string; errorMessage: string; stack?: string } | undefined\n > => {\n try {\n await callback(event);\n } catch (error) {\n return {\n subscriptionId,\n errorMessage: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n }\n };\n\n const getSubscriptionIdsToPublish = (\n event: Event,\n callbacksBySubscriptionId: SubscriptionsForTopic,\n ): string[] => {\n const allSubscriptionIds = Object.keys(callbacksBySubscriptionId);\n\n if (event.publications.length === 0 || event.status === \"to-republish\") {\n return allSubscriptionIds;\n }\n\n const lastPublication = event.publications.reduce((latest, current) =>\n current.publishedAt > latest.publishedAt ? current : latest,\n );\n const failedSubscriptionIds = (lastPublication.failures ?? []).map(\n (failure) => failure.subscriptionId,\n );\n\n return allSubscriptionIds.filter((id) =>\n failedSubscriptionIds.includes(id),\n );\n };\n\n const eventBus: EventBus<Event> = {\n publish: async (event) => {\n const publishedAt = new Date();\n const topic = event.topic;\n\n const callbacksBySubscriptionSlug = subscriptions[topic];\n\n if (!callbacksBySubscriptionSlug) {\n event.publications.push({\n publishedAt,\n publishedSubscribers: [],\n });\n event.status = \"published\";\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n return;\n }\n\n const subscriptionIdsToPublish = getSubscriptionIdsToPublish(\n event,\n callbacksBySubscriptionSlug,\n );\n\n const failuresOrUndefined = await Promise.all(\n subscriptionIdsToPublish.map((subscriptionId) =>\n executeCallback(\n event,\n subscriptionId,\n callbacksBySubscriptionSlug[subscriptionId],\n ),\n ),\n );\n\n const failures = failuresOrUndefined.filter(\n (\n f,\n ): f is {\n subscriptionId: string;\n errorMessage: string;\n stack?: string;\n } => f !== undefined,\n );\n\n const publications: EventPublication[] = [\n ...event.publications,\n {\n publishedAt,\n publishedSubscribers: subscriptionIdsToPublish.map(\n (id) => id as SubscriptionId,\n ),\n ...(failures.length > 0 && { failures }),\n },\n ];\n\n if (failures.length === 0) {\n event.status = \"published\";\n } else {\n const wasMaxNumberOfErrorsReached = publications.length >= maxRetries;\n event.status = wasMaxNumberOfErrorsReached\n ? \"quarantined\"\n : \"failed-but-will-retry\";\n }\n\n event.publications = publications;\n\n await withUow(async (uow) => {\n await uow.eventRepository.save(event);\n });\n },\n\n subscribe: ({ topic, subscriptionId, callBack }) => {\n if (!subscriptions[topic]) {\n subscriptions[topic] = {};\n }\n\n const subscriptionsForTopic = subscriptions[topic];\n if (subscriptionsForTopic) {\n subscriptionsForTopic[subscriptionId] = callBack as (\n event: GenericEvent<string, unknown, DefaultContext>,\n ) => Promise<void>;\n }\n },\n };\n\n /**\n * Identity function for type inference when defining subscription maps.\n * Ensures all topics are covered and handlers have correct payload types.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({\n * OrderCreated: [{ subscriptionId: \"notify\", handler: async (e) => {...} }],\n * OrderShipped: [], // Required even if empty\n * });\n * ```\n */\n const defineSubscriptions = (\n subscriptions: TopicSubscriptions<Event>,\n ): TopicSubscriptions<Event> => subscriptions;\n\n /**\n * Subscribe all handlers from a topic subscription map to this event bus.\n *\n * @example\n * ```typescript\n * const subscriptions = defineSubscriptions({...});\n * subscribeAll(subscriptions);\n * ```\n */\n const subscribeAll = (subscriptions: TopicSubscriptions<Event>): void => {\n subscribeByTopic(eventBus, subscriptions);\n };\n\n /**\n * Subscribe a global handler to multiple topics with optional filtering.\n *\n * @example\n * ```typescript\n * subscribeGlobal(subscriptions, {\n * subscriptionId: \"audit-log\",\n * handler: async (event) => auditLog.record(event),\n * filter: { exclude: [\"NotificationAdded\"] },\n * });\n * ```\n */\n const subscribeGlobal = (\n subscriptions: TopicSubscriptions<Event>,\n config: GlobalSubscriberConfig<Event>,\n ): void => {\n subscribeGlobalToTopics(eventBus, subscriptions, config);\n };\n\n return {\n eventBus,\n createNewEvent,\n defineSubscriptions,\n subscribeAll,\n subscribeGlobal,\n };\n};\n"],"mappings":"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","names":["subscriptions"]}
|
|
@@ -7,6 +7,7 @@ export { createInMemoryEventBus } from './InMemoryEventBus.cjs';
|
|
|
7
7
|
export { createInMemoryEventQueries } from './InMemoryEventQueries.cjs';
|
|
8
8
|
import '../../createNewEvent.cjs';
|
|
9
9
|
import '../../ports/EventBus.cjs';
|
|
10
|
+
import '../../subscriptions.cjs';
|
|
10
11
|
|
|
11
12
|
declare const createInMemoryEventRepositoryAndQueries: <Event extends GenericEvent<string, unknown, DefaultContext>>() => {
|
|
12
13
|
eventRepository: EventRepository<Event>;
|
|
@@ -7,6 +7,7 @@ export { createInMemoryEventBus } from './InMemoryEventBus.js';
|
|
|
7
7
|
export { createInMemoryEventQueries } from './InMemoryEventQueries.js';
|
|
8
8
|
import '../../createNewEvent.js';
|
|
9
9
|
import '../../ports/EventBus.js';
|
|
10
|
+
import '../../subscriptions.js';
|
|
10
11
|
|
|
11
12
|
declare const createInMemoryEventRepositoryAndQueries: <Event extends GenericEvent<string, unknown, DefaultContext>>() => {
|
|
12
13
|
eventRepository: EventRepository<Event>;
|
package/dist/index.cjs
CHANGED
|
@@ -18,10 +18,12 @@ module.exports = __toCommonJS(index_exports);
|
|
|
18
18
|
__reExport(index_exports, require("./adapters/in-memory/index.ts"), module.exports);
|
|
19
19
|
__reExport(index_exports, require("./createEventCrawler.ts"), module.exports);
|
|
20
20
|
__reExport(index_exports, require("./createNewEvent.ts"), module.exports);
|
|
21
|
+
__reExport(index_exports, require("./subscriptions.ts"), module.exports);
|
|
21
22
|
// Annotate the CommonJS export names for ESM import in node:
|
|
22
23
|
0 && (module.exports = {
|
|
23
24
|
...require("./adapters/in-memory/index.ts"),
|
|
24
25
|
...require("./createEventCrawler.ts"),
|
|
25
|
-
...require("./createNewEvent.ts")
|
|
26
|
+
...require("./createNewEvent.ts"),
|
|
27
|
+
...require("./subscriptions.ts")
|
|
26
28
|
});
|
|
27
29
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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":[]}
|
|
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 * from \"./subscriptions.ts\";\nexport type * from \"./types.ts\";\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAAA,0BAAc,0CAAd;AACA,0BAAc,oCADd;AAEA,0BAAc,gCAFd;AAMA,0BAAc,+BANd;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,7 @@ export { CreateNewEvent, makeCreateNewEvent } from './createNewEvent.cjs';
|
|
|
4
4
|
export { EventBus } from './ports/EventBus.cjs';
|
|
5
5
|
export { EventQueries } from './ports/EventQueries.cjs';
|
|
6
6
|
export { EventRepository, EventsUnitOfWork, WithEventsUow, WithEventsUowOptions } from './ports/EventRepository.cjs';
|
|
7
|
+
export { GlobalSubscriberConfig, NarrowEvent, TopicFilter, TopicSubscriber, TopicSubscriptions, subscribeByTopic, subscribeGlobalToTopics } from './subscriptions.cjs';
|
|
7
8
|
export { DefaultContext, EventFailure, EventId, EventPublication, EventStatus, Flavor, GenericEvent, SubscriptionId, UserId } from './types.cjs';
|
|
8
9
|
export { createInMemoryEventBus } from './adapters/in-memory/InMemoryEventBus.cjs';
|
|
9
10
|
export { createInMemoryEventQueries } from './adapters/in-memory/InMemoryEventQueries.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { CreateNewEvent, makeCreateNewEvent } from './createNewEvent.js';
|
|
|
4
4
|
export { EventBus } from './ports/EventBus.js';
|
|
5
5
|
export { EventQueries } from './ports/EventQueries.js';
|
|
6
6
|
export { EventRepository, EventsUnitOfWork, WithEventsUow, WithEventsUowOptions } from './ports/EventRepository.js';
|
|
7
|
+
export { GlobalSubscriberConfig, NarrowEvent, TopicFilter, TopicSubscriber, TopicSubscriptions, subscribeByTopic, subscribeGlobalToTopics } from './subscriptions.js';
|
|
7
8
|
export { DefaultContext, EventFailure, EventId, EventPublication, EventStatus, Flavor, GenericEvent, SubscriptionId, UserId } from './types.js';
|
|
8
9
|
export { createInMemoryEventBus } from './adapters/in-memory/InMemoryEventBus.js';
|
|
9
10
|
export { createInMemoryEventQueries } from './adapters/in-memory/InMemoryEventQueries.js';
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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 * from './subscriptions.mjs';\nexport type * from './types.mjs';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc;","names":[]}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 subscriptions_exports = {};
|
|
20
|
+
__export(subscriptions_exports, {
|
|
21
|
+
subscribeByTopic: () => subscribeByTopic,
|
|
22
|
+
subscribeGlobalToTopics: () => subscribeGlobalToTopics
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(subscriptions_exports);
|
|
25
|
+
function subscribeByTopic(eventBus, subscriptions) {
|
|
26
|
+
for (const topic of Object.keys(subscriptions)) {
|
|
27
|
+
const handlers = subscriptions[topic];
|
|
28
|
+
for (const { subscriptionId, handler } of handlers) {
|
|
29
|
+
eventBus.subscribe({
|
|
30
|
+
topic,
|
|
31
|
+
subscriptionId,
|
|
32
|
+
callBack: handler
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function subscribeGlobalToTopics(eventBus, subscriptions, config) {
|
|
38
|
+
const allTopics = Object.keys(subscriptions);
|
|
39
|
+
let topicsToSubscribe;
|
|
40
|
+
const filter = config.filter;
|
|
41
|
+
if (!filter) {
|
|
42
|
+
topicsToSubscribe = allTopics;
|
|
43
|
+
} else if ("include" in filter) {
|
|
44
|
+
topicsToSubscribe = filter.include;
|
|
45
|
+
} else {
|
|
46
|
+
topicsToSubscribe = allTopics.filter((t) => !filter.exclude.includes(t));
|
|
47
|
+
}
|
|
48
|
+
for (const topic of topicsToSubscribe) {
|
|
49
|
+
eventBus.subscribe({
|
|
50
|
+
topic,
|
|
51
|
+
subscriptionId: config.subscriptionId,
|
|
52
|
+
callBack: config.handler
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
57
|
+
0 && (module.exports = {
|
|
58
|
+
subscribeByTopic,
|
|
59
|
+
subscribeGlobalToTopics
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=subscriptions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/subscriptions.ts"],"sourcesContent":["import type { EventBus } from \"./ports/EventBus.ts\";\nimport type { DefaultContext, GenericEvent, SubscriptionId } from \"./types.ts\";\n\n/**\n * Extracts a specific event type from a union of events by topic.\n *\n * @example\n * ```typescript\n * type MyEvents =\n * | GenericEvent<\"OrderCreated\", { orderId: string }>\n * | GenericEvent<\"OrderShipped\", { trackingNumber: string }>;\n *\n * type OrderCreatedEvent = NarrowEvent<MyEvents, \"OrderCreated\">;\n * // Result: GenericEvent<\"OrderCreated\", { orderId: string }>\n * ```\n */\nexport type NarrowEvent<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n Topic extends AllEvents[\"topic\"],\n> = Extract<AllEvents, { topic: Topic }>;\n\n/**\n * A subscriber for a specific event topic.\n * The handler receives the full event (not just payload) for access to metadata.\n */\nexport type TopicSubscriber<\n E extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n /** Unique identifier for retry tracking. */\n subscriptionId: SubscriptionId;\n /** Async handler receiving the full event with metadata (id, occurredAt, etc). */\n handler: (event: E) => Promise<void>;\n};\n\n/**\n * Complete subscription map requiring ALL event topics.\n * Forces explicit decision about each topic, documenting intent.\n *\n * @example\n * ```typescript\n * const subscriptions: TopicSubscriptions<MyEvents> = {\n * OrderCreated: [{ subscriptionId: \"notify\", handler: async (e) => {...} }],\n * OrderShipped: [], // Required even if empty\n * };\n * ```\n */\nexport type TopicSubscriptions<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n [K in AllEvents[\"topic\"]]: Array<TopicSubscriber<NarrowEvent<AllEvents, K>>>;\n};\n\n/**\n * Filter for global handlers. Use `include` OR `exclude` (mutually exclusive).\n *\n * @example\n * ```typescript\n * { include: [\"OrderCreated\", \"OrderShipped\"] } // Only these topics\n * { exclude: [\"NotificationAdded\"] } // All except these\n * ```\n */\nexport type TopicFilter<Topic extends string> =\n | { include: Topic[] }\n | { exclude: Topic[] };\n\n/**\n * Configuration for a global handler that listens to multiple topics.\n */\nexport type GlobalSubscriberConfig<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n /** Unique identifier for retry tracking. */\n subscriptionId: SubscriptionId;\n /** Async handler receiving any event matching the filter. */\n handler: (event: AllEvents) => Promise<void>;\n /** Optional topic filter. Without filter, receives all topics. */\n filter?: TopicFilter<AllEvents[\"topic\"]>;\n};\n\n/**\n * Subscribe all handlers from a topic subscription map to an event bus.\n * Standalone function for use with any EventBus implementation.\n *\n * @example\n * ```typescript\n * const subscriptions: TopicSubscriptions<MyEvents> = {\n * OrderCreated: [{ subscriptionId: \"notify\", handler: sendNotification }],\n * OrderShipped: [{ subscriptionId: \"track\", handler: updateTracking }],\n * };\n * subscribeByTopic(eventBus, subscriptions);\n * ```\n */\nexport function subscribeByTopic<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n>(\n eventBus: EventBus<AllEvents>,\n subscriptions: TopicSubscriptions<AllEvents>,\n): void {\n for (const topic of Object.keys(subscriptions) as AllEvents[\"topic\"][]) {\n const handlers = subscriptions[topic];\n for (const { subscriptionId, handler } of handlers) {\n eventBus.subscribe({\n topic,\n subscriptionId,\n callBack: handler,\n });\n }\n }\n}\n\n/**\n * Subscribe a global handler to multiple topics with optional filtering.\n * Standalone function for use with any EventBus implementation.\n *\n * @example\n * ```typescript\n * // Audit log for all events except noisy ones\n * subscribeGlobalToTopics(eventBus, subscriptions, {\n * subscriptionId: \"audit-log\",\n * handler: async (event) => auditLog.record(event),\n * filter: { exclude: [\"NotificationAdded\"] },\n * });\n *\n * // Analytics for specific topics only\n * subscribeGlobalToTopics(eventBus, subscriptions, {\n * subscriptionId: \"order-analytics\",\n * handler: async (event) => analytics.track(event),\n * filter: { include: [\"OrderCreated\", \"OrderShipped\"] },\n * });\n * ```\n */\nexport function subscribeGlobalToTopics<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n>(\n eventBus: EventBus<AllEvents>,\n subscriptions: TopicSubscriptions<AllEvents>,\n config: GlobalSubscriberConfig<AllEvents>,\n): void {\n const allTopics = Object.keys(subscriptions) as AllEvents[\"topic\"][];\n\n let topicsToSubscribe: AllEvents[\"topic\"][];\n\n const filter = config.filter;\n if (!filter) {\n topicsToSubscribe = allTopics;\n } else if (\"include\" in filter) {\n topicsToSubscribe = filter.include;\n } else {\n topicsToSubscribe = allTopics.filter((t) => !filter.exclude.includes(t));\n }\n\n for (const topic of topicsToSubscribe) {\n eventBus.subscribe({\n topic,\n subscriptionId: config.subscriptionId,\n callBack: config.handler,\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4FO,SAAS,iBAGd,UACA,eACM;AACN,aAAW,SAAS,OAAO,KAAK,aAAa,GAA2B;AACtE,UAAM,WAAW,cAAc,KAAK;AACpC,eAAW,EAAE,gBAAgB,QAAQ,KAAK,UAAU;AAClD,eAAS,UAAU;AAAA,QACjB;AAAA,QACA;AAAA,QACA,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAuBO,SAAS,wBAGd,UACA,eACA,QACM;AACN,QAAM,YAAY,OAAO,KAAK,aAAa;AAE3C,MAAI;AAEJ,QAAM,SAAS,OAAO;AACtB,MAAI,CAAC,QAAQ;AACX,wBAAoB;AAAA,EACtB,WAAW,aAAa,QAAQ;AAC9B,wBAAoB,OAAO;AAAA,EAC7B,OAAO;AACL,wBAAoB,UAAU,OAAO,CAAC,MAAM,CAAC,OAAO,QAAQ,SAAS,CAAC,CAAC;AAAA,EACzE;AAEA,aAAW,SAAS,mBAAmB;AACrC,aAAS,UAAU;AAAA,MACjB;AAAA,MACA,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { EventBus } from './ports/EventBus.cjs';
|
|
2
|
+
import { GenericEvent, DefaultContext, SubscriptionId } from './types.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts a specific event type from a union of events by topic.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* type MyEvents =
|
|
10
|
+
* | GenericEvent<"OrderCreated", { orderId: string }>
|
|
11
|
+
* | GenericEvent<"OrderShipped", { trackingNumber: string }>;
|
|
12
|
+
*
|
|
13
|
+
* type OrderCreatedEvent = NarrowEvent<MyEvents, "OrderCreated">;
|
|
14
|
+
* // Result: GenericEvent<"OrderCreated", { orderId: string }>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type NarrowEvent<AllEvents extends GenericEvent<string, unknown, DefaultContext>, Topic extends AllEvents["topic"]> = Extract<AllEvents, {
|
|
18
|
+
topic: Topic;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* A subscriber for a specific event topic.
|
|
22
|
+
* The handler receives the full event (not just payload) for access to metadata.
|
|
23
|
+
*/
|
|
24
|
+
type TopicSubscriber<E extends GenericEvent<string, unknown, DefaultContext>> = {
|
|
25
|
+
/** Unique identifier for retry tracking. */
|
|
26
|
+
subscriptionId: SubscriptionId;
|
|
27
|
+
/** Async handler receiving the full event with metadata (id, occurredAt, etc). */
|
|
28
|
+
handler: (event: E) => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Complete subscription map requiring ALL event topics.
|
|
32
|
+
* Forces explicit decision about each topic, documenting intent.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const subscriptions: TopicSubscriptions<MyEvents> = {
|
|
37
|
+
* OrderCreated: [{ subscriptionId: "notify", handler: async (e) => {...} }],
|
|
38
|
+
* OrderShipped: [], // Required even if empty
|
|
39
|
+
* };
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
type TopicSubscriptions<AllEvents extends GenericEvent<string, unknown, DefaultContext>> = {
|
|
43
|
+
[K in AllEvents["topic"]]: Array<TopicSubscriber<NarrowEvent<AllEvents, K>>>;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Filter for global handlers. Use `include` OR `exclude` (mutually exclusive).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* { include: ["OrderCreated", "OrderShipped"] } // Only these topics
|
|
51
|
+
* { exclude: ["NotificationAdded"] } // All except these
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
type TopicFilter<Topic extends string> = {
|
|
55
|
+
include: Topic[];
|
|
56
|
+
} | {
|
|
57
|
+
exclude: Topic[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Configuration for a global handler that listens to multiple topics.
|
|
61
|
+
*/
|
|
62
|
+
type GlobalSubscriberConfig<AllEvents extends GenericEvent<string, unknown, DefaultContext>> = {
|
|
63
|
+
/** Unique identifier for retry tracking. */
|
|
64
|
+
subscriptionId: SubscriptionId;
|
|
65
|
+
/** Async handler receiving any event matching the filter. */
|
|
66
|
+
handler: (event: AllEvents) => Promise<void>;
|
|
67
|
+
/** Optional topic filter. Without filter, receives all topics. */
|
|
68
|
+
filter?: TopicFilter<AllEvents["topic"]>;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Subscribe all handlers from a topic subscription map to an event bus.
|
|
72
|
+
* Standalone function for use with any EventBus implementation.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const subscriptions: TopicSubscriptions<MyEvents> = {
|
|
77
|
+
* OrderCreated: [{ subscriptionId: "notify", handler: sendNotification }],
|
|
78
|
+
* OrderShipped: [{ subscriptionId: "track", handler: updateTracking }],
|
|
79
|
+
* };
|
|
80
|
+
* subscribeByTopic(eventBus, subscriptions);
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function subscribeByTopic<AllEvents extends GenericEvent<string, unknown, DefaultContext>>(eventBus: EventBus<AllEvents>, subscriptions: TopicSubscriptions<AllEvents>): void;
|
|
84
|
+
/**
|
|
85
|
+
* Subscribe a global handler to multiple topics with optional filtering.
|
|
86
|
+
* Standalone function for use with any EventBus implementation.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // Audit log for all events except noisy ones
|
|
91
|
+
* subscribeGlobalToTopics(eventBus, subscriptions, {
|
|
92
|
+
* subscriptionId: "audit-log",
|
|
93
|
+
* handler: async (event) => auditLog.record(event),
|
|
94
|
+
* filter: { exclude: ["NotificationAdded"] },
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // Analytics for specific topics only
|
|
98
|
+
* subscribeGlobalToTopics(eventBus, subscriptions, {
|
|
99
|
+
* subscriptionId: "order-analytics",
|
|
100
|
+
* handler: async (event) => analytics.track(event),
|
|
101
|
+
* filter: { include: ["OrderCreated", "OrderShipped"] },
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare function subscribeGlobalToTopics<AllEvents extends GenericEvent<string, unknown, DefaultContext>>(eventBus: EventBus<AllEvents>, subscriptions: TopicSubscriptions<AllEvents>, config: GlobalSubscriberConfig<AllEvents>): void;
|
|
106
|
+
|
|
107
|
+
export { type GlobalSubscriberConfig, type NarrowEvent, type TopicFilter, type TopicSubscriber, type TopicSubscriptions, subscribeByTopic, subscribeGlobalToTopics };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { EventBus } from './ports/EventBus.js';
|
|
2
|
+
import { GenericEvent, DefaultContext, SubscriptionId } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts a specific event type from a union of events by topic.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* type MyEvents =
|
|
10
|
+
* | GenericEvent<"OrderCreated", { orderId: string }>
|
|
11
|
+
* | GenericEvent<"OrderShipped", { trackingNumber: string }>;
|
|
12
|
+
*
|
|
13
|
+
* type OrderCreatedEvent = NarrowEvent<MyEvents, "OrderCreated">;
|
|
14
|
+
* // Result: GenericEvent<"OrderCreated", { orderId: string }>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type NarrowEvent<AllEvents extends GenericEvent<string, unknown, DefaultContext>, Topic extends AllEvents["topic"]> = Extract<AllEvents, {
|
|
18
|
+
topic: Topic;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* A subscriber for a specific event topic.
|
|
22
|
+
* The handler receives the full event (not just payload) for access to metadata.
|
|
23
|
+
*/
|
|
24
|
+
type TopicSubscriber<E extends GenericEvent<string, unknown, DefaultContext>> = {
|
|
25
|
+
/** Unique identifier for retry tracking. */
|
|
26
|
+
subscriptionId: SubscriptionId;
|
|
27
|
+
/** Async handler receiving the full event with metadata (id, occurredAt, etc). */
|
|
28
|
+
handler: (event: E) => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Complete subscription map requiring ALL event topics.
|
|
32
|
+
* Forces explicit decision about each topic, documenting intent.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const subscriptions: TopicSubscriptions<MyEvents> = {
|
|
37
|
+
* OrderCreated: [{ subscriptionId: "notify", handler: async (e) => {...} }],
|
|
38
|
+
* OrderShipped: [], // Required even if empty
|
|
39
|
+
* };
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
type TopicSubscriptions<AllEvents extends GenericEvent<string, unknown, DefaultContext>> = {
|
|
43
|
+
[K in AllEvents["topic"]]: Array<TopicSubscriber<NarrowEvent<AllEvents, K>>>;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Filter for global handlers. Use `include` OR `exclude` (mutually exclusive).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* { include: ["OrderCreated", "OrderShipped"] } // Only these topics
|
|
51
|
+
* { exclude: ["NotificationAdded"] } // All except these
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
type TopicFilter<Topic extends string> = {
|
|
55
|
+
include: Topic[];
|
|
56
|
+
} | {
|
|
57
|
+
exclude: Topic[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Configuration for a global handler that listens to multiple topics.
|
|
61
|
+
*/
|
|
62
|
+
type GlobalSubscriberConfig<AllEvents extends GenericEvent<string, unknown, DefaultContext>> = {
|
|
63
|
+
/** Unique identifier for retry tracking. */
|
|
64
|
+
subscriptionId: SubscriptionId;
|
|
65
|
+
/** Async handler receiving any event matching the filter. */
|
|
66
|
+
handler: (event: AllEvents) => Promise<void>;
|
|
67
|
+
/** Optional topic filter. Without filter, receives all topics. */
|
|
68
|
+
filter?: TopicFilter<AllEvents["topic"]>;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Subscribe all handlers from a topic subscription map to an event bus.
|
|
72
|
+
* Standalone function for use with any EventBus implementation.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const subscriptions: TopicSubscriptions<MyEvents> = {
|
|
77
|
+
* OrderCreated: [{ subscriptionId: "notify", handler: sendNotification }],
|
|
78
|
+
* OrderShipped: [{ subscriptionId: "track", handler: updateTracking }],
|
|
79
|
+
* };
|
|
80
|
+
* subscribeByTopic(eventBus, subscriptions);
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function subscribeByTopic<AllEvents extends GenericEvent<string, unknown, DefaultContext>>(eventBus: EventBus<AllEvents>, subscriptions: TopicSubscriptions<AllEvents>): void;
|
|
84
|
+
/**
|
|
85
|
+
* Subscribe a global handler to multiple topics with optional filtering.
|
|
86
|
+
* Standalone function for use with any EventBus implementation.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // Audit log for all events except noisy ones
|
|
91
|
+
* subscribeGlobalToTopics(eventBus, subscriptions, {
|
|
92
|
+
* subscriptionId: "audit-log",
|
|
93
|
+
* handler: async (event) => auditLog.record(event),
|
|
94
|
+
* filter: { exclude: ["NotificationAdded"] },
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // Analytics for specific topics only
|
|
98
|
+
* subscribeGlobalToTopics(eventBus, subscriptions, {
|
|
99
|
+
* subscriptionId: "order-analytics",
|
|
100
|
+
* handler: async (event) => analytics.track(event),
|
|
101
|
+
* filter: { include: ["OrderCreated", "OrderShipped"] },
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare function subscribeGlobalToTopics<AllEvents extends GenericEvent<string, unknown, DefaultContext>>(eventBus: EventBus<AllEvents>, subscriptions: TopicSubscriptions<AllEvents>, config: GlobalSubscriberConfig<AllEvents>): void;
|
|
106
|
+
|
|
107
|
+
export { type GlobalSubscriberConfig, type NarrowEvent, type TopicFilter, type TopicSubscriber, type TopicSubscriptions, subscribeByTopic, subscribeGlobalToTopics };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function subscribeByTopic(eventBus, subscriptions) {
|
|
2
|
+
for (const topic of Object.keys(subscriptions)) {
|
|
3
|
+
const handlers = subscriptions[topic];
|
|
4
|
+
for (const { subscriptionId, handler } of handlers) {
|
|
5
|
+
eventBus.subscribe({
|
|
6
|
+
topic,
|
|
7
|
+
subscriptionId,
|
|
8
|
+
callBack: handler
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function subscribeGlobalToTopics(eventBus, subscriptions, config) {
|
|
14
|
+
const allTopics = Object.keys(subscriptions);
|
|
15
|
+
let topicsToSubscribe;
|
|
16
|
+
const filter = config.filter;
|
|
17
|
+
if (!filter) {
|
|
18
|
+
topicsToSubscribe = allTopics;
|
|
19
|
+
} else if ("include" in filter) {
|
|
20
|
+
topicsToSubscribe = filter.include;
|
|
21
|
+
} else {
|
|
22
|
+
topicsToSubscribe = allTopics.filter((t) => !filter.exclude.includes(t));
|
|
23
|
+
}
|
|
24
|
+
for (const topic of topicsToSubscribe) {
|
|
25
|
+
eventBus.subscribe({
|
|
26
|
+
topic,
|
|
27
|
+
subscriptionId: config.subscriptionId,
|
|
28
|
+
callBack: config.handler
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
subscribeByTopic,
|
|
34
|
+
subscribeGlobalToTopics
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=subscriptions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/subscriptions.ts"],"sourcesContent":["import type { EventBus } from './ports/EventBus.mjs';\nimport type { DefaultContext, GenericEvent, SubscriptionId } from './types.mjs';\n\n/**\n * Extracts a specific event type from a union of events by topic.\n *\n * @example\n * ```typescript\n * type MyEvents =\n * | GenericEvent<\"OrderCreated\", { orderId: string }>\n * | GenericEvent<\"OrderShipped\", { trackingNumber: string }>;\n *\n * type OrderCreatedEvent = NarrowEvent<MyEvents, \"OrderCreated\">;\n * // Result: GenericEvent<\"OrderCreated\", { orderId: string }>\n * ```\n */\nexport type NarrowEvent<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n Topic extends AllEvents[\"topic\"],\n> = Extract<AllEvents, { topic: Topic }>;\n\n/**\n * A subscriber for a specific event topic.\n * The handler receives the full event (not just payload) for access to metadata.\n */\nexport type TopicSubscriber<\n E extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n /** Unique identifier for retry tracking. */\n subscriptionId: SubscriptionId;\n /** Async handler receiving the full event with metadata (id, occurredAt, etc). */\n handler: (event: E) => Promise<void>;\n};\n\n/**\n * Complete subscription map requiring ALL event topics.\n * Forces explicit decision about each topic, documenting intent.\n *\n * @example\n * ```typescript\n * const subscriptions: TopicSubscriptions<MyEvents> = {\n * OrderCreated: [{ subscriptionId: \"notify\", handler: async (e) => {...} }],\n * OrderShipped: [], // Required even if empty\n * };\n * ```\n */\nexport type TopicSubscriptions<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n [K in AllEvents[\"topic\"]]: Array<TopicSubscriber<NarrowEvent<AllEvents, K>>>;\n};\n\n/**\n * Filter for global handlers. Use `include` OR `exclude` (mutually exclusive).\n *\n * @example\n * ```typescript\n * { include: [\"OrderCreated\", \"OrderShipped\"] } // Only these topics\n * { exclude: [\"NotificationAdded\"] } // All except these\n * ```\n */\nexport type TopicFilter<Topic extends string> =\n | { include: Topic[] }\n | { exclude: Topic[] };\n\n/**\n * Configuration for a global handler that listens to multiple topics.\n */\nexport type GlobalSubscriberConfig<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n> = {\n /** Unique identifier for retry tracking. */\n subscriptionId: SubscriptionId;\n /** Async handler receiving any event matching the filter. */\n handler: (event: AllEvents) => Promise<void>;\n /** Optional topic filter. Without filter, receives all topics. */\n filter?: TopicFilter<AllEvents[\"topic\"]>;\n};\n\n/**\n * Subscribe all handlers from a topic subscription map to an event bus.\n * Standalone function for use with any EventBus implementation.\n *\n * @example\n * ```typescript\n * const subscriptions: TopicSubscriptions<MyEvents> = {\n * OrderCreated: [{ subscriptionId: \"notify\", handler: sendNotification }],\n * OrderShipped: [{ subscriptionId: \"track\", handler: updateTracking }],\n * };\n * subscribeByTopic(eventBus, subscriptions);\n * ```\n */\nexport function subscribeByTopic<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n>(\n eventBus: EventBus<AllEvents>,\n subscriptions: TopicSubscriptions<AllEvents>,\n): void {\n for (const topic of Object.keys(subscriptions) as AllEvents[\"topic\"][]) {\n const handlers = subscriptions[topic];\n for (const { subscriptionId, handler } of handlers) {\n eventBus.subscribe({\n topic,\n subscriptionId,\n callBack: handler,\n });\n }\n }\n}\n\n/**\n * Subscribe a global handler to multiple topics with optional filtering.\n * Standalone function for use with any EventBus implementation.\n *\n * @example\n * ```typescript\n * // Audit log for all events except noisy ones\n * subscribeGlobalToTopics(eventBus, subscriptions, {\n * subscriptionId: \"audit-log\",\n * handler: async (event) => auditLog.record(event),\n * filter: { exclude: [\"NotificationAdded\"] },\n * });\n *\n * // Analytics for specific topics only\n * subscribeGlobalToTopics(eventBus, subscriptions, {\n * subscriptionId: \"order-analytics\",\n * handler: async (event) => analytics.track(event),\n * filter: { include: [\"OrderCreated\", \"OrderShipped\"] },\n * });\n * ```\n */\nexport function subscribeGlobalToTopics<\n AllEvents extends GenericEvent<string, unknown, DefaultContext>,\n>(\n eventBus: EventBus<AllEvents>,\n subscriptions: TopicSubscriptions<AllEvents>,\n config: GlobalSubscriberConfig<AllEvents>,\n): void {\n const allTopics = Object.keys(subscriptions) as AllEvents[\"topic\"][];\n\n let topicsToSubscribe: AllEvents[\"topic\"][];\n\n const filter = config.filter;\n if (!filter) {\n topicsToSubscribe = allTopics;\n } else if (\"include\" in filter) {\n topicsToSubscribe = filter.include;\n } else {\n topicsToSubscribe = allTopics.filter((t) => !filter.exclude.includes(t));\n }\n\n for (const topic of topicsToSubscribe) {\n eventBus.subscribe({\n topic,\n subscriptionId: config.subscriptionId,\n callBack: config.handler,\n });\n }\n}\n"],"mappings":"AA4FO,SAAS,iBAGd,UACA,eACM;AACN,aAAW,SAAS,OAAO,KAAK,aAAa,GAA2B;AACtE,UAAM,WAAW,cAAc,KAAK;AACpC,eAAW,EAAE,gBAAgB,QAAQ,KAAK,UAAU;AAClD,eAAS,UAAU;AAAA,QACjB;AAAA,QACA;AAAA,QACA,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAuBO,SAAS,wBAGd,UACA,eACA,QACM;AACN,QAAM,YAAY,OAAO,KAAK,aAAa;AAE3C,MAAI;AAEJ,QAAM,SAAS,OAAO;AACtB,MAAI,CAAC,QAAQ;AACX,wBAAoB;AAAA,EACtB,WAAW,aAAa,QAAQ;AAC9B,wBAAoB,OAAO;AAAA,EAC7B,OAAO;AACL,wBAAoB,UAAU,OAAO,CAAC,MAAM,CAAC,OAAO,QAAQ,SAAS,CAAC,CAAC;AAAA,EACzE;AAEA,aAAW,SAAS,mBAAmB;AACrC,aAAS,UAAU;AAAA,MACjB;AAAA,MACA,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AACF;","names":[]}
|
package/dist/types.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Branded type helper for nominal typing.\n * Adds a phantom type property to distinguish between structurally identical types.\n */\nexport type Flavor<T, FlavorT> = T & {\n _type?: FlavorT;\n};\n\n/** Unique identifier for an event subscription. */\nexport type SubscriptionId = Flavor<string, \"SubscriptionId\">;\n\n/** Unique identifier for a user who triggered an event. */\nexport type UserId = Flavor<string, \"UserId\">;\n\n/** Unique identifier for an event. */\nexport type EventId = Flavor<string, \"EventId\">;\n\n/**\n * Records a subscription failure during event publication.\n * Contains the subscription that failed and error details for debugging.\n */\nexport type EventFailure = {\n subscriptionId: SubscriptionId;\n errorMessage: string;\n /** Stack trace captured when the subscription callback threw. */\n stack?: string;\n};\n\n/**\n * Records a single publication attempt for an event.\n * Tracks which subscribers were notified and any failures that occurred.\n */\nexport type EventPublication = {\n publishedAt: Date;\n /** All subscription IDs that were attempted in this publication. */\n publishedSubscribers: SubscriptionId[];\n /** Subscriptions that failed during this publication attempt. */\n failures
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Branded type helper for nominal typing.\n * Adds a phantom type property to distinguish between structurally identical types.\n */\nexport type Flavor<T, FlavorT> = T & {\n _type?: FlavorT;\n};\n\n/** Unique identifier for an event subscription. */\nexport type SubscriptionId = Flavor<string, \"SubscriptionId\">;\n\n/** Unique identifier for a user who triggered an event. */\nexport type UserId = Flavor<string, \"UserId\">;\n\n/** Unique identifier for an event. */\nexport type EventId = Flavor<string, \"EventId\">;\n\n/**\n * Records a subscription failure during event publication.\n * Contains the subscription that failed and error details for debugging.\n */\nexport type EventFailure = {\n subscriptionId: SubscriptionId;\n errorMessage: string;\n /** Stack trace captured when the subscription callback threw. */\n stack?: string;\n};\n\n/**\n * Records a single publication attempt for an event.\n * Tracks which subscribers were notified and any failures that occurred.\n */\nexport type EventPublication = {\n publishedAt: Date;\n /** All subscription IDs that were attempted in this publication. */\n publishedSubscribers: SubscriptionId[];\n /** Subscriptions that failed during this publication attempt. */\n failures?: EventFailure[];\n};\n\n/**\n * Event lifecycle status.\n * - `never-published`: New event, not yet processed by crawler\n * - `in-process`: Currently being published by crawler\n * - `published`: Successfully delivered to all subscribers\n * - `failed-but-will-retry`: Some subscribers failed, will retry\n * - `quarantined`: Exceeded max retries, requires manual intervention\n * - `to-republish`: Force republish to all subscribers (manual trigger)\n */\nexport type EventStatus =\n | \"never-published\"\n | \"to-republish\"\n | \"in-process\"\n | \"published\"\n | \"failed-but-will-retry\"\n | \"quarantined\";\n\n/** Context type constraint - must be a string record or undefined. */\nexport type DefaultContext = Record<string, string> | undefined;\n\n/**\n * Generic event type for the outbox pattern.\n * Events are persisted in the same transaction as domain changes,\n * then asynchronously published to subscribers.\n *\n * @typeParam T - Event topic/type string literal\n * @typeParam P - Event payload type\n * @typeParam C - Optional context for filtering (e.g., tenant ID)\n *\n * @example\n * ```typescript\n * type MyEvents =\n * | GenericEvent<\"UserCreated\", { userId: string; email: string }>\n * | GenericEvent<\"OrderPlaced\", { orderId: string }, { tenantId: string }>;\n * ```\n */\nexport type GenericEvent<\n T extends string,\n P,\n C extends DefaultContext = undefined,\n> = {\n /** Unique event identifier. */\n id: EventId;\n /** When the event occurred in the domain. */\n occurredAt: Date;\n /** Event type/topic for routing to subscribers. */\n topic: T;\n /** Event-specific data. */\n payload: P;\n /** Current lifecycle status. */\n status: EventStatus;\n /** History of publication attempts. */\n publications: EventPublication[];\n /** User who triggered the action that created this event. */\n triggeredByUserId: UserId;\n /** Optional priority for processing order (not yet implemented in crawler). */\n priority?: number;\n /** Optional context for filtering events (e.g., by tenant). */\n context?: C;\n};\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/types.d.cts
CHANGED
|
@@ -30,7 +30,7 @@ type EventPublication = {
|
|
|
30
30
|
/** All subscription IDs that were attempted in this publication. */
|
|
31
31
|
publishedSubscribers: SubscriptionId[];
|
|
32
32
|
/** Subscriptions that failed during this publication attempt. */
|
|
33
|
-
failures
|
|
33
|
+
failures?: EventFailure[];
|
|
34
34
|
};
|
|
35
35
|
/**
|
|
36
36
|
* Event lifecycle status.
|
package/dist/types.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ type EventPublication = {
|
|
|
30
30
|
/** All subscription IDs that were attempted in this publication. */
|
|
31
31
|
publishedSubscribers: SubscriptionId[];
|
|
32
32
|
/** Subscriptions that failed during this publication attempt. */
|
|
33
|
-
failures
|
|
33
|
+
failures?: EventFailure[];
|
|
34
34
|
};
|
|
35
35
|
/**
|
|
36
36
|
* Event lifecycle status.
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "The purpose of this repository is to make it easy to setup event driven architecture using outbox pattern",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.5.0",
|
|
7
7
|
"main": "./dist/index.mjs",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { makeCreateNewEvent } from "../../createNewEvent.ts";
|
|
2
2
|
import type { EventBus } from "../../ports/EventBus.ts";
|
|
3
3
|
import type { WithEventsUow } from "../../ports/EventRepository.ts";
|
|
4
|
+
import {
|
|
5
|
+
type GlobalSubscriberConfig,
|
|
6
|
+
subscribeByTopic,
|
|
7
|
+
subscribeGlobalToTopics,
|
|
8
|
+
type TopicSubscriptions,
|
|
9
|
+
} from "../../subscriptions.ts";
|
|
4
10
|
import type {
|
|
5
11
|
DefaultContext,
|
|
6
12
|
EventId,
|
|
@@ -66,7 +72,7 @@ export const createInMemoryEventBus = <
|
|
|
66
72
|
const lastPublication = event.publications.reduce((latest, current) =>
|
|
67
73
|
current.publishedAt > latest.publishedAt ? current : latest,
|
|
68
74
|
);
|
|
69
|
-
const failedSubscriptionIds = lastPublication.failures.map(
|
|
75
|
+
const failedSubscriptionIds = (lastPublication.failures ?? []).map(
|
|
70
76
|
(failure) => failure.subscriptionId,
|
|
71
77
|
);
|
|
72
78
|
|
|
@@ -86,7 +92,6 @@ export const createInMemoryEventBus = <
|
|
|
86
92
|
event.publications.push({
|
|
87
93
|
publishedAt,
|
|
88
94
|
publishedSubscribers: [],
|
|
89
|
-
failures: [],
|
|
90
95
|
});
|
|
91
96
|
event.status = "published";
|
|
92
97
|
await withUow(async (uow) => {
|
|
@@ -127,7 +132,7 @@ export const createInMemoryEventBus = <
|
|
|
127
132
|
publishedSubscribers: subscriptionIdsToPublish.map(
|
|
128
133
|
(id) => id as SubscriptionId,
|
|
129
134
|
),
|
|
130
|
-
failures,
|
|
135
|
+
...(failures.length > 0 && { failures }),
|
|
131
136
|
},
|
|
132
137
|
];
|
|
133
138
|
|
|
@@ -161,5 +166,59 @@ export const createInMemoryEventBus = <
|
|
|
161
166
|
},
|
|
162
167
|
};
|
|
163
168
|
|
|
164
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Identity function for type inference when defining subscription maps.
|
|
171
|
+
* Ensures all topics are covered and handlers have correct payload types.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const subscriptions = defineSubscriptions({
|
|
176
|
+
* OrderCreated: [{ subscriptionId: "notify", handler: async (e) => {...} }],
|
|
177
|
+
* OrderShipped: [], // Required even if empty
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
const defineSubscriptions = (
|
|
182
|
+
subscriptions: TopicSubscriptions<Event>,
|
|
183
|
+
): TopicSubscriptions<Event> => subscriptions;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Subscribe all handlers from a topic subscription map to this event bus.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```typescript
|
|
190
|
+
* const subscriptions = defineSubscriptions({...});
|
|
191
|
+
* subscribeAll(subscriptions);
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
const subscribeAll = (subscriptions: TopicSubscriptions<Event>): void => {
|
|
195
|
+
subscribeByTopic(eventBus, subscriptions);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Subscribe a global handler to multiple topics with optional filtering.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* subscribeGlobal(subscriptions, {
|
|
204
|
+
* subscriptionId: "audit-log",
|
|
205
|
+
* handler: async (event) => auditLog.record(event),
|
|
206
|
+
* filter: { exclude: ["NotificationAdded"] },
|
|
207
|
+
* });
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
const subscribeGlobal = (
|
|
211
|
+
subscriptions: TopicSubscriptions<Event>,
|
|
212
|
+
config: GlobalSubscriberConfig<Event>,
|
|
213
|
+
): void => {
|
|
214
|
+
subscribeGlobalToTopics(eventBus, subscriptions, config);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
eventBus,
|
|
219
|
+
createNewEvent,
|
|
220
|
+
defineSubscriptions,
|
|
221
|
+
subscribeAll,
|
|
222
|
+
subscribeGlobal,
|
|
223
|
+
};
|
|
165
224
|
};
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { EventBus } from "./ports/EventBus.ts";
|
|
2
|
+
import type { DefaultContext, GenericEvent, SubscriptionId } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts a specific event type from a union of events by topic.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* type MyEvents =
|
|
10
|
+
* | GenericEvent<"OrderCreated", { orderId: string }>
|
|
11
|
+
* | GenericEvent<"OrderShipped", { trackingNumber: string }>;
|
|
12
|
+
*
|
|
13
|
+
* type OrderCreatedEvent = NarrowEvent<MyEvents, "OrderCreated">;
|
|
14
|
+
* // Result: GenericEvent<"OrderCreated", { orderId: string }>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export type NarrowEvent<
|
|
18
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
19
|
+
Topic extends AllEvents["topic"],
|
|
20
|
+
> = Extract<AllEvents, { topic: Topic }>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A subscriber for a specific event topic.
|
|
24
|
+
* The handler receives the full event (not just payload) for access to metadata.
|
|
25
|
+
*/
|
|
26
|
+
export type TopicSubscriber<
|
|
27
|
+
E extends GenericEvent<string, unknown, DefaultContext>,
|
|
28
|
+
> = {
|
|
29
|
+
/** Unique identifier for retry tracking. */
|
|
30
|
+
subscriptionId: SubscriptionId;
|
|
31
|
+
/** Async handler receiving the full event with metadata (id, occurredAt, etc). */
|
|
32
|
+
handler: (event: E) => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Complete subscription map requiring ALL event topics.
|
|
37
|
+
* Forces explicit decision about each topic, documenting intent.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const subscriptions: TopicSubscriptions<MyEvents> = {
|
|
42
|
+
* OrderCreated: [{ subscriptionId: "notify", handler: async (e) => {...} }],
|
|
43
|
+
* OrderShipped: [], // Required even if empty
|
|
44
|
+
* };
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export type TopicSubscriptions<
|
|
48
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
49
|
+
> = {
|
|
50
|
+
[K in AllEvents["topic"]]: Array<TopicSubscriber<NarrowEvent<AllEvents, K>>>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Filter for global handlers. Use `include` OR `exclude` (mutually exclusive).
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* { include: ["OrderCreated", "OrderShipped"] } // Only these topics
|
|
59
|
+
* { exclude: ["NotificationAdded"] } // All except these
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export type TopicFilter<Topic extends string> =
|
|
63
|
+
| { include: Topic[] }
|
|
64
|
+
| { exclude: Topic[] };
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Configuration for a global handler that listens to multiple topics.
|
|
68
|
+
*/
|
|
69
|
+
export type GlobalSubscriberConfig<
|
|
70
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
71
|
+
> = {
|
|
72
|
+
/** Unique identifier for retry tracking. */
|
|
73
|
+
subscriptionId: SubscriptionId;
|
|
74
|
+
/** Async handler receiving any event matching the filter. */
|
|
75
|
+
handler: (event: AllEvents) => Promise<void>;
|
|
76
|
+
/** Optional topic filter. Without filter, receives all topics. */
|
|
77
|
+
filter?: TopicFilter<AllEvents["topic"]>;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Subscribe all handlers from a topic subscription map to an event bus.
|
|
82
|
+
* Standalone function for use with any EventBus implementation.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const subscriptions: TopicSubscriptions<MyEvents> = {
|
|
87
|
+
* OrderCreated: [{ subscriptionId: "notify", handler: sendNotification }],
|
|
88
|
+
* OrderShipped: [{ subscriptionId: "track", handler: updateTracking }],
|
|
89
|
+
* };
|
|
90
|
+
* subscribeByTopic(eventBus, subscriptions);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function subscribeByTopic<
|
|
94
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
95
|
+
>(
|
|
96
|
+
eventBus: EventBus<AllEvents>,
|
|
97
|
+
subscriptions: TopicSubscriptions<AllEvents>,
|
|
98
|
+
): void {
|
|
99
|
+
for (const topic of Object.keys(subscriptions) as AllEvents["topic"][]) {
|
|
100
|
+
const handlers = subscriptions[topic];
|
|
101
|
+
for (const { subscriptionId, handler } of handlers) {
|
|
102
|
+
eventBus.subscribe({
|
|
103
|
+
topic,
|
|
104
|
+
subscriptionId,
|
|
105
|
+
callBack: handler,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Subscribe a global handler to multiple topics with optional filtering.
|
|
113
|
+
* Standalone function for use with any EventBus implementation.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* // Audit log for all events except noisy ones
|
|
118
|
+
* subscribeGlobalToTopics(eventBus, subscriptions, {
|
|
119
|
+
* subscriptionId: "audit-log",
|
|
120
|
+
* handler: async (event) => auditLog.record(event),
|
|
121
|
+
* filter: { exclude: ["NotificationAdded"] },
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* // Analytics for specific topics only
|
|
125
|
+
* subscribeGlobalToTopics(eventBus, subscriptions, {
|
|
126
|
+
* subscriptionId: "order-analytics",
|
|
127
|
+
* handler: async (event) => analytics.track(event),
|
|
128
|
+
* filter: { include: ["OrderCreated", "OrderShipped"] },
|
|
129
|
+
* });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export function subscribeGlobalToTopics<
|
|
133
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
134
|
+
>(
|
|
135
|
+
eventBus: EventBus<AllEvents>,
|
|
136
|
+
subscriptions: TopicSubscriptions<AllEvents>,
|
|
137
|
+
config: GlobalSubscriberConfig<AllEvents>,
|
|
138
|
+
): void {
|
|
139
|
+
const allTopics = Object.keys(subscriptions) as AllEvents["topic"][];
|
|
140
|
+
|
|
141
|
+
let topicsToSubscribe: AllEvents["topic"][];
|
|
142
|
+
|
|
143
|
+
const filter = config.filter;
|
|
144
|
+
if (!filter) {
|
|
145
|
+
topicsToSubscribe = allTopics;
|
|
146
|
+
} else if ("include" in filter) {
|
|
147
|
+
topicsToSubscribe = filter.include;
|
|
148
|
+
} else {
|
|
149
|
+
topicsToSubscribe = allTopics.filter((t) => !filter.exclude.includes(t));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const topic of topicsToSubscribe) {
|
|
153
|
+
eventBus.subscribe({
|
|
154
|
+
topic,
|
|
155
|
+
subscriptionId: config.subscriptionId,
|
|
156
|
+
callBack: config.handler,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type EventPublication = {
|
|
|
35
35
|
/** All subscription IDs that were attempted in this publication. */
|
|
36
36
|
publishedSubscribers: SubscriptionId[];
|
|
37
37
|
/** Subscriptions that failed during this publication attempt. */
|
|
38
|
-
failures
|
|
38
|
+
failures?: EventFailure[];
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
/**
|