@zucker-framework/queue 1.0.0
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/LICENSE +201 -0
- package/NOTICE +22 -0
- package/THIRD_PARTY_NOTICES +4 -0
- package/dist/index.d.mts +384 -0
- package/dist/index.d.ts +384 -0
- package/dist/index.js +911 -0
- package/dist/index.mjs +876 -0
- package/package.json +38 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
BullMqQueueEvents: () => BullMqQueueEvents,
|
|
25
|
+
BullMqQueueProvider: () => BullMqQueueProvider,
|
|
26
|
+
BullMqWorkerHost: () => BullMqWorkerHost,
|
|
27
|
+
InMemoryQueue: () => InMemoryQueue,
|
|
28
|
+
QUEUE_PROVIDER: () => QUEUE_PROVIDER,
|
|
29
|
+
QUEUE_WORKER_KEY: () => QUEUE_WORKER_KEY,
|
|
30
|
+
QueueService: () => QueueService,
|
|
31
|
+
QueueWorker: () => QueueWorker,
|
|
32
|
+
TaskLifecycleManager: () => TaskLifecycleManager,
|
|
33
|
+
ZuckerQueueModule: () => ZuckerQueueModule,
|
|
34
|
+
bullMqConnectionFromUrl: () => bullMqConnectionFromUrl
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/queue.interface.ts
|
|
39
|
+
var QUEUE_PROVIDER = "QUEUE_PROVIDER";
|
|
40
|
+
|
|
41
|
+
// src/in-memory-queue.ts
|
|
42
|
+
var import_common = require("@nestjs/common");
|
|
43
|
+
var InMemoryQueue = class _InMemoryQueue {
|
|
44
|
+
static {
|
|
45
|
+
__name(this, "InMemoryQueue");
|
|
46
|
+
}
|
|
47
|
+
logger = new import_common.Logger(_InMemoryQueue.name);
|
|
48
|
+
queues = /* @__PURE__ */ new Map();
|
|
49
|
+
paused = /* @__PURE__ */ new Set();
|
|
50
|
+
counter = 0;
|
|
51
|
+
/** timer 引用:queueName:jobId → timer */
|
|
52
|
+
delayTimers = /* @__PURE__ */ new Map();
|
|
53
|
+
getQueue(name) {
|
|
54
|
+
let q = this.queues.get(name);
|
|
55
|
+
if (!q) {
|
|
56
|
+
q = /* @__PURE__ */ new Map();
|
|
57
|
+
this.queues.set(name, q);
|
|
58
|
+
}
|
|
59
|
+
return q;
|
|
60
|
+
}
|
|
61
|
+
nextId() {
|
|
62
|
+
return String(++this.counter);
|
|
63
|
+
}
|
|
64
|
+
async add(queueName, data, options) {
|
|
65
|
+
const q = this.getQueue(queueName);
|
|
66
|
+
const id = options?.jobId ?? this.nextId();
|
|
67
|
+
const job = {
|
|
68
|
+
id,
|
|
69
|
+
data,
|
|
70
|
+
status: "waiting",
|
|
71
|
+
progress: 0,
|
|
72
|
+
attempts: 0,
|
|
73
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
74
|
+
};
|
|
75
|
+
q.set(id, job);
|
|
76
|
+
return job;
|
|
77
|
+
}
|
|
78
|
+
async ensure(queueName, data, options) {
|
|
79
|
+
const existing = await this.getJob(queueName, options.jobId);
|
|
80
|
+
if (existing && existing.status !== "completed" && existing.status !== "failed") {
|
|
81
|
+
return existing;
|
|
82
|
+
}
|
|
83
|
+
if (existing) await this.removeJob(queueName, options.jobId);
|
|
84
|
+
return this.add(queueName, data, options);
|
|
85
|
+
}
|
|
86
|
+
async addDelayed(queueName, data, delayMs, options) {
|
|
87
|
+
const q = this.getQueue(queueName);
|
|
88
|
+
const id = options?.jobId ?? this.nextId();
|
|
89
|
+
const job = {
|
|
90
|
+
id,
|
|
91
|
+
data,
|
|
92
|
+
status: "delayed",
|
|
93
|
+
progress: 0,
|
|
94
|
+
attempts: 0,
|
|
95
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
96
|
+
};
|
|
97
|
+
q.set(id, job);
|
|
98
|
+
const timerKey = `${queueName}:${id}`;
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
this.delayTimers.delete(timerKey);
|
|
101
|
+
const existing = q.get(id);
|
|
102
|
+
if (existing && existing.status === "delayed") {
|
|
103
|
+
existing.status = "waiting";
|
|
104
|
+
}
|
|
105
|
+
}, delayMs);
|
|
106
|
+
this.delayTimers.set(timerKey, timer);
|
|
107
|
+
return job;
|
|
108
|
+
}
|
|
109
|
+
async addRepeatable(queueName, data, pattern, options) {
|
|
110
|
+
this.logger.warn(`InMemoryQueue \u4E0D\u652F\u6301\u5B9A\u65F6\u4EFB\u52A1\u3002pattern "${pattern}" \u88AB\u5FFD\u7565\uFF0C\u4EC5\u521B\u5EFA\u4E00\u6B21\u6027\u4EFB\u52A1\u3002\u751F\u4EA7\u73AF\u5883\u8BF7\u4F7F\u7528 BullMQ\u3002`);
|
|
111
|
+
return this.add(queueName, data, options);
|
|
112
|
+
}
|
|
113
|
+
async getJob(queueName, jobId) {
|
|
114
|
+
const q = this.getQueue(queueName);
|
|
115
|
+
return q.get(jobId) ?? null;
|
|
116
|
+
}
|
|
117
|
+
async removeJob(queueName, jobId) {
|
|
118
|
+
const q = this.getQueue(queueName);
|
|
119
|
+
q.delete(jobId);
|
|
120
|
+
const timerKey = `${queueName}:${jobId}`;
|
|
121
|
+
const timer = this.delayTimers.get(timerKey);
|
|
122
|
+
if (timer) {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
this.delayTimers.delete(timerKey);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async pause(queueName) {
|
|
128
|
+
this.paused.add(queueName);
|
|
129
|
+
}
|
|
130
|
+
async resume(queueName) {
|
|
131
|
+
this.paused.delete(queueName);
|
|
132
|
+
}
|
|
133
|
+
async getStats(queueName) {
|
|
134
|
+
const q = this.getQueue(queueName);
|
|
135
|
+
const stats = {
|
|
136
|
+
waiting: 0,
|
|
137
|
+
active: 0,
|
|
138
|
+
completed: 0,
|
|
139
|
+
failed: 0,
|
|
140
|
+
delayed: 0
|
|
141
|
+
};
|
|
142
|
+
for (const job of q.values()) {
|
|
143
|
+
if (job.status !== "unknown") stats[job.status]++;
|
|
144
|
+
}
|
|
145
|
+
return stats;
|
|
146
|
+
}
|
|
147
|
+
onModuleDestroy() {
|
|
148
|
+
for (const timer of this.delayTimers.values()) {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
}
|
|
151
|
+
this.delayTimers.clear();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/bullmq-queue.provider.ts
|
|
156
|
+
var import_node_crypto = require("crypto");
|
|
157
|
+
var import_bullmq = require("bullmq");
|
|
158
|
+
var BullMqQueueProvider = class {
|
|
159
|
+
static {
|
|
160
|
+
__name(this, "BullMqQueueProvider");
|
|
161
|
+
}
|
|
162
|
+
options;
|
|
163
|
+
queues = /* @__PURE__ */ new Map();
|
|
164
|
+
defaultJobName;
|
|
165
|
+
queueFactory;
|
|
166
|
+
constructor(options) {
|
|
167
|
+
this.options = options;
|
|
168
|
+
this.defaultJobName = options.defaultJobName ?? "task";
|
|
169
|
+
this.queueFactory = options.queueFactory ?? ((name, queueOptions) => new import_bullmq.Queue(name, queueOptions));
|
|
170
|
+
}
|
|
171
|
+
async add(queueName, data, options) {
|
|
172
|
+
this.assertValidJobId(options?.jobId);
|
|
173
|
+
const job = await this.getQueue(queueName).add(options?.name ?? this.defaultJobName, data, this.toBullMqOptions(options));
|
|
174
|
+
return this.toJob(job);
|
|
175
|
+
}
|
|
176
|
+
async ensure(queueName, data, options) {
|
|
177
|
+
this.assertValidJobId(options.jobId);
|
|
178
|
+
const queue = this.getQueue(queueName);
|
|
179
|
+
const existing = await queue.getJob(options.jobId);
|
|
180
|
+
if (existing) {
|
|
181
|
+
const state = await existing.getState();
|
|
182
|
+
if (state !== "completed" && state !== "failed") {
|
|
183
|
+
return this.toJob(existing);
|
|
184
|
+
}
|
|
185
|
+
await existing.remove();
|
|
186
|
+
}
|
|
187
|
+
return this.add(queueName, data, options);
|
|
188
|
+
}
|
|
189
|
+
async addDelayed(queueName, data, delayMs, options) {
|
|
190
|
+
if (delayMs < 0) throw new RangeError("delayMs cannot be negative");
|
|
191
|
+
this.assertValidJobId(options?.jobId);
|
|
192
|
+
const job = await this.getQueue(queueName).add(options?.name ?? this.defaultJobName, data, {
|
|
193
|
+
...this.toBullMqOptions(options),
|
|
194
|
+
delay: delayMs
|
|
195
|
+
});
|
|
196
|
+
return this.toJob(job);
|
|
197
|
+
}
|
|
198
|
+
async addRepeatable(queueName, data, pattern, options) {
|
|
199
|
+
this.assertValidJobId(options?.jobId);
|
|
200
|
+
const name = options?.name ?? this.defaultJobName;
|
|
201
|
+
const schedulerId = options?.jobId ?? `scheduler-${(0, import_node_crypto.createHash)("sha256").update(name).update("\0").update(pattern).digest("hex")}`;
|
|
202
|
+
const job = await this.getQueue(queueName).upsertJobScheduler(schedulerId, {
|
|
203
|
+
pattern
|
|
204
|
+
}, {
|
|
205
|
+
name,
|
|
206
|
+
data,
|
|
207
|
+
opts: this.toBullMqSchedulerOptions(options)
|
|
208
|
+
});
|
|
209
|
+
return this.toJob(job);
|
|
210
|
+
}
|
|
211
|
+
async getJob(queueName, jobId) {
|
|
212
|
+
const job = await this.getQueue(queueName).getJob(jobId);
|
|
213
|
+
return job ? this.toJob(job) : null;
|
|
214
|
+
}
|
|
215
|
+
async removeJob(queueName, jobId) {
|
|
216
|
+
const job = await this.getQueue(queueName).getJob(jobId);
|
|
217
|
+
if (job) await job.remove();
|
|
218
|
+
}
|
|
219
|
+
async pause(queueName) {
|
|
220
|
+
await this.getQueue(queueName).pause();
|
|
221
|
+
}
|
|
222
|
+
async resume(queueName) {
|
|
223
|
+
await this.getQueue(queueName).resume();
|
|
224
|
+
}
|
|
225
|
+
async getStats(queueName) {
|
|
226
|
+
const pausedStateForStats = "paused";
|
|
227
|
+
const counts = await this.getQueue(queueName).getJobCounts("wait", "active", "completed", "failed", "delayed", "prioritized", pausedStateForStats);
|
|
228
|
+
return {
|
|
229
|
+
waiting: (counts.wait ?? 0) + (counts.prioritized ?? 0) + (counts.paused ?? 0),
|
|
230
|
+
active: counts.active ?? 0,
|
|
231
|
+
completed: counts.completed ?? 0,
|
|
232
|
+
failed: counts.failed ?? 0,
|
|
233
|
+
delayed: counts.delayed ?? 0
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async waitUntilReady() {
|
|
237
|
+
await Promise.all([
|
|
238
|
+
...this.queues.values()
|
|
239
|
+
].map((queue) => queue.waitUntilReady()));
|
|
240
|
+
}
|
|
241
|
+
async close() {
|
|
242
|
+
const queues = [
|
|
243
|
+
...this.queues.values()
|
|
244
|
+
];
|
|
245
|
+
this.queues.clear();
|
|
246
|
+
await Promise.all(queues.map((queue) => queue.close()));
|
|
247
|
+
}
|
|
248
|
+
async onModuleDestroy() {
|
|
249
|
+
await this.close();
|
|
250
|
+
}
|
|
251
|
+
getQueue(name) {
|
|
252
|
+
const existing = this.queues.get(name);
|
|
253
|
+
if (existing) return existing;
|
|
254
|
+
const queue = this.queueFactory(name, {
|
|
255
|
+
connection: this.options.connection,
|
|
256
|
+
prefix: this.options.prefix
|
|
257
|
+
});
|
|
258
|
+
this.queues.set(name, queue);
|
|
259
|
+
return queue;
|
|
260
|
+
}
|
|
261
|
+
toBullMqOptions(options) {
|
|
262
|
+
if (!options) return void 0;
|
|
263
|
+
return {
|
|
264
|
+
jobId: options.jobId,
|
|
265
|
+
deduplication: options.deduplicationId ? {
|
|
266
|
+
id: options.deduplicationId
|
|
267
|
+
} : void 0,
|
|
268
|
+
priority: options.priority,
|
|
269
|
+
attempts: options.attempts,
|
|
270
|
+
backoff: options.backoff,
|
|
271
|
+
removeOnComplete: options.removeOnComplete,
|
|
272
|
+
removeOnFail: options.removeOnFail
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
toBullMqSchedulerOptions(options) {
|
|
276
|
+
if (!options) return void 0;
|
|
277
|
+
return {
|
|
278
|
+
priority: options.priority,
|
|
279
|
+
attempts: options.attempts,
|
|
280
|
+
backoff: options.backoff,
|
|
281
|
+
removeOnComplete: options.removeOnComplete,
|
|
282
|
+
removeOnFail: options.removeOnFail
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
assertValidJobId(jobId) {
|
|
286
|
+
if (jobId?.includes(":")) {
|
|
287
|
+
throw new TypeError("BullMQ jobId cannot contain a colon");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async toJob(job) {
|
|
291
|
+
const state = await job.getState();
|
|
292
|
+
return {
|
|
293
|
+
id: String(job.id),
|
|
294
|
+
data: job.data,
|
|
295
|
+
status: this.mapState(state),
|
|
296
|
+
progress: job.progress,
|
|
297
|
+
attempts: job.attemptsMade,
|
|
298
|
+
failedReason: job.failedReason || void 0,
|
|
299
|
+
createdAt: new Date(job.timestamp),
|
|
300
|
+
processedAt: job.processedOn ? new Date(job.processedOn) : void 0,
|
|
301
|
+
completedAt: job.finishedOn ? new Date(job.finishedOn) : void 0
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
mapState(state) {
|
|
305
|
+
if (state === "active" || state === "completed" || state === "failed" || state === "delayed") {
|
|
306
|
+
return state;
|
|
307
|
+
}
|
|
308
|
+
if (state === "waiting" || state === "waiting-children" || state === "prioritized" || state === "paused") {
|
|
309
|
+
return "waiting";
|
|
310
|
+
}
|
|
311
|
+
return "unknown";
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/bullmq-connection.ts
|
|
316
|
+
function bullMqConnectionFromUrl(redisUrl, options = {}) {
|
|
317
|
+
const parsed = new URL(redisUrl);
|
|
318
|
+
if (parsed.protocol !== "redis:" && parsed.protocol !== "rediss:") {
|
|
319
|
+
throw new TypeError("BullMQ connection URL must use redis:// or rediss://");
|
|
320
|
+
}
|
|
321
|
+
const databasePath = parsed.pathname.replace(/^\//, "");
|
|
322
|
+
const database = databasePath === "" ? 0 : Number(databasePath);
|
|
323
|
+
if (!Number.isInteger(database) || database < 0) {
|
|
324
|
+
throw new TypeError("BullMQ Redis database must be a non-negative integer");
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
host: parsed.hostname,
|
|
328
|
+
port: parsed.port ? Number(parsed.port) : 6379,
|
|
329
|
+
username: parsed.username ? decodeURIComponent(parsed.username) : void 0,
|
|
330
|
+
password: parsed.password ? decodeURIComponent(parsed.password) : void 0,
|
|
331
|
+
db: database,
|
|
332
|
+
tls: parsed.protocol === "rediss:" ? {} : void 0,
|
|
333
|
+
maxRetriesPerRequest: options.role === "worker" ? null : 1
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
__name(bullMqConnectionFromUrl, "bullMqConnectionFromUrl");
|
|
337
|
+
|
|
338
|
+
// src/bullmq-worker-host.ts
|
|
339
|
+
var import_bullmq2 = require("bullmq");
|
|
340
|
+
var BullMqWorkerHost = class {
|
|
341
|
+
static {
|
|
342
|
+
__name(this, "BullMqWorkerHost");
|
|
343
|
+
}
|
|
344
|
+
options;
|
|
345
|
+
workers = /* @__PURE__ */ new Map();
|
|
346
|
+
readyQueues = /* @__PURE__ */ new Set();
|
|
347
|
+
workerFactory;
|
|
348
|
+
constructor(options) {
|
|
349
|
+
this.options = options;
|
|
350
|
+
this.workerFactory = options.workerFactory ?? ((queueName, processor, workerOptions) => new import_bullmq2.Worker(queueName, processor, workerOptions));
|
|
351
|
+
}
|
|
352
|
+
register(definition) {
|
|
353
|
+
if (this.workers.has(definition.queueName)) {
|
|
354
|
+
throw new Error(`Worker already registered for queue: ${definition.queueName}`);
|
|
355
|
+
}
|
|
356
|
+
if ((definition.concurrency ?? 1) < 1) throw new RangeError("concurrency must be at least 1");
|
|
357
|
+
if ((definition.lockDurationMs ?? 3e4) < 1) {
|
|
358
|
+
throw new RangeError("lockDurationMs must be at least 1");
|
|
359
|
+
}
|
|
360
|
+
const worker = this.workerFactory(definition.queueName, async (job) => definition.handler({
|
|
361
|
+
id: String(job.id),
|
|
362
|
+
name: job.name,
|
|
363
|
+
data: job.data,
|
|
364
|
+
attempt: job.attemptsMade + 1,
|
|
365
|
+
updateProgress: /* @__PURE__ */ __name((progress) => job.updateProgress(progress), "updateProgress"),
|
|
366
|
+
log: /* @__PURE__ */ __name((message) => job.log(message), "log")
|
|
367
|
+
}), {
|
|
368
|
+
connection: this.options.connection,
|
|
369
|
+
prefix: this.options.prefix,
|
|
370
|
+
concurrency: definition.concurrency ?? 1,
|
|
371
|
+
lockDuration: definition.lockDurationMs ?? 3e4,
|
|
372
|
+
autorun: definition.autorun ?? true
|
|
373
|
+
});
|
|
374
|
+
worker.on("ready", () => this.readyQueues.add(definition.queueName));
|
|
375
|
+
worker.on("error", (error) => {
|
|
376
|
+
this.readyQueues.delete(definition.queueName);
|
|
377
|
+
this.options.onError?.(definition.queueName, error);
|
|
378
|
+
});
|
|
379
|
+
this.workers.set(definition.queueName, worker);
|
|
380
|
+
}
|
|
381
|
+
async waitUntilReady() {
|
|
382
|
+
await Promise.all([
|
|
383
|
+
...this.workers.values()
|
|
384
|
+
].map((worker) => worker.waitUntilReady()));
|
|
385
|
+
for (const queueName of this.workers.keys()) this.readyQueues.add(queueName);
|
|
386
|
+
}
|
|
387
|
+
health() {
|
|
388
|
+
const registeredQueues = [
|
|
389
|
+
...this.workers.keys()
|
|
390
|
+
];
|
|
391
|
+
const runningQueues = registeredQueues.filter((queueName) => this.workers.get(queueName)?.isRunning());
|
|
392
|
+
return {
|
|
393
|
+
ready: registeredQueues.length > 0 && this.readyQueues.size === registeredQueues.length && runningQueues.length === registeredQueues.length,
|
|
394
|
+
registeredQueues,
|
|
395
|
+
runningQueues
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Actively verifies the Redis connections behind every worker.
|
|
400
|
+
*
|
|
401
|
+
* BullMQ's ready/error events cannot detect a half-open connection where the
|
|
402
|
+
* TCP socket still exists but Redis no longer answers. Readiness endpoints
|
|
403
|
+
* should use this bounded probe instead of relying on lifecycle events alone.
|
|
404
|
+
*/
|
|
405
|
+
async probeHealth(timeoutMs = 1e3) {
|
|
406
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
|
|
407
|
+
throw new RangeError("timeoutMs must be at least 1");
|
|
408
|
+
}
|
|
409
|
+
const current = this.health();
|
|
410
|
+
if (!current.ready) return current;
|
|
411
|
+
let timer;
|
|
412
|
+
try {
|
|
413
|
+
await Promise.race([
|
|
414
|
+
Promise.all([
|
|
415
|
+
...this.workers.values()
|
|
416
|
+
].map(async (worker) => {
|
|
417
|
+
const client = await worker.getBackend().client;
|
|
418
|
+
await client.get(worker.toKey("health-probe"));
|
|
419
|
+
})),
|
|
420
|
+
new Promise((_, reject) => {
|
|
421
|
+
timer = setTimeout(() => reject(new Error("Redis worker health probe timed out")), timeoutMs);
|
|
422
|
+
timer.unref?.();
|
|
423
|
+
})
|
|
424
|
+
]);
|
|
425
|
+
return this.health();
|
|
426
|
+
} catch {
|
|
427
|
+
return {
|
|
428
|
+
...this.health(),
|
|
429
|
+
ready: false
|
|
430
|
+
};
|
|
431
|
+
} finally {
|
|
432
|
+
if (timer) clearTimeout(timer);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
run(queueName) {
|
|
436
|
+
const worker = this.requireWorker(queueName);
|
|
437
|
+
if (worker.isRunning()) return;
|
|
438
|
+
void worker.run().catch((error) => this.options.onError?.(queueName, error));
|
|
439
|
+
}
|
|
440
|
+
async pause() {
|
|
441
|
+
await Promise.all([
|
|
442
|
+
...this.workers.values()
|
|
443
|
+
].map((worker) => worker.pause()));
|
|
444
|
+
}
|
|
445
|
+
async resume() {
|
|
446
|
+
await Promise.all([
|
|
447
|
+
...this.workers.values()
|
|
448
|
+
].map((worker) => worker.resume()));
|
|
449
|
+
}
|
|
450
|
+
async close(force = false) {
|
|
451
|
+
const workers = [
|
|
452
|
+
...this.workers.values()
|
|
453
|
+
];
|
|
454
|
+
this.workers.clear();
|
|
455
|
+
this.readyQueues.clear();
|
|
456
|
+
await Promise.all(workers.map((worker) => worker.close(force)));
|
|
457
|
+
}
|
|
458
|
+
async onModuleDestroy() {
|
|
459
|
+
await this.close();
|
|
460
|
+
}
|
|
461
|
+
requireWorker(queueName) {
|
|
462
|
+
const worker = this.workers.get(queueName);
|
|
463
|
+
if (!worker) throw new Error(`No worker registered for queue: ${queueName}`);
|
|
464
|
+
return worker;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// src/bullmq-queue-events.ts
|
|
469
|
+
var import_bullmq3 = require("bullmq");
|
|
470
|
+
var BullMqQueueEvents = class {
|
|
471
|
+
static {
|
|
472
|
+
__name(this, "BullMqQueueEvents");
|
|
473
|
+
}
|
|
474
|
+
options;
|
|
475
|
+
listeners = /* @__PURE__ */ new Map();
|
|
476
|
+
queueEventsFactory;
|
|
477
|
+
constructor(options) {
|
|
478
|
+
this.options = options;
|
|
479
|
+
this.queueEventsFactory = options.queueEventsFactory ?? ((queueName, queueOptions) => new import_bullmq3.QueueEvents(queueName, queueOptions));
|
|
480
|
+
}
|
|
481
|
+
listen(queueName, handlers) {
|
|
482
|
+
const queueEvents = this.getQueueEvents(queueName);
|
|
483
|
+
const subscriptions = [];
|
|
484
|
+
if (handlers.completed) {
|
|
485
|
+
const handler = /* @__PURE__ */ __name(({ jobId, returnvalue }) => handlers.completed?.({
|
|
486
|
+
queueName,
|
|
487
|
+
jobId,
|
|
488
|
+
returnvalue
|
|
489
|
+
}), "handler");
|
|
490
|
+
queueEvents.on("completed", handler);
|
|
491
|
+
subscriptions.push(() => queueEvents.off("completed", handler));
|
|
492
|
+
}
|
|
493
|
+
if (handlers.failed) {
|
|
494
|
+
const handler = /* @__PURE__ */ __name(({ jobId, failedReason }) => handlers.failed?.({
|
|
495
|
+
queueName,
|
|
496
|
+
jobId,
|
|
497
|
+
failedReason
|
|
498
|
+
}), "handler");
|
|
499
|
+
queueEvents.on("failed", handler);
|
|
500
|
+
subscriptions.push(() => queueEvents.off("failed", handler));
|
|
501
|
+
}
|
|
502
|
+
if (handlers.progress) {
|
|
503
|
+
const handler = /* @__PURE__ */ __name(({ jobId, data }) => handlers.progress?.({
|
|
504
|
+
queueName,
|
|
505
|
+
jobId,
|
|
506
|
+
data
|
|
507
|
+
}), "handler");
|
|
508
|
+
queueEvents.on("progress", handler);
|
|
509
|
+
subscriptions.push(() => queueEvents.off("progress", handler));
|
|
510
|
+
}
|
|
511
|
+
if (handlers.waiting) {
|
|
512
|
+
const handler = /* @__PURE__ */ __name(({ jobId }) => handlers.waiting?.({
|
|
513
|
+
queueName,
|
|
514
|
+
jobId
|
|
515
|
+
}), "handler");
|
|
516
|
+
queueEvents.on("waiting", handler);
|
|
517
|
+
subscriptions.push(() => queueEvents.off("waiting", handler));
|
|
518
|
+
}
|
|
519
|
+
if (handlers.active) {
|
|
520
|
+
const handler = /* @__PURE__ */ __name(({ jobId }) => handlers.active?.({
|
|
521
|
+
queueName,
|
|
522
|
+
jobId
|
|
523
|
+
}), "handler");
|
|
524
|
+
queueEvents.on("active", handler);
|
|
525
|
+
subscriptions.push(() => queueEvents.off("active", handler));
|
|
526
|
+
}
|
|
527
|
+
return () => {
|
|
528
|
+
for (const unsubscribe of subscriptions) unsubscribe();
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
async waitUntilReady() {
|
|
532
|
+
await Promise.all([
|
|
533
|
+
...this.listeners.values()
|
|
534
|
+
].map((listener) => listener.waitUntilReady()));
|
|
535
|
+
}
|
|
536
|
+
async close() {
|
|
537
|
+
const listeners = [
|
|
538
|
+
...this.listeners.values()
|
|
539
|
+
];
|
|
540
|
+
this.listeners.clear();
|
|
541
|
+
await Promise.all(listeners.map((listener) => listener.close()));
|
|
542
|
+
}
|
|
543
|
+
async onModuleDestroy() {
|
|
544
|
+
await this.close();
|
|
545
|
+
}
|
|
546
|
+
getQueueEvents(queueName) {
|
|
547
|
+
const existing = this.listeners.get(queueName);
|
|
548
|
+
if (existing) return existing;
|
|
549
|
+
const queueEvents = this.queueEventsFactory(queueName, {
|
|
550
|
+
connection: this.options.connection,
|
|
551
|
+
prefix: this.options.prefix
|
|
552
|
+
});
|
|
553
|
+
queueEvents.on("error", (error) => this.options.onError?.(queueName, error));
|
|
554
|
+
this.listeners.set(queueName, queueEvents);
|
|
555
|
+
return queueEvents;
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// src/queue.service.ts
|
|
560
|
+
var import_common2 = require("@nestjs/common");
|
|
561
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
562
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
563
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
564
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
565
|
+
} else {
|
|
566
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
567
|
+
if (d = decorators[i]) {
|
|
568
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
573
|
+
}
|
|
574
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
575
|
+
function _ts_metadata(metadataKey, metadataValue) {
|
|
576
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
577
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
581
|
+
function _ts_param(paramIndex, decorator) {
|
|
582
|
+
return function(target, key) {
|
|
583
|
+
decorator(target, key, paramIndex);
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
__name(_ts_param, "_ts_param");
|
|
587
|
+
var QueueService = class {
|
|
588
|
+
static {
|
|
589
|
+
__name(this, "QueueService");
|
|
590
|
+
}
|
|
591
|
+
provider;
|
|
592
|
+
constructor(provider) {
|
|
593
|
+
this.provider = provider;
|
|
594
|
+
}
|
|
595
|
+
/** 添加任务到队列 */
|
|
596
|
+
add(queueName, data, options) {
|
|
597
|
+
return this.provider.add(queueName, data, options);
|
|
598
|
+
}
|
|
599
|
+
/** Ensure a stable transport job exists for durable queued work. */
|
|
600
|
+
ensure(queueName, data, options) {
|
|
601
|
+
return this.provider.ensure(queueName, data, options);
|
|
602
|
+
}
|
|
603
|
+
/** 添加延迟任务 */
|
|
604
|
+
addDelayed(queueName, data, delayMs, options) {
|
|
605
|
+
return this.provider.addDelayed(queueName, data, delayMs, options);
|
|
606
|
+
}
|
|
607
|
+
/** 添加定时任务 */
|
|
608
|
+
addRepeatable(queueName, data, pattern, options) {
|
|
609
|
+
return this.provider.addRepeatable(queueName, data, pattern, options);
|
|
610
|
+
}
|
|
611
|
+
/** 获取任务状态 */
|
|
612
|
+
getJob(queueName, jobId) {
|
|
613
|
+
return this.provider.getJob(queueName, jobId);
|
|
614
|
+
}
|
|
615
|
+
/** 移除任务 */
|
|
616
|
+
removeJob(queueName, jobId) {
|
|
617
|
+
return this.provider.removeJob(queueName, jobId);
|
|
618
|
+
}
|
|
619
|
+
/** 暂停队列 */
|
|
620
|
+
pause(queueName) {
|
|
621
|
+
return this.provider.pause(queueName);
|
|
622
|
+
}
|
|
623
|
+
/** 恢复队列 */
|
|
624
|
+
resume(queueName) {
|
|
625
|
+
return this.provider.resume(queueName);
|
|
626
|
+
}
|
|
627
|
+
/** 获取队列统计 */
|
|
628
|
+
getStats(queueName) {
|
|
629
|
+
return this.provider.getStats(queueName);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
QueueService = _ts_decorate([
|
|
633
|
+
(0, import_common2.Injectable)(),
|
|
634
|
+
_ts_param(0, (0, import_common2.Inject)(QUEUE_PROVIDER)),
|
|
635
|
+
_ts_metadata("design:type", Function),
|
|
636
|
+
_ts_metadata("design:paramtypes", [
|
|
637
|
+
typeof QueueProvider === "undefined" ? Object : QueueProvider
|
|
638
|
+
])
|
|
639
|
+
], QueueService);
|
|
640
|
+
|
|
641
|
+
// src/queue.module.ts
|
|
642
|
+
var import_common3 = require("@nestjs/common");
|
|
643
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
644
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
645
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
646
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
647
|
+
} else {
|
|
648
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
649
|
+
if (d = decorators[i]) {
|
|
650
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
655
|
+
}
|
|
656
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
657
|
+
var QUEUE_MODULE_OPTIONS = /* @__PURE__ */ Symbol("QUEUE_MODULE_OPTIONS");
|
|
658
|
+
var ZuckerQueueModule = class _ZuckerQueueModule {
|
|
659
|
+
static {
|
|
660
|
+
__name(this, "ZuckerQueueModule");
|
|
661
|
+
}
|
|
662
|
+
static forRoot(options = {}) {
|
|
663
|
+
return {
|
|
664
|
+
module: _ZuckerQueueModule,
|
|
665
|
+
global: true,
|
|
666
|
+
providers: [
|
|
667
|
+
{
|
|
668
|
+
provide: QUEUE_PROVIDER,
|
|
669
|
+
useFactory: /* @__PURE__ */ __name(() => options.provider ?? new InMemoryQueue(), "useFactory")
|
|
670
|
+
},
|
|
671
|
+
QueueService
|
|
672
|
+
],
|
|
673
|
+
exports: [
|
|
674
|
+
QUEUE_PROVIDER,
|
|
675
|
+
QueueService
|
|
676
|
+
]
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
static forRootAsync(options) {
|
|
680
|
+
const asyncProviders = this.createAsyncProviders(options);
|
|
681
|
+
return {
|
|
682
|
+
module: _ZuckerQueueModule,
|
|
683
|
+
global: true,
|
|
684
|
+
imports: options.imports ?? [],
|
|
685
|
+
providers: [
|
|
686
|
+
...asyncProviders,
|
|
687
|
+
{
|
|
688
|
+
provide: QUEUE_PROVIDER,
|
|
689
|
+
useFactory: /* @__PURE__ */ __name((opts) => opts.provider ?? new InMemoryQueue(), "useFactory"),
|
|
690
|
+
inject: [
|
|
691
|
+
QUEUE_MODULE_OPTIONS
|
|
692
|
+
]
|
|
693
|
+
},
|
|
694
|
+
QueueService
|
|
695
|
+
],
|
|
696
|
+
exports: [
|
|
697
|
+
QUEUE_PROVIDER,
|
|
698
|
+
QueueService
|
|
699
|
+
]
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
static createAsyncProviders(options) {
|
|
703
|
+
if (options.useFactory) {
|
|
704
|
+
return [
|
|
705
|
+
{
|
|
706
|
+
provide: QUEUE_MODULE_OPTIONS,
|
|
707
|
+
useFactory: options.useFactory,
|
|
708
|
+
inject: options.inject ?? []
|
|
709
|
+
}
|
|
710
|
+
];
|
|
711
|
+
}
|
|
712
|
+
const factoryType = options.useClass ?? options.useExisting;
|
|
713
|
+
if (!factoryType) {
|
|
714
|
+
throw new Error("QueueModuleAsyncOptions requires useFactory, useClass, or useExisting");
|
|
715
|
+
}
|
|
716
|
+
const optionsProvider = {
|
|
717
|
+
provide: QUEUE_MODULE_OPTIONS,
|
|
718
|
+
useFactory: /* @__PURE__ */ __name(async (factory) => factory.createQueueOptions(), "useFactory"),
|
|
719
|
+
inject: [
|
|
720
|
+
factoryType
|
|
721
|
+
]
|
|
722
|
+
};
|
|
723
|
+
if (options.useClass) {
|
|
724
|
+
return [
|
|
725
|
+
{
|
|
726
|
+
provide: options.useClass,
|
|
727
|
+
useClass: options.useClass
|
|
728
|
+
},
|
|
729
|
+
optionsProvider
|
|
730
|
+
];
|
|
731
|
+
}
|
|
732
|
+
return [
|
|
733
|
+
optionsProvider
|
|
734
|
+
];
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
ZuckerQueueModule = _ts_decorate2([
|
|
738
|
+
(0, import_common3.Global)(),
|
|
739
|
+
(0, import_common3.Module)({})
|
|
740
|
+
], ZuckerQueueModule);
|
|
741
|
+
|
|
742
|
+
// src/worker.decorator.ts
|
|
743
|
+
var import_common4 = require("@nestjs/common");
|
|
744
|
+
var QUEUE_WORKER_KEY = "ZUCKER_QUEUE_WORKER";
|
|
745
|
+
function QueueWorker(options) {
|
|
746
|
+
const opts = typeof options === "string" ? {
|
|
747
|
+
queue: options
|
|
748
|
+
} : options;
|
|
749
|
+
return (0, import_common4.SetMetadata)(QUEUE_WORKER_KEY, opts);
|
|
750
|
+
}
|
|
751
|
+
__name(QueueWorker, "QueueWorker");
|
|
752
|
+
|
|
753
|
+
// src/task-lifecycle.ts
|
|
754
|
+
var import_common5 = require("@nestjs/common");
|
|
755
|
+
var taskIdCounter = 0;
|
|
756
|
+
function generateTaskId() {
|
|
757
|
+
return `task_${Date.now()}_${++taskIdCounter}`;
|
|
758
|
+
}
|
|
759
|
+
__name(generateTaskId, "generateTaskId");
|
|
760
|
+
var TaskLifecycleManager = class _TaskLifecycleManager {
|
|
761
|
+
static {
|
|
762
|
+
__name(this, "TaskLifecycleManager");
|
|
763
|
+
}
|
|
764
|
+
logger = new import_common5.Logger(_TaskLifecycleManager.name);
|
|
765
|
+
tasks = /* @__PURE__ */ new Map();
|
|
766
|
+
cancelledTasks = /* @__PURE__ */ new Set();
|
|
767
|
+
deadLetterQueue = [];
|
|
768
|
+
maxDlqSize;
|
|
769
|
+
constructor(options) {
|
|
770
|
+
this.maxDlqSize = options?.maxDlqSize ?? 100;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* 提交任务并异步执行
|
|
774
|
+
*/
|
|
775
|
+
submit(definition) {
|
|
776
|
+
const id = generateTaskId();
|
|
777
|
+
const info = {
|
|
778
|
+
id,
|
|
779
|
+
name: definition.name,
|
|
780
|
+
status: "pending",
|
|
781
|
+
progress: 0,
|
|
782
|
+
createdAt: Date.now(),
|
|
783
|
+
attempts: 0,
|
|
784
|
+
metadata: definition.metadata
|
|
785
|
+
};
|
|
786
|
+
this.tasks.set(id, info);
|
|
787
|
+
this.executeTask(id, definition);
|
|
788
|
+
return id;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* 获取任务状态
|
|
792
|
+
*/
|
|
793
|
+
getTask(id) {
|
|
794
|
+
return this.tasks.get(id);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* 列出所有任务
|
|
798
|
+
*/
|
|
799
|
+
listTasks(filter) {
|
|
800
|
+
const tasks = [
|
|
801
|
+
...this.tasks.values()
|
|
802
|
+
];
|
|
803
|
+
if (filter?.status) {
|
|
804
|
+
return tasks.filter((t) => t.status === filter.status);
|
|
805
|
+
}
|
|
806
|
+
return tasks;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* 取消任务
|
|
810
|
+
*/
|
|
811
|
+
kill(id) {
|
|
812
|
+
const task = this.tasks.get(id);
|
|
813
|
+
if (!task || task.status === "completed" || task.status === "failed" || task.status === "killed") {
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
this.cancelledTasks.add(id);
|
|
817
|
+
task.status = "killed";
|
|
818
|
+
task.completedAt = Date.now();
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* 获取死信队列中的失败任务
|
|
823
|
+
*/
|
|
824
|
+
getDeadLetterQueue() {
|
|
825
|
+
return [
|
|
826
|
+
...this.deadLetterQueue
|
|
827
|
+
];
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* 清理已完成的任务(释放内存)
|
|
831
|
+
*/
|
|
832
|
+
cleanup(olderThanMs = 36e5) {
|
|
833
|
+
const cutoff = Date.now() - olderThanMs;
|
|
834
|
+
let cleaned = 0;
|
|
835
|
+
for (const [id, task] of this.tasks) {
|
|
836
|
+
if ((task.status === "completed" || task.status === "failed" || task.status === "killed") && (task.completedAt ?? task.createdAt) < cutoff) {
|
|
837
|
+
this.tasks.delete(id);
|
|
838
|
+
cleaned++;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return cleaned;
|
|
842
|
+
}
|
|
843
|
+
async executeTask(id, definition) {
|
|
844
|
+
const info = this.tasks.get(id);
|
|
845
|
+
const maxRetries = definition.maxRetries ?? 0;
|
|
846
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
847
|
+
if (this.cancelledTasks.has(id)) return;
|
|
848
|
+
info.status = "running";
|
|
849
|
+
info.startedAt = Date.now();
|
|
850
|
+
info.attempts = attempt + 1;
|
|
851
|
+
const ctx = {
|
|
852
|
+
taskId: id,
|
|
853
|
+
updateProgress: /* @__PURE__ */ __name((progress, message) => {
|
|
854
|
+
info.progress = Math.max(0, Math.min(1, progress));
|
|
855
|
+
info.progressMessage = message;
|
|
856
|
+
}, "updateProgress"),
|
|
857
|
+
isCancelled: /* @__PURE__ */ __name(() => this.cancelledTasks.has(id), "isCancelled")
|
|
858
|
+
};
|
|
859
|
+
try {
|
|
860
|
+
const resultPromise = definition.handler(ctx);
|
|
861
|
+
let result;
|
|
862
|
+
if (definition.timeoutMs) {
|
|
863
|
+
result = await Promise.race([
|
|
864
|
+
resultPromise,
|
|
865
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Task timeout after ${definition.timeoutMs}ms`)), definition.timeoutMs))
|
|
866
|
+
]);
|
|
867
|
+
} else {
|
|
868
|
+
result = await resultPromise;
|
|
869
|
+
}
|
|
870
|
+
if (this.cancelledTasks.has(id)) return;
|
|
871
|
+
info.status = "completed";
|
|
872
|
+
info.progress = 1;
|
|
873
|
+
info.result = result;
|
|
874
|
+
info.completedAt = Date.now();
|
|
875
|
+
return;
|
|
876
|
+
} catch (error) {
|
|
877
|
+
if (this.cancelledTasks.has(id)) return;
|
|
878
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
879
|
+
if (attempt < maxRetries) {
|
|
880
|
+
this.logger.warn(`Task "${info.name}" attempt ${attempt + 1} failed: ${errorMsg}. Retrying...`);
|
|
881
|
+
await new Promise((r) => setTimeout(r, 1e3 * Math.pow(2, attempt)));
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
info.status = "failed";
|
|
885
|
+
info.error = errorMsg;
|
|
886
|
+
info.completedAt = Date.now();
|
|
887
|
+
if (this.deadLetterQueue.length >= this.maxDlqSize) {
|
|
888
|
+
this.deadLetterQueue.shift();
|
|
889
|
+
}
|
|
890
|
+
this.deadLetterQueue.push({
|
|
891
|
+
...info
|
|
892
|
+
});
|
|
893
|
+
this.logger.error(`Task "${info.name}" failed after ${info.attempts} attempts: ${errorMsg}`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
899
|
+
0 && (module.exports = {
|
|
900
|
+
BullMqQueueEvents,
|
|
901
|
+
BullMqQueueProvider,
|
|
902
|
+
BullMqWorkerHost,
|
|
903
|
+
InMemoryQueue,
|
|
904
|
+
QUEUE_PROVIDER,
|
|
905
|
+
QUEUE_WORKER_KEY,
|
|
906
|
+
QueueService,
|
|
907
|
+
QueueWorker,
|
|
908
|
+
TaskLifecycleManager,
|
|
909
|
+
ZuckerQueueModule,
|
|
910
|
+
bullMqConnectionFromUrl
|
|
911
|
+
});
|