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.
Files changed (123) hide show
  1. package/dist/application/backgroundTasks.d.ts +10 -0
  2. package/dist/application/backgroundTasks.d.ts.map +1 -1
  3. package/dist/application/backgroundTasks.js +67 -6
  4. package/dist/application/backgroundTasks.js.map +1 -1
  5. package/dist/application/cleanupTasks.js +1 -1
  6. package/dist/application/cleanupTasks.js.map +1 -1
  7. package/dist/application/clientTracking.d.ts.map +1 -1
  8. package/dist/application/clientTracking.js +23 -10
  9. package/dist/application/clientTracking.js.map +1 -1
  10. package/dist/application/dependencyProcessor.d.ts.map +1 -1
  11. package/dist/application/dependencyProcessor.js +14 -13
  12. package/dist/application/dependencyProcessor.js.map +1 -1
  13. package/dist/application/eventsManager.d.ts.map +1 -1
  14. package/dist/application/eventsManager.js +16 -4
  15. package/dist/application/eventsManager.js.map +1 -1
  16. package/dist/application/jobLogsManager.d.ts +2 -2
  17. package/dist/application/jobLogsManager.d.ts.map +1 -1
  18. package/dist/application/jobLogsManager.js +13 -3
  19. package/dist/application/jobLogsManager.js.map +1 -1
  20. package/dist/application/lockManager.js +1 -1
  21. package/dist/application/lockManager.js.map +1 -1
  22. package/dist/application/operations/ack.d.ts +2 -1
  23. package/dist/application/operations/ack.d.ts.map +1 -1
  24. package/dist/application/operations/ack.js +12 -0
  25. package/dist/application/operations/ack.js.map +1 -1
  26. package/dist/application/operations/jobManagement.d.ts +1 -1
  27. package/dist/application/operations/jobManagement.d.ts.map +1 -1
  28. package/dist/application/operations/jobManagement.js +23 -3
  29. package/dist/application/operations/jobManagement.js.map +1 -1
  30. package/dist/application/operations/push.d.ts +1 -1
  31. package/dist/application/operations/push.d.ts.map +1 -1
  32. package/dist/application/operations/push.js +13 -5
  33. package/dist/application/operations/push.js.map +1 -1
  34. package/dist/application/operations/queryOperations.d.ts +3 -0
  35. package/dist/application/operations/queryOperations.d.ts.map +1 -1
  36. package/dist/application/operations/queryOperations.js +29 -0
  37. package/dist/application/operations/queryOperations.js.map +1 -1
  38. package/dist/application/queueManager.d.ts +15 -1
  39. package/dist/application/queueManager.d.ts.map +1 -1
  40. package/dist/application/queueManager.js +69 -2
  41. package/dist/application/queueManager.js.map +1 -1
  42. package/dist/application/stallDetection.js +25 -20
  43. package/dist/application/stallDetection.js.map +1 -1
  44. package/dist/application/webhookManager.d.ts.map +1 -1
  45. package/dist/application/webhookManager.js +18 -2
  46. package/dist/application/webhookManager.js.map +1 -1
  47. package/dist/application/workerManager.d.ts.map +1 -1
  48. package/dist/application/workerManager.js +4 -2
  49. package/dist/application/workerManager.js.map +1 -1
  50. package/dist/client/events.d.ts +29 -0
  51. package/dist/client/events.d.ts.map +1 -1
  52. package/dist/client/events.js +92 -21
  53. package/dist/client/events.js.map +1 -1
  54. package/dist/client/flow.d.ts +122 -3
  55. package/dist/client/flow.d.ts.map +1 -1
  56. package/dist/client/flow.js +374 -2
  57. package/dist/client/flow.js.map +1 -1
  58. package/dist/client/index.d.ts +2 -2
  59. package/dist/client/index.d.ts.map +1 -1
  60. package/dist/client/queue/queue.d.ts +260 -1
  61. package/dist/client/queue/queue.d.ts.map +1 -1
  62. package/dist/client/queue/queue.js +1343 -16
  63. package/dist/client/queue/queue.js.map +1 -1
  64. package/dist/client/tcpPool.d.ts.map +1 -1
  65. package/dist/client/tcpPool.js +19 -8
  66. package/dist/client/tcpPool.js.map +1 -1
  67. package/dist/client/types.d.ts +430 -13
  68. package/dist/client/types.d.ts.map +1 -1
  69. package/dist/client/types.js +346 -5
  70. package/dist/client/types.js.map +1 -1
  71. package/dist/client/worker/ackBatcher.d.ts +1 -0
  72. package/dist/client/worker/ackBatcher.d.ts.map +1 -1
  73. package/dist/client/worker/ackBatcher.js +9 -0
  74. package/dist/client/worker/ackBatcher.js.map +1 -1
  75. package/dist/client/worker/processor.js +6 -1
  76. package/dist/client/worker/processor.js.map +1 -1
  77. package/dist/client/worker/worker.d.ts +117 -0
  78. package/dist/client/worker/worker.d.ts.map +1 -1
  79. package/dist/client/worker/worker.js +375 -3
  80. package/dist/client/worker/worker.js.map +1 -1
  81. package/dist/domain/queue/priorityQueue.d.ts.map +1 -1
  82. package/dist/domain/queue/priorityQueue.js +24 -18
  83. package/dist/domain/queue/priorityQueue.js.map +1 -1
  84. package/dist/domain/queue/shard.d.ts +4 -0
  85. package/dist/domain/queue/shard.d.ts.map +1 -1
  86. package/dist/domain/queue/shard.js +21 -7
  87. package/dist/domain/queue/shard.js.map +1 -1
  88. package/dist/domain/types/job.d.ts +89 -2
  89. package/dist/domain/types/job.d.ts.map +1 -1
  90. package/dist/domain/types/job.js +94 -26
  91. package/dist/domain/types/job.js.map +1 -1
  92. package/dist/domain/types/queue.d.ts +11 -1
  93. package/dist/domain/types/queue.d.ts.map +1 -1
  94. package/dist/infrastructure/persistence/sqliteBatch.d.ts +9 -4
  95. package/dist/infrastructure/persistence/sqliteBatch.d.ts.map +1 -1
  96. package/dist/infrastructure/persistence/sqliteBatch.js +34 -17
  97. package/dist/infrastructure/persistence/sqliteBatch.js.map +1 -1
  98. package/dist/infrastructure/persistence/sqliteSerializer.d.ts.map +1 -1
  99. package/dist/infrastructure/persistence/sqliteSerializer.js +14 -0
  100. package/dist/infrastructure/persistence/sqliteSerializer.js.map +1 -1
  101. package/dist/infrastructure/scheduler/cronScheduler.d.ts.map +1 -1
  102. package/dist/infrastructure/scheduler/cronScheduler.js +29 -15
  103. package/dist/infrastructure/scheduler/cronScheduler.js.map +1 -1
  104. package/dist/infrastructure/server/handlers/query.d.ts.map +1 -1
  105. package/dist/infrastructure/server/handlers/query.js +1 -16
  106. package/dist/infrastructure/server/handlers/query.js.map +1 -1
  107. package/dist/infrastructure/server/rateLimiter.d.ts.map +1 -1
  108. package/dist/infrastructure/server/rateLimiter.js +5 -3
  109. package/dist/infrastructure/server/rateLimiter.js.map +1 -1
  110. package/dist/infrastructure/server/tcp.d.ts.map +1 -1
  111. package/dist/infrastructure/server/tcp.js +36 -4
  112. package/dist/infrastructure/server/tcp.js.map +1 -1
  113. package/dist/main.js +5 -2
  114. package/dist/main.js.map +1 -1
  115. package/dist/shared/lock.d.ts +1 -1
  116. package/dist/shared/lock.d.ts.map +1 -1
  117. package/dist/shared/lock.js +6 -4
  118. package/dist/shared/lock.js.map +1 -1
  119. package/dist/shared/lru.d.ts +28 -0
  120. package/dist/shared/lru.d.ts.map +1 -1
  121. package/dist/shared/lru.js +28 -0
  122. package/dist/shared/lru.js.map +1 -1
  123. 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: { name, ...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: merged.removeOnComplete,
65
- removeOnFail: merged.removeOnFail,
66
- repeat: merged.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: { name, ...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: m.removeOnComplete,
112
- removeOnFail: m.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(job, jobData?.name ?? 'default');
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: String(jobData.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: jobData.createdAt ?? Date.now(),
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(job, jobData?.name ?? 'default');
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: jobData?.name ?? 'default',
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: Date.now(),
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
  }