@relaybus/nestjs 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,17 +1,17 @@
1
- import { Catalog, SubscribeOptions, EventOf, HandlerContext, CatalogEventSelection, CatalogEntry, RelayBus, RelayBusOptions, EventStoreStatistics, EventClass, EventEnvelope } from '@relaybus/core';
1
+ import { Catalog, SubscribeOptions, EventOf, HandlerContext, CatalogEventSelection, CatalogEntry, RelayBus, RelayBusOptions, EventStoreStatistics, CatalogDeclaration, EventClass, EventEnvelope } from '@relaybus/core';
2
2
  export { RelayBus } from '@relaybus/core';
3
3
  import * as _nestjs_common from '@nestjs/common';
4
- import { OnModuleInit, OnApplicationBootstrap, BeforeApplicationShutdown } from '@nestjs/common';
5
- import { DiscoveryService, MetadataScanner } from '@nestjs/core';
4
+ import { OnModuleInit, OnApplicationBootstrap, BeforeApplicationShutdown, OnApplicationShutdown, DynamicModule } from '@nestjs/common';
5
+ import { DiscoveryService, MetadataScanner, ModulesContainer } from '@nestjs/core';
6
6
 
7
7
  /**
8
- * Options of {@link EventConsumer}: the core {@link SubscribeOptions} applied to
8
+ * Options of {@link RelayBusCatalogConsumer}: the core {@link SubscribeOptions} applied to
9
9
  * every handler of the class, except `id`, which is always
10
10
  * `${ClassName}.on${EventName}`, and optionally the events the class consumes.
11
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 {
12
+ type RelayBusCatalogConsumerOptions<TCatalog extends Catalog = Catalog, TName extends keyof TCatalog = keyof TCatalog> = Omit<SubscribeOptions, 'id'> & CatalogEventSelection<TName>;
13
+ /** What one `@RelayBusCatalogConsumer` decorator recorded on a class. */
14
+ interface RelayBusCatalogConsumerMetadata {
15
15
  readonly catalog: Catalog;
16
16
  /** Subscribe options applied to every handler of the class. */
17
17
  readonly options: Omit<SubscribeOptions, 'id'>;
@@ -26,7 +26,7 @@ interface EventConsumerMetadata {
26
26
  * events. The event parameter is checked strictly; the context is typed
27
27
  * `HandlerContext<never>` so a method may declare any transaction type.
28
28
  */
29
- type EventConsumerClass<TCatalog extends Catalog, TName extends keyof TCatalog> = abstract new (...args: never[]) => {
29
+ type RelayBusCatalogConsumerClass<TCatalog extends Catalog, TName extends keyof TCatalog> = abstract new (...args: never[]) => {
30
30
  readonly [TKey in TName & string as `on${TKey}`]: (event: EventOf<TCatalog[TKey]>, context: HandlerContext<never>) => unknown;
31
31
  };
32
32
  /**
@@ -45,35 +45,46 @@ type EventConsumerClass<TCatalog extends Catalog, TName extends keyof TCatalog>
45
45
  *
46
46
  * `catalogs: [consume(Catalog, { events })]` in the module options makes the
47
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
48
+ * to `@RelayBusConsumer` in the same class (give a method that carries both a
49
+ * distinct `@RelayBusConsumer` id). The provider must be a singleton. Declarations
50
50
  * are inherited; a subclass that declares the same catalog again replaces the
51
51
  * inherited declaration of that catalog, and declaring one catalog twice on
52
52
  * the same class throws a {@link ConfigurationError}.
53
53
  *
54
54
  * ```ts
55
55
  * @Injectable()
56
- * @EventConsumer(OrderEvents, { events: ['OrderPlaced'], transactional: true })
56
+ * @RelayBusCatalogConsumer(OrderEvents, { events: ['OrderPlaced'], transactional: true })
57
57
  * export class BillingHandlers {
58
58
  * onOrderPlaced(event: EventOf<typeof OrderPlaced>) {}
59
59
  * }
60
60
  * ```
61
61
  */
62
- declare function EventConsumer<TCatalog extends Catalog>(catalog: TCatalog, options?: Omit<SubscribeOptions, 'id'> & {
62
+ declare function RelayBusCatalogConsumer<TCatalog extends Catalog>(catalog: TCatalog, options?: Omit<SubscribeOptions, 'id'> & {
63
63
  readonly events?: undefined;
64
64
  }): ClassDecorator;
65
- declare function EventConsumer<TCatalog extends Catalog, const TName extends keyof TCatalog>(catalog: TCatalog, options: Omit<SubscribeOptions, 'id'> & {
65
+ declare function RelayBusCatalogConsumer<TCatalog extends Catalog, const TName extends keyof TCatalog>(catalog: TCatalog, options: Omit<SubscribeOptions, 'id'> & {
66
66
  readonly events: readonly TName[];
67
- }): (target: EventConsumerClass<TCatalog, TName>) => void;
67
+ }): (target: RelayBusCatalogConsumerClass<TCatalog, TName>) => void;
68
68
  /**
69
69
  * With options whose `events` are not statically known (a variable typed
70
- * {@link EventConsumerOptions}), the selection is checked when the module
70
+ * {@link RelayBusCatalogConsumerOptions}), the selection is checked when the module
71
71
  * initialises instead of at compile time.
72
72
  */
73
- declare function EventConsumer<TCatalog extends Catalog>(catalog: TCatalog, options: EventConsumerOptions<TCatalog>): ClassDecorator;
73
+ declare function RelayBusCatalogConsumer<TCatalog extends Catalog>(catalog: TCatalog, options: RelayBusCatalogConsumerOptions<TCatalog>): ClassDecorator;
74
+ /**
75
+ * @deprecated Renamed to {@link RelayBusCatalogConsumer}, which behaves identically; this alias is
76
+ * removed in 1.0.0.
77
+ */
78
+ declare const EventConsumer: typeof RelayBusCatalogConsumer;
79
+ /** @deprecated Renamed to {@link RelayBusCatalogConsumerOptions}; this alias is removed in 1.0.0. */
80
+ type EventConsumerOptions<TCatalog extends Catalog = Catalog, TName extends keyof TCatalog = keyof TCatalog> = RelayBusCatalogConsumerOptions<TCatalog, TName>;
81
+ /** @deprecated Renamed to {@link RelayBusCatalogConsumerMetadata}; this alias is removed in 1.0.0. */
82
+ type EventConsumerMetadata = RelayBusCatalogConsumerMetadata;
83
+ /** @deprecated Renamed to {@link RelayBusCatalogConsumerClass}; this alias is removed in 1.0.0. */
84
+ type EventConsumerClass<TCatalog extends Catalog, TName extends keyof TCatalog> = RelayBusCatalogConsumerClass<TCatalog, TName>;
74
85
 
75
86
  /**
76
- * Finds every `@Subscribe` method and every `@EventConsumer` class among the
87
+ * Finds every `@RelayBusConsumer` method and every `@RelayBusCatalogConsumer` class among the
77
88
  * singleton providers of the application and registers them on the bus before
78
89
  * the bus starts. Providers that are request-scoped or transient, directly or
79
90
  * through a dependency, and use either decorator fail the boot. A request-scoped provider whose
@@ -84,9 +95,19 @@ declare class RelayBusHandlerExplorer implements OnModuleInit {
84
95
  private readonly discovery;
85
96
  private readonly metadataScanner;
86
97
  private readonly bus;
87
- constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, bus: RelayBus);
98
+ private readonly modules;
99
+ constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, bus: RelayBus, modules: ModulesContainer);
88
100
  onModuleInit(): void;
101
+ /** Checks the `RelayBusModule.forFeature` contract of every module that imports one. */
102
+ private assertModuleContracts;
103
+ private importersOf;
104
+ /** Records the event types the provider of `wrapper` consumes under the provider's module. */
105
+ private recordConsumedTypes;
106
+ private registerProvider;
107
+ private registerSubscribedMethod;
89
108
  private registerEventConsumers;
109
+ /** The catalog entries `lookup` handles: the selected ones, else every entry it has a method for. */
110
+ private catalogHandlers;
90
111
  private subscribedMethods;
91
112
  /**
92
113
  * Request-scoped and transient wrappers have no usable instance (a factory
@@ -147,19 +168,52 @@ declare class RelayBusHealth {
147
168
 
148
169
  /**
149
170
  * Starts the bus once every handler is registered and the application has
150
- * bootstrapped, and stops it (draining running handlers) on shutdown. A start
151
- * failure rejects the hook, so the application does not boot.
171
+ * bootstrapped, and stops it (draining running handlers, within `dispatcher.shutdownTimeout`) on
172
+ * shutdown: `bus.stop()` resolves at that deadline at the latest, so a hung query or broker cannot
173
+ * hold `app.close()` until the platform kills the process. A start
174
+ * failure rejects the hook, so the application does not boot. `@RelayBusTransaction()`
175
+ * methods find the bus from module initialisation until the application has shut down.
152
176
  */
153
- declare class RelayBusLifecycle implements OnApplicationBootstrap, BeforeApplicationShutdown {
177
+ declare class RelayBusLifecycle implements OnModuleInit, OnApplicationBootstrap, BeforeApplicationShutdown, OnApplicationShutdown {
154
178
  private readonly bus;
155
179
  constructor(bus: RelayBus);
180
+ onModuleInit(): void;
156
181
  onApplicationBootstrap(): Promise<void>;
157
182
  beforeApplicationShutdown(): Promise<void>;
183
+ /** After the bus stopped, so handlers still draining at shutdown can open transactions. */
184
+ onApplicationShutdown(): void;
185
+ }
186
+
187
+ /**
188
+ * What one feature module consumes and produces, given to `RelayBusModule.forFeature`. Each item is
189
+ * a whole catalog or a `consume(...)` / `produce(...)` declaration of some of its events.
190
+ */
191
+ interface RelayBusFeatureOptions {
192
+ /** Events the importing module handles: each needs a `@RelayBusConsumer` method (or a catalog consumer) among its providers. */
193
+ readonly consumes?: readonly (Catalog | CatalogDeclaration)[];
194
+ /** Events the importing module publishes for other services: each needs a route to a transport. */
195
+ readonly produces?: readonly (Catalog | CatalogDeclaration)[];
196
+ }
197
+ /** One event of a feature contract, named for error messages. */
198
+ type ContractEvent = {
199
+ /** `Catalog.EventName`. */
200
+ readonly label: string;
201
+ readonly type: string;
202
+ };
203
+ /**
204
+ * The contract of the module that imports `RelayBusModule.forFeature(...)`, checked when the
205
+ * application initialises.
206
+ */
207
+ declare class RelayBusFeatureContract {
208
+ readonly consumes: readonly ContractEvent[];
209
+ readonly produces: readonly ContractEvent[];
210
+ /** @throws ConfigurationError for a `produce(...)` in `consumes`, a `consume(...)` in `produces`, or something that is not a catalog. */
211
+ constructor(options: RelayBusFeatureOptions);
158
212
  }
159
213
 
160
214
  /**
161
- * Registers one {@link RelayBus} for the application, discovers `@Subscribe`
162
- * handlers in every module, starts the bus after bootstrap and stops it on
215
+ * Registers one {@link RelayBus} for the application, discovers `@RelayBusConsumer` and `@RelayBusCatalogConsumer`
216
+ * handlers in every module, runs `@RelayBusTransaction()` methods on the bus, starts the bus after bootstrap and stops it on
163
217
  * shutdown. Global unless `isGlobal: false`.
164
218
  *
165
219
  * ```ts
@@ -167,28 +221,124 @@ declare class RelayBusLifecycle implements OnApplicationBootstrap, BeforeApplica
167
221
  * ```
168
222
  */
169
223
  declare class RelayBusModule extends RelayBusConfigurableModule {
224
+ /**
225
+ * The relaybus contract of a feature module: import it in the module whose providers consume
226
+ * and produce the events. When the application initialises, every consumed event needs a
227
+ * `@RelayBusConsumer` method (or a `@RelayBusCatalogConsumer` class) among the providers of
228
+ * that module, spread over any classes and method names, and every produced event needs a
229
+ * route to a transport; otherwise the application fails to start naming the module and each
230
+ * missing event. Needs `RelayBusModule.forRoot` in the application.
231
+ *
232
+ * ```ts
233
+ * @Module({
234
+ * imports: [RelayBusModule.forFeature({ consumes: [OrderEvents], produces: [produce(BillingEvents)] })],
235
+ * providers: [OrderPlacedHandlers, OrderCancelledHandlers],
236
+ * })
237
+ * export class BillingModule {}
238
+ * ```
239
+ *
240
+ * @throws ConfigurationError for a `produce(...)` in `consumes` or a `consume(...)` in `produces`.
241
+ */
242
+ static forFeature(options: RelayBusFeatureOptions): DynamicModule;
243
+ }
244
+
245
+ /**
246
+ * Carries the contract of `RelayBusModule.forFeature(...)` into the module that imports it. The
247
+ * contract is checked against that module's providers when the application initialises.
248
+ */
249
+ declare class RelayBusFeatureModule {
170
250
  }
171
251
 
172
252
  /**
173
- * Options of {@link Subscribe}: the core {@link SubscribeOptions}, except that
253
+ * Hands repositories the transaction of the enclosing `@RelayBusTransaction()` method (or
254
+ * `bus.transaction`, transactional handler, `transactionResolver`), typed as the store's
255
+ * transaction handle: `PoolClient` for `pg`, a Kysely `Transaction`, a TypeORM `EntityManager`,
256
+ * a Prisma transaction client, a MongoDB `ClientSession`.
257
+ *
258
+ * ```ts
259
+ * @Injectable()
260
+ * export class OrderRepository {
261
+ * constructor(private readonly transactions: RelayBusTransactionHost<PoolClient>) {}
262
+ *
263
+ * async insert(orderId: string): Promise<void> {
264
+ * await this.transactions.current().query('INSERT INTO orders (id) VALUES ($1)', [orderId]);
265
+ * }
266
+ * }
267
+ * ```
268
+ */
269
+ declare class RelayBusTransactionHost<TTransaction = unknown> {
270
+ private readonly bus;
271
+ constructor(bus: RelayBus<TTransaction>);
272
+ /**
273
+ * The active transaction.
274
+ *
275
+ * @throws TransactionRequiredError when the caller runs outside every transaction.
276
+ */
277
+ current(): TTransaction;
278
+ /** The active transaction, or undefined outside every transaction. */
279
+ find(): TTransaction | undefined;
280
+ }
281
+
282
+ /** The compile error a `@RelayBusTransaction()` on a method that does not return a Promise reads. */
283
+ type RelayBusTransactionNeedsAsyncMethod = '@RelayBusTransaction() needs a method that returns a Promise: make the method async';
284
+ /**
285
+ * Accepts the descriptor of a method that returns a Promise; any other method fails to compile
286
+ * with {@link RelayBusTransactionNeedsAsyncMethod} in the message.
287
+ */
288
+ type AsyncMethodDescriptor<TMethod> = TypedPropertyDescriptor<TMethod> & (TMethod extends (...args: never[]) => PromiseLike<unknown> ? unknown : RelayBusTransactionNeedsAsyncMethod);
289
+ /** What `@RelayBusTransaction()` returns: a method decorator for async methods only. */
290
+ type RelayBusTransactionDecorator = <TMethod extends (...args: never[]) => unknown>(target: object, propertyKey: string | symbol, descriptor: AsyncMethodDescriptor<TMethod>) => void;
291
+ /**
292
+ * Runs the method in a relaybus transaction: `bus.publish(...)` inside it, and in everything it
293
+ * awaits, joins the transaction without passing `{ transaction }`, and a repository reads it with
294
+ * `bus.currentTransaction()` or {@link RelayBusTransactionHost}. The transaction commits when the
295
+ * method resolves and rolls back when it rejects; it is held to the bus `transactionTimeout`.
296
+ *
297
+ * - The method itself is replaced, so `this.other()` calls of decorated methods are transactional
298
+ * too, unlike interceptor-based decorators.
299
+ * - A decorated method called while a transaction is active (another decorated method,
300
+ * `bus.transaction`, a transactional handler, or the `transactionResolver`, for example
301
+ * `@nestjs-cls/transactional`) joins it instead of opening a second one.
302
+ * - The bus is the one of the application's `RelayBusModule`; the class does not inject it.
303
+ * - Only methods that return a Promise compile.
304
+ *
305
+ * ```ts
306
+ * @Injectable()
307
+ * export class OrderService {
308
+ * @RelayBusTransaction()
309
+ * async place(orderId: string): Promise<void> {
310
+ * await this.orders.insert(orderId);
311
+ * await this.bus.publish(new OrderPlaced({ orderId }));
312
+ * }
313
+ * }
314
+ * ```
315
+ */
316
+ declare function RelayBusTransaction(): RelayBusTransactionDecorator;
317
+
318
+ /**
319
+ * Options of {@link RelayBusConsumer}: the core {@link SubscribeOptions}, except that
174
320
  * `id` is optional and defaults to `ClassName.methodName`.
175
321
  */
176
- type SubscribeDecoratorOptions = Omit<SubscribeOptions, 'id'> & {
322
+ type RelayBusConsumerOptions = Omit<SubscribeOptions, 'id'> & {
177
323
  /**
178
324
  * Stable id of the handler. Deliveries are recorded under it, so renaming it
179
325
  * (or the class or method it is derived from) orphans pending deliveries.
180
326
  * Default: `${ClassName}.${methodName}`. Required when one method carries
181
- * several `@Subscribe` decorators.
327
+ * several `@RelayBusConsumer` decorators.
182
328
  */
183
329
  readonly id?: string;
184
330
  };
185
- /** What one `@Subscribe` decorator recorded on a method. */
186
- interface SubscribeMetadata {
331
+ /** What one `@RelayBusConsumer` decorator recorded on a method. */
332
+ interface RelayBusConsumerMetadata {
187
333
  readonly event: EventClass<unknown> | string;
188
- readonly options: SubscribeDecoratorOptions;
334
+ readonly options: RelayBusConsumerOptions;
189
335
  }
336
+ /** @deprecated Renamed to {@link RelayBusConsumerOptions}; this alias is removed in 1.0.0. */
337
+ type SubscribeDecoratorOptions = RelayBusConsumerOptions;
338
+ /** @deprecated Renamed to {@link RelayBusConsumerMetadata}; this alias is removed in 1.0.0. */
339
+ type SubscribeMetadata = RelayBusConsumerMetadata;
190
340
  type HandlerMethod<TPayload> = (event: EventEnvelope<TPayload>, context: HandlerContext<never>) => unknown;
191
- type SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<TMethod>) => void;
341
+ type HandlerDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<TMethod>) => void;
192
342
  /**
193
343
  * Subscribes a provider method to an event class or type. The method is
194
344
  * registered with `bus.subscribe` when the module initialises, with `this`
@@ -201,12 +351,17 @@ type SubscribeDecorator<TPayload> = <TMethod extends HandlerMethod<TPayload>>(ta
201
351
  * ```ts
202
352
  * @Injectable()
203
353
  * export class ShippingHandlers {
204
- * @Subscribe(OrderPlaced, { transactional: true })
354
+ * @RelayBusConsumer(OrderPlaced, { transactional: true })
205
355
  * async onOrderPlaced(event: EventEnvelope<{ orderId: string }>, context: HandlerContext) {}
206
356
  * }
207
357
  * ```
208
358
  */
209
- declare function Subscribe<TPayload>(event: EventClass<TPayload>, options?: SubscribeDecoratorOptions): SubscribeDecorator<TPayload>;
210
- declare function Subscribe(eventType: string, options?: SubscribeDecoratorOptions): SubscribeDecorator<never>;
359
+ declare function RelayBusConsumer<TPayload>(event: EventClass<TPayload>, options?: RelayBusConsumerOptions): HandlerDecorator<TPayload>;
360
+ declare function RelayBusConsumer(eventType: string, options?: RelayBusConsumerOptions): HandlerDecorator<never>;
361
+ /**
362
+ * @deprecated Renamed to {@link RelayBusConsumer}, which behaves identically; this alias is
363
+ * removed in 1.0.0.
364
+ */
365
+ declare const Subscribe: typeof RelayBusConsumer;
211
366
 
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 };
367
+ export { type AsyncMethodDescriptor, type ContractEvent, EventConsumer, type EventConsumerClass, type EventConsumerMetadata, type EventConsumerOptions, RelayBusCatalogConsumer, type RelayBusCatalogConsumerClass, type RelayBusCatalogConsumerMetadata, type RelayBusCatalogConsumerOptions, RelayBusConsumer, type RelayBusConsumerMetadata, type RelayBusConsumerOptions, RelayBusFeatureContract, RelayBusFeatureModule, type RelayBusFeatureOptions, RelayBusHandlerExplorer, RelayBusHealth, type RelayBusHealthReport, RelayBusLifecycle, RelayBusModule, type RelayBusModuleExtras, type RelayBusModuleOptions, type RelayBusModuleOptionsFactory, RelayBusTransaction, type RelayBusTransactionDecorator, RelayBusTransactionHost, type RelayBusTransactionNeedsAsyncMethod, Subscribe, type SubscribeDecoratorOptions, type SubscribeMetadata };