@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 +88 -0
- package/dist/index.cjs +89 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -7
- package/dist/index.d.ts +76 -7
- package/dist/index.js +90 -4
- package/dist/index.js.map +1 -1
- package/package.json +22 -10
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
|
package/dist/index.cjs.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":["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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
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.
|
|
4
|
-
"description": "NestJS module for relaybus:
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
67
|
-
"@relaybus/core": "0.
|
|
78
|
+
"@relaybus/testing": "0.2.0",
|
|
79
|
+
"@relaybus/core": "0.2.0"
|
|
68
80
|
},
|
|
69
81
|
"scripts": {
|
|
70
82
|
"build": "tsup",
|