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