@bymax-one/nest-queue 1.2.7 → 1.2.9

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,46 @@ versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
11
 
12
12
  ---
13
13
 
14
+ ## [1.2.9] — 2026-11-01
15
+
16
+ No action required: behaviour for an `Error` with a string `message` is unchanged, and the public API, the types
17
+ (`index.d.ts` and `index.d.cts` are identical) and the peer ranges are unchanged. Measured with
18
+ `diff -r` of a fresh build against the unpacked 1.2.8 tarball, only the two runtime bundles
19
+ (`index.mjs`, `index.cjs`) differ.
20
+
21
+ ### Fixed
22
+
23
+ - **The fallback `error` listener no longer throws on an emission that is not an `Error`.** The
24
+ listener the library attaches to every `Queue`, `Worker` and `QueueEvents`, and to the `FlowProducer` when flows are enabled, read
25
+ `err.message` directly, so `emit('error', undefined)` or `emit('error', null)` threw a `TypeError`
26
+ out of the listener, and out of `emit` to its caller, which can end the process. It now logs
27
+ whatever value arrived, falling back to a fixed phrase for one it cannot print.
28
+ - **A rejected `enqueueBulk` with a non-`Error` reason still produces `BULK_ENQUEUE_FAILED`.**
29
+ The cause was read with `(err as Error).message`, which threw a `TypeError` for a `null` or
30
+ `undefined` rejection, replacing the typed exception; it now carries a string cause for any
31
+ rejected value.
32
+
33
+ ---
34
+
35
+ ## [1.2.8] — 2026-11-01
36
+
37
+ No action required: behaviour, the public API, the types and the peer ranges are unchanged.
38
+ Measured with `diff -r` of a fresh build against the unpacked 1.2.7 tarball, `dist/` is identical;
39
+ the package otherwise changes in `README.md`, this file and the version in `package.json`.
40
+
41
+ ### Documentation
42
+
43
+ - **The README now states how many Redis connections the library holds.** A new "Redis connections"
44
+ section gives the budget measured against Redis 8: 1 for the module in Mode B and none of its own
45
+ in Mode A, 2 per worker, 2 per queue observed with `@OnQueueEvent`, and none for producers and
46
+ flows, all released on close. It carries a worked example (20 workers and 5 observed queues hold
47
+ 51 connections in Mode B) and a note on connection quotas of managed Redis plans.
48
+ - **The Mode B description of the connections was wrong.** It said the library opens one connection
49
+ per worker and per observed queue; each of those holds two, a blocking connection and a command
50
+ connection.
51
+
52
+ ---
53
+
14
54
  ## [1.2.7] — 2026-10-31
15
55
 
16
56
  Action required if your `connection.url` can be empty or malformed: an invalid or empty
@@ -728,7 +768,9 @@ v6 peer range.
728
768
 
729
769
  ---
730
770
 
731
- [Unreleased]: https://github.com/bymaxone/nest-queue/compare/v1.2.7...HEAD
771
+ [Unreleased]: https://github.com/bymaxone/nest-queue/compare/v1.2.9...HEAD
772
+ [1.2.9]: https://github.com/bymaxone/nest-queue/compare/v1.2.8...v1.2.9
773
+ [1.2.8]: https://github.com/bymaxone/nest-queue/compare/v1.2.7...v1.2.8
732
774
  [1.2.7]: https://github.com/bymaxone/nest-queue/compare/v1.2.6...v1.2.7
733
775
  [1.2.6]: https://github.com/bymaxone/nest-queue/compare/v1.2.5...v1.2.6
734
776
  [1.2.5]: https://github.com/bymaxone/nest-queue/compare/v1.2.4...v1.2.5
package/README.md CHANGED
@@ -313,8 +313,41 @@ BymaxQueueModule.forRoot({
313
313
  })
314
314
  ```
315
315
 
316
- The library opens one connection for queues, one per worker and one per queue that has an
317
- `@OnQueueEvent` listener. The worker and `QueueEvents` connections carry `maxRetriesPerRequest: null`.
316
+ The library opens one connection for all queues and flows, and two for each worker and for each
317
+ queue that has an `@OnQueueEvent` listener ([the budget is below](#-redis-connections)). The worker
318
+ and `QueueEvents` connections carry `maxRetriesPerRequest: null`.
319
+
320
+ ---
321
+
322
+ ## 🔌 Redis connections
323
+
324
+ How many connections the library holds is linear in what you register, so you can budget it before
325
+ you deploy. Counts were read from `INFO clients` against Redis 8 with BullMQ 6 and `ioredis` 6, and
326
+ every connection is released when the application context closes.
327
+
328
+ | What you register | Connections held |
329
+ | ---------------------------------------------- | ------------------------------------------------------------------------------ |
330
+ | The module (Mode B, library-owned connection) | 1, shared by every producer and every flow |
331
+ | The module (Mode A, your client) | 0 of its own; your client is yours and is never counted |
332
+ | A queue you enqueue to, with `QueueService` | 0 more: queues share the module's connection |
333
+ | `FlowService` (flows enabled) | 0 more |
334
+ | Each worker (`@Processor` or `WorkerRegistry`) | 2: a blocking connection that waits for jobs, and a command connection |
335
+ | Each queue observed with `@OnQueueEvent` | 2: a blocking connection that reads the event stream, and a command connection |
336
+
337
+ Producers and flows add nothing, so the cost comes from consumers. The blocking connection of a
338
+ worker cannot be shared: it is parked inside a blocking command until a job arrives.
339
+
340
+ **Worked example.** One application instance in Mode B with 20 workers and 5 queues observed with
341
+ `@OnQueueEvent` holds 1 + (2 × 20) + (2 × 5) = **51** connections; in Mode A the same instance holds
342
+ 50, plus whatever your own client uses. Each instance of the application opens its own set, so three
343
+ instances hold 153.
344
+
345
+ **Managed Redis.** Hosted Redis plans cap the number of concurrent connections, and a plan sized for
346
+ a handful of clients is exhausted by one instance with a few dozen workers. Multiply the table by
347
+ the number of instances you run (include rolling deploys, where old and new instances overlap) and
348
+ compare it with the limit of your plan; self-managed Redis allows 10 000 by default (`maxclients`).
349
+ In `CLIENT LIST`, the blocking connection of a worker has a name that starts with
350
+ `<prefix>:<base64 of the queue name>`, which identifies it; the command connection is unnamed.
318
351
 
319
352
  ---
320
353
 
@@ -368,10 +368,22 @@ function validateJobSchedulerOptions(repeat) {
368
368
  }
369
369
  }
370
370
 
371
+ // src/server/utils/describe-error.ts
372
+ function describeError(cause) {
373
+ try {
374
+ if (typeof cause === "object" && cause !== null && "message" in cause) {
375
+ if (typeof cause.message === "string") return cause.message;
376
+ }
377
+ return String(cause);
378
+ } catch {
379
+ return "unknown error";
380
+ }
381
+ }
382
+
371
383
  // src/server/utils/attach-error-listener.ts
372
384
  function attachDefaultErrorListener(emitter, logger, kind, queueName) {
373
385
  emitter.on("error", (err) => {
374
- logger.error(`${kind} for queue "${queueName}" emitted an error: ${err.message}`);
386
+ logger.error(`${kind} for queue "${queueName}" emitted an error: ${describeError(err)}`);
375
387
  });
376
388
  }
377
389
 
@@ -503,7 +515,7 @@ exports.QueueService = class QueueService {
503
515
  QUEUE_ERROR_CODES.BULK_ENQUEUE_FAILED,
504
516
  common.HttpStatus.INTERNAL_SERVER_ERROR,
505
517
  {
506
- cause: err.message
518
+ cause: describeError(err)
507
519
  }
508
520
  );
509
521
  }
@@ -809,20 +821,6 @@ exports.MetricsService = class MetricsService {
809
821
  exports.MetricsService = __decorateClass([
810
822
  common.Injectable()
811
823
  ], exports.MetricsService);
812
-
813
- // src/server/utils/describe-error.ts
814
- function describeError(cause) {
815
- try {
816
- if (typeof cause === "object" && cause !== null && "message" in cause) {
817
- if (typeof cause.message === "string") return cause.message;
818
- }
819
- return String(cause);
820
- } catch {
821
- return "unknown error";
822
- }
823
- }
824
-
825
- // src/server/services/worker-registry.service.ts
826
824
  var _entries;
827
825
  exports.WorkerRegistry = class WorkerRegistry {
828
826
  constructor(connection, options) {
@@ -366,10 +366,22 @@ function validateJobSchedulerOptions(repeat) {
366
366
  }
367
367
  }
368
368
 
369
+ // src/server/utils/describe-error.ts
370
+ function describeError(cause) {
371
+ try {
372
+ if (typeof cause === "object" && cause !== null && "message" in cause) {
373
+ if (typeof cause.message === "string") return cause.message;
374
+ }
375
+ return String(cause);
376
+ } catch {
377
+ return "unknown error";
378
+ }
379
+ }
380
+
369
381
  // src/server/utils/attach-error-listener.ts
370
382
  function attachDefaultErrorListener(emitter, logger, kind, queueName) {
371
383
  emitter.on("error", (err) => {
372
- logger.error(`${kind} for queue "${queueName}" emitted an error: ${err.message}`);
384
+ logger.error(`${kind} for queue "${queueName}" emitted an error: ${describeError(err)}`);
373
385
  });
374
386
  }
375
387
 
@@ -501,7 +513,7 @@ var QueueService = class {
501
513
  QUEUE_ERROR_CODES.BULK_ENQUEUE_FAILED,
502
514
  HttpStatus.INTERNAL_SERVER_ERROR,
503
515
  {
504
- cause: err.message
516
+ cause: describeError(err)
505
517
  }
506
518
  );
507
519
  }
@@ -807,20 +819,6 @@ var MetricsService = class {
807
819
  MetricsService = __decorateClass([
808
820
  Injectable()
809
821
  ], MetricsService);
810
-
811
- // src/server/utils/describe-error.ts
812
- function describeError(cause) {
813
- try {
814
- if (typeof cause === "object" && cause !== null && "message" in cause) {
815
- if (typeof cause.message === "string") return cause.message;
816
- }
817
- return String(cause);
818
- } catch {
819
- return "unknown error";
820
- }
821
- }
822
-
823
- // src/server/services/worker-registry.service.ts
824
822
  var _entries;
825
823
  var WorkerRegistry = class {
826
824
  constructor(connection, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-queue",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "NestJS dynamic module wrapping BullMQ — typed jobs, flows, job schedulers, deduplication, OpenTelemetry, graceful shutdown",
5
5
  "author": "Bymax One <support@bymax.one>",
6
6
  "license": "MIT",