@l-etabli/events 0.4.3 → 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 +15 -1
- 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 +18 -1
- 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/package.json +1 -1
- package/src/adapters/in-memory/InMemoryEventBus.ts +61 -1
- package/src/index.ts +1 -0
- package/src/subscriptions.ts +159 -0
|
@@ -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)({
|
|
@@ -118,7 +119,20 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
};
|
|
121
|
-
|
|
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
|
+
};
|
|
122
136
|
};
|
|
123
137
|
// Annotate the CommonJS export names for ESM import in node:
|
|
124
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 });\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
|
|
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({
|
|
@@ -95,7 +99,20 @@ const createInMemoryEventBus = (withUow, options = {}) => {
|
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
};
|
|
98
|
-
|
|
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
|
+
};
|
|
99
116
|
};
|
|
100
117
|
export {
|
|
101
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 });\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
|
|
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/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,
|
|
@@ -160,5 +166,59 @@ export const createInMemoryEventBus = <
|
|
|
160
166
|
},
|
|
161
167
|
};
|
|
162
168
|
|
|
163
|
-
|
|
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
|
+
};
|
|
164
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
|
+
}
|