@relaybus/nestjs 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @relaybus/nestjs
2
2
 
3
+ @relaybus/nestjs is the NestJS module of relaybus, a reliable, storage-agnostic event bus for NestJS and Node.js: declare handlers with `@Subscribe` and publish inside your database transaction, and every committed event reaches every handler at least once.
4
+
3
5
  NestJS integration for [relaybus](../core): one module, one decorator, correct lifecycle.
4
6
 
5
7
  ## Install
@@ -108,6 +110,67 @@ Rules, all enforced at boot (the application fails to start, no silent skips):
108
110
  class and method, so decorators that wrap the method later do not drop them, and subclasses
109
111
  inherit the handlers of their base class.
110
112
 
113
+ ## `@EventConsumer(catalog, options?)`
114
+
115
+ ```ts
116
+ export const OrderEvents = defineCatalog({ OrderPlaced, OrderShipped }, { name: 'OrderEvents' });
117
+
118
+ @Injectable()
119
+ @EventConsumer(OrderEvents, { transactional: true })
120
+ export class ShippingHandlers implements HandlersOf<typeof OrderEvents> {
121
+ onOrderPlaced(event: EventOf<typeof OrderPlaced>, context: HandlerContext) {}
122
+ onOrderShipped(event: EventOf<typeof OrderShipped>) {}
123
+ }
124
+
125
+ RelayBusModule.forRoot({ store, catalogs: [OrderEvents] });
126
+ ```
127
+
128
+ Registers every `on${EventName}` method the class defines, under the stable id
129
+ `ClassName.on${EventName}`, with `this` bound to the provider. `implements HandlersOf<...>` makes
130
+ the compiler reject a class that misses a handler; `PartialHandlersOf<C, 'OrderShipped'>`
131
+ documents a deliberate subset. Options are core's `SubscribeOptions` without `id`, applied to every
132
+ handler of the class.
133
+
134
+ - `catalogs: [OrderEvents]` in the module options is the boot check: if no provider (via
135
+ `@EventConsumer`, `@Subscribe` or a configured route) handles an event, the application fails to
136
+ start with the list of missing events and the methods they expect.
137
+ - It works next to `@Subscribe` in the same class. A `@Subscribe` that would take the same default
138
+ id (`@Subscribe(OrderPlaced) onOrderPlaced()`) fails init naming both; give it an explicit `id`.
139
+ - Same rules as `@Subscribe`: singleton providers only, `useExisting` aliases register once,
140
+ subclasses inherit the decoration and use their own class name in ids.
141
+ - A class decorated with `@EventConsumer` that defines none of the catalog's methods fails init,
142
+ which catches misspelled method names.
143
+ - Declarations are inherited from base classes; a subclass that declares the same catalog again
144
+ replaces the inherited declaration, and one catalog declared twice on one class throws.
145
+
146
+ ### Microservices: consuming part of a shared catalog
147
+
148
+ ```ts
149
+ // shipping service
150
+ @Injectable()
151
+ @EventConsumer(OrderEvents, { events: ['OrderPlaced'] })
152
+ export class ShippingHandlers {
153
+ onOrderPlaced(event: EventOf<typeof OrderPlaced>) {}
154
+ }
155
+
156
+ RelayBusModule.forRoot({
157
+ store,
158
+ transports: [rabbit],
159
+ subscriptions: [{ transport: 'rabbit', destinations: ['orders'] }],
160
+ routes: [{ event: OrderShipped, transport: 'rabbit', destination: 'shipments' }],
161
+ catalogs: [
162
+ consume(OrderEvents, { events: ['OrderPlaced'] }),
163
+ produce(OrderEvents, { events: ['OrderShipped'] }),
164
+ ],
165
+ });
166
+ ```
167
+
168
+ - With `events`, exactly those methods are registered (other `on${EventName}` methods are
169
+ ignored), and a class that misses one of them does not compile and fails init.
170
+ - `consume(...)` fails the boot when a consumed event has no handler in the application;
171
+ `produce(...)` fails it when a produced event has no route. See `@relaybus/core`, "Microservices:
172
+ shared catalogs".
173
+
111
174
  ## Lifecycle
112
175
 
113
176
  1. `onModuleInit`: handlers are discovered and registered on the bus.
@@ -236,3 +299,28 @@ await moduleRef.close();
236
299
  test classes.
237
300
  - **App hangs on shutdown:** a handler ignores `context.signal`; it is waited for until
238
301
  `dispatcher.shutdownTimeout`.
302
+
303
+ ## When to use @relaybus/nestjs, and when not to
304
+
305
+ Use it when your service is a NestJS 11 or 12 application and you want decorator-based handlers, lifecycle integration and a health check. It needs `@relaybus/core` and a store; for other frameworks use `@relaybus/core` directly.
306
+
307
+ Documentation: <https://berkcansavur.github.io/relaybus/packages/nestjs>
308
+
309
+ ### relaybus vs alternatives
310
+
311
+ <!-- relaybus:snippet:comparison -->
312
+
313
+ | | What it is | Survives a restart | Written in your database transaction | Choose it when |
314
+ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
315
+ | **relaybus** | Event bus built on the transactional outbox: PostgreSQL and MongoDB stores, Kafka and RabbitMQ transports, per-handler deliveries | Yes, events live in your database | Yes | Events must never be lost or phantom, and handlers need retries, dead letters and ordering keys |
316
+ | [`@nestjs/event-emitter`](https://docs.nestjs.com/techniques/events) | In-process event emitter built on `eventemitter2` | No persistence is documented | No | Simple in-process notifications where losing an event on restart is acceptable |
317
+ | [`@nestjs/cqrs`](https://docs.nestjs.com/recipes/cqrs) | CommandBus, QueryBus, EventBus and sagas; the docs say sagas and event handlers run in memory | No built-in persistence | No | You want CQRS structure; it can sit on top of an outbox |
318
+ | [`@nestjs/outbox`](https://github.com/nestjs/outbox) | Transactional outbox module from the NestJS organisation: PostgreSQL, MySQL and in-memory stores, relay with leases and retries, dead-letter table, inbox | Yes with the database stores | Yes | You need MySQL, or a NestJS-organisation module; compare features with relaybus before choosing |
319
+ | [BullMQ](https://docs.bullmq.io/) | Job queue on Redis with retries, delayed jobs and flows; at-least-once in the worst case | Yes, in Redis | No, jobs are stored in Redis and cannot join a SQL or MongoDB transaction | Your stack is Redis-based and background jobs matter more than event atomicity |
320
+ | [pg-boss](https://github.com/timgit/pg-boss) | Job queue on PostgreSQL using `SKIP LOCKED`, with retries, dead letter queues, scheduling and pub/sub; jobs can be created in your own transaction | Yes, in PostgreSQL | Yes, through its database adapters | You want a PostgreSQL job queue with scheduling; relaybus is an event bus with per-handler deliveries and transports |
321
+ | Kafka client directly | A distributed log; the idempotent producer and transactions give exactly-once within Kafka | Yes, in Kafka | No, producing to Kafka is a second write | You consume and produce only inside Kafka; add an outbox when a database write must be announced |
322
+
323
+ This table summarises the vendors' own documentation as of October 2026; check their docs for current details.
324
+ Full comparison and sources: <https://berkcansavur.github.io/relaybus/comparison>.
325
+
326
+ <!-- relaybus:end -->
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ require('reflect-metadata');
4
+ var core = require('@relaybus/core');
3
5
  var common = require('@nestjs/common');
4
6
  var core$1 = require('@nestjs/core');
5
- var core = require('@relaybus/core');
6
- require('reflect-metadata');
7
7
 
8
8
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
9
  var __decorateClass = (decorators, target, key, kind) => {
@@ -14,6 +14,42 @@ var __decorateClass = (decorators, target, key, kind) => {
14
14
  return result;
15
15
  };
16
16
  var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
17
+ var EVENT_CONSUMER_METADATA_KEY = /* @__PURE__ */ Symbol.for("relaybus.nestjs.event-consumer");
18
+ function EventConsumer(catalog, {
19
+ events,
20
+ ...options
21
+ } = {}) {
22
+ const { name } = core.describeCatalog(catalog);
23
+ const selectedEntries = events === void 0 ? void 0 : core.consume(catalog, { events }).entries;
24
+ return (target) => {
25
+ const existing = Reflect.getOwnMetadata(EVENT_CONSUMER_METADATA_KEY, target) ?? [];
26
+ if (existing.some((metadata) => metadata.catalog === catalog)) {
27
+ throw new core.ConfigurationError(
28
+ `@EventConsumer(${name ?? "catalog"}) is declared twice on ${target.name}; merge the declarations into one`
29
+ );
30
+ }
31
+ Reflect.defineMetadata(
32
+ EVENT_CONSUMER_METADATA_KEY,
33
+ [...existing, { catalog, options, selectedEntries }],
34
+ target
35
+ );
36
+ };
37
+ }
38
+ function readEventConsumerMetadata(target) {
39
+ const lineage = [];
40
+ for (let current = target; current !== Function.prototype; ) {
41
+ lineage.unshift(current);
42
+ current = Object.getPrototypeOf(current);
43
+ }
44
+ const byCatalog = /* @__PURE__ */ new Map();
45
+ for (const type of lineage) {
46
+ const own = Reflect.getOwnMetadata(EVENT_CONSUMER_METADATA_KEY, type) ?? [];
47
+ for (const metadata of own) {
48
+ byCatalog.set(metadata.catalog, metadata);
49
+ }
50
+ }
51
+ return [...byCatalog.values()];
52
+ }
17
53
  var SUBSCRIBE_METADATA_KEY = /* @__PURE__ */ Symbol.for("relaybus.nestjs.subscribe");
18
54
  function Subscribe(event, options = {}) {
19
55
  return (target, propertyKey) => {
@@ -43,6 +79,7 @@ exports.RelayBusHandlerExplorer = class RelayBusHandlerExplorer {
43
79
  onModuleInit() {
44
80
  const registeredBy = /* @__PURE__ */ new Map();
45
81
  const visited = /* @__PURE__ */ new Map();
82
+ const consumers = /* @__PURE__ */ new WeakSet();
46
83
  for (const wrapper of this.discovery.getProviders()) {
47
84
  if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {
48
85
  this.assertNoHandlers(wrapper);
@@ -82,6 +119,47 @@ exports.RelayBusHandlerExplorer = class RelayBusHandlerExplorer {
82
119
  );
83
120
  }
84
121
  }
122
+ if (!consumers.has(instance)) {
123
+ consumers.add(instance);
124
+ this.registerEventConsumers(instance, className, registeredBy);
125
+ }
126
+ }
127
+ }
128
+ registerEventConsumers(instance, className, registeredBy) {
129
+ const lookup = instance;
130
+ for (const { catalog, options, selectedEntries } of readEventConsumerMetadata(
131
+ instance.constructor
132
+ )) {
133
+ const { name, entries } = core.describeCatalog(catalog);
134
+ const label = name ?? "catalog";
135
+ const handlers = selectedEntries ?? entries.filter((entry) => isMethod(lookup, entry));
136
+ const missing = handlers.filter((entry) => !isMethod(lookup, entry));
137
+ if (missing.length > 0) {
138
+ throw new core.ConfigurationError(
139
+ `${className} uses @EventConsumer(${label}) but lacks the handler methods of events it consumes: ${missing.map((entry) => entry.handlerName).join(", ")}`
140
+ );
141
+ }
142
+ if (handlers.length === 0) {
143
+ throw new core.ConfigurationError(
144
+ `${className} uses @EventConsumer(${label}) but defines none of its handler methods (${entries.map((entry) => entry.handlerName).join(", ")})`
145
+ );
146
+ }
147
+ for (const entry of handlers) {
148
+ const id = `${className}.${entry.handlerName}`;
149
+ const previous = registeredBy.get(id);
150
+ if (previous !== void 0) {
151
+ throw new core.ConfigurationError(
152
+ `Handler id '${id}' of @EventConsumer(${label}) is already used by ${previous}; give the @Subscribe a unique { id } or drop it`
153
+ );
154
+ }
155
+ registeredBy.set(id, `${className}.${entry.handlerName}`);
156
+ const method = lookup[entry.handlerName];
157
+ this.bus.subscribe(
158
+ entry.eventClass,
159
+ (envelope, context) => method.call(instance, envelope, context),
160
+ { ...options, id }
161
+ );
162
+ }
85
163
  }
86
164
  }
87
165
  subscribedMethods(prototype) {
@@ -101,6 +179,11 @@ exports.RelayBusHandlerExplorer = class RelayBusHandlerExplorer {
101
179
  */
102
180
  assertNoHandlers(wrapper) {
103
181
  const type = wrapper.isFactory ? wrapper.token : wrapper.metatype;
182
+ if (typeof type === "function" && readEventConsumerMetadata(type).length > 0) {
183
+ throw new core.ConfigurationError(
184
+ `${type.name} uses @EventConsumer but its provider is request-scoped or transient (directly or through a dependency); handlers must be singleton providers`
185
+ );
186
+ }
104
187
  const prototype = typeof type === "function" ? type.prototype : null;
105
188
  const [first] = prototype === null ? [] : this.subscribedMethods(prototype);
106
189
  if (first !== void 0) {
@@ -116,6 +199,9 @@ exports.RelayBusHandlerExplorer = __decorateClass([
116
199
  __decorateParam(1, common.Inject(core$1.MetadataScanner)),
117
200
  __decorateParam(2, common.Inject(core.RelayBus))
118
201
  ], exports.RelayBusHandlerExplorer);
202
+ function isMethod(instance, entry) {
203
+ return typeof instance[entry.handlerName] === "function";
204
+ }
119
205
  var {
120
206
  ConfigurableModuleClass: RelayBusConfigurableModule,
121
207
  MODULE_OPTIONS_TOKEN: RELAYBUS_MODULE_OPTIONS
@@ -206,6 +292,7 @@ Object.defineProperty(exports, "RelayBus", {
206
292
  enumerable: true,
207
293
  get: function () { return core.RelayBus; }
208
294
  });
295
+ exports.EventConsumer = EventConsumer;
209
296
  exports.Subscribe = Subscribe;
210
297
  //# sourceMappingURL=index.cjs.map
211
298
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/subscribe.decorator.ts","../src/handler-explorer.ts","../src/module-options.ts","../src/relay-bus-health.ts","../src/relay-bus-lifecycle.ts","../src/relay-bus.module.ts"],"names":["RelayBusHandlerExplorer","ConfigurationError","Injectable","DiscoveryService","MetadataScanner","RelayBus","ConfigurableModuleBuilder","RelayBusHealth","systemClock","Inject","RelayBusLifecycle","RelayBusModule","Module","DiscoveryModule","Logger"],"mappings":"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","file":"index.cjs","sourcesContent":["import 'reflect-metadata';\nimport type { EventClass, EventEnvelope, HandlerContext, SubscribeOptions } from '@relaybus/core';\n\n/**\n * Options of {@link Subscribe}: the core {@link SubscribeOptions}, except that\n * `id` is optional and defaults to `ClassName.methodName`.\n */\nexport type SubscribeDecoratorOptions = Omit<SubscribeOptions, 'id'> & {\n /**\n * Stable id of the handler. Deliveries are recorded under it, so renaming it\n * (or the class or method it is derived from) orphans pending deliveries.\n * Default: `${ClassName}.${methodName}`. Required when one method carries\n * several `@Subscribe` decorators.\n */\n readonly id?: string;\n};\n\n/** What one `@Subscribe` decorator recorded on a method. */\nexport interface SubscribeMetadata {\n readonly event: EventClass<unknown> | string;\n readonly options: SubscribeDecoratorOptions;\n}\n\nconst SUBSCRIBE_METADATA_KEY = Symbol.for('relaybus.nestjs.subscribe');\n\ntype HandlerMethod<TPayload> = (\n event: EventEnvelope<TPayload>,\n context: HandlerContext<never>,\n) => unknown;\n\ntype SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(\n target: object,\n propertyKey: string | symbol,\n descriptor: TypedPropertyDescriptor<TMethod>,\n) => void;\n\n/**\n * Subscribes a provider method to an event class or type. The method is\n * registered with `bus.subscribe` when the module initialises, with `this`\n * bound to the provider instance, and receives `(envelope, context)`.\n *\n * The provider must be a singleton (default scope) and belong to any module of\n * the application. The decorator name differs from `@nestjs/event-emitter`'s\n * `@OnEvent` so both can be used in one application.\n *\n * ```ts\n * @Injectable()\n * export class ShippingHandlers {\n * @Subscribe(OrderPlaced, { transactional: true })\n * async onOrderPlaced(event: EventEnvelope<{ orderId: string }>, context: HandlerContext) {}\n * }\n * ```\n */\nexport function Subscribe<TPayload>(\n event: EventClass<TPayload>,\n options?: SubscribeDecoratorOptions,\n): SubscribeDecorator<TPayload>;\nexport function Subscribe(\n eventType: string,\n options?: SubscribeDecoratorOptions,\n): SubscribeDecorator<never>;\nexport function Subscribe(\n event: EventClass<unknown> | string,\n options: SubscribeDecoratorOptions = {},\n): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void {\n return (target, propertyKey) => {\n // Keyed by (prototype, property) rather than stored on the method function, so a\n // decorator that later replaces the method does not drop the subscription.\n const existing =\n (Reflect.getOwnMetadata(SUBSCRIBE_METADATA_KEY, target, propertyKey) as\n SubscribeMetadata[] | undefined) ?? [];\n Reflect.defineMetadata(\n SUBSCRIBE_METADATA_KEY,\n [...existing, { event, options }],\n target,\n propertyKey,\n );\n };\n}\n\n/**\n * Reads the `@Subscribe` declarations of a method, in declaration order. The\n * prototype chain is searched, so a subclass inherits the declarations of the\n * nearest ancestor that declares them.\n */\nexport function readSubscribeMetadata(\n prototype: object,\n propertyKey: string | symbol,\n): readonly SubscribeMetadata[] {\n return (\n (Reflect.getMetadata(SUBSCRIBE_METADATA_KEY, prototype, propertyKey) as\n SubscribeMetadata[] | undefined) ?? []\n );\n}\n","import { Inject, Injectable, type OnModuleInit } from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper.js';\nimport { ConfigurationError, RelayBus, type EventHandler } from '@relaybus/core';\nimport { readSubscribeMetadata, type SubscribeMetadata } from './subscribe.decorator.js';\n\ninterface SubscribedMethod {\n readonly methodName: string;\n readonly declarations: readonly SubscribeMetadata[];\n}\n\n/**\n * Finds every `@Subscribe` method of every singleton provider in the\n * application and registers it on the bus before the bus starts. Providers\n * that are request-scoped or transient, directly or through a dependency, and\n * declare `@Subscribe` methods fail the boot. A request-scoped provider whose\n * token is neither a class nor an instance carrying the decorated prototype\n * (for example a string-token factory) cannot be inspected and is skipped.\n */\n@Injectable()\nexport class RelayBusHandlerExplorer implements OnModuleInit {\n constructor(\n @Inject(DiscoveryService) private readonly discovery: DiscoveryService,\n @Inject(MetadataScanner) private readonly metadataScanner: MetadataScanner,\n @Inject(RelayBus) private readonly bus: RelayBus,\n ) {}\n\n onModuleInit(): void {\n const registeredBy = new Map<string, string>();\n const visited = new Map<object, Set<string>>();\n for (const wrapper of this.discovery.getProviders()) {\n if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {\n this.assertNoHandlers(wrapper);\n continue;\n }\n const instance = wrapper.instance as unknown;\n if (typeof instance !== 'object' || instance === null) {\n continue;\n }\n const prototype = Object.getPrototypeOf(instance) as object | null;\n if (prototype === null) {\n continue;\n }\n const className = instance.constructor.name;\n for (const { methodName, declarations } of this.subscribedMethods(prototype)) {\n // A `useExisting` alias exposes the same instance under another token.\n const seen = visited.get(instance) ?? new Set<string>();\n visited.set(instance, seen);\n if (seen.has(methodName)) {\n continue;\n }\n seen.add(methodName);\n const method = (instance as Record<string, unknown>)[methodName] as (\n ...args: Parameters<EventHandler>\n ) => unknown;\n const location = `${className}.${methodName}`;\n for (const { event, options } of declarations) {\n const id = options.id ?? location;\n const previous = registeredBy.get(id);\n if (previous !== undefined) {\n throw new ConfigurationError(\n `Handler id '${id}' is used by both ${previous} and ${location}; ` +\n `pass a unique { id } to @Subscribe`,\n );\n }\n registeredBy.set(id, location);\n // Core resolves both classes and type strings; the overloads cannot express the union.\n this.bus.subscribe(\n event as never,\n (envelope, context) => method.call(instance, envelope, context),\n { ...options, id },\n );\n }\n }\n }\n }\n\n private subscribedMethods(prototype: object): SubscribedMethod[] {\n const methods: SubscribedMethod[] = [];\n for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {\n const declarations = readSubscribeMetadata(prototype, methodName);\n if (declarations.length > 0) {\n methods.push({ methodName, declarations });\n }\n }\n return methods;\n }\n\n /**\n * Request-scoped and transient wrappers have no usable instance (a factory\n * wrapper has none at all), so the decorated prototype is taken from the\n * provider class, or from the provider token for factories when it is a class.\n */\n private assertNoHandlers(wrapper: InstanceWrapper): void {\n const type = (wrapper.isFactory ? wrapper.token : wrapper.metatype) as unknown;\n const prototype = typeof type === 'function' ? (type.prototype as object) : null;\n const [first] = prototype === null ? [] : this.subscribedMethods(prototype);\n if (first !== undefined) {\n throw new ConfigurationError(\n `${(prototype as { constructor: { name: string } }).constructor.name}.${first.methodName} ` +\n `uses @Subscribe but its provider is request-scoped or transient ` +\n `(directly or through a dependency); handlers must be singleton providers`,\n );\n }\n }\n}\n","import { ConfigurableModuleBuilder } from '@nestjs/common';\nimport type { RelayBusOptions } from '@relaybus/core';\n\n/**\n * Options of {@link RelayBusModule}: everything {@link RelayBusOptions} accepts\n * except `logger`, which is always a Nest `Logger` with context `RelayBus`\n * (configure it with `app.useLogger`). Only `store` is required.\n */\nexport type RelayBusModuleOptions<TTransaction = unknown> = Omit<\n RelayBusOptions<TTransaction>,\n 'logger'\n>;\n\n/** Module-level extras accepted next to the options by `forRoot` and `forRootAsync`. */\nexport interface RelayBusModuleExtras {\n /** Make `RelayBus` and `RelayBusHealth` injectable everywhere. Default true. */\n readonly isGlobal?: boolean;\n}\n\n/** Implemented by classes passed as `useClass` / `useExisting` to `forRootAsync`. */\nexport interface RelayBusModuleOptionsFactory {\n create(): Promise<RelayBusModuleOptions> | RelayBusModuleOptions;\n}\n\nexport const {\n ConfigurableModuleClass: RelayBusConfigurableModule,\n MODULE_OPTIONS_TOKEN: RELAYBUS_MODULE_OPTIONS,\n} = new ConfigurableModuleBuilder<RelayBusModuleOptions>()\n .setClassMethodName('forRoot')\n .setExtras<RelayBusModuleExtras>({ isGlobal: true }, (definition, extras) => ({\n ...definition,\n global: extras.isGlobal === true,\n }))\n .build();\n","import { Inject, Injectable } from '@nestjs/common';\nimport { RelayBus, systemClock, type EventStoreStatistics } from '@relaybus/core';\nimport { RELAYBUS_MODULE_OPTIONS, type RelayBusModuleOptions } from './module-options.js';\n\n/** Result of {@link RelayBusHealth.check}. */\nexport interface RelayBusHealthReport {\n /**\n * `down` when the bus is not running, a transport reports itself not ready\n * (dead Kafka consumer, disconnected RabbitMQ) or the store cannot be queried.\n */\n readonly status: 'up' | 'down';\n readonly running: boolean;\n /** `bus.isReady()`: running and every transport ready. */\n readonly ready: boolean;\n /** Number of registered handlers and routes. */\n readonly handlers: number;\n /** Delivery counts; absent when the bus is not running or the store failed. */\n readonly statistics?: EventStoreStatistics;\n /** Milliseconds the oldest due delivery has waited; 0 when nothing is due. */\n readonly lagMilliseconds?: number;\n /** Message of the store error, when the statistics query failed. */\n readonly error?: string;\n}\n\n/**\n * Health probe of the bus without a dependency on `@nestjs/terminus`. Wrap\n * {@link check} in a terminus indicator (see the README). The lag is measured\n * with the `clock` given to the module options (the system clock by default),\n * the same clock the bus uses.\n */\n@Injectable()\nexport class RelayBusHealth {\n constructor(\n @Inject(RelayBus) private readonly bus: RelayBus,\n @Inject(RELAYBUS_MODULE_OPTIONS) private readonly options: RelayBusModuleOptions,\n ) {}\n\n async check(): Promise<RelayBusHealthReport> {\n const running = this.bus.isRunning;\n const ready = this.bus.isReady();\n const handlers = this.bus.handlers().length;\n if (!running) {\n return { status: 'down', running, ready, handlers };\n }\n try {\n const statistics = await this.bus.statistics();\n const clock = this.options.clock ?? systemClock;\n const lagMilliseconds =\n statistics.oldestDueAt === null\n ? 0\n : Math.max(0, clock.now().getTime() - statistics.oldestDueAt.getTime());\n return {\n status: ready ? 'up' : 'down',\n running,\n ready,\n handlers,\n statistics,\n lagMilliseconds,\n };\n } catch (error) {\n return {\n status: 'down',\n running,\n ready,\n handlers,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n","import {\n Inject,\n Injectable,\n type BeforeApplicationShutdown,\n type OnApplicationBootstrap,\n} from '@nestjs/common';\nimport { RelayBus } from '@relaybus/core';\n\n/**\n * Starts the bus once every handler is registered and the application has\n * bootstrapped, and stops it (draining running handlers) on shutdown. A start\n * failure rejects the hook, so the application does not boot.\n */\n@Injectable()\nexport class RelayBusLifecycle implements OnApplicationBootstrap, BeforeApplicationShutdown {\n constructor(@Inject(RelayBus) private readonly bus: RelayBus) {}\n\n async onApplicationBootstrap(): Promise<void> {\n await this.bus.start();\n }\n\n async beforeApplicationShutdown(): Promise<void> {\n await this.bus.stop();\n }\n}\n","import { Logger, Module } from '@nestjs/common';\nimport { DiscoveryModule } from '@nestjs/core';\nimport { RelayBus } from '@relaybus/core';\nimport { RelayBusHandlerExplorer } from './handler-explorer.js';\nimport {\n RELAYBUS_MODULE_OPTIONS,\n RelayBusConfigurableModule,\n type RelayBusModuleOptions,\n} from './module-options.js';\nimport { RelayBusHealth } from './relay-bus-health.js';\nimport { RelayBusLifecycle } from './relay-bus-lifecycle.js';\n\n/**\n * Registers one {@link RelayBus} for the application, discovers `@Subscribe`\n * handlers in every module, starts the bus after bootstrap and stops it on\n * shutdown. Global unless `isGlobal: false`.\n *\n * ```ts\n * RelayBusModule.forRoot({ store: new PostgresEventStore({ pool }), source: 'orders' })\n * ```\n */\n@Module({\n imports: [DiscoveryModule],\n providers: [\n {\n provide: RelayBus,\n inject: [RELAYBUS_MODULE_OPTIONS],\n useFactory: (options: RelayBusModuleOptions) =>\n new RelayBus({ ...options, logger: new Logger('RelayBus') }),\n },\n RelayBusHandlerExplorer,\n RelayBusLifecycle,\n RelayBusHealth,\n ],\n exports: [RelayBus, RelayBusHealth],\n})\nexport class RelayBusModule extends RelayBusConfigurableModule {}\n"]}
1
+ {"version":3,"sources":["../src/event-consumer.decorator.ts","../src/subscribe.decorator.ts","../src/handler-explorer.ts","../src/module-options.ts","../src/relay-bus-health.ts","../src/relay-bus-lifecycle.ts","../src/relay-bus.module.ts"],"names":["describeCatalog","consume","ConfigurationError","RelayBusHandlerExplorer","Injectable","DiscoveryService","MetadataScanner","RelayBus","ConfigurableModuleBuilder","RelayBusHealth","systemClock","Inject","RelayBusLifecycle","RelayBusModule","Module","DiscoveryModule","Logger"],"mappings":"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","file":"index.cjs","sourcesContent":["import 'reflect-metadata';\nimport {\n ConfigurationError,\n consume,\n describeCatalog,\n type Catalog,\n type CatalogEntry,\n type CatalogEventSelection,\n type EventOf,\n type HandlerContext,\n type SubscribeOptions,\n} from '@relaybus/core';\n\n/**\n * Options of {@link EventConsumer}: the core {@link SubscribeOptions} applied to\n * every handler of the class, except `id`, which is always\n * `${ClassName}.on${EventName}`, and optionally the events the class consumes.\n */\nexport type EventConsumerOptions<\n TCatalog extends Catalog = Catalog,\n TName extends keyof TCatalog = keyof TCatalog,\n> = Omit<SubscribeOptions, 'id'> & CatalogEventSelection<TName>;\n\n/** What one `@EventConsumer` decorator recorded on a class. */\nexport interface EventConsumerMetadata {\n readonly catalog: Catalog;\n /** Subscribe options applied to every handler of the class. */\n readonly options: Omit<SubscribeOptions, 'id'>;\n /**\n * Entries named by `options.events`, whose methods are all required;\n * undefined when the class consumes whichever handler methods it defines.\n */\n readonly selectedEntries: readonly CatalogEntry[] | undefined;\n}\n\n/**\n * A class whose instances have a handler method for each of the named catalog\n * events. The event parameter is checked strictly; the context is typed\n * `HandlerContext<never>` so a method may declare any transaction type.\n */\nexport type EventConsumerClass<\n TCatalog extends Catalog,\n TName extends keyof TCatalog,\n> = abstract new (...args: never[]) => {\n readonly [TKey in TName & string as `on${TKey}`]: (\n event: EventOf<TCatalog[TKey]>,\n context: HandlerContext<never>,\n ) => unknown;\n};\n\nconst EVENT_CONSUMER_METADATA_KEY = Symbol.for('relaybus.nestjs.event-consumer');\n\n/**\n * Subscribes a provider class to the events of a catalog: its\n * `on${EventName}` methods are registered when the module initialises, under\n * the stable id `${ClassName}.on${EventName}`, with `this` bound to the\n * provider instance.\n *\n * - Without `events`, every handler method the class defines is registered; a\n * class that defines none fails the boot, which catches misspelled names.\n * `implements HandlersOf<typeof Catalog>` makes the compiler require all of them.\n * - With `events`, exactly the methods of those events are registered (methods\n * of other events are ignored), and a class missing one of them fails to\n * compile and fails the boot. This is how a service consumes its part of a\n * catalog shared between services.\n *\n * `catalogs: [consume(Catalog, { events })]` in the module options makes the\n * application fail to boot if no provider handles a consumed event. Works next\n * to `@Subscribe` in the same class (give a method that carries both a\n * distinct `@Subscribe` id). The provider must be a singleton. Declarations\n * are inherited; a subclass that declares the same catalog again replaces the\n * inherited declaration of that catalog, and declaring one catalog twice on\n * the same class throws a {@link ConfigurationError}.\n *\n * ```ts\n * @Injectable()\n * @EventConsumer(OrderEvents, { events: ['OrderPlaced'], transactional: true })\n * export class BillingHandlers {\n * onOrderPlaced(event: EventOf<typeof OrderPlaced>) {}\n * }\n * ```\n */\nexport function EventConsumer<TCatalog extends Catalog>(\n catalog: TCatalog,\n options?: Omit<SubscribeOptions, 'id'> & { readonly events?: undefined },\n): ClassDecorator;\nexport function EventConsumer<TCatalog extends Catalog, const TName extends keyof TCatalog>(\n catalog: TCatalog,\n options: Omit<SubscribeOptions, 'id'> & { readonly events: readonly TName[] },\n): (target: EventConsumerClass<TCatalog, TName>) => void;\n/**\n * With options whose `events` are not statically known (a variable typed\n * {@link EventConsumerOptions}), the selection is checked when the module\n * initialises instead of at compile time.\n */\nexport function EventConsumer<TCatalog extends Catalog>(\n catalog: TCatalog,\n options: EventConsumerOptions<TCatalog>,\n): ClassDecorator;\nexport function EventConsumer(\n catalog: Catalog,\n {\n events,\n ...options\n }: Omit<SubscribeOptions, 'id'> & {\n readonly events?: readonly PropertyKey[] | undefined;\n } = {},\n): ClassDecorator {\n const { name } = describeCatalog(catalog);\n const selectedEntries =\n events === undefined\n ? undefined\n : consume(catalog, { events: events as readonly (keyof Catalog)[] }).entries;\n return (target) => {\n const existing =\n (Reflect.getOwnMetadata(EVENT_CONSUMER_METADATA_KEY, target) as\n EventConsumerMetadata[] | undefined) ?? [];\n if (existing.some((metadata) => metadata.catalog === catalog)) {\n throw new ConfigurationError(\n `@EventConsumer(${name ?? 'catalog'}) is declared twice on ${target.name}; ` +\n `merge the declarations into one`,\n );\n }\n Reflect.defineMetadata(\n EVENT_CONSUMER_METADATA_KEY,\n [...existing, { catalog, options, selectedEntries }],\n target,\n );\n };\n}\n\n/**\n * Reads the `@EventConsumer` declarations of a class and of its ancestors,\n * ancestors first, each in declaration order. When a class and an ancestor\n * both declare a catalog, the declaration nearest to the class wins.\n */\nexport function readEventConsumerMetadata(target: object): readonly EventConsumerMetadata[] {\n const lineage: object[] = [];\n for (let current = target; current !== Function.prototype;) {\n lineage.unshift(current);\n current = Object.getPrototypeOf(current) as object;\n }\n const byCatalog = new Map<Catalog, EventConsumerMetadata>();\n for (const type of lineage) {\n const own =\n (Reflect.getOwnMetadata(EVENT_CONSUMER_METADATA_KEY, type) as\n EventConsumerMetadata[] | undefined) ?? [];\n for (const metadata of own) {\n byCatalog.set(metadata.catalog, metadata);\n }\n }\n return [...byCatalog.values()];\n}\n","import 'reflect-metadata';\nimport type { EventClass, EventEnvelope, HandlerContext, SubscribeOptions } from '@relaybus/core';\n\n/**\n * Options of {@link Subscribe}: the core {@link SubscribeOptions}, except that\n * `id` is optional and defaults to `ClassName.methodName`.\n */\nexport type SubscribeDecoratorOptions = Omit<SubscribeOptions, 'id'> & {\n /**\n * Stable id of the handler. Deliveries are recorded under it, so renaming it\n * (or the class or method it is derived from) orphans pending deliveries.\n * Default: `${ClassName}.${methodName}`. Required when one method carries\n * several `@Subscribe` decorators.\n */\n readonly id?: string;\n};\n\n/** What one `@Subscribe` decorator recorded on a method. */\nexport interface SubscribeMetadata {\n readonly event: EventClass<unknown> | string;\n readonly options: SubscribeDecoratorOptions;\n}\n\nconst SUBSCRIBE_METADATA_KEY = Symbol.for('relaybus.nestjs.subscribe');\n\ntype HandlerMethod<TPayload> = (\n event: EventEnvelope<TPayload>,\n context: HandlerContext<never>,\n) => unknown;\n\ntype SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(\n target: object,\n propertyKey: string | symbol,\n descriptor: TypedPropertyDescriptor<TMethod>,\n) => void;\n\n/**\n * Subscribes a provider method to an event class or type. The method is\n * registered with `bus.subscribe` when the module initialises, with `this`\n * bound to the provider instance, and receives `(envelope, context)`.\n *\n * The provider must be a singleton (default scope) and belong to any module of\n * the application. The decorator name differs from `@nestjs/event-emitter`'s\n * `@OnEvent` so both can be used in one application.\n *\n * ```ts\n * @Injectable()\n * export class ShippingHandlers {\n * @Subscribe(OrderPlaced, { transactional: true })\n * async onOrderPlaced(event: EventEnvelope<{ orderId: string }>, context: HandlerContext) {}\n * }\n * ```\n */\nexport function Subscribe<TPayload>(\n event: EventClass<TPayload>,\n options?: SubscribeDecoratorOptions,\n): SubscribeDecorator<TPayload>;\nexport function Subscribe(\n eventType: string,\n options?: SubscribeDecoratorOptions,\n): SubscribeDecorator<never>;\nexport function Subscribe(\n event: EventClass<unknown> | string,\n options: SubscribeDecoratorOptions = {},\n): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void {\n return (target, propertyKey) => {\n // Keyed by (prototype, property) rather than stored on the method function, so a\n // decorator that later replaces the method does not drop the subscription.\n const existing =\n (Reflect.getOwnMetadata(SUBSCRIBE_METADATA_KEY, target, propertyKey) as\n SubscribeMetadata[] | undefined) ?? [];\n Reflect.defineMetadata(\n SUBSCRIBE_METADATA_KEY,\n [...existing, { event, options }],\n target,\n propertyKey,\n );\n };\n}\n\n/**\n * Reads the `@Subscribe` declarations of a method, in declaration order. The\n * prototype chain is searched, so a subclass inherits the declarations of the\n * nearest ancestor that declares them.\n */\nexport function readSubscribeMetadata(\n prototype: object,\n propertyKey: string | symbol,\n): readonly SubscribeMetadata[] {\n return (\n (Reflect.getMetadata(SUBSCRIBE_METADATA_KEY, prototype, propertyKey) as\n SubscribeMetadata[] | undefined) ?? []\n );\n}\n","import { Inject, Injectable, type OnModuleInit } from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper.js';\nimport {\n ConfigurationError,\n RelayBus,\n describeCatalog,\n type CatalogEntry,\n type EventHandler,\n} from '@relaybus/core';\nimport { readEventConsumerMetadata } from './event-consumer.decorator.js';\nimport { readSubscribeMetadata, type SubscribeMetadata } from './subscribe.decorator.js';\n\ninterface SubscribedMethod {\n readonly methodName: string;\n readonly declarations: readonly SubscribeMetadata[];\n}\n\n/**\n * Finds every `@Subscribe` method and every `@EventConsumer` class among the\n * singleton providers of the application and registers them on the bus before\n * the bus starts. Providers that are request-scoped or transient, directly or\n * through a dependency, and use either decorator fail the boot. A request-scoped provider whose\n * token is neither a class nor an instance carrying the decorated prototype\n * (for example a string-token factory) cannot be inspected and is skipped.\n */\n@Injectable()\nexport class RelayBusHandlerExplorer implements OnModuleInit {\n constructor(\n @Inject(DiscoveryService) private readonly discovery: DiscoveryService,\n @Inject(MetadataScanner) private readonly metadataScanner: MetadataScanner,\n @Inject(RelayBus) private readonly bus: RelayBus,\n ) {}\n\n onModuleInit(): void {\n const registeredBy = new Map<string, string>();\n const visited = new Map<object, Set<string>>();\n const consumers = new WeakSet<object>();\n for (const wrapper of this.discovery.getProviders()) {\n if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {\n this.assertNoHandlers(wrapper);\n continue;\n }\n const instance = wrapper.instance as unknown;\n if (typeof instance !== 'object' || instance === null) {\n continue;\n }\n const prototype = Object.getPrototypeOf(instance) as object | null;\n if (prototype === null) {\n continue;\n }\n const className = instance.constructor.name;\n for (const { methodName, declarations } of this.subscribedMethods(prototype)) {\n // A `useExisting` alias exposes the same instance under another token.\n const seen = visited.get(instance) ?? new Set<string>();\n visited.set(instance, seen);\n if (seen.has(methodName)) {\n continue;\n }\n seen.add(methodName);\n const method = (instance as Record<string, unknown>)[methodName] as (\n ...args: Parameters<EventHandler>\n ) => unknown;\n const location = `${className}.${methodName}`;\n for (const { event, options } of declarations) {\n const id = options.id ?? location;\n const previous = registeredBy.get(id);\n if (previous !== undefined) {\n throw new ConfigurationError(\n `Handler id '${id}' is used by both ${previous} and ${location}; ` +\n `pass a unique { id } to @Subscribe`,\n );\n }\n registeredBy.set(id, location);\n // Core resolves both classes and type strings; the overloads cannot express the union.\n this.bus.subscribe(\n event as never,\n (envelope, context) => method.call(instance, envelope, context),\n { ...options, id },\n );\n }\n }\n if (!consumers.has(instance)) {\n consumers.add(instance);\n this.registerEventConsumers(instance, className, registeredBy);\n }\n }\n }\n\n private registerEventConsumers(\n instance: object,\n className: string,\n registeredBy: Map<string, string>,\n ): void {\n const lookup = instance as Record<string, unknown>;\n for (const { catalog, options, selectedEntries } of readEventConsumerMetadata(\n instance.constructor,\n )) {\n const { name, entries } = describeCatalog(catalog);\n const label = name ?? 'catalog';\n const handlers = selectedEntries ?? entries.filter((entry) => isMethod(lookup, entry));\n const missing = handlers.filter((entry) => !isMethod(lookup, entry));\n if (missing.length > 0) {\n throw new ConfigurationError(\n `${className} uses @EventConsumer(${label}) but lacks the handler methods of events ` +\n `it consumes: ${missing.map((entry) => entry.handlerName).join(', ')}`,\n );\n }\n if (handlers.length === 0) {\n throw new ConfigurationError(\n `${className} uses @EventConsumer(${label}) but defines none of its handler ` +\n `methods (${entries.map((entry) => entry.handlerName).join(', ')})`,\n );\n }\n for (const entry of handlers) {\n const id = `${className}.${entry.handlerName}`;\n const previous = registeredBy.get(id);\n if (previous !== undefined) {\n throw new ConfigurationError(\n `Handler id '${id}' of @EventConsumer(${label}) is already used by ${previous}; ` +\n `give the @Subscribe a unique { id } or drop it`,\n );\n }\n registeredBy.set(id, `${className}.${entry.handlerName}`);\n const method = lookup[entry.handlerName] as (...args: Parameters<EventHandler>) => unknown;\n this.bus.subscribe(\n entry.eventClass as never,\n (envelope, context) => method.call(instance, envelope, context),\n { ...options, id },\n );\n }\n }\n }\n\n private subscribedMethods(prototype: object): SubscribedMethod[] {\n const methods: SubscribedMethod[] = [];\n for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {\n const declarations = readSubscribeMetadata(prototype, methodName);\n if (declarations.length > 0) {\n methods.push({ methodName, declarations });\n }\n }\n return methods;\n }\n\n /**\n * Request-scoped and transient wrappers have no usable instance (a factory\n * wrapper has none at all), so the decorated prototype is taken from the\n * provider class, or from the provider token for factories when it is a class.\n */\n private assertNoHandlers(wrapper: InstanceWrapper): void {\n const type = (wrapper.isFactory ? wrapper.token : wrapper.metatype) as unknown;\n if (typeof type === 'function' && readEventConsumerMetadata(type).length > 0) {\n throw new ConfigurationError(\n `${type.name} uses @EventConsumer but its provider is request-scoped or transient ` +\n `(directly or through a dependency); handlers must be singleton providers`,\n );\n }\n const prototype = typeof type === 'function' ? (type.prototype as object) : null;\n const [first] = prototype === null ? [] : this.subscribedMethods(prototype);\n if (first !== undefined) {\n throw new ConfigurationError(\n `${(prototype as { constructor: { name: string } }).constructor.name}.${first.methodName} ` +\n `uses @Subscribe but its provider is request-scoped or transient ` +\n `(directly or through a dependency); handlers must be singleton providers`,\n );\n }\n }\n}\n\nfunction isMethod(instance: Record<string, unknown>, entry: CatalogEntry): boolean {\n return typeof instance[entry.handlerName] === 'function';\n}\n","import { ConfigurableModuleBuilder } from '@nestjs/common';\nimport type { RelayBusOptions } from '@relaybus/core';\n\n/**\n * Options of {@link RelayBusModule}: everything {@link RelayBusOptions} accepts\n * except `logger`, which is always a Nest `Logger` with context `RelayBus`\n * (configure it with `app.useLogger`). Only `store` is required.\n */\nexport type RelayBusModuleOptions<TTransaction = unknown> = Omit<\n RelayBusOptions<TTransaction>,\n 'logger'\n>;\n\n/** Module-level extras accepted next to the options by `forRoot` and `forRootAsync`. */\nexport interface RelayBusModuleExtras {\n /** Make `RelayBus` and `RelayBusHealth` injectable everywhere. Default true. */\n readonly isGlobal?: boolean;\n}\n\n/** Implemented by classes passed as `useClass` / `useExisting` to `forRootAsync`. */\nexport interface RelayBusModuleOptionsFactory {\n create(): Promise<RelayBusModuleOptions> | RelayBusModuleOptions;\n}\n\nexport const {\n ConfigurableModuleClass: RelayBusConfigurableModule,\n MODULE_OPTIONS_TOKEN: RELAYBUS_MODULE_OPTIONS,\n} = new ConfigurableModuleBuilder<RelayBusModuleOptions>()\n .setClassMethodName('forRoot')\n .setExtras<RelayBusModuleExtras>({ isGlobal: true }, (definition, extras) => ({\n ...definition,\n global: extras.isGlobal === true,\n }))\n .build();\n","import { Inject, Injectable } from '@nestjs/common';\nimport { RelayBus, systemClock, type EventStoreStatistics } from '@relaybus/core';\nimport { RELAYBUS_MODULE_OPTIONS, type RelayBusModuleOptions } from './module-options.js';\n\n/** Result of {@link RelayBusHealth.check}. */\nexport interface RelayBusHealthReport {\n /**\n * `down` when the bus is not running, a transport reports itself not ready\n * (dead Kafka consumer, disconnected RabbitMQ) or the store cannot be queried.\n */\n readonly status: 'up' | 'down';\n readonly running: boolean;\n /** `bus.isReady()`: running and every transport ready. */\n readonly ready: boolean;\n /** Number of registered handlers and routes. */\n readonly handlers: number;\n /** Delivery counts; absent when the bus is not running or the store failed. */\n readonly statistics?: EventStoreStatistics;\n /** Milliseconds the oldest due delivery has waited; 0 when nothing is due. */\n readonly lagMilliseconds?: number;\n /** Message of the store error, when the statistics query failed. */\n readonly error?: string;\n}\n\n/**\n * Health probe of the bus without a dependency on `@nestjs/terminus`. Wrap\n * {@link check} in a terminus indicator (see the README). The lag is measured\n * with the `clock` given to the module options (the system clock by default),\n * the same clock the bus uses.\n */\n@Injectable()\nexport class RelayBusHealth {\n constructor(\n @Inject(RelayBus) private readonly bus: RelayBus,\n @Inject(RELAYBUS_MODULE_OPTIONS) private readonly options: RelayBusModuleOptions,\n ) {}\n\n async check(): Promise<RelayBusHealthReport> {\n const running = this.bus.isRunning;\n const ready = this.bus.isReady();\n const handlers = this.bus.handlers().length;\n if (!running) {\n return { status: 'down', running, ready, handlers };\n }\n try {\n const statistics = await this.bus.statistics();\n const clock = this.options.clock ?? systemClock;\n const lagMilliseconds =\n statistics.oldestDueAt === null\n ? 0\n : Math.max(0, clock.now().getTime() - statistics.oldestDueAt.getTime());\n return {\n status: ready ? 'up' : 'down',\n running,\n ready,\n handlers,\n statistics,\n lagMilliseconds,\n };\n } catch (error) {\n return {\n status: 'down',\n running,\n ready,\n handlers,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n","import {\n Inject,\n Injectable,\n type BeforeApplicationShutdown,\n type OnApplicationBootstrap,\n} from '@nestjs/common';\nimport { RelayBus } from '@relaybus/core';\n\n/**\n * Starts the bus once every handler is registered and the application has\n * bootstrapped, and stops it (draining running handlers) on shutdown. A start\n * failure rejects the hook, so the application does not boot.\n */\n@Injectable()\nexport class RelayBusLifecycle implements OnApplicationBootstrap, BeforeApplicationShutdown {\n constructor(@Inject(RelayBus) private readonly bus: RelayBus) {}\n\n async onApplicationBootstrap(): Promise<void> {\n await this.bus.start();\n }\n\n async beforeApplicationShutdown(): Promise<void> {\n await this.bus.stop();\n }\n}\n","import { Logger, Module } from '@nestjs/common';\nimport { DiscoveryModule } from '@nestjs/core';\nimport { RelayBus } from '@relaybus/core';\nimport { RelayBusHandlerExplorer } from './handler-explorer.js';\nimport {\n RELAYBUS_MODULE_OPTIONS,\n RelayBusConfigurableModule,\n type RelayBusModuleOptions,\n} from './module-options.js';\nimport { RelayBusHealth } from './relay-bus-health.js';\nimport { RelayBusLifecycle } from './relay-bus-lifecycle.js';\n\n/**\n * Registers one {@link RelayBus} for the application, discovers `@Subscribe`\n * handlers in every module, starts the bus after bootstrap and stops it on\n * shutdown. Global unless `isGlobal: false`.\n *\n * ```ts\n * RelayBusModule.forRoot({ store: new PostgresEventStore({ pool }), source: 'orders' })\n * ```\n */\n@Module({\n imports: [DiscoveryModule],\n providers: [\n {\n provide: RelayBus,\n inject: [RELAYBUS_MODULE_OPTIONS],\n useFactory: (options: RelayBusModuleOptions) =>\n new RelayBus({ ...options, logger: new Logger('RelayBus') }),\n },\n RelayBusHandlerExplorer,\n RelayBusLifecycle,\n RelayBusHealth,\n ],\n exports: [RelayBus, RelayBusHealth],\n})\nexport class RelayBusModule extends RelayBusConfigurableModule {}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,14 +1,82 @@
1
+ import { Catalog, SubscribeOptions, EventOf, HandlerContext, CatalogEventSelection, CatalogEntry, RelayBus, RelayBusOptions, EventStoreStatistics, EventClass, EventEnvelope } from '@relaybus/core';
2
+ export { RelayBus } from '@relaybus/core';
1
3
  import * as _nestjs_common from '@nestjs/common';
2
4
  import { OnModuleInit, OnApplicationBootstrap, BeforeApplicationShutdown } from '@nestjs/common';
3
5
  import { DiscoveryService, MetadataScanner } from '@nestjs/core';
4
- import { RelayBus, RelayBusOptions, EventStoreStatistics, EventClass, SubscribeOptions, EventEnvelope, HandlerContext } from '@relaybus/core';
5
- export { RelayBus } from '@relaybus/core';
6
6
 
7
7
  /**
8
- * Finds every `@Subscribe` method of every singleton provider in the
9
- * application and registers it on the bus before the bus starts. Providers
10
- * that are request-scoped or transient, directly or through a dependency, and
11
- * declare `@Subscribe` methods fail the boot. A request-scoped provider whose
8
+ * Options of {@link EventConsumer}: the core {@link SubscribeOptions} applied to
9
+ * every handler of the class, except `id`, which is always
10
+ * `${ClassName}.on${EventName}`, and optionally the events the class consumes.
11
+ */
12
+ type EventConsumerOptions<TCatalog extends Catalog = Catalog, TName extends keyof TCatalog = keyof TCatalog> = Omit<SubscribeOptions, 'id'> & CatalogEventSelection<TName>;
13
+ /** What one `@EventConsumer` decorator recorded on a class. */
14
+ interface EventConsumerMetadata {
15
+ readonly catalog: Catalog;
16
+ /** Subscribe options applied to every handler of the class. */
17
+ readonly options: Omit<SubscribeOptions, 'id'>;
18
+ /**
19
+ * Entries named by `options.events`, whose methods are all required;
20
+ * undefined when the class consumes whichever handler methods it defines.
21
+ */
22
+ readonly selectedEntries: readonly CatalogEntry[] | undefined;
23
+ }
24
+ /**
25
+ * A class whose instances have a handler method for each of the named catalog
26
+ * events. The event parameter is checked strictly; the context is typed
27
+ * `HandlerContext<never>` so a method may declare any transaction type.
28
+ */
29
+ type EventConsumerClass<TCatalog extends Catalog, TName extends keyof TCatalog> = abstract new (...args: never[]) => {
30
+ readonly [TKey in TName & string as `on${TKey}`]: (event: EventOf<TCatalog[TKey]>, context: HandlerContext<never>) => unknown;
31
+ };
32
+ /**
33
+ * Subscribes a provider class to the events of a catalog: its
34
+ * `on${EventName}` methods are registered when the module initialises, under
35
+ * the stable id `${ClassName}.on${EventName}`, with `this` bound to the
36
+ * provider instance.
37
+ *
38
+ * - Without `events`, every handler method the class defines is registered; a
39
+ * class that defines none fails the boot, which catches misspelled names.
40
+ * `implements HandlersOf<typeof Catalog>` makes the compiler require all of them.
41
+ * - With `events`, exactly the methods of those events are registered (methods
42
+ * of other events are ignored), and a class missing one of them fails to
43
+ * compile and fails the boot. This is how a service consumes its part of a
44
+ * catalog shared between services.
45
+ *
46
+ * `catalogs: [consume(Catalog, { events })]` in the module options makes the
47
+ * application fail to boot if no provider handles a consumed event. Works next
48
+ * to `@Subscribe` in the same class (give a method that carries both a
49
+ * distinct `@Subscribe` id). The provider must be a singleton. Declarations
50
+ * are inherited; a subclass that declares the same catalog again replaces the
51
+ * inherited declaration of that catalog, and declaring one catalog twice on
52
+ * the same class throws a {@link ConfigurationError}.
53
+ *
54
+ * ```ts
55
+ * @Injectable()
56
+ * @EventConsumer(OrderEvents, { events: ['OrderPlaced'], transactional: true })
57
+ * export class BillingHandlers {
58
+ * onOrderPlaced(event: EventOf<typeof OrderPlaced>) {}
59
+ * }
60
+ * ```
61
+ */
62
+ declare function EventConsumer<TCatalog extends Catalog>(catalog: TCatalog, options?: Omit<SubscribeOptions, 'id'> & {
63
+ readonly events?: undefined;
64
+ }): ClassDecorator;
65
+ declare function EventConsumer<TCatalog extends Catalog, const TName extends keyof TCatalog>(catalog: TCatalog, options: Omit<SubscribeOptions, 'id'> & {
66
+ readonly events: readonly TName[];
67
+ }): (target: EventConsumerClass<TCatalog, TName>) => void;
68
+ /**
69
+ * With options whose `events` are not statically known (a variable typed
70
+ * {@link EventConsumerOptions}), the selection is checked when the module
71
+ * initialises instead of at compile time.
72
+ */
73
+ declare function EventConsumer<TCatalog extends Catalog>(catalog: TCatalog, options: EventConsumerOptions<TCatalog>): ClassDecorator;
74
+
75
+ /**
76
+ * Finds every `@Subscribe` method and every `@EventConsumer` class among the
77
+ * singleton providers of the application and registers them on the bus before
78
+ * the bus starts. Providers that are request-scoped or transient, directly or
79
+ * through a dependency, and use either decorator fail the boot. A request-scoped provider whose
12
80
  * token is neither a class nor an instance carrying the decorated prototype
13
81
  * (for example a string-token factory) cannot be inspected and is skipped.
14
82
  */
@@ -18,6 +86,7 @@ declare class RelayBusHandlerExplorer implements OnModuleInit {
18
86
  private readonly bus;
19
87
  constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, bus: RelayBus);
20
88
  onModuleInit(): void;
89
+ private registerEventConsumers;
21
90
  private subscribedMethods;
22
91
  /**
23
92
  * Request-scoped and transient wrappers have no usable instance (a factory
@@ -140,4 +209,4 @@ type SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(ta
140
209
  declare function Subscribe<TPayload>(event: EventClass<TPayload>, options?: SubscribeDecoratorOptions): SubscribeDecorator<TPayload>;
141
210
  declare function Subscribe(eventType: string, options?: SubscribeDecoratorOptions): SubscribeDecorator<never>;
142
211
 
143
- export { RelayBusHandlerExplorer, RelayBusHealth, type RelayBusHealthReport, RelayBusLifecycle, RelayBusModule, type RelayBusModuleExtras, type RelayBusModuleOptions, type RelayBusModuleOptionsFactory, Subscribe, type SubscribeDecoratorOptions, type SubscribeMetadata };
212
+ export { EventConsumer, type EventConsumerClass, type EventConsumerMetadata, type EventConsumerOptions, RelayBusHandlerExplorer, RelayBusHealth, type RelayBusHealthReport, RelayBusLifecycle, RelayBusModule, type RelayBusModuleExtras, type RelayBusModuleOptions, type RelayBusModuleOptionsFactory, Subscribe, type SubscribeDecoratorOptions, type SubscribeMetadata };
package/dist/index.d.ts CHANGED
@@ -1,14 +1,82 @@
1
+ import { Catalog, SubscribeOptions, EventOf, HandlerContext, CatalogEventSelection, CatalogEntry, RelayBus, RelayBusOptions, EventStoreStatistics, EventClass, EventEnvelope } from '@relaybus/core';
2
+ export { RelayBus } from '@relaybus/core';
1
3
  import * as _nestjs_common from '@nestjs/common';
2
4
  import { OnModuleInit, OnApplicationBootstrap, BeforeApplicationShutdown } from '@nestjs/common';
3
5
  import { DiscoveryService, MetadataScanner } from '@nestjs/core';
4
- import { RelayBus, RelayBusOptions, EventStoreStatistics, EventClass, SubscribeOptions, EventEnvelope, HandlerContext } from '@relaybus/core';
5
- export { RelayBus } from '@relaybus/core';
6
6
 
7
7
  /**
8
- * Finds every `@Subscribe` method of every singleton provider in the
9
- * application and registers it on the bus before the bus starts. Providers
10
- * that are request-scoped or transient, directly or through a dependency, and
11
- * declare `@Subscribe` methods fail the boot. A request-scoped provider whose
8
+ * Options of {@link EventConsumer}: the core {@link SubscribeOptions} applied to
9
+ * every handler of the class, except `id`, which is always
10
+ * `${ClassName}.on${EventName}`, and optionally the events the class consumes.
11
+ */
12
+ type EventConsumerOptions<TCatalog extends Catalog = Catalog, TName extends keyof TCatalog = keyof TCatalog> = Omit<SubscribeOptions, 'id'> & CatalogEventSelection<TName>;
13
+ /** What one `@EventConsumer` decorator recorded on a class. */
14
+ interface EventConsumerMetadata {
15
+ readonly catalog: Catalog;
16
+ /** Subscribe options applied to every handler of the class. */
17
+ readonly options: Omit<SubscribeOptions, 'id'>;
18
+ /**
19
+ * Entries named by `options.events`, whose methods are all required;
20
+ * undefined when the class consumes whichever handler methods it defines.
21
+ */
22
+ readonly selectedEntries: readonly CatalogEntry[] | undefined;
23
+ }
24
+ /**
25
+ * A class whose instances have a handler method for each of the named catalog
26
+ * events. The event parameter is checked strictly; the context is typed
27
+ * `HandlerContext<never>` so a method may declare any transaction type.
28
+ */
29
+ type EventConsumerClass<TCatalog extends Catalog, TName extends keyof TCatalog> = abstract new (...args: never[]) => {
30
+ readonly [TKey in TName & string as `on${TKey}`]: (event: EventOf<TCatalog[TKey]>, context: HandlerContext<never>) => unknown;
31
+ };
32
+ /**
33
+ * Subscribes a provider class to the events of a catalog: its
34
+ * `on${EventName}` methods are registered when the module initialises, under
35
+ * the stable id `${ClassName}.on${EventName}`, with `this` bound to the
36
+ * provider instance.
37
+ *
38
+ * - Without `events`, every handler method the class defines is registered; a
39
+ * class that defines none fails the boot, which catches misspelled names.
40
+ * `implements HandlersOf<typeof Catalog>` makes the compiler require all of them.
41
+ * - With `events`, exactly the methods of those events are registered (methods
42
+ * of other events are ignored), and a class missing one of them fails to
43
+ * compile and fails the boot. This is how a service consumes its part of a
44
+ * catalog shared between services.
45
+ *
46
+ * `catalogs: [consume(Catalog, { events })]` in the module options makes the
47
+ * application fail to boot if no provider handles a consumed event. Works next
48
+ * to `@Subscribe` in the same class (give a method that carries both a
49
+ * distinct `@Subscribe` id). The provider must be a singleton. Declarations
50
+ * are inherited; a subclass that declares the same catalog again replaces the
51
+ * inherited declaration of that catalog, and declaring one catalog twice on
52
+ * the same class throws a {@link ConfigurationError}.
53
+ *
54
+ * ```ts
55
+ * @Injectable()
56
+ * @EventConsumer(OrderEvents, { events: ['OrderPlaced'], transactional: true })
57
+ * export class BillingHandlers {
58
+ * onOrderPlaced(event: EventOf<typeof OrderPlaced>) {}
59
+ * }
60
+ * ```
61
+ */
62
+ declare function EventConsumer<TCatalog extends Catalog>(catalog: TCatalog, options?: Omit<SubscribeOptions, 'id'> & {
63
+ readonly events?: undefined;
64
+ }): ClassDecorator;
65
+ declare function EventConsumer<TCatalog extends Catalog, const TName extends keyof TCatalog>(catalog: TCatalog, options: Omit<SubscribeOptions, 'id'> & {
66
+ readonly events: readonly TName[];
67
+ }): (target: EventConsumerClass<TCatalog, TName>) => void;
68
+ /**
69
+ * With options whose `events` are not statically known (a variable typed
70
+ * {@link EventConsumerOptions}), the selection is checked when the module
71
+ * initialises instead of at compile time.
72
+ */
73
+ declare function EventConsumer<TCatalog extends Catalog>(catalog: TCatalog, options: EventConsumerOptions<TCatalog>): ClassDecorator;
74
+
75
+ /**
76
+ * Finds every `@Subscribe` method and every `@EventConsumer` class among the
77
+ * singleton providers of the application and registers them on the bus before
78
+ * the bus starts. Providers that are request-scoped or transient, directly or
79
+ * through a dependency, and use either decorator fail the boot. A request-scoped provider whose
12
80
  * token is neither a class nor an instance carrying the decorated prototype
13
81
  * (for example a string-token factory) cannot be inspected and is skipped.
14
82
  */
@@ -18,6 +86,7 @@ declare class RelayBusHandlerExplorer implements OnModuleInit {
18
86
  private readonly bus;
19
87
  constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, bus: RelayBus);
20
88
  onModuleInit(): void;
89
+ private registerEventConsumers;
21
90
  private subscribedMethods;
22
91
  /**
23
92
  * Request-scoped and transient wrappers have no usable instance (a factory
@@ -140,4 +209,4 @@ type SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(ta
140
209
  declare function Subscribe<TPayload>(event: EventClass<TPayload>, options?: SubscribeDecoratorOptions): SubscribeDecorator<TPayload>;
141
210
  declare function Subscribe(eventType: string, options?: SubscribeDecoratorOptions): SubscribeDecorator<never>;
142
211
 
143
- export { RelayBusHandlerExplorer, RelayBusHealth, type RelayBusHealthReport, RelayBusLifecycle, RelayBusModule, type RelayBusModuleExtras, type RelayBusModuleOptions, type RelayBusModuleOptionsFactory, Subscribe, type SubscribeDecoratorOptions, type SubscribeMetadata };
212
+ export { EventConsumer, type EventConsumerClass, type EventConsumerMetadata, type EventConsumerOptions, RelayBusHandlerExplorer, RelayBusHealth, type RelayBusHealthReport, RelayBusLifecycle, RelayBusModule, type RelayBusModuleExtras, type RelayBusModuleOptions, type RelayBusModuleOptionsFactory, Subscribe, type SubscribeDecoratorOptions, type SubscribeMetadata };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
+ import 'reflect-metadata';
2
+ import { RelayBus, systemClock, ConfigurationError, describeCatalog, consume } from '@relaybus/core';
3
+ export { RelayBus } from '@relaybus/core';
1
4
  import { Injectable, Inject, ConfigurableModuleBuilder, Module, Logger } from '@nestjs/common';
2
5
  import { DiscoveryService, MetadataScanner, DiscoveryModule } from '@nestjs/core';
3
- import { RelayBus, systemClock, ConfigurationError } from '@relaybus/core';
4
- export { RelayBus } from '@relaybus/core';
5
- import 'reflect-metadata';
6
6
 
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
8
  var __decorateClass = (decorators, target, key, kind) => {
@@ -13,6 +13,42 @@ var __decorateClass = (decorators, target, key, kind) => {
13
13
  return result;
14
14
  };
15
15
  var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
16
+ var EVENT_CONSUMER_METADATA_KEY = /* @__PURE__ */ Symbol.for("relaybus.nestjs.event-consumer");
17
+ function EventConsumer(catalog, {
18
+ events,
19
+ ...options
20
+ } = {}) {
21
+ const { name } = describeCatalog(catalog);
22
+ const selectedEntries = events === void 0 ? void 0 : consume(catalog, { events }).entries;
23
+ return (target) => {
24
+ const existing = Reflect.getOwnMetadata(EVENT_CONSUMER_METADATA_KEY, target) ?? [];
25
+ if (existing.some((metadata) => metadata.catalog === catalog)) {
26
+ throw new ConfigurationError(
27
+ `@EventConsumer(${name ?? "catalog"}) is declared twice on ${target.name}; merge the declarations into one`
28
+ );
29
+ }
30
+ Reflect.defineMetadata(
31
+ EVENT_CONSUMER_METADATA_KEY,
32
+ [...existing, { catalog, options, selectedEntries }],
33
+ target
34
+ );
35
+ };
36
+ }
37
+ function readEventConsumerMetadata(target) {
38
+ const lineage = [];
39
+ for (let current = target; current !== Function.prototype; ) {
40
+ lineage.unshift(current);
41
+ current = Object.getPrototypeOf(current);
42
+ }
43
+ const byCatalog = /* @__PURE__ */ new Map();
44
+ for (const type of lineage) {
45
+ const own = Reflect.getOwnMetadata(EVENT_CONSUMER_METADATA_KEY, type) ?? [];
46
+ for (const metadata of own) {
47
+ byCatalog.set(metadata.catalog, metadata);
48
+ }
49
+ }
50
+ return [...byCatalog.values()];
51
+ }
16
52
  var SUBSCRIBE_METADATA_KEY = /* @__PURE__ */ Symbol.for("relaybus.nestjs.subscribe");
17
53
  function Subscribe(event, options = {}) {
18
54
  return (target, propertyKey) => {
@@ -42,6 +78,7 @@ var RelayBusHandlerExplorer = class {
42
78
  onModuleInit() {
43
79
  const registeredBy = /* @__PURE__ */ new Map();
44
80
  const visited = /* @__PURE__ */ new Map();
81
+ const consumers = /* @__PURE__ */ new WeakSet();
45
82
  for (const wrapper of this.discovery.getProviders()) {
46
83
  if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {
47
84
  this.assertNoHandlers(wrapper);
@@ -81,6 +118,47 @@ var RelayBusHandlerExplorer = class {
81
118
  );
82
119
  }
83
120
  }
121
+ if (!consumers.has(instance)) {
122
+ consumers.add(instance);
123
+ this.registerEventConsumers(instance, className, registeredBy);
124
+ }
125
+ }
126
+ }
127
+ registerEventConsumers(instance, className, registeredBy) {
128
+ const lookup = instance;
129
+ for (const { catalog, options, selectedEntries } of readEventConsumerMetadata(
130
+ instance.constructor
131
+ )) {
132
+ const { name, entries } = describeCatalog(catalog);
133
+ const label = name ?? "catalog";
134
+ const handlers = selectedEntries ?? entries.filter((entry) => isMethod(lookup, entry));
135
+ const missing = handlers.filter((entry) => !isMethod(lookup, entry));
136
+ if (missing.length > 0) {
137
+ throw new ConfigurationError(
138
+ `${className} uses @EventConsumer(${label}) but lacks the handler methods of events it consumes: ${missing.map((entry) => entry.handlerName).join(", ")}`
139
+ );
140
+ }
141
+ if (handlers.length === 0) {
142
+ throw new ConfigurationError(
143
+ `${className} uses @EventConsumer(${label}) but defines none of its handler methods (${entries.map((entry) => entry.handlerName).join(", ")})`
144
+ );
145
+ }
146
+ for (const entry of handlers) {
147
+ const id = `${className}.${entry.handlerName}`;
148
+ const previous = registeredBy.get(id);
149
+ if (previous !== void 0) {
150
+ throw new ConfigurationError(
151
+ `Handler id '${id}' of @EventConsumer(${label}) is already used by ${previous}; give the @Subscribe a unique { id } or drop it`
152
+ );
153
+ }
154
+ registeredBy.set(id, `${className}.${entry.handlerName}`);
155
+ const method = lookup[entry.handlerName];
156
+ this.bus.subscribe(
157
+ entry.eventClass,
158
+ (envelope, context) => method.call(instance, envelope, context),
159
+ { ...options, id }
160
+ );
161
+ }
84
162
  }
85
163
  }
86
164
  subscribedMethods(prototype) {
@@ -100,6 +178,11 @@ var RelayBusHandlerExplorer = class {
100
178
  */
101
179
  assertNoHandlers(wrapper) {
102
180
  const type = wrapper.isFactory ? wrapper.token : wrapper.metatype;
181
+ if (typeof type === "function" && readEventConsumerMetadata(type).length > 0) {
182
+ throw new ConfigurationError(
183
+ `${type.name} uses @EventConsumer but its provider is request-scoped or transient (directly or through a dependency); handlers must be singleton providers`
184
+ );
185
+ }
103
186
  const prototype = typeof type === "function" ? type.prototype : null;
104
187
  const [first] = prototype === null ? [] : this.subscribedMethods(prototype);
105
188
  if (first !== void 0) {
@@ -115,6 +198,9 @@ RelayBusHandlerExplorer = __decorateClass([
115
198
  __decorateParam(1, Inject(MetadataScanner)),
116
199
  __decorateParam(2, Inject(RelayBus))
117
200
  ], RelayBusHandlerExplorer);
201
+ function isMethod(instance, entry) {
202
+ return typeof instance[entry.handlerName] === "function";
203
+ }
118
204
  var {
119
205
  ConfigurableModuleClass: RelayBusConfigurableModule,
120
206
  MODULE_OPTIONS_TOKEN: RELAYBUS_MODULE_OPTIONS
@@ -201,6 +287,6 @@ RelayBusModule = __decorateClass([
201
287
  })
202
288
  ], RelayBusModule);
203
289
 
204
- export { RelayBusHandlerExplorer, RelayBusHealth, RelayBusLifecycle, RelayBusModule, Subscribe };
290
+ export { EventConsumer, RelayBusHandlerExplorer, RelayBusHealth, RelayBusLifecycle, RelayBusModule, Subscribe };
205
291
  //# sourceMappingURL=index.js.map
206
292
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/subscribe.decorator.ts","../src/handler-explorer.ts","../src/module-options.ts","../src/relay-bus-health.ts","../src/relay-bus-lifecycle.ts","../src/relay-bus.module.ts"],"names":["Injectable","Inject","RelayBus"],"mappings":"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","file":"index.js","sourcesContent":["import 'reflect-metadata';\nimport type { EventClass, EventEnvelope, HandlerContext, SubscribeOptions } from '@relaybus/core';\n\n/**\n * Options of {@link Subscribe}: the core {@link SubscribeOptions}, except that\n * `id` is optional and defaults to `ClassName.methodName`.\n */\nexport type SubscribeDecoratorOptions = Omit<SubscribeOptions, 'id'> & {\n /**\n * Stable id of the handler. Deliveries are recorded under it, so renaming it\n * (or the class or method it is derived from) orphans pending deliveries.\n * Default: `${ClassName}.${methodName}`. Required when one method carries\n * several `@Subscribe` decorators.\n */\n readonly id?: string;\n};\n\n/** What one `@Subscribe` decorator recorded on a method. */\nexport interface SubscribeMetadata {\n readonly event: EventClass<unknown> | string;\n readonly options: SubscribeDecoratorOptions;\n}\n\nconst SUBSCRIBE_METADATA_KEY = Symbol.for('relaybus.nestjs.subscribe');\n\ntype HandlerMethod<TPayload> = (\n event: EventEnvelope<TPayload>,\n context: HandlerContext<never>,\n) => unknown;\n\ntype SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(\n target: object,\n propertyKey: string | symbol,\n descriptor: TypedPropertyDescriptor<TMethod>,\n) => void;\n\n/**\n * Subscribes a provider method to an event class or type. The method is\n * registered with `bus.subscribe` when the module initialises, with `this`\n * bound to the provider instance, and receives `(envelope, context)`.\n *\n * The provider must be a singleton (default scope) and belong to any module of\n * the application. The decorator name differs from `@nestjs/event-emitter`'s\n * `@OnEvent` so both can be used in one application.\n *\n * ```ts\n * @Injectable()\n * export class ShippingHandlers {\n * @Subscribe(OrderPlaced, { transactional: true })\n * async onOrderPlaced(event: EventEnvelope<{ orderId: string }>, context: HandlerContext) {}\n * }\n * ```\n */\nexport function Subscribe<TPayload>(\n event: EventClass<TPayload>,\n options?: SubscribeDecoratorOptions,\n): SubscribeDecorator<TPayload>;\nexport function Subscribe(\n eventType: string,\n options?: SubscribeDecoratorOptions,\n): SubscribeDecorator<never>;\nexport function Subscribe(\n event: EventClass<unknown> | string,\n options: SubscribeDecoratorOptions = {},\n): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void {\n return (target, propertyKey) => {\n // Keyed by (prototype, property) rather than stored on the method function, so a\n // decorator that later replaces the method does not drop the subscription.\n const existing =\n (Reflect.getOwnMetadata(SUBSCRIBE_METADATA_KEY, target, propertyKey) as\n SubscribeMetadata[] | undefined) ?? [];\n Reflect.defineMetadata(\n SUBSCRIBE_METADATA_KEY,\n [...existing, { event, options }],\n target,\n propertyKey,\n );\n };\n}\n\n/**\n * Reads the `@Subscribe` declarations of a method, in declaration order. The\n * prototype chain is searched, so a subclass inherits the declarations of the\n * nearest ancestor that declares them.\n */\nexport function readSubscribeMetadata(\n prototype: object,\n propertyKey: string | symbol,\n): readonly SubscribeMetadata[] {\n return (\n (Reflect.getMetadata(SUBSCRIBE_METADATA_KEY, prototype, propertyKey) as\n SubscribeMetadata[] | undefined) ?? []\n );\n}\n","import { Inject, Injectable, type OnModuleInit } from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper.js';\nimport { ConfigurationError, RelayBus, type EventHandler } from '@relaybus/core';\nimport { readSubscribeMetadata, type SubscribeMetadata } from './subscribe.decorator.js';\n\ninterface SubscribedMethod {\n readonly methodName: string;\n readonly declarations: readonly SubscribeMetadata[];\n}\n\n/**\n * Finds every `@Subscribe` method of every singleton provider in the\n * application and registers it on the bus before the bus starts. Providers\n * that are request-scoped or transient, directly or through a dependency, and\n * declare `@Subscribe` methods fail the boot. A request-scoped provider whose\n * token is neither a class nor an instance carrying the decorated prototype\n * (for example a string-token factory) cannot be inspected and is skipped.\n */\n@Injectable()\nexport class RelayBusHandlerExplorer implements OnModuleInit {\n constructor(\n @Inject(DiscoveryService) private readonly discovery: DiscoveryService,\n @Inject(MetadataScanner) private readonly metadataScanner: MetadataScanner,\n @Inject(RelayBus) private readonly bus: RelayBus,\n ) {}\n\n onModuleInit(): void {\n const registeredBy = new Map<string, string>();\n const visited = new Map<object, Set<string>>();\n for (const wrapper of this.discovery.getProviders()) {\n if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {\n this.assertNoHandlers(wrapper);\n continue;\n }\n const instance = wrapper.instance as unknown;\n if (typeof instance !== 'object' || instance === null) {\n continue;\n }\n const prototype = Object.getPrototypeOf(instance) as object | null;\n if (prototype === null) {\n continue;\n }\n const className = instance.constructor.name;\n for (const { methodName, declarations } of this.subscribedMethods(prototype)) {\n // A `useExisting` alias exposes the same instance under another token.\n const seen = visited.get(instance) ?? new Set<string>();\n visited.set(instance, seen);\n if (seen.has(methodName)) {\n continue;\n }\n seen.add(methodName);\n const method = (instance as Record<string, unknown>)[methodName] as (\n ...args: Parameters<EventHandler>\n ) => unknown;\n const location = `${className}.${methodName}`;\n for (const { event, options } of declarations) {\n const id = options.id ?? location;\n const previous = registeredBy.get(id);\n if (previous !== undefined) {\n throw new ConfigurationError(\n `Handler id '${id}' is used by both ${previous} and ${location}; ` +\n `pass a unique { id } to @Subscribe`,\n );\n }\n registeredBy.set(id, location);\n // Core resolves both classes and type strings; the overloads cannot express the union.\n this.bus.subscribe(\n event as never,\n (envelope, context) => method.call(instance, envelope, context),\n { ...options, id },\n );\n }\n }\n }\n }\n\n private subscribedMethods(prototype: object): SubscribedMethod[] {\n const methods: SubscribedMethod[] = [];\n for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {\n const declarations = readSubscribeMetadata(prototype, methodName);\n if (declarations.length > 0) {\n methods.push({ methodName, declarations });\n }\n }\n return methods;\n }\n\n /**\n * Request-scoped and transient wrappers have no usable instance (a factory\n * wrapper has none at all), so the decorated prototype is taken from the\n * provider class, or from the provider token for factories when it is a class.\n */\n private assertNoHandlers(wrapper: InstanceWrapper): void {\n const type = (wrapper.isFactory ? wrapper.token : wrapper.metatype) as unknown;\n const prototype = typeof type === 'function' ? (type.prototype as object) : null;\n const [first] = prototype === null ? [] : this.subscribedMethods(prototype);\n if (first !== undefined) {\n throw new ConfigurationError(\n `${(prototype as { constructor: { name: string } }).constructor.name}.${first.methodName} ` +\n `uses @Subscribe but its provider is request-scoped or transient ` +\n `(directly or through a dependency); handlers must be singleton providers`,\n );\n }\n }\n}\n","import { ConfigurableModuleBuilder } from '@nestjs/common';\nimport type { RelayBusOptions } from '@relaybus/core';\n\n/**\n * Options of {@link RelayBusModule}: everything {@link RelayBusOptions} accepts\n * except `logger`, which is always a Nest `Logger` with context `RelayBus`\n * (configure it with `app.useLogger`). Only `store` is required.\n */\nexport type RelayBusModuleOptions<TTransaction = unknown> = Omit<\n RelayBusOptions<TTransaction>,\n 'logger'\n>;\n\n/** Module-level extras accepted next to the options by `forRoot` and `forRootAsync`. */\nexport interface RelayBusModuleExtras {\n /** Make `RelayBus` and `RelayBusHealth` injectable everywhere. Default true. */\n readonly isGlobal?: boolean;\n}\n\n/** Implemented by classes passed as `useClass` / `useExisting` to `forRootAsync`. */\nexport interface RelayBusModuleOptionsFactory {\n create(): Promise<RelayBusModuleOptions> | RelayBusModuleOptions;\n}\n\nexport const {\n ConfigurableModuleClass: RelayBusConfigurableModule,\n MODULE_OPTIONS_TOKEN: RELAYBUS_MODULE_OPTIONS,\n} = new ConfigurableModuleBuilder<RelayBusModuleOptions>()\n .setClassMethodName('forRoot')\n .setExtras<RelayBusModuleExtras>({ isGlobal: true }, (definition, extras) => ({\n ...definition,\n global: extras.isGlobal === true,\n }))\n .build();\n","import { Inject, Injectable } from '@nestjs/common';\nimport { RelayBus, systemClock, type EventStoreStatistics } from '@relaybus/core';\nimport { RELAYBUS_MODULE_OPTIONS, type RelayBusModuleOptions } from './module-options.js';\n\n/** Result of {@link RelayBusHealth.check}. */\nexport interface RelayBusHealthReport {\n /**\n * `down` when the bus is not running, a transport reports itself not ready\n * (dead Kafka consumer, disconnected RabbitMQ) or the store cannot be queried.\n */\n readonly status: 'up' | 'down';\n readonly running: boolean;\n /** `bus.isReady()`: running and every transport ready. */\n readonly ready: boolean;\n /** Number of registered handlers and routes. */\n readonly handlers: number;\n /** Delivery counts; absent when the bus is not running or the store failed. */\n readonly statistics?: EventStoreStatistics;\n /** Milliseconds the oldest due delivery has waited; 0 when nothing is due. */\n readonly lagMilliseconds?: number;\n /** Message of the store error, when the statistics query failed. */\n readonly error?: string;\n}\n\n/**\n * Health probe of the bus without a dependency on `@nestjs/terminus`. Wrap\n * {@link check} in a terminus indicator (see the README). The lag is measured\n * with the `clock` given to the module options (the system clock by default),\n * the same clock the bus uses.\n */\n@Injectable()\nexport class RelayBusHealth {\n constructor(\n @Inject(RelayBus) private readonly bus: RelayBus,\n @Inject(RELAYBUS_MODULE_OPTIONS) private readonly options: RelayBusModuleOptions,\n ) {}\n\n async check(): Promise<RelayBusHealthReport> {\n const running = this.bus.isRunning;\n const ready = this.bus.isReady();\n const handlers = this.bus.handlers().length;\n if (!running) {\n return { status: 'down', running, ready, handlers };\n }\n try {\n const statistics = await this.bus.statistics();\n const clock = this.options.clock ?? systemClock;\n const lagMilliseconds =\n statistics.oldestDueAt === null\n ? 0\n : Math.max(0, clock.now().getTime() - statistics.oldestDueAt.getTime());\n return {\n status: ready ? 'up' : 'down',\n running,\n ready,\n handlers,\n statistics,\n lagMilliseconds,\n };\n } catch (error) {\n return {\n status: 'down',\n running,\n ready,\n handlers,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n","import {\n Inject,\n Injectable,\n type BeforeApplicationShutdown,\n type OnApplicationBootstrap,\n} from '@nestjs/common';\nimport { RelayBus } from '@relaybus/core';\n\n/**\n * Starts the bus once every handler is registered and the application has\n * bootstrapped, and stops it (draining running handlers) on shutdown. A start\n * failure rejects the hook, so the application does not boot.\n */\n@Injectable()\nexport class RelayBusLifecycle implements OnApplicationBootstrap, BeforeApplicationShutdown {\n constructor(@Inject(RelayBus) private readonly bus: RelayBus) {}\n\n async onApplicationBootstrap(): Promise<void> {\n await this.bus.start();\n }\n\n async beforeApplicationShutdown(): Promise<void> {\n await this.bus.stop();\n }\n}\n","import { Logger, Module } from '@nestjs/common';\nimport { DiscoveryModule } from '@nestjs/core';\nimport { RelayBus } from '@relaybus/core';\nimport { RelayBusHandlerExplorer } from './handler-explorer.js';\nimport {\n RELAYBUS_MODULE_OPTIONS,\n RelayBusConfigurableModule,\n type RelayBusModuleOptions,\n} from './module-options.js';\nimport { RelayBusHealth } from './relay-bus-health.js';\nimport { RelayBusLifecycle } from './relay-bus-lifecycle.js';\n\n/**\n * Registers one {@link RelayBus} for the application, discovers `@Subscribe`\n * handlers in every module, starts the bus after bootstrap and stops it on\n * shutdown. Global unless `isGlobal: false`.\n *\n * ```ts\n * RelayBusModule.forRoot({ store: new PostgresEventStore({ pool }), source: 'orders' })\n * ```\n */\n@Module({\n imports: [DiscoveryModule],\n providers: [\n {\n provide: RelayBus,\n inject: [RELAYBUS_MODULE_OPTIONS],\n useFactory: (options: RelayBusModuleOptions) =>\n new RelayBus({ ...options, logger: new Logger('RelayBus') }),\n },\n RelayBusHandlerExplorer,\n RelayBusLifecycle,\n RelayBusHealth,\n ],\n exports: [RelayBus, RelayBusHealth],\n})\nexport class RelayBusModule extends RelayBusConfigurableModule {}\n"]}
1
+ {"version":3,"sources":["../src/event-consumer.decorator.ts","../src/subscribe.decorator.ts","../src/handler-explorer.ts","../src/module-options.ts","../src/relay-bus-health.ts","../src/relay-bus-lifecycle.ts","../src/relay-bus.module.ts"],"names":["ConfigurationError","describeCatalog","Injectable","Inject","RelayBus"],"mappings":"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","file":"index.js","sourcesContent":["import 'reflect-metadata';\nimport {\n ConfigurationError,\n consume,\n describeCatalog,\n type Catalog,\n type CatalogEntry,\n type CatalogEventSelection,\n type EventOf,\n type HandlerContext,\n type SubscribeOptions,\n} from '@relaybus/core';\n\n/**\n * Options of {@link EventConsumer}: the core {@link SubscribeOptions} applied to\n * every handler of the class, except `id`, which is always\n * `${ClassName}.on${EventName}`, and optionally the events the class consumes.\n */\nexport type EventConsumerOptions<\n TCatalog extends Catalog = Catalog,\n TName extends keyof TCatalog = keyof TCatalog,\n> = Omit<SubscribeOptions, 'id'> & CatalogEventSelection<TName>;\n\n/** What one `@EventConsumer` decorator recorded on a class. */\nexport interface EventConsumerMetadata {\n readonly catalog: Catalog;\n /** Subscribe options applied to every handler of the class. */\n readonly options: Omit<SubscribeOptions, 'id'>;\n /**\n * Entries named by `options.events`, whose methods are all required;\n * undefined when the class consumes whichever handler methods it defines.\n */\n readonly selectedEntries: readonly CatalogEntry[] | undefined;\n}\n\n/**\n * A class whose instances have a handler method for each of the named catalog\n * events. The event parameter is checked strictly; the context is typed\n * `HandlerContext<never>` so a method may declare any transaction type.\n */\nexport type EventConsumerClass<\n TCatalog extends Catalog,\n TName extends keyof TCatalog,\n> = abstract new (...args: never[]) => {\n readonly [TKey in TName & string as `on${TKey}`]: (\n event: EventOf<TCatalog[TKey]>,\n context: HandlerContext<never>,\n ) => unknown;\n};\n\nconst EVENT_CONSUMER_METADATA_KEY = Symbol.for('relaybus.nestjs.event-consumer');\n\n/**\n * Subscribes a provider class to the events of a catalog: its\n * `on${EventName}` methods are registered when the module initialises, under\n * the stable id `${ClassName}.on${EventName}`, with `this` bound to the\n * provider instance.\n *\n * - Without `events`, every handler method the class defines is registered; a\n * class that defines none fails the boot, which catches misspelled names.\n * `implements HandlersOf<typeof Catalog>` makes the compiler require all of them.\n * - With `events`, exactly the methods of those events are registered (methods\n * of other events are ignored), and a class missing one of them fails to\n * compile and fails the boot. This is how a service consumes its part of a\n * catalog shared between services.\n *\n * `catalogs: [consume(Catalog, { events })]` in the module options makes the\n * application fail to boot if no provider handles a consumed event. Works next\n * to `@Subscribe` in the same class (give a method that carries both a\n * distinct `@Subscribe` id). The provider must be a singleton. Declarations\n * are inherited; a subclass that declares the same catalog again replaces the\n * inherited declaration of that catalog, and declaring one catalog twice on\n * the same class throws a {@link ConfigurationError}.\n *\n * ```ts\n * @Injectable()\n * @EventConsumer(OrderEvents, { events: ['OrderPlaced'], transactional: true })\n * export class BillingHandlers {\n * onOrderPlaced(event: EventOf<typeof OrderPlaced>) {}\n * }\n * ```\n */\nexport function EventConsumer<TCatalog extends Catalog>(\n catalog: TCatalog,\n options?: Omit<SubscribeOptions, 'id'> & { readonly events?: undefined },\n): ClassDecorator;\nexport function EventConsumer<TCatalog extends Catalog, const TName extends keyof TCatalog>(\n catalog: TCatalog,\n options: Omit<SubscribeOptions, 'id'> & { readonly events: readonly TName[] },\n): (target: EventConsumerClass<TCatalog, TName>) => void;\n/**\n * With options whose `events` are not statically known (a variable typed\n * {@link EventConsumerOptions}), the selection is checked when the module\n * initialises instead of at compile time.\n */\nexport function EventConsumer<TCatalog extends Catalog>(\n catalog: TCatalog,\n options: EventConsumerOptions<TCatalog>,\n): ClassDecorator;\nexport function EventConsumer(\n catalog: Catalog,\n {\n events,\n ...options\n }: Omit<SubscribeOptions, 'id'> & {\n readonly events?: readonly PropertyKey[] | undefined;\n } = {},\n): ClassDecorator {\n const { name } = describeCatalog(catalog);\n const selectedEntries =\n events === undefined\n ? undefined\n : consume(catalog, { events: events as readonly (keyof Catalog)[] }).entries;\n return (target) => {\n const existing =\n (Reflect.getOwnMetadata(EVENT_CONSUMER_METADATA_KEY, target) as\n EventConsumerMetadata[] | undefined) ?? [];\n if (existing.some((metadata) => metadata.catalog === catalog)) {\n throw new ConfigurationError(\n `@EventConsumer(${name ?? 'catalog'}) is declared twice on ${target.name}; ` +\n `merge the declarations into one`,\n );\n }\n Reflect.defineMetadata(\n EVENT_CONSUMER_METADATA_KEY,\n [...existing, { catalog, options, selectedEntries }],\n target,\n );\n };\n}\n\n/**\n * Reads the `@EventConsumer` declarations of a class and of its ancestors,\n * ancestors first, each in declaration order. When a class and an ancestor\n * both declare a catalog, the declaration nearest to the class wins.\n */\nexport function readEventConsumerMetadata(target: object): readonly EventConsumerMetadata[] {\n const lineage: object[] = [];\n for (let current = target; current !== Function.prototype;) {\n lineage.unshift(current);\n current = Object.getPrototypeOf(current) as object;\n }\n const byCatalog = new Map<Catalog, EventConsumerMetadata>();\n for (const type of lineage) {\n const own =\n (Reflect.getOwnMetadata(EVENT_CONSUMER_METADATA_KEY, type) as\n EventConsumerMetadata[] | undefined) ?? [];\n for (const metadata of own) {\n byCatalog.set(metadata.catalog, metadata);\n }\n }\n return [...byCatalog.values()];\n}\n","import 'reflect-metadata';\nimport type { EventClass, EventEnvelope, HandlerContext, SubscribeOptions } from '@relaybus/core';\n\n/**\n * Options of {@link Subscribe}: the core {@link SubscribeOptions}, except that\n * `id` is optional and defaults to `ClassName.methodName`.\n */\nexport type SubscribeDecoratorOptions = Omit<SubscribeOptions, 'id'> & {\n /**\n * Stable id of the handler. Deliveries are recorded under it, so renaming it\n * (or the class or method it is derived from) orphans pending deliveries.\n * Default: `${ClassName}.${methodName}`. Required when one method carries\n * several `@Subscribe` decorators.\n */\n readonly id?: string;\n};\n\n/** What one `@Subscribe` decorator recorded on a method. */\nexport interface SubscribeMetadata {\n readonly event: EventClass<unknown> | string;\n readonly options: SubscribeDecoratorOptions;\n}\n\nconst SUBSCRIBE_METADATA_KEY = Symbol.for('relaybus.nestjs.subscribe');\n\ntype HandlerMethod<TPayload> = (\n event: EventEnvelope<TPayload>,\n context: HandlerContext<never>,\n) => unknown;\n\ntype SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(\n target: object,\n propertyKey: string | symbol,\n descriptor: TypedPropertyDescriptor<TMethod>,\n) => void;\n\n/**\n * Subscribes a provider method to an event class or type. The method is\n * registered with `bus.subscribe` when the module initialises, with `this`\n * bound to the provider instance, and receives `(envelope, context)`.\n *\n * The provider must be a singleton (default scope) and belong to any module of\n * the application. The decorator name differs from `@nestjs/event-emitter`'s\n * `@OnEvent` so both can be used in one application.\n *\n * ```ts\n * @Injectable()\n * export class ShippingHandlers {\n * @Subscribe(OrderPlaced, { transactional: true })\n * async onOrderPlaced(event: EventEnvelope<{ orderId: string }>, context: HandlerContext) {}\n * }\n * ```\n */\nexport function Subscribe<TPayload>(\n event: EventClass<TPayload>,\n options?: SubscribeDecoratorOptions,\n): SubscribeDecorator<TPayload>;\nexport function Subscribe(\n eventType: string,\n options?: SubscribeDecoratorOptions,\n): SubscribeDecorator<never>;\nexport function Subscribe(\n event: EventClass<unknown> | string,\n options: SubscribeDecoratorOptions = {},\n): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void {\n return (target, propertyKey) => {\n // Keyed by (prototype, property) rather than stored on the method function, so a\n // decorator that later replaces the method does not drop the subscription.\n const existing =\n (Reflect.getOwnMetadata(SUBSCRIBE_METADATA_KEY, target, propertyKey) as\n SubscribeMetadata[] | undefined) ?? [];\n Reflect.defineMetadata(\n SUBSCRIBE_METADATA_KEY,\n [...existing, { event, options }],\n target,\n propertyKey,\n );\n };\n}\n\n/**\n * Reads the `@Subscribe` declarations of a method, in declaration order. The\n * prototype chain is searched, so a subclass inherits the declarations of the\n * nearest ancestor that declares them.\n */\nexport function readSubscribeMetadata(\n prototype: object,\n propertyKey: string | symbol,\n): readonly SubscribeMetadata[] {\n return (\n (Reflect.getMetadata(SUBSCRIBE_METADATA_KEY, prototype, propertyKey) as\n SubscribeMetadata[] | undefined) ?? []\n );\n}\n","import { Inject, Injectable, type OnModuleInit } from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper.js';\nimport {\n ConfigurationError,\n RelayBus,\n describeCatalog,\n type CatalogEntry,\n type EventHandler,\n} from '@relaybus/core';\nimport { readEventConsumerMetadata } from './event-consumer.decorator.js';\nimport { readSubscribeMetadata, type SubscribeMetadata } from './subscribe.decorator.js';\n\ninterface SubscribedMethod {\n readonly methodName: string;\n readonly declarations: readonly SubscribeMetadata[];\n}\n\n/**\n * Finds every `@Subscribe` method and every `@EventConsumer` class among the\n * singleton providers of the application and registers them on the bus before\n * the bus starts. Providers that are request-scoped or transient, directly or\n * through a dependency, and use either decorator fail the boot. A request-scoped provider whose\n * token is neither a class nor an instance carrying the decorated prototype\n * (for example a string-token factory) cannot be inspected and is skipped.\n */\n@Injectable()\nexport class RelayBusHandlerExplorer implements OnModuleInit {\n constructor(\n @Inject(DiscoveryService) private readonly discovery: DiscoveryService,\n @Inject(MetadataScanner) private readonly metadataScanner: MetadataScanner,\n @Inject(RelayBus) private readonly bus: RelayBus,\n ) {}\n\n onModuleInit(): void {\n const registeredBy = new Map<string, string>();\n const visited = new Map<object, Set<string>>();\n const consumers = new WeakSet<object>();\n for (const wrapper of this.discovery.getProviders()) {\n if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {\n this.assertNoHandlers(wrapper);\n continue;\n }\n const instance = wrapper.instance as unknown;\n if (typeof instance !== 'object' || instance === null) {\n continue;\n }\n const prototype = Object.getPrototypeOf(instance) as object | null;\n if (prototype === null) {\n continue;\n }\n const className = instance.constructor.name;\n for (const { methodName, declarations } of this.subscribedMethods(prototype)) {\n // A `useExisting` alias exposes the same instance under another token.\n const seen = visited.get(instance) ?? new Set<string>();\n visited.set(instance, seen);\n if (seen.has(methodName)) {\n continue;\n }\n seen.add(methodName);\n const method = (instance as Record<string, unknown>)[methodName] as (\n ...args: Parameters<EventHandler>\n ) => unknown;\n const location = `${className}.${methodName}`;\n for (const { event, options } of declarations) {\n const id = options.id ?? location;\n const previous = registeredBy.get(id);\n if (previous !== undefined) {\n throw new ConfigurationError(\n `Handler id '${id}' is used by both ${previous} and ${location}; ` +\n `pass a unique { id } to @Subscribe`,\n );\n }\n registeredBy.set(id, location);\n // Core resolves both classes and type strings; the overloads cannot express the union.\n this.bus.subscribe(\n event as never,\n (envelope, context) => method.call(instance, envelope, context),\n { ...options, id },\n );\n }\n }\n if (!consumers.has(instance)) {\n consumers.add(instance);\n this.registerEventConsumers(instance, className, registeredBy);\n }\n }\n }\n\n private registerEventConsumers(\n instance: object,\n className: string,\n registeredBy: Map<string, string>,\n ): void {\n const lookup = instance as Record<string, unknown>;\n for (const { catalog, options, selectedEntries } of readEventConsumerMetadata(\n instance.constructor,\n )) {\n const { name, entries } = describeCatalog(catalog);\n const label = name ?? 'catalog';\n const handlers = selectedEntries ?? entries.filter((entry) => isMethod(lookup, entry));\n const missing = handlers.filter((entry) => !isMethod(lookup, entry));\n if (missing.length > 0) {\n throw new ConfigurationError(\n `${className} uses @EventConsumer(${label}) but lacks the handler methods of events ` +\n `it consumes: ${missing.map((entry) => entry.handlerName).join(', ')}`,\n );\n }\n if (handlers.length === 0) {\n throw new ConfigurationError(\n `${className} uses @EventConsumer(${label}) but defines none of its handler ` +\n `methods (${entries.map((entry) => entry.handlerName).join(', ')})`,\n );\n }\n for (const entry of handlers) {\n const id = `${className}.${entry.handlerName}`;\n const previous = registeredBy.get(id);\n if (previous !== undefined) {\n throw new ConfigurationError(\n `Handler id '${id}' of @EventConsumer(${label}) is already used by ${previous}; ` +\n `give the @Subscribe a unique { id } or drop it`,\n );\n }\n registeredBy.set(id, `${className}.${entry.handlerName}`);\n const method = lookup[entry.handlerName] as (...args: Parameters<EventHandler>) => unknown;\n this.bus.subscribe(\n entry.eventClass as never,\n (envelope, context) => method.call(instance, envelope, context),\n { ...options, id },\n );\n }\n }\n }\n\n private subscribedMethods(prototype: object): SubscribedMethod[] {\n const methods: SubscribedMethod[] = [];\n for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {\n const declarations = readSubscribeMetadata(prototype, methodName);\n if (declarations.length > 0) {\n methods.push({ methodName, declarations });\n }\n }\n return methods;\n }\n\n /**\n * Request-scoped and transient wrappers have no usable instance (a factory\n * wrapper has none at all), so the decorated prototype is taken from the\n * provider class, or from the provider token for factories when it is a class.\n */\n private assertNoHandlers(wrapper: InstanceWrapper): void {\n const type = (wrapper.isFactory ? wrapper.token : wrapper.metatype) as unknown;\n if (typeof type === 'function' && readEventConsumerMetadata(type).length > 0) {\n throw new ConfigurationError(\n `${type.name} uses @EventConsumer but its provider is request-scoped or transient ` +\n `(directly or through a dependency); handlers must be singleton providers`,\n );\n }\n const prototype = typeof type === 'function' ? (type.prototype as object) : null;\n const [first] = prototype === null ? [] : this.subscribedMethods(prototype);\n if (first !== undefined) {\n throw new ConfigurationError(\n `${(prototype as { constructor: { name: string } }).constructor.name}.${first.methodName} ` +\n `uses @Subscribe but its provider is request-scoped or transient ` +\n `(directly or through a dependency); handlers must be singleton providers`,\n );\n }\n }\n}\n\nfunction isMethod(instance: Record<string, unknown>, entry: CatalogEntry): boolean {\n return typeof instance[entry.handlerName] === 'function';\n}\n","import { ConfigurableModuleBuilder } from '@nestjs/common';\nimport type { RelayBusOptions } from '@relaybus/core';\n\n/**\n * Options of {@link RelayBusModule}: everything {@link RelayBusOptions} accepts\n * except `logger`, which is always a Nest `Logger` with context `RelayBus`\n * (configure it with `app.useLogger`). Only `store` is required.\n */\nexport type RelayBusModuleOptions<TTransaction = unknown> = Omit<\n RelayBusOptions<TTransaction>,\n 'logger'\n>;\n\n/** Module-level extras accepted next to the options by `forRoot` and `forRootAsync`. */\nexport interface RelayBusModuleExtras {\n /** Make `RelayBus` and `RelayBusHealth` injectable everywhere. Default true. */\n readonly isGlobal?: boolean;\n}\n\n/** Implemented by classes passed as `useClass` / `useExisting` to `forRootAsync`. */\nexport interface RelayBusModuleOptionsFactory {\n create(): Promise<RelayBusModuleOptions> | RelayBusModuleOptions;\n}\n\nexport const {\n ConfigurableModuleClass: RelayBusConfigurableModule,\n MODULE_OPTIONS_TOKEN: RELAYBUS_MODULE_OPTIONS,\n} = new ConfigurableModuleBuilder<RelayBusModuleOptions>()\n .setClassMethodName('forRoot')\n .setExtras<RelayBusModuleExtras>({ isGlobal: true }, (definition, extras) => ({\n ...definition,\n global: extras.isGlobal === true,\n }))\n .build();\n","import { Inject, Injectable } from '@nestjs/common';\nimport { RelayBus, systemClock, type EventStoreStatistics } from '@relaybus/core';\nimport { RELAYBUS_MODULE_OPTIONS, type RelayBusModuleOptions } from './module-options.js';\n\n/** Result of {@link RelayBusHealth.check}. */\nexport interface RelayBusHealthReport {\n /**\n * `down` when the bus is not running, a transport reports itself not ready\n * (dead Kafka consumer, disconnected RabbitMQ) or the store cannot be queried.\n */\n readonly status: 'up' | 'down';\n readonly running: boolean;\n /** `bus.isReady()`: running and every transport ready. */\n readonly ready: boolean;\n /** Number of registered handlers and routes. */\n readonly handlers: number;\n /** Delivery counts; absent when the bus is not running or the store failed. */\n readonly statistics?: EventStoreStatistics;\n /** Milliseconds the oldest due delivery has waited; 0 when nothing is due. */\n readonly lagMilliseconds?: number;\n /** Message of the store error, when the statistics query failed. */\n readonly error?: string;\n}\n\n/**\n * Health probe of the bus without a dependency on `@nestjs/terminus`. Wrap\n * {@link check} in a terminus indicator (see the README). The lag is measured\n * with the `clock` given to the module options (the system clock by default),\n * the same clock the bus uses.\n */\n@Injectable()\nexport class RelayBusHealth {\n constructor(\n @Inject(RelayBus) private readonly bus: RelayBus,\n @Inject(RELAYBUS_MODULE_OPTIONS) private readonly options: RelayBusModuleOptions,\n ) {}\n\n async check(): Promise<RelayBusHealthReport> {\n const running = this.bus.isRunning;\n const ready = this.bus.isReady();\n const handlers = this.bus.handlers().length;\n if (!running) {\n return { status: 'down', running, ready, handlers };\n }\n try {\n const statistics = await this.bus.statistics();\n const clock = this.options.clock ?? systemClock;\n const lagMilliseconds =\n statistics.oldestDueAt === null\n ? 0\n : Math.max(0, clock.now().getTime() - statistics.oldestDueAt.getTime());\n return {\n status: ready ? 'up' : 'down',\n running,\n ready,\n handlers,\n statistics,\n lagMilliseconds,\n };\n } catch (error) {\n return {\n status: 'down',\n running,\n ready,\n handlers,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n","import {\n Inject,\n Injectable,\n type BeforeApplicationShutdown,\n type OnApplicationBootstrap,\n} from '@nestjs/common';\nimport { RelayBus } from '@relaybus/core';\n\n/**\n * Starts the bus once every handler is registered and the application has\n * bootstrapped, and stops it (draining running handlers) on shutdown. A start\n * failure rejects the hook, so the application does not boot.\n */\n@Injectable()\nexport class RelayBusLifecycle implements OnApplicationBootstrap, BeforeApplicationShutdown {\n constructor(@Inject(RelayBus) private readonly bus: RelayBus) {}\n\n async onApplicationBootstrap(): Promise<void> {\n await this.bus.start();\n }\n\n async beforeApplicationShutdown(): Promise<void> {\n await this.bus.stop();\n }\n}\n","import { Logger, Module } from '@nestjs/common';\nimport { DiscoveryModule } from '@nestjs/core';\nimport { RelayBus } from '@relaybus/core';\nimport { RelayBusHandlerExplorer } from './handler-explorer.js';\nimport {\n RELAYBUS_MODULE_OPTIONS,\n RelayBusConfigurableModule,\n type RelayBusModuleOptions,\n} from './module-options.js';\nimport { RelayBusHealth } from './relay-bus-health.js';\nimport { RelayBusLifecycle } from './relay-bus-lifecycle.js';\n\n/**\n * Registers one {@link RelayBus} for the application, discovers `@Subscribe`\n * handlers in every module, starts the bus after bootstrap and stops it on\n * shutdown. Global unless `isGlobal: false`.\n *\n * ```ts\n * RelayBusModule.forRoot({ store: new PostgresEventStore({ pool }), source: 'orders' })\n * ```\n */\n@Module({\n imports: [DiscoveryModule],\n providers: [\n {\n provide: RelayBus,\n inject: [RELAYBUS_MODULE_OPTIONS],\n useFactory: (options: RelayBusModuleOptions) =>\n new RelayBus({ ...options, logger: new Logger('RelayBus') }),\n },\n RelayBusHandlerExplorer,\n RelayBusLifecycle,\n RelayBusHealth,\n ],\n exports: [RelayBus, RelayBusHealth],\n})\nexport class RelayBusModule extends RelayBusConfigurableModule {}\n"]}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@relaybus/nestjs",
3
- "version": "0.1.0",
4
- "description": "NestJS module for relaybus: decorator-based event handlers, forRoot/forRootAsync configuration and lifecycle integration.",
3
+ "version": "0.2.0",
4
+ "description": "NestJS event bus module for relaybus: @Subscribe handlers on a transactional outbox, with retries, dead letters and graceful shutdown, so events are never lost between your database and your handlers.",
5
5
  "license": "MIT",
6
6
  "author": "Berkcan Şavur <berkcansavur@gmail.com>",
7
- "homepage": "https://github.com/berkcansavur/relaybus#readme",
7
+ "homepage": "https://berkcansavur.github.io/relaybus/packages/nestjs",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/berkcansavur/relaybus.git",
@@ -13,12 +13,24 @@
13
13
  "bugs": "https://github.com/berkcansavur/relaybus/issues",
14
14
  "keywords": [
15
15
  "relaybus",
16
- "event-bus",
17
- "outbox",
18
16
  "nestjs",
19
- "nest",
17
+ "nestjs-event-bus",
18
+ "nestjs-microservices",
19
+ "nestjs-outbox",
20
+ "nestjs-events",
21
+ "event-bus",
22
+ "event-driven",
23
+ "transactional-outbox",
24
+ "outbox-pattern",
25
+ "domain-events",
26
+ "reliable-events",
27
+ "message-bus",
28
+ "at-least-once",
29
+ "idempotent-consumer",
30
+ "dead-letter-queue",
31
+ "retry",
20
32
  "decorator",
21
- "module"
33
+ "typescript"
22
34
  ],
23
35
  "type": "module",
24
36
  "sideEffects": false,
@@ -55,7 +67,7 @@
55
67
  "@nestjs/core": "^11.0.0 || ^12.0.0",
56
68
  "reflect-metadata": "^0.1.13 || ^0.2.0",
57
69
  "rxjs": "^7.1.0",
58
- "@relaybus/core": "^0.1.0"
70
+ "@relaybus/core": "^0.2.0"
59
71
  },
60
72
  "devDependencies": {
61
73
  "@nestjs/common": "12.1.3",
@@ -63,8 +75,8 @@
63
75
  "@nestjs/testing": "12.1.3",
64
76
  "reflect-metadata": "0.2.2",
65
77
  "rxjs": "7.8.2",
66
- "@relaybus/testing": "0.1.0",
67
- "@relaybus/core": "0.1.0"
78
+ "@relaybus/testing": "0.2.0",
79
+ "@relaybus/core": "0.2.0"
68
80
  },
69
81
  "scripts": {
70
82
  "build": "tsup",