@bymax-one/nest-queue 1.0.7 → 1.0.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 +38 -0
- package/README.md +20 -12
- package/dist/server/index.cjs +83 -43
- package/dist/server/index.d.cts +4 -6
- package/dist/server/index.d.ts +4 -6
- package/dist/server/index.mjs +83 -43
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.0.9] — 2026-08-06
|
|
11
|
+
|
|
12
|
+
**Published-artifact change, not a behavioural one.** `dist/` differs from `1.0.8` — this
|
|
13
|
+
bundler preserves comments and the source gained mutation-suppression notes — but no runtime
|
|
14
|
+
path changed. Measured by building both revisions and diffing the output.
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- The mutation badge and README said **99.06%**; the measured score is **99.68%**.
|
|
19
|
+
|
|
20
|
+
### Tests
|
|
21
|
+
|
|
22
|
+
- The withheld Redis connection now has the shape of its guarantee asserted: that the property is
|
|
23
|
+
non-enumerable, that redefining it throws, and that the URL's password stays out of both
|
|
24
|
+
`JSON.stringify` and `inspect({ showHidden: true })`.
|
|
25
|
+
|
|
26
|
+
## [1.0.8] — 2026-08-04
|
|
27
|
+
|
|
28
|
+
**Runtime change.** `dist/` differs from `1.0.7`.
|
|
29
|
+
|
|
30
|
+
### Security
|
|
31
|
+
|
|
32
|
+
- The Redis credentials are no longer disclosed when a service that holds them is
|
|
33
|
+
serialized. `connection` moves from a plain field on the resolved options to a
|
|
34
|
+
non-enumerable accessor, and `ConnectionResolver` keeps the ioredis client and the
|
|
35
|
+
consumer's module options in ECMAScript private fields, as do the maps holding the
|
|
36
|
+
`Queue`, `Worker` and `QueueEvents` instances. Those objects were all reachable by
|
|
37
|
+
walking a service: a `url` carries the password inline, and an ioredis instance carries
|
|
38
|
+
`options.password` as a plain field, so `JSON.stringify`, object spread and
|
|
39
|
+
`util.inspect` on `QueueService` emitted the password in plaintext — which is what a
|
|
40
|
+
structured logger does when it renders a provider it was handed, and what an error
|
|
41
|
+
reporter does when it captures the scope of a throw.
|
|
42
|
+
|
|
43
|
+
Reading on purpose is unchanged: `options.connection` resolves as before, and no public
|
|
44
|
+
type or export changed.
|
|
45
|
+
|
|
10
46
|
## [1.0.7] — 2026-08-04
|
|
11
47
|
|
|
12
48
|
**Runtime change.** `dist/` differs from `1.0.6`: the four decorators no longer carry
|
|
@@ -369,6 +405,8 @@ v6 peer range.
|
|
|
369
405
|
|
|
370
406
|
---
|
|
371
407
|
|
|
408
|
+
[1.0.9]: https://github.com/bymaxone/nest-queue/compare/v1.0.8...v1.0.9
|
|
409
|
+
[1.0.8]: https://github.com/bymaxone/nest-queue/compare/v1.0.7...v1.0.8
|
|
372
410
|
[1.0.7]: https://github.com/bymaxone/nest-queue/compare/v1.0.6...v1.0.7
|
|
373
411
|
[1.0.6]: https://github.com/bymaxone/nest-queue/releases/tag/v1.0.6
|
|
374
412
|
[1.0.5]: https://github.com/bymaxone/nest-queue/releases/tag/v1.0.5
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<a href="https://www.npmjs.com/package/@bymax-one/nest-queue"><img src="https://img.shields.io/npm/dm/@bymax-one/nest-queue?style=flat-square&colorA=000000&colorB=000000" alt="npm downloads" /></a>
|
|
15
15
|
<a href="https://github.com/bymaxone/nest-queue/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/bymaxone/nest-queue/ci.yml?branch=main&style=flat-square&colorA=000000&label=CI" alt="CI status" /></a>
|
|
16
16
|
<a href="https://github.com/bymaxone/nest-queue/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square&colorA=000000" alt="coverage" /></a>
|
|
17
|
-
<a href="https://github.com/bymaxone/nest-queue/blob/main/docs/mutation_testing_results.md"><img src="https://img.shields.io/badge/mutation-99.
|
|
17
|
+
<a href="https://github.com/bymaxone/nest-queue/blob/main/docs/mutation_testing_results.md"><img src="https://img.shields.io/badge/mutation-99.68%25-brightgreen?style=flat-square&colorA=000000" alt="mutation score" /></a>
|
|
18
18
|
<a href="https://scorecard.dev/viewer/?uri=github.com/bymaxone/nest-queue"><img src="https://api.scorecard.dev/projects/github.com/bymaxone/nest-queue/badge?style=flat-square" alt="OpenSSF Scorecard" /></a>
|
|
19
19
|
<a href="https://github.com/bymaxone/nest-queue/blob/main/LICENSE"><img src="https://img.shields.io/github/license/bymaxone/nest-queue?style=flat-square&colorA=000000&colorB=000000" alt="license" /></a>
|
|
20
20
|
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" /></a>
|
|
@@ -711,6 +711,14 @@ message, or an exception payload. `QueueException.details` carries only scalar
|
|
|
711
711
|
configuration values (`{ actualValue, expectedValue, limit, received }`), never a
|
|
712
712
|
connection object and never `job.data`.
|
|
713
713
|
|
|
714
|
+
They are also not reachable by serializing the objects that hold them. The connection is
|
|
715
|
+
attached to the resolved options as a non-enumerable accessor, and the resolver keeps the
|
|
716
|
+
ioredis client — which carries `options.password` as a plain field — in a private field,
|
|
717
|
+
as do the registries that hold the `Queue`, `Worker` and `QueueEvents` instances. So
|
|
718
|
+
`JSON.stringify`, object spread, `util.inspect` and `util.inspect` with `showHidden` all
|
|
719
|
+
come back without credentials, which is what matters when a structured logger renders a
|
|
720
|
+
provider it was handed or an error reporter captures the scope of a throw.
|
|
721
|
+
|
|
714
722
|
### Job data is opaque
|
|
715
723
|
|
|
716
724
|
The library treats `job.data` as a payload to transport, never to inspect. It is never
|
|
@@ -755,16 +763,16 @@ key will do it twice at some point.
|
|
|
755
763
|
|
|
756
764
|
## 🛡️ Security Table
|
|
757
765
|
|
|
758
|
-
| Layer | Implementation
|
|
759
|
-
| ----------------- |
|
|
760
|
-
| Credentials | Injected options only; never read from `process.env`, never logged, never placed in an exception
|
|
761
|
-
| Error payloads | `QueueException.details` restricted to scalar configuration values — no `job.data`, no connection object
|
|
762
|
-
| Job payloads | Treated as opaque; never deep-merged (prototype-pollution guard), never logged
|
|
763
|
-
| Namespacing | `prefix` applied uniformly to `Queue`, `Worker`, `QueueEvents` and `FlowProducer`
|
|
764
|
-
| Connection policy | `maxRetriesPerRequest: null` confined to duplicated consumer connections; producers keep fail-fast retries
|
|
765
|
-
| Delivery | At-least-once with a bounded drain; unacknowledged work is re-queued by BullMQ's stalled check
|
|
766
|
-
| Supply chain | `dependencies: {}`; SHA-pinned Actions, OSV-Scanner, TruffleHog, OpenSSF Scorecard; npm publish over OIDC
|
|
767
|
-
| Input validation | Options validated at bootstrap; cron patterns validated by BullMQ's own parser, never a hand-rolled regex
|
|
766
|
+
| Layer | Implementation |
|
|
767
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
768
|
+
| Credentials | Injected options only; never read from `process.env`, never logged, never placed in an exception; held in a non-enumerable accessor and private fields, so serializing a service omits them |
|
|
769
|
+
| Error payloads | `QueueException.details` restricted to scalar configuration values — no `job.data`, no connection object |
|
|
770
|
+
| Job payloads | Treated as opaque; never deep-merged (prototype-pollution guard), never logged |
|
|
771
|
+
| Namespacing | `prefix` applied uniformly to `Queue`, `Worker`, `QueueEvents` and `FlowProducer` |
|
|
772
|
+
| Connection policy | `maxRetriesPerRequest: null` confined to duplicated consumer connections; producers keep fail-fast retries |
|
|
773
|
+
| Delivery | At-least-once with a bounded drain; unacknowledged work is re-queued by BullMQ's stalled check |
|
|
774
|
+
| Supply chain | `dependencies: {}`; SHA-pinned Actions, OSV-Scanner, TruffleHog, OpenSSF Scorecard; npm publish over OIDC |
|
|
775
|
+
| Input validation | Options validated at bootstrap; cron patterns validated by BullMQ's own parser, never a hand-rolled regex |
|
|
768
776
|
|
|
769
777
|
> [!IMPORTANT]
|
|
770
778
|
> **Delivery is at-least-once, and `prefix` is not an access boundary.** Handlers must be
|
|
@@ -788,7 +796,7 @@ key will do it twice at some point.
|
|
|
788
796
|
## 🧪 Testing & Quality
|
|
789
797
|
|
|
790
798
|
- **100% line/branch coverage** on every implemented file (hard CI gate).
|
|
791
|
-
- **Mutation score 99.
|
|
799
|
+
- **Mutation score 99.68%** (Stryker `break 95`) — see [`docs/mutation_testing_results.md`](docs/mutation_testing_results.md).
|
|
792
800
|
- **Real-Redis E2E** via Testcontainers (workers, flows, schedulers, deduplication, retries,
|
|
793
801
|
DLQ, graceful shutdown).
|
|
794
802
|
- **Type-level API tests** (`pnpm test:types`) pin the published signatures, so a refactor
|
package/dist/server/index.cjs
CHANGED
|
@@ -6,7 +6,12 @@ var ioredis = require('ioredis');
|
|
|
6
6
|
var bullmq = require('bullmq');
|
|
7
7
|
var path = require('path');
|
|
8
8
|
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
9
10
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __typeError = (msg) => {
|
|
12
|
+
throw TypeError(msg);
|
|
13
|
+
};
|
|
14
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
10
15
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
11
16
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
12
17
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
@@ -15,6 +20,11 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
15
20
|
return result;
|
|
16
21
|
};
|
|
17
22
|
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
23
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
24
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
25
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
26
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
27
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
|
|
18
28
|
|
|
19
29
|
// src/shared/constants/error-codes.ts
|
|
20
30
|
var QUEUE_ERROR_CODES = {
|
|
@@ -111,8 +121,8 @@ var DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 3e4;
|
|
|
111
121
|
|
|
112
122
|
// src/server/config/resolved-options.ts
|
|
113
123
|
function applyDefaults(opts) {
|
|
124
|
+
const connection = opts.connection;
|
|
114
125
|
const base = {
|
|
115
|
-
connection: opts.connection,
|
|
116
126
|
defaultJobOptions: { ...DEFAULT_JOB_OPTIONS, ...opts.defaultJobOptions ?? {} },
|
|
117
127
|
prefix: opts.prefix ?? "bull",
|
|
118
128
|
queueOptions: opts.queueOptions ?? {},
|
|
@@ -128,6 +138,12 @@ function applyDefaults(opts) {
|
|
|
128
138
|
connectionReadyTimeoutMs: opts.connectionReadyTimeoutMs ?? DEFAULT_CONNECTION_READY_TIMEOUT_MS
|
|
129
139
|
};
|
|
130
140
|
if (opts.telemetry !== void 0) base.telemetry = opts.telemetry;
|
|
141
|
+
Object.defineProperty(base, "connection", {
|
|
142
|
+
get: () => connection,
|
|
143
|
+
enumerable: false,
|
|
144
|
+
// Stryker disable next-line BooleanLiteral: equivalent HERE — this object is `Object.freeze`d on the way out, and freezing makes every property non-configurable regardless. The flag stays because it states the guarantee at the point the accessor is defined, and because the sibling packages that withhold secrets the same way are NOT frozen, where it is load-bearing
|
|
145
|
+
configurable: false
|
|
146
|
+
});
|
|
131
147
|
return Object.freeze(base);
|
|
132
148
|
}
|
|
133
149
|
|
|
@@ -157,34 +173,44 @@ function duplicateConnection(client) {
|
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
// src/server/services/connection-resolver.service.ts
|
|
176
|
+
var _client, _options;
|
|
160
177
|
exports.ConnectionResolver = class ConnectionResolver {
|
|
161
178
|
constructor(options) {
|
|
162
|
-
|
|
179
|
+
/**
|
|
180
|
+
* The resolved Redis client.
|
|
181
|
+
*
|
|
182
|
+
* An ECMAScript private field rather than a TypeScript `private` one, which
|
|
183
|
+
* is erased at runtime: an ioredis instance carries `options.password` as a
|
|
184
|
+
* plain field, so leaving this enumerable would let anything that serializes
|
|
185
|
+
* a service holding this resolver walk into the credentials.
|
|
186
|
+
*/
|
|
187
|
+
__privateAdd(this, _client);
|
|
188
|
+
/** The consumer's module options, which carry the Redis credentials. */
|
|
189
|
+
__privateAdd(this, _options);
|
|
190
|
+
__publicField(this, "mode");
|
|
191
|
+
__privateSet(this, _options, options);
|
|
163
192
|
}
|
|
164
|
-
options;
|
|
165
|
-
client;
|
|
166
|
-
mode;
|
|
167
193
|
/** Resolve and validate the connection. Call once during module bootstrap. */
|
|
168
194
|
async init() {
|
|
169
|
-
const cfg = this.
|
|
195
|
+
const cfg = __privateGet(this, _options).connection;
|
|
170
196
|
if ("client" in cfg) {
|
|
171
197
|
this.initModeA(cfg.client);
|
|
172
198
|
return;
|
|
173
199
|
}
|
|
174
200
|
this.mode = "mode-b-owned";
|
|
175
|
-
this
|
|
201
|
+
__privateSet(this, _client, "url" in cfg ? new ioredis.Redis(cfg.url, { ...cfg.options ?? {}, lazyConnect: false }) : new ioredis.Redis({ ...cfg.options, lazyConnect: false }));
|
|
176
202
|
await this.waitReady(
|
|
177
|
-
this.
|
|
203
|
+
__privateGet(this, _options).connectionReadyTimeoutMs ?? DEFAULT_CONNECTION_READY_TIMEOUT_MS
|
|
178
204
|
);
|
|
179
205
|
}
|
|
180
206
|
/** The resolved Queue-role client. */
|
|
181
207
|
getClient() {
|
|
182
|
-
if (!this
|
|
208
|
+
if (!__privateGet(this, _client)) {
|
|
183
209
|
throw new QueueException(QUEUE_ERROR_CODES.CONNECTION_INVALID, 500, {
|
|
184
210
|
reason: "not initialized"
|
|
185
211
|
});
|
|
186
212
|
}
|
|
187
|
-
return this
|
|
213
|
+
return __privateGet(this, _client);
|
|
188
214
|
}
|
|
189
215
|
/** The resolved connection mode. */
|
|
190
216
|
getMode() {
|
|
@@ -201,8 +227,8 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
201
227
|
}
|
|
202
228
|
/** Close the library-owned connection on shutdown; never touch a BYO client. */
|
|
203
229
|
async teardown() {
|
|
204
|
-
if (this.isOwned() && this
|
|
205
|
-
const client = this
|
|
230
|
+
if (this.isOwned() && __privateGet(this, _client)) {
|
|
231
|
+
const client = __privateGet(this, _client);
|
|
206
232
|
await client.quit().catch(() => {
|
|
207
233
|
client.disconnect();
|
|
208
234
|
});
|
|
@@ -211,7 +237,7 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
211
237
|
/** Validate and adopt a bring-your-own client for the Queue role. */
|
|
212
238
|
initModeA(client) {
|
|
213
239
|
this.mode = "mode-a-byo";
|
|
214
|
-
this
|
|
240
|
+
__privateSet(this, _client, client);
|
|
215
241
|
if (!isClientUsable(client)) {
|
|
216
242
|
throw new QueueException(QUEUE_ERROR_CODES.CONNECTION_INVALID, 500, { status: client.status });
|
|
217
243
|
}
|
|
@@ -249,6 +275,8 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
249
275
|
});
|
|
250
276
|
}
|
|
251
277
|
};
|
|
278
|
+
_client = new WeakMap();
|
|
279
|
+
_options = new WeakMap();
|
|
252
280
|
exports.ConnectionResolver = __decorateClass([
|
|
253
281
|
common.Injectable(),
|
|
254
282
|
__decorateParam(0, common.Inject(BYMAX_QUEUE_OPTIONS))
|
|
@@ -297,15 +325,16 @@ var MAX_BULK_SIZE = 1e3;
|
|
|
297
325
|
function isOperationalError(err) {
|
|
298
326
|
return typeof err?.code === "string";
|
|
299
327
|
}
|
|
328
|
+
var _queues;
|
|
300
329
|
exports.QueueService = class QueueService {
|
|
301
330
|
constructor(connection, options) {
|
|
302
331
|
this.connection = connection;
|
|
303
332
|
this.options = options;
|
|
333
|
+
__publicField(this, "logger", new common.Logger(exports.QueueService.name));
|
|
334
|
+
__privateAdd(this, _queues, /* @__PURE__ */ new Map());
|
|
304
335
|
}
|
|
305
336
|
connection;
|
|
306
337
|
options;
|
|
307
|
-
logger = new common.Logger(exports.QueueService.name);
|
|
308
|
-
queues = /* @__PURE__ */ new Map();
|
|
309
338
|
/**
|
|
310
339
|
* Return the cached `Queue` for `queueName`, or create one with the module
|
|
311
340
|
* defaults applied. Subsequent calls with the same name return the same
|
|
@@ -315,7 +344,7 @@ exports.QueueService = class QueueService {
|
|
|
315
344
|
* @param overrides - Optional per-queue option overrides.
|
|
316
345
|
*/
|
|
317
346
|
getOrCreateQueue(queueName, overrides) {
|
|
318
|
-
const existing = this.
|
|
347
|
+
const existing = __privateGet(this, _queues).get(queueName);
|
|
319
348
|
if (existing) return existing;
|
|
320
349
|
const queue = new bullmq.Queue(queueName, {
|
|
321
350
|
connection: this.connection.getClient(),
|
|
@@ -326,7 +355,7 @@ exports.QueueService = class QueueService {
|
|
|
326
355
|
...overrides
|
|
327
356
|
});
|
|
328
357
|
attachDefaultErrorListener(queue, this.logger, "Queue", queueName);
|
|
329
|
-
this.
|
|
358
|
+
__privateGet(this, _queues).set(queueName, queue);
|
|
330
359
|
return queue;
|
|
331
360
|
}
|
|
332
361
|
/** Resolve the raw cached queue (default generics) for internal delegation. */
|
|
@@ -530,7 +559,7 @@ exports.QueueService = class QueueService {
|
|
|
530
559
|
}
|
|
531
560
|
/** Return a read-only view of the cached queues. */
|
|
532
561
|
getCachedQueues() {
|
|
533
|
-
return this
|
|
562
|
+
return __privateGet(this, _queues);
|
|
534
563
|
}
|
|
535
564
|
/**
|
|
536
565
|
* Close every cached queue (swallowing per-queue errors) and clear the cache.
|
|
@@ -542,12 +571,13 @@ exports.QueueService = class QueueService {
|
|
|
542
571
|
* exists to prevent. `QueueLifecycle` is the only caller.
|
|
543
572
|
*/
|
|
544
573
|
async closeAll() {
|
|
545
|
-
for (const queue of this.
|
|
574
|
+
for (const queue of __privateGet(this, _queues).values()) {
|
|
546
575
|
await queue.close().catch(() => void 0);
|
|
547
576
|
}
|
|
548
|
-
this.
|
|
577
|
+
__privateGet(this, _queues).clear();
|
|
549
578
|
}
|
|
550
579
|
};
|
|
580
|
+
_queues = new WeakMap();
|
|
551
581
|
exports.QueueService = __decorateClass([
|
|
552
582
|
common.Injectable(),
|
|
553
583
|
__decorateParam(0, common.Inject(exports.ConnectionResolver)),
|
|
@@ -704,15 +734,16 @@ exports.MetricsService = class MetricsService {
|
|
|
704
734
|
exports.MetricsService = __decorateClass([
|
|
705
735
|
common.Injectable()
|
|
706
736
|
], exports.MetricsService);
|
|
737
|
+
var _entries;
|
|
707
738
|
exports.WorkerRegistry = class WorkerRegistry {
|
|
708
739
|
constructor(connection, options) {
|
|
709
740
|
this.connection = connection;
|
|
710
741
|
this.options = options;
|
|
742
|
+
__publicField(this, "logger", new common.Logger(exports.WorkerRegistry.name));
|
|
743
|
+
__privateAdd(this, _entries, /* @__PURE__ */ new Map());
|
|
711
744
|
}
|
|
712
745
|
connection;
|
|
713
746
|
options;
|
|
714
|
-
logger = new common.Logger(exports.WorkerRegistry.name);
|
|
715
|
-
entries = /* @__PURE__ */ new Map();
|
|
716
747
|
/**
|
|
717
748
|
* Register an in-process worker with a handler function. The BullMQ `Worker`
|
|
718
749
|
* constructor is synchronous — no Redis handshake is performed at registration
|
|
@@ -743,7 +774,7 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
743
774
|
});
|
|
744
775
|
}
|
|
745
776
|
attachDefaultErrorListener(worker, this.logger, "Worker", queueName);
|
|
746
|
-
this.
|
|
777
|
+
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
747
778
|
return worker;
|
|
748
779
|
}
|
|
749
780
|
/**
|
|
@@ -782,7 +813,7 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
782
813
|
});
|
|
783
814
|
}
|
|
784
815
|
attachDefaultErrorListener(worker, this.logger, "Worker", queueName);
|
|
785
|
-
this.
|
|
816
|
+
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
786
817
|
return worker;
|
|
787
818
|
}
|
|
788
819
|
/**
|
|
@@ -793,11 +824,11 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
793
824
|
* @param queueName - The queue whose worker should be removed.
|
|
794
825
|
*/
|
|
795
826
|
async unregister(queueName) {
|
|
796
|
-
const entry = this.
|
|
827
|
+
const entry = __privateGet(this, _entries).get(queueName);
|
|
797
828
|
if (!entry) return;
|
|
798
829
|
await entry.worker.close();
|
|
799
830
|
await this.closeConnection(entry.connection);
|
|
800
|
-
this.
|
|
831
|
+
__privateGet(this, _entries).delete(queueName);
|
|
801
832
|
}
|
|
802
833
|
/**
|
|
803
834
|
* Returns the queue names of all currently registered workers.
|
|
@@ -805,7 +836,7 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
805
836
|
* @returns An immutable array of queue names.
|
|
806
837
|
*/
|
|
807
838
|
list() {
|
|
808
|
-
return Array.from(this.
|
|
839
|
+
return Array.from(__privateGet(this, _entries).keys());
|
|
809
840
|
}
|
|
810
841
|
/**
|
|
811
842
|
* Returns a read-only view of the registered workers keyed by queue name.
|
|
@@ -815,7 +846,7 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
815
846
|
*/
|
|
816
847
|
getAll() {
|
|
817
848
|
return new Map(
|
|
818
|
-
Array.from(this
|
|
849
|
+
Array.from(__privateGet(this, _entries), ([name, entry]) => [name, entry.worker])
|
|
819
850
|
);
|
|
820
851
|
}
|
|
821
852
|
/**
|
|
@@ -828,7 +859,7 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
828
859
|
*/
|
|
829
860
|
getConnections() {
|
|
830
861
|
return new Map(
|
|
831
|
-
Array.from(this
|
|
862
|
+
Array.from(__privateGet(this, _entries), ([name, entry]) => [name, entry.connection])
|
|
832
863
|
);
|
|
833
864
|
}
|
|
834
865
|
/**
|
|
@@ -851,7 +882,7 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
851
882
|
* @throws {QueueException} `DUPLICATE_PROCESSOR` if the queue is already registered.
|
|
852
883
|
*/
|
|
853
884
|
guardDuplicate(queueName) {
|
|
854
|
-
if (this.
|
|
885
|
+
if (__privateGet(this, _entries).has(queueName)) {
|
|
855
886
|
throw new QueueException(QUEUE_ERROR_CODES.DUPLICATE_PROCESSOR, 500, { queueName });
|
|
856
887
|
}
|
|
857
888
|
}
|
|
@@ -960,21 +991,23 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
960
991
|
return result;
|
|
961
992
|
}
|
|
962
993
|
};
|
|
994
|
+
_entries = new WeakMap();
|
|
963
995
|
exports.WorkerRegistry = __decorateClass([
|
|
964
996
|
common.Injectable(),
|
|
965
997
|
__decorateParam(0, common.Inject(exports.ConnectionResolver)),
|
|
966
998
|
__decorateParam(1, common.Inject(BYMAX_QUEUE_RESOLVED_OPTIONS))
|
|
967
999
|
], exports.WorkerRegistry);
|
|
1000
|
+
var _events, _connections;
|
|
968
1001
|
exports.QueueEventsRegistry = class QueueEventsRegistry {
|
|
969
1002
|
constructor(connection, options) {
|
|
970
1003
|
this.connection = connection;
|
|
971
1004
|
this.options = options;
|
|
1005
|
+
__publicField(this, "logger", new common.Logger(exports.QueueEventsRegistry.name));
|
|
1006
|
+
__privateAdd(this, _events, /* @__PURE__ */ new Map());
|
|
1007
|
+
__privateAdd(this, _connections, /* @__PURE__ */ new Map());
|
|
972
1008
|
}
|
|
973
1009
|
connection;
|
|
974
1010
|
options;
|
|
975
|
-
logger = new common.Logger(exports.QueueEventsRegistry.name);
|
|
976
|
-
events = /* @__PURE__ */ new Map();
|
|
977
|
-
connections = /* @__PURE__ */ new Map();
|
|
978
1011
|
/**
|
|
979
1012
|
* Returns the cached `QueueEvents` for `queueName`, creating one on first
|
|
980
1013
|
* call. Subsequent calls for the same name return the same instance without
|
|
@@ -984,7 +1017,7 @@ exports.QueueEventsRegistry = class QueueEventsRegistry {
|
|
|
984
1017
|
* @returns The `QueueEvents` instance for the queue.
|
|
985
1018
|
*/
|
|
986
1019
|
getOrCreate(queueName) {
|
|
987
|
-
const existing = this.
|
|
1020
|
+
const existing = __privateGet(this, _events).get(queueName);
|
|
988
1021
|
if (existing) return existing;
|
|
989
1022
|
const conn = duplicateConnection(this.connection.getClient());
|
|
990
1023
|
let qe;
|
|
@@ -995,8 +1028,8 @@ exports.QueueEventsRegistry = class QueueEventsRegistry {
|
|
|
995
1028
|
throw err;
|
|
996
1029
|
}
|
|
997
1030
|
attachDefaultErrorListener(qe, this.logger, "QueueEvents", queueName);
|
|
998
|
-
this.
|
|
999
|
-
this.
|
|
1031
|
+
__privateGet(this, _events).set(queueName, qe);
|
|
1032
|
+
__privateGet(this, _connections).set(queueName, conn);
|
|
1000
1033
|
return qe;
|
|
1001
1034
|
}
|
|
1002
1035
|
/**
|
|
@@ -1005,7 +1038,7 @@ exports.QueueEventsRegistry = class QueueEventsRegistry {
|
|
|
1005
1038
|
* @returns An immutable array of queue names.
|
|
1006
1039
|
*/
|
|
1007
1040
|
list() {
|
|
1008
|
-
return Array.from(this.
|
|
1041
|
+
return Array.from(__privateGet(this, _events).keys());
|
|
1009
1042
|
}
|
|
1010
1043
|
/**
|
|
1011
1044
|
* Returns the live `QueueEvents` map. Intended for the shutdown orchestrator.
|
|
@@ -1013,7 +1046,7 @@ exports.QueueEventsRegistry = class QueueEventsRegistry {
|
|
|
1013
1046
|
* @returns A read-only view of the internal map.
|
|
1014
1047
|
*/
|
|
1015
1048
|
getAll() {
|
|
1016
|
-
return this
|
|
1049
|
+
return __privateGet(this, _events);
|
|
1017
1050
|
}
|
|
1018
1051
|
/**
|
|
1019
1052
|
* Returns a read-only view of the duplicated connections the library opened
|
|
@@ -1029,9 +1062,11 @@ exports.QueueEventsRegistry = class QueueEventsRegistry {
|
|
|
1029
1062
|
* @returns A read-only snapshot of queue name to duplicated connection.
|
|
1030
1063
|
*/
|
|
1031
1064
|
getConnections() {
|
|
1032
|
-
return new Map(this
|
|
1065
|
+
return new Map(__privateGet(this, _connections));
|
|
1033
1066
|
}
|
|
1034
1067
|
};
|
|
1068
|
+
_events = new WeakMap();
|
|
1069
|
+
_connections = new WeakMap();
|
|
1035
1070
|
exports.QueueEventsRegistry = __decorateClass([
|
|
1036
1071
|
common.Injectable(),
|
|
1037
1072
|
__decorateParam(0, common.Inject(exports.ConnectionResolver)),
|
|
@@ -1299,10 +1334,15 @@ QueueLifecycle = __decorateClass([
|
|
|
1299
1334
|
], QueueLifecycle);
|
|
1300
1335
|
|
|
1301
1336
|
// src/server/bymax-queue.module.ts
|
|
1302
|
-
var { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } =
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1337
|
+
var { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = (
|
|
1338
|
+
// The `moduleName` shapes the generated token's description only — a label read in Nest's DI
|
|
1339
|
+
// error output, never compared against anything. Mutation testing reports it as surviving for
|
|
1340
|
+
// that reason; no Stryker directive attaches inside this builder chain, so the note lives here.
|
|
1341
|
+
new common.ConfigurableModuleBuilder({ moduleName: "BymaxQueue" }).setClassMethodName("forRoot").setFactoryMethodName("createQueueOptions").setExtras({ isGlobal: true }, (definition, extras) => ({
|
|
1342
|
+
...definition,
|
|
1343
|
+
global: extras.isGlobal
|
|
1344
|
+
})).build()
|
|
1345
|
+
);
|
|
1306
1346
|
exports.BymaxQueueModule = class BymaxQueueModule extends ConfigurableModuleClass {
|
|
1307
1347
|
/**
|
|
1308
1348
|
* Synchronous registration. Validates options, resolves defaults, and wires the
|
package/dist/server/index.d.cts
CHANGED
|
@@ -354,8 +354,7 @@ type JobSchedulerRepeatOptions = {
|
|
|
354
354
|
* for `ready`, closing it on shutdown.
|
|
355
355
|
*/
|
|
356
356
|
declare class ConnectionResolver {
|
|
357
|
-
private
|
|
358
|
-
private client;
|
|
357
|
+
#private;
|
|
359
358
|
private mode;
|
|
360
359
|
constructor(options: BymaxQueueModuleOptions);
|
|
361
360
|
/** Resolve and validate the connection. Call once during module bootstrap. */
|
|
@@ -390,10 +389,10 @@ type CleanableStatus = 'completed' | 'failed' | 'delayed' | 'wait' | 'active' |
|
|
|
390
389
|
* enqueueing, inspection, metrics, and lifecycle control.
|
|
391
390
|
*/
|
|
392
391
|
declare class QueueService {
|
|
392
|
+
#private;
|
|
393
393
|
private readonly connection;
|
|
394
394
|
private readonly options;
|
|
395
395
|
private readonly logger;
|
|
396
|
-
private readonly queues;
|
|
397
396
|
constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
|
|
398
397
|
/**
|
|
399
398
|
* Return the cached `Queue` for `queueName`, or create one with the module
|
|
@@ -792,10 +791,10 @@ interface SandboxedWorkerConfig {
|
|
|
792
791
|
* })
|
|
793
792
|
*/
|
|
794
793
|
declare class WorkerRegistry {
|
|
794
|
+
#private;
|
|
795
795
|
private readonly connection;
|
|
796
796
|
private readonly options;
|
|
797
797
|
private readonly logger;
|
|
798
|
-
private readonly entries;
|
|
799
798
|
constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
|
|
800
799
|
/**
|
|
801
800
|
* Register an in-process worker with a handler function. The BullMQ `Worker`
|
|
@@ -937,11 +936,10 @@ declare class WorkerRegistry {
|
|
|
937
936
|
* qe.on('completed', ({ jobId }) => console.log(jobId))
|
|
938
937
|
*/
|
|
939
938
|
declare class QueueEventsRegistry {
|
|
939
|
+
#private;
|
|
940
940
|
private readonly connection;
|
|
941
941
|
private readonly options;
|
|
942
942
|
private readonly logger;
|
|
943
|
-
private readonly events;
|
|
944
|
-
private readonly connections;
|
|
945
943
|
constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
|
|
946
944
|
/**
|
|
947
945
|
* Returns the cached `QueueEvents` for `queueName`, creating one on first
|
package/dist/server/index.d.ts
CHANGED
|
@@ -354,8 +354,7 @@ type JobSchedulerRepeatOptions = {
|
|
|
354
354
|
* for `ready`, closing it on shutdown.
|
|
355
355
|
*/
|
|
356
356
|
declare class ConnectionResolver {
|
|
357
|
-
private
|
|
358
|
-
private client;
|
|
357
|
+
#private;
|
|
359
358
|
private mode;
|
|
360
359
|
constructor(options: BymaxQueueModuleOptions);
|
|
361
360
|
/** Resolve and validate the connection. Call once during module bootstrap. */
|
|
@@ -390,10 +389,10 @@ type CleanableStatus = 'completed' | 'failed' | 'delayed' | 'wait' | 'active' |
|
|
|
390
389
|
* enqueueing, inspection, metrics, and lifecycle control.
|
|
391
390
|
*/
|
|
392
391
|
declare class QueueService {
|
|
392
|
+
#private;
|
|
393
393
|
private readonly connection;
|
|
394
394
|
private readonly options;
|
|
395
395
|
private readonly logger;
|
|
396
|
-
private readonly queues;
|
|
397
396
|
constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
|
|
398
397
|
/**
|
|
399
398
|
* Return the cached `Queue` for `queueName`, or create one with the module
|
|
@@ -792,10 +791,10 @@ interface SandboxedWorkerConfig {
|
|
|
792
791
|
* })
|
|
793
792
|
*/
|
|
794
793
|
declare class WorkerRegistry {
|
|
794
|
+
#private;
|
|
795
795
|
private readonly connection;
|
|
796
796
|
private readonly options;
|
|
797
797
|
private readonly logger;
|
|
798
|
-
private readonly entries;
|
|
799
798
|
constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
|
|
800
799
|
/**
|
|
801
800
|
* Register an in-process worker with a handler function. The BullMQ `Worker`
|
|
@@ -937,11 +936,10 @@ declare class WorkerRegistry {
|
|
|
937
936
|
* qe.on('completed', ({ jobId }) => console.log(jobId))
|
|
938
937
|
*/
|
|
939
938
|
declare class QueueEventsRegistry {
|
|
939
|
+
#private;
|
|
940
940
|
private readonly connection;
|
|
941
941
|
private readonly options;
|
|
942
942
|
private readonly logger;
|
|
943
|
-
private readonly events;
|
|
944
|
-
private readonly connections;
|
|
945
943
|
constructor(connection: ConnectionResolver, options: ResolvedQueueOptions);
|
|
946
944
|
/**
|
|
947
945
|
* Returns the cached `QueueEvents` for `queueName`, creating one on first
|
package/dist/server/index.mjs
CHANGED
|
@@ -4,7 +4,12 @@ import { Redis } from 'ioredis';
|
|
|
4
4
|
import { Worker, QueueEvents, Queue, FlowProducer } from 'bullmq';
|
|
5
5
|
import { isAbsolute, extname } from 'path';
|
|
6
6
|
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
7
8
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
9
|
+
var __typeError = (msg) => {
|
|
10
|
+
throw TypeError(msg);
|
|
11
|
+
};
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
13
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
9
14
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
10
15
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
@@ -13,6 +18,11 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
13
18
|
return result;
|
|
14
19
|
};
|
|
15
20
|
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
21
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
22
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
23
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
24
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
25
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
|
|
16
26
|
|
|
17
27
|
// src/shared/constants/error-codes.ts
|
|
18
28
|
var QUEUE_ERROR_CODES = {
|
|
@@ -109,8 +119,8 @@ var DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 3e4;
|
|
|
109
119
|
|
|
110
120
|
// src/server/config/resolved-options.ts
|
|
111
121
|
function applyDefaults(opts) {
|
|
122
|
+
const connection = opts.connection;
|
|
112
123
|
const base = {
|
|
113
|
-
connection: opts.connection,
|
|
114
124
|
defaultJobOptions: { ...DEFAULT_JOB_OPTIONS, ...opts.defaultJobOptions ?? {} },
|
|
115
125
|
prefix: opts.prefix ?? "bull",
|
|
116
126
|
queueOptions: opts.queueOptions ?? {},
|
|
@@ -126,6 +136,12 @@ function applyDefaults(opts) {
|
|
|
126
136
|
connectionReadyTimeoutMs: opts.connectionReadyTimeoutMs ?? DEFAULT_CONNECTION_READY_TIMEOUT_MS
|
|
127
137
|
};
|
|
128
138
|
if (opts.telemetry !== void 0) base.telemetry = opts.telemetry;
|
|
139
|
+
Object.defineProperty(base, "connection", {
|
|
140
|
+
get: () => connection,
|
|
141
|
+
enumerable: false,
|
|
142
|
+
// Stryker disable next-line BooleanLiteral: equivalent HERE — this object is `Object.freeze`d on the way out, and freezing makes every property non-configurable regardless. The flag stays because it states the guarantee at the point the accessor is defined, and because the sibling packages that withhold secrets the same way are NOT frozen, where it is load-bearing
|
|
143
|
+
configurable: false
|
|
144
|
+
});
|
|
129
145
|
return Object.freeze(base);
|
|
130
146
|
}
|
|
131
147
|
|
|
@@ -155,34 +171,44 @@ function duplicateConnection(client) {
|
|
|
155
171
|
}
|
|
156
172
|
|
|
157
173
|
// src/server/services/connection-resolver.service.ts
|
|
174
|
+
var _client, _options;
|
|
158
175
|
var ConnectionResolver = class {
|
|
159
176
|
constructor(options) {
|
|
160
|
-
|
|
177
|
+
/**
|
|
178
|
+
* The resolved Redis client.
|
|
179
|
+
*
|
|
180
|
+
* An ECMAScript private field rather than a TypeScript `private` one, which
|
|
181
|
+
* is erased at runtime: an ioredis instance carries `options.password` as a
|
|
182
|
+
* plain field, so leaving this enumerable would let anything that serializes
|
|
183
|
+
* a service holding this resolver walk into the credentials.
|
|
184
|
+
*/
|
|
185
|
+
__privateAdd(this, _client);
|
|
186
|
+
/** The consumer's module options, which carry the Redis credentials. */
|
|
187
|
+
__privateAdd(this, _options);
|
|
188
|
+
__publicField(this, "mode");
|
|
189
|
+
__privateSet(this, _options, options);
|
|
161
190
|
}
|
|
162
|
-
options;
|
|
163
|
-
client;
|
|
164
|
-
mode;
|
|
165
191
|
/** Resolve and validate the connection. Call once during module bootstrap. */
|
|
166
192
|
async init() {
|
|
167
|
-
const cfg = this.
|
|
193
|
+
const cfg = __privateGet(this, _options).connection;
|
|
168
194
|
if ("client" in cfg) {
|
|
169
195
|
this.initModeA(cfg.client);
|
|
170
196
|
return;
|
|
171
197
|
}
|
|
172
198
|
this.mode = "mode-b-owned";
|
|
173
|
-
this
|
|
199
|
+
__privateSet(this, _client, "url" in cfg ? new Redis(cfg.url, { ...cfg.options ?? {}, lazyConnect: false }) : new Redis({ ...cfg.options, lazyConnect: false }));
|
|
174
200
|
await this.waitReady(
|
|
175
|
-
this.
|
|
201
|
+
__privateGet(this, _options).connectionReadyTimeoutMs ?? DEFAULT_CONNECTION_READY_TIMEOUT_MS
|
|
176
202
|
);
|
|
177
203
|
}
|
|
178
204
|
/** The resolved Queue-role client. */
|
|
179
205
|
getClient() {
|
|
180
|
-
if (!this
|
|
206
|
+
if (!__privateGet(this, _client)) {
|
|
181
207
|
throw new QueueException(QUEUE_ERROR_CODES.CONNECTION_INVALID, 500, {
|
|
182
208
|
reason: "not initialized"
|
|
183
209
|
});
|
|
184
210
|
}
|
|
185
|
-
return this
|
|
211
|
+
return __privateGet(this, _client);
|
|
186
212
|
}
|
|
187
213
|
/** The resolved connection mode. */
|
|
188
214
|
getMode() {
|
|
@@ -199,8 +225,8 @@ var ConnectionResolver = class {
|
|
|
199
225
|
}
|
|
200
226
|
/** Close the library-owned connection on shutdown; never touch a BYO client. */
|
|
201
227
|
async teardown() {
|
|
202
|
-
if (this.isOwned() && this
|
|
203
|
-
const client = this
|
|
228
|
+
if (this.isOwned() && __privateGet(this, _client)) {
|
|
229
|
+
const client = __privateGet(this, _client);
|
|
204
230
|
await client.quit().catch(() => {
|
|
205
231
|
client.disconnect();
|
|
206
232
|
});
|
|
@@ -209,7 +235,7 @@ var ConnectionResolver = class {
|
|
|
209
235
|
/** Validate and adopt a bring-your-own client for the Queue role. */
|
|
210
236
|
initModeA(client) {
|
|
211
237
|
this.mode = "mode-a-byo";
|
|
212
|
-
this
|
|
238
|
+
__privateSet(this, _client, client);
|
|
213
239
|
if (!isClientUsable(client)) {
|
|
214
240
|
throw new QueueException(QUEUE_ERROR_CODES.CONNECTION_INVALID, 500, { status: client.status });
|
|
215
241
|
}
|
|
@@ -247,6 +273,8 @@ var ConnectionResolver = class {
|
|
|
247
273
|
});
|
|
248
274
|
}
|
|
249
275
|
};
|
|
276
|
+
_client = new WeakMap();
|
|
277
|
+
_options = new WeakMap();
|
|
250
278
|
ConnectionResolver = __decorateClass([
|
|
251
279
|
Injectable(),
|
|
252
280
|
__decorateParam(0, Inject(BYMAX_QUEUE_OPTIONS))
|
|
@@ -295,15 +323,16 @@ var MAX_BULK_SIZE = 1e3;
|
|
|
295
323
|
function isOperationalError(err) {
|
|
296
324
|
return typeof err?.code === "string";
|
|
297
325
|
}
|
|
326
|
+
var _queues;
|
|
298
327
|
var QueueService = class {
|
|
299
328
|
constructor(connection, options) {
|
|
300
329
|
this.connection = connection;
|
|
301
330
|
this.options = options;
|
|
331
|
+
__publicField(this, "logger", new Logger(QueueService.name));
|
|
332
|
+
__privateAdd(this, _queues, /* @__PURE__ */ new Map());
|
|
302
333
|
}
|
|
303
334
|
connection;
|
|
304
335
|
options;
|
|
305
|
-
logger = new Logger(QueueService.name);
|
|
306
|
-
queues = /* @__PURE__ */ new Map();
|
|
307
336
|
/**
|
|
308
337
|
* Return the cached `Queue` for `queueName`, or create one with the module
|
|
309
338
|
* defaults applied. Subsequent calls with the same name return the same
|
|
@@ -313,7 +342,7 @@ var QueueService = class {
|
|
|
313
342
|
* @param overrides - Optional per-queue option overrides.
|
|
314
343
|
*/
|
|
315
344
|
getOrCreateQueue(queueName, overrides) {
|
|
316
|
-
const existing = this.
|
|
345
|
+
const existing = __privateGet(this, _queues).get(queueName);
|
|
317
346
|
if (existing) return existing;
|
|
318
347
|
const queue = new Queue(queueName, {
|
|
319
348
|
connection: this.connection.getClient(),
|
|
@@ -324,7 +353,7 @@ var QueueService = class {
|
|
|
324
353
|
...overrides
|
|
325
354
|
});
|
|
326
355
|
attachDefaultErrorListener(queue, this.logger, "Queue", queueName);
|
|
327
|
-
this.
|
|
356
|
+
__privateGet(this, _queues).set(queueName, queue);
|
|
328
357
|
return queue;
|
|
329
358
|
}
|
|
330
359
|
/** Resolve the raw cached queue (default generics) for internal delegation. */
|
|
@@ -528,7 +557,7 @@ var QueueService = class {
|
|
|
528
557
|
}
|
|
529
558
|
/** Return a read-only view of the cached queues. */
|
|
530
559
|
getCachedQueues() {
|
|
531
|
-
return this
|
|
560
|
+
return __privateGet(this, _queues);
|
|
532
561
|
}
|
|
533
562
|
/**
|
|
534
563
|
* Close every cached queue (swallowing per-queue errors) and clear the cache.
|
|
@@ -540,12 +569,13 @@ var QueueService = class {
|
|
|
540
569
|
* exists to prevent. `QueueLifecycle` is the only caller.
|
|
541
570
|
*/
|
|
542
571
|
async closeAll() {
|
|
543
|
-
for (const queue of this.
|
|
572
|
+
for (const queue of __privateGet(this, _queues).values()) {
|
|
544
573
|
await queue.close().catch(() => void 0);
|
|
545
574
|
}
|
|
546
|
-
this.
|
|
575
|
+
__privateGet(this, _queues).clear();
|
|
547
576
|
}
|
|
548
577
|
};
|
|
578
|
+
_queues = new WeakMap();
|
|
549
579
|
QueueService = __decorateClass([
|
|
550
580
|
Injectable(),
|
|
551
581
|
__decorateParam(0, Inject(ConnectionResolver)),
|
|
@@ -702,15 +732,16 @@ var MetricsService = class {
|
|
|
702
732
|
MetricsService = __decorateClass([
|
|
703
733
|
Injectable()
|
|
704
734
|
], MetricsService);
|
|
735
|
+
var _entries;
|
|
705
736
|
var WorkerRegistry = class {
|
|
706
737
|
constructor(connection, options) {
|
|
707
738
|
this.connection = connection;
|
|
708
739
|
this.options = options;
|
|
740
|
+
__publicField(this, "logger", new Logger(WorkerRegistry.name));
|
|
741
|
+
__privateAdd(this, _entries, /* @__PURE__ */ new Map());
|
|
709
742
|
}
|
|
710
743
|
connection;
|
|
711
744
|
options;
|
|
712
|
-
logger = new Logger(WorkerRegistry.name);
|
|
713
|
-
entries = /* @__PURE__ */ new Map();
|
|
714
745
|
/**
|
|
715
746
|
* Register an in-process worker with a handler function. The BullMQ `Worker`
|
|
716
747
|
* constructor is synchronous — no Redis handshake is performed at registration
|
|
@@ -741,7 +772,7 @@ var WorkerRegistry = class {
|
|
|
741
772
|
});
|
|
742
773
|
}
|
|
743
774
|
attachDefaultErrorListener(worker, this.logger, "Worker", queueName);
|
|
744
|
-
this.
|
|
775
|
+
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
745
776
|
return worker;
|
|
746
777
|
}
|
|
747
778
|
/**
|
|
@@ -780,7 +811,7 @@ var WorkerRegistry = class {
|
|
|
780
811
|
});
|
|
781
812
|
}
|
|
782
813
|
attachDefaultErrorListener(worker, this.logger, "Worker", queueName);
|
|
783
|
-
this.
|
|
814
|
+
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
784
815
|
return worker;
|
|
785
816
|
}
|
|
786
817
|
/**
|
|
@@ -791,11 +822,11 @@ var WorkerRegistry = class {
|
|
|
791
822
|
* @param queueName - The queue whose worker should be removed.
|
|
792
823
|
*/
|
|
793
824
|
async unregister(queueName) {
|
|
794
|
-
const entry = this.
|
|
825
|
+
const entry = __privateGet(this, _entries).get(queueName);
|
|
795
826
|
if (!entry) return;
|
|
796
827
|
await entry.worker.close();
|
|
797
828
|
await this.closeConnection(entry.connection);
|
|
798
|
-
this.
|
|
829
|
+
__privateGet(this, _entries).delete(queueName);
|
|
799
830
|
}
|
|
800
831
|
/**
|
|
801
832
|
* Returns the queue names of all currently registered workers.
|
|
@@ -803,7 +834,7 @@ var WorkerRegistry = class {
|
|
|
803
834
|
* @returns An immutable array of queue names.
|
|
804
835
|
*/
|
|
805
836
|
list() {
|
|
806
|
-
return Array.from(this.
|
|
837
|
+
return Array.from(__privateGet(this, _entries).keys());
|
|
807
838
|
}
|
|
808
839
|
/**
|
|
809
840
|
* Returns a read-only view of the registered workers keyed by queue name.
|
|
@@ -813,7 +844,7 @@ var WorkerRegistry = class {
|
|
|
813
844
|
*/
|
|
814
845
|
getAll() {
|
|
815
846
|
return new Map(
|
|
816
|
-
Array.from(this
|
|
847
|
+
Array.from(__privateGet(this, _entries), ([name, entry]) => [name, entry.worker])
|
|
817
848
|
);
|
|
818
849
|
}
|
|
819
850
|
/**
|
|
@@ -826,7 +857,7 @@ var WorkerRegistry = class {
|
|
|
826
857
|
*/
|
|
827
858
|
getConnections() {
|
|
828
859
|
return new Map(
|
|
829
|
-
Array.from(this
|
|
860
|
+
Array.from(__privateGet(this, _entries), ([name, entry]) => [name, entry.connection])
|
|
830
861
|
);
|
|
831
862
|
}
|
|
832
863
|
/**
|
|
@@ -849,7 +880,7 @@ var WorkerRegistry = class {
|
|
|
849
880
|
* @throws {QueueException} `DUPLICATE_PROCESSOR` if the queue is already registered.
|
|
850
881
|
*/
|
|
851
882
|
guardDuplicate(queueName) {
|
|
852
|
-
if (this.
|
|
883
|
+
if (__privateGet(this, _entries).has(queueName)) {
|
|
853
884
|
throw new QueueException(QUEUE_ERROR_CODES.DUPLICATE_PROCESSOR, 500, { queueName });
|
|
854
885
|
}
|
|
855
886
|
}
|
|
@@ -958,21 +989,23 @@ var WorkerRegistry = class {
|
|
|
958
989
|
return result;
|
|
959
990
|
}
|
|
960
991
|
};
|
|
992
|
+
_entries = new WeakMap();
|
|
961
993
|
WorkerRegistry = __decorateClass([
|
|
962
994
|
Injectable(),
|
|
963
995
|
__decorateParam(0, Inject(ConnectionResolver)),
|
|
964
996
|
__decorateParam(1, Inject(BYMAX_QUEUE_RESOLVED_OPTIONS))
|
|
965
997
|
], WorkerRegistry);
|
|
998
|
+
var _events, _connections;
|
|
966
999
|
var QueueEventsRegistry = class {
|
|
967
1000
|
constructor(connection, options) {
|
|
968
1001
|
this.connection = connection;
|
|
969
1002
|
this.options = options;
|
|
1003
|
+
__publicField(this, "logger", new Logger(QueueEventsRegistry.name));
|
|
1004
|
+
__privateAdd(this, _events, /* @__PURE__ */ new Map());
|
|
1005
|
+
__privateAdd(this, _connections, /* @__PURE__ */ new Map());
|
|
970
1006
|
}
|
|
971
1007
|
connection;
|
|
972
1008
|
options;
|
|
973
|
-
logger = new Logger(QueueEventsRegistry.name);
|
|
974
|
-
events = /* @__PURE__ */ new Map();
|
|
975
|
-
connections = /* @__PURE__ */ new Map();
|
|
976
1009
|
/**
|
|
977
1010
|
* Returns the cached `QueueEvents` for `queueName`, creating one on first
|
|
978
1011
|
* call. Subsequent calls for the same name return the same instance without
|
|
@@ -982,7 +1015,7 @@ var QueueEventsRegistry = class {
|
|
|
982
1015
|
* @returns The `QueueEvents` instance for the queue.
|
|
983
1016
|
*/
|
|
984
1017
|
getOrCreate(queueName) {
|
|
985
|
-
const existing = this.
|
|
1018
|
+
const existing = __privateGet(this, _events).get(queueName);
|
|
986
1019
|
if (existing) return existing;
|
|
987
1020
|
const conn = duplicateConnection(this.connection.getClient());
|
|
988
1021
|
let qe;
|
|
@@ -993,8 +1026,8 @@ var QueueEventsRegistry = class {
|
|
|
993
1026
|
throw err;
|
|
994
1027
|
}
|
|
995
1028
|
attachDefaultErrorListener(qe, this.logger, "QueueEvents", queueName);
|
|
996
|
-
this.
|
|
997
|
-
this.
|
|
1029
|
+
__privateGet(this, _events).set(queueName, qe);
|
|
1030
|
+
__privateGet(this, _connections).set(queueName, conn);
|
|
998
1031
|
return qe;
|
|
999
1032
|
}
|
|
1000
1033
|
/**
|
|
@@ -1003,7 +1036,7 @@ var QueueEventsRegistry = class {
|
|
|
1003
1036
|
* @returns An immutable array of queue names.
|
|
1004
1037
|
*/
|
|
1005
1038
|
list() {
|
|
1006
|
-
return Array.from(this.
|
|
1039
|
+
return Array.from(__privateGet(this, _events).keys());
|
|
1007
1040
|
}
|
|
1008
1041
|
/**
|
|
1009
1042
|
* Returns the live `QueueEvents` map. Intended for the shutdown orchestrator.
|
|
@@ -1011,7 +1044,7 @@ var QueueEventsRegistry = class {
|
|
|
1011
1044
|
* @returns A read-only view of the internal map.
|
|
1012
1045
|
*/
|
|
1013
1046
|
getAll() {
|
|
1014
|
-
return this
|
|
1047
|
+
return __privateGet(this, _events);
|
|
1015
1048
|
}
|
|
1016
1049
|
/**
|
|
1017
1050
|
* Returns a read-only view of the duplicated connections the library opened
|
|
@@ -1027,9 +1060,11 @@ var QueueEventsRegistry = class {
|
|
|
1027
1060
|
* @returns A read-only snapshot of queue name to duplicated connection.
|
|
1028
1061
|
*/
|
|
1029
1062
|
getConnections() {
|
|
1030
|
-
return new Map(this
|
|
1063
|
+
return new Map(__privateGet(this, _connections));
|
|
1031
1064
|
}
|
|
1032
1065
|
};
|
|
1066
|
+
_events = new WeakMap();
|
|
1067
|
+
_connections = new WeakMap();
|
|
1033
1068
|
QueueEventsRegistry = __decorateClass([
|
|
1034
1069
|
Injectable(),
|
|
1035
1070
|
__decorateParam(0, Inject(ConnectionResolver)),
|
|
@@ -1297,10 +1332,15 @@ QueueLifecycle = __decorateClass([
|
|
|
1297
1332
|
], QueueLifecycle);
|
|
1298
1333
|
|
|
1299
1334
|
// src/server/bymax-queue.module.ts
|
|
1300
|
-
var { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } =
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1335
|
+
var { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = (
|
|
1336
|
+
// The `moduleName` shapes the generated token's description only — a label read in Nest's DI
|
|
1337
|
+
// error output, never compared against anything. Mutation testing reports it as surviving for
|
|
1338
|
+
// that reason; no Stryker directive attaches inside this builder chain, so the note lives here.
|
|
1339
|
+
new ConfigurableModuleBuilder({ moduleName: "BymaxQueue" }).setClassMethodName("forRoot").setFactoryMethodName("createQueueOptions").setExtras({ isGlobal: true }, (definition, extras) => ({
|
|
1340
|
+
...definition,
|
|
1341
|
+
global: extras.isGlobal
|
|
1342
|
+
})).build()
|
|
1343
|
+
);
|
|
1304
1344
|
var BymaxQueueModule = class extends ConfigurableModuleClass {
|
|
1305
1345
|
/**
|
|
1306
1346
|
* Synchronous registration. Validates options, resolves defaults, and wires the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-queue",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"prettier --write"
|
|
147
147
|
]
|
|
148
148
|
},
|
|
149
|
-
"packageManager": "pnpm@11.
|
|
149
|
+
"packageManager": "pnpm@11.20.0",
|
|
150
150
|
"engines": {
|
|
151
151
|
"node": ">=24.0.0"
|
|
152
152
|
},
|