@igniter-js/jobs 0.1.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/AGENTS.md +557 -0
- package/README.md +410 -0
- package/dist/adapter-CcQCatSa.d.mts +1411 -0
- package/dist/adapter-CcQCatSa.d.ts +1411 -0
- package/dist/adapters/bullmq.adapter.d.mts +131 -0
- package/dist/adapters/bullmq.adapter.d.ts +131 -0
- package/dist/adapters/bullmq.adapter.js +598 -0
- package/dist/adapters/bullmq.adapter.js.map +1 -0
- package/dist/adapters/bullmq.adapter.mjs +596 -0
- package/dist/adapters/bullmq.adapter.mjs.map +1 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +1129 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +1126 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/adapters/memory.adapter.d.mts +118 -0
- package/dist/adapters/memory.adapter.d.ts +118 -0
- package/dist/adapters/memory.adapter.js +571 -0
- package/dist/adapters/memory.adapter.js.map +1 -0
- package/dist/adapters/memory.adapter.mjs +569 -0
- package/dist/adapters/memory.adapter.mjs.map +1 -0
- package/dist/index.d.mts +1107 -0
- package/dist/index.d.ts +1107 -0
- package/dist/index.js +1137 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1128 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@igniter-js/core');
|
|
4
|
+
|
|
5
|
+
// src/errors/igniter-jobs.error.ts
|
|
6
|
+
var IgniterJobsError = class _IgniterJobsError extends core.IgniterError {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
super({
|
|
9
|
+
code: options.code,
|
|
10
|
+
message: options.message,
|
|
11
|
+
statusCode: options.statusCode ?? 500,
|
|
12
|
+
causer: "@igniter-js/jobs",
|
|
13
|
+
cause: options.cause,
|
|
14
|
+
details: options.details,
|
|
15
|
+
logger: options.logger
|
|
16
|
+
});
|
|
17
|
+
this.code = options.code;
|
|
18
|
+
this.details = options.details;
|
|
19
|
+
this.name = "IgniterJobsError";
|
|
20
|
+
if (Error.captureStackTrace) {
|
|
21
|
+
Error.captureStackTrace(this, _IgniterJobsError);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Convert error to a plain object for serialization.
|
|
26
|
+
*/
|
|
27
|
+
toJSON() {
|
|
28
|
+
return {
|
|
29
|
+
name: this.name,
|
|
30
|
+
code: this.code,
|
|
31
|
+
message: this.message,
|
|
32
|
+
statusCode: this.statusCode,
|
|
33
|
+
details: this.details,
|
|
34
|
+
stack: this.stack
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/adapters/bullmq.adapter.ts
|
|
40
|
+
var BullMQAdapter = class _BullMQAdapter {
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.queues = /* @__PURE__ */ new Map();
|
|
43
|
+
this.workers = /* @__PURE__ */ new Map();
|
|
44
|
+
this.queueEvents = /* @__PURE__ */ new Map();
|
|
45
|
+
this.BullMQ = null;
|
|
46
|
+
this.redis = options.redis;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a new BullMQ adapter.
|
|
50
|
+
*
|
|
51
|
+
* @param options - Adapter options with Redis connection
|
|
52
|
+
* @returns A new BullMQAdapter instance
|
|
53
|
+
*/
|
|
54
|
+
static create(options) {
|
|
55
|
+
return new _BullMQAdapter(options);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the underlying Redis client.
|
|
59
|
+
*/
|
|
60
|
+
get client() {
|
|
61
|
+
return this.redis;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Lazily load BullMQ module.
|
|
65
|
+
*/
|
|
66
|
+
async getBullMQ() {
|
|
67
|
+
if (!this.BullMQ) {
|
|
68
|
+
this.BullMQ = await import('bullmq');
|
|
69
|
+
}
|
|
70
|
+
return this.BullMQ;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get or create a queue instance.
|
|
74
|
+
*/
|
|
75
|
+
async getOrCreateQueue(name) {
|
|
76
|
+
if (!this.queues.has(name)) {
|
|
77
|
+
const { Queue } = await this.getBullMQ();
|
|
78
|
+
const queue = new Queue(name, {
|
|
79
|
+
connection: this.redis
|
|
80
|
+
});
|
|
81
|
+
this.queues.set(name, queue);
|
|
82
|
+
}
|
|
83
|
+
return this.queues.get(name);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get or create queue events instance.
|
|
87
|
+
*/
|
|
88
|
+
async getQueueEvents(name) {
|
|
89
|
+
if (!this.queueEvents.has(name)) {
|
|
90
|
+
const { QueueEvents } = await this.getBullMQ();
|
|
91
|
+
const events = new QueueEvents(name, {
|
|
92
|
+
connection: this.redis
|
|
93
|
+
});
|
|
94
|
+
this.queueEvents.set(name, events);
|
|
95
|
+
}
|
|
96
|
+
return this.queueEvents.get(name);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Convert BullMQ job state to IgniterJobStatus.
|
|
100
|
+
*/
|
|
101
|
+
mapJobState(state) {
|
|
102
|
+
const stateMap = {
|
|
103
|
+
waiting: "waiting",
|
|
104
|
+
active: "active",
|
|
105
|
+
completed: "completed",
|
|
106
|
+
failed: "failed",
|
|
107
|
+
delayed: "delayed",
|
|
108
|
+
paused: "paused",
|
|
109
|
+
"waiting-children": "waiting"
|
|
110
|
+
};
|
|
111
|
+
return stateMap[state] || "waiting";
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Convert BullMQ job to IgniterJobInfo.
|
|
115
|
+
*/
|
|
116
|
+
async mapJobToInfo(job) {
|
|
117
|
+
const data = job.data;
|
|
118
|
+
const state = await job.getState();
|
|
119
|
+
return {
|
|
120
|
+
id: job.id,
|
|
121
|
+
name: job.name,
|
|
122
|
+
queue: job.queueName,
|
|
123
|
+
state: this.mapJobState(state || "waiting"),
|
|
124
|
+
data: data.input ?? data,
|
|
125
|
+
result: job.returnvalue,
|
|
126
|
+
error: job.failedReason,
|
|
127
|
+
progress: typeof job.progress === "number" ? job.progress : 0,
|
|
128
|
+
attempts: job.attemptsMade,
|
|
129
|
+
timestamp: job.timestamp,
|
|
130
|
+
processedOn: job.processedOn,
|
|
131
|
+
finishedOn: job.finishedOn,
|
|
132
|
+
delay: job.delay,
|
|
133
|
+
priority: job.opts?.priority,
|
|
134
|
+
scope: data.scope,
|
|
135
|
+
actor: data.actor,
|
|
136
|
+
metadata: data.metadata
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// ==========================================
|
|
140
|
+
// JOB OPERATIONS
|
|
141
|
+
// ==========================================
|
|
142
|
+
async dispatch(params) {
|
|
143
|
+
const queue = await this.getOrCreateQueue(params.queue);
|
|
144
|
+
const jobOptions = {
|
|
145
|
+
jobId: params.jobId,
|
|
146
|
+
delay: params.delay,
|
|
147
|
+
priority: params.priority,
|
|
148
|
+
attempts: params.attempts ?? 3,
|
|
149
|
+
backoff: params.backoff ? {
|
|
150
|
+
type: params.backoff.type === "custom" ? "fixed" : params.backoff.type,
|
|
151
|
+
delay: params.backoff.type === "custom" ? params.backoff.delays[0] : params.backoff.delay
|
|
152
|
+
} : void 0,
|
|
153
|
+
removeOnComplete: params.removeOnComplete,
|
|
154
|
+
removeOnFail: params.removeOnFail
|
|
155
|
+
};
|
|
156
|
+
const jobData = {
|
|
157
|
+
input: params.data,
|
|
158
|
+
scope: params.scope,
|
|
159
|
+
actor: params.actor
|
|
160
|
+
};
|
|
161
|
+
const job = await queue.add(params.name, jobData, jobOptions);
|
|
162
|
+
return job.id;
|
|
163
|
+
}
|
|
164
|
+
async schedule(params) {
|
|
165
|
+
const queue = await this.getOrCreateQueue(params.queue);
|
|
166
|
+
const jobOptions = {
|
|
167
|
+
jobId: params.jobId,
|
|
168
|
+
delay: params.at ? params.at.getTime() - Date.now() : params.delay,
|
|
169
|
+
priority: params.priority,
|
|
170
|
+
attempts: params.attempts ?? 3,
|
|
171
|
+
backoff: params.backoff ? {
|
|
172
|
+
type: params.backoff.type === "custom" ? "fixed" : params.backoff.type,
|
|
173
|
+
delay: params.backoff.type === "custom" ? params.backoff.delays[0] : params.backoff.delay
|
|
174
|
+
} : void 0,
|
|
175
|
+
removeOnComplete: params.removeOnComplete,
|
|
176
|
+
removeOnFail: params.removeOnFail,
|
|
177
|
+
repeat: params.cron ? {
|
|
178
|
+
pattern: params.cron,
|
|
179
|
+
tz: params.timezone
|
|
180
|
+
} : params.every ? {
|
|
181
|
+
every: params.every
|
|
182
|
+
} : void 0
|
|
183
|
+
};
|
|
184
|
+
const jobData = {
|
|
185
|
+
input: params.data,
|
|
186
|
+
scope: params.scope,
|
|
187
|
+
actor: params.actor
|
|
188
|
+
};
|
|
189
|
+
const job = await queue.add(params.name, jobData, jobOptions);
|
|
190
|
+
return job.id;
|
|
191
|
+
}
|
|
192
|
+
async getJob(queue, jobId) {
|
|
193
|
+
const q = await this.getOrCreateQueue(queue);
|
|
194
|
+
const job = await q.getJob(jobId);
|
|
195
|
+
if (!job) return null;
|
|
196
|
+
return await this.mapJobToInfo(job);
|
|
197
|
+
}
|
|
198
|
+
async getJobState(queue, jobId) {
|
|
199
|
+
const q = await this.getOrCreateQueue(queue);
|
|
200
|
+
const job = await q.getJob(jobId);
|
|
201
|
+
if (!job) return null;
|
|
202
|
+
const state = await job.getState();
|
|
203
|
+
return this.mapJobState(state);
|
|
204
|
+
}
|
|
205
|
+
async getJobProgress(queue, jobId) {
|
|
206
|
+
const q = await this.getOrCreateQueue(queue);
|
|
207
|
+
const job = await q.getJob(jobId);
|
|
208
|
+
if (!job) return 0;
|
|
209
|
+
return typeof job.progress === "number" ? job.progress : 0;
|
|
210
|
+
}
|
|
211
|
+
async getJobLogs(queue, jobId) {
|
|
212
|
+
const q = await this.getOrCreateQueue(queue);
|
|
213
|
+
const job = await q.getJob(jobId);
|
|
214
|
+
if (!job) return [];
|
|
215
|
+
const { logs } = await q.getJobLogs(jobId);
|
|
216
|
+
return logs.map((log, index) => ({
|
|
217
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
218
|
+
message: log,
|
|
219
|
+
level: "info"
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
async retryJob(queue, jobId) {
|
|
223
|
+
const q = await this.getOrCreateQueue(queue);
|
|
224
|
+
const job = await q.getJob(jobId);
|
|
225
|
+
if (!job) {
|
|
226
|
+
throw new IgniterJobsError({
|
|
227
|
+
code: "JOBS_JOB_NOT_FOUND",
|
|
228
|
+
message: `Job "${jobId}" not found in queue "${queue}"`,
|
|
229
|
+
statusCode: 404
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
await job.retry();
|
|
233
|
+
}
|
|
234
|
+
async removeJob(queue, jobId) {
|
|
235
|
+
const q = await this.getOrCreateQueue(queue);
|
|
236
|
+
const job = await q.getJob(jobId);
|
|
237
|
+
if (!job) return;
|
|
238
|
+
await job.remove();
|
|
239
|
+
}
|
|
240
|
+
async promoteJob(queue, jobId) {
|
|
241
|
+
const q = await this.getOrCreateQueue(queue);
|
|
242
|
+
const job = await q.getJob(jobId);
|
|
243
|
+
if (!job) {
|
|
244
|
+
throw new IgniterJobsError({
|
|
245
|
+
code: "JOBS_JOB_NOT_FOUND",
|
|
246
|
+
message: `Job "${jobId}" not found in queue "${queue}"`,
|
|
247
|
+
statusCode: 404
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
await job.promote();
|
|
251
|
+
}
|
|
252
|
+
async moveJob(queue, jobId, state, reason) {
|
|
253
|
+
const q = await this.getOrCreateQueue(queue);
|
|
254
|
+
const job = await q.getJob(jobId);
|
|
255
|
+
if (!job) {
|
|
256
|
+
throw new IgniterJobsError({
|
|
257
|
+
code: "JOBS_JOB_NOT_FOUND",
|
|
258
|
+
message: `Job "${jobId}" not found in queue "${queue}"`,
|
|
259
|
+
statusCode: 404
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (state === "failed") {
|
|
263
|
+
await job.moveToFailed(new Error(reason || "Manually moved to failed"), "manual");
|
|
264
|
+
} else {
|
|
265
|
+
await job.moveToCompleted(reason || "Manually completed", "manual");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async retryJobs(queue, jobIds) {
|
|
269
|
+
const q = await this.getOrCreateQueue(queue);
|
|
270
|
+
await Promise.all(
|
|
271
|
+
jobIds.map(async (jobId) => {
|
|
272
|
+
const job = await q.getJob(jobId);
|
|
273
|
+
if (job) await job.retry();
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
async removeJobs(queue, jobIds) {
|
|
278
|
+
const q = await this.getOrCreateQueue(queue);
|
|
279
|
+
await Promise.all(
|
|
280
|
+
jobIds.map(async (jobId) => {
|
|
281
|
+
const job = await q.getJob(jobId);
|
|
282
|
+
if (job) await job.remove();
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
// ==========================================
|
|
287
|
+
// QUEUE OPERATIONS
|
|
288
|
+
// ==========================================
|
|
289
|
+
async getQueue(queue) {
|
|
290
|
+
const q = await this.getOrCreateQueue(queue);
|
|
291
|
+
const isPaused = await q.isPaused();
|
|
292
|
+
const counts = await q.getJobCounts();
|
|
293
|
+
return {
|
|
294
|
+
name: queue,
|
|
295
|
+
isPaused,
|
|
296
|
+
jobCounts: {
|
|
297
|
+
waiting: counts.waiting || 0,
|
|
298
|
+
active: counts.active || 0,
|
|
299
|
+
completed: counts.completed || 0,
|
|
300
|
+
failed: counts.failed || 0,
|
|
301
|
+
delayed: counts.delayed || 0,
|
|
302
|
+
paused: counts.paused || 0
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
async pauseQueue(queue) {
|
|
307
|
+
const q = await this.getOrCreateQueue(queue);
|
|
308
|
+
await q.pause();
|
|
309
|
+
}
|
|
310
|
+
async resumeQueue(queue) {
|
|
311
|
+
const q = await this.getOrCreateQueue(queue);
|
|
312
|
+
await q.resume();
|
|
313
|
+
}
|
|
314
|
+
async drainQueue(queue) {
|
|
315
|
+
const q = await this.getOrCreateQueue(queue);
|
|
316
|
+
await q.drain();
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
async cleanQueue(queue, options) {
|
|
320
|
+
const q = await this.getOrCreateQueue(queue);
|
|
321
|
+
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
322
|
+
let total = 0;
|
|
323
|
+
for (const status of statuses) {
|
|
324
|
+
const cleaned = await q.clean(
|
|
325
|
+
options.olderThan ?? 0,
|
|
326
|
+
options.limit ?? 1e3,
|
|
327
|
+
status
|
|
328
|
+
);
|
|
329
|
+
total += cleaned.length;
|
|
330
|
+
}
|
|
331
|
+
return total;
|
|
332
|
+
}
|
|
333
|
+
async obliterateQueue(queue, options) {
|
|
334
|
+
const q = await this.getOrCreateQueue(queue);
|
|
335
|
+
await q.obliterate({ force: options?.force });
|
|
336
|
+
}
|
|
337
|
+
async retryAllFailed(queue) {
|
|
338
|
+
const q = await this.getOrCreateQueue(queue);
|
|
339
|
+
const failed = await q.getFailed();
|
|
340
|
+
await Promise.all(failed.map((job) => job.retry()));
|
|
341
|
+
return failed.length;
|
|
342
|
+
}
|
|
343
|
+
async getJobCounts(queue) {
|
|
344
|
+
const q = await this.getOrCreateQueue(queue);
|
|
345
|
+
const counts = await q.getJobCounts();
|
|
346
|
+
return {
|
|
347
|
+
waiting: counts.waiting || 0,
|
|
348
|
+
active: counts.active || 0,
|
|
349
|
+
completed: counts.completed || 0,
|
|
350
|
+
failed: counts.failed || 0,
|
|
351
|
+
delayed: counts.delayed || 0,
|
|
352
|
+
paused: counts.paused || 0
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
async listJobs(queue, options) {
|
|
356
|
+
const q = await this.getOrCreateQueue(queue);
|
|
357
|
+
const statuses = options?.status || ["waiting", "active", "completed", "failed", "delayed"];
|
|
358
|
+
const jobs = [];
|
|
359
|
+
for (const status of statuses) {
|
|
360
|
+
const statusJobs = await q.getJobs([status], options?.start, options?.end);
|
|
361
|
+
jobs.push(...statusJobs);
|
|
362
|
+
}
|
|
363
|
+
return Promise.all(jobs.map(async (job) => {
|
|
364
|
+
const data = job.data;
|
|
365
|
+
const state = await job.getState();
|
|
366
|
+
return {
|
|
367
|
+
id: job.id,
|
|
368
|
+
name: job.name,
|
|
369
|
+
queue: job.queueName,
|
|
370
|
+
state: this.mapJobState(state || "waiting"),
|
|
371
|
+
data: data.input ?? data,
|
|
372
|
+
result: job.returnvalue,
|
|
373
|
+
error: job.failedReason,
|
|
374
|
+
progress: typeof job.progress === "number" ? job.progress : 0,
|
|
375
|
+
attempts: job.attemptsMade,
|
|
376
|
+
timestamp: job.timestamp,
|
|
377
|
+
processedOn: job.processedOn,
|
|
378
|
+
finishedOn: job.finishedOn,
|
|
379
|
+
scope: data.scope,
|
|
380
|
+
actor: data.actor
|
|
381
|
+
};
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
// ==========================================
|
|
385
|
+
// PAUSE/RESUME JOB TYPES
|
|
386
|
+
// ==========================================
|
|
387
|
+
async pauseJobType(queue, jobName) {
|
|
388
|
+
this.config?.logger?.warn?.(`pauseJobType is not fully supported in BullMQ adapter`);
|
|
389
|
+
}
|
|
390
|
+
async resumeJobType(queue, jobName) {
|
|
391
|
+
this.config?.logger?.warn?.(`resumeJobType is not fully supported in BullMQ adapter`);
|
|
392
|
+
}
|
|
393
|
+
// ==========================================
|
|
394
|
+
// EVENTS
|
|
395
|
+
// ==========================================
|
|
396
|
+
async subscribe(pattern, handler) {
|
|
397
|
+
const subscriptions = [];
|
|
398
|
+
for (const [queueName] of this.queues) {
|
|
399
|
+
const events = await this.getQueueEvents(queueName);
|
|
400
|
+
const completedHandler = async (args) => {
|
|
401
|
+
if (this.matchesPattern(pattern, `${queueName}:${args.jobId}:completed`)) {
|
|
402
|
+
await handler({
|
|
403
|
+
type: "completed",
|
|
404
|
+
data: args,
|
|
405
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
const failedHandler = async (args) => {
|
|
410
|
+
if (this.matchesPattern(pattern, `${queueName}:${args.jobId}:failed`)) {
|
|
411
|
+
await handler({
|
|
412
|
+
type: "failed",
|
|
413
|
+
data: args,
|
|
414
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
events.on("completed", completedHandler);
|
|
419
|
+
events.on("failed", failedHandler);
|
|
420
|
+
subscriptions.push(async () => {
|
|
421
|
+
events.off("completed", completedHandler);
|
|
422
|
+
events.off("failed", failedHandler);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return async () => {
|
|
426
|
+
await Promise.all(subscriptions.map((unsub) => unsub()));
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
matchesPattern(pattern, eventType) {
|
|
430
|
+
if (pattern === "*") return true;
|
|
431
|
+
const regex = new RegExp(
|
|
432
|
+
"^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
433
|
+
);
|
|
434
|
+
return regex.test(eventType);
|
|
435
|
+
}
|
|
436
|
+
// ==========================================
|
|
437
|
+
// WORKERS
|
|
438
|
+
// ==========================================
|
|
439
|
+
async createWorker(config, handler) {
|
|
440
|
+
const { Worker } = await this.getBullMQ();
|
|
441
|
+
const workerId = `worker-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
442
|
+
const workers = [];
|
|
443
|
+
for (const queueName of config.queues) {
|
|
444
|
+
const workerOptions = {
|
|
445
|
+
connection: this.redis,
|
|
446
|
+
concurrency: config.concurrency,
|
|
447
|
+
lockDuration: config.lockDuration,
|
|
448
|
+
limiter: config.limiter
|
|
449
|
+
};
|
|
450
|
+
const worker = new Worker(
|
|
451
|
+
queueName,
|
|
452
|
+
async (job) => {
|
|
453
|
+
const data = job.data;
|
|
454
|
+
return handler({
|
|
455
|
+
id: job.id,
|
|
456
|
+
name: job.name,
|
|
457
|
+
queue: job.queueName,
|
|
458
|
+
data: data.input ?? data,
|
|
459
|
+
attempt: job.attemptsMade + 1,
|
|
460
|
+
timestamp: job.timestamp,
|
|
461
|
+
scope: data.scope,
|
|
462
|
+
actor: data.actor,
|
|
463
|
+
log: async (level, message) => {
|
|
464
|
+
await job.log(`[${level.toUpperCase()}] ${message}`);
|
|
465
|
+
},
|
|
466
|
+
updateProgress: async (progress) => {
|
|
467
|
+
await job.updateProgress(progress);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
},
|
|
471
|
+
workerOptions
|
|
472
|
+
);
|
|
473
|
+
if (config.onIdle) {
|
|
474
|
+
worker.on("drained", config.onIdle);
|
|
475
|
+
}
|
|
476
|
+
workers.push(worker);
|
|
477
|
+
this.workers.set(`${workerId}-${queueName}`, worker);
|
|
478
|
+
}
|
|
479
|
+
let isPaused = false;
|
|
480
|
+
const startTime = Date.now();
|
|
481
|
+
let processed = 0;
|
|
482
|
+
let failed = 0;
|
|
483
|
+
let completed = 0;
|
|
484
|
+
for (const worker of workers) {
|
|
485
|
+
worker.on("completed", () => {
|
|
486
|
+
processed++;
|
|
487
|
+
completed++;
|
|
488
|
+
});
|
|
489
|
+
worker.on("failed", () => {
|
|
490
|
+
processed++;
|
|
491
|
+
failed++;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
id: workerId,
|
|
496
|
+
pause: async () => {
|
|
497
|
+
await Promise.all(workers.map((w) => w.pause()));
|
|
498
|
+
isPaused = true;
|
|
499
|
+
},
|
|
500
|
+
resume: async () => {
|
|
501
|
+
await Promise.all(workers.map((w) => w.resume()));
|
|
502
|
+
isPaused = false;
|
|
503
|
+
},
|
|
504
|
+
close: async () => {
|
|
505
|
+
await Promise.all(workers.map((w) => w.close()));
|
|
506
|
+
for (const worker of workers) {
|
|
507
|
+
const key = Array.from(this.workers.entries()).find(
|
|
508
|
+
([_, w]) => w === worker
|
|
509
|
+
)?.[0];
|
|
510
|
+
if (key) this.workers.delete(key);
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
isRunning: () => !isPaused && workers.every((w) => w.isRunning()),
|
|
514
|
+
isPaused: () => isPaused,
|
|
515
|
+
getMetrics: async () => ({
|
|
516
|
+
processed,
|
|
517
|
+
failed,
|
|
518
|
+
completed,
|
|
519
|
+
active: workers.reduce((sum, w) => sum + (w.isRunning() ? 1 : 0), 0),
|
|
520
|
+
uptime: Date.now() - startTime
|
|
521
|
+
})
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
// ==========================================
|
|
525
|
+
// SEARCH
|
|
526
|
+
// ==========================================
|
|
527
|
+
async searchJobs(filter) {
|
|
528
|
+
const results = [];
|
|
529
|
+
const queuesToSearch = filter.queue ? [filter.queue] : Array.from(this.queues.keys());
|
|
530
|
+
for (const queueName of queuesToSearch) {
|
|
531
|
+
const jobs = await this.listJobs(queueName, {
|
|
532
|
+
status: filter.status,
|
|
533
|
+
start: filter.offset,
|
|
534
|
+
end: filter.limit ? (filter.offset || 0) + filter.limit : void 0
|
|
535
|
+
});
|
|
536
|
+
for (const job of jobs) {
|
|
537
|
+
if (filter.jobName && job.name !== filter.jobName) continue;
|
|
538
|
+
if (filter.scopeId && job.scope?.id !== filter.scopeId) continue;
|
|
539
|
+
if (filter.actorId && job.actor?.id !== filter.actorId) continue;
|
|
540
|
+
if (filter.dateRange?.from && job.timestamp < filter.dateRange.from.getTime()) continue;
|
|
541
|
+
if (filter.dateRange?.to && job.timestamp > filter.dateRange.to.getTime()) continue;
|
|
542
|
+
results.push(job);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (filter.orderBy) {
|
|
546
|
+
const [field, direction] = filter.orderBy.split(":");
|
|
547
|
+
results.sort((a, b) => {
|
|
548
|
+
const aVal = field === "createdAt" ? a.timestamp : a[field];
|
|
549
|
+
const bVal = field === "createdAt" ? b.timestamp : b[field];
|
|
550
|
+
return direction === "asc" ? aVal - bVal : bVal - aVal;
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
return results.slice(0, filter.limit || 100);
|
|
554
|
+
}
|
|
555
|
+
async searchQueues(filter) {
|
|
556
|
+
const results = [];
|
|
557
|
+
for (const [queueName, queue] of this.queues) {
|
|
558
|
+
if (filter.name && !queueName.includes(filter.name)) continue;
|
|
559
|
+
const isPaused = await queue.isPaused();
|
|
560
|
+
if (filter.isPaused !== void 0 && isPaused !== filter.isPaused) continue;
|
|
561
|
+
const counts = await queue.getJobCounts();
|
|
562
|
+
results.push({
|
|
563
|
+
name: queueName,
|
|
564
|
+
isPaused,
|
|
565
|
+
jobCounts: {
|
|
566
|
+
waiting: counts.waiting || 0,
|
|
567
|
+
active: counts.active || 0,
|
|
568
|
+
completed: counts.completed || 0,
|
|
569
|
+
failed: counts.failed || 0,
|
|
570
|
+
delayed: counts.delayed || 0,
|
|
571
|
+
paused: counts.paused || 0
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
return results;
|
|
576
|
+
}
|
|
577
|
+
// ==========================================
|
|
578
|
+
// LIFECYCLE
|
|
579
|
+
// ==========================================
|
|
580
|
+
async shutdown() {
|
|
581
|
+
for (const [_, worker] of this.workers) {
|
|
582
|
+
await worker.close();
|
|
583
|
+
}
|
|
584
|
+
this.workers.clear();
|
|
585
|
+
for (const [_, events] of this.queueEvents) {
|
|
586
|
+
await events.close();
|
|
587
|
+
}
|
|
588
|
+
this.queueEvents.clear();
|
|
589
|
+
for (const [_, queue] of this.queues) {
|
|
590
|
+
await queue.close();
|
|
591
|
+
}
|
|
592
|
+
this.queues.clear();
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
exports.BullMQAdapter = BullMQAdapter;
|
|
597
|
+
//# sourceMappingURL=bullmq.adapter.js.map
|
|
598
|
+
//# sourceMappingURL=bullmq.adapter.js.map
|