@bymax-one/nest-queue 1.2.9 → 1.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -1
- package/README.md +27 -16
- package/dist/server/index.cjs +59 -484
- package/dist/server/index.d.cts +44 -14
- package/dist/server/index.d.ts +44 -14
- package/dist/server/index.mjs +60 -485
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,105 @@ versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [1.2.11] — 2026-11-06
|
|
15
|
+
|
|
16
|
+
Action required only if you extend or replace `QueueLifecycle`, or rely on the order in which the
|
|
17
|
+
library closes its resources: the producer side now closes after the HTTP server stops, see the Apply
|
|
18
|
+
note. Healthy use is unchanged. The public API and the peer ranges are unchanged; the declaration
|
|
19
|
+
files differ because `QueueLifecycle` now also implements `OnApplicationShutdown` (and in JSDoc).
|
|
20
|
+
Measured with `diff -r` of a fresh build against the unpacked 1.2.10 tarball, the runtime bundles
|
|
21
|
+
(`index.mjs`, `index.cjs`) differ in code.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **A request still being served when the application closed could not enqueue.** NestJS runs every
|
|
26
|
+
`onModuleDestroy` hook before it stops the HTTP server, and `QueueLifecycle` closed the flow
|
|
27
|
+
producer, the cached queues and the library-owned Redis connection in that hook. A request that
|
|
28
|
+
enqueued in the window between the two (a request in flight during `app.close()`, or a
|
|
29
|
+
`SIGTERM` with shutdown hooks enabled) failed with `Connection is closed.` in Mode B. Those three
|
|
30
|
+
resources are now released in `onApplicationShutdown`, which NestJS runs after the server has
|
|
31
|
+
stopped. Consumption still stops first: workers drain within `shutdown.drainTimeoutMs`, then
|
|
32
|
+
queue-events and the connections opened for workers and queue-events close, all in
|
|
33
|
+
`onModuleDestroy`. `shutdown.drainOnShutdown` now runs in `onApplicationShutdown` too, right before
|
|
34
|
+
the queues close, so a job enqueued late is drained with the rest. The log line `Queue shutdown
|
|
35
|
+
complete in Nms (forced N worker(s))` is now written once, at the end, and its time covers both
|
|
36
|
+
hooks. In Mode A the consumer's client is untouched, as before.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **The shipped bundles no longer carry comments.** `dist/server/index.mjs` and `index.cjs` are
|
|
41
|
+
smaller; the declaration files keep their JSDoc, and the comment removal does not change the
|
|
42
|
+
runtime behaviour of the bundles.
|
|
43
|
+
|
|
44
|
+
### Documentation
|
|
45
|
+
|
|
46
|
+
- **The README describes the two shutdown hooks** and when each resource closes.
|
|
47
|
+
|
|
48
|
+
#### Apply to a derived backend
|
|
49
|
+
|
|
50
|
+
- If you subclass or wrap `QueueLifecycle`, or call `onModuleDestroy()` yourself to tear the library
|
|
51
|
+
down in a test, also call `onApplicationShutdown()` afterwards: the flow producer, the cached
|
|
52
|
+
queues and the library-owned connection are no longer closed by `onModuleDestroy()`. Nest calls
|
|
53
|
+
both when the application closes.
|
|
54
|
+
- If you enqueue from a hook that runs after the library's `onModuleDestroy` (for example your own
|
|
55
|
+
`beforeApplicationShutdown`), the enqueue now succeeds instead of failing with
|
|
56
|
+
`Connection is closed.`; if you worked around that failure, the workaround can go.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## [1.2.10] — 2026-11-01
|
|
61
|
+
|
|
62
|
+
Action required only if code relies on the status of a failed schedule: `upsertJobScheduler` no
|
|
63
|
+
longer reports every failure as `INVALID_REPEAT_OPTIONS` (400), see the Apply note. Healthy use is
|
|
64
|
+
unchanged. The public API, the types (the declaration files differ only in JSDoc comments) and the
|
|
65
|
+
peer ranges are unchanged. Measured with `diff -r` of a fresh build against the unpacked 1.2.9 tarball,
|
|
66
|
+
the runtime bundles (`index.mjs`, `index.cjs`) differ in code.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- **A failed schedule write was reported as an invalid cron.** `upsertJobScheduler` turned every
|
|
71
|
+
error without a string `code` into `INVALID_REPEAT_OPTIONS` (400, "pattern must be a valid cron
|
|
72
|
+
expression"). A Redis reply error carries no `code`, so a replica refusing the write (`READONLY`),
|
|
73
|
+
an out-of-memory server (`OOM`) or a missing login (`NOAUTH`) was reported as the caller's mistake,
|
|
74
|
+
and the caller was told not to retry. The pattern is now checked first with BullMQ's own repeat
|
|
75
|
+
strategy, a pure function with no Redis call, and only a pattern it refuses is
|
|
76
|
+
`INVALID_REPEAT_OPTIONS`; every failure of the write itself reaches the caller as it is. A
|
|
77
|
+
consumer-supplied `settings.repeatStrategy` replaces the parser, so the pattern is not checked.
|
|
78
|
+
A malformed pattern on a schedule whose iteration `limit` is already reached is now rejected as
|
|
79
|
+
well; BullMQ's own upsert returns before parsing such a schedule and silently accepts it.
|
|
80
|
+
- **A failing `@OnWorkerEvent` or `@OnQueueEvent` handler could end the process.** The listener was
|
|
81
|
+
bound straight onto the emitter, which neither awaits it nor catches a throw: an async handler
|
|
82
|
+
that rejected became an unhandled rejection, which ends a Node process by default, and a
|
|
83
|
+
synchronous throw was re-emitted as `error`. The failure is now logged with the handler's name
|
|
84
|
+
and the code `queue.event_handler_failed`, and the process carries on.
|
|
85
|
+
- **`drainOnShutdown` failed silently.** A queue whose drain failed kept its waiting jobs with no
|
|
86
|
+
trace in the log; it now logs a warning naming the queue and the cause.
|
|
87
|
+
- **A worker whose close failed was logged as a drain timeout.** Any rejection of the graceful
|
|
88
|
+
close was reported as "exceeded the drain budget"; a real error, such as a lost connection, now
|
|
89
|
+
has its own message with the cause. The worker is force-closed either way.
|
|
90
|
+
|
|
91
|
+
### Documentation
|
|
92
|
+
|
|
93
|
+
- **A duplicate does not add a job.** The README and the JSDoc of `enqueue`, `enqueueBulk` and the
|
|
94
|
+
flow methods now say that when a custom `jobId` already exists, or a `deduplication` rule keeps
|
|
95
|
+
the job already there, BullMQ answers with the id of the existing job together with the data of
|
|
96
|
+
this call, so the returned object does not describe what is stored; `getJob` returns it.
|
|
97
|
+
- **The scheduler and event-handler behaviour above is described in the README.**
|
|
98
|
+
- **Correction to 1.2.9.** On 1.2.8 only the `FlowProducer` listener could end the process; on
|
|
99
|
+
`Queue`, `Worker` and `QueueEvents` BullMQ's wrapper re-emitted the `TypeError` and the error was
|
|
100
|
+
reported with a misleading log line. 1.2.9 fixes both.
|
|
101
|
+
|
|
102
|
+
#### Apply to a derived backend
|
|
103
|
+
|
|
104
|
+
- If you branch on `INVALID_REPEAT_OPTIONS` to detect any failure of `upsertJobScheduler`, change
|
|
105
|
+
it: a valid pattern whose write fails now throws the original error (a Redis reply error, a
|
|
106
|
+
connection error), not a 400 `QueueException`. An unparseable pattern is still
|
|
107
|
+
`INVALID_REPEAT_OPTIONS`, now thrown before Redis is asked.
|
|
108
|
+
- A listener that fails is now logged instead of ending the process. If something relied on the
|
|
109
|
+
crash to stop a faulty deploy, watch for the log code `queue.event_handler_failed`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
14
113
|
## [1.2.9] — 2026-11-01
|
|
15
114
|
|
|
16
115
|
No action required: behaviour for an `Error` with a string `message` is unchanged, and the public API, the types
|
|
@@ -768,7 +867,9 @@ v6 peer range.
|
|
|
768
867
|
|
|
769
868
|
---
|
|
770
869
|
|
|
771
|
-
[Unreleased]: https://github.com/bymaxone/nest-queue/compare/v1.2.
|
|
870
|
+
[Unreleased]: https://github.com/bymaxone/nest-queue/compare/v1.2.11...HEAD
|
|
871
|
+
[1.2.11]: https://github.com/bymaxone/nest-queue/compare/v1.2.10...v1.2.11
|
|
872
|
+
[1.2.10]: https://github.com/bymaxone/nest-queue/compare/v1.2.9...v1.2.10
|
|
772
873
|
[1.2.9]: https://github.com/bymaxone/nest-queue/compare/v1.2.8...v1.2.9
|
|
773
874
|
[1.2.8]: https://github.com/bymaxone/nest-queue/compare/v1.2.7...v1.2.8
|
|
774
875
|
[1.2.7]: https://github.com/bymaxone/nest-queue/compare/v1.2.6...v1.2.7
|
package/README.md
CHANGED
|
@@ -235,20 +235,20 @@ export class EmailModule {}
|
|
|
235
235
|
|
|
236
236
|
Every option passed to `forRoot` / `forRootAsync`. Only `connection` is required.
|
|
237
237
|
|
|
238
|
-
| Option | Type | Default | Description
|
|
239
|
-
| -------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
240
|
-
| `connection` | `{ client } \| { url, options? } \| { options }` | — | **Required.** Mode A supplies an existing `ioredis` client; Mode B lets the library open its own. The arms are mutually exclusive.
|
|
241
|
-
| `prefix` | `string` | `'bull'` | Redis key namespace. Applied to the `Queue`, `Worker`, `QueueEvents` **and** `FlowProducer` — see [Architecture](#-architecture).
|
|
242
|
-
| `defaultJobOptions` | `JobsOptions` | `attempts: 3` · exponential backoff `2000` ms · `removeOnComplete: { age: 24 h, count: 1000 }` · `removeOnFail: { age: 7 d, count: 5000 }` | Merged into every job enqueued through the service; per-job options win. The bounded `removeOn*` caps are what keep Redis from growing without limit under a failure storm.
|
|
243
|
-
| `queueOptions` | `Partial<QueueOptions>` | `{}` | Extra BullMQ `Queue` options. `connection`, `defaultJobOptions` and `prefix` are owned by the library and cannot be set here.
|
|
244
|
-
| `flows.enabled` | `boolean` | `false` | `FlowService` is always provided; while disabled, every method except `close()` throws `FLOW_DISABLED`.
|
|
245
|
-
| `metrics.enabled` | `boolean` | `false` | `MetricsService` is always provided; while disabled, every method throws `METRICS_DISABLED`.
|
|
246
|
-
| `metrics.cacheTtlMs` | `number` | `5_000` | TTL of the metrics snapshot cache. `0` disables caching. Must be `>= 0`.
|
|
247
|
-
| `telemetry` | `Telemetry` | — | A BullMQ `Telemetry` instance (typically `new BullMQOtel(...)` from the optional `bullmq-otel` peer), attached to every `Queue` and `Worker`.
|
|
248
|
-
| `shutdown.drainTimeoutMs` | `number` | `30_000` | Upper bound on the graceful drain before workers are force-closed. Must be `> 0`.
|
|
249
|
-
| `shutdown.drainOnShutdown` | `boolean` | `false` | Development and test only:
|
|
250
|
-
| `connectionReadyTimeoutMs` | `number` | `10_000` | Mode B only: how long to wait for Redis `ready` before failing with `CONNECTION_TIMEOUT`.
|
|
251
|
-
| `isGlobal` | `boolean` | `true` | Register the module globally, so `QueueService` is injectable without re-importing.
|
|
238
|
+
| Option | Type | Default | Description |
|
|
239
|
+
| -------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
240
|
+
| `connection` | `{ client } \| { url, options? } \| { options }` | — | **Required.** Mode A supplies an existing `ioredis` client; Mode B lets the library open its own. The arms are mutually exclusive. |
|
|
241
|
+
| `prefix` | `string` | `'bull'` | Redis key namespace. Applied to the `Queue`, `Worker`, `QueueEvents` **and** `FlowProducer` — see [Architecture](#-architecture). |
|
|
242
|
+
| `defaultJobOptions` | `JobsOptions` | `attempts: 3` · exponential backoff `2000` ms · `removeOnComplete: { age: 24 h, count: 1000 }` · `removeOnFail: { age: 7 d, count: 5000 }` | Merged into every job enqueued through the service; per-job options win. The bounded `removeOn*` caps are what keep Redis from growing without limit under a failure storm. |
|
|
243
|
+
| `queueOptions` | `Partial<QueueOptions>` | `{}` | Extra BullMQ `Queue` options. `connection`, `defaultJobOptions` and `prefix` are owned by the library and cannot be set here. |
|
|
244
|
+
| `flows.enabled` | `boolean` | `false` | `FlowService` is always provided; while disabled, every method except `close()` throws `FLOW_DISABLED`. |
|
|
245
|
+
| `metrics.enabled` | `boolean` | `false` | `MetricsService` is always provided; while disabled, every method throws `METRICS_DISABLED`. |
|
|
246
|
+
| `metrics.cacheTtlMs` | `number` | `5_000` | TTL of the metrics snapshot cache. `0` disables caching. Must be `>= 0`. |
|
|
247
|
+
| `telemetry` | `Telemetry` | — | A BullMQ `Telemetry` instance (typically `new BullMQOtel(...)` from the optional `bullmq-otel` peer), attached to every `Queue` and `Worker`. |
|
|
248
|
+
| `shutdown.drainTimeoutMs` | `number` | `30_000` | Upper bound on the graceful drain before workers are force-closed. Must be `> 0`. |
|
|
249
|
+
| `shutdown.drainOnShutdown` | `boolean` | `false` | Development and test only: once the HTTP server has stopped, call `drain()` on every cached queue, which **removes** its waiting jobs (delayed and active jobs are left alone). Leave it `false` in production. |
|
|
250
|
+
| `connectionReadyTimeoutMs` | `number` | `10_000` | Mode B only: how long to wait for Redis `ready` before failing with `CONNECTION_TIMEOUT`. |
|
|
251
|
+
| `isGlobal` | `boolean` | `true` | Register the module globally, so `QueueService` is injectable without re-importing. |
|
|
252
252
|
|
|
253
253
|
> [!WARNING]
|
|
254
254
|
> **`shutdown.drainOnShutdown` deletes jobs.** When it is `true`, shutdown calls `drain()` on every
|
|
@@ -557,6 +557,8 @@ onFailed(job: Job | undefined, err: Error): void {
|
|
|
557
557
|
}
|
|
558
558
|
```
|
|
559
559
|
|
|
560
|
+
A listener that throws, or returns a promise that rejects, is logged with the handler's name and the code `queue.event_handler_failed` and the process carries on; it is never an unhandled rejection. Handle the error inside the listener when the failure matters.
|
|
561
|
+
|
|
560
562
|
### `@OnQueueEvent(eventName)`
|
|
561
563
|
|
|
562
564
|
Listens to BullMQ `QueueEvents` (global, cross-process events). The library opens one shared `QueueEvents` connection per queue when it discovers the first listener for it; there is no option to enable.
|
|
@@ -602,6 +604,7 @@ const schedulers = await this.queue.getJobSchedulers('notifications')
|
|
|
602
604
|
|
|
603
605
|
Cron-with-seconds (6-field patterns) is supported. Patterns are validated by BullMQ's
|
|
604
606
|
own `cron-parser`, not a hand-rolled regex.
|
|
607
|
+
A pattern the parser rejects is `INVALID_REPEAT_OPTIONS` (400) before Redis is asked. Every other failure of the upsert, such as a read-only replica or a lost connection, reaches you as it is.
|
|
605
608
|
|
|
606
609
|
---
|
|
607
610
|
|
|
@@ -643,6 +646,10 @@ await this.queue.enqueue(
|
|
|
643
646
|
)
|
|
644
647
|
```
|
|
645
648
|
|
|
649
|
+
### A duplicate does not add a job
|
|
650
|
+
|
|
651
|
+
When a custom `jobId` already exists, or a `deduplication` rule keeps the job that is already there (a rule with `replace` swaps it for the new one instead), `enqueue`, `enqueueBulk` and the flow methods add nothing: BullMQ answers with the id of the **existing** job together with the data and options of **this** call, so the returned object does not describe what is stored. Call `QueueService.getJob` when you need the stored job.
|
|
652
|
+
|
|
646
653
|
### OpenTelemetry
|
|
647
654
|
|
|
648
655
|
Pass a `bullmq-otel` telemetry instance (optional peer dep) to trace spans from `enqueue` through the handler:
|
|
@@ -660,7 +667,7 @@ BymaxQueueModule.forRoot({
|
|
|
660
667
|
|
|
661
668
|
## 🛑 Graceful Shutdown
|
|
662
669
|
|
|
663
|
-
`QueueLifecycle` runs automatically when NestJS closes the application context, through its `onModuleDestroy`
|
|
670
|
+
`QueueLifecycle` runs automatically when NestJS closes the application context, through its `onModuleDestroy` and `onApplicationShutdown` hooks (call `app.enableShutdownHooks()` so that a termination signal closes the context).
|
|
664
671
|
It implements a **bounded drain protocol** with at-least-once semantics:
|
|
665
672
|
|
|
666
673
|
1. All workers are closed with a grace period (`drainTimeoutMs`, default 30 s).
|
|
@@ -668,7 +675,11 @@ It implements a **bounded drain protocol** with at-least-once semantics:
|
|
|
668
675
|
timeout fires — whichever comes first.
|
|
669
676
|
3. On timeout, the worker is force-closed (`worker.close(true)`), preserving stalled
|
|
670
677
|
jobs for reprocessing by the next instance.
|
|
671
|
-
4.
|
|
678
|
+
4. Queue-events and the connections the library opened for workers and queue-events are closed.
|
|
679
|
+
5. Once NestJS has stopped the HTTP server (`onApplicationShutdown`), the flow producer, the
|
|
680
|
+
cached queues and, in Mode B, the library's own Redis connection are closed. NestJS runs
|
|
681
|
+
`onModuleDestroy` before it stops the HTTP server, so a request that is still being served can
|
|
682
|
+
enqueue a job after step 4; those resources stay open until the server is down.
|
|
672
683
|
|
|
673
684
|
**Handlers must be idempotent.** Use `jobId`, `deduplication`, or application-level
|
|
674
685
|
idempotency keys to guard against reprocessing a job that was partially handled before
|