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