@bymax-one/nest-queue 1.0.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,1386 @@
1
+ import * as _nestjs_common from '@nestjs/common';
2
+ import { ModuleMetadata, Type, DynamicModule, OnModuleDestroy, HttpException, HttpStatus } from '@nestjs/common';
3
+ import { JobsOptions, QueueOptions, Telemetry, BulkJobOptions, Queue, Job, FlowJob, JobNode, FlowProducer, Worker, QueueEvents } from 'bullmq';
4
+ export { FlowJob, FlowProducer, Job, JobNode, JobSchedulerJson, JobsOptions, Queue, QueueEvents, SandboxedJob, Telemetry, Worker } from 'bullmq';
5
+ import { Redis, RedisOptions } from 'ioredis';
6
+
7
+ /**
8
+ * @fileoverview Dual-mode Redis connection configuration contract.
9
+ * @layer server/interfaces
10
+ */
11
+
12
+ /**
13
+ * Connection configuration — Mode A (bring your own client) or Mode B
14
+ * (library-owned), discriminated by the presence of `client`, `url`, or
15
+ * `options`. The three arms are mutually exclusive.
16
+ *
17
+ * Mode A: the caller passes a `Redis` already configured for BullMQ (typically
18
+ * obtained from `@bymax-one/nest-cache`). The library uses it AS-IS for the
19
+ * Queue and FlowProducer role and never closes it; Worker/QueueEvents roles get
20
+ * a duplicated connection forced to `maxRetriesPerRequest: null`.
21
+ *
22
+ * Mode B: the library opens its own `ioredis` from a URL or options object. The
23
+ * Queue/FlowProducer connection keeps ioredis' default retries (so enqueue
24
+ * fails fast during a Redis outage); the library closes it on shutdown.
25
+ *
26
+ * @example Mode A
27
+ * const config: QueueConnectionConfig = { client: queueRedis }
28
+ * @example Mode B (url)
29
+ * const config: QueueConnectionConfig = { url: 'redis://localhost:6379', options: { db: 1 } }
30
+ * @example Mode B (options only)
31
+ * const config: QueueConnectionConfig = { options: { host: 'localhost', port: 6379, db: 1 } }
32
+ */
33
+ type QueueConnectionConfig = {
34
+ client: Redis;
35
+ ownsConnection?: false;
36
+ } | {
37
+ url: string;
38
+ options?: Partial<RedisOptions>;
39
+ } | {
40
+ options: RedisOptions;
41
+ };
42
+ /** Internal discriminator the ConnectionResolver uses to tag the active mode. */
43
+ type QueueConnectionMode = 'mode-a-byo' | 'mode-b-owned';
44
+
45
+ /**
46
+ * @fileoverview Synchronous and asynchronous module configuration contracts.
47
+ * @layer server/interfaces
48
+ */
49
+
50
+ /**
51
+ * Synchronous configuration for `BymaxQueueModule.forRoot()`.
52
+ *
53
+ * @example
54
+ * BymaxQueueModule.forRoot({ connection: { url: 'redis://localhost:6379' } })
55
+ */
56
+ interface BymaxQueueModuleOptions {
57
+ /** Redis connection configuration. Required. */
58
+ connection: QueueConnectionConfig;
59
+ /** Default options applied to every job enqueued through the service. */
60
+ defaultJobOptions?: JobsOptions;
61
+ /** Prefix for all Redis keys (multi-tenant isolation). Default: 'bull'. */
62
+ prefix?: string;
63
+ /** Default options applied to every Queue, merged with the job/prefix defaults. */
64
+ queueOptions?: Partial<Omit<QueueOptions, 'connection' | 'defaultJobOptions' | 'prefix'>>;
65
+ /** Flow Producer configuration. Opt-in. */
66
+ flows?: {
67
+ enabled?: boolean;
68
+ };
69
+ /** Metrics collection configuration. Opt-in. */
70
+ metrics?: {
71
+ enabled?: boolean;
72
+ cacheTtlMs?: number;
73
+ };
74
+ /**
75
+ * Opt-in OpenTelemetry — a BullMQ `Telemetry` (typically `new BullMQOtel(...)`
76
+ * from the optional peer `bullmq-otel`); attached to every Queue and Worker.
77
+ */
78
+ telemetry?: Telemetry;
79
+ /** Behavior on application shutdown. */
80
+ shutdown?: {
81
+ drainTimeoutMs?: number;
82
+ drainOnShutdown?: boolean;
83
+ };
84
+ /**
85
+ * Register the module globally. Mapped to `DynamicModule.global` by the
86
+ * configurable module builder's `setExtras` (no hand-written `@Global`).
87
+ * Default: true.
88
+ */
89
+ isGlobal?: boolean;
90
+ /** Mode B only: milliseconds to wait for Redis `ready` before throwing. Default: 10_000. */
91
+ connectionReadyTimeoutMs?: number;
92
+ }
93
+ /** A factory that produces module options, for async configuration. */
94
+ interface BymaxQueueOptionsFactory {
95
+ /** Build the module options synchronously or asynchronously. */
96
+ createQueueOptions(): Promise<BymaxQueueModuleOptions> | BymaxQueueModuleOptions;
97
+ }
98
+ /** Asynchronous configuration mirroring the NestJS async dynamic-module pattern. */
99
+ interface BymaxQueueModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
100
+ /** Factory returning the module options. */
101
+ useFactory?: (...args: unknown[]) => Promise<BymaxQueueModuleOptions> | BymaxQueueModuleOptions;
102
+ /** Class implementing the options factory. */
103
+ useClass?: Type<BymaxQueueOptionsFactory>;
104
+ /** Existing provider implementing the options factory. */
105
+ useExisting?: Type<BymaxQueueOptionsFactory>;
106
+ /** Providers injected into the factory. */
107
+ inject?: readonly (Type<unknown> | string | symbol)[];
108
+ }
109
+
110
+ /** Generated configurable-module artifacts; `forRoot`/`forRootAsync` register the module. */
111
+ declare const ConfigurableModuleClass: _nestjs_common.ConfigurableModuleCls<BymaxQueueModuleOptions, "forRoot", "createQueueOptions", {
112
+ isGlobal: boolean;
113
+ }>;
114
+ declare const OPTIONS_TYPE: BymaxQueueModuleOptions & Partial<{
115
+ isGlobal: boolean;
116
+ }>;
117
+ declare const ASYNC_OPTIONS_TYPE: _nestjs_common.ConfigurableModuleAsyncOptions<BymaxQueueModuleOptions, "createQueueOptions"> & Partial<{
118
+ isGlobal: boolean;
119
+ }>;
120
+ /**
121
+ * Root module for the queue library. Use `forRoot` for synchronous registration
122
+ * or `forRootAsync` when the options depend on other modules.
123
+ *
124
+ * @example Synchronous
125
+ * BymaxQueueModule.forRoot({ connection: { url: 'redis://localhost:6379' } })
126
+ * @example Asynchronous (Mode A — bring your own client)
127
+ * BymaxQueueModule.forRootAsync({
128
+ * inject: [REDIS_CLIENT],
129
+ * useFactory: (client: Redis) => ({ connection: { client } }),
130
+ * })
131
+ */
132
+ declare class BymaxQueueModule extends ConfigurableModuleClass {
133
+ /**
134
+ * Synchronous registration. Validates options, resolves defaults, and wires the
135
+ * connection resolver (initialized via async factory), the queue service, the
136
+ * worker registry, the queue-events registry, processor discovery, and the
137
+ * graceful-shutdown lifecycle.
138
+ *
139
+ * @param options - Static module options.
140
+ * @returns The configured dynamic module.
141
+ */
142
+ static forRoot(options: typeof OPTIONS_TYPE): DynamicModule;
143
+ /**
144
+ * Asynchronous registration. Use when options depend on other modules
145
+ * (ConfigService, BymaxCacheModule, etc.). Mirrors the NestJS standard async
146
+ * dynamic-module pattern (`useFactory` | `useClass` | `useExisting` + `inject`)
147
+ * and registers the same provider graph and exports as {@link forRoot}, with
148
+ * `global` applied from the `isGlobal` extra. The resolved options and the
149
+ * initialized connection are derived from the async-resolved options at runtime.
150
+ *
151
+ * @param options - Asynchronous module options.
152
+ * @returns The configured dynamic module.
153
+ */
154
+ static forRootAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule;
155
+ /**
156
+ * Providers shared verbatim by both registration paths: the options alias, the
157
+ * initialized connection resolver, the resolved client/mode tokens, and the
158
+ * singleton services. `BYMAX_QUEUE_RESOLVED_OPTIONS`, `FlowService`, and
159
+ * `MetricsService` are intentionally omitted — they differ per path.
160
+ *
161
+ * @returns The shared provider list.
162
+ */
163
+ private static buildCoreProviders;
164
+ /**
165
+ * The public exports shared by both registration paths.
166
+ *
167
+ * @returns A fresh exports list (services and injection tokens).
168
+ */
169
+ private static buildExports;
170
+ }
171
+
172
+ /**
173
+ * @fileoverview Symbol injection tokens for the module's providers.
174
+ * @layer server/constants
175
+ */
176
+ /** Token for the raw, user-supplied module options. */
177
+ declare const BYMAX_QUEUE_OPTIONS: unique symbol;
178
+ /** Token for the resolved Redis client used by the Queue role. */
179
+ declare const BYMAX_QUEUE_REDIS_CLIENT: unique symbol;
180
+ /** Token for the resolved connection mode (`mode-a-byo` or `mode-b-owned`). */
181
+ declare const BYMAX_QUEUE_CONNECTION_MODE: unique symbol;
182
+ /** Token for the frozen, defaults-applied module options. */
183
+ declare const BYMAX_QUEUE_RESOLVED_OPTIONS: unique symbol;
184
+
185
+ /**
186
+ * @fileoverview Fully-resolved module options with defaults applied.
187
+ * @layer server/config
188
+ */
189
+
190
+ /** Fully-resolved options — every optional field of the input is filled. */
191
+ interface ResolvedQueueOptions {
192
+ /** Connection configuration, passed through untouched. */
193
+ connection: QueueConnectionConfig;
194
+ /** Default job options, merged over the library defaults. */
195
+ defaultJobOptions: JobsOptions;
196
+ /** Redis key prefix. */
197
+ prefix: string;
198
+ /** Extra Queue options applied to every queue. */
199
+ queueOptions: Partial<Omit<QueueOptions, 'connection' | 'defaultJobOptions' | 'prefix'>>;
200
+ /** Flow support configuration. */
201
+ flows: {
202
+ enabled: boolean;
203
+ };
204
+ /** Metrics support configuration. */
205
+ metrics: {
206
+ enabled: boolean;
207
+ cacheTtlMs: number;
208
+ };
209
+ /** Shutdown behavior configuration. */
210
+ shutdown: {
211
+ drainTimeoutMs: number;
212
+ drainOnShutdown: boolean;
213
+ };
214
+ /** Optional OpenTelemetry instance, attached to every Queue/Worker when present. */
215
+ telemetry?: Telemetry;
216
+ /** Mode B ready timeout in milliseconds. */
217
+ connectionReadyTimeoutMs: number;
218
+ }
219
+
220
+ /**
221
+ * @fileoverview Job descriptor used by the bulk-enqueue API.
222
+ * @layer server/interfaces
223
+ */
224
+
225
+ /**
226
+ * A single job descriptor used by `enqueueBulk`.
227
+ *
228
+ * @example
229
+ * const job: BulkJob<{ to: string }> = { name: 'send', data: { to: 'a@b.com' } }
230
+ */
231
+ interface BulkJob<TData = unknown> {
232
+ /** Job name used by handlers to dispatch. */
233
+ name: string;
234
+ /** Typed job payload. */
235
+ data: TData;
236
+ /** Per-job BullMQ options (priority, delay, jobId, deduplication, ...). */
237
+ options?: BulkJobOptions;
238
+ }
239
+
240
+ /**
241
+ * @fileoverview Job status union shared across runtimes.
242
+ * @layer shared/types
243
+ */
244
+ /**
245
+ * Snapshot statuses BullMQ exposes via `Queue.getJobCounts()`.
246
+ * Kept in sync with BullMQ's internal status set.
247
+ *
248
+ * @example
249
+ * const status: JobStatus = 'waiting'
250
+ */
251
+ type JobStatus = 'waiting' | 'active' | 'completed' | 'failed' | 'delayed' | 'paused';
252
+
253
+ /**
254
+ * @fileoverview Instantaneous queue metrics snapshot type.
255
+ * @layer shared/types
256
+ */
257
+
258
+ /**
259
+ * Instantaneous snapshot of a queue's job counts grouped by status.
260
+ *
261
+ * @example
262
+ * const metrics: QueueMetrics = {
263
+ * queue: 'email',
264
+ * counts: { waiting: 3, active: 1, completed: 120, failed: 2, delayed: 0, paused: 0 },
265
+ * collectedAt: '2026-06-26T12:00:00.000Z',
266
+ * }
267
+ */
268
+ interface QueueMetrics {
269
+ /** Queue name the snapshot was taken from. */
270
+ queue: string;
271
+ /** Number of jobs in each of the six BullMQ statuses. */
272
+ counts: Record<JobStatus, number>;
273
+ /** ISO 8601 UTC timestamp when the snapshot was collected. */
274
+ collectedAt: string;
275
+ }
276
+
277
+ /**
278
+ * @fileoverview Discriminated union for the BullMQ Job Schedulers (recurring jobs) API.
279
+ * @layer shared/types
280
+ */
281
+ /**
282
+ * Discriminated union for the BullMQ Job Schedulers API — the current
283
+ * recurring-jobs surface (the legacy repeatable API is removed in BullMQ v6).
284
+ * Either `pattern` (cron) OR `every` (ms interval) is provided, never both.
285
+ *
286
+ * A thin, validated projection of BullMQ's `RepeatOptions` minus the internal
287
+ * scheduler `key`.
288
+ *
289
+ * @example Cron schedule
290
+ * const daily: JobSchedulerRepeatOptions = { pattern: '0 3 * * *', tz: 'America/Sao_Paulo' }
291
+ * @example Fixed interval
292
+ * const everyMinute: JobSchedulerRepeatOptions = { every: 60_000 }
293
+ */
294
+ type JobSchedulerRepeatOptions = {
295
+ /** Crontab expression (5-field, or 6-field with seconds). */
296
+ pattern: string;
297
+ /** IANA timezone (e.g. 'America/Sao_Paulo'). Default: UTC. */
298
+ tz?: string;
299
+ /** Optional cap on the number of runs. */
300
+ limit?: number;
301
+ /** Start time (epoch ms or ISO string). */
302
+ startDate?: number | string;
303
+ /** Stop time (epoch ms or ISO string). Must be in the future or BullMQ throws. */
304
+ endDate?: number | string;
305
+ } | {
306
+ /** Interval in milliseconds between runs. */
307
+ every: number;
308
+ /** Optional cap on the number of runs. */
309
+ limit?: number;
310
+ /** Phase offset (ms) applied to interval schedulers. */
311
+ offset?: number;
312
+ /** Start time (epoch ms or ISO string). */
313
+ startDate?: number | string;
314
+ /** Stop time (epoch ms or ISO string). */
315
+ endDate?: number | string;
316
+ };
317
+
318
+ /**
319
+ * @fileoverview Dual-mode Redis connection resolver. Resolves a BYO client
320
+ * (Mode A) or opens a library-owned ioredis with a ready timeout (Mode B), and
321
+ * enforces the per-role `maxRetriesPerRequest` policy.
322
+ * @layer server/services
323
+ */
324
+
325
+ /**
326
+ * Resolves the Queue-role Redis connection and tracks ownership so the lifecycle
327
+ * knows whether to close it on shutdown.
328
+ *
329
+ * - Mode A (BYO): the received client is used as-is for the Queue/FlowProducer
330
+ * role and never closed; a duplicated probe verifies that the blocking-role
331
+ * override resolves to `maxRetriesPerRequest: null`.
332
+ * - Mode B (owned): the library opens its own client (URL or options) and waits
333
+ * for `ready`, closing it on shutdown.
334
+ */
335
+ declare class ConnectionResolver {
336
+ private readonly options;
337
+ private client;
338
+ private mode;
339
+ constructor(options: BymaxQueueModuleOptions);
340
+ /** Resolve and validate the connection. Call once during module bootstrap. */
341
+ init(): Promise<void>;
342
+ /** The resolved Queue-role client. */
343
+ getClient(): Redis;
344
+ /** The resolved connection mode. */
345
+ getMode(): QueueConnectionMode;
346
+ /** Whether the library owns (and must close) the connection. */
347
+ isOwned(): boolean;
348
+ /** Close the library-owned connection on shutdown; never touch a BYO client. */
349
+ teardown(): Promise<void>;
350
+ /** Validate and adopt a bring-your-own client for the Queue role. */
351
+ private initModeA;
352
+ /** Resolve when the owned client reaches `ready`, or reject on timeout/error. */
353
+ private waitReady;
354
+ }
355
+
356
+ /**
357
+ * @fileoverview Base queue service — per-name Queue cache, typed enqueue/inspect,
358
+ * uncached metrics, and control helpers over BullMQ.
359
+ * @layer server/services
360
+ */
361
+
362
+ /** A typed job in the library's public shape. */
363
+ type TypedJob<TData, TResult> = Job<TData, TResult>;
364
+ /** Status filter accepted by {@link QueueService.cleanQueue}, matching BullMQ. */
365
+ type CleanableStatus = 'completed' | 'failed' | 'delayed' | 'wait' | 'active' | 'paused';
366
+ /**
367
+ * The central interaction point with BullMQ queues. Maintains a per-name `Queue`
368
+ * cache, applies module defaults to every queue, and exposes typed helpers for
369
+ * enqueueing, inspection, metrics, and lifecycle control.
370
+ */
371
+ declare class QueueService {
372
+ private readonly connection;
373
+ private readonly options;
374
+ private readonly logger;
375
+ private readonly queues;
376
+ constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
377
+ /**
378
+ * Return the cached `Queue` for `queueName`, or create one with the module
379
+ * defaults applied. Subsequent calls with the same name return the same
380
+ * instance.
381
+ *
382
+ * @param queueName - Unique queue name (used as the Redis key prefix).
383
+ * @param overrides - Optional per-queue option overrides.
384
+ */
385
+ getOrCreateQueue<TData = unknown, TResult = unknown>(queueName: string, overrides?: Partial<Omit<QueueOptions, 'connection' | 'prefix'>>): Queue<TData, TResult>;
386
+ /** Resolve the raw cached queue (default generics) for internal delegation. */
387
+ private queueFor;
388
+ /**
389
+ * Add a single job to a queue. The queue is created lazily if needed.
390
+ *
391
+ * Delivery is **at-least-once** — a handler may run more than once for the same
392
+ * job (worker crash, lock expiry, or a shutdown force-close). Make handlers
393
+ * idempotent: use an idempotency key on writes, prefer upserts over inserts, or
394
+ * keep an "already-processed" marker keyed by `job.id`.
395
+ *
396
+ * `options` surfaces BullMQ natives directly — including `jobId` (idempotent
397
+ * insert: a second add with the same id is a no-op while the first exists) and
398
+ * `deduplication` (windowed deduplication). The library writes no deduplication
399
+ * code; BullMQ owns the behavior. The deduplication key is independent of
400
+ * `jobId`, so both can be set together or apart. Both collapse duplicate
401
+ * **producers** and do not change the at-least-once guarantee on the
402
+ * **consumer** side.
403
+ *
404
+ * Deduplication modes (`deduplication: { id, ttl?, extend?, replace?, keepLastIfActive? }`):
405
+ * - Simple `{ id }` — collapse duplicates until the in-flight job completes or fails.
406
+ * - Throttle `{ id, ttl }` — ignore duplicates for `ttl` milliseconds.
407
+ * - Debounce `{ id, ttl, extend: true, replace: true }` — keep only the latest
408
+ * data; each duplicate resets the TTL.
409
+ * - keep-last-if-active `{ id, keepLastIfActive: true }` — while a job is active,
410
+ * store the latest data and run a single follow-up when it finishes.
411
+ *
412
+ * @param queueName - Target queue (created lazily if absent).
413
+ * @param jobName - Job identifier used by `@Process` to dispatch.
414
+ * @param data - Typed job payload.
415
+ * @param options - Per-job BullMQ options (priority, delay, `jobId`, `deduplication`, ...).
416
+ * @returns The created job.
417
+ * @example Throttle: at most one reindex per term every 5 seconds.
418
+ * await queueService.enqueue('search', 'reindex', { term }, {
419
+ * deduplication: { id: `reindex:${term}`, ttl: 5_000 },
420
+ * })
421
+ */
422
+ enqueue<TData = unknown, TResult = unknown>(queueName: string, jobName: string, data: TData, options?: JobsOptions): Promise<TypedJob<TData, TResult>>;
423
+ /**
424
+ * Enqueue multiple jobs in a single Redis roundtrip. The batch is bounded by
425
+ * {@link MAX_BULK_SIZE} to guard against a self-inflicted memory spike; any
426
+ * failure is wrapped in a `QueueException`. Delivery is at-least-once, so every
427
+ * job's handler must be idempotent (see {@link QueueService.enqueue}).
428
+ *
429
+ * @param queueName - Target queue.
430
+ * @param jobs - Job descriptors (length must not exceed the bulk size cap).
431
+ */
432
+ enqueueBulk<TData = unknown, TResult = unknown>(queueName: string, jobs: readonly BulkJob<TData>[]): Promise<TypedJob<TData, TResult>[]>;
433
+ /**
434
+ * Fetch a job by id. Returns `null` when the job does not exist.
435
+ *
436
+ * @param queueName - Target queue.
437
+ * @param jobId - The job identifier.
438
+ */
439
+ getJob<TData = unknown, TResult = unknown>(queueName: string, jobId: string): Promise<TypedJob<TData, TResult> | null>;
440
+ /**
441
+ * Fetch jobs in a given status with pagination.
442
+ *
443
+ * @param queueName - Target queue.
444
+ * @param status - Status filter.
445
+ * @param start - Starting index (inclusive). Default: 0.
446
+ * @param end - Ending index (inclusive). Default: 50.
447
+ */
448
+ getJobs<TData = unknown, TResult = unknown>(queueName: string, status: JobStatus, start?: number, end?: number): Promise<TypedJob<TData, TResult>[]>;
449
+ /**
450
+ * Return an uncached snapshot of job counts grouped by status.
451
+ *
452
+ * @param queueName - Target queue.
453
+ */
454
+ getMetrics(queueName: string): Promise<QueueMetrics>;
455
+ /**
456
+ * Create or update a Job Scheduler — the current BullMQ recurring-jobs API.
457
+ * Idempotent by `schedulerId`: BullMQ performs an atomic `override: true`
458
+ * upsert, so re-registering on every boot updates the schedule in place and
459
+ * never duplicates it. This supersedes the repeatable-jobs surface removed in
460
+ * BullMQ v6.
461
+ *
462
+ * The schedule is structurally validated first; the cron string itself is
463
+ * parsed by BullMQ at registration time, and a parse failure is rethrown as
464
+ * `INVALID_REPEAT_OPTIONS` (400).
465
+ *
466
+ * @param queueName - Target queue.
467
+ * @param schedulerId - Stable, unique scheduler identifier (the upsert key).
468
+ * @param repeat - Cron `pattern` OR `every` (ms), plus optional tz/limit/start/end.
469
+ * @param template - Optional job template; `name` defaults to `schedulerId`, `data` to `{}`.
470
+ * @returns The first scheduled (delayed) job, or `undefined`.
471
+ * @throws {QueueException} `INVALID_REPEAT_OPTIONS` (400) for an invalid schedule.
472
+ */
473
+ upsertJobScheduler<TData = unknown, TResult = unknown>(queueName: string, schedulerId: string, repeat: JobSchedulerRepeatOptions, template?: {
474
+ name?: string;
475
+ data?: TData;
476
+ opts?: JobsOptions;
477
+ }): Promise<Job<TData, TResult> | undefined>;
478
+ /**
479
+ * Remove a Job Scheduler by id.
480
+ *
481
+ * @param queueName - Target queue.
482
+ * @param schedulerId - The scheduler identifier to remove.
483
+ * @returns `true` when a scheduler was removed, `false` otherwise.
484
+ */
485
+ removeJobScheduler(queueName: string, schedulerId: string): Promise<boolean>;
486
+ /**
487
+ * List the Job Schedulers registered on a queue, for inspection or health checks.
488
+ *
489
+ * @param queueName - Target queue.
490
+ * @param start - Page start index (inclusive). Default: 0.
491
+ * @param end - Page end index (inclusive). Default: 50.
492
+ * @param asc - Sort ascending by next run time. Default: true.
493
+ * @returns The registered schedulers in the requested page.
494
+ */
495
+ getJobSchedulers(queueName: string, start?: number, end?: number, asc?: boolean): Promise<Awaited<ReturnType<Queue['getJobSchedulers']>>>;
496
+ /** Pause a queue, halting the processing of new jobs. */
497
+ pauseQueue(queueName: string): Promise<void>;
498
+ /** Resume a previously paused queue. */
499
+ resumeQueue(queueName: string): Promise<void>;
500
+ /**
501
+ * Remove up to `limit` jobs older than `gracePeriodMs` in a given status. The
502
+ * argument order mirrors BullMQ's `Queue.clean(grace, limit, type?)` exactly;
503
+ * `limit` is required and `0` means no limit. Returns the removed job ids.
504
+ *
505
+ * @param queueName - Target queue.
506
+ * @param gracePeriodMs - Keep jobs younger than this many milliseconds.
507
+ * @param limit - Maximum number of jobs to remove (`0` = no limit).
508
+ * @param status - Status of jobs to clean.
509
+ */
510
+ cleanQueue(queueName: string, gracePeriodMs: number, limit: number, status?: CleanableStatus): Promise<string[]>;
511
+ /** Return a read-only view of the cached queues. */
512
+ getCachedQueues(): ReadonlyMap<string, Queue>;
513
+ /**
514
+ * Close every cached queue (swallowing per-queue errors) and clear the cache.
515
+ *
516
+ * Deliberately NOT named `onModuleDestroy`: NestJS invokes lifecycle hooks by
517
+ * method name, so a hook here would run on Nest's own schedule, concurrently
518
+ * with — and possibly before — the bounded drain in `QueueLifecycle`. Closing a
519
+ * queue while its worker is still draining is exactly what the ordered sequence
520
+ * exists to prevent. `QueueLifecycle` is the only caller.
521
+ */
522
+ closeAll(): Promise<void>;
523
+ }
524
+
525
+ /**
526
+ * @fileoverview FlowService — a guarded wrapper over the BullMQ `FlowProducer`
527
+ * for hierarchical (parent/child) job graphs. Opt-in: the provider is always
528
+ * registered so injecting it never fails, but every method throws
529
+ * `FLOW_DISABLED` (503) unless `flows.enabled` was set.
530
+ * @layer server/services
531
+ */
532
+
533
+ /**
534
+ * Wrapper over the BullMQ `FlowProducer` for hierarchical (parent/child) job
535
+ * graphs. The producer runs on the main connection (Mode A received client or
536
+ * Mode B library-owned client) and is created only when flows are enabled.
537
+ *
538
+ * Opt-in: this provider is registered unconditionally so that injecting it never
539
+ * raises `UnknownDependenciesException`; instead, every operation throws
540
+ * `QueueException(FLOW_DISABLED, 503)` when `flows.enabled !== true`.
541
+ *
542
+ * @example
543
+ * await flowService.add({
544
+ * name: 'generate-pdf',
545
+ * queueName: 'pdf',
546
+ * data: { reportId: '123' },
547
+ * children: [{ name: 'fetch-data', queueName: 'data', data: { reportId: '123' } }],
548
+ * })
549
+ */
550
+ declare class FlowService {
551
+ private readonly logger;
552
+ private readonly producer?;
553
+ constructor(connection: ConnectionResolver, enabled: boolean, prefix: string, telemetry?: Telemetry);
554
+ /**
555
+ * Add a flow tree (a parent with its descendant children). The root job
556
+ * becomes processable only after every descendant has completed.
557
+ *
558
+ * By BullMQ default a failed (retry-exhausted) child does NOT fail its parent:
559
+ * the parent stays in the `waiting-children` state until the child eventually
560
+ * completes. To make a child's failure propagate upward and fail its parent,
561
+ * set `failParentOnFailure: true` on that child (it cascades when ancestors
562
+ * also set it). To let a parent proceed despite a failed child, set
563
+ * `ignoreDependencyOnFailure: true` on that child. Bound each child's
564
+ * `attempts` so an unrecoverable child cannot leave the parent waiting forever.
565
+ *
566
+ * @param flow - Flow tree definition.
567
+ * @returns The root `JobNode` containing the created job and its children.
568
+ * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
569
+ */
570
+ add(flow: FlowJob): Promise<JobNode>;
571
+ /**
572
+ * Add multiple flow trees in a single Redis roundtrip.
573
+ *
574
+ * @param flows - Flow tree definitions.
575
+ * @returns The root `JobNode` for each flow, in input order.
576
+ * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
577
+ */
578
+ addBulk(flows: readonly FlowJob[]): Promise<JobNode[]>;
579
+ /**
580
+ * Escape hatch returning the underlying `FlowProducer` for advanced use cases.
581
+ * Prefer {@link FlowService.add} / {@link FlowService.addBulk} when possible.
582
+ *
583
+ * @returns The underlying BullMQ `FlowProducer`.
584
+ * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
585
+ */
586
+ getProducer(): FlowProducer;
587
+ /**
588
+ * Close the producer when active; a no-op when inactive. A failed `close()` is
589
+ * swallowed so it never aborts the shutdown sequence.
590
+ *
591
+ * Deliberately NOT named `onModuleDestroy`: NestJS invokes lifecycle hooks by
592
+ * method name, so a hook here would run in addition to the call the ordered
593
+ * sequence already makes, closing the producer twice. `QueueLifecycle` is the
594
+ * only caller.
595
+ */
596
+ close(): Promise<void>;
597
+ /**
598
+ * Return the active producer or throw when flows are disabled. The producer is
599
+ * present if and only if flows were enabled at construction time.
600
+ *
601
+ * @throws {QueueException} `FLOW_DISABLED` (503) when no producer exists.
602
+ */
603
+ private requireProducer;
604
+ }
605
+
606
+ /**
607
+ * @fileoverview MetricsService — an opt-in, in-memory TTL cache over
608
+ * `QueueService.getMetrics`. Delegates one direction (Metrics → Queue) so there
609
+ * is no circular dependency. Guarded: throws `METRICS_DISABLED` (503) unless
610
+ * `metrics.enabled` was set.
611
+ * @layer server/services
612
+ */
613
+
614
+ /**
615
+ * Opt-in in-memory TTL cache over `QueueService.getMetrics`. A per-second
616
+ * `/health` poll reads from the cache instead of hammering Redis. The cache
617
+ * delegates to `QueueService` in a single direction, so there is no circular
618
+ * dependency. Every method throws `METRICS_DISABLED` (503) when metrics are not
619
+ * enabled.
620
+ *
621
+ * @example Health check pattern (consumer side — `@nestjs/terminus`, not a dependency here)
622
+ * @Injectable()
623
+ * class QueueHealthIndicator extends HealthIndicator {
624
+ * constructor(private readonly metrics: MetricsService) { super() }
625
+ * async isHealthy(key: string): Promise<HealthIndicatorResult> {
626
+ * const all = await this.metrics.getAll()
627
+ * const stuck = all.filter((m) => m.counts.active > 100 || m.counts.failed > 1000)
628
+ * return this.getStatus(key, stuck.length === 0, { stuck: stuck.map((m) => m.queue) })
629
+ * }
630
+ * }
631
+ */
632
+ declare class MetricsService {
633
+ private readonly queueService;
634
+ private readonly enabled;
635
+ private readonly ttlMs;
636
+ private readonly cache;
637
+ constructor(queueService: QueueService, enabled: boolean, ttlMs: number);
638
+ /**
639
+ * Return cached metrics for `queueName`, or fetch fresh (and cache them) on a
640
+ * miss or after expiry.
641
+ *
642
+ * @param queueName - Target queue.
643
+ * @returns The current metrics snapshot.
644
+ * @throws {QueueException} `METRICS_DISABLED` (503) when metrics are disabled.
645
+ */
646
+ get(queueName: string): Promise<QueueMetrics>;
647
+ /**
648
+ * Return a metrics snapshot for every queue currently cached in `QueueService`.
649
+ *
650
+ * @returns Metrics for all known queues.
651
+ * @throws {QueueException} `METRICS_DISABLED` (503) when metrics are disabled.
652
+ */
653
+ getAll(): Promise<readonly QueueMetrics[]>;
654
+ /**
655
+ * Invalidate the cache for `queueName`, or the whole cache when omitted.
656
+ *
657
+ * @param queueName - The queue entry to drop; omit to clear everything.
658
+ * @throws {QueueException} `METRICS_DISABLED` (503) when metrics are disabled.
659
+ */
660
+ invalidate(queueName?: string): void;
661
+ /**
662
+ * Guard every operation behind the opt-in flag.
663
+ *
664
+ * @throws {QueueException} `METRICS_DISABLED` (503) when metrics are disabled.
665
+ */
666
+ private ensureEnabled;
667
+ }
668
+
669
+ /**
670
+ * @fileoverview Worker tuning options surfaced when registering a processor.
671
+ * @layer server/interfaces
672
+ */
673
+ /**
674
+ * Tunables applied when registering a Worker (via decorator or programmatically).
675
+ * Maps to the subset of BullMQ's worker options the library exposes through its
676
+ * defaults.
677
+ *
678
+ * There is intentionally NO `sandboxed` boolean. BullMQ sandboxed processors are
679
+ * created by passing a FILE PATH (not a class) to the Worker constructor, so they
680
+ * run out-of-process and cannot use NestJS dependency injection. A toggle on a
681
+ * DI-managed processor is therefore impossible — sandboxed work uses a separate
682
+ * registration path (`WorkerRegistry.registerSandboxed`).
683
+ *
684
+ * @example
685
+ * const options: WorkerOptions = { concurrency: 5, limiter: { max: 10, duration: 1000 } }
686
+ */
687
+ interface WorkerOptions {
688
+ /** Maximum number of jobs processed concurrently. Default: 2. */
689
+ concurrency?: number;
690
+ /** Rate limiter — at most `max` jobs per `duration` milliseconds. */
691
+ limiter?: {
692
+ max: number;
693
+ duration: number;
694
+ };
695
+ /** Start the worker automatically on registration. Default: true. */
696
+ autorun?: boolean;
697
+ /** Lock duration for active jobs, in milliseconds. Default: 30_000. */
698
+ lockDuration?: number;
699
+ /** Interval between stalled-job checks, in milliseconds. Default: 30_000. */
700
+ stalledInterval?: number;
701
+ }
702
+
703
+ /**
704
+ * @fileoverview `WorkerRegistry` — creates, tracks, and destroys BullMQ
705
+ * `Worker` instances. Provides both an in-process path (handler function, NestJS
706
+ * DI available) and a file-based sandboxed path (no NestJS DI). Every worker
707
+ * connection is a duplicate of the Queue-role client with
708
+ * `maxRetriesPerRequest: null`, as required by BullMQ blocking commands. The
709
+ * duplicated connection is tracked alongside the worker so the shutdown
710
+ * orchestrator can close exactly the connections the library opened.
711
+ * @layer server/services
712
+ */
713
+
714
+ /**
715
+ * Configuration for registering an in-process (NestJS-DI-backed) worker.
716
+ *
717
+ * @template TData - Shape of the job data payload.
718
+ * @template TResult - Shape of the job result.
719
+ */
720
+ interface ProgrammaticWorkerConfig<TData = unknown, TResult = unknown> {
721
+ /** BullMQ queue the worker should consume. */
722
+ queueName: string;
723
+ /** Async function that processes a single job. Must be idempotent. */
724
+ handler: (job: Job<TData, TResult>) => Promise<TResult>;
725
+ /** Optional worker tuning overrides. */
726
+ options?: WorkerOptions;
727
+ }
728
+ /**
729
+ * Configuration for registering a sandboxed (file-based, out-of-process) worker.
730
+ * The processor runs in a separate Node.js process and cannot access NestJS DI.
731
+ * `processorFile` must be an absolute path or a `file:` URL with a `.js`, `.cjs`,
732
+ * or `.mjs` extension.
733
+ *
734
+ * @see https://docs.bullmq.io/guide/workers/sandboxed-processors
735
+ */
736
+ interface SandboxedWorkerConfig {
737
+ /** BullMQ queue the worker should consume. */
738
+ queueName: string;
739
+ /**
740
+ * Absolute path or `file:` URL to the compiled `.js`/`.cjs`/`.mjs` processor
741
+ * artifact. Relative paths and non-JS extensions are rejected at registration
742
+ * time. The file must export a default async function
743
+ * `(job: Job) => Promise<unknown>`.
744
+ */
745
+ processorFile: string | URL;
746
+ /** Optional worker tuning overrides, including `useWorkerThreads`. */
747
+ options?: WorkerOptions & {
748
+ useWorkerThreads?: boolean;
749
+ };
750
+ }
751
+ /**
752
+ * Creates and lifecycle-manages BullMQ `Worker` instances on behalf of the
753
+ * module. Supports two registration paths:
754
+ *
755
+ * - **Programmatic** (`register`) — in-process handler; NestJS DI is available
756
+ * inside the handler function.
757
+ * - **Sandboxed** (`registerSandboxed`) — file-based, out-of-process; the
758
+ * processor file is loaded in a separate Node.js child process and has **no**
759
+ * access to NestJS DI.
760
+ *
761
+ * All workers use a duplicated Redis connection (`maxRetriesPerRequest: null`)
762
+ * for the blocking `BRPOPLPUSH`/`BZPOPMIN`/`BLMOVE` commands. The duplicated
763
+ * connection is closed immediately when the Worker constructor fails, preventing
764
+ * a connection leak.
765
+ *
766
+ * @example
767
+ * const worker = registry.register({
768
+ * queueName: 'email',
769
+ * handler: async (job) => sendEmail(job.data),
770
+ * options: { concurrency: 5 },
771
+ * })
772
+ */
773
+ declare class WorkerRegistry {
774
+ private readonly connection;
775
+ private readonly options;
776
+ private readonly logger;
777
+ private readonly entries;
778
+ constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
779
+ /**
780
+ * Register an in-process worker with a handler function. The BullMQ `Worker`
781
+ * constructor is synchronous — no Redis handshake is performed at registration
782
+ * time. The duplicated connection is disconnected immediately if construction
783
+ * fails, preventing a Redis connection leak.
784
+ *
785
+ * @param config - Queue name, handler, and optional worker options.
786
+ * @returns The created BullMQ `Worker` instance.
787
+ * @throws {QueueException} `DUPLICATE_PROCESSOR` when a worker for
788
+ * `queueName` already exists.
789
+ * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when options are
790
+ * invalid or the BullMQ `Worker` constructor throws.
791
+ */
792
+ register<TData = unknown, TResult = unknown>(config: ProgrammaticWorkerConfig<TData, TResult>): Worker<TData, TResult>;
793
+ /**
794
+ * Register a sandboxed (file-based, out-of-process) worker. The processor
795
+ * file must be an absolute path or a `file:` URL pointing to a compiled
796
+ * `.js`/`.cjs`/`.mjs` artifact. NestJS DI is **not** available inside the
797
+ * processor. The BullMQ `Worker` constructor is synchronous.
798
+ *
799
+ * @param config - Queue name, processor file path/URL, and optional options.
800
+ * @returns The created BullMQ `Worker` instance.
801
+ * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when `processorFile`
802
+ * is a relative path, has a disallowed extension, or uses a non-`file:` URL protocol.
803
+ * @throws {QueueException} `DUPLICATE_PROCESSOR` when a worker for
804
+ * `queueName` already exists.
805
+ * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when options are
806
+ * invalid or the BullMQ `Worker` constructor throws.
807
+ */
808
+ registerSandboxed(config: SandboxedWorkerConfig): Worker;
809
+ /**
810
+ * Stop and remove the worker for the given queue, releasing the duplicated
811
+ * connection the library opened for it. If no worker exists for `queueName`
812
+ * this is a no-op.
813
+ *
814
+ * @param queueName - The queue whose worker should be removed.
815
+ */
816
+ unregister(queueName: string): Promise<void>;
817
+ /**
818
+ * Returns the queue names of all currently registered workers.
819
+ *
820
+ * @returns An immutable array of queue names.
821
+ */
822
+ list(): readonly string[];
823
+ /**
824
+ * Returns a read-only view of the registered workers keyed by queue name.
825
+ * Intended for the shutdown orchestrator only.
826
+ *
827
+ * @returns A read-only map of queue name to worker.
828
+ */
829
+ getAll(): ReadonlyMap<string, Worker>;
830
+ /**
831
+ * Returns a read-only view of the duplicated connections the library opened
832
+ * for its workers, keyed by queue name. Intended for the shutdown orchestrator
833
+ * so it can close exactly the connections the library created (Mode A never
834
+ * exposes the consumer's shared client here).
835
+ *
836
+ * @returns A read-only map of queue name to duplicated connection.
837
+ */
838
+ getConnections(): ReadonlyMap<string, Redis>;
839
+ /**
840
+ * Gracefully quit a duplicated connection, falling back to a hard disconnect
841
+ * if the quit handshake fails so a socket is never leaked.
842
+ *
843
+ * @param connection - The duplicated connection to release.
844
+ */
845
+ private closeConnection;
846
+ /**
847
+ * Guard against registering two workers for the same queue.
848
+ *
849
+ * @param queueName - Queue name to check.
850
+ * @throws {QueueException} `DUPLICATE_PROCESSOR` if the queue is already registered.
851
+ */
852
+ private guardDuplicate;
853
+ /**
854
+ * Validate tunable options before constructing a Worker.
855
+ * Enforces `concurrency` in `[1, MAX_WORKER_CONCURRENCY]` and
856
+ * `limiter.max`/`limiter.duration` `>= 1`.
857
+ *
858
+ * @param opts - The caller-supplied options (may be undefined).
859
+ * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when values are out of range.
860
+ */
861
+ private validateOptions;
862
+ /**
863
+ * Validate that `processorFile` is a safe, reachable artifact path. Accepts
864
+ * absolute paths with `.js`/`.cjs`/`.mjs` extensions and `file:` URLs. Rejects
865
+ * relative paths, non-JS extensions, and non-`file:` URL protocols to prevent
866
+ * path-traversal and arbitrary-file-execution attacks.
867
+ *
868
+ * @param processorFile - The path or URL supplied by the caller.
869
+ * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when validation fails.
870
+ */
871
+ private validateProcessorFile;
872
+ /**
873
+ * Reject a processor path whose extension is not an executable JS module,
874
+ * the last line of defense against loading an arbitrary file as a worker.
875
+ *
876
+ * @param filePath - The filesystem path (or file: URL pathname) to check.
877
+ * @throws {QueueException} `WORKER_REGISTRATION_FAILED` for a disallowed extension.
878
+ */
879
+ private assertProcessorExtension;
880
+ /**
881
+ * Build a BullMQ `WorkerOptions` object from the library's `WorkerOptions`
882
+ * shape. Only forwards optional fields that are explicitly set to preserve
883
+ * `exactOptionalPropertyTypes` correctness. Accepts the pre-created duplicated
884
+ * connection so callers can close it on construction failure.
885
+ *
886
+ * @param opts - Library-level worker options.
887
+ * @param conn - Pre-created duplicated Redis connection.
888
+ * @returns A BullMQ-compatible options object.
889
+ */
890
+ private buildBullOptions;
891
+ }
892
+
893
+ /**
894
+ * @fileoverview `QueueEventsRegistry` — lazily creates and caches one BullMQ
895
+ * `QueueEvents` instance per queue. A `QueueEvents` connection is opened only
896
+ * when at least one `@OnQueueEvent` listener is registered for that queue,
897
+ * avoiding unnecessary Redis connections. Each duplicated connection is tracked
898
+ * so the shutdown orchestrator can close exactly what the library opened.
899
+ * @layer server/services
900
+ */
901
+
902
+ /**
903
+ * Lazily creates and caches one BullMQ `QueueEvents` instance per queue.
904
+ * The backing Redis connection uses `maxRetriesPerRequest: null` (via
905
+ * `duplicateConnection`) because `QueueEvents` subscribes via blocking
906
+ * `SUBSCRIBE` commands.
907
+ *
908
+ * A single `QueueEvents` is shared across all `@OnQueueEvent` listeners for
909
+ * the same queue. Connections are opened on demand — only when
910
+ * `getOrCreate(queueName)` is first called for a given queue. Shutdown is
911
+ * orchestrated centrally by the queue lifecycle service, which closes each
912
+ * `QueueEvents` and then releases the duplicated connection exposed here.
913
+ *
914
+ * @example
915
+ * const qe = registry.getOrCreate('email')
916
+ * qe.on('completed', ({ jobId }) => console.log(jobId))
917
+ */
918
+ declare class QueueEventsRegistry {
919
+ private readonly connection;
920
+ private readonly options;
921
+ private readonly logger;
922
+ private readonly events;
923
+ private readonly connections;
924
+ constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
925
+ /**
926
+ * Returns the cached `QueueEvents` for `queueName`, creating one on first
927
+ * call. Subsequent calls for the same name return the same instance without
928
+ * opening a second Redis connection.
929
+ *
930
+ * @param queueName - The BullMQ queue to observe.
931
+ * @returns The `QueueEvents` instance for the queue.
932
+ */
933
+ getOrCreate(queueName: string): QueueEvents;
934
+ /**
935
+ * Returns the queue names for which a `QueueEvents` connection is open.
936
+ *
937
+ * @returns An immutable array of queue names.
938
+ */
939
+ list(): readonly string[];
940
+ /**
941
+ * Returns the live `QueueEvents` map. Intended for the shutdown orchestrator.
942
+ *
943
+ * @returns A read-only view of the internal map.
944
+ */
945
+ getAll(): ReadonlyMap<string, QueueEvents>;
946
+ /**
947
+ * Returns a read-only view of the duplicated connections the library opened
948
+ * for its `QueueEvents`, keyed by queue name. Intended for the shutdown
949
+ * orchestrator so it can close exactly the connections the library created
950
+ * (Mode A never exposes the consumer's shared client here).
951
+ *
952
+ * Returns a copy. `ReadonlyMap` is a compile-time claim only — handing back the
953
+ * live registry lets any consumer cast it away and delete a connection the
954
+ * shutdown sequence is about to close. The allocation costs nothing on a path
955
+ * that runs once per shutdown.
956
+ *
957
+ * @returns A read-only snapshot of queue name to duplicated connection.
958
+ */
959
+ getConnections(): ReadonlyMap<string, Redis>;
960
+ }
961
+
962
+ /**
963
+ * @fileoverview `QueueLifecycle` — the single, ordered graceful-shutdown
964
+ * orchestrator. On `onModuleDestroy` it drains every worker within a bounded
965
+ * budget, then tears down queue-events, the connections the library opened,
966
+ * the flow producer, the cached queues, and finally the Queue-role connection.
967
+ *
968
+ * Delivery is at-least-once: when a worker exceeds the drain budget it is
969
+ * force-closed and its in-flight job becomes `stalled` and is retried by another
970
+ * worker. Handlers must therefore be idempotent, and `lockDuration` should
971
+ * comfortably exceed the worst-case handler runtime so healthy long jobs are
972
+ * never treated as stalled.
973
+ * @layer server/lifecycle
974
+ */
975
+
976
+ /**
977
+ * Ordered graceful-shutdown service. Implementing the protocol in one place
978
+ * guarantees the teardown order — workers drain first, then queue-events, then
979
+ * the duplicated connections, then the flow producer, the queues, and finally
980
+ * the Queue-role connection. Every step swallows its own errors so a single
981
+ * failure never aborts the rest of the sequence.
982
+ *
983
+ * Connection ownership: in **Mode B** the library-owned Queue-role client is
984
+ * quit by the connection resolver; in **Mode A** that step is a no-op and only
985
+ * the duplicated worker/queue-events connections the library created are closed
986
+ * — the consumer's shared client is never touched.
987
+ */
988
+ declare class QueueLifecycle implements OnModuleDestroy {
989
+ private readonly workers;
990
+ private readonly events;
991
+ private readonly queues;
992
+ private readonly flow;
993
+ private readonly connection;
994
+ private readonly resolved;
995
+ private readonly logger;
996
+ constructor(workers: WorkerRegistry, events: QueueEventsRegistry, queues: QueueService, flow: FlowService, connection: ConnectionResolver, resolved: ResolvedQueueOptions);
997
+ /**
998
+ * Run the ordered shutdown sequence. Called by NestJS when the application
999
+ * context closes (e.g. on `SIGTERM` with shutdown hooks enabled).
1000
+ */
1001
+ onModuleDestroy(): Promise<void>;
1002
+ /**
1003
+ * Close every worker with a bounded drain, force-closing any that exceed the
1004
+ * budget. Returns the number of workers that had to be force-closed.
1005
+ *
1006
+ * @returns The count of force-closed workers.
1007
+ */
1008
+ private closeWorkers;
1009
+ /**
1010
+ * Race a graceful `worker.close()` against a timer. `worker.close()` already
1011
+ * drains active jobs and takes no timeout argument, so the timer is the only
1012
+ * way to bound the wait. Rejects with a timeout error when the budget elapses.
1013
+ *
1014
+ * @param worker - The worker to close.
1015
+ * @param timeoutMs - The drain budget in milliseconds.
1016
+ */
1017
+ private closeWorkerWithTimeout;
1018
+ /** Close every cached `QueueEvents`, swallowing individual failures. */
1019
+ private closeQueueEvents;
1020
+ /**
1021
+ * Close the duplicated worker/queue-events connections the library opened.
1022
+ * BullMQ treats a passed-in client as shared and never closes it, so the
1023
+ * library must release these explicitly. In Mode A the consumer's shared
1024
+ * client is never among them.
1025
+ */
1026
+ private closeDuplicatedConnections;
1027
+ /**
1028
+ * Drain every cached queue when `drainOnShutdown` is enabled (dev/test only).
1029
+ * Removes waiting and delayed jobs; off by default to stay production-safe.
1030
+ */
1031
+ private drainQueuesIfRequested;
1032
+ /** Close the flow producer (idempotent), swallowing any failure. */
1033
+ private closeFlowProducer;
1034
+ /** Close every cached queue and clear the cache, swallowing individual failures. */
1035
+ private closeQueues;
1036
+ /**
1037
+ * Tear down the Queue-role connection. Mode B quits the library-owned client;
1038
+ * Mode A is a no-op for the consumer's shared client.
1039
+ */
1040
+ private teardownConnection;
1041
+ }
1042
+
1043
+ /**
1044
+ * @fileoverview `@Processor` class decorator — marks a provider as the handler
1045
+ * for a named BullMQ queue. Writes {@link ProcessorMetadata} to class-level
1046
+ * reflection metadata under {@link PROCESSOR_METADATA_KEY}.
1047
+ * @layer server/decorators
1048
+ */
1049
+
1050
+ /**
1051
+ * Marks a class as the processor for the given queue. The class must be
1052
+ * registered as a NestJS provider so that `ProcessorDiscoveryService` can
1053
+ * discover and wire it during module initialization.
1054
+ *
1055
+ * When `options.concurrency` is omitted the library falls back to
1056
+ * {@link DEFAULT_WORKER_CONCURRENCY} and emits a `Logger.warn` at discovery
1057
+ * time (not here — the decorator itself is side-effect-free).
1058
+ *
1059
+ * Delivery is at-least-once: every `@Process` handler on the class MUST be
1060
+ * idempotent (idempotency key on writes, upserts over inserts, or an
1061
+ * "already-processed" marker keyed by `job.id`). Set `lockDuration` comfortably
1062
+ * above the worst-case handler runtime so healthy long jobs are never treated as
1063
+ * stalled.
1064
+ *
1065
+ * @param queueName - The BullMQ queue this class processes.
1066
+ * @param options - Optional worker tuning overrides.
1067
+ *
1068
+ * @example
1069
+ * `@Processor`('email', { concurrency: 5 })
1070
+ * class EmailProcessor {
1071
+ * `@Process`()
1072
+ * async handle(job: Job<EmailPayload>) { ... }
1073
+ * }
1074
+ */
1075
+ declare function Processor(queueName: string, options?: WorkerOptions): ClassDecorator;
1076
+
1077
+ /**
1078
+ * @fileoverview `@Process` method decorator — registers a method as the handler
1079
+ * for a specific job name (or as the catch-all when no name is given). Metadata
1080
+ * is accumulated in an array so multiple `@Process` annotations on the same
1081
+ * class coexist without overwriting each other.
1082
+ * @layer server/decorators
1083
+ */
1084
+
1085
+ /**
1086
+ * Marks a method as a job processor. If `jobName` is supplied the method is
1087
+ * only invoked for jobs whose `name` matches; if omitted the method acts as a
1088
+ * catch-all for all other job names on the same queue.
1089
+ *
1090
+ * Multiple `@Process` decorators on the same class are supported — each adds
1091
+ * an entry to the accumulated metadata array.
1092
+ *
1093
+ * Delivery is at-least-once: a handler may run more than once for the same job
1094
+ * (worker crash, lock expiry, or a shutdown force-close). Handlers MUST be
1095
+ * idempotent — use an idempotency key on writes, prefer upserts over inserts, or
1096
+ * keep an "already-processed" marker keyed by `job.id`.
1097
+ *
1098
+ * @param jobName - Optional job-name filter. Omit for a catch-all handler.
1099
+ *
1100
+ * @example
1101
+ * `@Process`('send-email')
1102
+ * async handleSend(job: Job<EmailPayload>) { ... }
1103
+ *
1104
+ * `@Process`()
1105
+ * async handleOthers(job: Job<EmailPayload>) { ... }
1106
+ */
1107
+ declare function Process(jobName?: string): MethodDecorator;
1108
+
1109
+ /**
1110
+ * @fileoverview `@OnWorkerEvent` method decorator — binds a method as a
1111
+ * worker-local event listener. The handler is attached directly to the BullMQ
1112
+ * `Worker` instance (no extra Redis connection) and receives the **full**
1113
+ * `Job` object, including `job.data`, `job.returnvalue`, `job.attemptsMade`,
1114
+ * and timing fields. Use `'progress'` to observe `job.updateProgress()`.
1115
+ * @layer server/decorators
1116
+ */
1117
+
1118
+ /**
1119
+ * Union of BullMQ `Worker` event names surfaced by this library. Handlers
1120
+ * registered with `@OnWorkerEvent` receive the **full** `Job` instance for
1121
+ * job-scoped events (completed, failed, active, progress) and scalars for
1122
+ * infrastructure events (stalled, closing, closed, error).
1123
+ *
1124
+ * @see https://docs.bullmq.io/guide/workers/worker-events
1125
+ */
1126
+ type WorkerEventName = 'completed' | 'failed' | 'progress' | 'active' | 'stalled' | 'closing' | 'closed' | 'error';
1127
+ /**
1128
+ * Marks a method as a worker-local event listener for the given event. The
1129
+ * method is bound to the BullMQ `Worker` via `worker.on(event, fn)` at
1130
+ * discovery time. Because worker-local events are emitted directly on the
1131
+ * `Worker` instance, **no extra Redis connection is opened**.
1132
+ *
1133
+ * Handler receives:
1134
+ * - `completed` — `(job: Job, returnValue: unknown, prev: string) => void`
1135
+ * - `failed` — `(job: Job | undefined, error: Error, prev: string) => void`
1136
+ * - `progress` — `(job: Job, progress: number | object) => void`
1137
+ * - `active` — `(job: Job, prev: string) => void`
1138
+ * - `stalled` — `(jobId: string, prev: string) => void`
1139
+ * - `error` — `(error: Error) => void`
1140
+ * - `closing` — `(msg: string) => void`
1141
+ * - `closed` — `() => void`
1142
+ *
1143
+ * Multiple `@OnWorkerEvent` decorators on the same class accumulate without
1144
+ * overwriting each other.
1145
+ *
1146
+ * Delivery is at-least-once. A `'stalled'` job — for example after a shutdown
1147
+ * force-close or a lock expiry — is re-run by another worker: this is the
1148
+ * visible at-least-once path, so handlers must be idempotent. Set `lockDuration`
1149
+ * comfortably above the worst-case handler runtime to avoid false stalls.
1150
+ *
1151
+ * Dead-letter-queue pattern (`'failed'`): the library ships no DLQ service, but
1152
+ * the endorsed pattern is a `'failed'` listener that, once retries are exhausted
1153
+ * (`job.attemptsMade >= (job.opts.attempts ?? 1)`), re-enqueues the payload plus
1154
+ * failure metadata onto a `<queue>-dlq` queue via `QueueService.enqueue`. Use a
1155
+ * stable DLQ `jobId` (e.g. `dlq-${job.id}`) so a redelivered `'failed'` event is
1156
+ * idempotent and never double-inserts (a custom BullMQ job id must not contain `:`).
1157
+ *
1158
+ * @param eventName - The worker event to listen for.
1159
+ *
1160
+ * @example
1161
+ * `@OnWorkerEvent`('completed')
1162
+ * onCompleted(job: Job, returnValue: unknown) {
1163
+ * this.logger.log(`Job ${job.id} done`, returnValue)
1164
+ * }
1165
+ */
1166
+ declare function OnWorkerEvent(eventName: WorkerEventName): MethodDecorator;
1167
+
1168
+ /**
1169
+ * @fileoverview `@OnQueueEvent` method decorator — binds a method as a global
1170
+ * queue-event listener. The handler is attached to a per-queue `QueueEvents`
1171
+ * instance (one extra Redis connection per queue, opened lazily) and receives
1172
+ * a **serialized** payload — only `jobId` and stringified fields such as
1173
+ * `returnvalue` (a string, not the actual return value) or `failedReason`.
1174
+ * To inspect the full `Job`, call `QueueService.getJob(jobId)`.
1175
+ * @layer server/decorators
1176
+ */
1177
+
1178
+ /**
1179
+ * Union of BullMQ `QueueEvents` event names surfaced by this library. Listeners
1180
+ * registered with `@OnQueueEvent` receive a serialized event payload, NOT the
1181
+ * full `Job` instance.
1182
+ *
1183
+ * @see https://docs.bullmq.io/guide/workers/queueevents
1184
+ */
1185
+ type QueueEventName = 'completed' | 'failed' | 'active' | 'progress' | 'stalled' | 'waiting' | 'delayed' | 'paused' | 'resumed' | 'cleaned' | 'error';
1186
+ /**
1187
+ * Marks a method as a global queue-event listener. The method is bound to a
1188
+ * lazily-created `QueueEvents` instance via `queueEvents.on(event, fn)` at
1189
+ * discovery time. A single `QueueEvents` connection is shared across all
1190
+ * `@OnQueueEvent` listeners for the same queue.
1191
+ *
1192
+ * The handler receives a **serialized** payload, e.g.:
1193
+ * - `completed` — `{ jobId: string, returnvalue: string, prev?: string }`
1194
+ * - `failed` — `{ jobId: string, failedReason: string, prev?: string }`
1195
+ * - `active` — `{ jobId: string, prev?: string }`
1196
+ * - `progress` — `{ jobId: string, data: number | object }`
1197
+ * - `waiting` — `{ jobId: string }`
1198
+ *
1199
+ * `error` is the exception to that rule: it carries a real `Error` instance, not
1200
+ * a serialized payload, because it reports a fault on the connection rather than
1201
+ * a transition of any single job — so there is no `jobId` to look up. The library
1202
+ * registers its own listener for it, which logs and keeps an unlistened emission
1203
+ * from throwing; a handler declared here runs in addition to that one.
1204
+ *
1205
+ * To retrieve the full job, call `QueueService.getJob(payload.jobId)`, which
1206
+ * may return `null` if the job was already evicted by `removeOnComplete`.
1207
+ *
1208
+ * Multiple `@OnQueueEvent` decorators on the same class accumulate without
1209
+ * overwriting each other.
1210
+ *
1211
+ * @param eventName - The queue event to listen for.
1212
+ *
1213
+ * @example
1214
+ * `@OnQueueEvent`('completed')
1215
+ * async onCompleted({ jobId, returnvalue }: { jobId: string; returnvalue: string }) {
1216
+ * this.logger.log(`Job ${jobId} finished with result ${returnvalue}`)
1217
+ * }
1218
+ */
1219
+ declare function OnQueueEvent(eventName: QueueEventName): MethodDecorator;
1220
+
1221
+ /**
1222
+ * @fileoverview Reflection metadata shapes attached by the processor decorators.
1223
+ * @layer server/interfaces
1224
+ */
1225
+
1226
+ /** Metadata attached by the processor decorator and read on worker discovery. */
1227
+ interface ProcessorMetadata {
1228
+ /** Queue the annotated class processes. */
1229
+ queueName: string;
1230
+ /** Worker tuning options for the queue. */
1231
+ workerOptions: WorkerOptions;
1232
+ /**
1233
+ * Internal flag set when the caller omitted `concurrency` on the decorator.
1234
+ * The discovery service logs a warning (naming the queue and the fallback value)
1235
+ * at bootstrap time; the decorator itself must never log.
1236
+ *
1237
+ * @internal
1238
+ */
1239
+ _warnedNoConcurrency?: boolean;
1240
+ }
1241
+ /** Metadata attached by the process-handler decorator on a handler method. */
1242
+ interface ProcessHandlerMetadata {
1243
+ /** Specific job-name filter; `undefined` means catch-all. */
1244
+ jobName?: string;
1245
+ /** Method key on the host class used by the registry to resolve dispatch. */
1246
+ methodKey: string | symbol;
1247
+ }
1248
+ /** Metadata attached by the queue-event listener decorator on a listener method. */
1249
+ interface QueueEventListenerMetadata {
1250
+ /** Queue event the method listens for. */
1251
+ eventName: string;
1252
+ /** Method key on the host class. */
1253
+ methodKey: string | symbol;
1254
+ }
1255
+
1256
+ /**
1257
+ * @fileoverview Standardized, HTTP-shaped exception for queue operations.
1258
+ * @layer server/errors
1259
+ */
1260
+
1261
+ /**
1262
+ * Standardized exception for queue operations. The response body always follows
1263
+ * the shape `{ error: { code, message, details } }`. The `code` is a stable,
1264
+ * transport-independent identifier; consumers catching this exception outside an
1265
+ * HTTP request should branch on `code`, not the HTTP status or the message text.
1266
+ * The code lives in the response envelope — read it via `getResponse()`.
1267
+ *
1268
+ * `details` must never carry secrets (connection strings, passwords, `job.data`)
1269
+ * — only scalar configuration values such as `{ actualValue, expectedValue }`.
1270
+ *
1271
+ * @example Throwing
1272
+ * throw new QueueException('queue.connection_timeout', 500, { timeoutMs: 10_000 })
1273
+ * @example Branching on the code in a consumer
1274
+ * catch (err) {
1275
+ * if (err instanceof QueueException) {
1276
+ * const { code } = (err.getResponse() as { error: { code: string } }).error
1277
+ * if (code === QUEUE_ERROR_CODES.FLOW_DISABLED) enableFlows()
1278
+ * }
1279
+ * }
1280
+ */
1281
+ declare class QueueException extends HttpException {
1282
+ constructor(code: string, statusCode?: HttpStatus, details?: Record<string, unknown>);
1283
+ }
1284
+
1285
+ /**
1286
+ * @fileoverview Stable error-code catalog emitted by the library, plus the
1287
+ * derived code union. Single source of truth, re-exported by the server barrel.
1288
+ * @layer shared/constants
1289
+ */
1290
+ /**
1291
+ * Stable, transport-independent error codes emitted through `QueueException`.
1292
+ * Declared `as const` so each value keeps its literal type in the emitted
1293
+ * declaration files.
1294
+ *
1295
+ * @example
1296
+ * if (error.code === QUEUE_ERROR_CODES.JOB_NOT_FOUND) { return null }
1297
+ */
1298
+ declare const QUEUE_ERROR_CODES: {
1299
+ readonly CONNECTION_INVALID: "queue.connection_invalid";
1300
+ readonly CONNECTION_REQUIRES_NULL_RETRIES: "queue.connection_requires_null_retries";
1301
+ readonly CONNECTION_TIMEOUT: "queue.connection_timeout";
1302
+ readonly QUEUE_NOT_FOUND: "queue.queue_not_found";
1303
+ readonly JOB_NOT_FOUND: "queue.job_not_found";
1304
+ readonly INVALID_JOB_DATA: "queue.invalid_job_data";
1305
+ readonly INVALID_REPEAT_OPTIONS: "queue.invalid_repeat_options";
1306
+ readonly DUPLICATE_PROCESSOR: "queue.duplicate_processor";
1307
+ readonly FLOW_DISABLED: "queue.flow_disabled";
1308
+ readonly METRICS_DISABLED: "queue.metrics_disabled";
1309
+ readonly SHUTDOWN_TIMEOUT_EXCEEDED: "queue.shutdown_timeout_exceeded";
1310
+ readonly BULK_ENQUEUE_FAILED: "queue.bulk_enqueue_failed";
1311
+ readonly WORKER_REGISTRATION_FAILED: "queue.worker_registration_failed";
1312
+ readonly INVALID_OPTIONS: "queue.invalid_options";
1313
+ };
1314
+ /** Union of every value in {@link QUEUE_ERROR_CODES}. */
1315
+ type QueueErrorCode = (typeof QUEUE_ERROR_CODES)[keyof typeof QUEUE_ERROR_CODES];
1316
+
1317
+ /**
1318
+ * @fileoverview Server-side error-code re-exports plus the human-readable
1319
+ * message catalog keyed by code.
1320
+ * @layer server/constants
1321
+ */
1322
+
1323
+ /**
1324
+ * Human-readable messages keyed by error code — mirrors the specification's
1325
+ * error catalog. Every value of {@link QUEUE_ERROR_CODES} has an entry.
1326
+ */
1327
+ declare const QUEUE_ERROR_MESSAGES: Record<string, string>;
1328
+
1329
+ /**
1330
+ * @fileoverview Consolidated default values applied to module options.
1331
+ * @layer server/constants
1332
+ */
1333
+ /** Default worker concurrency — a non-serial starting point, tuned per workload. */
1334
+ declare const DEFAULT_WORKER_CONCURRENCY: 2;
1335
+ /**
1336
+ * Maximum allowed concurrency per Worker. Values above this ceiling exhaust the
1337
+ * event loop, heap, and lock-renewal timers before providing meaningful throughput
1338
+ * gains. Single-digit hundreds are typical for I/O-bound workloads (BullMQ docs).
1339
+ */
1340
+ declare const MAX_WORKER_CONCURRENCY: 1000;
1341
+ /**
1342
+ * Default options applied to every job enqueued through the service. The
1343
+ * `removeOnFail.count` cap bounds Redis memory growth under failure storms.
1344
+ */
1345
+ declare const DEFAULT_JOB_OPTIONS: {
1346
+ readonly attempts: 3;
1347
+ readonly backoff: {
1348
+ readonly type: "exponential";
1349
+ readonly delay: 2000;
1350
+ };
1351
+ readonly removeOnComplete: {
1352
+ readonly age: number;
1353
+ readonly count: 1000;
1354
+ };
1355
+ readonly removeOnFail: {
1356
+ readonly age: number;
1357
+ readonly count: 5000;
1358
+ };
1359
+ };
1360
+ /** Default TTL (ms) for the metrics cache. */
1361
+ declare const DEFAULT_METRICS_CACHE_TTL_MS: 5000;
1362
+ /** Default window (ms) for in-flight jobs to drain before force-close on shutdown. */
1363
+ declare const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS: 30000;
1364
+
1365
+ /**
1366
+ * @fileoverview Canonical job-status constant object.
1367
+ * @layer shared/constants
1368
+ */
1369
+ /**
1370
+ * Canonical job status constants — use these in business logic instead of
1371
+ * hardcoded string literals. Declared `as const` so the literal types survive
1372
+ * into the emitted declaration files.
1373
+ *
1374
+ * @example
1375
+ * if (status === JOB_STATUS.FAILED) { retry() }
1376
+ */
1377
+ declare const JOB_STATUS: {
1378
+ readonly WAITING: "waiting";
1379
+ readonly ACTIVE: "active";
1380
+ readonly COMPLETED: "completed";
1381
+ readonly FAILED: "failed";
1382
+ readonly DELAYED: "delayed";
1383
+ readonly PAUSED: "paused";
1384
+ };
1385
+
1386
+ export { BYMAX_QUEUE_CONNECTION_MODE, BYMAX_QUEUE_OPTIONS, BYMAX_QUEUE_REDIS_CLIENT, BYMAX_QUEUE_RESOLVED_OPTIONS, type BulkJob, BymaxQueueModule, type BymaxQueueModuleAsyncOptions, type BymaxQueueModuleOptions, type BymaxQueueOptionsFactory, ConnectionResolver, DEFAULT_JOB_OPTIONS, DEFAULT_METRICS_CACHE_TTL_MS, DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS, DEFAULT_WORKER_CONCURRENCY, FlowService, JOB_STATUS, type JobSchedulerRepeatOptions, type JobStatus, MAX_WORKER_CONCURRENCY, MetricsService, OnQueueEvent, OnWorkerEvent, Process, type ProcessHandlerMetadata, Processor, type ProcessorMetadata, type ProgrammaticWorkerConfig, QUEUE_ERROR_CODES, QUEUE_ERROR_MESSAGES, type QueueConnectionConfig, type QueueConnectionMode, type QueueErrorCode, type QueueEventListenerMetadata, type QueueEventName, QueueEventsRegistry, QueueException, QueueLifecycle, type QueueMetrics, QueueService, type SandboxedWorkerConfig, type WorkerEventName, type WorkerOptions, WorkerRegistry };