@relaybus/nestjs 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Berkcan Şavur
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,238 @@
1
- # Temporary Holding Version
1
+ # @relaybus/nestjs
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ NestJS integration for [relaybus](../core): one module, one decorator, correct lifecycle.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @relaybus/core @relaybus/nestjs
9
+ ```
10
+
11
+ Peer dependencies: `@nestjs/common` and `@nestjs/core` (`^11 || ^12`), `reflect-metadata`, `rxjs`.
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ // app.module.ts
17
+ @Module({
18
+ imports: [RelayBusModule.forRoot({ store: new PostgresEventStore({ pool }), source: 'orders' })],
19
+ })
20
+ export class AppModule {}
21
+
22
+ // shipping.handlers.ts - any provider of any module
23
+ @Injectable()
24
+ export class ShippingHandlers {
25
+ @Subscribe(OrderPlaced)
26
+ async onOrderPlaced(event: EventEnvelope<{ orderId: string }>) {
27
+ /* ... */
28
+ }
29
+ }
30
+
31
+ // order.service.ts
32
+ await this.bus.transaction(async (tx) => {
33
+ await tx.query('INSERT INTO orders ...');
34
+ await this.bus.publish(new OrderPlaced({ orderId }), { transaction: tx });
35
+ });
36
+ ```
37
+
38
+ That is all: the module is global, the handler is discovered, the bus starts after bootstrap and
39
+ stops on shutdown. Inject the bus anywhere with `constructor(private readonly bus: RelayBus) {}`;
40
+ add the transaction type when you want it typed: `RelayBus<PoolClient>` (the generic is erased at
41
+ runtime, so DI is unaffected).
42
+
43
+ Call `app.enableShutdownHooks()` in `main.ts` so SIGTERM/SIGINT close the app and let the bus
44
+ drain running handlers (`dispatcher.shutdownTimeout`, default 30s).
45
+
46
+ ## `RelayBusModule`
47
+
48
+ - `forRoot(options)`
49
+ - `forRootAsync({ imports?, inject?, useFactory })`, or `useClass` / `useExisting` (a class with
50
+ `create(): RelayBusModuleOptions | Promise<RelayBusModuleOptions>`, see `RelayBusModuleOptionsFactory`)
51
+
52
+ Both accept `isGlobal` next to the options. It defaults to `true`; with `false`, import the module
53
+ wherever `RelayBus` is injected. `RelayBus` and `RelayBusHealth` are exported.
54
+
55
+ ### Options
56
+
57
+ Everything of core's `RelayBusOptions` passes through unchanged, except `logger`: the bus always
58
+ logs through a Nest `Logger` with context `RelayBus`, so `app.useLogger(...)` controls it.
59
+
60
+ | Option | Default | Notes |
61
+ | --------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------- |
62
+ | `store` | required | An `EventStore`: `@relaybus/postgres`, `@relaybus/mongodb`, `InMemoryEventStore` |
63
+ | `source` | `'relaybus'` | Service name recorded on every event; set it to your app name |
64
+ | `transactionMode` | `'required'` | `'optional'` lets `publish` open its own transaction |
65
+ | `transactionResolver` | none | Finds the caller's active transaction (see below) |
66
+ | `retry` | core default | Default retry policy of every handler and route |
67
+ | `handlerTimeout` | `false` | Default handler timeout |
68
+ | `dispatcher.enabled` | `true` | `false` on publish-only processes (API pods, scripts) |
69
+ | `dispatcher.concurrency` | `10` | Handlers running at once |
70
+ | `dispatcher.batchSize` | concurrency | Deliveries claimed per query |
71
+ | `dispatcher.pollInterval` | `'1s'` | Wait between claims when idle |
72
+ | `dispatcher.leaseDuration` | `'30s'` | Renewed every third of it |
73
+ | `dispatcher.shutdownTimeout` | `'30s'` | How long shutdown waits for running handlers |
74
+ | `retention.completed` / `.dead` / `.interval` | `'7d'` / `false` / `'1h'` | Cleanup of finished deliveries |
75
+ | `transports`, `routes`, `subscriptions` | none | Broker fan-out and fan-in (`@relaybus/kafka`, `@relaybus/rabbitmq`) |
76
+ | `setupStore` | `true` | Create tables/indexes on start |
77
+ | `observers` | none | Metrics and tracing hooks |
78
+ | `clock`, `generateIdentifier`, `random` | system | Mainly for tests |
79
+ | `isGlobal` | `true` | Module-level, see above |
80
+
81
+ ## `@Subscribe(event, options?)`
82
+
83
+ ```ts
84
+ @Subscribe(OrderPlaced) // class
85
+ @Subscribe('order.placed.v1', { concurrency: 2 }) // type string
86
+ @Subscribe(OrderPlaced, { id: 'billing.invoice', transactional: true, timeout: '10s' })
87
+ ```
88
+
89
+ Options are core's `SubscribeOptions` with `id` optional. Methods receive `(envelope, context)`;
90
+ with a class argument the envelope payload is typed from the event.
91
+
92
+ **Why `@Subscribe`:** `@nestjs/event-emitter` already owns `@OnEvent`, and the two are commonly
93
+ installed together. `@Subscribe` mirrors core's `bus.subscribe` and cannot collide.
94
+
95
+ Rules, all enforced at boot (the application fails to start, no silent skips):
96
+
97
+ - **Stable ids.** The default id is `ClassName.methodName`. Deliveries are stored under the id, so
98
+ renaming the class or method orphans pending deliveries; pin `{ id }` on handlers that matter.
99
+ - **Duplicate ids** fail init with both locations named. A method with several `@Subscribe`
100
+ decorators must give each an explicit `id`.
101
+ - **Singletons only.** Handlers on request-scoped or transient providers (or default-scoped
102
+ providers that depend on one) are rejected: deliveries run outside any request, so there is no
103
+ request instance to call. This covers class, `useClass` and `useFactory` providers (a factory is
104
+ checked through its class token; a request-scoped factory under a string token cannot be
105
+ inspected). Providers without `@Subscribe` are never inspected for scope.
106
+ - `this` is bound to the provider instance. Only providers are scanned, in every module. A handler
107
+ reachable through several tokens (`useExisting`) is registered once. Declarations are stored per
108
+ class and method, so decorators that wrap the method later do not drop them, and subclasses
109
+ inherit the handlers of their base class.
110
+
111
+ ## Lifecycle
112
+
113
+ 1. `onModuleInit`: handlers are discovered and registered on the bus.
114
+ 2. `onApplicationBootstrap`: `bus.start()` creates the schema (unless `setupStore: false`),
115
+ connects transports and starts the dispatcher. If it rejects (database down, broker
116
+ unreachable) the application does not boot.
117
+ 3. `beforeApplicationShutdown`: `bus.stop()` stops claiming, waits for running handlers up to
118
+ `shutdownTimeout`, then closes transports and the store. Needs `app.enableShutdownHooks()` for
119
+ signals; `app.close()` runs it regardless. Nest runs `onApplicationShutdown` hooks (where
120
+ TypeORM, Mongoose and most pools disconnect) after this, so handlers still have their
121
+ connections while draining. If your own provider disconnects in `onModuleDestroy` (common with
122
+ Prisma), move it to `onApplicationShutdown`.
123
+
124
+ ## Transactions
125
+
126
+ Inside `bus.transaction(fn)` every `publish` joins the transaction. Without it, publish needs a
127
+ transaction: pass `{ transaction }`, configure `transactionResolver`, or set
128
+ `transactionMode: 'optional'` (event stays durable but is no longer atomic with your writes).
129
+
130
+ ### Transactional handlers
131
+
132
+ ```ts
133
+ @Subscribe(PaymentCaptured, { id: 'ledger.record-capture', transactional: true })
134
+ async record(event: EventEnvelope<{ paymentId: string }>, context: HandlerContext<PoolClient>) {
135
+ // Writes through context.transaction commit atomically with the delivery completion:
136
+ // effectively-once processing. Keep these handlers short.
137
+ await context.transaction!.query('INSERT INTO ledger ...', [event.payload.paymentId]);
138
+ await this.bus.publish(new LedgerUpdated({ ... }), { transaction: context.transaction! });
139
+ }
140
+ ```
141
+
142
+ Non-transactional handlers are at-least-once: make them idempotent (deduplicate on `event.id`).
143
+
144
+ ### ORM integration
145
+
146
+ Prefer `transactionResolver`, so services call `bus.publish(event)` inside whatever transaction
147
+ the ORM already opened. The resolver is called synchronously and must return `undefined` when
148
+ there is no active transaction.
149
+
150
+ ```ts
151
+ // @nestjs-cls/transactional (not a dependency of this package)
152
+ RelayBusModule.forRootAsync({
153
+ inject: [TransactionHost],
154
+ useFactory: (txHost: TransactionHost) => ({
155
+ store,
156
+ transactionResolver: () => (txHost.isTransactionActive() ? txHost.tx : undefined),
157
+ }),
158
+ });
159
+ ```
160
+
161
+ - **Prisma / raw `pg`:** `@relaybus/postgres` store; Prisma's interactive transaction client is
162
+ adapted by the store package, see its README.
163
+ - **TypeORM:** `@relaybus/postgres` over the same `DataSource` connection; resolve the active
164
+ `EntityManager`'s query runner in `transactionResolver`.
165
+ - **MongoDB:** `@relaybus/mongodb` store; the transaction is the `ClientSession` (needs a replica set).
166
+
167
+ ## Health
168
+
169
+ `RelayBusHealth` (exported by the module) has no `@nestjs/terminus` dependency.
170
+
171
+ ```ts
172
+ const report = await health.check();
173
+ // { status: 'up' | 'down', running, ready, handlers, statistics?, lagMilliseconds?, error? }
174
+ ```
175
+
176
+ `down` when the bus is not running, a transport is not ready (`ready` is `bus.isReady()`: for
177
+ example a dead Kafka consumer or a disconnected RabbitMQ channel) or the store cannot be queried.
178
+ `lagMilliseconds` is how long the oldest due delivery has waited (measured with the `clock` from
179
+ the module options, the system clock by default), a good alert signal. With terminus 11:
180
+
181
+ ```ts
182
+ @Injectable()
183
+ export class RelayBusIndicator {
184
+ constructor(
185
+ private readonly indicators: HealthIndicatorService,
186
+ private readonly relayBus: RelayBusHealth,
187
+ ) {}
188
+
189
+ async isHealthy(key = 'relaybus') {
190
+ const indicator = this.indicators.check(key);
191
+ const { status, ...details } = await this.relayBus.check();
192
+ return status === 'up' ? indicator.up(details) : indicator.down(details);
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## Startup failure and restarts
198
+
199
+ The bus starts during application bootstrap. If the start fails (store setup, transport connect),
200
+ application boot fails with that error. A bus whose start failed is stopped for good and cannot be
201
+ started again: restart the process rather than retrying in place.
202
+
203
+ ## Testing
204
+
205
+ Use `@nestjs/testing` with `InMemoryEventStore` and turn the dispatcher off so tests control time:
206
+
207
+ ```ts
208
+ const moduleRef = await Test.createTestingModule({
209
+ imports: [
210
+ RelayBusModule.forRoot({ store: new InMemoryEventStore(), dispatcher: { enabled: false } }),
211
+ AppFeatureModule,
212
+ ],
213
+ }).compile();
214
+ await moduleRef.init();
215
+
216
+ await moduleRef.get(OrderService).place('o-1');
217
+ await moduleRef.get(RelayBus).drain(); // runs every due delivery, including handlers' follow-up events
218
+ // assert on side effects
219
+ await moduleRef.close();
220
+ ```
221
+
222
+ - `bus.runOnce()` runs a single batch; `bus.drain()` loops until nothing is due.
223
+ - Retries and delays are scheduled in the future: pass `clock: new FakeClock()` from
224
+ `@relaybus/testing`, `clock.advance(5_000)`, then `drain()` again.
225
+ - `bus.listDeliveries({ status: 'dead' })` shows handlers that exhausted their retries.
226
+
227
+ ## Troubleshooting
228
+
229
+ - **`Nest can't resolve dependencies of ... (?)`** when injecting `RelayBus`: the module is not
230
+ global (`isGlobal: false`) and not imported by the consuming module.
231
+ - **Handler never runs:** the provider is not listed in any module `providers`, the process runs
232
+ with `dispatcher.enabled: false`, or the event type differs (check `bus.handlers()`).
233
+ - **`uses @Subscribe but its provider is request-scoped or transient`:** make the provider a
234
+ singleton; inject request data through the event payload instead.
235
+ - **Vitest/esbuild:** esbuild does not emit decorator metadata; use explicit `@Inject(Token)` in
236
+ test classes.
237
+ - **App hangs on shutdown:** a handler ignores `context.signal`; it is waited for until
238
+ `dispatcher.shutdownTimeout`.
package/dist/index.cjs ADDED
@@ -0,0 +1,211 @@
1
+ 'use strict';
2
+
3
+ var common = require('@nestjs/common');
4
+ var core$1 = require('@nestjs/core');
5
+ var core = require('@relaybus/core');
6
+ require('reflect-metadata');
7
+
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __decorateClass = (decorators, target, key, kind) => {
10
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
11
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
12
+ if (decorator = decorators[i])
13
+ result = (decorator(result)) || result;
14
+ return result;
15
+ };
16
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
17
+ var SUBSCRIBE_METADATA_KEY = /* @__PURE__ */ Symbol.for("relaybus.nestjs.subscribe");
18
+ function Subscribe(event, options = {}) {
19
+ return (target, propertyKey) => {
20
+ const existing = Reflect.getOwnMetadata(SUBSCRIBE_METADATA_KEY, target, propertyKey) ?? [];
21
+ Reflect.defineMetadata(
22
+ SUBSCRIBE_METADATA_KEY,
23
+ [...existing, { event, options }],
24
+ target,
25
+ propertyKey
26
+ );
27
+ };
28
+ }
29
+ function readSubscribeMetadata(prototype, propertyKey) {
30
+ return Reflect.getMetadata(SUBSCRIBE_METADATA_KEY, prototype, propertyKey) ?? [];
31
+ }
32
+
33
+ // src/handler-explorer.ts
34
+ exports.RelayBusHandlerExplorer = class RelayBusHandlerExplorer {
35
+ constructor(discovery, metadataScanner, bus) {
36
+ this.discovery = discovery;
37
+ this.metadataScanner = metadataScanner;
38
+ this.bus = bus;
39
+ }
40
+ discovery;
41
+ metadataScanner;
42
+ bus;
43
+ onModuleInit() {
44
+ const registeredBy = /* @__PURE__ */ new Map();
45
+ const visited = /* @__PURE__ */ new Map();
46
+ for (const wrapper of this.discovery.getProviders()) {
47
+ if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {
48
+ this.assertNoHandlers(wrapper);
49
+ continue;
50
+ }
51
+ const instance = wrapper.instance;
52
+ if (typeof instance !== "object" || instance === null) {
53
+ continue;
54
+ }
55
+ const prototype = Object.getPrototypeOf(instance);
56
+ if (prototype === null) {
57
+ continue;
58
+ }
59
+ const className = instance.constructor.name;
60
+ for (const { methodName, declarations } of this.subscribedMethods(prototype)) {
61
+ const seen = visited.get(instance) ?? /* @__PURE__ */ new Set();
62
+ visited.set(instance, seen);
63
+ if (seen.has(methodName)) {
64
+ continue;
65
+ }
66
+ seen.add(methodName);
67
+ const method = instance[methodName];
68
+ const location = `${className}.${methodName}`;
69
+ for (const { event, options } of declarations) {
70
+ const id = options.id ?? location;
71
+ const previous = registeredBy.get(id);
72
+ if (previous !== void 0) {
73
+ throw new core.ConfigurationError(
74
+ `Handler id '${id}' is used by both ${previous} and ${location}; pass a unique { id } to @Subscribe`
75
+ );
76
+ }
77
+ registeredBy.set(id, location);
78
+ this.bus.subscribe(
79
+ event,
80
+ (envelope, context) => method.call(instance, envelope, context),
81
+ { ...options, id }
82
+ );
83
+ }
84
+ }
85
+ }
86
+ }
87
+ subscribedMethods(prototype) {
88
+ const methods = [];
89
+ for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {
90
+ const declarations = readSubscribeMetadata(prototype, methodName);
91
+ if (declarations.length > 0) {
92
+ methods.push({ methodName, declarations });
93
+ }
94
+ }
95
+ return methods;
96
+ }
97
+ /**
98
+ * Request-scoped and transient wrappers have no usable instance (a factory
99
+ * wrapper has none at all), so the decorated prototype is taken from the
100
+ * provider class, or from the provider token for factories when it is a class.
101
+ */
102
+ assertNoHandlers(wrapper) {
103
+ const type = wrapper.isFactory ? wrapper.token : wrapper.metatype;
104
+ const prototype = typeof type === "function" ? type.prototype : null;
105
+ const [first] = prototype === null ? [] : this.subscribedMethods(prototype);
106
+ if (first !== void 0) {
107
+ throw new core.ConfigurationError(
108
+ `${prototype.constructor.name}.${first.methodName} uses @Subscribe but its provider is request-scoped or transient (directly or through a dependency); handlers must be singleton providers`
109
+ );
110
+ }
111
+ }
112
+ };
113
+ exports.RelayBusHandlerExplorer = __decorateClass([
114
+ common.Injectable(),
115
+ __decorateParam(0, common.Inject(core$1.DiscoveryService)),
116
+ __decorateParam(1, common.Inject(core$1.MetadataScanner)),
117
+ __decorateParam(2, common.Inject(core.RelayBus))
118
+ ], exports.RelayBusHandlerExplorer);
119
+ var {
120
+ ConfigurableModuleClass: RelayBusConfigurableModule,
121
+ MODULE_OPTIONS_TOKEN: RELAYBUS_MODULE_OPTIONS
122
+ } = new common.ConfigurableModuleBuilder().setClassMethodName("forRoot").setExtras({ isGlobal: true }, (definition, extras) => ({
123
+ ...definition,
124
+ global: extras.isGlobal === true
125
+ })).build();
126
+
127
+ // src/relay-bus-health.ts
128
+ exports.RelayBusHealth = class RelayBusHealth {
129
+ constructor(bus, options) {
130
+ this.bus = bus;
131
+ this.options = options;
132
+ }
133
+ bus;
134
+ options;
135
+ async check() {
136
+ const running = this.bus.isRunning;
137
+ const ready = this.bus.isReady();
138
+ const handlers = this.bus.handlers().length;
139
+ if (!running) {
140
+ return { status: "down", running, ready, handlers };
141
+ }
142
+ try {
143
+ const statistics = await this.bus.statistics();
144
+ const clock = this.options.clock ?? core.systemClock;
145
+ const lagMilliseconds = statistics.oldestDueAt === null ? 0 : Math.max(0, clock.now().getTime() - statistics.oldestDueAt.getTime());
146
+ return {
147
+ status: ready ? "up" : "down",
148
+ running,
149
+ ready,
150
+ handlers,
151
+ statistics,
152
+ lagMilliseconds
153
+ };
154
+ } catch (error) {
155
+ return {
156
+ status: "down",
157
+ running,
158
+ ready,
159
+ handlers,
160
+ error: error instanceof Error ? error.message : String(error)
161
+ };
162
+ }
163
+ }
164
+ };
165
+ exports.RelayBusHealth = __decorateClass([
166
+ common.Injectable(),
167
+ __decorateParam(0, common.Inject(core.RelayBus)),
168
+ __decorateParam(1, common.Inject(RELAYBUS_MODULE_OPTIONS))
169
+ ], exports.RelayBusHealth);
170
+ exports.RelayBusLifecycle = class RelayBusLifecycle {
171
+ constructor(bus) {
172
+ this.bus = bus;
173
+ }
174
+ bus;
175
+ async onApplicationBootstrap() {
176
+ await this.bus.start();
177
+ }
178
+ async beforeApplicationShutdown() {
179
+ await this.bus.stop();
180
+ }
181
+ };
182
+ exports.RelayBusLifecycle = __decorateClass([
183
+ common.Injectable(),
184
+ __decorateParam(0, common.Inject(core.RelayBus))
185
+ ], exports.RelayBusLifecycle);
186
+ exports.RelayBusModule = class RelayBusModule extends RelayBusConfigurableModule {
187
+ };
188
+ exports.RelayBusModule = __decorateClass([
189
+ common.Module({
190
+ imports: [core$1.DiscoveryModule],
191
+ providers: [
192
+ {
193
+ provide: core.RelayBus,
194
+ inject: [RELAYBUS_MODULE_OPTIONS],
195
+ useFactory: (options) => new core.RelayBus({ ...options, logger: new common.Logger("RelayBus") })
196
+ },
197
+ exports.RelayBusHandlerExplorer,
198
+ exports.RelayBusLifecycle,
199
+ exports.RelayBusHealth
200
+ ],
201
+ exports: [core.RelayBus, exports.RelayBusHealth]
202
+ })
203
+ ], exports.RelayBusModule);
204
+
205
+ Object.defineProperty(exports, "RelayBus", {
206
+ enumerable: true,
207
+ get: function () { return core.RelayBus; }
208
+ });
209
+ exports.Subscribe = Subscribe;
210
+ //# sourceMappingURL=index.cjs.map
211
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,143 @@
1
+ import * as _nestjs_common from '@nestjs/common';
2
+ import { OnModuleInit, OnApplicationBootstrap, BeforeApplicationShutdown } from '@nestjs/common';
3
+ 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
+
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
12
+ * token is neither a class nor an instance carrying the decorated prototype
13
+ * (for example a string-token factory) cannot be inspected and is skipped.
14
+ */
15
+ declare class RelayBusHandlerExplorer implements OnModuleInit {
16
+ private readonly discovery;
17
+ private readonly metadataScanner;
18
+ private readonly bus;
19
+ constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, bus: RelayBus);
20
+ onModuleInit(): void;
21
+ private subscribedMethods;
22
+ /**
23
+ * Request-scoped and transient wrappers have no usable instance (a factory
24
+ * wrapper has none at all), so the decorated prototype is taken from the
25
+ * provider class, or from the provider token for factories when it is a class.
26
+ */
27
+ private assertNoHandlers;
28
+ }
29
+
30
+ /**
31
+ * Options of {@link RelayBusModule}: everything {@link RelayBusOptions} accepts
32
+ * except `logger`, which is always a Nest `Logger` with context `RelayBus`
33
+ * (configure it with `app.useLogger`). Only `store` is required.
34
+ */
35
+ type RelayBusModuleOptions<TTransaction = unknown> = Omit<RelayBusOptions<TTransaction>, 'logger'>;
36
+ /** Module-level extras accepted next to the options by `forRoot` and `forRootAsync`. */
37
+ interface RelayBusModuleExtras {
38
+ /** Make `RelayBus` and `RelayBusHealth` injectable everywhere. Default true. */
39
+ readonly isGlobal?: boolean;
40
+ }
41
+ /** Implemented by classes passed as `useClass` / `useExisting` to `forRootAsync`. */
42
+ interface RelayBusModuleOptionsFactory {
43
+ create(): Promise<RelayBusModuleOptions> | RelayBusModuleOptions;
44
+ }
45
+ declare const RelayBusConfigurableModule: _nestjs_common.ConfigurableModuleCls<RelayBusModuleOptions<unknown>, "forRoot", "create", RelayBusModuleExtras>;
46
+
47
+ /** Result of {@link RelayBusHealth.check}. */
48
+ interface RelayBusHealthReport {
49
+ /**
50
+ * `down` when the bus is not running, a transport reports itself not ready
51
+ * (dead Kafka consumer, disconnected RabbitMQ) or the store cannot be queried.
52
+ */
53
+ readonly status: 'up' | 'down';
54
+ readonly running: boolean;
55
+ /** `bus.isReady()`: running and every transport ready. */
56
+ readonly ready: boolean;
57
+ /** Number of registered handlers and routes. */
58
+ readonly handlers: number;
59
+ /** Delivery counts; absent when the bus is not running or the store failed. */
60
+ readonly statistics?: EventStoreStatistics;
61
+ /** Milliseconds the oldest due delivery has waited; 0 when nothing is due. */
62
+ readonly lagMilliseconds?: number;
63
+ /** Message of the store error, when the statistics query failed. */
64
+ readonly error?: string;
65
+ }
66
+ /**
67
+ * Health probe of the bus without a dependency on `@nestjs/terminus`. Wrap
68
+ * {@link check} in a terminus indicator (see the README). The lag is measured
69
+ * with the `clock` given to the module options (the system clock by default),
70
+ * the same clock the bus uses.
71
+ */
72
+ declare class RelayBusHealth {
73
+ private readonly bus;
74
+ private readonly options;
75
+ constructor(bus: RelayBus, options: RelayBusModuleOptions);
76
+ check(): Promise<RelayBusHealthReport>;
77
+ }
78
+
79
+ /**
80
+ * Starts the bus once every handler is registered and the application has
81
+ * bootstrapped, and stops it (draining running handlers) on shutdown. A start
82
+ * failure rejects the hook, so the application does not boot.
83
+ */
84
+ declare class RelayBusLifecycle implements OnApplicationBootstrap, BeforeApplicationShutdown {
85
+ private readonly bus;
86
+ constructor(bus: RelayBus);
87
+ onApplicationBootstrap(): Promise<void>;
88
+ beforeApplicationShutdown(): Promise<void>;
89
+ }
90
+
91
+ /**
92
+ * Registers one {@link RelayBus} for the application, discovers `@Subscribe`
93
+ * handlers in every module, starts the bus after bootstrap and stops it on
94
+ * shutdown. Global unless `isGlobal: false`.
95
+ *
96
+ * ```ts
97
+ * RelayBusModule.forRoot({ store: new PostgresEventStore({ pool }), source: 'orders' })
98
+ * ```
99
+ */
100
+ declare class RelayBusModule extends RelayBusConfigurableModule {
101
+ }
102
+
103
+ /**
104
+ * Options of {@link Subscribe}: the core {@link SubscribeOptions}, except that
105
+ * `id` is optional and defaults to `ClassName.methodName`.
106
+ */
107
+ type SubscribeDecoratorOptions = Omit<SubscribeOptions, 'id'> & {
108
+ /**
109
+ * Stable id of the handler. Deliveries are recorded under it, so renaming it
110
+ * (or the class or method it is derived from) orphans pending deliveries.
111
+ * Default: `${ClassName}.${methodName}`. Required when one method carries
112
+ * several `@Subscribe` decorators.
113
+ */
114
+ readonly id?: string;
115
+ };
116
+ /** What one `@Subscribe` decorator recorded on a method. */
117
+ interface SubscribeMetadata {
118
+ readonly event: EventClass<unknown> | string;
119
+ readonly options: SubscribeDecoratorOptions;
120
+ }
121
+ type HandlerMethod<TPayload> = (event: EventEnvelope<TPayload>, context: HandlerContext<never>) => unknown;
122
+ type SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<TMethod>) => void;
123
+ /**
124
+ * Subscribes a provider method to an event class or type. The method is
125
+ * registered with `bus.subscribe` when the module initialises, with `this`
126
+ * bound to the provider instance, and receives `(envelope, context)`.
127
+ *
128
+ * The provider must be a singleton (default scope) and belong to any module of
129
+ * the application. The decorator name differs from `@nestjs/event-emitter`'s
130
+ * `@OnEvent` so both can be used in one application.
131
+ *
132
+ * ```ts
133
+ * @Injectable()
134
+ * export class ShippingHandlers {
135
+ * @Subscribe(OrderPlaced, { transactional: true })
136
+ * async onOrderPlaced(event: EventEnvelope<{ orderId: string }>, context: HandlerContext) {}
137
+ * }
138
+ * ```
139
+ */
140
+ declare function Subscribe<TPayload>(event: EventClass<TPayload>, options?: SubscribeDecoratorOptions): SubscribeDecorator<TPayload>;
141
+ declare function Subscribe(eventType: string, options?: SubscribeDecoratorOptions): SubscribeDecorator<never>;
142
+
143
+ export { RelayBusHandlerExplorer, RelayBusHealth, type RelayBusHealthReport, RelayBusLifecycle, RelayBusModule, type RelayBusModuleExtras, type RelayBusModuleOptions, type RelayBusModuleOptionsFactory, Subscribe, type SubscribeDecoratorOptions, type SubscribeMetadata };
@@ -0,0 +1,143 @@
1
+ import * as _nestjs_common from '@nestjs/common';
2
+ import { OnModuleInit, OnApplicationBootstrap, BeforeApplicationShutdown } from '@nestjs/common';
3
+ 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
+
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
12
+ * token is neither a class nor an instance carrying the decorated prototype
13
+ * (for example a string-token factory) cannot be inspected and is skipped.
14
+ */
15
+ declare class RelayBusHandlerExplorer implements OnModuleInit {
16
+ private readonly discovery;
17
+ private readonly metadataScanner;
18
+ private readonly bus;
19
+ constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, bus: RelayBus);
20
+ onModuleInit(): void;
21
+ private subscribedMethods;
22
+ /**
23
+ * Request-scoped and transient wrappers have no usable instance (a factory
24
+ * wrapper has none at all), so the decorated prototype is taken from the
25
+ * provider class, or from the provider token for factories when it is a class.
26
+ */
27
+ private assertNoHandlers;
28
+ }
29
+
30
+ /**
31
+ * Options of {@link RelayBusModule}: everything {@link RelayBusOptions} accepts
32
+ * except `logger`, which is always a Nest `Logger` with context `RelayBus`
33
+ * (configure it with `app.useLogger`). Only `store` is required.
34
+ */
35
+ type RelayBusModuleOptions<TTransaction = unknown> = Omit<RelayBusOptions<TTransaction>, 'logger'>;
36
+ /** Module-level extras accepted next to the options by `forRoot` and `forRootAsync`. */
37
+ interface RelayBusModuleExtras {
38
+ /** Make `RelayBus` and `RelayBusHealth` injectable everywhere. Default true. */
39
+ readonly isGlobal?: boolean;
40
+ }
41
+ /** Implemented by classes passed as `useClass` / `useExisting` to `forRootAsync`. */
42
+ interface RelayBusModuleOptionsFactory {
43
+ create(): Promise<RelayBusModuleOptions> | RelayBusModuleOptions;
44
+ }
45
+ declare const RelayBusConfigurableModule: _nestjs_common.ConfigurableModuleCls<RelayBusModuleOptions<unknown>, "forRoot", "create", RelayBusModuleExtras>;
46
+
47
+ /** Result of {@link RelayBusHealth.check}. */
48
+ interface RelayBusHealthReport {
49
+ /**
50
+ * `down` when the bus is not running, a transport reports itself not ready
51
+ * (dead Kafka consumer, disconnected RabbitMQ) or the store cannot be queried.
52
+ */
53
+ readonly status: 'up' | 'down';
54
+ readonly running: boolean;
55
+ /** `bus.isReady()`: running and every transport ready. */
56
+ readonly ready: boolean;
57
+ /** Number of registered handlers and routes. */
58
+ readonly handlers: number;
59
+ /** Delivery counts; absent when the bus is not running or the store failed. */
60
+ readonly statistics?: EventStoreStatistics;
61
+ /** Milliseconds the oldest due delivery has waited; 0 when nothing is due. */
62
+ readonly lagMilliseconds?: number;
63
+ /** Message of the store error, when the statistics query failed. */
64
+ readonly error?: string;
65
+ }
66
+ /**
67
+ * Health probe of the bus without a dependency on `@nestjs/terminus`. Wrap
68
+ * {@link check} in a terminus indicator (see the README). The lag is measured
69
+ * with the `clock` given to the module options (the system clock by default),
70
+ * the same clock the bus uses.
71
+ */
72
+ declare class RelayBusHealth {
73
+ private readonly bus;
74
+ private readonly options;
75
+ constructor(bus: RelayBus, options: RelayBusModuleOptions);
76
+ check(): Promise<RelayBusHealthReport>;
77
+ }
78
+
79
+ /**
80
+ * Starts the bus once every handler is registered and the application has
81
+ * bootstrapped, and stops it (draining running handlers) on shutdown. A start
82
+ * failure rejects the hook, so the application does not boot.
83
+ */
84
+ declare class RelayBusLifecycle implements OnApplicationBootstrap, BeforeApplicationShutdown {
85
+ private readonly bus;
86
+ constructor(bus: RelayBus);
87
+ onApplicationBootstrap(): Promise<void>;
88
+ beforeApplicationShutdown(): Promise<void>;
89
+ }
90
+
91
+ /**
92
+ * Registers one {@link RelayBus} for the application, discovers `@Subscribe`
93
+ * handlers in every module, starts the bus after bootstrap and stops it on
94
+ * shutdown. Global unless `isGlobal: false`.
95
+ *
96
+ * ```ts
97
+ * RelayBusModule.forRoot({ store: new PostgresEventStore({ pool }), source: 'orders' })
98
+ * ```
99
+ */
100
+ declare class RelayBusModule extends RelayBusConfigurableModule {
101
+ }
102
+
103
+ /**
104
+ * Options of {@link Subscribe}: the core {@link SubscribeOptions}, except that
105
+ * `id` is optional and defaults to `ClassName.methodName`.
106
+ */
107
+ type SubscribeDecoratorOptions = Omit<SubscribeOptions, 'id'> & {
108
+ /**
109
+ * Stable id of the handler. Deliveries are recorded under it, so renaming it
110
+ * (or the class or method it is derived from) orphans pending deliveries.
111
+ * Default: `${ClassName}.${methodName}`. Required when one method carries
112
+ * several `@Subscribe` decorators.
113
+ */
114
+ readonly id?: string;
115
+ };
116
+ /** What one `@Subscribe` decorator recorded on a method. */
117
+ interface SubscribeMetadata {
118
+ readonly event: EventClass<unknown> | string;
119
+ readonly options: SubscribeDecoratorOptions;
120
+ }
121
+ type HandlerMethod<TPayload> = (event: EventEnvelope<TPayload>, context: HandlerContext<never>) => unknown;
122
+ type SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<TMethod>) => void;
123
+ /**
124
+ * Subscribes a provider method to an event class or type. The method is
125
+ * registered with `bus.subscribe` when the module initialises, with `this`
126
+ * bound to the provider instance, and receives `(envelope, context)`.
127
+ *
128
+ * The provider must be a singleton (default scope) and belong to any module of
129
+ * the application. The decorator name differs from `@nestjs/event-emitter`'s
130
+ * `@OnEvent` so both can be used in one application.
131
+ *
132
+ * ```ts
133
+ * @Injectable()
134
+ * export class ShippingHandlers {
135
+ * @Subscribe(OrderPlaced, { transactional: true })
136
+ * async onOrderPlaced(event: EventEnvelope<{ orderId: string }>, context: HandlerContext) {}
137
+ * }
138
+ * ```
139
+ */
140
+ declare function Subscribe<TPayload>(event: EventClass<TPayload>, options?: SubscribeDecoratorOptions): SubscribeDecorator<TPayload>;
141
+ declare function Subscribe(eventType: string, options?: SubscribeDecoratorOptions): SubscribeDecorator<never>;
142
+
143
+ export { RelayBusHandlerExplorer, RelayBusHealth, type RelayBusHealthReport, RelayBusLifecycle, RelayBusModule, type RelayBusModuleExtras, type RelayBusModuleOptions, type RelayBusModuleOptionsFactory, Subscribe, type SubscribeDecoratorOptions, type SubscribeMetadata };
package/dist/index.js ADDED
@@ -0,0 +1,206 @@
1
+ import { Injectable, Inject, ConfigurableModuleBuilder, Module, Logger } from '@nestjs/common';
2
+ 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
+
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __decorateClass = (decorators, target, key, kind) => {
9
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
10
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
11
+ if (decorator = decorators[i])
12
+ result = (decorator(result)) || result;
13
+ return result;
14
+ };
15
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
16
+ var SUBSCRIBE_METADATA_KEY = /* @__PURE__ */ Symbol.for("relaybus.nestjs.subscribe");
17
+ function Subscribe(event, options = {}) {
18
+ return (target, propertyKey) => {
19
+ const existing = Reflect.getOwnMetadata(SUBSCRIBE_METADATA_KEY, target, propertyKey) ?? [];
20
+ Reflect.defineMetadata(
21
+ SUBSCRIBE_METADATA_KEY,
22
+ [...existing, { event, options }],
23
+ target,
24
+ propertyKey
25
+ );
26
+ };
27
+ }
28
+ function readSubscribeMetadata(prototype, propertyKey) {
29
+ return Reflect.getMetadata(SUBSCRIBE_METADATA_KEY, prototype, propertyKey) ?? [];
30
+ }
31
+
32
+ // src/handler-explorer.ts
33
+ var RelayBusHandlerExplorer = class {
34
+ constructor(discovery, metadataScanner, bus) {
35
+ this.discovery = discovery;
36
+ this.metadataScanner = metadataScanner;
37
+ this.bus = bus;
38
+ }
39
+ discovery;
40
+ metadataScanner;
41
+ bus;
42
+ onModuleInit() {
43
+ const registeredBy = /* @__PURE__ */ new Map();
44
+ const visited = /* @__PURE__ */ new Map();
45
+ for (const wrapper of this.discovery.getProviders()) {
46
+ if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) {
47
+ this.assertNoHandlers(wrapper);
48
+ continue;
49
+ }
50
+ const instance = wrapper.instance;
51
+ if (typeof instance !== "object" || instance === null) {
52
+ continue;
53
+ }
54
+ const prototype = Object.getPrototypeOf(instance);
55
+ if (prototype === null) {
56
+ continue;
57
+ }
58
+ const className = instance.constructor.name;
59
+ for (const { methodName, declarations } of this.subscribedMethods(prototype)) {
60
+ const seen = visited.get(instance) ?? /* @__PURE__ */ new Set();
61
+ visited.set(instance, seen);
62
+ if (seen.has(methodName)) {
63
+ continue;
64
+ }
65
+ seen.add(methodName);
66
+ const method = instance[methodName];
67
+ const location = `${className}.${methodName}`;
68
+ for (const { event, options } of declarations) {
69
+ const id = options.id ?? location;
70
+ const previous = registeredBy.get(id);
71
+ if (previous !== void 0) {
72
+ throw new ConfigurationError(
73
+ `Handler id '${id}' is used by both ${previous} and ${location}; pass a unique { id } to @Subscribe`
74
+ );
75
+ }
76
+ registeredBy.set(id, location);
77
+ this.bus.subscribe(
78
+ event,
79
+ (envelope, context) => method.call(instance, envelope, context),
80
+ { ...options, id }
81
+ );
82
+ }
83
+ }
84
+ }
85
+ }
86
+ subscribedMethods(prototype) {
87
+ const methods = [];
88
+ for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {
89
+ const declarations = readSubscribeMetadata(prototype, methodName);
90
+ if (declarations.length > 0) {
91
+ methods.push({ methodName, declarations });
92
+ }
93
+ }
94
+ return methods;
95
+ }
96
+ /**
97
+ * Request-scoped and transient wrappers have no usable instance (a factory
98
+ * wrapper has none at all), so the decorated prototype is taken from the
99
+ * provider class, or from the provider token for factories when it is a class.
100
+ */
101
+ assertNoHandlers(wrapper) {
102
+ const type = wrapper.isFactory ? wrapper.token : wrapper.metatype;
103
+ const prototype = typeof type === "function" ? type.prototype : null;
104
+ const [first] = prototype === null ? [] : this.subscribedMethods(prototype);
105
+ if (first !== void 0) {
106
+ throw new ConfigurationError(
107
+ `${prototype.constructor.name}.${first.methodName} uses @Subscribe but its provider is request-scoped or transient (directly or through a dependency); handlers must be singleton providers`
108
+ );
109
+ }
110
+ }
111
+ };
112
+ RelayBusHandlerExplorer = __decorateClass([
113
+ Injectable(),
114
+ __decorateParam(0, Inject(DiscoveryService)),
115
+ __decorateParam(1, Inject(MetadataScanner)),
116
+ __decorateParam(2, Inject(RelayBus))
117
+ ], RelayBusHandlerExplorer);
118
+ var {
119
+ ConfigurableModuleClass: RelayBusConfigurableModule,
120
+ MODULE_OPTIONS_TOKEN: RELAYBUS_MODULE_OPTIONS
121
+ } = new ConfigurableModuleBuilder().setClassMethodName("forRoot").setExtras({ isGlobal: true }, (definition, extras) => ({
122
+ ...definition,
123
+ global: extras.isGlobal === true
124
+ })).build();
125
+
126
+ // src/relay-bus-health.ts
127
+ var RelayBusHealth = class {
128
+ constructor(bus, options) {
129
+ this.bus = bus;
130
+ this.options = options;
131
+ }
132
+ bus;
133
+ options;
134
+ async check() {
135
+ const running = this.bus.isRunning;
136
+ const ready = this.bus.isReady();
137
+ const handlers = this.bus.handlers().length;
138
+ if (!running) {
139
+ return { status: "down", running, ready, handlers };
140
+ }
141
+ try {
142
+ const statistics = await this.bus.statistics();
143
+ const clock = this.options.clock ?? systemClock;
144
+ const lagMilliseconds = statistics.oldestDueAt === null ? 0 : Math.max(0, clock.now().getTime() - statistics.oldestDueAt.getTime());
145
+ return {
146
+ status: ready ? "up" : "down",
147
+ running,
148
+ ready,
149
+ handlers,
150
+ statistics,
151
+ lagMilliseconds
152
+ };
153
+ } catch (error) {
154
+ return {
155
+ status: "down",
156
+ running,
157
+ ready,
158
+ handlers,
159
+ error: error instanceof Error ? error.message : String(error)
160
+ };
161
+ }
162
+ }
163
+ };
164
+ RelayBusHealth = __decorateClass([
165
+ Injectable(),
166
+ __decorateParam(0, Inject(RelayBus)),
167
+ __decorateParam(1, Inject(RELAYBUS_MODULE_OPTIONS))
168
+ ], RelayBusHealth);
169
+ var RelayBusLifecycle = class {
170
+ constructor(bus) {
171
+ this.bus = bus;
172
+ }
173
+ bus;
174
+ async onApplicationBootstrap() {
175
+ await this.bus.start();
176
+ }
177
+ async beforeApplicationShutdown() {
178
+ await this.bus.stop();
179
+ }
180
+ };
181
+ RelayBusLifecycle = __decorateClass([
182
+ Injectable(),
183
+ __decorateParam(0, Inject(RelayBus))
184
+ ], RelayBusLifecycle);
185
+ var RelayBusModule = class extends RelayBusConfigurableModule {
186
+ };
187
+ RelayBusModule = __decorateClass([
188
+ Module({
189
+ imports: [DiscoveryModule],
190
+ providers: [
191
+ {
192
+ provide: RelayBus,
193
+ inject: [RELAYBUS_MODULE_OPTIONS],
194
+ useFactory: (options) => new RelayBus({ ...options, logger: new Logger("RelayBus") })
195
+ },
196
+ RelayBusHandlerExplorer,
197
+ RelayBusLifecycle,
198
+ RelayBusHealth
199
+ ],
200
+ exports: [RelayBus, RelayBusHealth]
201
+ })
202
+ ], RelayBusModule);
203
+
204
+ export { RelayBusHandlerExplorer, RelayBusHealth, RelayBusLifecycle, RelayBusModule, Subscribe };
205
+ //# sourceMappingURL=index.js.map
206
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
1
  {
2
2
  "name": "@relaybus/nestjs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "NestJS module for relaybus: decorator-based event handlers, forRoot/forRootAsync configuration and lifecycle integration.",
5
+ "license": "MIT",
6
+ "author": "Berkcan Şavur <berkcansavur@gmail.com>",
7
+ "homepage": "https://github.com/berkcansavur/relaybus#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/berkcansavur/relaybus.git",
11
+ "directory": "packages/nestjs"
12
+ },
13
+ "bugs": "https://github.com/berkcansavur/relaybus/issues",
14
+ "keywords": [
15
+ "relaybus",
16
+ "event-bus",
17
+ "outbox",
18
+ "nestjs",
19
+ "nest",
20
+ "decorator",
21
+ "module"
22
+ ],
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "import": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "require": {
35
+ "types": "./dist/index.d.cts",
36
+ "default": "./dist/index.cjs"
37
+ }
38
+ },
39
+ "./package.json": "./package.json"
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "engines": {
47
+ "node": ">=22.12.0"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "provenance": true
52
+ },
53
+ "peerDependencies": {
54
+ "@nestjs/common": "^11.0.0 || ^12.0.0",
55
+ "@nestjs/core": "^11.0.0 || ^12.0.0",
56
+ "reflect-metadata": "^0.1.13 || ^0.2.0",
57
+ "rxjs": "^7.1.0",
58
+ "@relaybus/core": "^0.1.0"
59
+ },
60
+ "devDependencies": {
61
+ "@nestjs/common": "12.1.3",
62
+ "@nestjs/core": "12.1.3",
63
+ "@nestjs/testing": "12.1.3",
64
+ "reflect-metadata": "0.2.2",
65
+ "rxjs": "7.8.2",
66
+ "@relaybus/testing": "0.1.0",
67
+ "@relaybus/core": "0.1.0"
68
+ },
69
+ "scripts": {
70
+ "build": "tsup",
71
+ "typecheck": "tsc -p tsconfig.build.json --noEmit"
72
+ }
6
73
  }