@bymax-one/nest-queue 1.2.14 → 1.2.16

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.
@@ -105,9 +105,11 @@ interface BymaxQueueModuleOptions {
105
105
  */
106
106
  commandTimeoutMs?: number;
107
107
  /**
108
- * The deadline, in milliseconds, for one `enqueue` or `enqueueBulk` call in either connection mode.
109
- * When it passes, the call rejects with `CONNECTION_TIMEOUT`; the add may still complete in Redis
110
- * afterwards, so retry with a stable `jobId` or `deduplication`. Default: 10_000.
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.
111
113
  */
112
114
  enqueueTimeoutMs?: number;
113
115
  }
@@ -401,6 +403,14 @@ declare class ConnectionResolver {
401
403
  getMode(): QueueConnectionMode;
402
404
  /** Whether the library owns (and must close) the connection. */
403
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>;
404
414
  /** Close the library-owned connection on shutdown; never touch a BYO client. */
405
415
  teardown(): Promise<void>;
406
416
  /** Validate and adopt a bring-your-own client for the Queue role. */
@@ -446,20 +456,27 @@ declare class QueueService {
446
456
  * @param overrides - Optional per-queue option overrides.
447
457
  */
448
458
  getOrCreateQueue<TData = unknown, TResult = unknown>(queueName: string, overrides?: Partial<Omit<QueueOptions, 'connection' | 'prefix'>>): Queue<TData, TResult>;
449
- /** The error a producer call receives when Redis does not answer within `enqueueTimeoutMs`. */
450
- private enqueueTimeout;
451
459
  /**
452
- * Await a Redis call, reporting an ioredis command timeout as `CONNECTION_TIMEOUT` with the
453
- * operation and the command deadline of the connection in force.
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.
454
469
  */
455
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;
456
473
  /** Resolve the raw cached queue (default generics) for internal delegation. */
457
474
  private queueFor;
458
475
  /**
459
476
  * Add a single job to a queue. The queue is created lazily if needed.
460
477
  *
461
478
  * Delivery is **at-least-once** — a handler may run more than once for the same
462
- * 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
463
480
  * idempotent: use an idempotency key on writes, prefer upserts over inserts, or
464
481
  * keep an "already-processed" marker keyed by `job.id`.
465
482
  *
@@ -580,9 +597,11 @@ declare class QueueService {
580
597
  *
581
598
  * @param queueName - Target queue.
582
599
  * @param start - Page start index (inclusive). Default: 0.
583
- * @param end - Page end index (inclusive). Default: 50.
600
+ * @param end - Page end index (inclusive). Default: 50. The window may span at most
601
+ * `MAX_JOBS_PAGE_SIZE` (1000) entries, and BullMQ's `-1` (everything) is refused.
584
602
  * @param asc - Sort ascending by next run time. Default: true.
585
603
  * @returns The registered schedulers in the requested page.
604
+ * @throws {QueueException} `INVALID_OPTIONS` when the window is not a bounded range of integers.
586
605
  */
587
606
  getJobSchedulers(queueName: string, start?: number, end?: number, asc?: boolean): Promise<Awaited<ReturnType<Queue['getJobSchedulers']>>>;
588
607
  /** Pause a queue, halting the processing of new jobs. */
@@ -643,7 +662,7 @@ declare class FlowService {
643
662
  #private;
644
663
  private readonly logger;
645
664
  private readonly producer?;
646
- constructor(connection: ConnectionResolver, enabled: boolean, prefix: string, telemetry?: Telemetry);
665
+ constructor(connection: ConnectionResolver, enabled: boolean, prefix: string, telemetry?: Telemetry, enqueueTimeoutMs?: number);
647
666
  /**
648
667
  * Add a flow tree (a parent with its descendant children). The root job
649
668
  * becomes processable only after every descendant has completed.
@@ -661,7 +680,8 @@ declare class FlowService {
661
680
  * A job whose custom `jobId` already exists is not added again: its node carries the EXISTING
662
681
  * id with the data of this call, so the tree does not describe what is stored; call
663
682
  * `QueueService.getJob` when you need it.
664
- * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
683
+ * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled; `CONNECTION_TIMEOUT`
684
+ * when Redis does not answer within `enqueueTimeoutMs` (the flow may still have been added).
665
685
  */
666
686
  add(flow: FlowJob): Promise<JobNode>;
667
687
  /**
@@ -670,9 +690,17 @@ declare class FlowService {
670
690
  * @param flows - Flow tree definitions.
671
691
  * @returns The root `JobNode` for each flow, in input order.
672
692
  * A job whose custom `jobId` already exists is not added again; see {@link FlowService.add}.
673
- * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
693
+ * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled;
694
+ * `BULK_ENQUEUE_FAILED` for more than 1000 flows; `CONNECTION_TIMEOUT` as for
695
+ * {@link FlowService.add}.
674
696
  */
675
697
  addBulk(flows: readonly FlowJob[]): Promise<JobNode[]>;
698
+ /**
699
+ * Hold the caller to `enqueueTimeoutMs`, and report a command timeout or a down connection as
700
+ * `CONNECTION_TIMEOUT`. A call that timed out may still have been added: BullMQ is not asked to
701
+ * cancel it.
702
+ */
703
+ private bounded;
676
704
  /** The command deadline of the producer connection, read when a command has timed out. */
677
705
  private commandTimeout;
678
706
  /**
@@ -964,8 +992,9 @@ declare class WorkerRegistry {
964
992
  private guardDuplicate;
965
993
  /**
966
994
  * Validate tunable options before constructing a Worker.
967
- * Enforces `concurrency` in `[1, MAX_WORKER_CONCURRENCY]` and
968
- * `limiter.max`/`limiter.duration` `>= 1`.
995
+ * Enforces whole-number `concurrency` in `[1, MAX_WORKER_CONCURRENCY]`, whole-number
996
+ * `limiter.max`/`limiter.duration` `>= 1`, and `lockDuration`/`stalledInterval` within
997
+ * 1000..2^31 - 1.
969
998
  *
970
999
  * @param opts - The caller-supplied options (may be undefined).
971
1000
  * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when values are out of range.
@@ -1087,9 +1116,9 @@ declare class QueueEventsRegistry {
1087
1116
  * HTTP server has stopped, it releases what a request can still be using: the
1088
1117
  * flow producer, the cached queues, and finally the Queue-role connection.
1089
1118
  *
1090
- * Delivery is at-least-once: when a worker exceeds the drain budget it is
1091
- * force-closed and its in-flight job becomes `stalled` and is retried by another
1092
- * worker. Handlers must therefore be idempotent, and `lockDuration` should
1119
+ * Delivery is at-least-once: when a worker exceeds the drain budget it is stopped
1120
+ * without waiting for its job, whose lock then expires so the job becomes `stalled`
1121
+ * and is retried by another worker. Handlers must therefore be idempotent, and `lockDuration` should
1093
1122
  * comfortably exceed the worst-case handler runtime so healthy long jobs are
1094
1123
  * never treated as stalled.
1095
1124
  * @layer server/lifecycle
@@ -1138,11 +1167,16 @@ declare class QueueLifecycle implements OnModuleDestroy, OnApplicationShutdown {
1138
1167
  */
1139
1168
  onApplicationShutdown(): Promise<void>;
1140
1169
  /**
1141
- * Close every worker in parallel, each bounded by what is left of the shutdown budget, and
1142
- * force-close any that exceed it. Returns the number of workers that had to be force-closed.
1170
+ * Stop every worker in parallel, each bounded by what is left of the shutdown budget. A worker
1171
+ * is paused first (it stops fetching and waits for its active jobs) and then closed. One that is
1172
+ * still busy when the budget ends is closed with `close(true)`: because `pause` does not start a
1173
+ * close, that is the worker's FIRST close, so it does not wait for the job and stops the lock
1174
+ * renewal that would otherwise keep running and keep the process alive. The job's lock then
1175
+ * expires, and it stalls and is retried elsewhere. Returns how many workers were stopped
1176
+ * without draining.
1143
1177
  *
1144
1178
  * @param deadline - The epoch millisecond at which the shutdown budget ends.
1145
- * @returns The count of force-closed workers.
1179
+ * @returns The count of workers stopped without draining.
1146
1180
  */
1147
1181
  private closeWorkers;
1148
1182
  /** Close every cached `QueueEvents` in parallel within the budget, swallowing failures. */
@@ -1221,7 +1255,7 @@ declare function Processor(queueName: string, options?: WorkerOptions): ClassDec
1221
1255
  * an entry to the accumulated metadata array.
1222
1256
  *
1223
1257
  * Delivery is at-least-once: a handler may run more than once for the same job
1224
- * (worker crash, lock expiry, or a shutdown force-close). Handlers MUST be
1258
+ * (worker crash, lock expiry, or a shutdown that outlasts the drain budget). Handlers MUST be
1225
1259
  * idempotent — use an idempotency key on writes, prefer upserts over inserts, or
1226
1260
  * keep an "already-processed" marker keyed by `job.id`.
1227
1261
  *
@@ -1273,7 +1307,7 @@ type WorkerEventName = 'completed' | 'failed' | 'progress' | 'active' | 'stalled
1273
1307
  * overwriting each other.
1274
1308
  *
1275
1309
  * Delivery is at-least-once. A `'stalled'` job — for example after a shutdown
1276
- * force-close or a lock expiry — is re-run by another worker: this is the
1310
+ * that outlasts the drain budget, or a lock expiry — is re-run by another worker: this is the
1277
1311
  * visible at-least-once path, so handlers must be idempotent. Set `lockDuration`
1278
1312
  * comfortably above the worst-case handler runtime to avoid false stalls.
1279
1313
  *
@@ -1490,7 +1524,7 @@ declare const DEFAULT_JOB_OPTIONS: {
1490
1524
  };
1491
1525
  /** Default TTL (ms) for the metrics cache. */
1492
1526
  declare const DEFAULT_METRICS_CACHE_TTL_MS: 5000;
1493
- /** Default window (ms) for in-flight jobs to drain before force-close on shutdown. */
1527
+ /** Default window (ms) for in-flight jobs to drain before the worker is stopped on shutdown. */
1494
1528
  declare const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS: 30000;
1495
1529
 
1496
1530
  /**
@@ -105,9 +105,11 @@ interface BymaxQueueModuleOptions {
105
105
  */
106
106
  commandTimeoutMs?: number;
107
107
  /**
108
- * The deadline, in milliseconds, for one `enqueue` or `enqueueBulk` call in either connection mode.
109
- * When it passes, the call rejects with `CONNECTION_TIMEOUT`; the add may still complete in Redis
110
- * afterwards, so retry with a stable `jobId` or `deduplication`. Default: 10_000.
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.
111
113
  */
112
114
  enqueueTimeoutMs?: number;
113
115
  }
@@ -401,6 +403,14 @@ declare class ConnectionResolver {
401
403
  getMode(): QueueConnectionMode;
402
404
  /** Whether the library owns (and must close) the connection. */
403
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>;
404
414
  /** Close the library-owned connection on shutdown; never touch a BYO client. */
405
415
  teardown(): Promise<void>;
406
416
  /** Validate and adopt a bring-your-own client for the Queue role. */
@@ -446,20 +456,27 @@ declare class QueueService {
446
456
  * @param overrides - Optional per-queue option overrides.
447
457
  */
448
458
  getOrCreateQueue<TData = unknown, TResult = unknown>(queueName: string, overrides?: Partial<Omit<QueueOptions, 'connection' | 'prefix'>>): Queue<TData, TResult>;
449
- /** The error a producer call receives when Redis does not answer within `enqueueTimeoutMs`. */
450
- private enqueueTimeout;
451
459
  /**
452
- * Await a Redis call, reporting an ioredis command timeout as `CONNECTION_TIMEOUT` with the
453
- * operation and the command deadline of the connection in force.
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.
454
469
  */
455
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;
456
473
  /** Resolve the raw cached queue (default generics) for internal delegation. */
457
474
  private queueFor;
458
475
  /**
459
476
  * Add a single job to a queue. The queue is created lazily if needed.
460
477
  *
461
478
  * Delivery is **at-least-once** — a handler may run more than once for the same
462
- * 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
463
480
  * idempotent: use an idempotency key on writes, prefer upserts over inserts, or
464
481
  * keep an "already-processed" marker keyed by `job.id`.
465
482
  *
@@ -580,9 +597,11 @@ declare class QueueService {
580
597
  *
581
598
  * @param queueName - Target queue.
582
599
  * @param start - Page start index (inclusive). Default: 0.
583
- * @param end - Page end index (inclusive). Default: 50.
600
+ * @param end - Page end index (inclusive). Default: 50. The window may span at most
601
+ * `MAX_JOBS_PAGE_SIZE` (1000) entries, and BullMQ's `-1` (everything) is refused.
584
602
  * @param asc - Sort ascending by next run time. Default: true.
585
603
  * @returns The registered schedulers in the requested page.
604
+ * @throws {QueueException} `INVALID_OPTIONS` when the window is not a bounded range of integers.
586
605
  */
587
606
  getJobSchedulers(queueName: string, start?: number, end?: number, asc?: boolean): Promise<Awaited<ReturnType<Queue['getJobSchedulers']>>>;
588
607
  /** Pause a queue, halting the processing of new jobs. */
@@ -643,7 +662,7 @@ declare class FlowService {
643
662
  #private;
644
663
  private readonly logger;
645
664
  private readonly producer?;
646
- constructor(connection: ConnectionResolver, enabled: boolean, prefix: string, telemetry?: Telemetry);
665
+ constructor(connection: ConnectionResolver, enabled: boolean, prefix: string, telemetry?: Telemetry, enqueueTimeoutMs?: number);
647
666
  /**
648
667
  * Add a flow tree (a parent with its descendant children). The root job
649
668
  * becomes processable only after every descendant has completed.
@@ -661,7 +680,8 @@ declare class FlowService {
661
680
  * A job whose custom `jobId` already exists is not added again: its node carries the EXISTING
662
681
  * id with the data of this call, so the tree does not describe what is stored; call
663
682
  * `QueueService.getJob` when you need it.
664
- * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
683
+ * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled; `CONNECTION_TIMEOUT`
684
+ * when Redis does not answer within `enqueueTimeoutMs` (the flow may still have been added).
665
685
  */
666
686
  add(flow: FlowJob): Promise<JobNode>;
667
687
  /**
@@ -670,9 +690,17 @@ declare class FlowService {
670
690
  * @param flows - Flow tree definitions.
671
691
  * @returns The root `JobNode` for each flow, in input order.
672
692
  * A job whose custom `jobId` already exists is not added again; see {@link FlowService.add}.
673
- * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
693
+ * @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled;
694
+ * `BULK_ENQUEUE_FAILED` for more than 1000 flows; `CONNECTION_TIMEOUT` as for
695
+ * {@link FlowService.add}.
674
696
  */
675
697
  addBulk(flows: readonly FlowJob[]): Promise<JobNode[]>;
698
+ /**
699
+ * Hold the caller to `enqueueTimeoutMs`, and report a command timeout or a down connection as
700
+ * `CONNECTION_TIMEOUT`. A call that timed out may still have been added: BullMQ is not asked to
701
+ * cancel it.
702
+ */
703
+ private bounded;
676
704
  /** The command deadline of the producer connection, read when a command has timed out. */
677
705
  private commandTimeout;
678
706
  /**
@@ -964,8 +992,9 @@ declare class WorkerRegistry {
964
992
  private guardDuplicate;
965
993
  /**
966
994
  * Validate tunable options before constructing a Worker.
967
- * Enforces `concurrency` in `[1, MAX_WORKER_CONCURRENCY]` and
968
- * `limiter.max`/`limiter.duration` `>= 1`.
995
+ * Enforces whole-number `concurrency` in `[1, MAX_WORKER_CONCURRENCY]`, whole-number
996
+ * `limiter.max`/`limiter.duration` `>= 1`, and `lockDuration`/`stalledInterval` within
997
+ * 1000..2^31 - 1.
969
998
  *
970
999
  * @param opts - The caller-supplied options (may be undefined).
971
1000
  * @throws {QueueException} `WORKER_REGISTRATION_FAILED` when values are out of range.
@@ -1087,9 +1116,9 @@ declare class QueueEventsRegistry {
1087
1116
  * HTTP server has stopped, it releases what a request can still be using: the
1088
1117
  * flow producer, the cached queues, and finally the Queue-role connection.
1089
1118
  *
1090
- * Delivery is at-least-once: when a worker exceeds the drain budget it is
1091
- * force-closed and its in-flight job becomes `stalled` and is retried by another
1092
- * worker. Handlers must therefore be idempotent, and `lockDuration` should
1119
+ * Delivery is at-least-once: when a worker exceeds the drain budget it is stopped
1120
+ * without waiting for its job, whose lock then expires so the job becomes `stalled`
1121
+ * and is retried by another worker. Handlers must therefore be idempotent, and `lockDuration` should
1093
1122
  * comfortably exceed the worst-case handler runtime so healthy long jobs are
1094
1123
  * never treated as stalled.
1095
1124
  * @layer server/lifecycle
@@ -1138,11 +1167,16 @@ declare class QueueLifecycle implements OnModuleDestroy, OnApplicationShutdown {
1138
1167
  */
1139
1168
  onApplicationShutdown(): Promise<void>;
1140
1169
  /**
1141
- * Close every worker in parallel, each bounded by what is left of the shutdown budget, and
1142
- * force-close any that exceed it. Returns the number of workers that had to be force-closed.
1170
+ * Stop every worker in parallel, each bounded by what is left of the shutdown budget. A worker
1171
+ * is paused first (it stops fetching and waits for its active jobs) and then closed. One that is
1172
+ * still busy when the budget ends is closed with `close(true)`: because `pause` does not start a
1173
+ * close, that is the worker's FIRST close, so it does not wait for the job and stops the lock
1174
+ * renewal that would otherwise keep running and keep the process alive. The job's lock then
1175
+ * expires, and it stalls and is retried elsewhere. Returns how many workers were stopped
1176
+ * without draining.
1143
1177
  *
1144
1178
  * @param deadline - The epoch millisecond at which the shutdown budget ends.
1145
- * @returns The count of force-closed workers.
1179
+ * @returns The count of workers stopped without draining.
1146
1180
  */
1147
1181
  private closeWorkers;
1148
1182
  /** Close every cached `QueueEvents` in parallel within the budget, swallowing failures. */
@@ -1221,7 +1255,7 @@ declare function Processor(queueName: string, options?: WorkerOptions): ClassDec
1221
1255
  * an entry to the accumulated metadata array.
1222
1256
  *
1223
1257
  * Delivery is at-least-once: a handler may run more than once for the same job
1224
- * (worker crash, lock expiry, or a shutdown force-close). Handlers MUST be
1258
+ * (worker crash, lock expiry, or a shutdown that outlasts the drain budget). Handlers MUST be
1225
1259
  * idempotent — use an idempotency key on writes, prefer upserts over inserts, or
1226
1260
  * keep an "already-processed" marker keyed by `job.id`.
1227
1261
  *
@@ -1273,7 +1307,7 @@ type WorkerEventName = 'completed' | 'failed' | 'progress' | 'active' | 'stalled
1273
1307
  * overwriting each other.
1274
1308
  *
1275
1309
  * Delivery is at-least-once. A `'stalled'` job — for example after a shutdown
1276
- * force-close or a lock expiry — is re-run by another worker: this is the
1310
+ * that outlasts the drain budget, or a lock expiry — is re-run by another worker: this is the
1277
1311
  * visible at-least-once path, so handlers must be idempotent. Set `lockDuration`
1278
1312
  * comfortably above the worst-case handler runtime to avoid false stalls.
1279
1313
  *
@@ -1490,7 +1524,7 @@ declare const DEFAULT_JOB_OPTIONS: {
1490
1524
  };
1491
1525
  /** Default TTL (ms) for the metrics cache. */
1492
1526
  declare const DEFAULT_METRICS_CACHE_TTL_MS: 5000;
1493
- /** Default window (ms) for in-flight jobs to drain before force-close on shutdown. */
1527
+ /** Default window (ms) for in-flight jobs to drain before the worker is stopped on shutdown. */
1494
1528
  declare const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS: 30000;
1495
1529
 
1496
1530
  /**