@bymax-one/nest-queue 1.2.13 → 1.2.15

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.
@@ -58,7 +58,10 @@ interface BymaxQueueModuleOptions {
58
58
  connection: QueueConnectionConfig;
59
59
  /** Default options applied to every job enqueued through the service. */
60
60
  defaultJobOptions?: JobsOptions;
61
- /** Prefix for all Redis keys (multi-tenant isolation). Default: 'bull'. */
61
+ /**
62
+ * Prefix for all Redis keys, a namespace and not an access control: anything that can reach the
63
+ * Redis instance can read every prefix on it. Default: 'bull'.
64
+ */
62
65
  prefix?: string;
63
66
  /** Default options applied to every Queue, merged with the job/prefix defaults. */
64
67
  queueOptions?: Partial<Omit<QueueOptions, 'connection' | 'defaultJobOptions' | 'prefix'>>;
@@ -87,8 +90,28 @@ interface BymaxQueueModuleOptions {
87
90
  * Default: true.
88
91
  */
89
92
  isGlobal?: boolean;
90
- /** Mode B only: milliseconds to wait for Redis `ready` before throwing. Default: 10_000. */
93
+ /**
94
+ * Mode B only: the startup budget, in milliseconds, for Redis to reach `ready`. Connection errors
95
+ * while the client is still reconnecting do not end the wait; the budget does, with
96
+ * `CONNECTION_TIMEOUT`, and the client the library opened is closed. Default: 10_000.
97
+ */
91
98
  connectionReadyTimeoutMs?: number;
99
+ /**
100
+ * Mode B only: the deadline, in milliseconds, for one Redis command on the connection the library
101
+ * opened (ioredis `commandTimeout`). A command that gets no reply in time rejects instead of
102
+ * waiting out ioredis' own retry window. An `options.commandTimeout` in the connection config
103
+ * wins. Worker and QueueEvents connections never inherit it, because they block by design.
104
+ * Default: 5_000.
105
+ */
106
+ commandTimeoutMs?: number;
107
+ /**
108
+ * The deadline, in milliseconds, for one `QueueService` call that reaches Redis (`enqueue`,
109
+ * `getJob`, `getMetrics`, the scheduler, pause and clean calls, and the rest) in either connection
110
+ * mode, including the wait for the client to be ready on a queue's first call. When it passes, the
111
+ * call rejects with `CONNECTION_TIMEOUT`; an `enqueue` may still complete in Redis afterwards, so
112
+ * retry with a stable `jobId` or `deduplication`. Default: 10_000.
113
+ */
114
+ enqueueTimeoutMs?: number;
92
115
  }
93
116
  /** A factory that produces module options, for async configuration. */
94
117
  interface BymaxQueueOptionsFactory {
@@ -236,6 +259,10 @@ interface ResolvedQueueOptions {
236
259
  telemetry?: Telemetry;
237
260
  /** Mode B ready timeout in milliseconds. */
238
261
  connectionReadyTimeoutMs: number;
262
+ /** Mode B per-command deadline in milliseconds. */
263
+ commandTimeoutMs: number;
264
+ /** Deadline in milliseconds for one enqueue call. */
265
+ enqueueTimeoutMs: number;
239
266
  }
240
267
 
241
268
  /**
@@ -362,7 +389,9 @@ declare class ConnectionResolver {
362
389
  /**
363
390
  * Merge the consumer's options with the owned-connection policy
364
391
  * (`lazyConnect: false`, so the client connects eagerly and `init` can wait
365
- * for `ready`) and narrow the result to the shape ioredis 6's constructor
392
+ * for `ready`; a command deadline, so a frozen Redis fails a call in seconds; and no offline
393
+ * queue, so a call made during an outage fails at once instead of buffering without bound —
394
+ * each only where the consumer has not chosen a value) and narrow the result to the shape ioredis 6's constructor
366
395
  * accepts. The runtime object is unchanged — the cast only drops the
367
396
  * `undefined` that `RedisOptions.replyMapping` carries and the constructor
368
397
  * rejects.
@@ -374,11 +403,26 @@ declare class ConnectionResolver {
374
403
  getMode(): QueueConnectionMode;
375
404
  /** Whether the library owns (and must close) the connection. */
376
405
  isOwned(): boolean;
406
+ /**
407
+ * Settle when the client can take a command. While it is still connecting or reconnecting, every
408
+ * caller shares one wait on its `ready` event, so a health check that polls through a long outage
409
+ * adds one listener set to the client, not one per call, and gives it back when Redis returns. A
410
+ * lazy client (`wait`) connects on its first command and a finished one (`end`) never becomes
411
+ * ready, so both are left for the call itself.
412
+ */
413
+ awaitReady(): Promise<void>;
377
414
  /** Close the library-owned connection on shutdown; never touch a BYO client. */
378
415
  teardown(): Promise<void>;
379
416
  /** Validate and adopt a bring-your-own client for the Queue role. */
380
417
  private initModeA;
381
- /** Resolve when the owned client reaches `ready`, or reject on timeout/error. */
418
+ /**
419
+ * Resolve when the owned client reaches `ready`. A connection error while the client is still
420
+ * reconnecting does not end the wait — a Redis that restarts during a deploy refuses connections
421
+ * for a moment first — so the budget does. The two exceptions are an answer from the server that
422
+ * waiting cannot change (a refused credential) and a client that has stopped reconnecting; both
423
+ * reject at once. The rejection carries the last error's code and never its message, which can
424
+ * name the host.
425
+ */
382
426
  private waitReady;
383
427
  }
384
428
 
@@ -412,13 +456,27 @@ declare class QueueService {
412
456
  * @param overrides - Optional per-queue option overrides.
413
457
  */
414
458
  getOrCreateQueue<TData = unknown, TResult = unknown>(queueName: string, overrides?: Partial<Omit<QueueOptions, 'connection' | 'prefix'>>): Queue<TData, TResult>;
459
+ /**
460
+ * Run one Redis call under the per-call deadline (`enqueueTimeoutMs`), reporting every way Redis
461
+ * can fail to answer as the documented `CONNECTION_TIMEOUT`: the deadline passing, an ioredis
462
+ * command timeout, or a call refused because the connection is down.
463
+ *
464
+ * The first call on a queue name waits for the client to be ready through ONE wait shared by all
465
+ * callers, instead of letting BullMQ start its own unbounded wait per call, so a caller that
466
+ * polls through an outage does not pile up listeners. The call runs only if it is still within
467
+ * its deadline when the client is ready; a caller that was told it timed out never has its call
468
+ * run later, which would turn its retry into a duplicate.
469
+ */
470
+ private call;
471
+ /** Settle when a call on `queueName` may start: at once for a queue that has been used before. */
472
+ private afterReady;
415
473
  /** Resolve the raw cached queue (default generics) for internal delegation. */
416
474
  private queueFor;
417
475
  /**
418
476
  * Add a single job to a queue. The queue is created lazily if needed.
419
477
  *
420
478
  * Delivery is **at-least-once** — a handler may run more than once for the same
421
- * job (worker crash, lock expiry, or a shutdown force-close). Make handlers
479
+ * job (worker crash, lock expiry, or a shutdown that outlasts the drain budget). Make handlers
422
480
  * idempotent: use an idempotency key on writes, prefer upserts over inserts, or
423
481
  * keep an "already-processed" marker keyed by `job.id`.
424
482
  *
@@ -492,7 +550,9 @@ declare class QueueService {
492
550
  * @param queueName - Target queue.
493
551
  * @param status - Status filter.
494
552
  * @param start - Starting index (inclusive). Default: 0.
495
- * @param end - Ending index (inclusive). Default: 50.
553
+ * @param end - Ending index (inclusive). Default: 50. The window may span at most
554
+ * `MAX_JOBS_PAGE_SIZE` (1000) jobs, and BullMQ's `-1` (everything) is refused.
555
+ * @throws {QueueException} `INVALID_OPTIONS` when the window is not a bounded range of integers.
496
556
  */
497
557
  getJobs<TData = unknown, TResult = unknown>(queueName: string, status: JobStatus, start?: number, end?: number): Promise<TypedJob<TData, TResult>[]>;
498
558
  /**
@@ -597,6 +657,7 @@ declare class QueueService {
597
657
  * })
598
658
  */
599
659
  declare class FlowService {
660
+ #private;
600
661
  private readonly logger;
601
662
  private readonly producer?;
602
663
  constructor(connection: ConnectionResolver, enabled: boolean, prefix: string, telemetry?: Telemetry);
@@ -629,6 +690,8 @@ declare class FlowService {
629
690
  * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
630
691
  */
631
692
  addBulk(flows: readonly FlowJob[]): Promise<JobNode[]>;
693
+ /** The command deadline of the producer connection, read when a command has timed out. */
694
+ private commandTimeout;
632
695
  /**
633
696
  * Escape hatch returning the underlying `FlowProducer` for advanced use cases.
634
697
  * Prefer {@link FlowService.add} / {@link FlowService.addBulk} when possible.
@@ -751,6 +814,14 @@ interface WorkerOptions {
751
814
  lockDuration?: number;
752
815
  /** Interval between stalled-job checks, in milliseconds. Default: 30_000. */
753
816
  stalledInterval?: number;
817
+ /**
818
+ * Fail a job whose handler has not settled after this many milliseconds, so it retries under the
819
+ * job's `attempts` and `backoff` and its concurrency slot is freed. The handler's `signal` aborts
820
+ * at that moment; a handler that ignores it keeps running in the background. There is no default:
821
+ * without it a handler that never settles holds its slot indefinitely. Not available for
822
+ * sandboxed workers. Range: 1 to 2_147_483_647.
823
+ */
824
+ jobTimeoutMs?: number;
754
825
  }
755
826
 
756
827
  /**
@@ -773,8 +844,13 @@ interface WorkerOptions {
773
844
  interface ProgrammaticWorkerConfig<TData = unknown, TResult = unknown> {
774
845
  /** BullMQ queue the worker should consume. */
775
846
  queueName: string;
776
- /** Async function that processes a single job. Must be idempotent. */
777
- handler: (job: Job<TData, TResult>) => Promise<TResult>;
847
+ /**
848
+ * Async function that processes a single job. Must be idempotent. Its optional second argument
849
+ * is the job's `AbortSignal`: it aborts when the job's `jobTimeoutMs` passes or when BullMQ
850
+ * cancels the job (`worker.cancelJob()`, `worker.cancelAllJobs()`). Closing the worker does not
851
+ * abort it. Pass it to whatever the handler awaits so the work stops with the job.
852
+ */
853
+ handler: (job: Job<TData, TResult>, signal?: AbortSignal) => Promise<TResult>;
778
854
  /** Optional worker tuning overrides. */
779
855
  options?: WorkerOptions;
780
856
  }
@@ -912,6 +988,14 @@ declare class WorkerRegistry {
912
988
  * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when values are out of range.
913
989
  */
914
990
  private validateOptions;
991
+ /**
992
+ * Validate `jobTimeoutMs`, which must be a timer delay Node honours: above 2^31 - 1 ms it would
993
+ * fire after 1 ms and fail every job at once.
994
+ *
995
+ * @param opts - The caller-supplied options (may be undefined).
996
+ * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when the value is out of range.
997
+ */
998
+ private validateJobTimeout;
915
999
  /**
916
1000
  * Validate that `processorFile` is a safe, reachable artifact path. Accepts
917
1001
  * absolute paths with `.js`/`.cjs`/`.mjs` extensions and `file:` URLs. Rejects
@@ -1020,9 +1104,9 @@ declare class QueueEventsRegistry {
1020
1104
  * HTTP server has stopped, it releases what a request can still be using: the
1021
1105
  * flow producer, the cached queues, and finally the Queue-role connection.
1022
1106
  *
1023
- * Delivery is at-least-once: when a worker exceeds the drain budget it is
1024
- * force-closed and its in-flight job becomes `stalled` and is retried by another
1025
- * worker. Handlers must therefore be idempotent, and `lockDuration` should
1107
+ * Delivery is at-least-once: when a worker exceeds the drain budget it is stopped
1108
+ * without waiting for its job, whose lock then expires so the job becomes `stalled`
1109
+ * and is retried by another worker. Handlers must therefore be idempotent, and `lockDuration` should
1026
1110
  * comfortably exceed the worst-case handler runtime so healthy long jobs are
1027
1111
  * never treated as stalled.
1028
1112
  * @layer server/lifecycle
@@ -1071,28 +1155,26 @@ declare class QueueLifecycle implements OnModuleDestroy, OnApplicationShutdown {
1071
1155
  */
1072
1156
  onApplicationShutdown(): Promise<void>;
1073
1157
  /**
1074
- * Close every worker with a bounded drain, force-closing any that exceed the
1075
- * budget. Returns the number of workers that had to be force-closed.
1158
+ * Stop every worker in parallel, each bounded by what is left of the shutdown budget. A worker
1159
+ * is paused first (it stops fetching and waits for its active jobs) and then closed. One that is
1160
+ * still busy when the budget ends is closed with `close(true)`: because `pause` does not start a
1161
+ * close, that is the worker's FIRST close, so it does not wait for the job and stops the lock
1162
+ * renewal that would otherwise keep running and keep the process alive. The job's lock then
1163
+ * expires, and it stalls and is retried elsewhere. Returns how many workers were stopped
1164
+ * without draining.
1076
1165
  *
1077
- * @returns The count of force-closed workers.
1166
+ * @param deadline - The epoch millisecond at which the shutdown budget ends.
1167
+ * @returns The count of workers stopped without draining.
1078
1168
  */
1079
1169
  private closeWorkers;
1080
- /**
1081
- * Race a graceful `worker.close()` against a timer. `worker.close()` already
1082
- * drains active jobs and takes no timeout argument, so the timer is the only
1083
- * way to bound the wait. Rejects with a timeout error when the budget elapses.
1084
- *
1085
- * @param worker - The worker to close.
1086
- * @param timeoutMs - The drain budget in milliseconds.
1087
- */
1088
- private closeWorkerWithTimeout;
1089
- /** Close every cached `QueueEvents`, swallowing individual failures. */
1170
+ /** Close every cached `QueueEvents` in parallel within the budget, swallowing failures. */
1090
1171
  private closeQueueEvents;
1091
1172
  /**
1092
- * Close the duplicated worker/queue-events connections the library opened.
1093
- * BullMQ treats a passed-in client as shared and never closes it, so the
1094
- * library must release these explicitly. In Mode A the consumer's shared
1095
- * client is never among them.
1173
+ * Close the duplicated worker/queue-events connections the library opened, in parallel. BullMQ
1174
+ * treats a passed-in client as shared and never closes it, so the library must release these
1175
+ * explicitly. A connection that does not quit within the budget is disconnected, which also
1176
+ * releases anything still blocked on it. In Mode A the consumer's shared client is never among
1177
+ * them.
1096
1178
  */
1097
1179
  private closeDuplicatedConnections;
1098
1180
  /**
@@ -1161,7 +1243,7 @@ declare function Processor(queueName: string, options?: WorkerOptions): ClassDec
1161
1243
  * an entry to the accumulated metadata array.
1162
1244
  *
1163
1245
  * Delivery is at-least-once: a handler may run more than once for the same job
1164
- * (worker crash, lock expiry, or a shutdown force-close). Handlers MUST be
1246
+ * (worker crash, lock expiry, or a shutdown that outlasts the drain budget). Handlers MUST be
1165
1247
  * idempotent — use an idempotency key on writes, prefer upserts over inserts, or
1166
1248
  * keep an "already-processed" marker keyed by `job.id`.
1167
1249
  *
@@ -1213,7 +1295,7 @@ type WorkerEventName = 'completed' | 'failed' | 'progress' | 'active' | 'stalled
1213
1295
  * overwriting each other.
1214
1296
  *
1215
1297
  * Delivery is at-least-once. A `'stalled'` job — for example after a shutdown
1216
- * force-close or a lock expiry — is re-run by another worker: this is the
1298
+ * that outlasts the drain budget, or a lock expiry — is re-run by another worker: this is the
1217
1299
  * visible at-least-once path, so handlers must be idempotent. Set `lockDuration`
1218
1300
  * comfortably above the worst-case handler runtime to avoid false stalls.
1219
1301
  *
@@ -1408,13 +1490,16 @@ declare const DEFAULT_WORKER_CONCURRENCY: 2;
1408
1490
  declare const MAX_WORKER_CONCURRENCY: 1000;
1409
1491
  /**
1410
1492
  * Default options applied to every job enqueued through the service. The
1411
- * `removeOnFail.count` cap bounds Redis memory growth under failure storms.
1493
+ * `removeOnFail.count` cap bounds Redis memory growth under failure storms. The backoff jitter
1494
+ * spreads each retry over the upper half of its delay, so jobs that failed together (a Redis
1495
+ * outage, a downstream timeout) do not all retry in the same instant.
1412
1496
  */
1413
1497
  declare const DEFAULT_JOB_OPTIONS: {
1414
1498
  readonly attempts: 3;
1415
1499
  readonly backoff: {
1416
1500
  readonly type: "exponential";
1417
1501
  readonly delay: 2000;
1502
+ readonly jitter: 0.5;
1418
1503
  };
1419
1504
  readonly removeOnComplete: {
1420
1505
  readonly age: number;
@@ -1427,7 +1512,7 @@ declare const DEFAULT_JOB_OPTIONS: {
1427
1512
  };
1428
1513
  /** Default TTL (ms) for the metrics cache. */
1429
1514
  declare const DEFAULT_METRICS_CACHE_TTL_MS: 5000;
1430
- /** Default window (ms) for in-flight jobs to drain before force-close on shutdown. */
1515
+ /** Default window (ms) for in-flight jobs to drain before the worker is stopped on shutdown. */
1431
1516
  declare const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS: 30000;
1432
1517
 
1433
1518
  /**
@@ -58,7 +58,10 @@ interface BymaxQueueModuleOptions {
58
58
  connection: QueueConnectionConfig;
59
59
  /** Default options applied to every job enqueued through the service. */
60
60
  defaultJobOptions?: JobsOptions;
61
- /** Prefix for all Redis keys (multi-tenant isolation). Default: 'bull'. */
61
+ /**
62
+ * Prefix for all Redis keys, a namespace and not an access control: anything that can reach the
63
+ * Redis instance can read every prefix on it. Default: 'bull'.
64
+ */
62
65
  prefix?: string;
63
66
  /** Default options applied to every Queue, merged with the job/prefix defaults. */
64
67
  queueOptions?: Partial<Omit<QueueOptions, 'connection' | 'defaultJobOptions' | 'prefix'>>;
@@ -87,8 +90,28 @@ interface BymaxQueueModuleOptions {
87
90
  * Default: true.
88
91
  */
89
92
  isGlobal?: boolean;
90
- /** Mode B only: milliseconds to wait for Redis `ready` before throwing. Default: 10_000. */
93
+ /**
94
+ * Mode B only: the startup budget, in milliseconds, for Redis to reach `ready`. Connection errors
95
+ * while the client is still reconnecting do not end the wait; the budget does, with
96
+ * `CONNECTION_TIMEOUT`, and the client the library opened is closed. Default: 10_000.
97
+ */
91
98
  connectionReadyTimeoutMs?: number;
99
+ /**
100
+ * Mode B only: the deadline, in milliseconds, for one Redis command on the connection the library
101
+ * opened (ioredis `commandTimeout`). A command that gets no reply in time rejects instead of
102
+ * waiting out ioredis' own retry window. An `options.commandTimeout` in the connection config
103
+ * wins. Worker and QueueEvents connections never inherit it, because they block by design.
104
+ * Default: 5_000.
105
+ */
106
+ commandTimeoutMs?: number;
107
+ /**
108
+ * The deadline, in milliseconds, for one `QueueService` call that reaches Redis (`enqueue`,
109
+ * `getJob`, `getMetrics`, the scheduler, pause and clean calls, and the rest) in either connection
110
+ * mode, including the wait for the client to be ready on a queue's first call. When it passes, the
111
+ * call rejects with `CONNECTION_TIMEOUT`; an `enqueue` may still complete in Redis afterwards, so
112
+ * retry with a stable `jobId` or `deduplication`. Default: 10_000.
113
+ */
114
+ enqueueTimeoutMs?: number;
92
115
  }
93
116
  /** A factory that produces module options, for async configuration. */
94
117
  interface BymaxQueueOptionsFactory {
@@ -236,6 +259,10 @@ interface ResolvedQueueOptions {
236
259
  telemetry?: Telemetry;
237
260
  /** Mode B ready timeout in milliseconds. */
238
261
  connectionReadyTimeoutMs: number;
262
+ /** Mode B per-command deadline in milliseconds. */
263
+ commandTimeoutMs: number;
264
+ /** Deadline in milliseconds for one enqueue call. */
265
+ enqueueTimeoutMs: number;
239
266
  }
240
267
 
241
268
  /**
@@ -362,7 +389,9 @@ declare class ConnectionResolver {
362
389
  /**
363
390
  * Merge the consumer's options with the owned-connection policy
364
391
  * (`lazyConnect: false`, so the client connects eagerly and `init` can wait
365
- * for `ready`) and narrow the result to the shape ioredis 6's constructor
392
+ * for `ready`; a command deadline, so a frozen Redis fails a call in seconds; and no offline
393
+ * queue, so a call made during an outage fails at once instead of buffering without bound —
394
+ * each only where the consumer has not chosen a value) and narrow the result to the shape ioredis 6's constructor
366
395
  * accepts. The runtime object is unchanged — the cast only drops the
367
396
  * `undefined` that `RedisOptions.replyMapping` carries and the constructor
368
397
  * rejects.
@@ -374,11 +403,26 @@ declare class ConnectionResolver {
374
403
  getMode(): QueueConnectionMode;
375
404
  /** Whether the library owns (and must close) the connection. */
376
405
  isOwned(): boolean;
406
+ /**
407
+ * Settle when the client can take a command. While it is still connecting or reconnecting, every
408
+ * caller shares one wait on its `ready` event, so a health check that polls through a long outage
409
+ * adds one listener set to the client, not one per call, and gives it back when Redis returns. A
410
+ * lazy client (`wait`) connects on its first command and a finished one (`end`) never becomes
411
+ * ready, so both are left for the call itself.
412
+ */
413
+ awaitReady(): Promise<void>;
377
414
  /** Close the library-owned connection on shutdown; never touch a BYO client. */
378
415
  teardown(): Promise<void>;
379
416
  /** Validate and adopt a bring-your-own client for the Queue role. */
380
417
  private initModeA;
381
- /** Resolve when the owned client reaches `ready`, or reject on timeout/error. */
418
+ /**
419
+ * Resolve when the owned client reaches `ready`. A connection error while the client is still
420
+ * reconnecting does not end the wait — a Redis that restarts during a deploy refuses connections
421
+ * for a moment first — so the budget does. The two exceptions are an answer from the server that
422
+ * waiting cannot change (a refused credential) and a client that has stopped reconnecting; both
423
+ * reject at once. The rejection carries the last error's code and never its message, which can
424
+ * name the host.
425
+ */
382
426
  private waitReady;
383
427
  }
384
428
 
@@ -412,13 +456,27 @@ declare class QueueService {
412
456
  * @param overrides - Optional per-queue option overrides.
413
457
  */
414
458
  getOrCreateQueue<TData = unknown, TResult = unknown>(queueName: string, overrides?: Partial<Omit<QueueOptions, 'connection' | 'prefix'>>): Queue<TData, TResult>;
459
+ /**
460
+ * Run one Redis call under the per-call deadline (`enqueueTimeoutMs`), reporting every way Redis
461
+ * can fail to answer as the documented `CONNECTION_TIMEOUT`: the deadline passing, an ioredis
462
+ * command timeout, or a call refused because the connection is down.
463
+ *
464
+ * The first call on a queue name waits for the client to be ready through ONE wait shared by all
465
+ * callers, instead of letting BullMQ start its own unbounded wait per call, so a caller that
466
+ * polls through an outage does not pile up listeners. The call runs only if it is still within
467
+ * its deadline when the client is ready; a caller that was told it timed out never has its call
468
+ * run later, which would turn its retry into a duplicate.
469
+ */
470
+ private call;
471
+ /** Settle when a call on `queueName` may start: at once for a queue that has been used before. */
472
+ private afterReady;
415
473
  /** Resolve the raw cached queue (default generics) for internal delegation. */
416
474
  private queueFor;
417
475
  /**
418
476
  * Add a single job to a queue. The queue is created lazily if needed.
419
477
  *
420
478
  * Delivery is **at-least-once** — a handler may run more than once for the same
421
- * job (worker crash, lock expiry, or a shutdown force-close). Make handlers
479
+ * job (worker crash, lock expiry, or a shutdown that outlasts the drain budget). Make handlers
422
480
  * idempotent: use an idempotency key on writes, prefer upserts over inserts, or
423
481
  * keep an "already-processed" marker keyed by `job.id`.
424
482
  *
@@ -492,7 +550,9 @@ declare class QueueService {
492
550
  * @param queueName - Target queue.
493
551
  * @param status - Status filter.
494
552
  * @param start - Starting index (inclusive). Default: 0.
495
- * @param end - Ending index (inclusive). Default: 50.
553
+ * @param end - Ending index (inclusive). Default: 50. The window may span at most
554
+ * `MAX_JOBS_PAGE_SIZE` (1000) jobs, and BullMQ's `-1` (everything) is refused.
555
+ * @throws {QueueException} `INVALID_OPTIONS` when the window is not a bounded range of integers.
496
556
  */
497
557
  getJobs<TData = unknown, TResult = unknown>(queueName: string, status: JobStatus, start?: number, end?: number): Promise<TypedJob<TData, TResult>[]>;
498
558
  /**
@@ -597,6 +657,7 @@ declare class QueueService {
597
657
  * })
598
658
  */
599
659
  declare class FlowService {
660
+ #private;
600
661
  private readonly logger;
601
662
  private readonly producer?;
602
663
  constructor(connection: ConnectionResolver, enabled: boolean, prefix: string, telemetry?: Telemetry);
@@ -629,6 +690,8 @@ declare class FlowService {
629
690
  * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
630
691
  */
631
692
  addBulk(flows: readonly FlowJob[]): Promise<JobNode[]>;
693
+ /** The command deadline of the producer connection, read when a command has timed out. */
694
+ private commandTimeout;
632
695
  /**
633
696
  * Escape hatch returning the underlying `FlowProducer` for advanced use cases.
634
697
  * Prefer {@link FlowService.add} / {@link FlowService.addBulk} when possible.
@@ -751,6 +814,14 @@ interface WorkerOptions {
751
814
  lockDuration?: number;
752
815
  /** Interval between stalled-job checks, in milliseconds. Default: 30_000. */
753
816
  stalledInterval?: number;
817
+ /**
818
+ * Fail a job whose handler has not settled after this many milliseconds, so it retries under the
819
+ * job's `attempts` and `backoff` and its concurrency slot is freed. The handler's `signal` aborts
820
+ * at that moment; a handler that ignores it keeps running in the background. There is no default:
821
+ * without it a handler that never settles holds its slot indefinitely. Not available for
822
+ * sandboxed workers. Range: 1 to 2_147_483_647.
823
+ */
824
+ jobTimeoutMs?: number;
754
825
  }
755
826
 
756
827
  /**
@@ -773,8 +844,13 @@ interface WorkerOptions {
773
844
  interface ProgrammaticWorkerConfig<TData = unknown, TResult = unknown> {
774
845
  /** BullMQ queue the worker should consume. */
775
846
  queueName: string;
776
- /** Async function that processes a single job. Must be idempotent. */
777
- handler: (job: Job<TData, TResult>) => Promise<TResult>;
847
+ /**
848
+ * Async function that processes a single job. Must be idempotent. Its optional second argument
849
+ * is the job's `AbortSignal`: it aborts when the job's `jobTimeoutMs` passes or when BullMQ
850
+ * cancels the job (`worker.cancelJob()`, `worker.cancelAllJobs()`). Closing the worker does not
851
+ * abort it. Pass it to whatever the handler awaits so the work stops with the job.
852
+ */
853
+ handler: (job: Job<TData, TResult>, signal?: AbortSignal) => Promise<TResult>;
778
854
  /** Optional worker tuning overrides. */
779
855
  options?: WorkerOptions;
780
856
  }
@@ -912,6 +988,14 @@ declare class WorkerRegistry {
912
988
  * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when values are out of range.
913
989
  */
914
990
  private validateOptions;
991
+ /**
992
+ * Validate `jobTimeoutMs`, which must be a timer delay Node honours: above 2^31 - 1 ms it would
993
+ * fire after 1 ms and fail every job at once.
994
+ *
995
+ * @param opts - The caller-supplied options (may be undefined).
996
+ * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when the value is out of range.
997
+ */
998
+ private validateJobTimeout;
915
999
  /**
916
1000
  * Validate that `processorFile` is a safe, reachable artifact path. Accepts
917
1001
  * absolute paths with `.js`/`.cjs`/`.mjs` extensions and `file:` URLs. Rejects
@@ -1020,9 +1104,9 @@ declare class QueueEventsRegistry {
1020
1104
  * HTTP server has stopped, it releases what a request can still be using: the
1021
1105
  * flow producer, the cached queues, and finally the Queue-role connection.
1022
1106
  *
1023
- * Delivery is at-least-once: when a worker exceeds the drain budget it is
1024
- * force-closed and its in-flight job becomes `stalled` and is retried by another
1025
- * worker. Handlers must therefore be idempotent, and `lockDuration` should
1107
+ * Delivery is at-least-once: when a worker exceeds the drain budget it is stopped
1108
+ * without waiting for its job, whose lock then expires so the job becomes `stalled`
1109
+ * and is retried by another worker. Handlers must therefore be idempotent, and `lockDuration` should
1026
1110
  * comfortably exceed the worst-case handler runtime so healthy long jobs are
1027
1111
  * never treated as stalled.
1028
1112
  * @layer server/lifecycle
@@ -1071,28 +1155,26 @@ declare class QueueLifecycle implements OnModuleDestroy, OnApplicationShutdown {
1071
1155
  */
1072
1156
  onApplicationShutdown(): Promise<void>;
1073
1157
  /**
1074
- * Close every worker with a bounded drain, force-closing any that exceed the
1075
- * budget. Returns the number of workers that had to be force-closed.
1158
+ * Stop every worker in parallel, each bounded by what is left of the shutdown budget. A worker
1159
+ * is paused first (it stops fetching and waits for its active jobs) and then closed. One that is
1160
+ * still busy when the budget ends is closed with `close(true)`: because `pause` does not start a
1161
+ * close, that is the worker's FIRST close, so it does not wait for the job and stops the lock
1162
+ * renewal that would otherwise keep running and keep the process alive. The job's lock then
1163
+ * expires, and it stalls and is retried elsewhere. Returns how many workers were stopped
1164
+ * without draining.
1076
1165
  *
1077
- * @returns The count of force-closed workers.
1166
+ * @param deadline - The epoch millisecond at which the shutdown budget ends.
1167
+ * @returns The count of workers stopped without draining.
1078
1168
  */
1079
1169
  private closeWorkers;
1080
- /**
1081
- * Race a graceful `worker.close()` against a timer. `worker.close()` already
1082
- * drains active jobs and takes no timeout argument, so the timer is the only
1083
- * way to bound the wait. Rejects with a timeout error when the budget elapses.
1084
- *
1085
- * @param worker - The worker to close.
1086
- * @param timeoutMs - The drain budget in milliseconds.
1087
- */
1088
- private closeWorkerWithTimeout;
1089
- /** Close every cached `QueueEvents`, swallowing individual failures. */
1170
+ /** Close every cached `QueueEvents` in parallel within the budget, swallowing failures. */
1090
1171
  private closeQueueEvents;
1091
1172
  /**
1092
- * Close the duplicated worker/queue-events connections the library opened.
1093
- * BullMQ treats a passed-in client as shared and never closes it, so the
1094
- * library must release these explicitly. In Mode A the consumer's shared
1095
- * client is never among them.
1173
+ * Close the duplicated worker/queue-events connections the library opened, in parallel. BullMQ
1174
+ * treats a passed-in client as shared and never closes it, so the library must release these
1175
+ * explicitly. A connection that does not quit within the budget is disconnected, which also
1176
+ * releases anything still blocked on it. In Mode A the consumer's shared client is never among
1177
+ * them.
1096
1178
  */
1097
1179
  private closeDuplicatedConnections;
1098
1180
  /**
@@ -1161,7 +1243,7 @@ declare function Processor(queueName: string, options?: WorkerOptions): ClassDec
1161
1243
  * an entry to the accumulated metadata array.
1162
1244
  *
1163
1245
  * Delivery is at-least-once: a handler may run more than once for the same job
1164
- * (worker crash, lock expiry, or a shutdown force-close). Handlers MUST be
1246
+ * (worker crash, lock expiry, or a shutdown that outlasts the drain budget). Handlers MUST be
1165
1247
  * idempotent — use an idempotency key on writes, prefer upserts over inserts, or
1166
1248
  * keep an "already-processed" marker keyed by `job.id`.
1167
1249
  *
@@ -1213,7 +1295,7 @@ type WorkerEventName = 'completed' | 'failed' | 'progress' | 'active' | 'stalled
1213
1295
  * overwriting each other.
1214
1296
  *
1215
1297
  * Delivery is at-least-once. A `'stalled'` job — for example after a shutdown
1216
- * force-close or a lock expiry — is re-run by another worker: this is the
1298
+ * that outlasts the drain budget, or a lock expiry — is re-run by another worker: this is the
1217
1299
  * visible at-least-once path, so handlers must be idempotent. Set `lockDuration`
1218
1300
  * comfortably above the worst-case handler runtime to avoid false stalls.
1219
1301
  *
@@ -1408,13 +1490,16 @@ declare const DEFAULT_WORKER_CONCURRENCY: 2;
1408
1490
  declare const MAX_WORKER_CONCURRENCY: 1000;
1409
1491
  /**
1410
1492
  * Default options applied to every job enqueued through the service. The
1411
- * `removeOnFail.count` cap bounds Redis memory growth under failure storms.
1493
+ * `removeOnFail.count` cap bounds Redis memory growth under failure storms. The backoff jitter
1494
+ * spreads each retry over the upper half of its delay, so jobs that failed together (a Redis
1495
+ * outage, a downstream timeout) do not all retry in the same instant.
1412
1496
  */
1413
1497
  declare const DEFAULT_JOB_OPTIONS: {
1414
1498
  readonly attempts: 3;
1415
1499
  readonly backoff: {
1416
1500
  readonly type: "exponential";
1417
1501
  readonly delay: 2000;
1502
+ readonly jitter: 0.5;
1418
1503
  };
1419
1504
  readonly removeOnComplete: {
1420
1505
  readonly age: number;
@@ -1427,7 +1512,7 @@ declare const DEFAULT_JOB_OPTIONS: {
1427
1512
  };
1428
1513
  /** Default TTL (ms) for the metrics cache. */
1429
1514
  declare const DEFAULT_METRICS_CACHE_TTL_MS: 5000;
1430
- /** Default window (ms) for in-flight jobs to drain before force-close on shutdown. */
1515
+ /** Default window (ms) for in-flight jobs to drain before the worker is stopped on shutdown. */
1431
1516
  declare const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS: 30000;
1432
1517
 
1433
1518
  /**