@vercube/queue 1.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.
@@ -0,0 +1,953 @@
1
+ import { n as QueueTypes, t as QueueStrategy } from "./QueueStrategy-saWSBeU6.mjs";
2
+ import { BaseDecorator, Container } from "@vercube/di";
3
+ import { Logger } from "@vercube/logger";
4
+ import { App, BasePlugin, ValidationProvider } from "@vercube/core";
5
+ //#region src/Decorators/AnyJob.d.ts
6
+ /**
7
+ * Declares the decorated method as the handler of every job of the queue that no
8
+ * `@Job()` claims.
9
+ *
10
+ * A handler registered for a job name always wins, so `@AnyJob()` is the fallback
11
+ * rather than a replacement. It is what makes a queue somebody else fills
12
+ * consumable: messages produced outside this module carry no job name, and would
13
+ * otherwise be reported as unhandled.
14
+ *
15
+ * The real job name is on the context, so the handler can still branch on it.
16
+ * Only one `@AnyJob()` per queue is allowed, like any other handler.
17
+ *
18
+ * @param {QueueTypes.HandlerOptions} [options] - Retries, timeout and payload schema of this handler
19
+ * @returns {Function} The method decorator
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * // a queue filled by another application, whose messages carry no job name
24
+ * @Consumer({ queue: 'legacy-events' })
25
+ * export class LegacyConsumer {
26
+ * @AnyJob({ attempts: 3 })
27
+ * public async handle(payload: unknown, context: QueueTypes.JobContext): Promise<void> {
28
+ * await this.gEvents.ingest(payload, context.job);
29
+ * }
30
+ * }
31
+ * ```
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * // known jobs handled on their own, everything else swept up
36
+ * @Consumer({ queue: 'emails' })
37
+ * export class EmailConsumer {
38
+ * @Job('welcome')
39
+ * public async welcome(payload: Welcome): Promise<void> {}
40
+ *
41
+ * @AnyJob()
42
+ * public async rest(payload: unknown, context: QueueTypes.JobContext): Promise<void> {
43
+ * context.logger?.warn(`unrecognised job ${context.job}`);
44
+ * }
45
+ * }
46
+ * ```
47
+ */
48
+ export declare function AnyJob(options?: QueueTypes.HandlerOptions): Function;
49
+ //#endregion
50
+ //#region src/Decorators/Consumer.d.ts
51
+ /**
52
+ * Declares a class as the consumer of a queue.
53
+ *
54
+ * The class itself does nothing until it is bound in the container: that is when
55
+ * its `@Job()` methods register themselves and the queue starts being consumed.
56
+ * Options declared here become the defaults of every handler in the class.
57
+ *
58
+ * @param {QueueTypes.ConsumerOptions} options - Queue to consume, its concurrency and the handler defaults
59
+ * @returns {Function} The class decorator
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * @Consumer({ queue: 'emails', concurrency: 5 })
64
+ * export class EmailConsumer {
65
+ * @Job('welcome')
66
+ * public async welcome(payload: { userId: string }): Promise<void> {
67
+ * await this.mailer.sendWelcome(payload.userId);
68
+ * }
69
+ * }
70
+ *
71
+ * // in the container setup
72
+ * container.bind(EmailConsumer);
73
+ * ```
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * // defaults for every handler of the class, overridable per job
78
+ * @Consumer({ queue: 'reports', strategy: 'kafka', attempts: 3, timeout: 30_000 })
79
+ * export class ReportConsumer {}
80
+ * ```
81
+ */
82
+ export declare function Consumer(options: QueueTypes.ConsumerOptions): Function;
83
+ //#endregion
84
+ //#region src/Decorators/Job.d.ts
85
+ /** Options the `@Job()` decorator is created with. */
86
+ interface JobDecoratorOptions {
87
+ /** Name of the job the method handles. */
88
+ name: string;
89
+ /** Consumer-side options of this handler. */
90
+ options: QueueTypes.HandlerOptions;
91
+ }
92
+ /**
93
+ * Registers the decorated method as the handler of a single job.
94
+ * Runs when the container instantiates the consumer class.
95
+ */
96
+ export declare class JobDecorator extends BaseDecorator<JobDecoratorOptions> {
97
+ /** Queue manager the handler is registered with */
98
+ private gQueueManager;
99
+ /** Logger instance */
100
+ private gLogger;
101
+ /** The registration handed to the manager, kept so it can be removed again */
102
+ private fRegistration;
103
+ /**
104
+ * Registers the handler with the queue manager.
105
+ *
106
+ * @returns {void}
107
+ */
108
+ created(): void;
109
+ /**
110
+ * Removes the handler again, so rebinding the consumer class does not collide
111
+ * with the handler its previous instance registered.
112
+ *
113
+ * @returns {void}
114
+ */
115
+ destroyed(): void;
116
+ /**
117
+ * Reports a consumer that cannot be wired up.
118
+ *
119
+ * @param {string} message - What is wrong
120
+ * @returns {void}
121
+ */
122
+ private warn;
123
+ }
124
+ /**
125
+ * Declares the decorated method as the handler of a job.
126
+ *
127
+ * The method receives the job payload as its first argument and a
128
+ * {@link QueueTypes.JobContext} as its second. Returning marks the job as done,
129
+ * throwing marks the attempt as failed and hands it to the retry policy.
130
+ *
131
+ * Options given here override the defaults of the `@Consumer()` class. To handle
132
+ * everything the queue carries beyond the named jobs, see `@AnyJob()`.
133
+ *
134
+ * @param {string} name - Name of the job, as used when adding it to the queue
135
+ * @param {QueueTypes.HandlerOptions} [options] - Retries, timeout and payload schema of this handler
136
+ * @returns {Function} The method decorator
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * @Consumer({ queue: 'emails' })
141
+ * export class EmailConsumer {
142
+ * @Job('welcome')
143
+ * public async welcome(payload: { userId: string }): Promise<void> {
144
+ * await this.mailer.sendWelcome(payload.userId);
145
+ * }
146
+ * }
147
+ * ```
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * // three attempts with a growing delay, a validated payload and a hard time limit
152
+ * @Job('digest', {
153
+ * attempts: 3,
154
+ * backoff: { type: 'exponential', delay: 1000 },
155
+ * timeout: 30_000,
156
+ * schema: DigestSchema,
157
+ * })
158
+ * public async digest(payload: Digest, context: QueueTypes.JobContext<Digest>): Promise<void> {
159
+ * context.logger?.info(`attempt ${context.attempt} of ${context.attempts}`);
160
+ * await context.updateProgress(50);
161
+ * }
162
+ * ```
163
+ */
164
+ export declare function Job(name: string, options?: QueueTypes.HandlerOptions): Function;
165
+ //#endregion
166
+ //#region src/Decorators/OnJobCompleted.d.ts
167
+ /**
168
+ * Runs the decorated method after a job of the consumer's queue completed.
169
+ *
170
+ * The hook receives the {@link QueueTypes.JobContext} of the finished job. It
171
+ * never changes the outcome of that job: a throwing hook is logged and forgotten.
172
+ *
173
+ * @param {object} [options] - Narrows the hook down to a single job
174
+ * @param {string} [options.job] - Name of the only job to listen for, every job of the queue by default
175
+ * @returns {Function} The method decorator
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * @Consumer({ queue: 'emails' })
180
+ * export class EmailConsumer {
181
+ * @Job('welcome')
182
+ * public async welcome(payload: { userId: string }): Promise<void> {}
183
+ *
184
+ * @OnJobCompleted()
185
+ * public async sent(context: QueueTypes.JobContext): Promise<void> {
186
+ * this.metrics.increment(`emails.${context.job}.sent`);
187
+ * }
188
+ * }
189
+ * ```
190
+ */
191
+ export declare function OnJobCompleted(options?: {
192
+ job?: string;
193
+ }): Function;
194
+ //#endregion
195
+ //#region src/Decorators/OnJobFailed.d.ts
196
+ /**
197
+ * Runs the decorated method after an attempt of a job of the consumer's queue threw.
198
+ *
199
+ * The hook receives the error and the {@link QueueTypes.JobContext} of the failed
200
+ * attempt, so it can tell a retry from a final failure by comparing
201
+ * `context.attempt` with `context.attempts`. It never changes the outcome of the
202
+ * job: a throwing hook is logged and forgotten.
203
+ *
204
+ * @param {object} [options] - Narrows the hook down to a single job
205
+ * @param {string} [options.job] - Name of the only job to listen for, every job of the queue by default
206
+ * @returns {Function} The method decorator
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * @Consumer({ queue: 'emails' })
211
+ * export class EmailConsumer {
212
+ * @Job('welcome', { attempts: 3 })
213
+ * public async welcome(payload: { userId: string }): Promise<void> {}
214
+ *
215
+ * @OnJobFailed()
216
+ * public async failed(error: Error, context: QueueTypes.JobContext): Promise<void> {
217
+ * if (context.attempt === context.attempts) {
218
+ * await this.alerts.report(error, context.id);
219
+ * }
220
+ * }
221
+ * }
222
+ * ```
223
+ */
224
+ export declare function OnJobFailed(options?: {
225
+ job?: string;
226
+ }): Function;
227
+ //#endregion
228
+ //#region src/Plugins/QueuePlugin.d.ts
229
+ /**
230
+ * Queue Plugin for Vercube framework
231
+ *
232
+ * Binds the {@link QueueManager}, mounts the strategies it is given and lets the
233
+ * `@Consumer()` classes bound in the container start consuming once the
234
+ * application is up. Consumer classes themselves stay in your hands: bind them
235
+ * where you bind your controllers.
236
+ *
237
+ * @example
238
+ * ```ts
239
+ * import { defineConfig } from '@vercube/core';
240
+ * import { QueuePlugin } from '@vercube/queue';
241
+ * import { BullMQStrategy } from '@vercube/queue/strategies/BullMQStrategy';
242
+ *
243
+ * export default defineConfig({
244
+ * plugins: [
245
+ * [QueuePlugin, {
246
+ * strategies: [{ strategy: BullMQStrategy, initOptions: { connection: { host: '127.0.0.1', port: 6379 } } }],
247
+ * }],
248
+ * ],
249
+ * });
250
+ * ```
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * // a web process that only publishes jobs
255
+ * app.addPlugin(QueuePlugin, {
256
+ * autoStart: false,
257
+ * strategies: [{ strategy: MemoryStrategy }],
258
+ * });
259
+ * ```
260
+ *
261
+ * @see {@link https://vercube.dev} for full documentation
262
+ */
263
+ export declare class QueuePlugin extends BasePlugin<QueueTypes.PluginOptions> {
264
+ /**
265
+ * The name of the plugin.
266
+ * @override
267
+ */
268
+ name: string;
269
+ /**
270
+ * Binds the queue manager and mounts every configured strategy.
271
+ *
272
+ * Registering the plugin more than once, which happens as soon as it is listed
273
+ * both in the config and in `app.addPlugin()`, is harmless: the manager is
274
+ * bound once, settings are merged, and a name that is already mounted is left
275
+ * alone. Rebinding it would drop the settings and the strategies of whoever
276
+ * registered first.
277
+ *
278
+ * @param {App} app - The application instance
279
+ * @param {QueueTypes.PluginOptions} [options] - Strategies to mount and manager-wide settings
280
+ * @returns {Promise<void>} Resolves once every strategy is mounted
281
+ * @override
282
+ */
283
+ use(app: App, options?: QueueTypes.PluginOptions): Promise<void>;
284
+ }
285
+ //#endregion
286
+ //#region src/Errors/QueueError.d.ts
287
+ /**
288
+ * Error thrown by the queue module.
289
+ * Wraps transport errors with a stable shape so callers can tell what failed
290
+ * and whether the job is worth retrying.
291
+ */
292
+ export declare class QueueError extends Error {
293
+ /** The original error that caused this one. */
294
+ readonly cause?: Error;
295
+ /** The queue operation that failed, for example `publish` or `consume`. */
296
+ readonly operation: string;
297
+ /** Whether processing the job again could succeed. */
298
+ readonly retryable: boolean;
299
+ /** Additional non-sensitive context about the failure. */
300
+ readonly metadata?: Record<string, unknown>;
301
+ /**
302
+ * @param message - Human readable description of the failure.
303
+ * @param operation - Queue operation that failed.
304
+ * @param cause - Underlying error, when there is one.
305
+ * @param metadata - Additional non-sensitive context.
306
+ * @param retryable - Whether processing the job again could succeed, defaults to true.
307
+ */
308
+ constructor(message: string, operation: string, cause?: Error, metadata?: Record<string, unknown>, retryable?: boolean);
309
+ }
310
+ //#endregion
311
+ //#region src/Services/QueueManager.d.ts
312
+ /**
313
+ * Central entry point of the queue module.
314
+ *
315
+ * The manager owns the mounted strategies, the handlers registered by the
316
+ * decorators and everything that has to behave the same across transports:
317
+ * routing a job to its handler, validating payloads, retries, timeouts,
318
+ * lifecycle hooks and the counters the devtools read.
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * container.bind(QueueManager);
323
+ *
324
+ * const queue = container.get(QueueManager);
325
+ * await queue.mount({ strategy: MemoryStrategy });
326
+ *
327
+ * await queue.add({ queue: 'emails', job: 'welcome', payload: { userId: '1' } });
328
+ * ```
329
+ */
330
+ export declare class QueueManager {
331
+ /** Container instance */
332
+ protected gContainer: Container;
333
+ /** Logger instance */
334
+ protected gLogger: Logger | null;
335
+ /** Validation provider, needed only by handlers declaring a schema */
336
+ protected gValidation: ValidationProvider | null;
337
+ /** Mounted strategies, indexed by mount name */
338
+ protected fStrategies: Map<string, QueueTypes.MountedStrategy<any>>;
339
+ /** Registered handlers, indexed by strategy, queue and job name */
340
+ protected fRegistrations: Map<string, QueueTypes.Registration>;
341
+ /** Registered lifecycle hooks */
342
+ protected fHooks: {
343
+ completed: QueueTypes.HookRegistration[];
344
+ failed: QueueTypes.HookRegistration[];
345
+ };
346
+ /** Running consumers, indexed by strategy and queue */
347
+ protected fConsumers: Map<string, {
348
+ handle: QueueTypes.ConsumerHandle;
349
+ concurrency: number;
350
+ }>;
351
+ /** Per-queue counters, indexed by strategy and queue */
352
+ protected fMetrics: Map<string, QueueTypes.QueueMetrics>;
353
+ /** Recently processed jobs, newest first */
354
+ protected fEvents: QueueTypes.JobEvent[];
355
+ /** Manager-wide settings */
356
+ protected fDefaults: Required<QueueTypes.Defaults>;
357
+ /** Whether consumers should be running */
358
+ protected fStarted: boolean;
359
+ /** Serializes start, stop and mount work so consumers are never started twice */
360
+ protected fTail: Promise<void>;
361
+ /** Retries waiting for their backoff to elapse */
362
+ protected fPendingRetries: Set<Promise<void>>;
363
+ /** Listeners following the jobs this manager processes */
364
+ protected fListeners: Set<QueueTypes.JobListener>;
365
+ /**
366
+ * Whether consumers have been started.
367
+ *
368
+ * @returns {boolean} True once {@link QueueManager.start} ran
369
+ */
370
+ get started(): boolean;
371
+ /**
372
+ * Currently configured settings.
373
+ *
374
+ * @returns {Required<QueueTypes.Defaults>} A copy of the active settings
375
+ */
376
+ get defaults(): Required<QueueTypes.Defaults>;
377
+ /**
378
+ * Sets manager-wide settings. Calling it repeatedly merges into the existing
379
+ * ones, and settings passed per job or per handler always win.
380
+ *
381
+ * @param {QueueTypes.Defaults} defaults - Settings to apply
382
+ * @returns {void}
383
+ */
384
+ configure(defaults: QueueTypes.Defaults): void;
385
+ /**
386
+ * Mounts a strategy under a name. Every other call refers to it by that name,
387
+ * so a single application can talk to several brokers at once.
388
+ *
389
+ * The strategy is resolved through the container, connects on first use, and
390
+ * starts consuming right away when the manager is already running.
391
+ *
392
+ * @template T - Strategy being mounted
393
+ * @param {QueueTypes.Mount<T>} params - Mount name, strategy class and its init options
394
+ * @returns {Promise<void>} Resolves once the strategy is mounted
395
+ */
396
+ mount<T extends QueueStrategy<unknown>>({ name, strategy, initOptions }: QueueTypes.Mount<T>): Promise<void>;
397
+ /**
398
+ * Stops and closes a mounted strategy, and forgets it.
399
+ * Handlers registered for it stay registered, so mounting it again resumes them.
400
+ *
401
+ * @param {string} [name] - Mount name, defaults to `default`
402
+ * @returns {Promise<void>} Resolves once the strategy is closed
403
+ */
404
+ unmount(name?: string): Promise<void>;
405
+ /**
406
+ * Returns a mounted strategy, for the rare case a transport-specific API is needed.
407
+ *
408
+ * @param {string} [name] - Mount name, defaults to `default`
409
+ * @returns {QueueStrategy<unknown> | undefined} The strategy, or undefined when nothing is mounted under that name
410
+ */
411
+ getStrategy(name?: string): QueueStrategy<unknown> | undefined;
412
+ /**
413
+ * Adds a single job to a queue.
414
+ *
415
+ * @template TQueue - Queue the job is added to
416
+ * @template TJob - Name of the job
417
+ * @param {QueueTypes.AddRequest<TQueue, TJob>} request - Queue, job name, payload and per-job options
418
+ * @returns {Promise<QueueTypes.JobRef>} Reference to the published job
419
+ * @throws {QueueError} When no strategy is mounted under the requested name, or the job cannot be published
420
+ */
421
+ add<TQueue extends QueueTypes.QueueName, TJob extends QueueTypes.JobName<TQueue>>(request: QueueTypes.AddRequest<TQueue, TJob>): Promise<QueueTypes.JobRef>;
422
+ /**
423
+ * Adds many jobs of the same kind to a queue, using the transport's batch API
424
+ * when it has one.
425
+ *
426
+ * @template TQueue - Queue the jobs are added to
427
+ * @template TJob - Name of the jobs
428
+ * @param {QueueTypes.AddManyRequest<TQueue, TJob>} request - Queue, job name, payloads and per-job options
429
+ * @returns {Promise<QueueTypes.JobRef[]>} References to the published jobs, in the same order
430
+ * @throws {QueueError} When no strategy is mounted under the requested name, or the jobs cannot be published
431
+ */
432
+ addMany<TQueue extends QueueTypes.QueueName, TJob extends QueueTypes.JobName<TQueue>>(request: QueueTypes.AddManyRequest<TQueue, TJob>): Promise<QueueTypes.JobRef[]>;
433
+ /**
434
+ * Registers a handler for a single job, or for every job the queue has no
435
+ * other handler for when registered under `*`. The decorators call this, and so
436
+ * can application code building its consumers dynamically.
437
+ *
438
+ * When the manager is already running, the queue starts being consumed right away.
439
+ *
440
+ * @param {QueueTypes.Registration} registration - Queue, job name, handler and its options
441
+ * @returns {void}
442
+ * @throws {QueueError} When a handler for the same job is already registered
443
+ */
444
+ registerConsumer(registration: QueueTypes.Registration): void;
445
+ /**
446
+ * Registers a lifecycle hook for a queue.
447
+ *
448
+ * @param {'completed' | 'failed'} event - Event to listen for
449
+ * @param {QueueTypes.HookRegistration} registration - Queue, optional job filter and the hook itself
450
+ * @returns {void}
451
+ */
452
+ registerHook(event: 'completed' | 'failed', registration: QueueTypes.HookRegistration): void;
453
+ /**
454
+ * Removes a registered handler. The queue stops being consumed once its last
455
+ * handler is gone.
456
+ *
457
+ * @param {Pick<QueueTypes.Registration, 'strategy' | 'queue' | 'job'>} registration - Handler to remove
458
+ * @returns {void}
459
+ */
460
+ unregisterConsumer(registration: Pick<QueueTypes.Registration, 'strategy' | 'queue' | 'job'>): void;
461
+ /**
462
+ * Removes a registered lifecycle hook.
463
+ *
464
+ * @param {'completed' | 'failed'} event - Event the hook listens for
465
+ * @param {QueueTypes.HookRegistration} registration - The very registration that was registered
466
+ * @returns {void}
467
+ */
468
+ unregisterHook(event: 'completed' | 'failed', registration: QueueTypes.HookRegistration): void;
469
+ /**
470
+ * Connects every mounted strategy and starts consuming every queue that has
471
+ * a handler. Safe to call more than once, queues already running are left alone.
472
+ *
473
+ * @returns {Promise<void>} Resolves once every consumer is running
474
+ */
475
+ start(): Promise<void>;
476
+ /**
477
+ * Stops every consumer while keeping the connections open, so the application
478
+ * can still publish. In-flight jobs are awaited.
479
+ *
480
+ * @returns {Promise<void>} Resolves once every consumer is stopped
481
+ */
482
+ stop(): Promise<void>;
483
+ /**
484
+ * Stops every consumer and closes every connection.
485
+ *
486
+ * @returns {Promise<void>} Resolves once everything is closed
487
+ */
488
+ close(): Promise<void>;
489
+ /**
490
+ * Waits for the work the manager scheduled in the background: starting or
491
+ * stopping consumers, and retries waiting for their backoff to elapse.
492
+ *
493
+ * @returns {Promise<void>} Resolves once nothing is pending
494
+ */
495
+ drain(): Promise<void>;
496
+ /**
497
+ * Reads live counters of a queue straight from the transport.
498
+ *
499
+ * @param {object} params - Queue to read and the strategy to read it from
500
+ * @param {string} params.queue - Queue to read
501
+ * @param {string} [params.strategy] - Mount name, defaults to `default`
502
+ * @returns {Promise<QueueTypes.QueueStats>} The counters the transport reports, empty when it reports none
503
+ */
504
+ stats({ queue, strategy }: {
505
+ queue: string;
506
+ strategy?: string;
507
+ }): Promise<QueueTypes.QueueStats>;
508
+ /**
509
+ * Follows every job this manager finishes, as it finishes it.
510
+ *
511
+ * The listener is called with the same event that goes into the inspection
512
+ * buffer, right after the job settled, so a listener sees a queue live instead
513
+ * of polling it. A listener that throws is reported and kept.
514
+ *
515
+ * @param {QueueTypes.JobListener} listener - Called once per processed job
516
+ * @returns {() => void} Removes the listener again
517
+ */
518
+ subscribe(listener: QueueTypes.JobListener): () => void;
519
+ /**
520
+ * Shows what a queue is holding, without consuming any of it.
521
+ *
522
+ * Only transports that can be read without side effects support this, which
523
+ * they report as the `peek` capability. Everything else returns nothing rather
524
+ * than perturbing the queue: taking delivery of a message to look at it would
525
+ * change delivery counts and compete with the running consumer.
526
+ *
527
+ * @param {object} params - Queue to look at and how much of it to read
528
+ * @param {string} params.queue - Queue to look at
529
+ * @param {string} [params.strategy] - Mount name, defaults to `default`
530
+ * @param {number} [params.limit] - How many messages to read, defaults to 20
531
+ * @param {QueueTypes.PeekState[]} [params.states] - States to read, defaults to waiting, delayed and failed
532
+ * @returns {Promise<QueueTypes.PeekedJob[]>} The messages found, with their payloads rendered
533
+ * @throws {QueueError} When the transport fails to answer
534
+ */
535
+ peek({ queue, strategy, limit, states }: {
536
+ queue: string;
537
+ strategy?: string;
538
+ limit?: number;
539
+ states?: QueueTypes.PeekState[];
540
+ }): Promise<QueueTypes.PeekedJob[]>;
541
+ /**
542
+ * Describes what the module currently holds: mounted strategies, registered
543
+ * handlers, per-queue counters and the last processed jobs. Used by the devtools.
544
+ *
545
+ * @returns {QueueTypes.Snapshot} The current state of the queue module
546
+ */
547
+ inspect(): QueueTypes.Snapshot;
548
+ /**
549
+ * Processes a single job: routes it to its handler, validates the payload,
550
+ * enforces the timeout, runs the lifecycle hooks and applies the retry policy.
551
+ *
552
+ * The handler registered for the job name wins, and a handler registered under
553
+ * `*` picks up whatever is left.
554
+ *
555
+ * Rejecting tells the strategy the job failed for good, so it can dead-letter it.
556
+ *
557
+ * @param {string} strategy - Mount name the job came from
558
+ * @param {string} queue - Queue the job came from
559
+ * @param {QueueTypes.IncomingJob} incoming - The job as received from the transport
560
+ * @returns {Promise<void>} Resolves when the job may be acknowledged
561
+ * @throws {Error} When the job failed and the transport has to deal with it
562
+ */
563
+ protected process(strategy: string, queue: string, incoming: QueueTypes.IncomingJob): Promise<void>;
564
+ /**
565
+ * Does the work {@link QueueManager.process} traces.
566
+ *
567
+ * @param {string} strategy - Mount name the job came from
568
+ * @param {string} queue - Queue the job came from
569
+ * @param {QueueTypes.IncomingJob} incoming - The job as received from the transport
570
+ * @returns {Promise<void>} Resolves when the job may be acknowledged
571
+ * @throws {Error} When the job failed and the transport has to deal with it
572
+ */
573
+ protected processJob(strategy: string, queue: string, incoming: QueueTypes.IncomingJob): Promise<void>;
574
+ /**
575
+ * Applies the failure policy of a job: hooks first, then either a retry the
576
+ * manager schedules itself, or a rejection handing the job back to the transport.
577
+ *
578
+ * @param {Error} error - Error the attempt failed with
579
+ * @param {QueueTypes.Registration} registration - Handler that failed
580
+ * @param {QueueTypes.JobContext} context - Context of the failed attempt
581
+ * @param {number} duration - How long the attempt took, in milliseconds
582
+ * @returns {Promise<void>} Resolves once a retry has been scheduled
583
+ * @throws {Error} The original error, when the transport has to deal with the failure
584
+ */
585
+ protected handleFailure(error: Error, registration: QueueTypes.Registration, context: QueueTypes.JobContext, duration: number): Promise<void>;
586
+ /**
587
+ * Publishes the job again for its next attempt, waiting for the backoff first.
588
+ *
589
+ * The wait is handed to the transport when it can delay jobs, and kept on a
590
+ * timer otherwise, so the handler slot is released immediately either way.
591
+ *
592
+ * @param {QueueTypes.Registration} registration - Handler that failed
593
+ * @param {QueueTypes.JobContext} context - Context of the failed attempt
594
+ * @param {Error} error - Error the attempt failed with, reported when the retry cannot be published
595
+ * @returns {Promise<void>} Resolves once the retry exists, or once it has been scheduled on a timer
596
+ * @throws {Error} When the retry could be published right away and the transport refused it
597
+ */
598
+ protected scheduleRetry(registration: QueueTypes.Registration, context: QueueTypes.JobContext, error: Error): Promise<void>;
599
+ /**
600
+ * Runs a handler, failing the attempt when it outlives its timeout.
601
+ * A timed out handler is not interrupted, it is only stopped being waited for.
602
+ *
603
+ * @param {QueueTypes.Registration} registration - Handler to run
604
+ * @param {QueueTypes.JobContext} context - Context of the attempt
605
+ * @returns {Promise<void>} Resolves once the handler returned
606
+ * @throws {QueueError} When the handler outlives its timeout
607
+ */
608
+ protected runHandler(registration: QueueTypes.Registration, context: QueueTypes.JobContext): Promise<void>;
609
+ /**
610
+ * Validates a payload against the handler's schema, when it declares one.
611
+ *
612
+ * @param {QueueTypes.Registration} registration - Handler the payload is meant for
613
+ * @param {unknown} payload - Payload as received from the transport
614
+ * @param {string} job - Name of the job being processed, which a wildcard handler does not know upfront
615
+ * @returns {Promise<unknown>} The validated payload, as returned by the schema
616
+ * @throws {QueueError} When the payload does not match the schema, or no validation provider is bound
617
+ */
618
+ protected validate(registration: QueueTypes.Registration, payload: unknown, job: string): Promise<unknown>;
619
+ /**
620
+ * Runs the hooks registered for a queue. A hook filtered by job name runs for
621
+ * that job only, unless the filter is `*`. A throwing hook is logged and never
622
+ * changes the outcome of the job.
623
+ *
624
+ * @param {'completed' | 'failed'} event - Event being reported
625
+ * @param {QueueTypes.Registration} registration - Handler the event belongs to
626
+ * @param {QueueTypes.JobContext} context - Context of the attempt
627
+ * @param {Error} [error] - Error of the attempt, for the `failed` event
628
+ * @returns {Promise<void>} Resolves once every hook settled
629
+ */
630
+ protected runHooks(event: 'completed' | 'failed', registration: QueueTypes.Registration, context: QueueTypes.JobContext, error?: Error): Promise<void>;
631
+ /**
632
+ * Connects a mounted strategy and starts consuming every queue it has handlers for.
633
+ * Failures are logged and leave the other mounts untouched.
634
+ *
635
+ * @param {string} name - Mount name
636
+ * @returns {Promise<void>} Resolves once the mount is running
637
+ */
638
+ protected startMount(name: string): Promise<void>;
639
+ /**
640
+ * Starts consuming a single queue, unless it is already being consumed.
641
+ *
642
+ * @param {string} strategy - Mount name
643
+ * @param {string} queue - Queue to consume
644
+ * @returns {Promise<void>} Resolves once the consumer is running
645
+ */
646
+ protected startQueue(strategy: string, queue: string): Promise<void>;
647
+ /**
648
+ * Starts consuming a queue at a given concurrency and records the handle.
649
+ *
650
+ * @param {QueueTypes.MountedStrategy} mount - Mount the queue lives on
651
+ * @param {string} queue - Queue to consume
652
+ * @param {number} concurrency - How many jobs the transport may hand over at once
653
+ * @returns {Promise<void>} Resolves once the transport is delivering
654
+ * @throws {Error} When the strategy cannot start consuming
655
+ */
656
+ private consumeQueue;
657
+ /**
658
+ * Stops every consumer of a mount.
659
+ *
660
+ * @param {string} strategy - Mount name
661
+ * @returns {Promise<void>} Resolves once every consumer is stopped
662
+ */
663
+ protected stopConsumers(strategy: string): Promise<void>;
664
+ /**
665
+ * Stops the consumer of a single queue, waiting for its in-flight jobs.
666
+ *
667
+ * @param {string} strategy - Mount name
668
+ * @param {string} queue - Queue whose consumer is stopped
669
+ * @returns {Promise<void>} Resolves once the consumer is stopped
670
+ */
671
+ protected stopQueue(strategy: string, queue: string): Promise<void>;
672
+ /**
673
+ * Closes a strategy, keeping a failure from breaking a shutdown sequence.
674
+ *
675
+ * @param {QueueTypes.MountedStrategy} mount - Mount to close
676
+ * @returns {Promise<void>} Resolves once the strategy is closed
677
+ */
678
+ protected closeStrategy(mount: QueueTypes.MountedStrategy<any>): Promise<void>;
679
+ /**
680
+ * Initializes a strategy once, reusing the same promise for every later call.
681
+ *
682
+ * @param {QueueTypes.MountedStrategy} mount - Mount to initialize
683
+ * @returns {Promise<void>} Resolves once the strategy is ready
684
+ * @throws {QueueError} When the strategy cannot be initialized
685
+ */
686
+ protected ensureReady(mount: QueueTypes.MountedStrategy<any>): Promise<void>;
687
+ /**
688
+ * Resolves a mount by name and makes sure it is connected.
689
+ *
690
+ * @param {string} [name] - Mount name, defaults to `default`
691
+ * @param {string} operation - Operation asking for the mount, reported in the error
692
+ * @returns {Promise<QueueTypes.MountedStrategy>} The ready mount
693
+ * @throws {QueueError} When nothing is mounted under that name, or it cannot connect
694
+ */
695
+ protected resolveMount(name: string | undefined, operation: string): Promise<QueueTypes.MountedStrategy<any>>;
696
+ /**
697
+ * Builds the transport-facing shape of a job, adding the headers the module
698
+ * needs to route and retry it.
699
+ *
700
+ * @param {string} queue - Queue the job goes to
701
+ * @param {string} job - Name of the job
702
+ * @param {unknown} payload - Payload of the job
703
+ * @param {QueueTypes.JobOptions} [options] - Per-job options
704
+ * @returns {QueueTypes.PublishRequest} The job as a strategy expects it
705
+ */
706
+ protected createPublishRequest(queue: string, job: string, payload: unknown, options?: QueueTypes.JobOptions): QueueTypes.PublishRequest;
707
+ /**
708
+ * Builds the context a handler and its hooks receive.
709
+ *
710
+ * @param {string} strategy - Mount name the job came from
711
+ * @param {string} queue - Queue the job came from
712
+ * @param {QueueTypes.IncomingJob} incoming - The job as received from the transport
713
+ * @param {number} attempts - Total attempts this job may take
714
+ * @returns {QueueTypes.JobContext} The context of this attempt
715
+ */
716
+ protected createContext(strategy: string, queue: string, incoming: QueueTypes.IncomingJob, attempts: number): QueueTypes.JobContext;
717
+ /**
718
+ * Returns the counters of a queue, creating them on first use.
719
+ *
720
+ * @param {string} strategy - Mount name
721
+ * @param {string} queue - Queue name
722
+ * @returns {QueueTypes.QueueMetrics} The mutable counters of that queue
723
+ */
724
+ protected metricsFor(strategy: string, queue: string): QueueTypes.QueueMetrics;
725
+ /**
726
+ * Appends a processed job to the inspection buffer, dropping the oldest entry
727
+ * once the buffer is full.
728
+ *
729
+ * The payload and headers of a failure are kept only while `capturePayloads`
730
+ * is on, and never for a job that completed: that is where the volume is, and
731
+ * a job that worked has nothing to diagnose.
732
+ *
733
+ * @param {Omit<QueueTypes.JobEvent, 'at'>} event - The processed job
734
+ * @param {unknown} [payload] - Payload of the attempt, kept when capturing is on
735
+ * @param {Record<string, string>} [headers] - Headers of the attempt, kept when capturing is on
736
+ * @returns {void}
737
+ */
738
+ protected record(event: Omit<QueueTypes.JobEvent, 'at'>, payload?: unknown, headers?: Record<string, string>): void;
739
+ /**
740
+ * Describes an error for the inspection buffer: its name, message and a capped
741
+ * stack, plus what this module knows about it when it raised the error itself.
742
+ *
743
+ * @param {Error} error - Error the attempt failed with
744
+ * @returns {QueueTypes.JobFailure} The failure, ready to be inspected
745
+ */
746
+ protected describeFailure(error: Error): QueueTypes.JobFailure;
747
+ /**
748
+ * Reports the state of a mount for the devtools.
749
+ *
750
+ * @param {QueueTypes.MountedStrategy} mount - Mount to describe
751
+ * @returns {QueueTypes.StrategyStatus} What the mount is currently doing
752
+ */
753
+ protected statusOf(mount: QueueTypes.MountedStrategy<any>): QueueTypes.StrategyStatus;
754
+ /**
755
+ * Runs a piece of lifecycle work after everything scheduled before it, so
756
+ * consumers are never started or stopped concurrently.
757
+ *
758
+ * @param {() => Promise<void>} task - Work to run
759
+ * @returns {Promise<void>} Resolves once this task ran
760
+ */
761
+ protected enqueue(task: () => Promise<void>): Promise<void>;
762
+ /**
763
+ * Key a handler is registered under.
764
+ *
765
+ * @param {string} strategy - Mount name
766
+ * @param {string} queue - Queue name
767
+ * @param {string} job - Job name
768
+ * @returns {string} The registration key
769
+ */
770
+ protected consumerKey(strategy: string, queue: string, job: string): string;
771
+ /**
772
+ * Key a queue is tracked under.
773
+ *
774
+ * @param {string} strategy - Mount name
775
+ * @param {string} queue - Queue name
776
+ * @returns {string} The queue key
777
+ */
778
+ protected queueKey(strategy: string, queue: string): string;
779
+ /**
780
+ * Starts the consumers once the container is initialized, so every handler
781
+ * registered by a decorator is known before the first job is received.
782
+ *
783
+ * @returns {void}
784
+ */
785
+ protected init(): void;
786
+ /**
787
+ * Closes every connection when the container is torn down.
788
+ *
789
+ * @returns {void}
790
+ */
791
+ protected destroy(): void;
792
+ }
793
+ //#endregion
794
+ //#region src/Utils/Errors.d.ts
795
+ /**
796
+ * Turns any thrown value into a {@link QueueError}, leaving queue errors alone so
797
+ * their operation and `retryable` flag survive.
798
+ *
799
+ * @param error - The value that was thrown.
800
+ * @param message - Message of the resulting error.
801
+ * @param operation - Queue operation that failed.
802
+ * @param metadata - Additional non-sensitive context.
803
+ * @returns The error to throw.
804
+ */
805
+ export declare function toQueueError(error: unknown, message: string, operation: string, metadata?: Record<string, unknown>): QueueError;
806
+ //#endregion
807
+ //#region src/Utils/Job.d.ts
808
+ /**
809
+ * Job name a handler registers under to receive every job of its queue that no
810
+ * other handler claims.
811
+ */
812
+ export declare const WILDCARD_JOB = "*";
813
+ /** Header carrying the job name across transports that have no native notion of one. */
814
+ export declare const JOB_HEADER = "x-job";
815
+ /** Header carrying the current attempt number. */
816
+ export declare const ATTEMPT_HEADER = "x-attempt";
817
+ /** Header carrying the total number of attempts the publisher asked for. */
818
+ export declare const ATTEMPTS_HEADER = "x-attempts";
819
+ /** Header carrying the partition or routing key a job was published with. */
820
+ export declare const KEY_HEADER = "x-key";
821
+ /** Header carrying the priority a job was published with. */
822
+ export declare const PRIORITY_HEADER = "x-priority";
823
+ /**
824
+ * Most attempts a job may ever take.
825
+ *
826
+ * On transports that do not retry natively the budget is read off the wire, so a
827
+ * producer could otherwise ask for an arbitrarily large one and turn a single
828
+ * poison message into an unbounded republish loop.
829
+ */
830
+ export declare const MAX_ATTEMPTS = 50;
831
+ /**
832
+ * Longest a retry may be held back, one day.
833
+ *
834
+ * An exponential backoff over a large attempt count overflows to `Infinity`,
835
+ * which `setTimeout` clamps to one millisecond: the backoff meant to slow
836
+ * retries down would make them as fast as the runtime allows.
837
+ */
838
+ export declare const MAX_BACKOFF_MS = 86400000;
839
+ /**
840
+ * Reads a positive integer from a raw header value.
841
+ *
842
+ * @param raw - Header value as received from the transport, in any shape.
843
+ * @param fallback - Value returned when the header is absent or unusable.
844
+ * @param max - Largest value accepted, so a value off the wire cannot be unbounded.
845
+ * @returns The parsed integer, or the fallback.
846
+ */
847
+ export declare function readNumericHeader(raw: unknown, fallback: number, max?: number): number;
848
+ /**
849
+ * Normalizes transport headers into plain strings, so handlers never have to deal
850
+ * with buffers or numbers coming from the wire.
851
+ *
852
+ * @param headers - Raw headers as received from the transport.
853
+ * @returns Headers with string values only.
854
+ */
855
+ export declare function normalizeHeaders(headers: Record<string, unknown> | undefined | null): Record<string, string>;
856
+ /**
857
+ * Computes how long to wait before the next attempt of a job.
858
+ *
859
+ * @param backoff - Backoff policy, a number being a fixed delay in milliseconds.
860
+ * @param attempt - Attempt that just failed, starting at 1.
861
+ * @returns Delay in milliseconds, zero when no backoff is configured and never above {@link MAX_BACKOFF_MS}.
862
+ */
863
+ export declare function resolveBackoff(backoff: QueueTypes.Backoff | undefined, attempt: number): number;
864
+ /**
865
+ * Serializes a payload for transports that only carry bytes.
866
+ *
867
+ * @param payload - Payload to serialize.
868
+ * @returns The payload as a UTF-8 JSON buffer.
869
+ * @throws {QueueError} When the payload cannot be serialized.
870
+ */
871
+ export declare function encodePayload(payload: unknown): Buffer;
872
+ /**
873
+ * Deserializes a payload received from a transport that only carries bytes.
874
+ * Content that is not JSON is returned as text, so foreign producers do not
875
+ * break the consumer.
876
+ *
877
+ * @param content - Raw bytes received from the transport.
878
+ * @returns The parsed payload, or the raw text when it is not JSON.
879
+ */
880
+ export declare function decodePayload(content: Uint8Array | Buffer | string | null | undefined): unknown;
881
+ /**
882
+ * Generates an id for transports that do not assign one themselves.
883
+ *
884
+ * @returns A unique job id.
885
+ */
886
+ export declare function generateJobId(): string;
887
+ /**
888
+ * Waits for the given number of milliseconds.
889
+ *
890
+ * @param ms - Milliseconds to wait, values below one resolve immediately.
891
+ * @returns Resolves once the delay elapsed.
892
+ */
893
+ export declare function delay(ms: number): Promise<void>;
894
+ /**
895
+ * Drops the keys whose value is undefined.
896
+ *
897
+ * Options objects are built by spreading whatever the caller set, which leaves
898
+ * own properties holding `undefined` behind. A library that merges options with
899
+ * `Object.assign` cannot tell those apart from a deliberate value, so they have
900
+ * to go before the object is handed over.
901
+ *
902
+ * @param source - Object to clean up.
903
+ * @returns A copy without the undefined entries.
904
+ */
905
+ export declare function prune<T extends Record<string, unknown>>(source: T): T;
906
+ //#endregion
907
+ //#region src/Utils/Mount.d.ts
908
+ /**
909
+ * Type checks a single strategy mount and erases its options, so a list of
910
+ * mounts of different strategies stays checked.
911
+ *
912
+ * `QueueManager.mount()` infers the strategy from its argument and checks
913
+ * `initOptions` against it. A plain array in `QueuePlugin` options cannot do
914
+ * that, since there is no inference site per entry - this helper is that site.
915
+ *
916
+ * @param {QueueTypes.Mount<T>} mount - Mount name, strategy class and its init options
917
+ * @returns {QueueTypes.AnyMount} The very same mount, with its options type erased
918
+ *
919
+ * @example
920
+ * ```ts
921
+ * app.addPlugin(QueuePlugin, {
922
+ * strategies: [
923
+ * defineQueueStrategy({ strategy: RabbitMQStrategy, initOptions: { url: 'amqp://localhost' } }),
924
+ * defineQueueStrategy({
925
+ * name: 'events',
926
+ * strategy: KafkaStrategy,
927
+ * initOptions: { client: { brokers: ['localhost:9092'] }, groupId: 'workers' },
928
+ * }),
929
+ * ],
930
+ * });
931
+ * ```
932
+ */
933
+ export declare function defineQueueStrategy<T extends QueueStrategy<unknown>>(mount: QueueTypes.Mount<T>): QueueTypes.AnyMount;
934
+ //#endregion
935
+ //#region src/Utils/Metadata.d.ts
936
+ /**
937
+ * Stores the options a `@Consumer()` class was declared with.
938
+ *
939
+ * @param prototype - Prototype of the decorated class.
940
+ * @param options - Options the class was declared with.
941
+ * @returns Nothing.
942
+ */
943
+ export declare function setConsumerOptions(prototype: object, options: QueueTypes.ConsumerOptions): void;
944
+ /**
945
+ * Reads the options of the closest `@Consumer()` class in the prototype chain,
946
+ * so a handler declared on a base class still finds its queue.
947
+ *
948
+ * @param prototype - Prototype to start looking at.
949
+ * @returns The options, or undefined when no class in the chain is a consumer.
950
+ */
951
+ export declare function getConsumerOptions(prototype: object | null): QueueTypes.ConsumerOptions | undefined;
952
+ //#endregion
953
+ export { QueueStrategy, QueueTypes };