@bymax-one/nest-queue 1.2.10 → 1.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -1
- package/README.md +20 -16
- package/dist/server/index.cjs +7 -489
- package/dist/server/index.d.cts +29 -10
- package/dist/server/index.d.ts +29 -10
- package/dist/server/index.mjs +7 -489
- package/package.json +2 -2
package/dist/server/index.cjs
CHANGED
|
@@ -165,7 +165,6 @@ function applyDefaults(opts) {
|
|
|
165
165
|
Object.defineProperty(base, "connection", {
|
|
166
166
|
get: () => connection,
|
|
167
167
|
enumerable: false,
|
|
168
|
-
// 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
|
|
169
168
|
configurable: false
|
|
170
169
|
});
|
|
171
170
|
return Object.freeze(base);
|
|
@@ -202,21 +201,11 @@ function duplicateConnection(client) {
|
|
|
202
201
|
var _client, _options;
|
|
203
202
|
exports.ConnectionResolver = class ConnectionResolver {
|
|
204
203
|
constructor(options) {
|
|
205
|
-
/**
|
|
206
|
-
* The resolved Redis client.
|
|
207
|
-
*
|
|
208
|
-
* An ECMAScript private field rather than a TypeScript `private` one, which
|
|
209
|
-
* is erased at runtime: an ioredis instance carries `options.password` as a
|
|
210
|
-
* plain field, so leaving this enumerable would let anything that serializes
|
|
211
|
-
* a service holding this resolver walk into the credentials.
|
|
212
|
-
*/
|
|
213
204
|
__privateAdd(this, _client);
|
|
214
|
-
/** The consumer's module options, which carry the Redis credentials. */
|
|
215
205
|
__privateAdd(this, _options);
|
|
216
206
|
__publicField(this, "mode");
|
|
217
207
|
__privateSet(this, _options, options);
|
|
218
208
|
}
|
|
219
|
-
/** Resolve and validate the connection. Call once during module bootstrap. */
|
|
220
209
|
async init() {
|
|
221
210
|
const cfg = __privateGet(this, _options).connection;
|
|
222
211
|
if ("client" in cfg) {
|
|
@@ -229,18 +218,9 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
229
218
|
__privateGet(this, _options).connectionReadyTimeoutMs ?? DEFAULT_CONNECTION_READY_TIMEOUT_MS
|
|
230
219
|
);
|
|
231
220
|
}
|
|
232
|
-
/**
|
|
233
|
-
* Merge the consumer's options with the owned-connection policy
|
|
234
|
-
* (`lazyConnect: false`, so the client connects eagerly and `init` can wait
|
|
235
|
-
* for `ready`) and narrow the result to the shape ioredis 6's constructor
|
|
236
|
-
* accepts. The runtime object is unchanged — the cast only drops the
|
|
237
|
-
* `undefined` that `RedisOptions.replyMapping` carries and the constructor
|
|
238
|
-
* rejects.
|
|
239
|
-
*/
|
|
240
221
|
ownedOptions(options) {
|
|
241
222
|
return { ...options ?? {}, lazyConnect: false };
|
|
242
223
|
}
|
|
243
|
-
/** The resolved Queue-role client. */
|
|
244
224
|
getClient() {
|
|
245
225
|
if (!__privateGet(this, _client)) {
|
|
246
226
|
throw new QueueException(
|
|
@@ -253,7 +233,6 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
253
233
|
}
|
|
254
234
|
return __privateGet(this, _client);
|
|
255
235
|
}
|
|
256
|
-
/** The resolved connection mode. */
|
|
257
236
|
getMode() {
|
|
258
237
|
if (!this.mode) {
|
|
259
238
|
throw new QueueException(
|
|
@@ -266,11 +245,9 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
266
245
|
}
|
|
267
246
|
return this.mode;
|
|
268
247
|
}
|
|
269
|
-
/** Whether the library owns (and must close) the connection. */
|
|
270
248
|
isOwned() {
|
|
271
249
|
return this.mode === "mode-b-owned";
|
|
272
250
|
}
|
|
273
|
-
/** Close the library-owned connection on shutdown; never touch a BYO client. */
|
|
274
251
|
async teardown() {
|
|
275
252
|
if (this.isOwned() && __privateGet(this, _client)) {
|
|
276
253
|
const client = __privateGet(this, _client);
|
|
@@ -279,7 +256,6 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
279
256
|
});
|
|
280
257
|
}
|
|
281
258
|
}
|
|
282
|
-
/** Validate and adopt a bring-your-own client for the Queue role. */
|
|
283
259
|
initModeA(client) {
|
|
284
260
|
this.mode = "mode-a-byo";
|
|
285
261
|
__privateSet(this, _client, client);
|
|
@@ -297,7 +273,6 @@ exports.ConnectionResolver = class ConnectionResolver {
|
|
|
297
273
|
probe.disconnect();
|
|
298
274
|
}
|
|
299
275
|
}
|
|
300
|
-
/** Resolve when the owned client reaches `ready`, or reject on timeout/error. */
|
|
301
276
|
async waitReady(timeoutMs) {
|
|
302
277
|
const client = this.getClient();
|
|
303
278
|
if (client.status === "ready") return;
|
|
@@ -409,14 +384,6 @@ exports.QueueService = class QueueService {
|
|
|
409
384
|
}
|
|
410
385
|
connection;
|
|
411
386
|
options;
|
|
412
|
-
/**
|
|
413
|
-
* Return the cached `Queue` for `queueName`, or create one with the module
|
|
414
|
-
* defaults applied. Subsequent calls with the same name return the same
|
|
415
|
-
* instance.
|
|
416
|
-
*
|
|
417
|
-
* @param queueName - Unique queue name (used as the Redis key prefix).
|
|
418
|
-
* @param overrides - Optional per-queue option overrides.
|
|
419
|
-
*/
|
|
420
387
|
getOrCreateQueue(queueName, overrides) {
|
|
421
388
|
const existing = __privateGet(this, _queues).get(queueName);
|
|
422
389
|
if (existing) return existing;
|
|
@@ -432,77 +399,13 @@ exports.QueueService = class QueueService {
|
|
|
432
399
|
__privateGet(this, _queues).set(queueName, queue);
|
|
433
400
|
return queue;
|
|
434
401
|
}
|
|
435
|
-
/** Resolve the raw cached queue (default generics) for internal delegation. */
|
|
436
402
|
queueFor(queueName) {
|
|
437
403
|
return this.getOrCreateQueue(queueName);
|
|
438
404
|
}
|
|
439
|
-
/**
|
|
440
|
-
* Add a single job to a queue. The queue is created lazily if needed.
|
|
441
|
-
*
|
|
442
|
-
* Delivery is **at-least-once** — a handler may run more than once for the same
|
|
443
|
-
* job (worker crash, lock expiry, or a shutdown force-close). Make handlers
|
|
444
|
-
* idempotent: use an idempotency key on writes, prefer upserts over inserts, or
|
|
445
|
-
* keep an "already-processed" marker keyed by `job.id`.
|
|
446
|
-
*
|
|
447
|
-
* `options` surfaces BullMQ natives directly — including `jobId` (idempotent
|
|
448
|
-
* insert: a second add with the same id is a no-op while the first exists) and
|
|
449
|
-
* `deduplication` (windowed deduplication). The library writes no deduplication
|
|
450
|
-
* code; BullMQ owns the behavior. The deduplication key is independent of
|
|
451
|
-
* `jobId`, so both can be set together or apart. Both collapse duplicate
|
|
452
|
-
* **producers** and do not change the at-least-once guarantee on the
|
|
453
|
-
* **consumer** side.
|
|
454
|
-
*
|
|
455
|
-
* The deduplication `id` is a shared key across the queue, so a multi-tenant
|
|
456
|
-
* producer MUST scope it by tenant: keyed on the value alone
|
|
457
|
-
* (`` `reindex:${term}` ``), tenant A enqueuing a term suppresses tenant B's job
|
|
458
|
-
* for the same term.
|
|
459
|
-
*
|
|
460
|
-
* Scoping alone is not enough. Two variable fields joined by a delimiter that
|
|
461
|
-
* either may contain do not produce a unique key: with `` `${tenantId}:${term}` ``,
|
|
462
|
-
* the pairs `('a', 'b:c')` and `('a:b', 'c')` both yield `a:b:c`. Percent-encode
|
|
463
|
-
* every variable field so the delimiter cannot appear inside one:
|
|
464
|
-
* `` `${encodeURIComponent(tenantId)}:${encodeURIComponent(term)}` ``.
|
|
465
|
-
*
|
|
466
|
-
* Deduplication modes (`deduplication: { id, ttl?, extend?, replace?, keepLastIfActive? }`):
|
|
467
|
-
* - Simple `{ id }` — collapse duplicates until the in-flight job completes or fails.
|
|
468
|
-
* - Throttle `{ id, ttl }` — ignore duplicates for `ttl` milliseconds.
|
|
469
|
-
* - Debounce `{ id, ttl, extend: true, replace: true }` — keep only the latest
|
|
470
|
-
* data; each duplicate resets the TTL.
|
|
471
|
-
* - keep-last-if-active `{ id, keepLastIfActive: true }` — while a job is active,
|
|
472
|
-
* store the latest data and run a single follow-up when it finishes.
|
|
473
|
-
*
|
|
474
|
-
* @param queueName - Target queue (created lazily if absent).
|
|
475
|
-
* @param jobName - Job identifier used by `@Process` to dispatch.
|
|
476
|
-
* @param data - Typed job payload.
|
|
477
|
-
* @param options - Per-job BullMQ options (priority, delay, `jobId`, `deduplication`, ...).
|
|
478
|
-
* @returns The job BullMQ returned. When a custom `jobId` already exists, or a `deduplication`
|
|
479
|
-
* rule keeps the job already there (one with `replace` swaps it for the new job), nothing new
|
|
480
|
-
* is added: BullMQ answers with the id of the EXISTING job and the data and options of THIS
|
|
481
|
-
* call, so the returned object does not describe what is stored. Call
|
|
482
|
-
* {@link QueueService.getJob} when you need the stored job.
|
|
483
|
-
* @example Throttle: at most one reindex per term every 5 seconds. Both variable
|
|
484
|
-
* fields are percent-encoded, so no other tenant/term pair can build this key.
|
|
485
|
-
* const dedupId = `${encodeURIComponent(tenantId)}:reindex:${encodeURIComponent(term)}`
|
|
486
|
-
* await queueService.enqueue('search', 'reindex', { term }, {
|
|
487
|
-
* deduplication: { id: dedupId, ttl: 5_000 },
|
|
488
|
-
* })
|
|
489
|
-
*/
|
|
490
405
|
async enqueue(queueName, jobName, data, options) {
|
|
491
406
|
const job = await this.queueFor(queueName).add(jobName, data, options);
|
|
492
407
|
return job;
|
|
493
408
|
}
|
|
494
|
-
/**
|
|
495
|
-
* Enqueue multiple jobs in a single Redis roundtrip. The batch is bounded by
|
|
496
|
-
* {@link MAX_BULK_SIZE} to guard against a self-inflicted memory spike; any
|
|
497
|
-
* failure is wrapped in a `QueueException`. Delivery is at-least-once, so every
|
|
498
|
-
* job's handler must be idempotent (see {@link QueueService.enqueue}).
|
|
499
|
-
*
|
|
500
|
-
* @param queueName - Target queue.
|
|
501
|
-
* @param jobs - Job descriptors (length must not exceed the bulk size cap).
|
|
502
|
-
* @returns The jobs BullMQ returned, in input order. An entry whose custom `jobId` already
|
|
503
|
-
* exists is not added again: it carries the EXISTING id with the data of this call, as for
|
|
504
|
-
* {@link QueueService.enqueue}.
|
|
505
|
-
*/
|
|
506
409
|
async enqueueBulk(queueName, jobs) {
|
|
507
410
|
if (jobs.length > MAX_BULK_SIZE) {
|
|
508
411
|
throw new QueueException(
|
|
@@ -534,33 +437,14 @@ exports.QueueService = class QueueService {
|
|
|
534
437
|
);
|
|
535
438
|
}
|
|
536
439
|
}
|
|
537
|
-
/**
|
|
538
|
-
* Fetch a job by id. Returns `null` when the job does not exist.
|
|
539
|
-
*
|
|
540
|
-
* @param queueName - Target queue.
|
|
541
|
-
* @param jobId - The job identifier.
|
|
542
|
-
*/
|
|
543
440
|
async getJob(queueName, jobId) {
|
|
544
441
|
const job = await this.queueFor(queueName).getJob(jobId);
|
|
545
442
|
return job ?? null;
|
|
546
443
|
}
|
|
547
|
-
/**
|
|
548
|
-
* Fetch jobs in a given status with pagination.
|
|
549
|
-
*
|
|
550
|
-
* @param queueName - Target queue.
|
|
551
|
-
* @param status - Status filter.
|
|
552
|
-
* @param start - Starting index (inclusive). Default: 0.
|
|
553
|
-
* @param end - Ending index (inclusive). Default: 50.
|
|
554
|
-
*/
|
|
555
444
|
async getJobs(queueName, status, start = 0, end = 50) {
|
|
556
445
|
const jobs = await this.queueFor(queueName).getJobs([status], start, end);
|
|
557
446
|
return jobs;
|
|
558
447
|
}
|
|
559
|
-
/**
|
|
560
|
-
* Return an uncached snapshot of job counts grouped by status.
|
|
561
|
-
*
|
|
562
|
-
* @param queueName - Target queue.
|
|
563
|
-
*/
|
|
564
448
|
async getMetrics(queueName) {
|
|
565
449
|
const counts = await this.queueFor(queueName).getJobCounts(
|
|
566
450
|
"waiting",
|
|
@@ -575,24 +459,6 @@ exports.QueueService = class QueueService {
|
|
|
575
459
|
collectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
576
460
|
};
|
|
577
461
|
}
|
|
578
|
-
/**
|
|
579
|
-
* Create or update a Job Scheduler — the current BullMQ recurring-jobs API.
|
|
580
|
-
* Idempotent by `schedulerId`: BullMQ performs an atomic `override: true`
|
|
581
|
-
* upsert, so re-registering on every boot updates the schedule in place and
|
|
582
|
-
* never duplicates it. This supersedes the repeatable-jobs surface removed in
|
|
583
|
-
* BullMQ v6.
|
|
584
|
-
*
|
|
585
|
-
* The schedule is validated first, and a cron pattern BullMQ's parser rejects
|
|
586
|
-
* is `INVALID_REPEAT_OPTIONS` (400) before Redis is asked; any other failure
|
|
587
|
-
* propagates as it is.
|
|
588
|
-
*
|
|
589
|
-
* @param queueName - Target queue.
|
|
590
|
-
* @param schedulerId - Stable, unique scheduler identifier (the upsert key).
|
|
591
|
-
* @param repeat - Cron `pattern` OR `every` (ms), plus optional tz/limit/start/end.
|
|
592
|
-
* @param template - Optional job template; `name` defaults to `schedulerId`, `data` to `{}`.
|
|
593
|
-
* @returns The first scheduled (delayed) job, or `undefined`.
|
|
594
|
-
* @throws {QueueException} `INVALID_REPEAT_OPTIONS` (400) for an invalid schedule.
|
|
595
|
-
*/
|
|
596
462
|
async upsertJobScheduler(queueName, schedulerId, repeat, template) {
|
|
597
463
|
validateJobSchedulerOptions(repeat);
|
|
598
464
|
const queue = this.queueFor(queueName);
|
|
@@ -605,62 +471,24 @@ exports.QueueService = class QueueService {
|
|
|
605
471
|
const job = await queue.upsertJobScheduler(schedulerId, repeat, jobTemplate);
|
|
606
472
|
return job;
|
|
607
473
|
}
|
|
608
|
-
/**
|
|
609
|
-
* Remove a Job Scheduler by id.
|
|
610
|
-
*
|
|
611
|
-
* @param queueName - Target queue.
|
|
612
|
-
* @param schedulerId - The scheduler identifier to remove.
|
|
613
|
-
* @returns `true` when a scheduler was removed, `false` otherwise.
|
|
614
|
-
*/
|
|
615
474
|
async removeJobScheduler(queueName, schedulerId) {
|
|
616
475
|
return this.queueFor(queueName).removeJobScheduler(schedulerId);
|
|
617
476
|
}
|
|
618
|
-
/**
|
|
619
|
-
* List the Job Schedulers registered on a queue, for inspection or health checks.
|
|
620
|
-
*
|
|
621
|
-
* @param queueName - Target queue.
|
|
622
|
-
* @param start - Page start index (inclusive). Default: 0.
|
|
623
|
-
* @param end - Page end index (inclusive). Default: 50.
|
|
624
|
-
* @param asc - Sort ascending by next run time. Default: true.
|
|
625
|
-
* @returns The registered schedulers in the requested page.
|
|
626
|
-
*/
|
|
627
477
|
async getJobSchedulers(queueName, start = 0, end = 50, asc = true) {
|
|
628
478
|
return this.queueFor(queueName).getJobSchedulers(start, end, asc);
|
|
629
479
|
}
|
|
630
|
-
/** Pause a queue, halting the processing of new jobs. */
|
|
631
480
|
async pauseQueue(queueName) {
|
|
632
481
|
await this.queueFor(queueName).pause();
|
|
633
482
|
}
|
|
634
|
-
/** Resume a previously paused queue. */
|
|
635
483
|
async resumeQueue(queueName) {
|
|
636
484
|
await this.queueFor(queueName).resume();
|
|
637
485
|
}
|
|
638
|
-
/**
|
|
639
|
-
* Remove up to `limit` jobs older than `gracePeriodMs` in a given status. The
|
|
640
|
-
* argument order mirrors BullMQ's `Queue.clean(grace, limit, type?)` exactly;
|
|
641
|
-
* `limit` is required and `0` means no limit. Returns the removed job ids.
|
|
642
|
-
*
|
|
643
|
-
* @param queueName - Target queue.
|
|
644
|
-
* @param gracePeriodMs - Keep jobs younger than this many milliseconds.
|
|
645
|
-
* @param limit - Maximum number of jobs to remove (`0` = no limit).
|
|
646
|
-
* @param status - Status of jobs to clean.
|
|
647
|
-
*/
|
|
648
486
|
async cleanQueue(queueName, gracePeriodMs, limit, status) {
|
|
649
487
|
return this.queueFor(queueName).clean(gracePeriodMs, limit, status);
|
|
650
488
|
}
|
|
651
|
-
/** Return a read-only view of the cached queues. */
|
|
652
489
|
getCachedQueues() {
|
|
653
490
|
return __privateGet(this, _queues);
|
|
654
491
|
}
|
|
655
|
-
/**
|
|
656
|
-
* Close every cached queue (swallowing per-queue errors) and clear the cache.
|
|
657
|
-
*
|
|
658
|
-
* Deliberately NOT named `onModuleDestroy`: NestJS invokes lifecycle hooks by
|
|
659
|
-
* method name, so a hook here would run on Nest's own schedule, concurrently
|
|
660
|
-
* with — and possibly before — the bounded drain in `QueueLifecycle`. Closing a
|
|
661
|
-
* queue while its worker is still draining is exactly what the ordered sequence
|
|
662
|
-
* exists to prevent. `QueueLifecycle` is the only caller.
|
|
663
|
-
*/
|
|
664
492
|
async closeAll() {
|
|
665
493
|
for (const queue of __privateGet(this, _queues).values()) {
|
|
666
494
|
await queue.close().catch(() => void 0);
|
|
@@ -681,77 +509,26 @@ exports.FlowService = class FlowService {
|
|
|
681
509
|
if (enabled) {
|
|
682
510
|
this.producer = new bullmq.FlowProducer({
|
|
683
511
|
connection: connection.getClient(),
|
|
684
|
-
// Match the producer Queue's key prefix so flow jobs land in the same
|
|
685
|
-
// keyspace the workers poll; a non-default prefix breaks flows otherwise.
|
|
686
512
|
prefix,
|
|
687
513
|
...telemetry ? { telemetry } : {}
|
|
688
514
|
});
|
|
689
515
|
attachDefaultErrorListener(this.producer, this.logger, "FlowProducer", "*");
|
|
690
516
|
}
|
|
691
517
|
}
|
|
692
|
-
/**
|
|
693
|
-
* Add a flow tree (a parent with its descendant children). The root job
|
|
694
|
-
* becomes processable only after every descendant has completed.
|
|
695
|
-
*
|
|
696
|
-
* By BullMQ default a failed (retry-exhausted) child does NOT fail its parent:
|
|
697
|
-
* the parent stays in the `waiting-children` state until the child eventually
|
|
698
|
-
* completes. To make a child's failure propagate upward and fail its parent,
|
|
699
|
-
* set `failParentOnFailure: true` on that child (it cascades when ancestors
|
|
700
|
-
* also set it). To let a parent proceed despite a failed child, set
|
|
701
|
-
* `ignoreDependencyOnFailure: true` on that child. Bound each child's
|
|
702
|
-
* `attempts` so an unrecoverable child cannot leave the parent waiting forever.
|
|
703
|
-
*
|
|
704
|
-
* @param flow - Flow tree definition.
|
|
705
|
-
* @returns The root `JobNode` containing the created job and its children.
|
|
706
|
-
* A job whose custom `jobId` already exists is not added again: its node carries the EXISTING
|
|
707
|
-
* id with the data of this call, so the tree does not describe what is stored; call
|
|
708
|
-
* `QueueService.getJob` when you need it.
|
|
709
|
-
* @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
|
|
710
|
-
*/
|
|
711
518
|
async add(flow) {
|
|
712
519
|
return this.requireProducer().add(flow);
|
|
713
520
|
}
|
|
714
|
-
/**
|
|
715
|
-
* Add multiple flow trees in a single Redis roundtrip.
|
|
716
|
-
*
|
|
717
|
-
* @param flows - Flow tree definitions.
|
|
718
|
-
* @returns The root `JobNode` for each flow, in input order.
|
|
719
|
-
* A job whose custom `jobId` already exists is not added again; see {@link FlowService.add}.
|
|
720
|
-
* @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
|
|
721
|
-
*/
|
|
722
521
|
async addBulk(flows) {
|
|
723
522
|
return this.requireProducer().addBulk([...flows]);
|
|
724
523
|
}
|
|
725
|
-
/**
|
|
726
|
-
* Escape hatch returning the underlying `FlowProducer` for advanced use cases.
|
|
727
|
-
* Prefer {@link FlowService.add} / {@link FlowService.addBulk} when possible.
|
|
728
|
-
*
|
|
729
|
-
* @returns The underlying BullMQ `FlowProducer`.
|
|
730
|
-
* @throws {QueueException} `FLOW_DISABLED` (503) when flows are not enabled.
|
|
731
|
-
*/
|
|
732
524
|
getProducer() {
|
|
733
525
|
return this.requireProducer();
|
|
734
526
|
}
|
|
735
|
-
/**
|
|
736
|
-
* Close the producer when active; a no-op when inactive. A failed `close()` is
|
|
737
|
-
* swallowed so it never aborts the shutdown sequence.
|
|
738
|
-
*
|
|
739
|
-
* Deliberately NOT named `onModuleDestroy`: NestJS invokes lifecycle hooks by
|
|
740
|
-
* method name, so a hook here would run in addition to the call the ordered
|
|
741
|
-
* sequence already makes, closing the producer twice. `QueueLifecycle` is the
|
|
742
|
-
* only caller.
|
|
743
|
-
*/
|
|
744
527
|
async close() {
|
|
745
528
|
if (this.producer) {
|
|
746
529
|
await this.producer.close().catch(() => void 0);
|
|
747
530
|
}
|
|
748
531
|
}
|
|
749
|
-
/**
|
|
750
|
-
* Return the active producer or throw when flows are disabled. The producer is
|
|
751
|
-
* present if and only if flows were enabled at construction time.
|
|
752
|
-
*
|
|
753
|
-
* @throws {QueueException} `FLOW_DISABLED` (503) when no producer exists.
|
|
754
|
-
*/
|
|
755
532
|
requireProducer() {
|
|
756
533
|
if (!this.producer) {
|
|
757
534
|
throw new QueueException(QUEUE_ERROR_CODES.FLOW_DISABLED, common.HttpStatus.SERVICE_UNAVAILABLE);
|
|
@@ -772,14 +549,6 @@ exports.MetricsService = class MetricsService {
|
|
|
772
549
|
enabled;
|
|
773
550
|
ttlMs;
|
|
774
551
|
cache = /* @__PURE__ */ new Map();
|
|
775
|
-
/**
|
|
776
|
-
* Return cached metrics for `queueName`, or fetch fresh (and cache them) on a
|
|
777
|
-
* miss or after expiry.
|
|
778
|
-
*
|
|
779
|
-
* @param queueName - Target queue.
|
|
780
|
-
* @returns The current metrics snapshot.
|
|
781
|
-
* @throws {QueueException} `METRICS_DISABLED` (503) when metrics are disabled.
|
|
782
|
-
*/
|
|
783
552
|
async get(queueName) {
|
|
784
553
|
this.ensureEnabled();
|
|
785
554
|
const entry = this.cache.get(queueName);
|
|
@@ -790,23 +559,11 @@ exports.MetricsService = class MetricsService {
|
|
|
790
559
|
this.cache.set(queueName, { metrics, expiresAt: Date.now() + this.ttlMs });
|
|
791
560
|
return metrics;
|
|
792
561
|
}
|
|
793
|
-
/**
|
|
794
|
-
* Return a metrics snapshot for every queue currently cached in `QueueService`.
|
|
795
|
-
*
|
|
796
|
-
* @returns Metrics for all known queues.
|
|
797
|
-
* @throws {QueueException} `METRICS_DISABLED` (503) when metrics are disabled.
|
|
798
|
-
*/
|
|
799
562
|
async getAll() {
|
|
800
563
|
this.ensureEnabled();
|
|
801
564
|
const names = Array.from(this.queueService.getCachedQueues().keys());
|
|
802
565
|
return Promise.all(names.map((name) => this.get(name)));
|
|
803
566
|
}
|
|
804
|
-
/**
|
|
805
|
-
* Invalidate the cache for `queueName`, or the whole cache when omitted.
|
|
806
|
-
*
|
|
807
|
-
* @param queueName - The queue entry to drop; omit to clear everything.
|
|
808
|
-
* @throws {QueueException} `METRICS_DISABLED` (503) when metrics are disabled.
|
|
809
|
-
*/
|
|
810
567
|
invalidate(queueName) {
|
|
811
568
|
this.ensureEnabled();
|
|
812
569
|
if (queueName === void 0) {
|
|
@@ -815,11 +572,6 @@ exports.MetricsService = class MetricsService {
|
|
|
815
572
|
}
|
|
816
573
|
this.cache.delete(queueName);
|
|
817
574
|
}
|
|
818
|
-
/**
|
|
819
|
-
* Guard every operation behind the opt-in flag.
|
|
820
|
-
*
|
|
821
|
-
* @throws {QueueException} `METRICS_DISABLED` (503) when metrics are disabled.
|
|
822
|
-
*/
|
|
823
575
|
ensureEnabled() {
|
|
824
576
|
if (!this.enabled) {
|
|
825
577
|
throw new QueueException(QUEUE_ERROR_CODES.METRICS_DISABLED, common.HttpStatus.SERVICE_UNAVAILABLE);
|
|
@@ -839,19 +591,6 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
839
591
|
}
|
|
840
592
|
connection;
|
|
841
593
|
options;
|
|
842
|
-
/**
|
|
843
|
-
* Register an in-process worker with a handler function. The BullMQ `Worker`
|
|
844
|
-
* constructor is synchronous — no Redis handshake is performed at registration
|
|
845
|
-
* time. The duplicated connection is disconnected immediately if construction
|
|
846
|
-
* fails, preventing a Redis connection leak.
|
|
847
|
-
*
|
|
848
|
-
* @param config - Queue name, handler, and optional worker options.
|
|
849
|
-
* @returns The created BullMQ `Worker` instance.
|
|
850
|
-
* @throws {QueueException} `DUPLICATE_PROCESSOR` when a worker for
|
|
851
|
-
* `queueName` already exists.
|
|
852
|
-
* @throws {QueueException} `WORKER_REGISTRATION_FAILED` when options are
|
|
853
|
-
* invalid or the BullMQ `Worker` constructor throws.
|
|
854
|
-
*/
|
|
855
594
|
register(config) {
|
|
856
595
|
const { queueName, handler, options } = config;
|
|
857
596
|
this.guardDuplicate(queueName);
|
|
@@ -876,21 +615,6 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
876
615
|
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
877
616
|
return worker;
|
|
878
617
|
}
|
|
879
|
-
/**
|
|
880
|
-
* Register a sandboxed (file-based, out-of-process) worker. The processor
|
|
881
|
-
* file must be an absolute path or a `file:` URL pointing to a compiled
|
|
882
|
-
* `.js`/`.cjs`/`.mjs` artifact. NestJS DI is **not** available inside the
|
|
883
|
-
* processor. The BullMQ `Worker` constructor is synchronous.
|
|
884
|
-
*
|
|
885
|
-
* @param config - Queue name, processor file path/URL, and optional options.
|
|
886
|
-
* @returns The created BullMQ `Worker` instance.
|
|
887
|
-
* @throws {QueueException} `WORKER_REGISTRATION_FAILED` when `processorFile`
|
|
888
|
-
* is a relative path, has a disallowed extension, or uses a non-`file:` URL protocol.
|
|
889
|
-
* @throws {QueueException} `DUPLICATE_PROCESSOR` when a worker for
|
|
890
|
-
* `queueName` already exists.
|
|
891
|
-
* @throws {QueueException} `WORKER_REGISTRATION_FAILED` when options are
|
|
892
|
-
* invalid or the BullMQ `Worker` constructor throws.
|
|
893
|
-
*/
|
|
894
618
|
registerSandboxed(config) {
|
|
895
619
|
const { queueName, processorFile, options } = config;
|
|
896
620
|
this.validateProcessorFile(processorFile);
|
|
@@ -919,13 +643,6 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
919
643
|
__privateGet(this, _entries).set(queueName, { worker, connection: conn });
|
|
920
644
|
return worker;
|
|
921
645
|
}
|
|
922
|
-
/**
|
|
923
|
-
* Stop and remove the worker for the given queue, releasing the duplicated
|
|
924
|
-
* connection the library opened for it. If no worker exists for `queueName`
|
|
925
|
-
* this is a no-op.
|
|
926
|
-
*
|
|
927
|
-
* @param queueName - The queue whose worker should be removed.
|
|
928
|
-
*/
|
|
929
646
|
async unregister(queueName) {
|
|
930
647
|
const entry = __privateGet(this, _entries).get(queueName);
|
|
931
648
|
if (!entry) return;
|
|
@@ -933,44 +650,19 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
933
650
|
await this.closeConnection(entry.connection);
|
|
934
651
|
__privateGet(this, _entries).delete(queueName);
|
|
935
652
|
}
|
|
936
|
-
/**
|
|
937
|
-
* Returns the queue names of all currently registered workers.
|
|
938
|
-
*
|
|
939
|
-
* @returns An immutable array of queue names.
|
|
940
|
-
*/
|
|
941
653
|
list() {
|
|
942
654
|
return Array.from(__privateGet(this, _entries).keys());
|
|
943
655
|
}
|
|
944
|
-
/**
|
|
945
|
-
* Returns a read-only view of the registered workers keyed by queue name.
|
|
946
|
-
* Intended for the shutdown orchestrator only.
|
|
947
|
-
*
|
|
948
|
-
* @returns A read-only map of queue name to worker.
|
|
949
|
-
*/
|
|
950
656
|
getAll() {
|
|
951
657
|
return new Map(
|
|
952
658
|
Array.from(__privateGet(this, _entries), ([name, entry]) => [name, entry.worker])
|
|
953
659
|
);
|
|
954
660
|
}
|
|
955
|
-
/**
|
|
956
|
-
* Returns a read-only view of the duplicated connections the library opened
|
|
957
|
-
* for its workers, keyed by queue name. Intended for the shutdown orchestrator
|
|
958
|
-
* so it can close exactly the connections the library created (Mode A never
|
|
959
|
-
* exposes the consumer's shared client here).
|
|
960
|
-
*
|
|
961
|
-
* @returns A read-only map of queue name to duplicated connection.
|
|
962
|
-
*/
|
|
963
661
|
getConnections() {
|
|
964
662
|
return new Map(
|
|
965
663
|
Array.from(__privateGet(this, _entries), ([name, entry]) => [name, entry.connection])
|
|
966
664
|
);
|
|
967
665
|
}
|
|
968
|
-
/**
|
|
969
|
-
* Gracefully quit a duplicated connection, falling back to a hard disconnect
|
|
970
|
-
* if the quit handshake fails so a socket is never leaked.
|
|
971
|
-
*
|
|
972
|
-
* @param connection - The duplicated connection to release.
|
|
973
|
-
*/
|
|
974
666
|
async closeConnection(connection) {
|
|
975
667
|
try {
|
|
976
668
|
await connection.quit();
|
|
@@ -978,12 +670,6 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
978
670
|
connection.disconnect();
|
|
979
671
|
}
|
|
980
672
|
}
|
|
981
|
-
/**
|
|
982
|
-
* Guard against registering two workers for the same queue.
|
|
983
|
-
*
|
|
984
|
-
* @param queueName - Queue name to check.
|
|
985
|
-
* @throws {QueueException} `DUPLICATE_PROCESSOR` if the queue is already registered.
|
|
986
|
-
*/
|
|
987
673
|
guardDuplicate(queueName) {
|
|
988
674
|
if (__privateGet(this, _entries).has(queueName)) {
|
|
989
675
|
throw new QueueException(
|
|
@@ -993,14 +679,6 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
993
679
|
);
|
|
994
680
|
}
|
|
995
681
|
}
|
|
996
|
-
/**
|
|
997
|
-
* Validate tunable options before constructing a Worker.
|
|
998
|
-
* Enforces `concurrency` in `[1, MAX_WORKER_CONCURRENCY]` and
|
|
999
|
-
* `limiter.max`/`limiter.duration` `>= 1`.
|
|
1000
|
-
*
|
|
1001
|
-
* @param opts - The caller-supplied options (may be undefined).
|
|
1002
|
-
* @throws {QueueException} `WORKER_REGISTRATION_FAILED` when values are out of range.
|
|
1003
|
-
*/
|
|
1004
682
|
validateOptions(opts) {
|
|
1005
683
|
if (opts?.concurrency !== void 0) {
|
|
1006
684
|
if (opts.concurrency < 1) {
|
|
@@ -1047,15 +725,6 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
1047
725
|
}
|
|
1048
726
|
}
|
|
1049
727
|
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Validate that `processorFile` is a safe, reachable artifact path. Accepts
|
|
1052
|
-
* absolute paths with `.js`/`.cjs`/`.mjs` extensions and `file:` URLs. Rejects
|
|
1053
|
-
* relative paths, non-JS extensions, and non-`file:` URL protocols to prevent
|
|
1054
|
-
* path-traversal and arbitrary-file-execution attacks.
|
|
1055
|
-
*
|
|
1056
|
-
* @param processorFile - The path or URL supplied by the caller.
|
|
1057
|
-
* @throws {QueueException} `WORKER_REGISTRATION_FAILED` when validation fails.
|
|
1058
|
-
*/
|
|
1059
728
|
validateProcessorFile(processorFile) {
|
|
1060
729
|
if (processorFile instanceof URL) {
|
|
1061
730
|
if (processorFile.protocol !== "file:") {
|
|
@@ -1081,13 +750,6 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
1081
750
|
}
|
|
1082
751
|
this.assertProcessorExtension(processorFile);
|
|
1083
752
|
}
|
|
1084
|
-
/**
|
|
1085
|
-
* Reject a processor path whose extension is not an executable JS module,
|
|
1086
|
-
* the last line of defense against loading an arbitrary file as a worker.
|
|
1087
|
-
*
|
|
1088
|
-
* @param filePath - The filesystem path (or file: URL pathname) to check.
|
|
1089
|
-
* @throws {QueueException} `WORKER_REGISTRATION_FAILED` for a disallowed extension.
|
|
1090
|
-
*/
|
|
1091
753
|
assertProcessorExtension(filePath) {
|
|
1092
754
|
const allowedExtensions = /* @__PURE__ */ new Set([".js", ".cjs", ".mjs"]);
|
|
1093
755
|
if (!allowedExtensions.has(path.extname(filePath))) {
|
|
@@ -1100,23 +762,11 @@ exports.WorkerRegistry = class WorkerRegistry {
|
|
|
1100
762
|
);
|
|
1101
763
|
}
|
|
1102
764
|
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Build a BullMQ `WorkerOptions` object from the library's `WorkerOptions`
|
|
1105
|
-
* shape. Only forwards optional fields that are explicitly set to preserve
|
|
1106
|
-
* `exactOptionalPropertyTypes` correctness. Accepts the pre-created duplicated
|
|
1107
|
-
* connection so callers can close it on construction failure.
|
|
1108
|
-
*
|
|
1109
|
-
* @param opts - Library-level worker options.
|
|
1110
|
-
* @param conn - Pre-created duplicated Redis connection.
|
|
1111
|
-
* @returns A BullMQ-compatible options object.
|
|
1112
|
-
*/
|
|
1113
765
|
buildBullOptions(opts, conn) {
|
|
1114
766
|
const result = {
|
|
1115
767
|
connection: conn,
|
|
1116
768
|
concurrency: opts?.concurrency ?? DEFAULT_WORKER_CONCURRENCY,
|
|
1117
769
|
autorun: opts?.autorun ?? true,
|
|
1118
|
-
// Match the producer Queue's key prefix so the worker polls the same
|
|
1119
|
-
// keyspace; without this a non-default prefix leaves jobs unconsumed.
|
|
1120
770
|
prefix: this.options.prefix
|
|
1121
771
|
};
|
|
1122
772
|
if (opts?.limiter !== void 0) result.limiter = opts.limiter;
|
|
@@ -1143,14 +793,6 @@ exports.QueueEventsRegistry = class QueueEventsRegistry {
|
|
|
1143
793
|
}
|
|
1144
794
|
connection;
|
|
1145
795
|
options;
|
|
1146
|
-
/**
|
|
1147
|
-
* Returns the cached `QueueEvents` for `queueName`, creating one on first
|
|
1148
|
-
* call. Subsequent calls for the same name return the same instance without
|
|
1149
|
-
* opening a second Redis connection.
|
|
1150
|
-
*
|
|
1151
|
-
* @param queueName - The BullMQ queue to observe.
|
|
1152
|
-
* @returns The `QueueEvents` instance for the queue.
|
|
1153
|
-
*/
|
|
1154
796
|
getOrCreate(queueName) {
|
|
1155
797
|
const existing = __privateGet(this, _events).get(queueName);
|
|
1156
798
|
if (existing) return existing;
|
|
@@ -1167,35 +809,12 @@ exports.QueueEventsRegistry = class QueueEventsRegistry {
|
|
|
1167
809
|
__privateGet(this, _connections).set(queueName, conn);
|
|
1168
810
|
return qe;
|
|
1169
811
|
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Returns the queue names for which a `QueueEvents` connection is open.
|
|
1172
|
-
*
|
|
1173
|
-
* @returns An immutable array of queue names.
|
|
1174
|
-
*/
|
|
1175
812
|
list() {
|
|
1176
813
|
return Array.from(__privateGet(this, _events).keys());
|
|
1177
814
|
}
|
|
1178
|
-
/**
|
|
1179
|
-
* Returns the live `QueueEvents` map. Intended for the shutdown orchestrator.
|
|
1180
|
-
*
|
|
1181
|
-
* @returns A read-only view of the internal map.
|
|
1182
|
-
*/
|
|
1183
815
|
getAll() {
|
|
1184
816
|
return __privateGet(this, _events);
|
|
1185
817
|
}
|
|
1186
|
-
/**
|
|
1187
|
-
* Returns a read-only view of the duplicated connections the library opened
|
|
1188
|
-
* for its `QueueEvents`, keyed by queue name. Intended for the shutdown
|
|
1189
|
-
* orchestrator so it can close exactly the connections the library created
|
|
1190
|
-
* (Mode A never exposes the consumer's shared client here).
|
|
1191
|
-
*
|
|
1192
|
-
* Returns a copy. `ReadonlyMap` is a compile-time claim only — handing back the
|
|
1193
|
-
* live registry lets any consumer cast it away and delete a connection the
|
|
1194
|
-
* shutdown sequence is about to close. The allocation costs nothing on a path
|
|
1195
|
-
* that runs once per shutdown.
|
|
1196
|
-
*
|
|
1197
|
-
* @returns A read-only snapshot of queue name to duplicated connection.
|
|
1198
|
-
*/
|
|
1199
818
|
getConnections() {
|
|
1200
819
|
return new Map(__privateGet(this, _connections));
|
|
1201
820
|
}
|
|
@@ -1227,10 +846,6 @@ var ProcessorDiscoveryService = class {
|
|
|
1227
846
|
events;
|
|
1228
847
|
logger = new common.Logger(ProcessorDiscoveryService.name);
|
|
1229
848
|
registeredQueues = /* @__PURE__ */ new Set();
|
|
1230
|
-
/**
|
|
1231
|
-
* Entry point called by NestJS after the module is fully initialized.
|
|
1232
|
-
* Discovers all `@Processor`-annotated providers and wires them.
|
|
1233
|
-
*/
|
|
1234
849
|
onModuleInit() {
|
|
1235
850
|
const providers = this.discovery.getProviders();
|
|
1236
851
|
for (const wrapper of providers) {
|
|
@@ -1242,15 +857,6 @@ var ProcessorDiscoveryService = class {
|
|
|
1242
857
|
this.wireProcessor(instance, processorMeta);
|
|
1243
858
|
}
|
|
1244
859
|
}
|
|
1245
|
-
/**
|
|
1246
|
-
* Build a dispatcher function that routes `job.name` to the correct handler.
|
|
1247
|
-
* Named handlers take priority; a catch-all (`@Process()`) handles everything
|
|
1248
|
-
* else. Throws if no matching handler is found at job-processing time.
|
|
1249
|
-
*
|
|
1250
|
-
* @param instance - The provider instance whose methods serve as handlers.
|
|
1251
|
-
* @param handlers - Accumulated `@Process` metadata entries for the class.
|
|
1252
|
-
* @returns A dispatcher `(job: Job) => Promise<unknown>`.
|
|
1253
|
-
*/
|
|
1254
860
|
buildDispatcher(instance, handlers) {
|
|
1255
861
|
const namedHandlers = /* @__PURE__ */ new Map();
|
|
1256
862
|
let catchAll;
|
|
@@ -1275,13 +881,6 @@ var ProcessorDiscoveryService = class {
|
|
|
1275
881
|
);
|
|
1276
882
|
};
|
|
1277
883
|
}
|
|
1278
|
-
/**
|
|
1279
|
-
* Wire a single discovered processor class: validate uniqueness, register a
|
|
1280
|
-
* Worker, bind worker-local and queue-global event listeners.
|
|
1281
|
-
*
|
|
1282
|
-
* @param instance - The resolved provider instance.
|
|
1283
|
-
* @param processorMeta - Metadata written by `@Processor`.
|
|
1284
|
-
*/
|
|
1285
884
|
wireProcessor(instance, processorMeta) {
|
|
1286
885
|
const { queueName, workerOptions, _warnedNoConcurrency } = processorMeta;
|
|
1287
886
|
const ctor = instance.constructor;
|
|
@@ -1339,20 +938,6 @@ var ProcessorDiscoveryService = class {
|
|
|
1339
938
|
}
|
|
1340
939
|
}
|
|
1341
940
|
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Bind an event listener so a failing handler is reported instead of escaping.
|
|
1344
|
-
*
|
|
1345
|
-
* An `EventEmitter` neither awaits a listener nor catches its throw: a rejected promise becomes
|
|
1346
|
-
* an `unhandledRejection`, which ends the process by default, and BullMQ re-emits a synchronous
|
|
1347
|
-
* throw as `error`. A failing handler is the consumer's defect, never a reason to
|
|
1348
|
-
* stop the application, but it must still be visible, so the failure is logged with the handler
|
|
1349
|
-
* and a stable code and the process carries on.
|
|
1350
|
-
*
|
|
1351
|
-
* @param instance - The provider the handler belongs to (its `this`).
|
|
1352
|
-
* @param method - The handler method.
|
|
1353
|
-
* @param origin - Which decorator, event and method name the listener comes from.
|
|
1354
|
-
* @returns The listener to register on the emitter.
|
|
1355
|
-
*/
|
|
1356
941
|
guardListener(instance, method, origin) {
|
|
1357
942
|
const report = (cause) => {
|
|
1358
943
|
this.logger.error(
|
|
@@ -1392,29 +977,23 @@ var QueueLifecycle = class {
|
|
|
1392
977
|
connection;
|
|
1393
978
|
resolved;
|
|
1394
979
|
logger = new common.Logger(QueueLifecycle.name);
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
* context closes (e.g. on `SIGTERM` with shutdown hooks enabled).
|
|
1398
|
-
*/
|
|
980
|
+
startedAt;
|
|
981
|
+
forcedWorkers = 0;
|
|
1399
982
|
async onModuleDestroy() {
|
|
1400
|
-
|
|
1401
|
-
|
|
983
|
+
this.startedAt = Date.now();
|
|
984
|
+
this.forcedWorkers = await this.closeWorkers();
|
|
1402
985
|
await this.closeQueueEvents();
|
|
1403
986
|
await this.closeDuplicatedConnections();
|
|
987
|
+
}
|
|
988
|
+
async onApplicationShutdown() {
|
|
1404
989
|
await this.drainQueuesIfRequested();
|
|
1405
990
|
await this.closeFlowProducer();
|
|
1406
991
|
await this.closeQueues();
|
|
1407
992
|
await this.teardownConnection();
|
|
1408
993
|
this.logger.log(
|
|
1409
|
-
`Queue shutdown complete in ${String(Date.now() -
|
|
994
|
+
`Queue shutdown complete in ${String(Date.now() - (this.startedAt ?? Date.now()))}ms (forced ${String(this.forcedWorkers)} worker(s))`
|
|
1410
995
|
);
|
|
1411
996
|
}
|
|
1412
|
-
/**
|
|
1413
|
-
* Close every worker with a bounded drain, force-closing any that exceed the
|
|
1414
|
-
* budget. Returns the number of workers that had to be force-closed.
|
|
1415
|
-
*
|
|
1416
|
-
* @returns The count of force-closed workers.
|
|
1417
|
-
*/
|
|
1418
997
|
async closeWorkers() {
|
|
1419
998
|
let forced = 0;
|
|
1420
999
|
const drainTimeoutMs = this.resolved.shutdown.drainTimeoutMs;
|
|
@@ -1431,14 +1010,6 @@ var QueueLifecycle = class {
|
|
|
1431
1010
|
}
|
|
1432
1011
|
return forced;
|
|
1433
1012
|
}
|
|
1434
|
-
/**
|
|
1435
|
-
* Race a graceful `worker.close()` against a timer. `worker.close()` already
|
|
1436
|
-
* drains active jobs and takes no timeout argument, so the timer is the only
|
|
1437
|
-
* way to bound the wait. Rejects with a timeout error when the budget elapses.
|
|
1438
|
-
*
|
|
1439
|
-
* @param worker - The worker to close.
|
|
1440
|
-
* @param timeoutMs - The drain budget in milliseconds.
|
|
1441
|
-
*/
|
|
1442
1013
|
async closeWorkerWithTimeout(worker, timeoutMs) {
|
|
1443
1014
|
let timer;
|
|
1444
1015
|
const closed = worker.close();
|
|
@@ -1456,18 +1027,11 @@ var QueueLifecycle = class {
|
|
|
1456
1027
|
clearTimeout(timer);
|
|
1457
1028
|
}
|
|
1458
1029
|
}
|
|
1459
|
-
/** Close every cached `QueueEvents`, swallowing individual failures. */
|
|
1460
1030
|
async closeQueueEvents() {
|
|
1461
1031
|
for (const [, queueEvents] of this.events.getAll()) {
|
|
1462
1032
|
await queueEvents.close().catch(() => void 0);
|
|
1463
1033
|
}
|
|
1464
1034
|
}
|
|
1465
|
-
/**
|
|
1466
|
-
* Close the duplicated worker/queue-events connections the library opened.
|
|
1467
|
-
* BullMQ treats a passed-in client as shared and never closes it, so the
|
|
1468
|
-
* library must release these explicitly. In Mode A the consumer's shared
|
|
1469
|
-
* client is never among them.
|
|
1470
|
-
*/
|
|
1471
1035
|
async closeDuplicatedConnections() {
|
|
1472
1036
|
const connections = [
|
|
1473
1037
|
...this.workers.getConnections().values(),
|
|
@@ -1481,10 +1045,6 @@ var QueueLifecycle = class {
|
|
|
1481
1045
|
}
|
|
1482
1046
|
}
|
|
1483
1047
|
}
|
|
1484
|
-
/**
|
|
1485
|
-
* Drain every cached queue when `drainOnShutdown` is enabled (dev/test only).
|
|
1486
|
-
* Removes waiting jobs and leaves delayed and active ones; off by default to stay production-safe.
|
|
1487
|
-
*/
|
|
1488
1048
|
async drainQueuesIfRequested() {
|
|
1489
1049
|
if (!this.resolved.shutdown.drainOnShutdown) return;
|
|
1490
1050
|
for (const [name, queue] of this.queues.getCachedQueues()) {
|
|
@@ -1495,18 +1055,12 @@ var QueueLifecycle = class {
|
|
|
1495
1055
|
});
|
|
1496
1056
|
}
|
|
1497
1057
|
}
|
|
1498
|
-
/** Close the flow producer (idempotent), swallowing any failure. */
|
|
1499
1058
|
async closeFlowProducer() {
|
|
1500
1059
|
await this.flow.close().catch(() => void 0);
|
|
1501
1060
|
}
|
|
1502
|
-
/** Close every cached queue and clear the cache, swallowing individual failures. */
|
|
1503
1061
|
async closeQueues() {
|
|
1504
1062
|
await this.queues.closeAll().catch(() => void 0);
|
|
1505
1063
|
}
|
|
1506
|
-
/**
|
|
1507
|
-
* Tear down the Queue-role connection. Mode B quits the library-owned client;
|
|
1508
|
-
* Mode A is a no-op for the consumer's shared client.
|
|
1509
|
-
*/
|
|
1510
1064
|
async teardownConnection() {
|
|
1511
1065
|
await this.connection.teardown().catch(() => void 0);
|
|
1512
1066
|
}
|
|
@@ -1523,24 +1077,12 @@ QueueLifecycle = __decorateClass([
|
|
|
1523
1077
|
|
|
1524
1078
|
// src/server/bymax-queue.module.ts
|
|
1525
1079
|
var { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = (
|
|
1526
|
-
// The `moduleName` shapes the generated option token's identifier — `BYMAX_QUEUE_MODULE_OPTIONS`,
|
|
1527
|
-
// which the module spec asserts by value; emptying the name would degrade it to an opaque
|
|
1528
|
-
// `CONFIGURABLE_MODULE_OPTIONS[<hash>]`, so the argument is a caught regression, not a free label.
|
|
1529
1080
|
new common.ConfigurableModuleBuilder({ moduleName: "BymaxQueue" }).setClassMethodName("forRoot").setFactoryMethodName("createQueueOptions").setExtras({ isGlobal: true }, (definition, extras) => ({
|
|
1530
1081
|
...definition,
|
|
1531
1082
|
global: extras.isGlobal
|
|
1532
1083
|
})).build()
|
|
1533
1084
|
);
|
|
1534
1085
|
exports.BymaxQueueModule = class BymaxQueueModule extends ConfigurableModuleClass {
|
|
1535
|
-
/**
|
|
1536
|
-
* Synchronous registration. Validates options, resolves defaults, and wires the
|
|
1537
|
-
* connection resolver (initialized via async factory), the queue service, the
|
|
1538
|
-
* worker registry, the queue-events registry, processor discovery, and the
|
|
1539
|
-
* graceful-shutdown lifecycle.
|
|
1540
|
-
*
|
|
1541
|
-
* @param options - Static module options.
|
|
1542
|
-
* @returns The configured dynamic module.
|
|
1543
|
-
*/
|
|
1544
1086
|
static forRoot(options) {
|
|
1545
1087
|
validateOptions(options);
|
|
1546
1088
|
const resolved = applyDefaults(options);
|
|
@@ -1566,17 +1108,6 @@ exports.BymaxQueueModule = class BymaxQueueModule extends ConfigurableModuleClas
|
|
|
1566
1108
|
exports: exports.BymaxQueueModule.buildExports()
|
|
1567
1109
|
};
|
|
1568
1110
|
}
|
|
1569
|
-
/**
|
|
1570
|
-
* Asynchronous registration. Use when options depend on other modules
|
|
1571
|
-
* (ConfigService, BymaxCacheModule, etc.). Mirrors the NestJS standard async
|
|
1572
|
-
* dynamic-module pattern (`useFactory` | `useClass` | `useExisting` + `inject`)
|
|
1573
|
-
* and registers the same provider graph and exports as {@link forRoot}, with
|
|
1574
|
-
* `global` applied from the `isGlobal` extra. The resolved options and the
|
|
1575
|
-
* initialized connection are derived from the async-resolved options at runtime.
|
|
1576
|
-
*
|
|
1577
|
-
* @param options - Asynchronous module options.
|
|
1578
|
-
* @returns The configured dynamic module.
|
|
1579
|
-
*/
|
|
1580
1111
|
static forRootAsync(options) {
|
|
1581
1112
|
const base = super.forRootAsync(options);
|
|
1582
1113
|
return {
|
|
@@ -1607,14 +1138,6 @@ exports.BymaxQueueModule = class BymaxQueueModule extends ConfigurableModuleClas
|
|
|
1607
1138
|
exports: exports.BymaxQueueModule.buildExports()
|
|
1608
1139
|
};
|
|
1609
1140
|
}
|
|
1610
|
-
/**
|
|
1611
|
-
* Providers shared verbatim by both registration paths: the options alias, the
|
|
1612
|
-
* initialized connection resolver, the resolved client/mode tokens, and the
|
|
1613
|
-
* singleton services. `BYMAX_QUEUE_RESOLVED_OPTIONS`, `FlowService`, and
|
|
1614
|
-
* `MetricsService` are intentionally omitted — they differ per path.
|
|
1615
|
-
*
|
|
1616
|
-
* @returns The shared provider list.
|
|
1617
|
-
*/
|
|
1618
1141
|
static buildCoreProviders() {
|
|
1619
1142
|
return [
|
|
1620
1143
|
{ provide: BYMAX_QUEUE_OPTIONS, useExisting: MODULE_OPTIONS_TOKEN },
|
|
@@ -1644,11 +1167,6 @@ exports.BymaxQueueModule = class BymaxQueueModule extends ConfigurableModuleClas
|
|
|
1644
1167
|
QueueLifecycle
|
|
1645
1168
|
];
|
|
1646
1169
|
}
|
|
1647
|
-
/**
|
|
1648
|
-
* The public exports shared by both registration paths.
|
|
1649
|
-
*
|
|
1650
|
-
* @returns A fresh exports list (services and injection tokens).
|
|
1651
|
-
*/
|
|
1652
1170
|
static buildExports() {
|
|
1653
1171
|
return [
|
|
1654
1172
|
exports.QueueService,
|