@elysiumjs/heracles 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,804 +0,0 @@
1
- // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { Job } from '../job';
16
- import type { JobDispatchOptions, QueueOptions } from '../queue';
17
- import type { Transport } from '../transport';
18
- import type { QueuedJob, QueueState, Worker, WorkerInfo } from '../worker';
19
-
20
- import { InteractsWithConsole } from '@elysiumjs/core';
21
- import { sort, uid } from 'radash';
22
-
23
- import { TransportMode } from '../transport';
24
- import { WorkerStatus } from '../worker';
25
-
26
- /**
27
- * Base class for workers that manages multiple job queues and processes them in parallel.
28
- * Each queue processes its jobs sequentially by default, but can be configured for concurrency.
29
- * @author Axel Nana <axel.nana@workbud.com>
30
- */
31
- export abstract class BaseWorker extends InteractsWithConsole implements Worker {
32
- /**
33
- * The worker ID.
34
- */
35
- readonly #id: string;
36
-
37
- /**
38
- * The queues managed by this worker.
39
- */
40
- protected queues: Map<string, QueueState> = new Map();
41
-
42
- /**
43
- * The default queue name.
44
- */
45
- protected defaultQueueName: string = 'default';
46
-
47
- /**
48
- * The default transport used by this worker.
49
- */
50
- protected defaultTransport?: Transport;
51
-
52
- /**
53
- * The current status of this worker.
54
- */
55
- protected workerStatus: WorkerStatus = WorkerStatus.IDLE;
56
-
57
- /**
58
- * When this worker was started.
59
- */
60
- protected startedAt: Date = new Date();
61
-
62
- /**
63
- * Timer for checking scheduled jobs.
64
- */
65
- private scheduledJobTimer?: NodeJS.Timeout;
66
-
67
- /**
68
- * Interval in milliseconds to check for scheduled jobs.
69
- */
70
- protected scheduledJobInterval: number = 1000;
71
-
72
- constructor(id?: string) {
73
- super();
74
-
75
- this.#id = id ?? uid(24);
76
-
77
- // Initialize the default queue
78
- this.createQueue({ name: this.defaultQueueName });
79
- }
80
-
81
- /**
82
- * Gets the worker ID.
83
- * @returns The worker ID.
84
- */
85
- public get id(): string {
86
- return this.#id;
87
- }
88
-
89
- /**
90
- * Gets the worker status.
91
- */
92
- public get status(): WorkerStatus {
93
- return this.workerStatus;
94
- }
95
-
96
- /**
97
- * Creates a new queue with the specified options.
98
- *
99
- * @param options The queue configuration options.
100
- * @returns The Worker instance for method chaining.
101
- */
102
- public async createQueue(options: QueueOptions): Promise<Worker> {
103
- if (this.queues.has(options.name)) {
104
- this.debug(`Queue with name '${options.name}' already exists`);
105
- return this;
106
- }
107
-
108
- const queueState: QueueState = {
109
- options: {
110
- ...options,
111
- concurrency: options.concurrency || 1,
112
- maxRetries: options.maxRetries || 0,
113
- retryDelay: options.retryDelay || 1000,
114
- pauseOnError: options.pauseOnError || false
115
- },
116
- jobs: [],
117
- activeJobs: new Map(),
118
- processing: 0,
119
- paused: false,
120
- draining: false,
121
- transport: options.transport
122
- ? new options.transport(TransportMode.CONSUMER, options.transportOptions)
123
- : undefined
124
- };
125
-
126
- this.queues.set(options.name, queueState);
127
- this.debug(`Created queue '${options.name}'`);
128
-
129
- // If the queue has its own transport, start it
130
- if (queueState.transport) {
131
- await queueState.transport.start();
132
- }
133
-
134
- return this;
135
- }
136
-
137
- /**
138
- * Adds a job to the specified queue or the default queue if not specified.
139
- *
140
- * @param job The job to add to the queue.
141
- * @param queueName Optional name of the queue to add the job to. Defaults to the default queue.
142
- * @param options Additional options for job scheduling
143
- * @returns The Worker instance for method chaining.
144
- * @throws Error if the specified queue does not exist.
145
- */
146
- public async addJob(
147
- job: Job,
148
- queueName: string = this.defaultQueueName,
149
- options: JobDispatchOptions = {}
150
- ): Promise<Worker> {
151
- const queue = this.queues.get(queueName);
152
-
153
- if (!queue) {
154
- this.error(`Queue '${queueName}' does not exist`);
155
- throw new Error(`Queue '${queueName}' does not exist`);
156
- }
157
-
158
- if (queue.draining) {
159
- this.warning(`Queue '${queueName}' is draining, job ${job.id} will not be added`);
160
- throw new Error(`Queue '${queueName}' is draining`);
161
- }
162
-
163
- // Set job's queue name
164
- job.queueName = queueName;
165
-
166
- const queuedJob: QueuedJob = {
167
- job,
168
- retries: job.retries,
169
- queueName,
170
- enqueuedAt: new Date(),
171
- scheduledFor: options.scheduledFor,
172
- priority: options.priority ?? 10, // Default priority (lower = higher priority)
173
- maxRetries: options.maxRetries,
174
- retryDelay: options.retryDelay,
175
- overlapBehavior: options.overlapBehavior,
176
- overlapDelay: options.overlapDelay
177
- };
178
-
179
- // Add job to queue
180
- queue.jobs.push(queuedJob);
181
-
182
- // Sort jobs by priority, then by scheduled time, then by enqueued time
183
- queue.jobs = sort(
184
- sort(
185
- sort(queue.jobs, (job) => job.enqueuedAt.getTime()),
186
- (job) => (job.scheduledFor ? job.scheduledFor.getTime() : 0)
187
- ),
188
- (job) => job.priority ?? 10
189
- );
190
-
191
- this.debug(`Added job ${job.id} to queue '${queueName}'`);
192
-
193
- // Update worker status if needed
194
- if (this.workerStatus === WorkerStatus.IDLE) {
195
- this.workerStatus = WorkerStatus.ACTIVE;
196
- }
197
-
198
- // Start processing if the queue is not paused and not already at max concurrency
199
- if (!queue.paused && queue.processing < queue.options.concurrency!) {
200
- try {
201
- this.processQueueJobs(queueName);
202
- } catch (error) {
203
- this.error(`Error processing next job in queue '${queueName}': ${error}`);
204
- }
205
- }
206
-
207
- return this;
208
- }
209
-
210
- /**
211
- * Pauses a queue, preventing it from processing new jobs.
212
- *
213
- * @param queueName The name of the queue to pause. If not specified, pauses all queues.
214
- * @returns The Worker instance for method chaining.
215
- */
216
- public async pause(queueName?: string): Promise<Worker> {
217
- if (queueName) {
218
- const queue = this.queues.get(queueName);
219
- if (queue) {
220
- queue.paused = true;
221
- this.info(`Paused queue '${queueName}'`);
222
- } else {
223
- this.warning(`Cannot pause non-existent queue '${queueName}'`);
224
- }
225
- } else {
226
- for (const name of this.queues.keys()) {
227
- await this.pause(name);
228
- }
229
-
230
- // If all queues are paused and no jobs are processing, set worker status to PAUSED
231
- if (
232
- Array.from(this.queues.values()).every((q) => q.paused) &&
233
- Array.from(this.queues.values()).every((q) => q.processing === 0)
234
- ) {
235
- this.workerStatus = WorkerStatus.PAUSED;
236
- this.info(`Worker ${this.id} is now paused`);
237
- }
238
- }
239
- return this;
240
- }
241
-
242
- /**
243
- * Resumes a paused queue.
244
- *
245
- * @param queueName The name of the queue to resume. If not specified, resumes all queues.
246
- * @returns The Worker instance for method chaining.
247
- */
248
- public async resume(queueName?: string): Promise<Worker> {
249
- if (queueName) {
250
- const queue = this.queues.get(queueName);
251
- if (queue) {
252
- queue.paused = false;
253
- this.info(`Resumed queue '${queueName}'`);
254
-
255
- // Set worker status to ACTIVE if it was PAUSED
256
- if (this.workerStatus === WorkerStatus.PAUSED) {
257
- this.workerStatus = WorkerStatus.ACTIVE;
258
- }
259
-
260
- // Process jobs in queue
261
- await this.processQueueJobs(queueName);
262
- } else {
263
- this.warning(`Cannot resume non-existent queue '${queueName}'`);
264
- }
265
- } else {
266
- for (const name of this.queues.keys()) {
267
- await this.resume(name);
268
- }
269
-
270
- // If any queue has jobs, worker should be ACTIVE
271
- if (Array.from(this.queues.values()).some((q) => q.jobs.length > 0 || q.processing > 0)) {
272
- this.workerStatus = WorkerStatus.ACTIVE;
273
- this.info(`Worker ${this.id} is now active`);
274
- }
275
- }
276
- return this;
277
- }
278
-
279
- /**
280
- * Drains a queue, completing all current jobs but not accepting new ones.
281
- *
282
- * @param queueName The name of the queue to drain. If not specified, drains all queues.
283
- * @returns The Worker instance for method chaining.
284
- */
285
- public async drain(queueName?: string): Promise<Worker> {
286
- if (queueName) {
287
- const queue = this.queues.get(queueName);
288
- if (queue) {
289
- queue.draining = true;
290
- this.info(`Draining queue '${queueName}'`);
291
- } else {
292
- this.warning(`Cannot drain non-existent queue '${queueName}'`);
293
- }
294
- } else {
295
- // Set all queues to draining
296
- for (const name of this.queues.keys()) {
297
- await this.drain(name);
298
- }
299
-
300
- // Update worker status
301
- this.workerStatus = WorkerStatus.DRAINING;
302
- this.info(`Worker ${this.id} is now draining`);
303
- }
304
- return this;
305
- }
306
-
307
- /**
308
- * Clears all jobs from a queue.
309
- *
310
- * @param queueName The name of the queue to clear. If not specified, clears all queues.
311
- * @returns The Worker instance for method chaining.
312
- */
313
- public async clear(queueName?: string): Promise<Worker> {
314
- if (queueName) {
315
- const queue = this.queues.get(queueName);
316
- if (queue) {
317
- const jobCount = queue.jobs.length;
318
-
319
- // Cancel all jobs in the queue
320
- for (const queuedJob of queue.jobs) {
321
- queuedJob.job.cancel();
322
- }
323
-
324
- queue.jobs = [];
325
- this.info(`Cleared ${jobCount} jobs from queue '${queueName}'`);
326
- } else {
327
- this.warning(`Cannot clear non-existent queue '${queueName}'`);
328
- }
329
- } else {
330
- for (const [name, queue] of this.queues.entries()) {
331
- const jobCount = queue.jobs.length;
332
-
333
- // Cancel all jobs in the queue
334
- for (const queuedJob of queue.jobs) {
335
- queuedJob.job.cancel();
336
- }
337
-
338
- queue.jobs = [];
339
- this.info(`Cleared ${jobCount} jobs from queue '${name}'`);
340
- }
341
-
342
- // If all queues are empty and no jobs are processing, worker is IDLE
343
- if (
344
- Array.from(this.queues.values()).every((q) => q.jobs.length === 0 && q.processing === 0)
345
- ) {
346
- this.workerStatus = WorkerStatus.IDLE;
347
- }
348
- }
349
- return this;
350
- }
351
-
352
- /**
353
- * Gets the number of jobs in a queue.
354
- *
355
- * @param queueName The name of the queue to get the size of.
356
- * @returns The number of jobs in the queue.
357
- * @throws Error if the specified queue does not exist.
358
- */
359
- public async size(queueName: string = this.defaultQueueName): Promise<number> {
360
- const queue = this.queues.get(queueName);
361
- if (!queue) {
362
- throw new Error(`Queue '${queueName}' does not exist`);
363
- }
364
- return queue.jobs.length + queue.processing;
365
- }
366
-
367
- /**
368
- * Gets the total number of jobs across all queues.
369
- *
370
- * @returns The total number of jobs.
371
- */
372
- public async totalSize(): Promise<number> {
373
- let total = 0;
374
- for (const queue of this.queues.values()) {
375
- total += queue.jobs.length + queue.processing;
376
- }
377
- return total;
378
- }
379
-
380
- /**
381
- * Gets statistics about all queues.
382
- *
383
- * @returns An object containing statistics about each queue.
384
- */
385
- public async getStats(): Promise<
386
- Record<string, { waiting: number; processing: number; paused: boolean; draining: boolean }>
387
- > {
388
- const stats: Record<
389
- string,
390
- { waiting: number; processing: number; paused: boolean; draining: boolean }
391
- > = {};
392
-
393
- for (const [name, queue] of this.queues.entries()) {
394
- stats[name] = {
395
- waiting: queue.jobs.length,
396
- processing: queue.processing,
397
- paused: queue.paused,
398
- draining: queue.draining
399
- };
400
- }
401
-
402
- return stats;
403
- }
404
-
405
- /**
406
- * Gets information about this worker for the worker pool.
407
- *
408
- * @returns Worker information object
409
- */
410
- public getInfo(): WorkerInfo {
411
- // Calculate total active jobs
412
- let activeJobs = 0;
413
- for (const queue of this.queues.values()) {
414
- activeJobs += queue.processing;
415
- }
416
-
417
- // Get queue names
418
- const queueNames = Array.from(this.queues.keys());
419
-
420
- // Determine transport type
421
- const transportType = this.defaultTransport ? this.defaultTransport.constructor.name : 'None';
422
-
423
- // Create a wrapper for addJob that returns boolean instead of Worker
424
- const addJobWrapper = async (job: Job, queueName?: string): Promise<boolean> => {
425
- try {
426
- await this.addJob(job, queueName);
427
- return true;
428
- } catch (error) {
429
- return false;
430
- }
431
- };
432
-
433
- return {
434
- id: this.id,
435
- status: this.workerStatus,
436
- queues: queueNames,
437
- activeJobs,
438
- transport: transportType,
439
- startedAt: this.startedAt,
440
- addJob: addJobWrapper,
441
- cancelJob: this.cancelJob.bind(this),
442
- cancelAllJobs: this.cancelAllJobs.bind(this)
443
- };
444
- }
445
-
446
- /**
447
- * Cancels a job by its ID.
448
- *
449
- * @param jobId The ID of the job to cancel.
450
- * @param queueName Optional queue name to search in. If not provided, searches all queues.
451
- * @returns `true` if the job was found and canceled, false otherwise.
452
- */
453
- public async cancelJob(jobId: string, queueName?: string): Promise<boolean> {
454
- if (queueName) {
455
- return this.cancelJobInQueue(jobId, queueName);
456
- } else {
457
- for (const name of this.queues.keys()) {
458
- if (await this.cancelJobInQueue(jobId, name)) {
459
- return true;
460
- }
461
- }
462
- return false;
463
- }
464
- }
465
-
466
- /**
467
- * Cancels all jobs in a queue.
468
- *
469
- * @param queueName The name of the queue to cancel jobs from. If not provided, cancels jobs in all queues.
470
- * @returns The number of jobs canceled.
471
- */
472
- public async cancelAllJobs(queueName?: string): Promise<number> {
473
- let canceledCount = 0;
474
- if (queueName) {
475
- const queue = this.queues.get(queueName);
476
- if (queue) {
477
- // Cancel waiting jobs
478
- canceledCount = queue.jobs.length;
479
- for (const queuedJob of queue.jobs) {
480
- queuedJob.job.cancel();
481
- }
482
-
483
- // Try to cancel running jobs
484
- for (const [_, activeJob] of queue.activeJobs.entries()) {
485
- if (activeJob.job.cancel()) {
486
- canceledCount++;
487
- }
488
- }
489
-
490
- this.info(`Canceled ${canceledCount} jobs in queue '${queueName}'`);
491
- }
492
- } else {
493
- for (const [name, queue] of this.queues.entries()) {
494
- // Cancel waiting jobs
495
- let count = queue.jobs.length;
496
- for (const queuedJob of queue.jobs) {
497
- queuedJob.job.cancel();
498
- }
499
-
500
- // Try to cancel running jobs
501
- for (const [_, activeJob] of queue.activeJobs.entries()) {
502
- if (activeJob.job.cancel()) {
503
- count++;
504
- }
505
- }
506
-
507
- canceledCount += count;
508
- this.info(`Canceled ${count} jobs in queue '${name}'`);
509
- }
510
- }
511
-
512
- // Update worker status if needed
513
- if (
514
- canceledCount > 0 &&
515
- Array.from(this.queues.values()).every((q) => q.jobs.length === 0 && q.activeJobs.size === 0)
516
- ) {
517
- this.workerStatus = WorkerStatus.IDLE;
518
- }
519
-
520
- return canceledCount;
521
- }
522
-
523
- /**
524
- * Get a job by its ID.
525
- *
526
- * @param jobId The ID of the job to get.
527
- * @param queueName Optional queue name to search in. If not provided, searches all queues.
528
- * @returns The job if found, null otherwise.
529
- */
530
- public async getJob(jobId: string, queueName?: string): Promise<Job | null> {
531
- if (queueName) {
532
- const queue = this.queues.get(queueName);
533
- if (queue) {
534
- // Check waiting jobs
535
- const queuedJob = queue.jobs.find((j) => j.job.id === jobId);
536
- if (queuedJob) {
537
- return queuedJob.job;
538
- }
539
-
540
- // Check active jobs
541
- const activeJob = queue.activeJobs.get(jobId);
542
- if (activeJob) {
543
- return activeJob.job;
544
- }
545
- }
546
- } else {
547
- for (const queue of this.queues.values()) {
548
- // Check waiting jobs
549
- const queuedJob = queue.jobs.find((j) => j.job.id === jobId);
550
- if (queuedJob) {
551
- return queuedJob.job;
552
- }
553
-
554
- // Check active jobs
555
- const activeJob = queue.activeJobs.get(jobId);
556
- if (activeJob) {
557
- return activeJob.job;
558
- }
559
- }
560
- }
561
-
562
- return null;
563
- }
564
-
565
- /**
566
- * Start the worker.
567
- */
568
- public async start(): Promise<void> {
569
- this.workerStatus = WorkerStatus.ACTIVE;
570
- this.startedAt = new Date();
571
-
572
- // Start the transport if available
573
- if (this.defaultTransport) {
574
- await this.defaultTransport.start();
575
- }
576
-
577
- // Start processing jobs in all queues
578
- for (const queueName of this.queues.keys()) {
579
- await this.processQueueJobs(queueName);
580
- }
581
-
582
- // Start timer for scheduled jobs
583
- this.startScheduledJobTimer();
584
-
585
- this.info(`Worker ${this.id} started`);
586
- }
587
-
588
- /**
589
- * Stop the worker.
590
- *
591
- * @param force If true, cancel all running jobs. If false, allow them to complete.
592
- */
593
- public async stop(force: boolean = false): Promise<void> {
594
- this.workerStatus = WorkerStatus.STOPPING;
595
-
596
- // Stop scheduled job timer
597
- this.stopScheduledJobTimer();
598
-
599
- if (force) {
600
- // Cancel all jobs
601
- await this.cancelAllJobs();
602
-
603
- // Clear all queues
604
- for (const queueName of this.queues.keys()) {
605
- await this.clear(queueName);
606
- }
607
- } else {
608
- // Set all queues to draining
609
- for (const queueName of this.queues.keys()) {
610
- await this.drain(queueName);
611
- }
612
-
613
- // Wait for all queues to finish processing
614
- const allQueuesEmpty = (): boolean => {
615
- return Array.from(this.queues.values()).every((q) => q.processing === 0);
616
- };
617
-
618
- if (!allQueuesEmpty()) {
619
- this.info(
620
- `Worker ${this.id} waiting for ${Array.from(this.queues.values()).reduce((acc, q) => acc + q.processing, 0)} jobs to complete`
621
- );
622
-
623
- // Wait for queues to empty
624
- while (!allQueuesEmpty()) {
625
- await new Promise((resolve) => setTimeout(resolve, 100));
626
- }
627
- }
628
- }
629
-
630
- // Stop the transport if available
631
- if (this.defaultTransport) {
632
- await this.defaultTransport.stop();
633
- }
634
-
635
- // Stop any queue-specific transports
636
- for (const queue of this.queues.values()) {
637
- if (queue.transport) {
638
- await queue.transport.stop();
639
- }
640
- }
641
-
642
- this.workerStatus = WorkerStatus.STOPPED;
643
- this.info(`Worker ${this.id} stopped`);
644
- }
645
-
646
- /**
647
- * Set the concurrency for a queue.
648
- *
649
- * @param queueName The name of the queue.
650
- * @param concurrency The new concurrency value.
651
- */
652
- public async setConcurrency(queueName: string, concurrency: number): Promise<void> {
653
- const queue = this.queues.get(queueName);
654
-
655
- if (!queue) {
656
- this.error(`Cannot set concurrency: Queue '${queueName}' does not exist`);
657
- throw new Error(`Queue '${queueName}' does not exist`);
658
- }
659
-
660
- if (concurrency < 1) {
661
- this.warning(`Invalid concurrency value ${concurrency}, using 1 instead`);
662
- concurrency = 1;
663
- }
664
-
665
- queue.options.concurrency = concurrency;
666
- this.info(`Set concurrency for queue '${queueName}' to ${concurrency}`);
667
-
668
- // Process more jobs if we've increased concurrency
669
- return this.processQueueJobs(queueName);
670
- }
671
-
672
- /**
673
- * Start the timer for checking scheduled jobs.
674
- */
675
- private startScheduledJobTimer(): void {
676
- if (this.scheduledJobTimer) {
677
- clearInterval(this.scheduledJobTimer);
678
- }
679
-
680
- this.scheduledJobTimer = setInterval(() => {
681
- this.checkScheduledJobs();
682
- }, this.scheduledJobInterval);
683
- }
684
-
685
- /**
686
- * Stop the timer for checking scheduled jobs.
687
- */
688
- private stopScheduledJobTimer(): void {
689
- if (this.scheduledJobTimer) {
690
- clearInterval(this.scheduledJobTimer);
691
- this.scheduledJobTimer = undefined;
692
- }
693
- }
694
-
695
- /**
696
- * Check for jobs that are scheduled to run now.
697
- */
698
- private checkScheduledJobs(): void {
699
- const now = new Date();
700
-
701
- for (const [queueName, queue] of this.queues.entries()) {
702
- if (queue.paused || queue.draining) {
703
- continue;
704
- }
705
-
706
- // Look for scheduled jobs that are ready to run
707
- const readyJobs = queue.jobs.filter((job) => job.scheduledFor && job.scheduledFor <= now);
708
-
709
- // Remove the scheduledFor timestamp to make them eligible for processing
710
- for (const job of readyJobs) {
711
- job.scheduledFor = undefined;
712
- }
713
-
714
- // Start processing if queue is not at capacity
715
- if (readyJobs.length > 0 && queue.processing < queue.options.concurrency!) {
716
- this.processQueueJobs(queueName);
717
- }
718
- }
719
- }
720
-
721
- /**
722
- * Processes the next job in the specified queue.
723
- *
724
- * @param queueName The name of the queue to process.
725
- */
726
- protected abstract processNextJob(queueName: string): Promise<void>;
727
-
728
- /**
729
- * Processes jobs in a queue up to the concurrency limit.
730
- *
731
- * @param queueName The name of the queue to process.
732
- * @returns A promise that resolves when job processing has been initiated.
733
- */
734
- protected async processQueueJobs(queueName: string): Promise<void> {
735
- const queue = this.queues.get(queueName);
736
- if (!queue || queue.paused) {
737
- return;
738
- }
739
-
740
- // Start processing jobs up to the concurrency limit
741
- const promises: Promise<void>[] = [];
742
-
743
- while (promises.length < queue.options.concurrency!) {
744
- promises.push(this.processNextJob(queueName));
745
- }
746
-
747
- // Wait for all processing to start (not for jobs to complete)
748
- await Promise.all(promises);
749
-
750
- const states = Array.from(this.queues.values());
751
-
752
- // Update worker status
753
- if (states.some((q) => q.processing > 0)) {
754
- this.workerStatus = WorkerStatus.ACTIVE;
755
- } else if (states.every((q) => q.jobs.length === 0 && q.processing === 0)) {
756
- this.workerStatus = WorkerStatus.IDLE;
757
- }
758
-
759
- if (states.some((q) => q.jobs.length > 0)) {
760
- await Bun.sleep(10);
761
- setImmediate(() => this.processQueueJobs(queueName));
762
- }
763
- }
764
-
765
- /**
766
- * Cancels a job in a specific queue.
767
- *
768
- * @param jobId The ID of the job to cancel.
769
- * @param queueName The name of the queue to search in.
770
- * @returns `true` if the job was found and canceled, false otherwise.
771
- */
772
- private async cancelJobInQueue(jobId: string, queueName: string): Promise<boolean> {
773
- const queue = this.queues.get(queueName);
774
- if (!queue) {
775
- return false;
776
- }
777
-
778
- // Find the job in the waiting queue
779
- const jobIndex = queue.jobs.findIndex((job) => job.job.id === jobId);
780
- if (jobIndex >= 0) {
781
- // Mark the job as canceled
782
- const canceled = queue.jobs[jobIndex].job.cancel();
783
- if (canceled) {
784
- // Remove the job from the queue
785
- queue.jobs.splice(jobIndex, 1);
786
- this.info(`Canceled job ${jobId} in queue '${queueName}'`);
787
- return true;
788
- }
789
- }
790
-
791
- // Check active jobs
792
- if (queue.activeJobs.has(jobId)) {
793
- const activeJob = queue.activeJobs.get(jobId)!;
794
- const canceled = activeJob.job.cancel();
795
-
796
- if (canceled) {
797
- this.info(`Canceled running job ${jobId} in queue '${queueName}'`);
798
- return true;
799
- }
800
- }
801
-
802
- return false;
803
- }
804
- }