@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.
@@ -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
- return { eventBus, createNewEvent };
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 return { eventBus, createNewEvent };\n};\n"],"mappings":"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","names":[]}
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
- return { eventBus, createNewEvent };
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 return { eventBus, createNewEvent };\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/adapters/in-memory/InMemoryEventBus.ts"],"sourcesContent":["import { makeCreateNewEvent } from '../../createNewEvent.mjs';\nimport type { EventBus } from '../../ports/EventBus.mjs';\nimport type { WithEventsUow } from '../../ports/EventRepository.mjs';\nimport {\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
@@ -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
@@ -1,4 +1,5 @@
1
1
  export * from "./adapters/in-memory/index.mjs";
2
2
  export * from "./createEventCrawler.mjs";
3
3
  export * from "./createNewEvent.mjs";
4
+ export * from "./subscriptions.mjs";
4
5
  //# sourceMappingURL=index.mjs.map
@@ -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.4.3",
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
- return { eventBus, createNewEvent };
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
@@ -4,4 +4,5 @@ export * from "./createNewEvent.ts";
4
4
  export type * from "./ports/EventBus.ts";
5
5
  export type * from "./ports/EventQueries.ts";
6
6
  export type * from "./ports/EventRepository.ts";
7
+ export * from "./subscriptions.ts";
7
8
  export type * from "./types.ts";
@@ -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
+ }