@zucker-framework/queue 1.0.2 → 1.0.5
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/README.md +17 -0
- package/dist/index.d.mts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +56 -13
- package/dist/index.mjs +56 -13
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @zucker-framework/queue
|
|
2
|
+
|
|
3
|
+
`BullMqQueueProvider` owns producers; `BullMqWorkerHost` owns workers. Consumers retain outboxes, transaction boundaries, authorization, quota decisions and cancellation policy. Neither transport makes a business transaction atomic.
|
|
4
|
+
|
|
5
|
+
## Producer lifecycle
|
|
6
|
+
|
|
7
|
+
Construct with explicit `connection` and optional `prefix`, `onError(queueName, error)`. Call `waitUntilReady(['queue-name'])` to prewarm producers without adding a job; the no-argument form only waits for already-created queues. Close producers **after** gracefully closing workers that may still publish work. Error callbacks should redact connection errors before logging.
|
|
8
|
+
|
|
9
|
+
`addBulk(queueName, entries)` on the BullMQ adapter preserves native bulk submission, validating all job IDs before creating the queue; empty batches create no connection. `add` preserves stable transport IDs. `ensure` may replace terminal records: do not use it when a terminal job must remain evidence for outbox failure reconciliation. `getJob` exposes normalized status and failed reason. `getStats` combines waiting/prioritized with a read-only legacy paused-list count; BullMQ 6 does not accept `paused` as a getJobCounts type. This does not resume or migrate legacy queues and is not proof of v5→v6 compatibility. `getJobCounts(queueName)` retains BullMQ's native default count keys (including `waiting-children`) for backend-specific monitoring contracts without merging counts.
|
|
10
|
+
|
|
11
|
+
## Workers
|
|
12
|
+
|
|
13
|
+
`register({ queueName, handler, concurrency, lockDurationMs, stalledIntervalMs, autorun })` requires positive safe-integer settings. Handler jobs include `attempt` (one-based) and `maxAttempts` (configured budget, default 1), allowing product terminal-attempt policy without reaching into raw BullMQ jobs. BullMQ unrecoverable errors can still be thrown directly by a handler.
|
|
14
|
+
|
|
15
|
+
`close(false)` drains workers; concurrent calls share the same drain promise. Every close is awaited even if another close throws/rejects; failures are reported together as `AggregateError`, so a failed drain cannot signal completion while another worker is still active. Closed hosts/providers cannot register workers or reopen producers. Force closing has different recovery implications. `probeHealth` is a bounded read-only connection probe, not a task execution probe. Test with fake factories or explicitly isolated disposable Redis; never use existing queues for compatibility experiments.
|
|
16
|
+
|
|
17
|
+
New prewarm/error/retry-budget/stalled-interval contracts are local source additions pending a new formal release; 1.0.2 consumers must not import sibling source to obtain them.
|
package/dist/index.d.mts
CHANGED
|
@@ -93,14 +93,21 @@ interface BullMqQueueProviderOptions {
|
|
|
93
93
|
prefix?: string;
|
|
94
94
|
defaultJobName?: string;
|
|
95
95
|
queueFactory?: QueueFactory;
|
|
96
|
+
onError?: (queueName: string, error: Error) => void;
|
|
96
97
|
}
|
|
97
98
|
declare class BullMqQueueProvider implements QueueProvider {
|
|
98
99
|
private readonly options;
|
|
99
100
|
private readonly queues;
|
|
101
|
+
private closing?;
|
|
100
102
|
private readonly defaultJobName;
|
|
101
103
|
private readonly queueFactory;
|
|
102
104
|
constructor(options: BullMqQueueProviderOptions);
|
|
103
105
|
add<T>(queueName: string, data: T, options?: JobOptions): Promise<Job<T>>;
|
|
106
|
+
/** Preserve BullMQ native bulk submission semantics rather than looping add(). */
|
|
107
|
+
addBulk<T>(queueName: string, entries: readonly {
|
|
108
|
+
data: T;
|
|
109
|
+
options?: JobOptions;
|
|
110
|
+
}[]): Promise<Job<T>[]>;
|
|
104
111
|
ensure<T>(queueName: string, data: T, options: JobOptions & {
|
|
105
112
|
jobId: string;
|
|
106
113
|
}): Promise<Job<T>>;
|
|
@@ -110,8 +117,11 @@ declare class BullMqQueueProvider implements QueueProvider {
|
|
|
110
117
|
removeJob(queueName: string, jobId: string): Promise<void>;
|
|
111
118
|
pause(queueName: string): Promise<void>;
|
|
112
119
|
resume(queueName: string): Promise<void>;
|
|
120
|
+
/** Native count keys for existing BullMQ monitoring contracts. Unlike
|
|
121
|
+
* getStats(), this does not merge prioritized/legacy-paused into waiting. */
|
|
122
|
+
getJobCounts(queueName: string): Promise<Record<string, number>>;
|
|
113
123
|
getStats(queueName: string): Promise<QueueStats>;
|
|
114
|
-
waitUntilReady(): Promise<void>;
|
|
124
|
+
waitUntilReady(queueNames?: readonly string[]): Promise<void>;
|
|
115
125
|
close(): Promise<void>;
|
|
116
126
|
onModuleDestroy(): Promise<void>;
|
|
117
127
|
private getQueue;
|
|
@@ -133,6 +143,8 @@ interface QueueWorkerJob<TData> {
|
|
|
133
143
|
name: string;
|
|
134
144
|
data: TData;
|
|
135
145
|
attempt: number;
|
|
146
|
+
/** Configured retry budget, including this attempt. */
|
|
147
|
+
maxAttempts: number;
|
|
136
148
|
updateProgress(progress: JobProgress): Promise<void>;
|
|
137
149
|
log(message: string): Promise<number>;
|
|
138
150
|
}
|
|
@@ -142,6 +154,7 @@ interface QueueWorkerDefinition<TData = unknown, TResult = unknown> {
|
|
|
142
154
|
handler: QueueJobHandler<TData, TResult>;
|
|
143
155
|
concurrency?: number;
|
|
144
156
|
lockDurationMs?: number;
|
|
157
|
+
stalledIntervalMs?: number;
|
|
145
158
|
autorun?: boolean;
|
|
146
159
|
}
|
|
147
160
|
type WorkerFactory = (queueName: string, processor: (job: Job$1) => Promise<unknown>, options: WorkerOptions) => Worker;
|
|
@@ -160,6 +173,7 @@ declare class BullMqWorkerHost {
|
|
|
160
173
|
private readonly options;
|
|
161
174
|
private readonly workers;
|
|
162
175
|
private readonly readyQueues;
|
|
176
|
+
private closing?;
|
|
163
177
|
private readonly workerFactory;
|
|
164
178
|
constructor(options: BullMqWorkerHostOptions);
|
|
165
179
|
register<TData, TResult>(definition: QueueWorkerDefinition<TData, TResult>): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -93,14 +93,21 @@ interface BullMqQueueProviderOptions {
|
|
|
93
93
|
prefix?: string;
|
|
94
94
|
defaultJobName?: string;
|
|
95
95
|
queueFactory?: QueueFactory;
|
|
96
|
+
onError?: (queueName: string, error: Error) => void;
|
|
96
97
|
}
|
|
97
98
|
declare class BullMqQueueProvider implements QueueProvider {
|
|
98
99
|
private readonly options;
|
|
99
100
|
private readonly queues;
|
|
101
|
+
private closing?;
|
|
100
102
|
private readonly defaultJobName;
|
|
101
103
|
private readonly queueFactory;
|
|
102
104
|
constructor(options: BullMqQueueProviderOptions);
|
|
103
105
|
add<T>(queueName: string, data: T, options?: JobOptions): Promise<Job<T>>;
|
|
106
|
+
/** Preserve BullMQ native bulk submission semantics rather than looping add(). */
|
|
107
|
+
addBulk<T>(queueName: string, entries: readonly {
|
|
108
|
+
data: T;
|
|
109
|
+
options?: JobOptions;
|
|
110
|
+
}[]): Promise<Job<T>[]>;
|
|
104
111
|
ensure<T>(queueName: string, data: T, options: JobOptions & {
|
|
105
112
|
jobId: string;
|
|
106
113
|
}): Promise<Job<T>>;
|
|
@@ -110,8 +117,11 @@ declare class BullMqQueueProvider implements QueueProvider {
|
|
|
110
117
|
removeJob(queueName: string, jobId: string): Promise<void>;
|
|
111
118
|
pause(queueName: string): Promise<void>;
|
|
112
119
|
resume(queueName: string): Promise<void>;
|
|
120
|
+
/** Native count keys for existing BullMQ monitoring contracts. Unlike
|
|
121
|
+
* getStats(), this does not merge prioritized/legacy-paused into waiting. */
|
|
122
|
+
getJobCounts(queueName: string): Promise<Record<string, number>>;
|
|
113
123
|
getStats(queueName: string): Promise<QueueStats>;
|
|
114
|
-
waitUntilReady(): Promise<void>;
|
|
124
|
+
waitUntilReady(queueNames?: readonly string[]): Promise<void>;
|
|
115
125
|
close(): Promise<void>;
|
|
116
126
|
onModuleDestroy(): Promise<void>;
|
|
117
127
|
private getQueue;
|
|
@@ -133,6 +143,8 @@ interface QueueWorkerJob<TData> {
|
|
|
133
143
|
name: string;
|
|
134
144
|
data: TData;
|
|
135
145
|
attempt: number;
|
|
146
|
+
/** Configured retry budget, including this attempt. */
|
|
147
|
+
maxAttempts: number;
|
|
136
148
|
updateProgress(progress: JobProgress): Promise<void>;
|
|
137
149
|
log(message: string): Promise<number>;
|
|
138
150
|
}
|
|
@@ -142,6 +154,7 @@ interface QueueWorkerDefinition<TData = unknown, TResult = unknown> {
|
|
|
142
154
|
handler: QueueJobHandler<TData, TResult>;
|
|
143
155
|
concurrency?: number;
|
|
144
156
|
lockDurationMs?: number;
|
|
157
|
+
stalledIntervalMs?: number;
|
|
145
158
|
autorun?: boolean;
|
|
146
159
|
}
|
|
147
160
|
type WorkerFactory = (queueName: string, processor: (job: Job$1) => Promise<unknown>, options: WorkerOptions) => Worker;
|
|
@@ -160,6 +173,7 @@ declare class BullMqWorkerHost {
|
|
|
160
173
|
private readonly options;
|
|
161
174
|
private readonly workers;
|
|
162
175
|
private readonly readyQueues;
|
|
176
|
+
private closing?;
|
|
163
177
|
private readonly workerFactory;
|
|
164
178
|
constructor(options: BullMqWorkerHostOptions);
|
|
165
179
|
register<TData, TResult>(definition: QueueWorkerDefinition<TData, TResult>): void;
|
package/dist/index.js
CHANGED
|
@@ -161,6 +161,7 @@ var BullMqQueueProvider = class {
|
|
|
161
161
|
}
|
|
162
162
|
options;
|
|
163
163
|
queues = /* @__PURE__ */ new Map();
|
|
164
|
+
closing;
|
|
164
165
|
defaultJobName;
|
|
165
166
|
queueFactory;
|
|
166
167
|
constructor(options) {
|
|
@@ -173,6 +174,17 @@ var BullMqQueueProvider = class {
|
|
|
173
174
|
const job = await this.getQueue(queueName).add(options?.name ?? this.defaultJobName, data, this.toBullMqOptions(options));
|
|
174
175
|
return this.toJob(job);
|
|
175
176
|
}
|
|
177
|
+
/** Preserve BullMQ native bulk submission semantics rather than looping add(). */
|
|
178
|
+
async addBulk(queueName, entries) {
|
|
179
|
+
for (const entry of entries) this.assertValidJobId(entry.options?.jobId);
|
|
180
|
+
if (entries.length === 0) return [];
|
|
181
|
+
const jobs = await this.getQueue(queueName).addBulk(entries.map(({ data, options }) => ({
|
|
182
|
+
name: options?.name ?? this.defaultJobName,
|
|
183
|
+
data,
|
|
184
|
+
opts: this.toBullMqOptions(options)
|
|
185
|
+
})));
|
|
186
|
+
return Promise.all(jobs.map((job) => this.toJob(job)));
|
|
187
|
+
}
|
|
176
188
|
async ensure(queueName, data, options) {
|
|
177
189
|
this.assertValidJobId(options.jobId);
|
|
178
190
|
const queue = this.getQueue(queueName);
|
|
@@ -222,39 +234,54 @@ var BullMqQueueProvider = class {
|
|
|
222
234
|
async resume(queueName) {
|
|
223
235
|
await this.getQueue(queueName).resume();
|
|
224
236
|
}
|
|
237
|
+
/** Native count keys for existing BullMQ monitoring contracts. Unlike
|
|
238
|
+
* getStats(), this does not merge prioritized/legacy-paused into waiting. */
|
|
239
|
+
async getJobCounts(queueName) {
|
|
240
|
+
return this.getQueue(queueName).getJobCounts();
|
|
241
|
+
}
|
|
225
242
|
async getStats(queueName) {
|
|
226
|
-
const
|
|
227
|
-
const counts = await
|
|
243
|
+
const queue = this.getQueue(queueName);
|
|
244
|
+
const counts = await queue.getJobCounts("wait", "active", "completed", "failed", "delayed", "prioritized");
|
|
245
|
+
const client = await queue.backend.client;
|
|
246
|
+
const paused = await client.llen(queue.toKey("paused"));
|
|
228
247
|
return {
|
|
229
|
-
waiting: (counts.wait ?? 0) + (counts.prioritized ?? 0) +
|
|
248
|
+
waiting: (counts.wait ?? 0) + (counts.prioritized ?? 0) + paused,
|
|
230
249
|
active: counts.active ?? 0,
|
|
231
250
|
completed: counts.completed ?? 0,
|
|
232
251
|
failed: counts.failed ?? 0,
|
|
233
252
|
delayed: counts.delayed ?? 0
|
|
234
253
|
};
|
|
235
254
|
}
|
|
236
|
-
async waitUntilReady() {
|
|
255
|
+
async waitUntilReady(queueNames = []) {
|
|
256
|
+
for (const name of queueNames) this.getQueue(name);
|
|
237
257
|
await Promise.all([
|
|
238
258
|
...this.queues.values()
|
|
239
259
|
].map((queue) => queue.waitUntilReady()));
|
|
240
260
|
}
|
|
241
|
-
|
|
261
|
+
close() {
|
|
262
|
+
if (this.closing) return this.closing;
|
|
242
263
|
const queues = [
|
|
243
264
|
...this.queues.values()
|
|
244
265
|
];
|
|
245
266
|
this.queues.clear();
|
|
246
|
-
|
|
267
|
+
this.closing = Promise.allSettled(queues.map(async (queue) => queue.close())).then((results) => {
|
|
268
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
269
|
+
if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "Queue shutdown failed");
|
|
270
|
+
});
|
|
271
|
+
return this.closing;
|
|
247
272
|
}
|
|
248
273
|
async onModuleDestroy() {
|
|
249
274
|
await this.close();
|
|
250
275
|
}
|
|
251
276
|
getQueue(name) {
|
|
277
|
+
if (this.closing) throw new Error("Queue provider is closing");
|
|
252
278
|
const existing = this.queues.get(name);
|
|
253
279
|
if (existing) return existing;
|
|
254
280
|
const queue = this.queueFactory(name, {
|
|
255
281
|
connection: this.options.connection,
|
|
256
282
|
prefix: this.options.prefix
|
|
257
283
|
});
|
|
284
|
+
if (this.options.onError) queue.on("error", (error) => this.options.onError(name, error));
|
|
258
285
|
this.queues.set(name, queue);
|
|
259
286
|
return queue;
|
|
260
287
|
}
|
|
@@ -344,24 +371,30 @@ var BullMqWorkerHost = class {
|
|
|
344
371
|
options;
|
|
345
372
|
workers = /* @__PURE__ */ new Map();
|
|
346
373
|
readyQueues = /* @__PURE__ */ new Set();
|
|
374
|
+
closing;
|
|
347
375
|
workerFactory;
|
|
348
376
|
constructor(options) {
|
|
349
377
|
this.options = options;
|
|
350
378
|
this.workerFactory = options.workerFactory ?? ((queueName, processor, workerOptions) => new import_bullmq2.Worker(queueName, processor, workerOptions));
|
|
351
379
|
}
|
|
352
380
|
register(definition) {
|
|
381
|
+
if (this.closing) throw new Error("Worker host is closing");
|
|
353
382
|
if (this.workers.has(definition.queueName)) {
|
|
354
383
|
throw new Error(`Worker already registered for queue: ${definition.queueName}`);
|
|
355
384
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
385
|
+
for (const [name, value] of Object.entries({
|
|
386
|
+
concurrency: definition.concurrency ?? 1,
|
|
387
|
+
lockDurationMs: definition.lockDurationMs ?? 3e4,
|
|
388
|
+
stalledIntervalMs: definition.stalledIntervalMs ?? 3e4
|
|
389
|
+
})) {
|
|
390
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${name} must be a positive safe integer`);
|
|
359
391
|
}
|
|
360
392
|
const worker = this.workerFactory(definition.queueName, async (job) => definition.handler({
|
|
361
393
|
id: String(job.id),
|
|
362
394
|
name: job.name,
|
|
363
395
|
data: job.data,
|
|
364
396
|
attempt: job.attemptsMade + 1,
|
|
397
|
+
maxAttempts: job.opts?.attempts ?? 1,
|
|
365
398
|
updateProgress: /* @__PURE__ */ __name((progress) => job.updateProgress(progress), "updateProgress"),
|
|
366
399
|
log: /* @__PURE__ */ __name((message) => job.log(message), "log")
|
|
367
400
|
}), {
|
|
@@ -369,9 +402,14 @@ var BullMqWorkerHost = class {
|
|
|
369
402
|
prefix: this.options.prefix,
|
|
370
403
|
concurrency: definition.concurrency ?? 1,
|
|
371
404
|
lockDuration: definition.lockDurationMs ?? 3e4,
|
|
372
|
-
autorun: definition.autorun ?? true
|
|
405
|
+
autorun: definition.autorun ?? true,
|
|
406
|
+
...definition.stalledIntervalMs === void 0 ? {} : {
|
|
407
|
+
stalledInterval: definition.stalledIntervalMs
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
worker.on("ready", () => {
|
|
411
|
+
if (!this.closing) this.readyQueues.add(definition.queueName);
|
|
373
412
|
});
|
|
374
|
-
worker.on("ready", () => this.readyQueues.add(definition.queueName));
|
|
375
413
|
worker.on("error", (error) => {
|
|
376
414
|
this.readyQueues.delete(definition.queueName);
|
|
377
415
|
this.options.onError?.(definition.queueName, error);
|
|
@@ -447,13 +485,18 @@ var BullMqWorkerHost = class {
|
|
|
447
485
|
...this.workers.values()
|
|
448
486
|
].map((worker) => worker.resume()));
|
|
449
487
|
}
|
|
450
|
-
|
|
488
|
+
close(force = false) {
|
|
489
|
+
if (this.closing) return this.closing;
|
|
451
490
|
const workers = [
|
|
452
491
|
...this.workers.values()
|
|
453
492
|
];
|
|
454
493
|
this.workers.clear();
|
|
455
494
|
this.readyQueues.clear();
|
|
456
|
-
|
|
495
|
+
this.closing = Promise.allSettled(workers.map(async (worker) => worker.close(force))).then((results) => {
|
|
496
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
497
|
+
if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "Worker shutdown failed");
|
|
498
|
+
});
|
|
499
|
+
return this.closing;
|
|
457
500
|
}
|
|
458
501
|
async onModuleDestroy() {
|
|
459
502
|
await this.close();
|
package/dist/index.mjs
CHANGED
|
@@ -127,6 +127,7 @@ var BullMqQueueProvider = class {
|
|
|
127
127
|
}
|
|
128
128
|
options;
|
|
129
129
|
queues = /* @__PURE__ */ new Map();
|
|
130
|
+
closing;
|
|
130
131
|
defaultJobName;
|
|
131
132
|
queueFactory;
|
|
132
133
|
constructor(options) {
|
|
@@ -139,6 +140,17 @@ var BullMqQueueProvider = class {
|
|
|
139
140
|
const job = await this.getQueue(queueName).add(options?.name ?? this.defaultJobName, data, this.toBullMqOptions(options));
|
|
140
141
|
return this.toJob(job);
|
|
141
142
|
}
|
|
143
|
+
/** Preserve BullMQ native bulk submission semantics rather than looping add(). */
|
|
144
|
+
async addBulk(queueName, entries) {
|
|
145
|
+
for (const entry of entries) this.assertValidJobId(entry.options?.jobId);
|
|
146
|
+
if (entries.length === 0) return [];
|
|
147
|
+
const jobs = await this.getQueue(queueName).addBulk(entries.map(({ data, options }) => ({
|
|
148
|
+
name: options?.name ?? this.defaultJobName,
|
|
149
|
+
data,
|
|
150
|
+
opts: this.toBullMqOptions(options)
|
|
151
|
+
})));
|
|
152
|
+
return Promise.all(jobs.map((job) => this.toJob(job)));
|
|
153
|
+
}
|
|
142
154
|
async ensure(queueName, data, options) {
|
|
143
155
|
this.assertValidJobId(options.jobId);
|
|
144
156
|
const queue = this.getQueue(queueName);
|
|
@@ -188,39 +200,54 @@ var BullMqQueueProvider = class {
|
|
|
188
200
|
async resume(queueName) {
|
|
189
201
|
await this.getQueue(queueName).resume();
|
|
190
202
|
}
|
|
203
|
+
/** Native count keys for existing BullMQ monitoring contracts. Unlike
|
|
204
|
+
* getStats(), this does not merge prioritized/legacy-paused into waiting. */
|
|
205
|
+
async getJobCounts(queueName) {
|
|
206
|
+
return this.getQueue(queueName).getJobCounts();
|
|
207
|
+
}
|
|
191
208
|
async getStats(queueName) {
|
|
192
|
-
const
|
|
193
|
-
const counts = await
|
|
209
|
+
const queue = this.getQueue(queueName);
|
|
210
|
+
const counts = await queue.getJobCounts("wait", "active", "completed", "failed", "delayed", "prioritized");
|
|
211
|
+
const client = await queue.backend.client;
|
|
212
|
+
const paused = await client.llen(queue.toKey("paused"));
|
|
194
213
|
return {
|
|
195
|
-
waiting: (counts.wait ?? 0) + (counts.prioritized ?? 0) +
|
|
214
|
+
waiting: (counts.wait ?? 0) + (counts.prioritized ?? 0) + paused,
|
|
196
215
|
active: counts.active ?? 0,
|
|
197
216
|
completed: counts.completed ?? 0,
|
|
198
217
|
failed: counts.failed ?? 0,
|
|
199
218
|
delayed: counts.delayed ?? 0
|
|
200
219
|
};
|
|
201
220
|
}
|
|
202
|
-
async waitUntilReady() {
|
|
221
|
+
async waitUntilReady(queueNames = []) {
|
|
222
|
+
for (const name of queueNames) this.getQueue(name);
|
|
203
223
|
await Promise.all([
|
|
204
224
|
...this.queues.values()
|
|
205
225
|
].map((queue) => queue.waitUntilReady()));
|
|
206
226
|
}
|
|
207
|
-
|
|
227
|
+
close() {
|
|
228
|
+
if (this.closing) return this.closing;
|
|
208
229
|
const queues = [
|
|
209
230
|
...this.queues.values()
|
|
210
231
|
];
|
|
211
232
|
this.queues.clear();
|
|
212
|
-
|
|
233
|
+
this.closing = Promise.allSettled(queues.map(async (queue) => queue.close())).then((results) => {
|
|
234
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
235
|
+
if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "Queue shutdown failed");
|
|
236
|
+
});
|
|
237
|
+
return this.closing;
|
|
213
238
|
}
|
|
214
239
|
async onModuleDestroy() {
|
|
215
240
|
await this.close();
|
|
216
241
|
}
|
|
217
242
|
getQueue(name) {
|
|
243
|
+
if (this.closing) throw new Error("Queue provider is closing");
|
|
218
244
|
const existing = this.queues.get(name);
|
|
219
245
|
if (existing) return existing;
|
|
220
246
|
const queue = this.queueFactory(name, {
|
|
221
247
|
connection: this.options.connection,
|
|
222
248
|
prefix: this.options.prefix
|
|
223
249
|
});
|
|
250
|
+
if (this.options.onError) queue.on("error", (error) => this.options.onError(name, error));
|
|
224
251
|
this.queues.set(name, queue);
|
|
225
252
|
return queue;
|
|
226
253
|
}
|
|
@@ -310,24 +337,30 @@ var BullMqWorkerHost = class {
|
|
|
310
337
|
options;
|
|
311
338
|
workers = /* @__PURE__ */ new Map();
|
|
312
339
|
readyQueues = /* @__PURE__ */ new Set();
|
|
340
|
+
closing;
|
|
313
341
|
workerFactory;
|
|
314
342
|
constructor(options) {
|
|
315
343
|
this.options = options;
|
|
316
344
|
this.workerFactory = options.workerFactory ?? ((queueName, processor, workerOptions) => new Worker(queueName, processor, workerOptions));
|
|
317
345
|
}
|
|
318
346
|
register(definition) {
|
|
347
|
+
if (this.closing) throw new Error("Worker host is closing");
|
|
319
348
|
if (this.workers.has(definition.queueName)) {
|
|
320
349
|
throw new Error(`Worker already registered for queue: ${definition.queueName}`);
|
|
321
350
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
351
|
+
for (const [name, value] of Object.entries({
|
|
352
|
+
concurrency: definition.concurrency ?? 1,
|
|
353
|
+
lockDurationMs: definition.lockDurationMs ?? 3e4,
|
|
354
|
+
stalledIntervalMs: definition.stalledIntervalMs ?? 3e4
|
|
355
|
+
})) {
|
|
356
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${name} must be a positive safe integer`);
|
|
325
357
|
}
|
|
326
358
|
const worker = this.workerFactory(definition.queueName, async (job) => definition.handler({
|
|
327
359
|
id: String(job.id),
|
|
328
360
|
name: job.name,
|
|
329
361
|
data: job.data,
|
|
330
362
|
attempt: job.attemptsMade + 1,
|
|
363
|
+
maxAttempts: job.opts?.attempts ?? 1,
|
|
331
364
|
updateProgress: /* @__PURE__ */ __name((progress) => job.updateProgress(progress), "updateProgress"),
|
|
332
365
|
log: /* @__PURE__ */ __name((message) => job.log(message), "log")
|
|
333
366
|
}), {
|
|
@@ -335,9 +368,14 @@ var BullMqWorkerHost = class {
|
|
|
335
368
|
prefix: this.options.prefix,
|
|
336
369
|
concurrency: definition.concurrency ?? 1,
|
|
337
370
|
lockDuration: definition.lockDurationMs ?? 3e4,
|
|
338
|
-
autorun: definition.autorun ?? true
|
|
371
|
+
autorun: definition.autorun ?? true,
|
|
372
|
+
...definition.stalledIntervalMs === void 0 ? {} : {
|
|
373
|
+
stalledInterval: definition.stalledIntervalMs
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
worker.on("ready", () => {
|
|
377
|
+
if (!this.closing) this.readyQueues.add(definition.queueName);
|
|
339
378
|
});
|
|
340
|
-
worker.on("ready", () => this.readyQueues.add(definition.queueName));
|
|
341
379
|
worker.on("error", (error) => {
|
|
342
380
|
this.readyQueues.delete(definition.queueName);
|
|
343
381
|
this.options.onError?.(definition.queueName, error);
|
|
@@ -413,13 +451,18 @@ var BullMqWorkerHost = class {
|
|
|
413
451
|
...this.workers.values()
|
|
414
452
|
].map((worker) => worker.resume()));
|
|
415
453
|
}
|
|
416
|
-
|
|
454
|
+
close(force = false) {
|
|
455
|
+
if (this.closing) return this.closing;
|
|
417
456
|
const workers = [
|
|
418
457
|
...this.workers.values()
|
|
419
458
|
];
|
|
420
459
|
this.workers.clear();
|
|
421
460
|
this.readyQueues.clear();
|
|
422
|
-
|
|
461
|
+
this.closing = Promise.allSettled(workers.map(async (worker) => worker.close(force))).then((results) => {
|
|
462
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
463
|
+
if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "Worker shutdown failed");
|
|
464
|
+
});
|
|
465
|
+
return this.closing;
|
|
423
466
|
}
|
|
424
467
|
async onModuleDestroy() {
|
|
425
468
|
await this.close();
|