bunqueue 1.9.9 → 2.0.1
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/dist/application/backgroundTasks.d.ts +10 -0
- package/dist/application/backgroundTasks.d.ts.map +1 -1
- package/dist/application/backgroundTasks.js +67 -6
- package/dist/application/backgroundTasks.js.map +1 -1
- package/dist/application/cleanupTasks.js +1 -1
- package/dist/application/cleanupTasks.js.map +1 -1
- package/dist/application/clientTracking.d.ts.map +1 -1
- package/dist/application/clientTracking.js +23 -10
- package/dist/application/clientTracking.js.map +1 -1
- package/dist/application/dependencyProcessor.d.ts.map +1 -1
- package/dist/application/dependencyProcessor.js +14 -13
- package/dist/application/dependencyProcessor.js.map +1 -1
- package/dist/application/eventsManager.d.ts.map +1 -1
- package/dist/application/eventsManager.js +16 -4
- package/dist/application/eventsManager.js.map +1 -1
- package/dist/application/jobLogsManager.d.ts +2 -2
- package/dist/application/jobLogsManager.d.ts.map +1 -1
- package/dist/application/jobLogsManager.js +13 -3
- package/dist/application/jobLogsManager.js.map +1 -1
- package/dist/application/lockManager.js +1 -1
- package/dist/application/lockManager.js.map +1 -1
- package/dist/application/operations/ack.d.ts +2 -1
- package/dist/application/operations/ack.d.ts.map +1 -1
- package/dist/application/operations/ack.js +12 -0
- package/dist/application/operations/ack.js.map +1 -1
- package/dist/application/operations/jobManagement.d.ts +1 -1
- package/dist/application/operations/jobManagement.d.ts.map +1 -1
- package/dist/application/operations/jobManagement.js +23 -3
- package/dist/application/operations/jobManagement.js.map +1 -1
- package/dist/application/operations/push.d.ts +1 -1
- package/dist/application/operations/push.d.ts.map +1 -1
- package/dist/application/operations/push.js +13 -5
- package/dist/application/operations/push.js.map +1 -1
- package/dist/application/operations/queryOperations.d.ts +3 -0
- package/dist/application/operations/queryOperations.d.ts.map +1 -1
- package/dist/application/operations/queryOperations.js +29 -0
- package/dist/application/operations/queryOperations.js.map +1 -1
- package/dist/application/queueManager.d.ts +15 -1
- package/dist/application/queueManager.d.ts.map +1 -1
- package/dist/application/queueManager.js +69 -2
- package/dist/application/queueManager.js.map +1 -1
- package/dist/application/stallDetection.js +25 -20
- package/dist/application/stallDetection.js.map +1 -1
- package/dist/application/webhookManager.d.ts.map +1 -1
- package/dist/application/webhookManager.js +18 -2
- package/dist/application/webhookManager.js.map +1 -1
- package/dist/application/workerManager.d.ts.map +1 -1
- package/dist/application/workerManager.js +4 -2
- package/dist/application/workerManager.js.map +1 -1
- package/dist/client/events.d.ts +29 -0
- package/dist/client/events.d.ts.map +1 -1
- package/dist/client/events.js +92 -21
- package/dist/client/events.js.map +1 -1
- package/dist/client/flow.d.ts +122 -3
- package/dist/client/flow.d.ts.map +1 -1
- package/dist/client/flow.js +374 -2
- package/dist/client/flow.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/queue/queue.d.ts +260 -1
- package/dist/client/queue/queue.d.ts.map +1 -1
- package/dist/client/queue/queue.js +1343 -16
- package/dist/client/queue/queue.js.map +1 -1
- package/dist/client/tcpPool.d.ts.map +1 -1
- package/dist/client/tcpPool.js +19 -8
- package/dist/client/tcpPool.js.map +1 -1
- package/dist/client/types.d.ts +430 -13
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +346 -5
- package/dist/client/types.js.map +1 -1
- package/dist/client/worker/ackBatcher.d.ts +1 -0
- package/dist/client/worker/ackBatcher.d.ts.map +1 -1
- package/dist/client/worker/ackBatcher.js +9 -0
- package/dist/client/worker/ackBatcher.js.map +1 -1
- package/dist/client/worker/processor.js +6 -1
- package/dist/client/worker/processor.js.map +1 -1
- package/dist/client/worker/worker.d.ts +117 -0
- package/dist/client/worker/worker.d.ts.map +1 -1
- package/dist/client/worker/worker.js +375 -3
- package/dist/client/worker/worker.js.map +1 -1
- package/dist/domain/queue/priorityQueue.d.ts.map +1 -1
- package/dist/domain/queue/priorityQueue.js +24 -18
- package/dist/domain/queue/priorityQueue.js.map +1 -1
- package/dist/domain/queue/shard.d.ts +4 -0
- package/dist/domain/queue/shard.d.ts.map +1 -1
- package/dist/domain/queue/shard.js +21 -7
- package/dist/domain/queue/shard.js.map +1 -1
- package/dist/domain/types/job.d.ts +89 -2
- package/dist/domain/types/job.d.ts.map +1 -1
- package/dist/domain/types/job.js +94 -26
- package/dist/domain/types/job.js.map +1 -1
- package/dist/domain/types/queue.d.ts +11 -1
- package/dist/domain/types/queue.d.ts.map +1 -1
- package/dist/infrastructure/persistence/sqliteBatch.d.ts +9 -4
- package/dist/infrastructure/persistence/sqliteBatch.d.ts.map +1 -1
- package/dist/infrastructure/persistence/sqliteBatch.js +34 -17
- package/dist/infrastructure/persistence/sqliteBatch.js.map +1 -1
- package/dist/infrastructure/persistence/sqliteSerializer.d.ts.map +1 -1
- package/dist/infrastructure/persistence/sqliteSerializer.js +14 -0
- package/dist/infrastructure/persistence/sqliteSerializer.js.map +1 -1
- package/dist/infrastructure/scheduler/cronScheduler.d.ts.map +1 -1
- package/dist/infrastructure/scheduler/cronScheduler.js +29 -15
- package/dist/infrastructure/scheduler/cronScheduler.js.map +1 -1
- package/dist/infrastructure/server/handlers/query.d.ts.map +1 -1
- package/dist/infrastructure/server/handlers/query.js +1 -16
- package/dist/infrastructure/server/handlers/query.js.map +1 -1
- package/dist/infrastructure/server/rateLimiter.d.ts.map +1 -1
- package/dist/infrastructure/server/rateLimiter.js +5 -3
- package/dist/infrastructure/server/rateLimiter.js.map +1 -1
- package/dist/infrastructure/server/tcp.d.ts.map +1 -1
- package/dist/infrastructure/server/tcp.js +36 -4
- package/dist/infrastructure/server/tcp.js.map +1 -1
- package/dist/main.js +5 -2
- package/dist/main.js.map +1 -1
- package/dist/shared/lock.d.ts +1 -1
- package/dist/shared/lock.d.ts.map +1 -1
- package/dist/shared/lock.js +6 -4
- package/dist/shared/lock.js.map +1 -1
- package/dist/shared/lru.d.ts +28 -0
- package/dist/shared/lru.d.ts.map +1 -1
- package/dist/shared/lru.js +28 -0
- package/dist/shared/lru.js.map +1 -1
- package/package.json +1 -1
|
@@ -51,28 +51,93 @@ export class Queue {
|
|
|
51
51
|
/** Add a job to the queue */
|
|
52
52
|
async add(name, data, opts = {}) {
|
|
53
53
|
const merged = { ...this.opts.defaultJobOptions, ...opts };
|
|
54
|
+
// Add parent info to data if specified (BullMQ v5 flow support)
|
|
55
|
+
const jobData = { name, ...data };
|
|
56
|
+
if (merged.parent) {
|
|
57
|
+
jobData.__parentId = merged.parent.id;
|
|
58
|
+
jobData.__parentQueue = merged.parent.queue;
|
|
59
|
+
}
|
|
54
60
|
if (this.embedded) {
|
|
55
61
|
const manager = getSharedManager();
|
|
62
|
+
// Parse removeOnComplete/removeOnFail (can be boolean, number, or KeepJobs)
|
|
63
|
+
const removeOnComplete = typeof merged.removeOnComplete === 'boolean' ? merged.removeOnComplete : false;
|
|
64
|
+
const removeOnFail = typeof merged.removeOnFail === 'boolean' ? merged.removeOnFail : false;
|
|
65
|
+
// Parse repeat options with extended BullMQ v5 fields
|
|
66
|
+
const repeat = merged.repeat
|
|
67
|
+
? {
|
|
68
|
+
every: merged.repeat.every,
|
|
69
|
+
limit: merged.repeat.limit,
|
|
70
|
+
pattern: merged.repeat.pattern,
|
|
71
|
+
count: merged.repeat.count,
|
|
72
|
+
startDate: merged.repeat.startDate instanceof Date
|
|
73
|
+
? merged.repeat.startDate.getTime()
|
|
74
|
+
: typeof merged.repeat.startDate === 'string'
|
|
75
|
+
? new Date(merged.repeat.startDate).getTime()
|
|
76
|
+
: merged.repeat.startDate,
|
|
77
|
+
endDate: merged.repeat.endDate instanceof Date
|
|
78
|
+
? merged.repeat.endDate.getTime()
|
|
79
|
+
: typeof merged.repeat.endDate === 'string'
|
|
80
|
+
? new Date(merged.repeat.endDate).getTime()
|
|
81
|
+
: merged.repeat.endDate,
|
|
82
|
+
tz: merged.repeat.tz,
|
|
83
|
+
immediately: merged.repeat.immediately,
|
|
84
|
+
prevMillis: merged.repeat.prevMillis,
|
|
85
|
+
offset: merged.repeat.offset,
|
|
86
|
+
jobId: merged.repeat.jobId,
|
|
87
|
+
}
|
|
88
|
+
: undefined;
|
|
56
89
|
const job = await manager.push(this.name, {
|
|
57
|
-
data:
|
|
90
|
+
data: jobData,
|
|
58
91
|
priority: merged.priority,
|
|
59
92
|
delay: merged.delay,
|
|
60
93
|
maxAttempts: merged.attempts,
|
|
61
94
|
backoff: merged.backoff,
|
|
62
95
|
timeout: merged.timeout,
|
|
63
|
-
customId: merged.jobId,
|
|
64
|
-
removeOnComplete
|
|
65
|
-
removeOnFail
|
|
66
|
-
repeat
|
|
96
|
+
customId: merged.jobId ?? merged.deduplication?.id,
|
|
97
|
+
removeOnComplete,
|
|
98
|
+
removeOnFail,
|
|
99
|
+
repeat,
|
|
67
100
|
stallTimeout: merged.stallTimeout,
|
|
68
101
|
durable: merged.durable,
|
|
102
|
+
parentId: merged.parent ? jobId(merged.parent.id) : undefined,
|
|
103
|
+
// BullMQ v5 options
|
|
104
|
+
lifo: merged.lifo,
|
|
105
|
+
stackTraceLimit: merged.stackTraceLimit,
|
|
106
|
+
keepLogs: merged.keepLogs,
|
|
107
|
+
sizeLimit: merged.sizeLimit,
|
|
108
|
+
failParentOnFailure: merged.failParentOnFailure,
|
|
109
|
+
removeDependencyOnFailure: merged.removeDependencyOnFailure,
|
|
110
|
+
dedup: merged.deduplication ? { ttl: merged.deduplication.ttl } : undefined,
|
|
111
|
+
debounceId: merged.debounce?.id,
|
|
112
|
+
debounceTtl: merged.debounce?.ttl,
|
|
113
|
+
});
|
|
114
|
+
return toPublicJob({
|
|
115
|
+
job,
|
|
116
|
+
name,
|
|
117
|
+
getState: (jid) => this.getJobState(jid),
|
|
118
|
+
remove: (jid) => this.removeAsync(jid),
|
|
119
|
+
retry: (jid) => this.retryJob(jid),
|
|
120
|
+
getChildrenValues: (jid) => this.getChildrenValues(jid),
|
|
121
|
+
updateData: (jid, data) => this.updateJobData(jid, data),
|
|
122
|
+
promote: (jid) => this.promoteJob(jid),
|
|
123
|
+
changeDelay: (jid, delay) => this.changeJobDelay(jid, delay),
|
|
124
|
+
changePriority: (jid, opts) => this.changeJobPriority(jid, opts),
|
|
125
|
+
extendLock: (jid, token, duration) => this.extendJobLock(jid, token, duration),
|
|
126
|
+
clearLogs: (jid, keepLogs) => this.clearJobLogs(jid, keepLogs),
|
|
127
|
+
getDependencies: (jid, opts) => this.getJobDependencies(jid, opts),
|
|
128
|
+
getDependenciesCount: (jid, opts) => this.getJobDependenciesCount(jid, opts),
|
|
129
|
+
moveToCompleted: (jid, result, token) => this.moveJobToCompleted(jid, result, token),
|
|
130
|
+
moveToFailed: (jid, error, token) => this.moveJobToFailed(jid, error, token),
|
|
131
|
+
moveToWait: (jid, token) => this.moveJobToWait(jid, token),
|
|
132
|
+
moveToDelayed: (jid, timestamp, token) => this.moveJobToDelayed(jid, timestamp, token),
|
|
133
|
+
moveToWaitingChildren: (jid, token, opts) => this.moveJobToWaitingChildren(jid, token, opts),
|
|
134
|
+
waitUntilFinished: (jid, queueEvents, ttl) => this.waitJobUntilFinished(jid, queueEvents, ttl),
|
|
69
135
|
});
|
|
70
|
-
return toPublicJob(job, name);
|
|
71
136
|
}
|
|
72
137
|
const response = await this.tcp.send({
|
|
73
138
|
cmd: 'PUSH',
|
|
74
139
|
queue: this.name,
|
|
75
|
-
data:
|
|
140
|
+
data: jobData,
|
|
76
141
|
priority: merged.priority,
|
|
77
142
|
delay: merged.delay,
|
|
78
143
|
maxAttempts: merged.attempts,
|
|
@@ -84,6 +149,7 @@ export class Queue {
|
|
|
84
149
|
stallTimeout: merged.stallTimeout,
|
|
85
150
|
durable: merged.durable,
|
|
86
151
|
repeat: merged.repeat,
|
|
152
|
+
parentId: merged.parent?.id,
|
|
87
153
|
});
|
|
88
154
|
if (!response.ok) {
|
|
89
155
|
throw new Error(response.error ?? 'Failed to add job');
|
|
@@ -100,6 +166,9 @@ export class Queue {
|
|
|
100
166
|
const manager = getSharedManager();
|
|
101
167
|
const inputs = jobs.map(({ name, data, opts }) => {
|
|
102
168
|
const m = { ...this.opts.defaultJobOptions, ...opts };
|
|
169
|
+
// Parse removeOnComplete/removeOnFail (can be boolean, number, or KeepJobs)
|
|
170
|
+
const removeOnComplete = typeof m.removeOnComplete === 'boolean' ? m.removeOnComplete : false;
|
|
171
|
+
const removeOnFail = typeof m.removeOnFail === 'boolean' ? m.removeOnFail : false;
|
|
103
172
|
return {
|
|
104
173
|
data: { name, ...data },
|
|
105
174
|
priority: m.priority,
|
|
@@ -108,11 +177,43 @@ export class Queue {
|
|
|
108
177
|
backoff: m.backoff,
|
|
109
178
|
timeout: m.timeout,
|
|
110
179
|
customId: m.jobId,
|
|
111
|
-
removeOnComplete
|
|
112
|
-
removeOnFail
|
|
113
|
-
repeat: m.repeat
|
|
180
|
+
removeOnComplete,
|
|
181
|
+
removeOnFail,
|
|
182
|
+
repeat: m.repeat
|
|
183
|
+
? {
|
|
184
|
+
every: m.repeat.every,
|
|
185
|
+
limit: m.repeat.limit,
|
|
186
|
+
pattern: m.repeat.pattern,
|
|
187
|
+
count: m.repeat.count,
|
|
188
|
+
startDate: m.repeat.startDate instanceof Date
|
|
189
|
+
? m.repeat.startDate.getTime()
|
|
190
|
+
: typeof m.repeat.startDate === 'string'
|
|
191
|
+
? new Date(m.repeat.startDate).getTime()
|
|
192
|
+
: m.repeat.startDate,
|
|
193
|
+
endDate: m.repeat.endDate instanceof Date
|
|
194
|
+
? m.repeat.endDate.getTime()
|
|
195
|
+
: typeof m.repeat.endDate === 'string'
|
|
196
|
+
? new Date(m.repeat.endDate).getTime()
|
|
197
|
+
: m.repeat.endDate,
|
|
198
|
+
tz: m.repeat.tz,
|
|
199
|
+
immediately: m.repeat.immediately,
|
|
200
|
+
prevMillis: m.repeat.prevMillis,
|
|
201
|
+
offset: m.repeat.offset,
|
|
202
|
+
jobId: m.repeat.jobId,
|
|
203
|
+
}
|
|
204
|
+
: undefined,
|
|
114
205
|
stallTimeout: m.stallTimeout,
|
|
115
206
|
durable: m.durable,
|
|
207
|
+
// BullMQ v5 options
|
|
208
|
+
lifo: m.lifo,
|
|
209
|
+
stackTraceLimit: m.stackTraceLimit,
|
|
210
|
+
keepLogs: m.keepLogs,
|
|
211
|
+
sizeLimit: m.sizeLimit,
|
|
212
|
+
failParentOnFailure: m.failParentOnFailure,
|
|
213
|
+
removeDependencyOnFailure: m.removeDependencyOnFailure,
|
|
214
|
+
dedup: m.deduplication ? { ttl: m.deduplication.ttl } : undefined,
|
|
215
|
+
debounceId: m.debounce?.id,
|
|
216
|
+
debounceTtl: m.debounce?.ttl,
|
|
116
217
|
};
|
|
117
218
|
});
|
|
118
219
|
const ids = await manager.pushBatch(this.name, inputs);
|
|
@@ -131,6 +232,8 @@ export class Queue {
|
|
|
131
232
|
removeOnComplete: m.removeOnComplete,
|
|
132
233
|
removeOnFail: m.removeOnFail,
|
|
133
234
|
stallTimeout: m.stallTimeout,
|
|
235
|
+
repeat: m.repeat,
|
|
236
|
+
durable: m.durable,
|
|
134
237
|
};
|
|
135
238
|
});
|
|
136
239
|
const response = await this.tcp.send({ cmd: 'PUSHB', queue: this.name, jobs: jobInputs });
|
|
@@ -148,23 +251,121 @@ export class Queue {
|
|
|
148
251
|
if (!job)
|
|
149
252
|
return null;
|
|
150
253
|
const jobData = job.data;
|
|
151
|
-
return toPublicJob(
|
|
254
|
+
return toPublicJob({
|
|
255
|
+
job,
|
|
256
|
+
name: jobData?.name ?? 'default',
|
|
257
|
+
getState: (jid) => this.getJobState(jid),
|
|
258
|
+
remove: (jid) => this.removeAsync(jid),
|
|
259
|
+
retry: (jid) => this.retryJob(jid),
|
|
260
|
+
getChildrenValues: (jid) => this.getChildrenValues(jid),
|
|
261
|
+
updateData: (jid, data) => this.updateJobData(jid, data),
|
|
262
|
+
promote: (jid) => this.promoteJob(jid),
|
|
263
|
+
changeDelay: (jid, delay) => this.changeJobDelay(jid, delay),
|
|
264
|
+
changePriority: (jid, opts) => this.changeJobPriority(jid, opts),
|
|
265
|
+
extendLock: (jid, token, duration) => this.extendJobLock(jid, token, duration),
|
|
266
|
+
clearLogs: (jid, keepLogs) => this.clearJobLogs(jid, keepLogs),
|
|
267
|
+
getDependencies: (jid, opts) => this.getJobDependencies(jid, opts),
|
|
268
|
+
getDependenciesCount: (jid, opts) => this.getJobDependenciesCount(jid, opts),
|
|
269
|
+
moveToCompleted: (jid, result, token) => this.moveJobToCompleted(jid, result, token),
|
|
270
|
+
moveToFailed: (jid, error, token) => this.moveJobToFailed(jid, error, token),
|
|
271
|
+
moveToWait: (jid, token) => this.moveJobToWait(jid, token),
|
|
272
|
+
moveToDelayed: (jid, timestamp, token) => this.moveJobToDelayed(jid, timestamp, token),
|
|
273
|
+
moveToWaitingChildren: (jid, token, opts) => this.moveJobToWaitingChildren(jid, token, opts),
|
|
274
|
+
waitUntilFinished: (jid, queueEvents, ttl) => this.waitJobUntilFinished(jid, queueEvents, ttl),
|
|
275
|
+
});
|
|
152
276
|
}
|
|
153
277
|
const response = await this.tcp.send({ cmd: 'GetJob', id });
|
|
154
278
|
if (!response.ok || !response.job)
|
|
155
279
|
return null;
|
|
156
280
|
const jobData = response.job;
|
|
157
281
|
const data = jobData.data;
|
|
282
|
+
const jobIdStr = String(jobData.id);
|
|
283
|
+
const ts = jobData.createdAt ?? Date.now();
|
|
284
|
+
const runAt = jobData.runAt ?? ts;
|
|
158
285
|
return {
|
|
159
|
-
id:
|
|
286
|
+
id: jobIdStr,
|
|
160
287
|
name: data?.name ?? 'default',
|
|
161
288
|
data: jobData.data,
|
|
162
289
|
queueName: this.name,
|
|
163
290
|
attemptsMade: jobData.attempts ?? 0,
|
|
164
|
-
timestamp:
|
|
291
|
+
timestamp: ts,
|
|
165
292
|
progress: jobData.progress ?? 0,
|
|
293
|
+
// BullMQ v5 properties
|
|
294
|
+
delay: runAt > ts ? runAt - ts : 0,
|
|
295
|
+
processedOn: jobData.startedAt ?? undefined,
|
|
296
|
+
finishedOn: jobData.completedAt ?? undefined,
|
|
297
|
+
stacktrace: null,
|
|
298
|
+
stalledCounter: jobData.stallCount ?? 0,
|
|
299
|
+
priority: jobData.priority ?? 0,
|
|
300
|
+
parentKey: undefined,
|
|
301
|
+
opts: {},
|
|
302
|
+
token: undefined,
|
|
303
|
+
processedBy: undefined,
|
|
304
|
+
deduplicationId: jobData.customId ?? undefined,
|
|
305
|
+
repeatJobKey: undefined,
|
|
306
|
+
attemptsStarted: jobData.attempts ?? 0,
|
|
307
|
+
// Methods
|
|
166
308
|
updateProgress: async () => { },
|
|
167
309
|
log: async () => { },
|
|
310
|
+
getState: () => this.getJobState(jobIdStr),
|
|
311
|
+
remove: () => this.removeAsync(jobIdStr),
|
|
312
|
+
retry: () => this.retryJob(jobIdStr),
|
|
313
|
+
getChildrenValues: () => this.getChildrenValues(jobIdStr),
|
|
314
|
+
// BullMQ v5 state check methods
|
|
315
|
+
isWaiting: async () => (await this.getJobState(jobIdStr)) === 'waiting',
|
|
316
|
+
isActive: async () => (await this.getJobState(jobIdStr)) === 'active',
|
|
317
|
+
isDelayed: async () => (await this.getJobState(jobIdStr)) === 'delayed',
|
|
318
|
+
isCompleted: async () => (await this.getJobState(jobIdStr)) === 'completed',
|
|
319
|
+
isFailed: async () => (await this.getJobState(jobIdStr)) === 'failed',
|
|
320
|
+
isWaitingChildren: () => Promise.resolve(false),
|
|
321
|
+
// BullMQ v5 mutation methods
|
|
322
|
+
updateData: async () => { },
|
|
323
|
+
promote: async () => { },
|
|
324
|
+
changeDelay: async () => { },
|
|
325
|
+
changePriority: async () => { },
|
|
326
|
+
extendLock: () => Promise.resolve(0),
|
|
327
|
+
clearLogs: async () => { },
|
|
328
|
+
// BullMQ v5 dependency methods
|
|
329
|
+
getDependencies: () => Promise.resolve({ processed: {}, unprocessed: [] }),
|
|
330
|
+
getDependenciesCount: () => Promise.resolve({ processed: 0, unprocessed: 0 }),
|
|
331
|
+
// BullMQ v5 serialization methods
|
|
332
|
+
toJSON: () => ({
|
|
333
|
+
id: jobIdStr,
|
|
334
|
+
name: data?.name ?? 'default',
|
|
335
|
+
data: jobData.data,
|
|
336
|
+
opts: {},
|
|
337
|
+
progress: jobData.progress ?? 0,
|
|
338
|
+
delay: runAt > ts ? runAt - ts : 0,
|
|
339
|
+
timestamp: ts,
|
|
340
|
+
attemptsMade: jobData.attempts ?? 0,
|
|
341
|
+
stacktrace: null,
|
|
342
|
+
queueQualifiedName: `bull:${this.name}`,
|
|
343
|
+
}),
|
|
344
|
+
asJSON: () => ({
|
|
345
|
+
id: jobIdStr,
|
|
346
|
+
name: data?.name ?? 'default',
|
|
347
|
+
data: JSON.stringify(jobData.data),
|
|
348
|
+
opts: '{}',
|
|
349
|
+
progress: String(jobData.progress ?? 0),
|
|
350
|
+
delay: String(runAt > ts ? runAt - ts : 0),
|
|
351
|
+
timestamp: String(ts),
|
|
352
|
+
attemptsMade: String(jobData.attempts ?? 0),
|
|
353
|
+
stacktrace: null,
|
|
354
|
+
}),
|
|
355
|
+
// BullMQ v5 move methods
|
|
356
|
+
moveToCompleted: () => Promise.resolve(null),
|
|
357
|
+
moveToFailed: () => Promise.resolve(),
|
|
358
|
+
moveToWait: () => Promise.resolve(false),
|
|
359
|
+
moveToDelayed: () => Promise.resolve(),
|
|
360
|
+
moveToWaitingChildren: () => Promise.resolve(false),
|
|
361
|
+
waitUntilFinished: () => Promise.resolve(undefined),
|
|
362
|
+
// BullMQ v5 additional methods
|
|
363
|
+
discard: () => { },
|
|
364
|
+
getFailedChildrenValues: () => Promise.resolve({}),
|
|
365
|
+
getIgnoredChildrenFailures: () => Promise.resolve({}),
|
|
366
|
+
removeChildDependency: () => Promise.resolve(false),
|
|
367
|
+
removeDeduplicationKey: () => Promise.resolve(false),
|
|
368
|
+
removeUnprocessedChildren: () => Promise.resolve(),
|
|
168
369
|
};
|
|
169
370
|
}
|
|
170
371
|
/** Remove a job by ID */
|
|
@@ -174,6 +375,55 @@ export class Queue {
|
|
|
174
375
|
else
|
|
175
376
|
void this.tcp.send({ cmd: 'Cancel', id });
|
|
176
377
|
}
|
|
378
|
+
/** Remove a job by ID (async) */
|
|
379
|
+
async removeAsync(id) {
|
|
380
|
+
if (this.embedded) {
|
|
381
|
+
await getSharedManager().cancel(jobId(id));
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
await this.tcp.send({ cmd: 'Cancel', id });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/** Retry a job by ID */
|
|
388
|
+
async retryJob(id) {
|
|
389
|
+
if (this.embedded) {
|
|
390
|
+
// For embedded mode, we need to implement retry in QueueManager
|
|
391
|
+
// For now, just get the job and re-add it
|
|
392
|
+
const manager = getSharedManager();
|
|
393
|
+
const job = await manager.getJob(jobId(id));
|
|
394
|
+
if (job) {
|
|
395
|
+
await manager.push(job.queue, { data: job.data, priority: job.priority });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
await this.tcp.send({ cmd: 'RetryJob', id });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Get the return values of all children jobs (BullMQ v5 compatible).
|
|
404
|
+
* Returns a Record where keys are job keys (queueName:jobId) and values are return values.
|
|
405
|
+
*/
|
|
406
|
+
getChildrenValues(id) {
|
|
407
|
+
if (this.embedded) {
|
|
408
|
+
const manager = getSharedManager();
|
|
409
|
+
return manager.getChildrenValues(jobId(id));
|
|
410
|
+
}
|
|
411
|
+
// TCP mode - would need server-side support
|
|
412
|
+
// For now, return empty object
|
|
413
|
+
return Promise.resolve({});
|
|
414
|
+
}
|
|
415
|
+
/** Get job state by ID (async, works with both embedded and TCP) */
|
|
416
|
+
async getJobState(id) {
|
|
417
|
+
if (this.embedded) {
|
|
418
|
+
const manager = getSharedManager();
|
|
419
|
+
const state = await manager.getJobState(jobId(id));
|
|
420
|
+
return state;
|
|
421
|
+
}
|
|
422
|
+
const response = await this.tcp.send({ cmd: 'GetState', id });
|
|
423
|
+
if (!response.ok)
|
|
424
|
+
return 'unknown';
|
|
425
|
+
return response.state;
|
|
426
|
+
}
|
|
177
427
|
/** Get job counts by state */
|
|
178
428
|
getJobCounts() {
|
|
179
429
|
if (this.embedded) {
|
|
@@ -238,13 +488,309 @@ export class Queue {
|
|
|
238
488
|
else
|
|
239
489
|
void this.tcp.send({ cmd: 'Obliterate', queue: this.name });
|
|
240
490
|
}
|
|
491
|
+
/** Check if queue is paused */
|
|
492
|
+
isPaused() {
|
|
493
|
+
if (this.embedded) {
|
|
494
|
+
return getSharedManager().isPaused(this.name);
|
|
495
|
+
}
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
/** Check if queue is paused (async, works with TCP) */
|
|
499
|
+
async isPausedAsync() {
|
|
500
|
+
if (this.embedded)
|
|
501
|
+
return this.isPaused();
|
|
502
|
+
const response = await this.tcp.send({ cmd: 'IsPaused', queue: this.name });
|
|
503
|
+
if (!response.ok)
|
|
504
|
+
return false;
|
|
505
|
+
return response.paused === true;
|
|
506
|
+
}
|
|
507
|
+
/** Count jobs awaiting processing */
|
|
508
|
+
count() {
|
|
509
|
+
if (this.embedded) {
|
|
510
|
+
return getSharedManager().count(this.name);
|
|
511
|
+
}
|
|
512
|
+
return 0;
|
|
513
|
+
}
|
|
514
|
+
/** Count jobs awaiting processing (async, works with TCP) */
|
|
515
|
+
async countAsync() {
|
|
516
|
+
if (this.embedded)
|
|
517
|
+
return this.count();
|
|
518
|
+
const response = await this.tcp.send({ cmd: 'Count', queue: this.name });
|
|
519
|
+
if (!response.ok)
|
|
520
|
+
return 0;
|
|
521
|
+
return response.count ?? 0;
|
|
522
|
+
}
|
|
523
|
+
/** Get active jobs */
|
|
524
|
+
getActive(start = 0, end = 100) {
|
|
525
|
+
return this.getJobs({ state: 'active', start, end });
|
|
526
|
+
}
|
|
527
|
+
/** Get active jobs (async) */
|
|
528
|
+
async getActiveAsync(start = 0, end = 100) {
|
|
529
|
+
return this.getJobsAsync({ state: 'active', start, end });
|
|
530
|
+
}
|
|
531
|
+
/** Get completed jobs */
|
|
532
|
+
getCompleted(start = 0, end = 100) {
|
|
533
|
+
return this.getJobs({ state: 'completed', start, end });
|
|
534
|
+
}
|
|
535
|
+
/** Get completed jobs (async) */
|
|
536
|
+
async getCompletedAsync(start = 0, end = 100) {
|
|
537
|
+
return this.getJobsAsync({ state: 'completed', start, end });
|
|
538
|
+
}
|
|
539
|
+
/** Get failed jobs */
|
|
540
|
+
getFailed(start = 0, end = 100) {
|
|
541
|
+
return this.getJobs({ state: 'failed', start, end });
|
|
542
|
+
}
|
|
543
|
+
/** Get failed jobs (async) */
|
|
544
|
+
async getFailedAsync(start = 0, end = 100) {
|
|
545
|
+
return this.getJobsAsync({ state: 'failed', start, end });
|
|
546
|
+
}
|
|
547
|
+
/** Get delayed jobs */
|
|
548
|
+
getDelayed(start = 0, end = 100) {
|
|
549
|
+
return this.getJobs({ state: 'delayed', start, end });
|
|
550
|
+
}
|
|
551
|
+
/** Get delayed jobs (async) */
|
|
552
|
+
async getDelayedAsync(start = 0, end = 100) {
|
|
553
|
+
return this.getJobsAsync({ state: 'delayed', start, end });
|
|
554
|
+
}
|
|
555
|
+
/** Get waiting jobs */
|
|
556
|
+
getWaiting(start = 0, end = 100) {
|
|
557
|
+
return this.getJobs({ state: 'waiting', start, end });
|
|
558
|
+
}
|
|
559
|
+
/** Get waiting jobs (async) */
|
|
560
|
+
async getWaitingAsync(start = 0, end = 100) {
|
|
561
|
+
return this.getJobsAsync({ state: 'waiting', start, end });
|
|
562
|
+
}
|
|
563
|
+
/** Get active job count */
|
|
564
|
+
async getActiveCount() {
|
|
565
|
+
const counts = await this.getJobCountsAsync();
|
|
566
|
+
return counts.active;
|
|
567
|
+
}
|
|
568
|
+
/** Get completed job count */
|
|
569
|
+
async getCompletedCount() {
|
|
570
|
+
const counts = await this.getJobCountsAsync();
|
|
571
|
+
return counts.completed;
|
|
572
|
+
}
|
|
573
|
+
/** Get failed job count */
|
|
574
|
+
async getFailedCount() {
|
|
575
|
+
const counts = await this.getJobCountsAsync();
|
|
576
|
+
return counts.failed;
|
|
577
|
+
}
|
|
578
|
+
/** Get delayed job count */
|
|
579
|
+
async getDelayedCount() {
|
|
580
|
+
if (this.embedded) {
|
|
581
|
+
const stats = getSharedManager().getStats();
|
|
582
|
+
return stats.delayed;
|
|
583
|
+
}
|
|
584
|
+
const response = await this.tcp.send({ cmd: 'Stats' });
|
|
585
|
+
if (!response.ok)
|
|
586
|
+
return 0;
|
|
587
|
+
return response.stats?.delayed ?? 0;
|
|
588
|
+
}
|
|
589
|
+
/** Get waiting job count */
|
|
590
|
+
async getWaitingCount() {
|
|
591
|
+
const counts = await this.getJobCountsAsync();
|
|
592
|
+
return counts.waiting;
|
|
593
|
+
}
|
|
594
|
+
/** Clean old jobs from the queue */
|
|
595
|
+
clean(grace, limit, type) {
|
|
596
|
+
if (this.embedded) {
|
|
597
|
+
const count = getSharedManager().clean(this.name, grace, type, limit);
|
|
598
|
+
// Return empty array for now - would need to track removed IDs
|
|
599
|
+
return new Array(count).fill('');
|
|
600
|
+
}
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
/** Clean old jobs from the queue (async, works with TCP) */
|
|
604
|
+
async cleanAsync(grace, limit, type) {
|
|
605
|
+
if (this.embedded)
|
|
606
|
+
return this.clean(grace, limit, type);
|
|
607
|
+
const response = await this.tcp.send({
|
|
608
|
+
cmd: 'Clean',
|
|
609
|
+
queue: this.name,
|
|
610
|
+
grace,
|
|
611
|
+
limit,
|
|
612
|
+
type,
|
|
613
|
+
});
|
|
614
|
+
if (!response.ok)
|
|
615
|
+
return [];
|
|
616
|
+
return response.removed ?? [];
|
|
617
|
+
}
|
|
618
|
+
/** Retry failed jobs */
|
|
619
|
+
async retryJobs(opts) {
|
|
620
|
+
const state = opts?.state ?? 'failed';
|
|
621
|
+
const count = opts?.count ?? 100;
|
|
622
|
+
if (this.embedded) {
|
|
623
|
+
if (state === 'failed') {
|
|
624
|
+
return getSharedManager().retryDlq(this.name);
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
return getSharedManager().retryCompleted(this.name);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const cmd = state === 'failed' ? 'RetryDlq' : 'RetryCompleted';
|
|
631
|
+
const response = await this.tcp.send({ cmd, queue: this.name, count });
|
|
632
|
+
if (!response.ok)
|
|
633
|
+
return 0;
|
|
634
|
+
return response.count ?? 0;
|
|
635
|
+
}
|
|
636
|
+
/** Promote delayed jobs to waiting */
|
|
637
|
+
async promoteJobs(opts) {
|
|
638
|
+
const count = opts?.count ?? 100;
|
|
639
|
+
if (this.embedded) {
|
|
640
|
+
// Get delayed jobs and promote them
|
|
641
|
+
const jobs = this.getJobs({ state: 'delayed', start: 0, end: count });
|
|
642
|
+
let promoted = 0;
|
|
643
|
+
for (const job of jobs) {
|
|
644
|
+
const success = await getSharedManager().promote(jobId(job.id));
|
|
645
|
+
if (success)
|
|
646
|
+
promoted++;
|
|
647
|
+
}
|
|
648
|
+
return promoted;
|
|
649
|
+
}
|
|
650
|
+
const response = await this.tcp.send({ cmd: 'PromoteJobs', queue: this.name, count });
|
|
651
|
+
if (!response.ok)
|
|
652
|
+
return 0;
|
|
653
|
+
return response.count ?? 0;
|
|
654
|
+
}
|
|
655
|
+
/** Get job logs */
|
|
656
|
+
async getJobLogs(id, start = 0, end = 100, _asc = true) {
|
|
657
|
+
if (this.embedded) {
|
|
658
|
+
const logs = getSharedManager().getLogs(jobId(id));
|
|
659
|
+
const logStrings = logs.slice(start, end).map((l) => `[${l.level}] ${l.message}`);
|
|
660
|
+
return { logs: logStrings, count: logs.length };
|
|
661
|
+
}
|
|
662
|
+
const response = await this.tcp.send({ cmd: 'GetLogs', id, start, end });
|
|
663
|
+
if (!response.ok)
|
|
664
|
+
return { logs: [], count: 0 };
|
|
665
|
+
const result = response;
|
|
666
|
+
const logStrings = (result.logs ?? []).map((l) => `[${l.level}] ${l.message}`);
|
|
667
|
+
return { logs: logStrings, count: result.count ?? 0 };
|
|
668
|
+
}
|
|
669
|
+
/** Add a log entry to a job */
|
|
670
|
+
async addJobLog(id, logRow, _keepLogs) {
|
|
671
|
+
if (this.embedded) {
|
|
672
|
+
const success = getSharedManager().addLog(jobId(id), logRow);
|
|
673
|
+
return success ? 1 : 0;
|
|
674
|
+
}
|
|
675
|
+
const response = await this.tcp.send({ cmd: 'AddLog', id, message: logRow });
|
|
676
|
+
return response.ok ? 1 : 0;
|
|
677
|
+
}
|
|
678
|
+
/** Update job progress */
|
|
679
|
+
async updateJobProgress(id, progress) {
|
|
680
|
+
const progressValue = typeof progress === 'number' ? progress : 0;
|
|
681
|
+
const message = typeof progress === 'object' ? JSON.stringify(progress) : undefined;
|
|
682
|
+
if (this.embedded) {
|
|
683
|
+
await getSharedManager().updateProgress(jobId(id), progressValue, message);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
await this.tcp.send({ cmd: 'Progress', id, progress: progressValue, message });
|
|
687
|
+
}
|
|
688
|
+
/** Set global concurrency limit for this queue */
|
|
689
|
+
setGlobalConcurrency(concurrency) {
|
|
690
|
+
if (this.embedded) {
|
|
691
|
+
getSharedManager().setConcurrency(this.name, concurrency);
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
void this.tcp.send({ cmd: 'SetConcurrency', queue: this.name, limit: concurrency });
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/** Remove global concurrency limit */
|
|
698
|
+
removeGlobalConcurrency() {
|
|
699
|
+
if (this.embedded) {
|
|
700
|
+
getSharedManager().clearConcurrency(this.name);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
void this.tcp.send({ cmd: 'ClearConcurrency', queue: this.name });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/** Get global concurrency limit */
|
|
707
|
+
getGlobalConcurrency() {
|
|
708
|
+
// This would need to be implemented in QueueManager
|
|
709
|
+
// For now, return null
|
|
710
|
+
return Promise.resolve(null);
|
|
711
|
+
}
|
|
712
|
+
/** Set global rate limit for this queue */
|
|
713
|
+
setGlobalRateLimit(max, _duration) {
|
|
714
|
+
if (this.embedded) {
|
|
715
|
+
getSharedManager().setRateLimit(this.name, max);
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
void this.tcp.send({ cmd: 'RateLimit', queue: this.name, limit: max });
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/** Remove global rate limit */
|
|
722
|
+
removeGlobalRateLimit() {
|
|
723
|
+
if (this.embedded) {
|
|
724
|
+
getSharedManager().clearRateLimit(this.name);
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
void this.tcp.send({ cmd: 'RateLimitClear', queue: this.name });
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
/** Get global rate limit */
|
|
731
|
+
getGlobalRateLimit() {
|
|
732
|
+
// This would need to be implemented in QueueManager
|
|
733
|
+
// For now, return null
|
|
734
|
+
return Promise.resolve(null);
|
|
735
|
+
}
|
|
736
|
+
/** Get metrics for this queue */
|
|
737
|
+
async getMetrics(type, _start, _end) {
|
|
738
|
+
if (this.embedded) {
|
|
739
|
+
const stats = getSharedManager().getStats();
|
|
740
|
+
const count = type === 'completed' ? stats.completed : stats.dlq;
|
|
741
|
+
return { meta: { count }, data: [] };
|
|
742
|
+
}
|
|
743
|
+
const response = await this.tcp.send({ cmd: 'Metrics' });
|
|
744
|
+
if (!response.ok)
|
|
745
|
+
return { meta: { count: 0 }, data: [] };
|
|
746
|
+
const stats = response.stats;
|
|
747
|
+
const count = type === 'completed' ? (stats?.completed ?? 0) : (stats?.dlq ?? 0);
|
|
748
|
+
return { meta: { count }, data: [] };
|
|
749
|
+
}
|
|
750
|
+
/** Get workers processing this queue */
|
|
751
|
+
async getWorkers() {
|
|
752
|
+
if (this.embedded) {
|
|
753
|
+
// Would need to implement worker tracking
|
|
754
|
+
return [];
|
|
755
|
+
}
|
|
756
|
+
const response = await this.tcp.send({ cmd: 'ListWorkers' });
|
|
757
|
+
if (!response.ok)
|
|
758
|
+
return [];
|
|
759
|
+
return (response.workers ?? []);
|
|
760
|
+
}
|
|
761
|
+
/** Get worker count */
|
|
762
|
+
async getWorkersCount() {
|
|
763
|
+
const workers = await this.getWorkers();
|
|
764
|
+
return workers.length;
|
|
765
|
+
}
|
|
241
766
|
/** Get jobs with filtering and pagination */
|
|
242
767
|
getJobs(options = {}) {
|
|
243
768
|
if (this.embedded) {
|
|
244
769
|
const jobs = getSharedManager().getJobs(this.name, options);
|
|
245
770
|
return jobs.map((job) => {
|
|
246
771
|
const jobData = job.data;
|
|
247
|
-
return toPublicJob(
|
|
772
|
+
return toPublicJob({
|
|
773
|
+
job,
|
|
774
|
+
name: jobData?.name ?? 'default',
|
|
775
|
+
getState: (jid) => this.getJobState(jid),
|
|
776
|
+
remove: (jid) => this.removeAsync(jid),
|
|
777
|
+
retry: (jid) => this.retryJob(jid),
|
|
778
|
+
getChildrenValues: (jid) => this.getChildrenValues(jid),
|
|
779
|
+
updateData: (jid, data) => this.updateJobData(jid, data),
|
|
780
|
+
promote: (jid) => this.promoteJob(jid),
|
|
781
|
+
changeDelay: (jid, delay) => this.changeJobDelay(jid, delay),
|
|
782
|
+
changePriority: (jid, opts) => this.changeJobPriority(jid, opts),
|
|
783
|
+
extendLock: (jid, token, duration) => this.extendJobLock(jid, token, duration),
|
|
784
|
+
clearLogs: (jid, keepLogs) => this.clearJobLogs(jid, keepLogs),
|
|
785
|
+
getDependencies: (jid, opts) => this.getJobDependencies(jid, opts),
|
|
786
|
+
getDependenciesCount: (jid, opts) => this.getJobDependenciesCount(jid, opts),
|
|
787
|
+
moveToCompleted: (jid, result, token) => this.moveJobToCompleted(jid, result, token),
|
|
788
|
+
moveToFailed: (jid, error, token) => this.moveJobToFailed(jid, error, token),
|
|
789
|
+
moveToWait: (jid, token) => this.moveJobToWait(jid, token),
|
|
790
|
+
moveToDelayed: (jid, timestamp, token) => this.moveJobToDelayed(jid, timestamp, token),
|
|
791
|
+
moveToWaitingChildren: (jid, token, opts) => this.moveJobToWaitingChildren(jid, token, opts),
|
|
792
|
+
waitUntilFinished: (jid, queueEvents, ttl) => this.waitJobUntilFinished(jid, queueEvents, ttl),
|
|
793
|
+
});
|
|
248
794
|
});
|
|
249
795
|
}
|
|
250
796
|
// Sync TCP version returns empty - use getJobsAsync
|
|
@@ -267,16 +813,91 @@ export class Queue {
|
|
|
267
813
|
const jobs = response.jobs;
|
|
268
814
|
return jobs.map((j) => {
|
|
269
815
|
const jobData = j.data;
|
|
816
|
+
const jobName = jobData?.name ?? 'default';
|
|
270
817
|
return {
|
|
271
818
|
id: j.id,
|
|
272
|
-
name:
|
|
819
|
+
name: jobName,
|
|
273
820
|
data: j.data,
|
|
274
821
|
queueName: j.queue,
|
|
275
822
|
attemptsMade: j.attempts,
|
|
276
823
|
timestamp: j.createdAt,
|
|
277
824
|
progress: j.progress ?? 0,
|
|
825
|
+
// BullMQ v5 properties
|
|
826
|
+
delay: 0,
|
|
827
|
+
processedOn: undefined,
|
|
828
|
+
finishedOn: undefined,
|
|
829
|
+
stacktrace: null,
|
|
830
|
+
stalledCounter: 0,
|
|
831
|
+
priority: j.priority,
|
|
832
|
+
parentKey: undefined,
|
|
833
|
+
opts: {},
|
|
834
|
+
token: undefined,
|
|
835
|
+
processedBy: undefined,
|
|
836
|
+
deduplicationId: undefined,
|
|
837
|
+
repeatJobKey: undefined,
|
|
838
|
+
attemptsStarted: j.attempts,
|
|
839
|
+
// Methods
|
|
278
840
|
updateProgress: async () => { },
|
|
279
841
|
log: async () => { },
|
|
842
|
+
getState: () => this.getJobState(j.id),
|
|
843
|
+
remove: () => this.removeAsync(j.id),
|
|
844
|
+
retry: () => this.retryJob(j.id),
|
|
845
|
+
getChildrenValues: () => this.getChildrenValues(j.id),
|
|
846
|
+
// BullMQ v5 state check methods
|
|
847
|
+
isWaiting: async () => (await this.getJobState(j.id)) === 'waiting',
|
|
848
|
+
isActive: async () => (await this.getJobState(j.id)) === 'active',
|
|
849
|
+
isDelayed: async () => (await this.getJobState(j.id)) === 'delayed',
|
|
850
|
+
isCompleted: async () => (await this.getJobState(j.id)) === 'completed',
|
|
851
|
+
isFailed: async () => (await this.getJobState(j.id)) === 'failed',
|
|
852
|
+
isWaitingChildren: () => Promise.resolve(false),
|
|
853
|
+
// BullMQ v5 mutation methods
|
|
854
|
+
updateData: async () => { },
|
|
855
|
+
promote: async () => { },
|
|
856
|
+
changeDelay: async () => { },
|
|
857
|
+
changePriority: async () => { },
|
|
858
|
+
extendLock: () => Promise.resolve(0),
|
|
859
|
+
clearLogs: async () => { },
|
|
860
|
+
// BullMQ v5 dependency methods
|
|
861
|
+
getDependencies: () => Promise.resolve({ processed: {}, unprocessed: [] }),
|
|
862
|
+
getDependenciesCount: () => Promise.resolve({ processed: 0, unprocessed: 0 }),
|
|
863
|
+
// BullMQ v5 serialization methods
|
|
864
|
+
toJSON: () => ({
|
|
865
|
+
id: j.id,
|
|
866
|
+
name: jobName,
|
|
867
|
+
data: j.data,
|
|
868
|
+
opts: {},
|
|
869
|
+
progress: j.progress ?? 0,
|
|
870
|
+
delay: 0,
|
|
871
|
+
timestamp: j.createdAt,
|
|
872
|
+
attemptsMade: j.attempts,
|
|
873
|
+
stacktrace: null,
|
|
874
|
+
queueQualifiedName: `bull:${j.queue}`,
|
|
875
|
+
}),
|
|
876
|
+
asJSON: () => ({
|
|
877
|
+
id: j.id,
|
|
878
|
+
name: jobName,
|
|
879
|
+
data: JSON.stringify(j.data),
|
|
880
|
+
opts: '{}',
|
|
881
|
+
progress: String(j.progress ?? 0),
|
|
882
|
+
delay: '0',
|
|
883
|
+
timestamp: String(j.createdAt),
|
|
884
|
+
attemptsMade: String(j.attempts),
|
|
885
|
+
stacktrace: null,
|
|
886
|
+
}),
|
|
887
|
+
// BullMQ v5 move methods
|
|
888
|
+
moveToCompleted: () => Promise.resolve(null),
|
|
889
|
+
moveToFailed: () => Promise.resolve(),
|
|
890
|
+
moveToWait: () => Promise.resolve(false),
|
|
891
|
+
moveToDelayed: () => Promise.resolve(),
|
|
892
|
+
moveToWaitingChildren: () => Promise.resolve(false),
|
|
893
|
+
waitUntilFinished: () => Promise.resolve(undefined),
|
|
894
|
+
// BullMQ v5 additional methods
|
|
895
|
+
discard: () => { },
|
|
896
|
+
getFailedChildrenValues: () => Promise.resolve({}),
|
|
897
|
+
getIgnoredChildrenFailures: () => Promise.resolve({}),
|
|
898
|
+
removeChildDependency: () => Promise.resolve(false),
|
|
899
|
+
removeDeduplicationKey: () => Promise.resolve(false),
|
|
900
|
+
removeUnprocessedChildren: () => Promise.resolve(),
|
|
280
901
|
};
|
|
281
902
|
});
|
|
282
903
|
}
|
|
@@ -360,6 +981,540 @@ export class Queue {
|
|
|
360
981
|
return 0;
|
|
361
982
|
return response.count ?? 0;
|
|
362
983
|
}
|
|
984
|
+
// ============================================================================
|
|
985
|
+
// BullMQ v5 Compatibility Methods (Additional)
|
|
986
|
+
// ============================================================================
|
|
987
|
+
/**
|
|
988
|
+
* Trim events to a maximum length.
|
|
989
|
+
* In bunqueue, events are emitted in real-time and not stored, so this is a no-op.
|
|
990
|
+
*/
|
|
991
|
+
trimEvents(_maxLength) {
|
|
992
|
+
// Events in bunqueue are not persisted - they're emitted via EventEmitter
|
|
993
|
+
return Promise.resolve(0);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Get jobs sorted by priority (highest first).
|
|
997
|
+
* In BullMQ, this returns jobs waiting to be processed sorted by priority.
|
|
998
|
+
*/
|
|
999
|
+
getPrioritized(start = 0, end = -1) {
|
|
1000
|
+
// In bunqueue, all waiting jobs are already prioritized in the priority queue
|
|
1001
|
+
return this.getWaitingAsync(start, end);
|
|
1002
|
+
}
|
|
1003
|
+
/** Get count of prioritized jobs (same as waiting count in bunqueue) */
|
|
1004
|
+
getPrioritizedCount() {
|
|
1005
|
+
return this.getWaitingCount();
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Get jobs that are waiting for their parent to complete.
|
|
1009
|
+
* Used in job flows (parent-child dependencies).
|
|
1010
|
+
*/
|
|
1011
|
+
getWaitingChildren(start = 0, end = -1) {
|
|
1012
|
+
if (this.embedded) {
|
|
1013
|
+
// Get jobs with pending dependencies
|
|
1014
|
+
const jobs = getSharedManager().getJobs(this.name, {
|
|
1015
|
+
state: 'delayed',
|
|
1016
|
+
start,
|
|
1017
|
+
end: end === -1 ? 1000 : end,
|
|
1018
|
+
});
|
|
1019
|
+
// Filter to only those with dependencies (would need dependency tracking)
|
|
1020
|
+
const result = jobs
|
|
1021
|
+
.filter((j) => {
|
|
1022
|
+
const data = j.data;
|
|
1023
|
+
return data?._waitingParent === true;
|
|
1024
|
+
})
|
|
1025
|
+
.map((job) => {
|
|
1026
|
+
const jobData = job.data;
|
|
1027
|
+
return toPublicJob({
|
|
1028
|
+
job,
|
|
1029
|
+
name: jobData?.name ?? 'default',
|
|
1030
|
+
getState: (jid) => this.getJobState(jid),
|
|
1031
|
+
remove: (jid) => this.removeAsync(jid),
|
|
1032
|
+
retry: (jid) => this.retryJob(jid),
|
|
1033
|
+
getChildrenValues: (jid) => this.getChildrenValues(jid),
|
|
1034
|
+
updateData: (jid, data) => this.updateJobData(jid, data),
|
|
1035
|
+
promote: (jid) => this.promoteJob(jid),
|
|
1036
|
+
changeDelay: (jid, delay) => this.changeJobDelay(jid, delay),
|
|
1037
|
+
changePriority: (jid, opts) => this.changeJobPriority(jid, opts),
|
|
1038
|
+
extendLock: (jid, token, duration) => this.extendJobLock(jid, token, duration),
|
|
1039
|
+
clearLogs: (jid, keepLogs) => this.clearJobLogs(jid, keepLogs),
|
|
1040
|
+
getDependencies: (jid, opts) => this.getJobDependencies(jid, opts),
|
|
1041
|
+
getDependenciesCount: (jid, opts) => this.getJobDependenciesCount(jid, opts),
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
return Promise.resolve(result);
|
|
1045
|
+
}
|
|
1046
|
+
// TCP mode - not fully supported yet
|
|
1047
|
+
return Promise.resolve([]);
|
|
1048
|
+
}
|
|
1049
|
+
/** Get count of jobs waiting for their parent */
|
|
1050
|
+
async getWaitingChildrenCount() {
|
|
1051
|
+
const children = await this.getWaitingChildren();
|
|
1052
|
+
return children.length;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Get dependencies of a parent job.
|
|
1056
|
+
* Returns processed/unprocessed children of a parent job in a flow.
|
|
1057
|
+
*/
|
|
1058
|
+
getDependencies(_parentId, _type, _start = 0, _end = -1) {
|
|
1059
|
+
// Would require implementing dependency tracking
|
|
1060
|
+
return Promise.resolve({
|
|
1061
|
+
processed: {},
|
|
1062
|
+
unprocessed: [],
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Get the TTL of the current rate limit.
|
|
1067
|
+
* Returns 0 if no rate limit is active.
|
|
1068
|
+
*/
|
|
1069
|
+
getRateLimitTtl(_maxJobs) {
|
|
1070
|
+
// Would need to track rate limit expiration
|
|
1071
|
+
return Promise.resolve(0);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Set a temporary rate limit that expires after the given time.
|
|
1075
|
+
*/
|
|
1076
|
+
async rateLimit(expireTimeMs) {
|
|
1077
|
+
if (this.embedded) {
|
|
1078
|
+
// Set rate limit with expiration
|
|
1079
|
+
getSharedManager().setRateLimit(this.name, 1);
|
|
1080
|
+
// Schedule removal after expiration
|
|
1081
|
+
setTimeout(() => {
|
|
1082
|
+
getSharedManager().clearRateLimit(this.name);
|
|
1083
|
+
}, expireTimeMs);
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
await this.tcp.send({ cmd: 'RateLimit', queue: this.name, limit: 1 });
|
|
1087
|
+
// Note: TCP mode would need server-side expiration support
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Check if the queue is currently rate limited (at max capacity).
|
|
1092
|
+
*/
|
|
1093
|
+
isMaxed() {
|
|
1094
|
+
// Would need to check if rate limit is currently blocking
|
|
1095
|
+
return Promise.resolve(false);
|
|
1096
|
+
}
|
|
1097
|
+
// ============================================================================
|
|
1098
|
+
// Job Scheduler Methods (BullMQ v5 repeatable jobs)
|
|
1099
|
+
// ============================================================================
|
|
1100
|
+
/**
|
|
1101
|
+
* Create or update a job scheduler (repeatable job).
|
|
1102
|
+
* This is the BullMQ v5 way to handle cron/repeating jobs.
|
|
1103
|
+
*/
|
|
1104
|
+
async upsertJobScheduler(schedulerId, repeatOpts, jobTemplate) {
|
|
1105
|
+
const cronPattern = repeatOpts.pattern;
|
|
1106
|
+
const repeatEvery = repeatOpts.every;
|
|
1107
|
+
if (this.embedded) {
|
|
1108
|
+
const manager = getSharedManager();
|
|
1109
|
+
// Use the cron scheduler
|
|
1110
|
+
manager.addCron({
|
|
1111
|
+
name: schedulerId,
|
|
1112
|
+
queue: this.name,
|
|
1113
|
+
data: jobTemplate?.data ?? {},
|
|
1114
|
+
schedule: cronPattern,
|
|
1115
|
+
repeatEvery,
|
|
1116
|
+
timezone: 'UTC',
|
|
1117
|
+
});
|
|
1118
|
+
return {
|
|
1119
|
+
id: schedulerId,
|
|
1120
|
+
name: jobTemplate?.name ?? 'default',
|
|
1121
|
+
next: Date.now() + (repeatEvery ?? 60000),
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
const response = await this.tcp.send({
|
|
1126
|
+
cmd: 'Cron',
|
|
1127
|
+
name: schedulerId,
|
|
1128
|
+
queue: this.name,
|
|
1129
|
+
data: jobTemplate?.data ?? {},
|
|
1130
|
+
schedule: cronPattern,
|
|
1131
|
+
repeatEvery,
|
|
1132
|
+
});
|
|
1133
|
+
if (!response.ok)
|
|
1134
|
+
return null;
|
|
1135
|
+
return {
|
|
1136
|
+
id: schedulerId,
|
|
1137
|
+
name: jobTemplate?.name ?? 'default',
|
|
1138
|
+
next: response.nextRun ?? Date.now(),
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Remove a job scheduler.
|
|
1144
|
+
*/
|
|
1145
|
+
async removeJobScheduler(schedulerId) {
|
|
1146
|
+
if (this.embedded) {
|
|
1147
|
+
getSharedManager().removeCron(schedulerId);
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
const response = await this.tcp.send({ cmd: 'CronDelete', name: schedulerId });
|
|
1152
|
+
return response.ok === true;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Get a job scheduler by ID.
|
|
1157
|
+
*/
|
|
1158
|
+
async getJobScheduler(schedulerId) {
|
|
1159
|
+
if (this.embedded) {
|
|
1160
|
+
const crons = getSharedManager().listCrons();
|
|
1161
|
+
const cron = crons.find((c) => c.name === schedulerId);
|
|
1162
|
+
if (!cron)
|
|
1163
|
+
return null;
|
|
1164
|
+
return {
|
|
1165
|
+
id: cron.name,
|
|
1166
|
+
name: cron.name,
|
|
1167
|
+
next: cron.nextRun,
|
|
1168
|
+
pattern: cron.schedule ?? undefined,
|
|
1169
|
+
every: cron.repeatEvery ?? undefined,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
const response = await this.tcp.send({ cmd: 'CronList' });
|
|
1174
|
+
if (!response.ok)
|
|
1175
|
+
return null;
|
|
1176
|
+
const crons = response.crons;
|
|
1177
|
+
const cron = crons?.find((c) => c.name === schedulerId);
|
|
1178
|
+
if (!cron)
|
|
1179
|
+
return null;
|
|
1180
|
+
return {
|
|
1181
|
+
id: cron.name,
|
|
1182
|
+
name: cron.name,
|
|
1183
|
+
next: cron.nextRun,
|
|
1184
|
+
pattern: cron.schedule ?? undefined,
|
|
1185
|
+
every: cron.repeatEvery ?? undefined,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Get all job schedulers for this queue.
|
|
1191
|
+
*/
|
|
1192
|
+
async getJobSchedulers(_start = 0, _end = -1, _asc = true) {
|
|
1193
|
+
if (this.embedded) {
|
|
1194
|
+
const crons = getSharedManager()
|
|
1195
|
+
.listCrons()
|
|
1196
|
+
.filter((c) => c.queue === this.name);
|
|
1197
|
+
return crons.map((c) => ({
|
|
1198
|
+
id: c.name,
|
|
1199
|
+
name: c.name,
|
|
1200
|
+
next: c.nextRun,
|
|
1201
|
+
pattern: c.schedule ?? undefined,
|
|
1202
|
+
every: c.repeatEvery ?? undefined,
|
|
1203
|
+
}));
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
const response = await this.tcp.send({ cmd: 'CronList' });
|
|
1207
|
+
if (!response.ok)
|
|
1208
|
+
return [];
|
|
1209
|
+
const crons = response.crons;
|
|
1210
|
+
return (crons ?? [])
|
|
1211
|
+
.filter((c) => c.queue === this.name)
|
|
1212
|
+
.map((c) => ({
|
|
1213
|
+
id: c.name,
|
|
1214
|
+
name: c.name,
|
|
1215
|
+
next: c.nextRun,
|
|
1216
|
+
pattern: c.schedule ?? undefined,
|
|
1217
|
+
every: c.repeatEvery ?? undefined,
|
|
1218
|
+
}));
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
/** Get count of job schedulers for this queue */
|
|
1222
|
+
async getJobSchedulersCount() {
|
|
1223
|
+
const schedulers = await this.getJobSchedulers();
|
|
1224
|
+
return schedulers.length;
|
|
1225
|
+
}
|
|
1226
|
+
// ============================================================================
|
|
1227
|
+
// Deduplication Methods
|
|
1228
|
+
// ============================================================================
|
|
1229
|
+
/**
|
|
1230
|
+
* Get the job ID associated with a deduplication key.
|
|
1231
|
+
*/
|
|
1232
|
+
getDeduplicationJobId(deduplicationId) {
|
|
1233
|
+
if (this.embedded) {
|
|
1234
|
+
const job = getSharedManager().getJobByCustomId(deduplicationId);
|
|
1235
|
+
return Promise.resolve(job ? String(job.id) : null);
|
|
1236
|
+
}
|
|
1237
|
+
// TCP mode - would need server support
|
|
1238
|
+
return Promise.resolve(null);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Remove a deduplication key, allowing a new job with the same key.
|
|
1242
|
+
*/
|
|
1243
|
+
removeDeduplicationKey(deduplicationId) {
|
|
1244
|
+
if (this.embedded) {
|
|
1245
|
+
// Remove the custom ID mapping
|
|
1246
|
+
const job = getSharedManager().getJobByCustomId(deduplicationId);
|
|
1247
|
+
if (job) {
|
|
1248
|
+
// Would need a method to remove just the customId mapping
|
|
1249
|
+
return Promise.resolve(1);
|
|
1250
|
+
}
|
|
1251
|
+
return Promise.resolve(0);
|
|
1252
|
+
}
|
|
1253
|
+
return Promise.resolve(0);
|
|
1254
|
+
}
|
|
1255
|
+
// ============================================================================
|
|
1256
|
+
// Connection Methods
|
|
1257
|
+
// ============================================================================
|
|
1258
|
+
/**
|
|
1259
|
+
* Wait until the queue is ready (connection established).
|
|
1260
|
+
*/
|
|
1261
|
+
async waitUntilReady() {
|
|
1262
|
+
if (this.embedded) {
|
|
1263
|
+
// Embedded mode is always ready
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
// TCP mode - the pool handles connection
|
|
1267
|
+
// Just ensure we can send a ping
|
|
1268
|
+
await this.tcp.send({ cmd: 'Ping' });
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Disconnect from the server (async version of close).
|
|
1272
|
+
*/
|
|
1273
|
+
disconnect() {
|
|
1274
|
+
this.close();
|
|
1275
|
+
return Promise.resolve();
|
|
1276
|
+
}
|
|
1277
|
+
// ============================================================================
|
|
1278
|
+
// BullMQ v5 Job Mutation Methods (used by Job instances)
|
|
1279
|
+
// ============================================================================
|
|
1280
|
+
/** Update job data */
|
|
1281
|
+
async updateJobData(id, data) {
|
|
1282
|
+
if (this.embedded) {
|
|
1283
|
+
const manager = getSharedManager();
|
|
1284
|
+
await manager.updateJobData(jobId(id), data);
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1287
|
+
await this.tcp.send({ cmd: 'Update', id, data });
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
/** Promote a delayed job to waiting */
|
|
1291
|
+
async promoteJob(id) {
|
|
1292
|
+
if (this.embedded) {
|
|
1293
|
+
await getSharedManager().promote(jobId(id));
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
await this.tcp.send({ cmd: 'Promote', id });
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
/** Change job delay */
|
|
1300
|
+
async changeJobDelay(id, delay) {
|
|
1301
|
+
if (this.embedded) {
|
|
1302
|
+
const manager = getSharedManager();
|
|
1303
|
+
await manager.changeDelay(jobId(id), delay);
|
|
1304
|
+
}
|
|
1305
|
+
else {
|
|
1306
|
+
await this.tcp.send({ cmd: 'ChangeDelay', id, delay });
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
/** Change job priority */
|
|
1310
|
+
async changeJobPriority(id, opts) {
|
|
1311
|
+
if (this.embedded) {
|
|
1312
|
+
const manager = getSharedManager();
|
|
1313
|
+
await manager.changePriority(jobId(id), opts.priority);
|
|
1314
|
+
}
|
|
1315
|
+
else {
|
|
1316
|
+
await this.tcp.send({ cmd: 'ChangePriority', id, priority: opts.priority });
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/** Extend job lock */
|
|
1320
|
+
async extendJobLock(id, token, duration) {
|
|
1321
|
+
if (this.embedded) {
|
|
1322
|
+
const manager = getSharedManager();
|
|
1323
|
+
const extended = await manager.extendLock(jobId(id), token || null, duration);
|
|
1324
|
+
return extended ? duration : 0;
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
const response = await this.tcp.send({ cmd: 'ExtendLock', id, token, duration });
|
|
1328
|
+
return response.ok ? duration : 0;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
/** Clear job logs */
|
|
1332
|
+
async clearJobLogs(id, keepLogs) {
|
|
1333
|
+
if (this.embedded) {
|
|
1334
|
+
const manager = getSharedManager();
|
|
1335
|
+
manager.clearLogs(jobId(id), keepLogs);
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
await this.tcp.send({ cmd: 'ClearLogs', id, keepLogs });
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
/** Get job dependencies */
|
|
1342
|
+
async getJobDependencies(id, _opts) {
|
|
1343
|
+
if (this.embedded) {
|
|
1344
|
+
const manager = getSharedManager();
|
|
1345
|
+
const job = await manager.getJob(jobId(id));
|
|
1346
|
+
if (!job)
|
|
1347
|
+
return { processed: {}, unprocessed: [] };
|
|
1348
|
+
const childIds = job.childrenIds;
|
|
1349
|
+
const processed = {};
|
|
1350
|
+
const unprocessed = [];
|
|
1351
|
+
for (const childId of childIds) {
|
|
1352
|
+
const result = manager.getResult(childId);
|
|
1353
|
+
if (result !== undefined) {
|
|
1354
|
+
const childJob = await manager.getJob(childId);
|
|
1355
|
+
const key = childJob ? `${childJob.queue}:${childId}` : String(childId);
|
|
1356
|
+
processed[key] = result;
|
|
1357
|
+
}
|
|
1358
|
+
else {
|
|
1359
|
+
unprocessed.push(String(childId));
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
return { processed, unprocessed };
|
|
1363
|
+
}
|
|
1364
|
+
// TCP mode
|
|
1365
|
+
return { processed: {}, unprocessed: [] };
|
|
1366
|
+
}
|
|
1367
|
+
/** Get job dependencies count */
|
|
1368
|
+
async getJobDependenciesCount(id, _opts) {
|
|
1369
|
+
if (this.embedded) {
|
|
1370
|
+
const deps = await this.getJobDependencies(id);
|
|
1371
|
+
return {
|
|
1372
|
+
processed: Object.keys(deps.processed).length,
|
|
1373
|
+
unprocessed: deps.unprocessed.length,
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
return { processed: 0, unprocessed: 0 };
|
|
1377
|
+
}
|
|
1378
|
+
// ============================================================================
|
|
1379
|
+
// BullMQ v5 Job Move Methods
|
|
1380
|
+
// ============================================================================
|
|
1381
|
+
/**
|
|
1382
|
+
* Move job to completed state (BullMQ v5 compatible).
|
|
1383
|
+
* Used internally by workers to mark jobs as complete.
|
|
1384
|
+
*/
|
|
1385
|
+
async moveJobToCompleted(id, returnValue, _token) {
|
|
1386
|
+
if (this.embedded) {
|
|
1387
|
+
const manager = getSharedManager();
|
|
1388
|
+
await manager.ack(jobId(id), returnValue);
|
|
1389
|
+
return null; // Could return next job if fetchNext was true
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
await this.tcp.send({ cmd: 'ACK', id, result: returnValue });
|
|
1393
|
+
return null;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Move job to failed state (BullMQ v5 compatible).
|
|
1398
|
+
* Used internally by workers to mark jobs as failed.
|
|
1399
|
+
*/
|
|
1400
|
+
async moveJobToFailed(id, error, _token) {
|
|
1401
|
+
if (this.embedded) {
|
|
1402
|
+
const manager = getSharedManager();
|
|
1403
|
+
await manager.fail(jobId(id), error.message);
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
await this.tcp.send({ cmd: 'FAIL', id, error: error.message });
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Move job back to waiting state (BullMQ v5 compatible).
|
|
1411
|
+
* Useful for re-queueing a job that needs to be processed again.
|
|
1412
|
+
*/
|
|
1413
|
+
async moveJobToWait(id, _token) {
|
|
1414
|
+
if (this.embedded) {
|
|
1415
|
+
const manager = getSharedManager();
|
|
1416
|
+
const job = await manager.getJob(jobId(id));
|
|
1417
|
+
if (!job)
|
|
1418
|
+
return false;
|
|
1419
|
+
// Re-queue the job by pushing it again
|
|
1420
|
+
await manager.push(job.queue, {
|
|
1421
|
+
data: job.data,
|
|
1422
|
+
priority: job.priority,
|
|
1423
|
+
customId: job.customId ?? undefined,
|
|
1424
|
+
});
|
|
1425
|
+
return true;
|
|
1426
|
+
}
|
|
1427
|
+
else {
|
|
1428
|
+
const response = await this.tcp.send({ cmd: 'MoveToWait', id });
|
|
1429
|
+
return response.ok === true;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Move job to delayed state (BullMQ v5 compatible).
|
|
1434
|
+
* The job will become available for processing at the specified timestamp.
|
|
1435
|
+
*/
|
|
1436
|
+
async moveJobToDelayed(id, timestamp, _token) {
|
|
1437
|
+
if (this.embedded) {
|
|
1438
|
+
const manager = getSharedManager();
|
|
1439
|
+
const delay = Math.max(0, timestamp - Date.now());
|
|
1440
|
+
await manager.changeDelay(jobId(id), delay);
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
await this.tcp.send({ cmd: 'MoveToDelayed', id, timestamp });
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Move job to waiting-children state (BullMQ v5 compatible).
|
|
1448
|
+
* Job will wait for all children to complete before processing.
|
|
1449
|
+
*/
|
|
1450
|
+
async moveJobToWaitingChildren(id, _token, _opts) {
|
|
1451
|
+
if (this.embedded) {
|
|
1452
|
+
// In embedded mode, this is handled automatically by the dependency system
|
|
1453
|
+
// The job is already in waiting-children state if it has pending children
|
|
1454
|
+
const manager = getSharedManager();
|
|
1455
|
+
const job = await manager.getJob(jobId(id));
|
|
1456
|
+
if (!job)
|
|
1457
|
+
return false;
|
|
1458
|
+
// Check if job has unprocessed children
|
|
1459
|
+
const deps = await this.getJobDependencies(id);
|
|
1460
|
+
return deps.unprocessed.length > 0;
|
|
1461
|
+
}
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Wait until job has finished (completed or failed).
|
|
1466
|
+
* BullMQ v5 compatible method.
|
|
1467
|
+
*/
|
|
1468
|
+
async waitJobUntilFinished(id, queueEvents, ttl) {
|
|
1469
|
+
return new Promise((resolve, reject) => {
|
|
1470
|
+
const timeout = ttl
|
|
1471
|
+
? setTimeout(() => {
|
|
1472
|
+
cleanup();
|
|
1473
|
+
reject(new Error(`Job ${id} timed out after ${ttl}ms`));
|
|
1474
|
+
}, ttl)
|
|
1475
|
+
: null;
|
|
1476
|
+
// Cast queueEvents to expected interface
|
|
1477
|
+
const events = queueEvents;
|
|
1478
|
+
const completedHandler = (data) => {
|
|
1479
|
+
if (data.jobId === id) {
|
|
1480
|
+
cleanup();
|
|
1481
|
+
resolve(data.returnvalue);
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
const failedHandler = (data) => {
|
|
1485
|
+
if (data.jobId === id) {
|
|
1486
|
+
cleanup();
|
|
1487
|
+
reject(new Error(data.failedReason ?? 'Job failed'));
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
const cleanup = () => {
|
|
1491
|
+
if (timeout)
|
|
1492
|
+
clearTimeout(timeout);
|
|
1493
|
+
events.off('completed', completedHandler);
|
|
1494
|
+
events.off('failed', failedHandler);
|
|
1495
|
+
};
|
|
1496
|
+
events.on('completed', completedHandler);
|
|
1497
|
+
events.on('failed', failedHandler);
|
|
1498
|
+
// Also check if job is already finished
|
|
1499
|
+
void this.getJobState(id).then((state) => {
|
|
1500
|
+
if (state === 'completed') {
|
|
1501
|
+
cleanup();
|
|
1502
|
+
// Get the result
|
|
1503
|
+
if (this.embedded) {
|
|
1504
|
+
const result = getSharedManager().getResult(jobId(id));
|
|
1505
|
+
resolve(result);
|
|
1506
|
+
}
|
|
1507
|
+
else {
|
|
1508
|
+
resolve(undefined);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
else if (state === 'failed') {
|
|
1512
|
+
cleanup();
|
|
1513
|
+
reject(new Error('Job already failed'));
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
363
1518
|
close() {
|
|
364
1519
|
if (this.tcpPool) {
|
|
365
1520
|
if (this.useSharedPool)
|
|
@@ -370,20 +1525,118 @@ export class Queue {
|
|
|
370
1525
|
}
|
|
371
1526
|
createJobProxy(id, name, data) {
|
|
372
1527
|
const tcp = this.tcp;
|
|
1528
|
+
const ts = Date.now();
|
|
373
1529
|
return {
|
|
374
1530
|
id,
|
|
375
1531
|
name,
|
|
376
1532
|
data,
|
|
377
1533
|
queueName: this.name,
|
|
378
1534
|
attemptsMade: 0,
|
|
379
|
-
timestamp:
|
|
1535
|
+
timestamp: ts,
|
|
380
1536
|
progress: 0,
|
|
1537
|
+
// BullMQ v5 properties
|
|
1538
|
+
delay: 0,
|
|
1539
|
+
processedOn: undefined,
|
|
1540
|
+
finishedOn: undefined,
|
|
1541
|
+
stacktrace: null,
|
|
1542
|
+
stalledCounter: 0,
|
|
1543
|
+
priority: 0,
|
|
1544
|
+
parentKey: undefined,
|
|
1545
|
+
opts: {},
|
|
1546
|
+
token: undefined,
|
|
1547
|
+
processedBy: undefined,
|
|
1548
|
+
deduplicationId: undefined,
|
|
1549
|
+
repeatJobKey: undefined,
|
|
1550
|
+
attemptsStarted: 0,
|
|
1551
|
+
// Methods
|
|
381
1552
|
updateProgress: async (progress, message) => {
|
|
382
1553
|
await tcp.send({ cmd: 'Progress', id, progress, message });
|
|
383
1554
|
},
|
|
384
1555
|
log: async (message) => {
|
|
385
1556
|
await tcp.send({ cmd: 'AddLog', id, message });
|
|
386
1557
|
},
|
|
1558
|
+
getState: () => this.getJobState(id),
|
|
1559
|
+
remove: () => this.removeAsync(id),
|
|
1560
|
+
retry: () => this.retryJob(id),
|
|
1561
|
+
getChildrenValues: () => this.getChildrenValues(id),
|
|
1562
|
+
// BullMQ v5 state check methods
|
|
1563
|
+
isWaiting: async () => (await this.getJobState(id)) === 'waiting',
|
|
1564
|
+
isActive: async () => (await this.getJobState(id)) === 'active',
|
|
1565
|
+
isDelayed: async () => (await this.getJobState(id)) === 'delayed',
|
|
1566
|
+
isCompleted: async () => (await this.getJobState(id)) === 'completed',
|
|
1567
|
+
isFailed: async () => (await this.getJobState(id)) === 'failed',
|
|
1568
|
+
isWaitingChildren: () => Promise.resolve(false),
|
|
1569
|
+
// BullMQ v5 mutation methods
|
|
1570
|
+
updateData: async (newData) => {
|
|
1571
|
+
await tcp.send({ cmd: 'Update', id, data: newData });
|
|
1572
|
+
},
|
|
1573
|
+
promote: async () => {
|
|
1574
|
+
await tcp.send({ cmd: 'Promote', id });
|
|
1575
|
+
},
|
|
1576
|
+
changeDelay: async (delay) => {
|
|
1577
|
+
await tcp.send({ cmd: 'ChangeDelay', id, delay });
|
|
1578
|
+
},
|
|
1579
|
+
changePriority: async (opts) => {
|
|
1580
|
+
await tcp.send({ cmd: 'ChangePriority', id, priority: opts.priority });
|
|
1581
|
+
},
|
|
1582
|
+
extendLock: async (_token, duration) => {
|
|
1583
|
+
const res = await tcp.send({ cmd: 'ExtendLock', id, duration });
|
|
1584
|
+
return res.ok ? duration : 0;
|
|
1585
|
+
},
|
|
1586
|
+
clearLogs: async () => {
|
|
1587
|
+
await tcp.send({ cmd: 'ClearLogs', id });
|
|
1588
|
+
},
|
|
1589
|
+
// BullMQ v5 dependency methods
|
|
1590
|
+
getDependencies: () => Promise.resolve({ processed: {}, unprocessed: [] }),
|
|
1591
|
+
getDependenciesCount: () => Promise.resolve({ processed: 0, unprocessed: 0 }),
|
|
1592
|
+
// BullMQ v5 serialization methods
|
|
1593
|
+
toJSON: () => ({
|
|
1594
|
+
id,
|
|
1595
|
+
name,
|
|
1596
|
+
data,
|
|
1597
|
+
opts: {},
|
|
1598
|
+
progress: 0,
|
|
1599
|
+
delay: 0,
|
|
1600
|
+
timestamp: ts,
|
|
1601
|
+
attemptsMade: 0,
|
|
1602
|
+
stacktrace: null,
|
|
1603
|
+
queueQualifiedName: `bull:${this.name}`,
|
|
1604
|
+
}),
|
|
1605
|
+
asJSON: () => ({
|
|
1606
|
+
id,
|
|
1607
|
+
name,
|
|
1608
|
+
data: JSON.stringify(data),
|
|
1609
|
+
opts: '{}',
|
|
1610
|
+
progress: '0',
|
|
1611
|
+
delay: '0',
|
|
1612
|
+
timestamp: String(ts),
|
|
1613
|
+
attemptsMade: '0',
|
|
1614
|
+
stacktrace: null,
|
|
1615
|
+
}),
|
|
1616
|
+
// BullMQ v5 move methods
|
|
1617
|
+
moveToCompleted: async (returnValue) => {
|
|
1618
|
+
await tcp.send({ cmd: 'ACK', id, result: returnValue });
|
|
1619
|
+
return null;
|
|
1620
|
+
},
|
|
1621
|
+
moveToFailed: async (error) => {
|
|
1622
|
+
await tcp.send({ cmd: 'FAIL', id, error: error.message });
|
|
1623
|
+
},
|
|
1624
|
+
moveToWait: async () => {
|
|
1625
|
+
const res = await tcp.send({ cmd: 'MoveToWait', id });
|
|
1626
|
+
return res.ok === true;
|
|
1627
|
+
},
|
|
1628
|
+
moveToDelayed: async (timestamp) => {
|
|
1629
|
+
await tcp.send({ cmd: 'MoveToDelayed', id, timestamp });
|
|
1630
|
+
},
|
|
1631
|
+
moveToWaitingChildren: () => Promise.resolve(false),
|
|
1632
|
+
waitUntilFinished: () => Promise.resolve(undefined),
|
|
1633
|
+
// BullMQ v5 additional methods
|
|
1634
|
+
discard: () => { },
|
|
1635
|
+
getFailedChildrenValues: () => Promise.resolve({}),
|
|
1636
|
+
getIgnoredChildrenFailures: () => Promise.resolve({}),
|
|
1637
|
+
removeChildDependency: () => Promise.resolve(false),
|
|
1638
|
+
removeDeduplicationKey: () => Promise.resolve(false),
|
|
1639
|
+
removeUnprocessedChildren: () => Promise.resolve(),
|
|
387
1640
|
};
|
|
388
1641
|
}
|
|
389
1642
|
createSimpleJob(id, name, data, timestamp) {
|
|
@@ -395,8 +1648,82 @@ export class Queue {
|
|
|
395
1648
|
attemptsMade: 0,
|
|
396
1649
|
timestamp,
|
|
397
1650
|
progress: 0,
|
|
1651
|
+
// BullMQ v5 properties
|
|
1652
|
+
delay: 0,
|
|
1653
|
+
processedOn: undefined,
|
|
1654
|
+
finishedOn: undefined,
|
|
1655
|
+
stacktrace: null,
|
|
1656
|
+
stalledCounter: 0,
|
|
1657
|
+
priority: 0,
|
|
1658
|
+
parentKey: undefined,
|
|
1659
|
+
opts: {},
|
|
1660
|
+
token: undefined,
|
|
1661
|
+
processedBy: undefined,
|
|
1662
|
+
deduplicationId: undefined,
|
|
1663
|
+
repeatJobKey: undefined,
|
|
1664
|
+
attemptsStarted: 0,
|
|
1665
|
+
// Methods
|
|
398
1666
|
updateProgress: async () => { },
|
|
399
1667
|
log: async () => { },
|
|
1668
|
+
getState: () => this.getJobState(id),
|
|
1669
|
+
remove: () => this.removeAsync(id),
|
|
1670
|
+
retry: () => this.retryJob(id),
|
|
1671
|
+
getChildrenValues: () => this.getChildrenValues(id),
|
|
1672
|
+
// BullMQ v5 state check methods
|
|
1673
|
+
isWaiting: async () => (await this.getJobState(id)) === 'waiting',
|
|
1674
|
+
isActive: async () => (await this.getJobState(id)) === 'active',
|
|
1675
|
+
isDelayed: async () => (await this.getJobState(id)) === 'delayed',
|
|
1676
|
+
isCompleted: async () => (await this.getJobState(id)) === 'completed',
|
|
1677
|
+
isFailed: async () => (await this.getJobState(id)) === 'failed',
|
|
1678
|
+
isWaitingChildren: () => Promise.resolve(false),
|
|
1679
|
+
// BullMQ v5 mutation methods
|
|
1680
|
+
updateData: async () => { },
|
|
1681
|
+
promote: async () => { },
|
|
1682
|
+
changeDelay: async () => { },
|
|
1683
|
+
changePriority: async () => { },
|
|
1684
|
+
extendLock: () => Promise.resolve(0),
|
|
1685
|
+
clearLogs: async () => { },
|
|
1686
|
+
// BullMQ v5 dependency methods
|
|
1687
|
+
getDependencies: () => Promise.resolve({ processed: {}, unprocessed: [] }),
|
|
1688
|
+
getDependenciesCount: () => Promise.resolve({ processed: 0, unprocessed: 0 }),
|
|
1689
|
+
// BullMQ v5 serialization methods
|
|
1690
|
+
toJSON: () => ({
|
|
1691
|
+
id,
|
|
1692
|
+
name,
|
|
1693
|
+
data,
|
|
1694
|
+
opts: {},
|
|
1695
|
+
progress: 0,
|
|
1696
|
+
delay: 0,
|
|
1697
|
+
timestamp,
|
|
1698
|
+
attemptsMade: 0,
|
|
1699
|
+
stacktrace: null,
|
|
1700
|
+
queueQualifiedName: `bull:${this.name}`,
|
|
1701
|
+
}),
|
|
1702
|
+
asJSON: () => ({
|
|
1703
|
+
id,
|
|
1704
|
+
name,
|
|
1705
|
+
data: JSON.stringify(data),
|
|
1706
|
+
opts: '{}',
|
|
1707
|
+
progress: '0',
|
|
1708
|
+
delay: '0',
|
|
1709
|
+
timestamp: String(timestamp),
|
|
1710
|
+
attemptsMade: '0',
|
|
1711
|
+
stacktrace: null,
|
|
1712
|
+
}),
|
|
1713
|
+
// BullMQ v5 move methods
|
|
1714
|
+
moveToCompleted: () => Promise.resolve(null),
|
|
1715
|
+
moveToFailed: () => Promise.resolve(),
|
|
1716
|
+
moveToWait: () => Promise.resolve(false),
|
|
1717
|
+
moveToDelayed: () => Promise.resolve(),
|
|
1718
|
+
moveToWaitingChildren: () => Promise.resolve(false),
|
|
1719
|
+
waitUntilFinished: () => Promise.resolve(undefined),
|
|
1720
|
+
// BullMQ v5 additional methods
|
|
1721
|
+
discard: () => { },
|
|
1722
|
+
getFailedChildrenValues: () => Promise.resolve({}),
|
|
1723
|
+
getIgnoredChildrenFailures: () => Promise.resolve({}),
|
|
1724
|
+
removeChildDependency: () => Promise.resolve(false),
|
|
1725
|
+
removeDeduplicationKey: () => Promise.resolve(false),
|
|
1726
|
+
removeUnprocessedChildren: () => Promise.resolve(),
|
|
400
1727
|
};
|
|
401
1728
|
}
|
|
402
1729
|
}
|