@bymax-one/nest-queue 1.2.2 → 1.2.4
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 +84 -0
- package/README.md +2 -2
- package/dist/server/index.cjs +138 -64
- package/dist/server/index.d.cts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.mjs +139 -65
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,87 @@ versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [1.2.4] — 2026-10-30
|
|
15
|
+
|
|
16
|
+
No action required: behaviour, the public API, the types and the peer ranges are unchanged.
|
|
17
|
+
|
|
18
|
+
The status codes a `QueueException` carries are the same as in 1.2.3, but the bundle under `dist/`
|
|
19
|
+
is not byte-identical, and the differences are listed below so a reader does not have to take
|
|
20
|
+
"no change" on trust.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `QueueException` is now thrown with the named `HttpStatus` members (`BAD_REQUEST`,
|
|
25
|
+
`INTERNAL_SERVER_ERROR`, `SERVICE_UNAVAILABLE`) instead of the numbers 400, 500 and 503, at every
|
|
26
|
+
call site. The values are the same; the change satisfies `no-unsafe-enum-assignment`. Against a
|
|
27
|
+
fresh build of 1.2.3, the CommonJS bundle is identical once those members are replaced by their
|
|
28
|
+
numeric values, the ES module bundle differs only in the order of its `@nestjs/common` imports and
|
|
29
|
+
in line wrapping, and the type declarations differ in one JSDoc line (`FlowService`, which now
|
|
30
|
+
names `HttpStatus.SERVICE_UNAVAILABLE`).
|
|
31
|
+
|
|
32
|
+
### Security
|
|
33
|
+
|
|
34
|
+
Not shipped:
|
|
35
|
+
|
|
36
|
+
- Dependency resolution of the tooling the repository builds and tests with was raised to patched
|
|
37
|
+
versions: `fastify`, `undici`, `fast-uri`, `js-yaml`, `brace-expansion`, `qs`, `zod`,
|
|
38
|
+
`handlebars`, `@grpc/grpc-js` and `@nestjs/platform-fastify`. None of these ships in the package,
|
|
39
|
+
which still declares `"dependencies": {}`; they reach it only through development and example-app
|
|
40
|
+
tooling.
|
|
41
|
+
- `osv-scanner.toml` ignores `GHSA-gcqx-w48m-w2h4` (`deepmerge` 4.3.1, no patched release) until
|
|
42
|
+
2027-01-27. The package is reached only through `jest-config`, a development dependency.
|
|
43
|
+
|
|
44
|
+
### Documentation
|
|
45
|
+
|
|
46
|
+
- The README supply-chain wording now says that third-party GitHub Actions are pinned to a commit SHA,
|
|
47
|
+
which is what the workflows do; the organisation's own reusable workflows follow a version tag.
|
|
48
|
+
|
|
49
|
+
### Internal
|
|
50
|
+
|
|
51
|
+
Not shipped:
|
|
52
|
+
|
|
53
|
+
- Tests kill the four mutants that survived the full mutation run, and the one equivalent mutant is
|
|
54
|
+
disabled in place with the reason beside it. The mutation score is 100%.
|
|
55
|
+
- The README snippet check (`scripts/check-published-surface.mjs`) decides whether a snippet
|
|
56
|
+
declares a receiver from the parsed snippet rather than from its text, so decorated constructor
|
|
57
|
+
parameters, object-literal keys and JSX are classified correctly.
|
|
58
|
+
- Development and GitHub Actions dependencies were updated (Stryker, Jest, ts-jest, Prettier,
|
|
59
|
+
typescript-eslint, testcontainers, NestJS 11.2.x, and the CodeQL, pnpm, harden-runner, TruffleHog
|
|
60
|
+
and setup-node actions).
|
|
61
|
+
- The shared review-rules block in `AGENTS.md` was synchronised with its canonical copy.
|
|
62
|
+
- `SECURITY.md`, `CONTRIBUTING.md`, `CLAUDE.md`, `AGENTS.md` and the mutation-testing documents were
|
|
63
|
+
brought in line with the current configuration.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## [1.2.3] — 2026-09-05
|
|
68
|
+
|
|
69
|
+
**Nothing that runs in this package changed.** A fresh build of the release commit was packed and
|
|
70
|
+
compared against the published 1.2.2 tarball: `dist/` is identical file for file, and `package.json`
|
|
71
|
+
differs only in the version string. The remaining difference is this CHANGELOG entry, which ships
|
|
72
|
+
because `CHANGELOG.md` does.
|
|
73
|
+
|
|
74
|
+
It exists because the estate releases on a cadence, so a library whose artifact is unchanged
|
|
75
|
+
publishes anyway and says so rather than implying a benefit it does not deliver. **There is no
|
|
76
|
+
reason to upgrade from 1.2.2**, and a consumer on `^1.2.0` gains nothing by resolving to it.
|
|
77
|
+
|
|
78
|
+
### Internal
|
|
79
|
+
|
|
80
|
+
Not shipped — `files` is `dist`, `LICENSE`, `README.md`, `CHANGELOG.md`, and the only file touched
|
|
81
|
+
since 1.2.2 is `AGENTS.md`:
|
|
82
|
+
|
|
83
|
+
- The shared review-rules block was synchronised with its canonical copy, bringing in an invariant
|
|
84
|
+
for keys built from more than one field — the generalised form of the deduplication defect this
|
|
85
|
+
package corrected in 1.2.2.
|
|
86
|
+
- Two repository-specific review rules were corrected: one rested its case on a defect that had
|
|
87
|
+
since been fixed, so the claim had become false; the other required an authorship finding to
|
|
88
|
+
quote a resolvable commit id, a criterion the shared block superseded by placing author and
|
|
89
|
+
committer identity out of scope entirely.
|
|
90
|
+
|
|
10
91
|
## [1.2.2] — 2026-09-04
|
|
11
92
|
|
|
12
93
|
**1.2.1 was tagged but never published**, so this release carries its documentation fix as well
|
|
@@ -528,6 +609,9 @@ v6 peer range.
|
|
|
528
609
|
|
|
529
610
|
---
|
|
530
611
|
|
|
612
|
+
[Unreleased]: https://github.com/bymaxone/nest-queue/compare/v1.2.4...HEAD
|
|
613
|
+
[1.2.4]: https://github.com/bymaxone/nest-queue/compare/v1.2.3...v1.2.4
|
|
614
|
+
[1.2.3]: https://github.com/bymaxone/nest-queue/compare/v1.2.2...v1.2.3
|
|
531
615
|
[1.2.2]: https://github.com/bymaxone/nest-queue/compare/v1.2.1...v1.2.2
|
|
532
616
|
[1.2.1]: https://github.com/bymaxone/nest-queue/compare/v1.2.0...v1.2.1
|
|
533
617
|
[1.2.0]: https://github.com/bymaxone/nest-queue/compare/v1.0.11...v1.2.0
|
package/README.md
CHANGED
|
@@ -771,7 +771,7 @@ key will do it twice at some point.
|
|
|
771
771
|
| Namespacing | `prefix` applied uniformly to `Queue`, `Worker`, `QueueEvents` and `FlowProducer` |
|
|
772
772
|
| Connection policy | `maxRetriesPerRequest: null` confined to duplicated consumer connections; producers keep fail-fast retries |
|
|
773
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
|
|
774
|
+
| Supply chain | `dependencies: {}`; SHA-pinned third-party Actions, OSV-Scanner, TruffleHog, OpenSSF Scorecard; npm publish over OIDC |
|
|
775
775
|
| Input validation | Options validated at bootstrap; cron patterns validated by BullMQ's own parser, never a hand-rolled regex |
|
|
776
776
|
|
|
777
777
|
> [!IMPORTANT]
|
|
@@ -805,7 +805,7 @@ key will do it twice at some point.
|
|
|
805
805
|
module-resolution mode would (all four `attw` quadrants green, `node10` included), and
|
|
806
806
|
`pnpm smoke` resolves the packed tarball from a real consumer in ESM _and_ CommonJS.
|
|
807
807
|
- **Bundle budgets** enforced (`pnpm size`): server ≤ 18 KiB brotli, shared ≤ 2.5 KiB.
|
|
808
|
-
- **Supply chain:** SHA-pinned GitHub Actions, OSV-Scanner, TruffleHog, OpenSSF Scorecard,
|
|
808
|
+
- **Supply chain:** SHA-pinned third-party GitHub Actions, OSV-Scanner, TruffleHog, OpenSSF Scorecard,
|
|
809
809
|
npm publish with provenance (OIDC).
|
|
810
810
|
|
|
811
811
|
```bash
|
package/dist/server/index.cjs
CHANGED
|
@@ -80,7 +80,9 @@ var QueueException = class extends common.HttpException {
|
|
|
80
80
|
|
|
81
81
|
// src/server/config/validate-options.ts
|
|
82
82
|
function invalid(reason) {
|
|
83
|
-
throw new QueueException(QUEUE_ERROR_CODES.INVALID_OPTIONS,
|
|
83
|
+
throw new QueueException(QUEUE_ERROR_CODES.INVALID_OPTIONS, common.HttpStatus.INTERNAL_SERVER_ERROR, {
|
|
84
|
+
reason
|
|
85
|
+
});
|
|
84
86
|
}
|
|
85
87
|
function validateOptions(opts) {
|
|
86
88
|
const cfg = opts.connection;
|
|
@@ -152,15 +154,17 @@ var BYMAX_QUEUE_OPTIONS = /* @__PURE__ */ Symbol("BYMAX_QUEUE_OPTIONS");
|
|
|
152
154
|
var BYMAX_QUEUE_REDIS_CLIENT = /* @__PURE__ */ Symbol("BYMAX_QUEUE_REDIS_CLIENT");
|
|
153
155
|
var BYMAX_QUEUE_CONNECTION_MODE = /* @__PURE__ */ Symbol("BYMAX_QUEUE_CONNECTION_MODE");
|
|
154
156
|
var BYMAX_QUEUE_RESOLVED_OPTIONS = /* @__PURE__ */ Symbol("BYMAX_QUEUE_RESOLVED_OPTIONS");
|
|
155
|
-
|
|
156
|
-
// src/server/utils/validate-connection.ts
|
|
157
157
|
function assertBlockingConnection(client) {
|
|
158
158
|
const actualValue = client.options?.maxRetriesPerRequest;
|
|
159
159
|
if (actualValue !== null) {
|
|
160
|
-
throw new QueueException(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
throw new QueueException(
|
|
161
|
+
QUEUE_ERROR_CODES.CONNECTION_REQUIRES_NULL_RETRIES,
|
|
162
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
163
|
+
{
|
|
164
|
+
actualValue: actualValue ?? null,
|
|
165
|
+
expectedValue: null
|
|
166
|
+
}
|
|
167
|
+
);
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
170
|
function isClientUsable(client) {
|
|
@@ -217,18 +221,26 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
217
221
|
/** The resolved Queue-role client. */
|
|
218
222
|
getClient() {
|
|
219
223
|
if (!__privateGet(this, _client)) {
|
|
220
|
-
throw new QueueException(
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
throw new QueueException(
|
|
225
|
+
QUEUE_ERROR_CODES.CONNECTION_INVALID,
|
|
226
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
227
|
+
{
|
|
228
|
+
reason: "not initialized"
|
|
229
|
+
}
|
|
230
|
+
);
|
|
223
231
|
}
|
|
224
232
|
return __privateGet(this, _client);
|
|
225
233
|
}
|
|
226
234
|
/** The resolved connection mode. */
|
|
227
235
|
getMode() {
|
|
228
236
|
if (!this.mode) {
|
|
229
|
-
throw new QueueException(
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
throw new QueueException(
|
|
238
|
+
QUEUE_ERROR_CODES.CONNECTION_INVALID,
|
|
239
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
240
|
+
{
|
|
241
|
+
reason: "not initialized"
|
|
242
|
+
}
|
|
243
|
+
);
|
|
232
244
|
}
|
|
233
245
|
return this.mode;
|
|
234
246
|
}
|
|
@@ -250,7 +262,11 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
250
262
|
this.mode = "mode-a-byo";
|
|
251
263
|
__privateSet(this, _client, client);
|
|
252
264
|
if (!isClientUsable(client)) {
|
|
253
|
-
throw new QueueException(
|
|
265
|
+
throw new QueueException(
|
|
266
|
+
QUEUE_ERROR_CODES.CONNECTION_INVALID,
|
|
267
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
268
|
+
{ status: client.status }
|
|
269
|
+
);
|
|
254
270
|
}
|
|
255
271
|
const probe = duplicateConnection(client);
|
|
256
272
|
try {
|
|
@@ -279,7 +295,13 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
279
295
|
};
|
|
280
296
|
const timer = setTimeout(() => {
|
|
281
297
|
cleanup();
|
|
282
|
-
reject(
|
|
298
|
+
reject(
|
|
299
|
+
new QueueException(
|
|
300
|
+
QUEUE_ERROR_CODES.CONNECTION_TIMEOUT,
|
|
301
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
302
|
+
{ timeoutMs }
|
|
303
|
+
)
|
|
304
|
+
);
|
|
283
305
|
}, timeoutMs);
|
|
284
306
|
client.once("ready", onReady);
|
|
285
307
|
client.once("error", onError);
|
|
@@ -292,10 +314,10 @@ exports.ConnectionResolver = __decorateClass([
|
|
|
292
314
|
common.Injectable(),
|
|
293
315
|
__decorateParam(0, common.Inject(BYMAX_QUEUE_OPTIONS))
|
|
294
316
|
], exports.ConnectionResolver);
|
|
295
|
-
|
|
296
|
-
// src/server/utils/validate-job-scheduler-options.ts
|
|
297
317
|
function invalid2(reason) {
|
|
298
|
-
throw new QueueException(QUEUE_ERROR_CODES.INVALID_REPEAT_OPTIONS,
|
|
318
|
+
throw new QueueException(QUEUE_ERROR_CODES.INVALID_REPEAT_OPTIONS, common.HttpStatus.BAD_REQUEST, {
|
|
319
|
+
reason
|
|
320
|
+
});
|
|
299
321
|
}
|
|
300
322
|
function toEpochMs(date) {
|
|
301
323
|
return typeof date === "number" ? date : Date.parse(date);
|
|
@@ -435,11 +457,15 @@ exports.QueueService = class QueueService {
|
|
|
435
457
|
*/
|
|
436
458
|
async enqueueBulk(queueName, jobs) {
|
|
437
459
|
if (jobs.length > MAX_BULK_SIZE) {
|
|
438
|
-
throw new QueueException(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
460
|
+
throw new QueueException(
|
|
461
|
+
QUEUE_ERROR_CODES.BULK_ENQUEUE_FAILED,
|
|
462
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
463
|
+
{
|
|
464
|
+
reason: "batch size exceeds limit",
|
|
465
|
+
limit: MAX_BULK_SIZE,
|
|
466
|
+
received: jobs.length
|
|
467
|
+
}
|
|
468
|
+
);
|
|
443
469
|
}
|
|
444
470
|
try {
|
|
445
471
|
const created = await this.queueFor(queueName).addBulk(
|
|
@@ -451,9 +477,13 @@ exports.QueueService = class QueueService {
|
|
|
451
477
|
);
|
|
452
478
|
return created;
|
|
453
479
|
} catch (err) {
|
|
454
|
-
throw new QueueException(
|
|
455
|
-
|
|
456
|
-
|
|
480
|
+
throw new QueueException(
|
|
481
|
+
QUEUE_ERROR_CODES.BULK_ENQUEUE_FAILED,
|
|
482
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
483
|
+
{
|
|
484
|
+
cause: err.message
|
|
485
|
+
}
|
|
486
|
+
);
|
|
457
487
|
}
|
|
458
488
|
}
|
|
459
489
|
/**
|
|
@@ -529,7 +559,7 @@ exports.QueueService = class QueueService {
|
|
|
529
559
|
return job2;
|
|
530
560
|
} catch (err) {
|
|
531
561
|
if (isOperationalError(err)) throw err;
|
|
532
|
-
throw new QueueException(QUEUE_ERROR_CODES.INVALID_REPEAT_OPTIONS,
|
|
562
|
+
throw new QueueException(QUEUE_ERROR_CODES.INVALID_REPEAT_OPTIONS, common.HttpStatus.BAD_REQUEST, {
|
|
533
563
|
reason: "pattern must be a valid cron expression (5- or 6-field)"
|
|
534
564
|
});
|
|
535
565
|
}
|
|
@@ -682,7 +712,7 @@ exports.FlowService = class FlowService {
|
|
|
682
712
|
*/
|
|
683
713
|
requireProducer() {
|
|
684
714
|
if (!this.producer) {
|
|
685
|
-
throw new QueueException(QUEUE_ERROR_CODES.FLOW_DISABLED,
|
|
715
|
+
throw new QueueException(QUEUE_ERROR_CODES.FLOW_DISABLED, common.HttpStatus.SERVICE_UNAVAILABLE);
|
|
686
716
|
}
|
|
687
717
|
return this.producer;
|
|
688
718
|
}
|
|
@@ -750,7 +780,7 @@ exports.MetricsService = class MetricsService {
|
|
|
750
780
|
*/
|
|
751
781
|
ensureEnabled() {
|
|
752
782
|
if (!this.enabled) {
|
|
753
|
-
throw new QueueException(QUEUE_ERROR_CODES.METRICS_DISABLED,
|
|
783
|
+
throw new QueueException(QUEUE_ERROR_CODES.METRICS_DISABLED, common.HttpStatus.SERVICE_UNAVAILABLE);
|
|
754
784
|
}
|
|
755
785
|
}
|
|
756
786
|
};
|
|
@@ -791,10 +821,14 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
791
821
|
worker = new bullmq.Worker(queueName, handler, bullOpts);
|
|
792
822
|
} catch (cause) {
|
|
793
823
|
conn.disconnect();
|
|
794
|
-
throw new QueueException(
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
824
|
+
throw new QueueException(
|
|
825
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
826
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
827
|
+
{
|
|
828
|
+
queueName,
|
|
829
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
830
|
+
}
|
|
831
|
+
);
|
|
798
832
|
}
|
|
799
833
|
attachDefaultErrorListener(worker, this.logger, "Worker", queueName);
|
|
800
834
|
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
@@ -830,10 +864,14 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
830
864
|
worker = new bullmq.Worker(queueName, processorFile, bullOpts);
|
|
831
865
|
} catch (cause) {
|
|
832
866
|
conn.disconnect();
|
|
833
|
-
throw new QueueException(
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
867
|
+
throw new QueueException(
|
|
868
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
869
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
870
|
+
{
|
|
871
|
+
queueName,
|
|
872
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
873
|
+
}
|
|
874
|
+
);
|
|
837
875
|
}
|
|
838
876
|
attachDefaultErrorListener(worker, this.logger, "Worker", queueName);
|
|
839
877
|
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
@@ -906,7 +944,11 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
906
944
|
*/
|
|
907
945
|
guardDuplicate(queueName) {
|
|
908
946
|
if (__privateGet(this, _entries).has(queueName)) {
|
|
909
|
-
throw new QueueException(
|
|
947
|
+
throw new QueueException(
|
|
948
|
+
QUEUE_ERROR_CODES.DUPLICATE_PROCESSOR,
|
|
949
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
950
|
+
{ queueName }
|
|
951
|
+
);
|
|
910
952
|
}
|
|
911
953
|
}
|
|
912
954
|
/**
|
|
@@ -920,30 +962,46 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
920
962
|
validateOptions(opts) {
|
|
921
963
|
if (opts?.concurrency !== void 0) {
|
|
922
964
|
if (opts.concurrency < 1) {
|
|
923
|
-
throw new QueueException(
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
965
|
+
throw new QueueException(
|
|
966
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
967
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
968
|
+
{
|
|
969
|
+
reason: "concurrency must be >= 1",
|
|
970
|
+
actual: opts.concurrency
|
|
971
|
+
}
|
|
972
|
+
);
|
|
927
973
|
}
|
|
928
974
|
if (opts.concurrency > MAX_WORKER_CONCURRENCY) {
|
|
929
|
-
throw new QueueException(
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
975
|
+
throw new QueueException(
|
|
976
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
977
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
978
|
+
{
|
|
979
|
+
reason: `concurrency must be <= ${MAX_WORKER_CONCURRENCY.toString()}`,
|
|
980
|
+
actual: opts.concurrency
|
|
981
|
+
}
|
|
982
|
+
);
|
|
933
983
|
}
|
|
934
984
|
}
|
|
935
985
|
if (opts?.limiter !== void 0) {
|
|
936
986
|
if (opts.limiter.max < 1) {
|
|
937
|
-
throw new QueueException(
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
987
|
+
throw new QueueException(
|
|
988
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
989
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
990
|
+
{
|
|
991
|
+
reason: "limiter.max must be >= 1",
|
|
992
|
+
actual: opts.limiter.max
|
|
993
|
+
}
|
|
994
|
+
);
|
|
941
995
|
}
|
|
942
996
|
if (opts.limiter.duration < 1) {
|
|
943
|
-
throw new QueueException(
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
997
|
+
throw new QueueException(
|
|
998
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
999
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1000
|
+
{
|
|
1001
|
+
reason: "limiter.duration must be >= 1",
|
|
1002
|
+
actual: opts.limiter.duration
|
|
1003
|
+
}
|
|
1004
|
+
);
|
|
947
1005
|
}
|
|
948
1006
|
}
|
|
949
1007
|
}
|
|
@@ -959,17 +1017,25 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
959
1017
|
validateProcessorFile(processorFile) {
|
|
960
1018
|
if (processorFile instanceof URL) {
|
|
961
1019
|
if (processorFile.protocol !== "file:") {
|
|
962
|
-
throw new QueueException(
|
|
963
|
-
|
|
964
|
-
|
|
1020
|
+
throw new QueueException(
|
|
1021
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
1022
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1023
|
+
{
|
|
1024
|
+
reason: "processorFile URL must use the file: protocol"
|
|
1025
|
+
}
|
|
1026
|
+
);
|
|
965
1027
|
}
|
|
966
1028
|
this.assertProcessorExtension(processorFile.pathname);
|
|
967
1029
|
return;
|
|
968
1030
|
}
|
|
969
1031
|
if (!path.isAbsolute(processorFile)) {
|
|
970
|
-
throw new QueueException(
|
|
971
|
-
|
|
972
|
-
|
|
1032
|
+
throw new QueueException(
|
|
1033
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
1034
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1035
|
+
{
|
|
1036
|
+
reason: "processorFile must be an absolute path"
|
|
1037
|
+
}
|
|
1038
|
+
);
|
|
973
1039
|
}
|
|
974
1040
|
this.assertProcessorExtension(processorFile);
|
|
975
1041
|
}
|
|
@@ -983,9 +1049,13 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
983
1049
|
assertProcessorExtension(filePath) {
|
|
984
1050
|
const allowedExtensions = /* @__PURE__ */ new Set([".js", ".cjs", ".mjs"]);
|
|
985
1051
|
if (!allowedExtensions.has(path.extname(filePath))) {
|
|
986
|
-
throw new QueueException(
|
|
987
|
-
|
|
988
|
-
|
|
1052
|
+
throw new QueueException(
|
|
1053
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
1054
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1055
|
+
{
|
|
1056
|
+
reason: "processorFile must have a .js, .cjs, or .mjs extension"
|
|
1057
|
+
}
|
|
1058
|
+
);
|
|
989
1059
|
}
|
|
990
1060
|
}
|
|
991
1061
|
/**
|
|
@@ -1173,7 +1243,11 @@ var ProcessorDiscoveryService = class {
|
|
|
1173
1243
|
const { queueName, workerOptions, _warnedNoConcurrency } = processorMeta;
|
|
1174
1244
|
const ctor = instance.constructor;
|
|
1175
1245
|
if (this.registeredQueues.has(queueName)) {
|
|
1176
|
-
throw new QueueException(
|
|
1246
|
+
throw new QueueException(
|
|
1247
|
+
QUEUE_ERROR_CODES.DUPLICATE_PROCESSOR,
|
|
1248
|
+
common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1249
|
+
{ queueName }
|
|
1250
|
+
);
|
|
1177
1251
|
}
|
|
1178
1252
|
this.registeredQueues.add(queueName);
|
|
1179
1253
|
if (_warnedNoConcurrency) {
|
package/dist/server/index.d.cts
CHANGED
|
@@ -579,7 +579,7 @@ declare class QueueService {
|
|
|
579
579
|
*
|
|
580
580
|
* Opt-in: this provider is registered unconditionally so that injecting it never
|
|
581
581
|
* raises `UnknownDependenciesException`; instead, every operation throws
|
|
582
|
-
* `QueueException(FLOW_DISABLED,
|
|
582
|
+
* `QueueException(FLOW_DISABLED, HttpStatus.SERVICE_UNAVAILABLE)` when `flows.enabled !== true`.
|
|
583
583
|
*
|
|
584
584
|
* @example
|
|
585
585
|
* await flowService.add({
|
package/dist/server/index.d.ts
CHANGED
|
@@ -579,7 +579,7 @@ declare class QueueService {
|
|
|
579
579
|
*
|
|
580
580
|
* Opt-in: this provider is registered unconditionally so that injecting it never
|
|
581
581
|
* raises `UnknownDependenciesException`; instead, every operation throws
|
|
582
|
-
* `QueueException(FLOW_DISABLED,
|
|
582
|
+
* `QueueException(FLOW_DISABLED, HttpStatus.SERVICE_UNAVAILABLE)` when `flows.enabled !== true`.
|
|
583
583
|
*
|
|
584
584
|
* @example
|
|
585
585
|
* await flowService.add({
|
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Injectable, Inject, ConfigurableModuleBuilder, Module, Logger, HttpException
|
|
1
|
+
import { Injectable, Inject, ConfigurableModuleBuilder, Module, HttpStatus, Logger, HttpException } from '@nestjs/common';
|
|
2
2
|
import { DiscoveryService, DiscoveryModule } from '@nestjs/core';
|
|
3
3
|
import { Redis } from 'ioredis';
|
|
4
4
|
import { Worker, QueueEvents, Queue, FlowProducer } from 'bullmq';
|
|
@@ -78,7 +78,9 @@ var QueueException = class extends HttpException {
|
|
|
78
78
|
|
|
79
79
|
// src/server/config/validate-options.ts
|
|
80
80
|
function invalid(reason) {
|
|
81
|
-
throw new QueueException(QUEUE_ERROR_CODES.INVALID_OPTIONS,
|
|
81
|
+
throw new QueueException(QUEUE_ERROR_CODES.INVALID_OPTIONS, HttpStatus.INTERNAL_SERVER_ERROR, {
|
|
82
|
+
reason
|
|
83
|
+
});
|
|
82
84
|
}
|
|
83
85
|
function validateOptions(opts) {
|
|
84
86
|
const cfg = opts.connection;
|
|
@@ -150,15 +152,17 @@ var BYMAX_QUEUE_OPTIONS = /* @__PURE__ */ Symbol("BYMAX_QUEUE_OPTIONS");
|
|
|
150
152
|
var BYMAX_QUEUE_REDIS_CLIENT = /* @__PURE__ */ Symbol("BYMAX_QUEUE_REDIS_CLIENT");
|
|
151
153
|
var BYMAX_QUEUE_CONNECTION_MODE = /* @__PURE__ */ Symbol("BYMAX_QUEUE_CONNECTION_MODE");
|
|
152
154
|
var BYMAX_QUEUE_RESOLVED_OPTIONS = /* @__PURE__ */ Symbol("BYMAX_QUEUE_RESOLVED_OPTIONS");
|
|
153
|
-
|
|
154
|
-
// src/server/utils/validate-connection.ts
|
|
155
155
|
function assertBlockingConnection(client) {
|
|
156
156
|
const actualValue = client.options?.maxRetriesPerRequest;
|
|
157
157
|
if (actualValue !== null) {
|
|
158
|
-
throw new QueueException(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
throw new QueueException(
|
|
159
|
+
QUEUE_ERROR_CODES.CONNECTION_REQUIRES_NULL_RETRIES,
|
|
160
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
161
|
+
{
|
|
162
|
+
actualValue: actualValue ?? null,
|
|
163
|
+
expectedValue: null
|
|
164
|
+
}
|
|
165
|
+
);
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
168
|
function isClientUsable(client) {
|
|
@@ -215,18 +219,26 @@ var ConnectionResolver = class {
|
|
|
215
219
|
/** The resolved Queue-role client. */
|
|
216
220
|
getClient() {
|
|
217
221
|
if (!__privateGet(this, _client)) {
|
|
218
|
-
throw new QueueException(
|
|
219
|
-
|
|
220
|
-
|
|
222
|
+
throw new QueueException(
|
|
223
|
+
QUEUE_ERROR_CODES.CONNECTION_INVALID,
|
|
224
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
225
|
+
{
|
|
226
|
+
reason: "not initialized"
|
|
227
|
+
}
|
|
228
|
+
);
|
|
221
229
|
}
|
|
222
230
|
return __privateGet(this, _client);
|
|
223
231
|
}
|
|
224
232
|
/** The resolved connection mode. */
|
|
225
233
|
getMode() {
|
|
226
234
|
if (!this.mode) {
|
|
227
|
-
throw new QueueException(
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
throw new QueueException(
|
|
236
|
+
QUEUE_ERROR_CODES.CONNECTION_INVALID,
|
|
237
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
238
|
+
{
|
|
239
|
+
reason: "not initialized"
|
|
240
|
+
}
|
|
241
|
+
);
|
|
230
242
|
}
|
|
231
243
|
return this.mode;
|
|
232
244
|
}
|
|
@@ -248,7 +260,11 @@ var ConnectionResolver = class {
|
|
|
248
260
|
this.mode = "mode-a-byo";
|
|
249
261
|
__privateSet(this, _client, client);
|
|
250
262
|
if (!isClientUsable(client)) {
|
|
251
|
-
throw new QueueException(
|
|
263
|
+
throw new QueueException(
|
|
264
|
+
QUEUE_ERROR_CODES.CONNECTION_INVALID,
|
|
265
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
266
|
+
{ status: client.status }
|
|
267
|
+
);
|
|
252
268
|
}
|
|
253
269
|
const probe = duplicateConnection(client);
|
|
254
270
|
try {
|
|
@@ -277,7 +293,13 @@ var ConnectionResolver = class {
|
|
|
277
293
|
};
|
|
278
294
|
const timer = setTimeout(() => {
|
|
279
295
|
cleanup();
|
|
280
|
-
reject(
|
|
296
|
+
reject(
|
|
297
|
+
new QueueException(
|
|
298
|
+
QUEUE_ERROR_CODES.CONNECTION_TIMEOUT,
|
|
299
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
300
|
+
{ timeoutMs }
|
|
301
|
+
)
|
|
302
|
+
);
|
|
281
303
|
}, timeoutMs);
|
|
282
304
|
client.once("ready", onReady);
|
|
283
305
|
client.once("error", onError);
|
|
@@ -290,10 +312,10 @@ ConnectionResolver = __decorateClass([
|
|
|
290
312
|
Injectable(),
|
|
291
313
|
__decorateParam(0, Inject(BYMAX_QUEUE_OPTIONS))
|
|
292
314
|
], ConnectionResolver);
|
|
293
|
-
|
|
294
|
-
// src/server/utils/validate-job-scheduler-options.ts
|
|
295
315
|
function invalid2(reason) {
|
|
296
|
-
throw new QueueException(QUEUE_ERROR_CODES.INVALID_REPEAT_OPTIONS,
|
|
316
|
+
throw new QueueException(QUEUE_ERROR_CODES.INVALID_REPEAT_OPTIONS, HttpStatus.BAD_REQUEST, {
|
|
317
|
+
reason
|
|
318
|
+
});
|
|
297
319
|
}
|
|
298
320
|
function toEpochMs(date) {
|
|
299
321
|
return typeof date === "number" ? date : Date.parse(date);
|
|
@@ -433,11 +455,15 @@ var QueueService = class {
|
|
|
433
455
|
*/
|
|
434
456
|
async enqueueBulk(queueName, jobs) {
|
|
435
457
|
if (jobs.length > MAX_BULK_SIZE) {
|
|
436
|
-
throw new QueueException(
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
458
|
+
throw new QueueException(
|
|
459
|
+
QUEUE_ERROR_CODES.BULK_ENQUEUE_FAILED,
|
|
460
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
461
|
+
{
|
|
462
|
+
reason: "batch size exceeds limit",
|
|
463
|
+
limit: MAX_BULK_SIZE,
|
|
464
|
+
received: jobs.length
|
|
465
|
+
}
|
|
466
|
+
);
|
|
441
467
|
}
|
|
442
468
|
try {
|
|
443
469
|
const created = await this.queueFor(queueName).addBulk(
|
|
@@ -449,9 +475,13 @@ var QueueService = class {
|
|
|
449
475
|
);
|
|
450
476
|
return created;
|
|
451
477
|
} catch (err) {
|
|
452
|
-
throw new QueueException(
|
|
453
|
-
|
|
454
|
-
|
|
478
|
+
throw new QueueException(
|
|
479
|
+
QUEUE_ERROR_CODES.BULK_ENQUEUE_FAILED,
|
|
480
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
481
|
+
{
|
|
482
|
+
cause: err.message
|
|
483
|
+
}
|
|
484
|
+
);
|
|
455
485
|
}
|
|
456
486
|
}
|
|
457
487
|
/**
|
|
@@ -527,7 +557,7 @@ var QueueService = class {
|
|
|
527
557
|
return job2;
|
|
528
558
|
} catch (err) {
|
|
529
559
|
if (isOperationalError(err)) throw err;
|
|
530
|
-
throw new QueueException(QUEUE_ERROR_CODES.INVALID_REPEAT_OPTIONS,
|
|
560
|
+
throw new QueueException(QUEUE_ERROR_CODES.INVALID_REPEAT_OPTIONS, HttpStatus.BAD_REQUEST, {
|
|
531
561
|
reason: "pattern must be a valid cron expression (5- or 6-field)"
|
|
532
562
|
});
|
|
533
563
|
}
|
|
@@ -680,7 +710,7 @@ var FlowService = class {
|
|
|
680
710
|
*/
|
|
681
711
|
requireProducer() {
|
|
682
712
|
if (!this.producer) {
|
|
683
|
-
throw new QueueException(QUEUE_ERROR_CODES.FLOW_DISABLED,
|
|
713
|
+
throw new QueueException(QUEUE_ERROR_CODES.FLOW_DISABLED, HttpStatus.SERVICE_UNAVAILABLE);
|
|
684
714
|
}
|
|
685
715
|
return this.producer;
|
|
686
716
|
}
|
|
@@ -748,7 +778,7 @@ var MetricsService = class {
|
|
|
748
778
|
*/
|
|
749
779
|
ensureEnabled() {
|
|
750
780
|
if (!this.enabled) {
|
|
751
|
-
throw new QueueException(QUEUE_ERROR_CODES.METRICS_DISABLED,
|
|
781
|
+
throw new QueueException(QUEUE_ERROR_CODES.METRICS_DISABLED, HttpStatus.SERVICE_UNAVAILABLE);
|
|
752
782
|
}
|
|
753
783
|
}
|
|
754
784
|
};
|
|
@@ -789,10 +819,14 @@ var WorkerRegistry = class {
|
|
|
789
819
|
worker = new Worker(queueName, handler, bullOpts);
|
|
790
820
|
} catch (cause) {
|
|
791
821
|
conn.disconnect();
|
|
792
|
-
throw new QueueException(
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
822
|
+
throw new QueueException(
|
|
823
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
824
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
825
|
+
{
|
|
826
|
+
queueName,
|
|
827
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
828
|
+
}
|
|
829
|
+
);
|
|
796
830
|
}
|
|
797
831
|
attachDefaultErrorListener(worker, this.logger, "Worker", queueName);
|
|
798
832
|
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
@@ -828,10 +862,14 @@ var WorkerRegistry = class {
|
|
|
828
862
|
worker = new Worker(queueName, processorFile, bullOpts);
|
|
829
863
|
} catch (cause) {
|
|
830
864
|
conn.disconnect();
|
|
831
|
-
throw new QueueException(
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
865
|
+
throw new QueueException(
|
|
866
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
867
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
868
|
+
{
|
|
869
|
+
queueName,
|
|
870
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
871
|
+
}
|
|
872
|
+
);
|
|
835
873
|
}
|
|
836
874
|
attachDefaultErrorListener(worker, this.logger, "Worker", queueName);
|
|
837
875
|
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
@@ -904,7 +942,11 @@ var WorkerRegistry = class {
|
|
|
904
942
|
*/
|
|
905
943
|
guardDuplicate(queueName) {
|
|
906
944
|
if (__privateGet(this, _entries).has(queueName)) {
|
|
907
|
-
throw new QueueException(
|
|
945
|
+
throw new QueueException(
|
|
946
|
+
QUEUE_ERROR_CODES.DUPLICATE_PROCESSOR,
|
|
947
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
948
|
+
{ queueName }
|
|
949
|
+
);
|
|
908
950
|
}
|
|
909
951
|
}
|
|
910
952
|
/**
|
|
@@ -918,30 +960,46 @@ var WorkerRegistry = class {
|
|
|
918
960
|
validateOptions(opts) {
|
|
919
961
|
if (opts?.concurrency !== void 0) {
|
|
920
962
|
if (opts.concurrency < 1) {
|
|
921
|
-
throw new QueueException(
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
963
|
+
throw new QueueException(
|
|
964
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
965
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
966
|
+
{
|
|
967
|
+
reason: "concurrency must be >= 1",
|
|
968
|
+
actual: opts.concurrency
|
|
969
|
+
}
|
|
970
|
+
);
|
|
925
971
|
}
|
|
926
972
|
if (opts.concurrency > MAX_WORKER_CONCURRENCY) {
|
|
927
|
-
throw new QueueException(
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
973
|
+
throw new QueueException(
|
|
974
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
975
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
976
|
+
{
|
|
977
|
+
reason: `concurrency must be <= ${MAX_WORKER_CONCURRENCY.toString()}`,
|
|
978
|
+
actual: opts.concurrency
|
|
979
|
+
}
|
|
980
|
+
);
|
|
931
981
|
}
|
|
932
982
|
}
|
|
933
983
|
if (opts?.limiter !== void 0) {
|
|
934
984
|
if (opts.limiter.max < 1) {
|
|
935
|
-
throw new QueueException(
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
985
|
+
throw new QueueException(
|
|
986
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
987
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
988
|
+
{
|
|
989
|
+
reason: "limiter.max must be >= 1",
|
|
990
|
+
actual: opts.limiter.max
|
|
991
|
+
}
|
|
992
|
+
);
|
|
939
993
|
}
|
|
940
994
|
if (opts.limiter.duration < 1) {
|
|
941
|
-
throw new QueueException(
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
995
|
+
throw new QueueException(
|
|
996
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
997
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
998
|
+
{
|
|
999
|
+
reason: "limiter.duration must be >= 1",
|
|
1000
|
+
actual: opts.limiter.duration
|
|
1001
|
+
}
|
|
1002
|
+
);
|
|
945
1003
|
}
|
|
946
1004
|
}
|
|
947
1005
|
}
|
|
@@ -957,17 +1015,25 @@ var WorkerRegistry = class {
|
|
|
957
1015
|
validateProcessorFile(processorFile) {
|
|
958
1016
|
if (processorFile instanceof URL) {
|
|
959
1017
|
if (processorFile.protocol !== "file:") {
|
|
960
|
-
throw new QueueException(
|
|
961
|
-
|
|
962
|
-
|
|
1018
|
+
throw new QueueException(
|
|
1019
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
1020
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1021
|
+
{
|
|
1022
|
+
reason: "processorFile URL must use the file: protocol"
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
963
1025
|
}
|
|
964
1026
|
this.assertProcessorExtension(processorFile.pathname);
|
|
965
1027
|
return;
|
|
966
1028
|
}
|
|
967
1029
|
if (!isAbsolute(processorFile)) {
|
|
968
|
-
throw new QueueException(
|
|
969
|
-
|
|
970
|
-
|
|
1030
|
+
throw new QueueException(
|
|
1031
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
1032
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1033
|
+
{
|
|
1034
|
+
reason: "processorFile must be an absolute path"
|
|
1035
|
+
}
|
|
1036
|
+
);
|
|
971
1037
|
}
|
|
972
1038
|
this.assertProcessorExtension(processorFile);
|
|
973
1039
|
}
|
|
@@ -981,9 +1047,13 @@ var WorkerRegistry = class {
|
|
|
981
1047
|
assertProcessorExtension(filePath) {
|
|
982
1048
|
const allowedExtensions = /* @__PURE__ */ new Set([".js", ".cjs", ".mjs"]);
|
|
983
1049
|
if (!allowedExtensions.has(extname(filePath))) {
|
|
984
|
-
throw new QueueException(
|
|
985
|
-
|
|
986
|
-
|
|
1050
|
+
throw new QueueException(
|
|
1051
|
+
QUEUE_ERROR_CODES.WORKER_REGISTRATION_FAILED,
|
|
1052
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1053
|
+
{
|
|
1054
|
+
reason: "processorFile must have a .js, .cjs, or .mjs extension"
|
|
1055
|
+
}
|
|
1056
|
+
);
|
|
987
1057
|
}
|
|
988
1058
|
}
|
|
989
1059
|
/**
|
|
@@ -1171,7 +1241,11 @@ var ProcessorDiscoveryService = class {
|
|
|
1171
1241
|
const { queueName, workerOptions, _warnedNoConcurrency } = processorMeta;
|
|
1172
1242
|
const ctor = instance.constructor;
|
|
1173
1243
|
if (this.registeredQueues.has(queueName)) {
|
|
1174
|
-
throw new QueueException(
|
|
1244
|
+
throw new QueueException(
|
|
1245
|
+
QUEUE_ERROR_CODES.DUPLICATE_PROCESSOR,
|
|
1246
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
1247
|
+
{ queueName }
|
|
1248
|
+
);
|
|
1175
1249
|
}
|
|
1176
1250
|
this.registeredQueues.add(queueName);
|
|
1177
1251
|
if (_warnedNoConcurrency) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-queue",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
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",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@stryker-mutator/core": "^10.0.0",
|
|
104
104
|
"@stryker-mutator/jest-runner": "^10.0.0",
|
|
105
105
|
"@types/jest": "^30.0.0",
|
|
106
|
-
"@types/node": "^
|
|
106
|
+
"@types/node": "^24.19.2",
|
|
107
107
|
"bullmq": "^6.0.0",
|
|
108
108
|
"eslint": "^10.8.1",
|
|
109
109
|
"eslint-config-prettier": "^10.1.8",
|