@bymax-one/nest-queue 1.2.10 → 1.2.12

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 CHANGED
@@ -11,6 +11,80 @@ versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
11
 
12
12
  ---
13
13
 
14
+ ## [1.2.12] — 2026-11-09
15
+
16
+ No action required. The public API, the declaration files and the peer ranges are unchanged
17
+ (`check-api-surface` reports no difference against 1.2.11); the runtime bundles (`index.mjs`,
18
+ `index.cjs`) differ in code, measured with `diff -r` of a fresh build against the unpacked 1.2.11
19
+ tarball. The package also ships a corrected README.
20
+
21
+ ### Fixed
22
+
23
+ - **An outage on a connection the library opens no longer prints `[ioredis] Unhandled error event`
24
+ to stderr.** In Mode B the owned connection listened for `error` only while it waited for
25
+ `ready`, so until BullMQ built something on it (BullMQ adds its own listener to a client it is
26
+ handed) a later outage reached ioredis with no listener, and ioredis wrote one line per
27
+ reconnect attempt to raw stderr, outside the structured logger. The owned connection now keeps a
28
+ listener for its whole life that logs through the module's logger. The same holds for the
29
+ duplicate opened for each `QueueEvents` (BullMQ listens on its own copy, not on this one) and for
30
+ the probe the Mode A check duplicates. Every error is logged as it arrives, with no throttling.
31
+ A client you pass in Mode A is yours and the library adds no listener to it.
32
+
33
+ ### Documentation
34
+
35
+ - **The README's Mode A example now compiles and runs.** It imported `CACHE_REDIS_CLIENT` from
36
+ `@bymax-one/nest-cache`, a name that package does not export. The example now injects
37
+ `ConnectionManager` and passes `cache.getClient()`, and says that `getClient()` is typed
38
+ `Redis | Cluster` while Mode A takes a standalone `Redis`.
39
+
40
+ ---
41
+
42
+ ## [1.2.11] — 2026-11-06
43
+
44
+ Action required only if you extend or replace `QueueLifecycle`, or rely on the order in which the
45
+ library closes its resources: the producer side now closes after the HTTP server stops, see the Apply
46
+ note. Healthy use is unchanged. The public API and the peer ranges are unchanged; the declaration
47
+ files differ because `QueueLifecycle` now also implements `OnApplicationShutdown` (and in JSDoc).
48
+ Measured with `diff -r` of a fresh build against the unpacked 1.2.10 tarball, the runtime bundles
49
+ (`index.mjs`, `index.cjs`) differ in code.
50
+
51
+ ### Fixed
52
+
53
+ - **A request still being served when the application closed could not enqueue.** NestJS runs every
54
+ `onModuleDestroy` hook before it stops the HTTP server, and `QueueLifecycle` closed the flow
55
+ producer, the cached queues and the library-owned Redis connection in that hook. A request that
56
+ enqueued in the window between the two (a request in flight during `app.close()`, or a
57
+ `SIGTERM` with shutdown hooks enabled) failed with `Connection is closed.` in Mode B. Those three
58
+ resources are now released in `onApplicationShutdown`, which NestJS runs after the server has
59
+ stopped. Consumption still stops first: workers drain within `shutdown.drainTimeoutMs`, then
60
+ queue-events and the connections opened for workers and queue-events close, all in
61
+ `onModuleDestroy`. `shutdown.drainOnShutdown` now runs in `onApplicationShutdown` too, right before
62
+ the queues close, so a job enqueued late is drained with the rest. The log line `Queue shutdown
63
+ complete in Nms (forced N worker(s))` is now written once, at the end, and its time covers both
64
+ hooks. In Mode A the consumer's client is untouched, as before.
65
+
66
+ ### Changed
67
+
68
+ - **The shipped bundles no longer carry comments.** `dist/server/index.mjs` and `index.cjs` are
69
+ smaller; the declaration files keep their JSDoc, and the comment removal does not change the
70
+ runtime behaviour of the bundles.
71
+
72
+ ### Documentation
73
+
74
+ - **The README describes the two shutdown hooks** and when each resource closes.
75
+
76
+ #### Apply to a derived backend
77
+
78
+ - If you subclass or wrap `QueueLifecycle`, or call `onModuleDestroy()` yourself to tear the library
79
+ down in a test, also call `onApplicationShutdown()` afterwards: the flow producer, the cached
80
+ queues and the library-owned connection are no longer closed by `onModuleDestroy()`. Nest calls
81
+ both when the application closes.
82
+ - If you enqueue from a hook that runs after the library's `onModuleDestroy` (for example your own
83
+ `beforeApplicationShutdown`), the enqueue now succeeds instead of failing with
84
+ `Connection is closed.`; if you worked around that failure, the workaround can go.
85
+
86
+ ---
87
+
14
88
  ## [1.2.10] — 2026-11-01
15
89
 
16
90
  Action required only if code relies on the status of a failed schedule: `upsertJobScheduler` no
@@ -821,7 +895,9 @@ v6 peer range.
821
895
 
822
896
  ---
823
897
 
824
- [Unreleased]: https://github.com/bymaxone/nest-queue/compare/v1.2.10...HEAD
898
+ [Unreleased]: https://github.com/bymaxone/nest-queue/compare/v1.2.12...HEAD
899
+ [1.2.12]: https://github.com/bymaxone/nest-queue/compare/v1.2.11...v1.2.12
900
+ [1.2.11]: https://github.com/bymaxone/nest-queue/compare/v1.2.10...v1.2.11
825
901
  [1.2.10]: https://github.com/bymaxone/nest-queue/compare/v1.2.9...v1.2.10
826
902
  [1.2.9]: https://github.com/bymaxone/nest-queue/compare/v1.2.8...v1.2.9
827
903
  [1.2.8]: https://github.com/bymaxone/nest-queue/compare/v1.2.7...v1.2.8
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: after the workers close, 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. |
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
@@ -270,18 +270,22 @@ Worker and QueueEvents connections are duplicated from this client with
270
270
 
271
271
  ```typescript
272
272
  import { BymaxQueueModule } from '@bymax-one/nest-queue'
273
- import { CACHE_REDIS_CLIENT } from '@bymax-one/nest-cache'
273
+ import { ConnectionManager } from '@bymax-one/nest-cache'
274
274
  import type { Redis } from 'ioredis'
275
275
 
276
276
  BymaxQueueModule.forRootAsync({
277
- inject: [CACHE_REDIS_CLIENT],
278
- useFactory: (queueRedis: Redis) => ({
279
- connection: { client: queueRedis },
277
+ inject: [ConnectionManager],
278
+ useFactory: (cache: ConnectionManager) => ({
279
+ connection: { client: cache.getClient() as Redis },
280
280
  isGlobal: true,
281
281
  }),
282
282
  })
283
283
  ```
284
284
 
285
+ `BymaxCacheModule` is global by default and provides `ConnectionManager`; `getClient()` returns the shared
286
+ client. It is typed `Redis | Cluster`, and Mode A takes a standalone `Redis`, so run
287
+ `@bymax-one/nest-cache` in standalone mode.
288
+
285
289
  Mode A is the recommended setup when you already have `@bymax-one/nest-cache` in the app.
286
290
  One client is shared; the lib duplicates it per role and applies the correct retry policy
287
291
  without touching your cache connection.
@@ -667,7 +671,7 @@ BymaxQueueModule.forRoot({
667
671
 
668
672
  ## 🛑 Graceful Shutdown
669
673
 
670
- `QueueLifecycle` runs automatically when NestJS closes the application context, through its `onModuleDestroy` hook (call `app.enableShutdownHooks()` so that a termination signal closes the context).
674
+ `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).
671
675
  It implements a **bounded drain protocol** with at-least-once semantics:
672
676
 
673
677
  1. All workers are closed with a grace period (`drainTimeoutMs`, default 30 s).
@@ -675,7 +679,11 @@ It implements a **bounded drain protocol** with at-least-once semantics:
675
679
  timeout fires — whichever comes first.
676
680
  3. On timeout, the worker is force-closed (`worker.close(true)`), preserving stalled
677
681
  jobs for reprocessing by the next instance.
678
- 4. In Mode B, Redis connections are disconnected after all workers are closed.
682
+ 4. Queue-events and the connections the library opened for workers and queue-events are closed.
683
+ 5. Once NestJS has stopped the HTTP server (`onApplicationShutdown`), the flow producer, the
684
+ cached queues and, in Mode B, the library's own Redis connection are closed. NestJS runs
685
+ `onModuleDestroy` before it stops the HTTP server, so a request that is still being served can
686
+ enqueue a job after step 4; those resources stay open until the server is down.
679
687
 
680
688
  **Handlers must be idempotent.** Use `jobId`, `deduplication`, or application-level
681
689
  idempotency keys to guard against reprocessing a job that was partially handled before