groupmq-plus 1.1.0 → 1.1.2
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/index.cjs +218 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -9
- package/dist/index.d.ts +62 -9
- package/dist/index.js +218 -10
- package/dist/index.js.map +1 -1
- package/dist/lua/change-delay.lua +20 -13
- package/dist/lua/clean-status.lua +13 -4
- package/dist/lua/complete-and-reserve-next-with-metadata.lua +8 -0
- package/dist/lua/complete-with-metadata.lua +8 -0
- package/dist/lua/enqueue-flow.lua +4 -0
- package/dist/lua/record-job-result.lua +10 -0
- package/dist/lua/remove.lua +13 -3
- package/dist/lua/reserve.lua +2 -2
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -37,6 +37,7 @@ declare class Job<T = any> {
|
|
|
37
37
|
readonly timestamp: number;
|
|
38
38
|
readonly orderMs?: number;
|
|
39
39
|
readonly status: Status$1 | 'unknown';
|
|
40
|
+
readonly parentId?: string;
|
|
40
41
|
constructor(args: {
|
|
41
42
|
queue: Queue<T>;
|
|
42
43
|
id: string;
|
|
@@ -56,6 +57,7 @@ declare class Job<T = any> {
|
|
|
56
57
|
timestamp: number;
|
|
57
58
|
orderMs?: number;
|
|
58
59
|
status?: Status$1 | 'unknown';
|
|
60
|
+
parentId?: string;
|
|
59
61
|
});
|
|
60
62
|
getState(): Promise<Status$1 | 'stuck' | 'waiting-children' | 'prioritized' | 'unknown'>;
|
|
61
63
|
toJSON(): {
|
|
@@ -84,6 +86,31 @@ declare class Job<T = any> {
|
|
|
84
86
|
retry(_state?: Extract<Status$1, 'completed' | 'failed'>): Promise<void>;
|
|
85
87
|
updateData(jobData: T): Promise<void>;
|
|
86
88
|
update(jobData: T): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Wait until this job is completed or failed.
|
|
91
|
+
* @param timeoutMs Optional timeout in milliseconds (0 = no timeout)
|
|
92
|
+
*/
|
|
93
|
+
waitUntilFinished(timeoutMs?: number): Promise<unknown>;
|
|
94
|
+
/**
|
|
95
|
+
* Get all child jobs of this job (if it's a parent in a flow).
|
|
96
|
+
* @returns Array of child Job instances
|
|
97
|
+
*/
|
|
98
|
+
getChildren(): Promise<Job<any>[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Get the return values of all child jobs in a flow.
|
|
101
|
+
* @returns Object mapping child job IDs to their return values
|
|
102
|
+
*/
|
|
103
|
+
getChildrenValues(): Promise<Record<string, any>>;
|
|
104
|
+
/**
|
|
105
|
+
* Get the number of remaining child jobs that haven't completed yet.
|
|
106
|
+
* @returns Number of remaining dependencies, or null if not a parent job
|
|
107
|
+
*/
|
|
108
|
+
getDependenciesCount(): Promise<number | null>;
|
|
109
|
+
/**
|
|
110
|
+
* Get the parent job of this job (if it's a child in a flow).
|
|
111
|
+
* @returns Parent Job instance, or undefined if no parent or parent was deleted
|
|
112
|
+
*/
|
|
113
|
+
getParent(): Promise<Job<any> | undefined>;
|
|
87
114
|
static fromReserved<T = any>(queue: Queue<T>, reserved: ReservedJob<T>, meta?: {
|
|
88
115
|
processedOn?: number;
|
|
89
116
|
finishedOn?: number;
|
|
@@ -493,6 +520,9 @@ declare class Queue<T = any> {
|
|
|
493
520
|
orderingDelayMs: number;
|
|
494
521
|
name: string;
|
|
495
522
|
private _consecutiveEmptyReserves;
|
|
523
|
+
private subscriber?;
|
|
524
|
+
private eventsSubscribed;
|
|
525
|
+
private waitingJobs;
|
|
496
526
|
private promoterRedis?;
|
|
497
527
|
private promoterRunning;
|
|
498
528
|
private promoterLockId?;
|
|
@@ -529,6 +559,18 @@ declare class Queue<T = any> {
|
|
|
529
559
|
* @returns An object mapping child job IDs to their results
|
|
530
560
|
*/
|
|
531
561
|
getFlowResults(parentId: string): Promise<Record<string, any>>;
|
|
562
|
+
/**
|
|
563
|
+
* Gets all child job IDs for a parent job in a flow.
|
|
564
|
+
* @param parentId The ID of the parent job
|
|
565
|
+
* @returns An array of child job IDs
|
|
566
|
+
*/
|
|
567
|
+
getFlowChildrenIds(parentId: string): Promise<string[]>;
|
|
568
|
+
/**
|
|
569
|
+
* Gets all child jobs for a parent job in a flow.
|
|
570
|
+
* @param parentId The ID of the parent job
|
|
571
|
+
* @returns An array of Job instances for all children
|
|
572
|
+
*/
|
|
573
|
+
getFlowChildren(parentId: string): Promise<Job<any>[]>;
|
|
532
574
|
private addSingle;
|
|
533
575
|
private flushBatch;
|
|
534
576
|
reserve(): Promise<ReservedJob<T> | null>;
|
|
@@ -765,6 +807,12 @@ declare class Queue<T = any> {
|
|
|
765
807
|
* Attempts to mimic BullMQ's Job shape for fields commonly used by BullBoard.
|
|
766
808
|
*/
|
|
767
809
|
getJob(id: string): Promise<Job<T>>;
|
|
810
|
+
private setupSubscriber;
|
|
811
|
+
private handleJobEvent;
|
|
812
|
+
/**
|
|
813
|
+
* Wait for a job to complete or fail, similar to BullMQ's waitUntilFinished.
|
|
814
|
+
*/
|
|
815
|
+
waitUntilFinished(jobId: string, timeoutMs?: number): Promise<unknown>;
|
|
768
816
|
/**
|
|
769
817
|
* Fetch jobs by statuses, emulating BullMQ's Queue.getJobs API used by BullBoard.
|
|
770
818
|
* Only getter functionality; ordering is best-effort.
|
|
@@ -917,7 +965,10 @@ interface DispatchStrategy {
|
|
|
917
965
|
}
|
|
918
966
|
//#endregion
|
|
919
967
|
//#region src/worker.d.ts
|
|
920
|
-
|
|
968
|
+
declare class UnrecoverableError extends Error {
|
|
969
|
+
constructor(message?: string);
|
|
970
|
+
}
|
|
971
|
+
type BackoffStrategy = (attempt: number, error: unknown) => number;
|
|
921
972
|
interface WorkerEvents<T = any> extends Record<string, (...args: any[]) => void> {
|
|
922
973
|
error: (error: Error) => void;
|
|
923
974
|
closed: () => void;
|
|
@@ -950,10 +1001,10 @@ type WorkerOptions<T> = {
|
|
|
950
1001
|
name?: string;
|
|
951
1002
|
/**
|
|
952
1003
|
* The function that processes jobs. Must be async and handle job failures gracefully.
|
|
953
|
-
* @param job The
|
|
1004
|
+
* @param job The Job instance to process, with access to all Job methods
|
|
954
1005
|
* @returns Promise that resolves when job is complete
|
|
955
1006
|
*/
|
|
956
|
-
handler: (job:
|
|
1007
|
+
handler: (job: Job<T>) => Promise<unknown>;
|
|
957
1008
|
/**
|
|
958
1009
|
* Heartbeat interval in milliseconds to keep jobs alive during processing.
|
|
959
1010
|
* Prevents jobs from timing out during long-running operations.
|
|
@@ -970,9 +1021,9 @@ type WorkerOptions<T> = {
|
|
|
970
1021
|
/**
|
|
971
1022
|
* Error handler called when job processing fails or worker encounters errors
|
|
972
1023
|
* @param err The error that occurred
|
|
973
|
-
* @param job The
|
|
1024
|
+
* @param job The Job instance that failed (if applicable)
|
|
974
1025
|
*/
|
|
975
|
-
onError?: (err: unknown, job?:
|
|
1026
|
+
onError?: (err: unknown, job?: Job<T>) => void;
|
|
976
1027
|
/**
|
|
977
1028
|
* Maximum number of retry attempts for failed jobs at the worker level.
|
|
978
1029
|
* This overrides the queue's default maxAttempts setting.
|
|
@@ -988,9 +1039,11 @@ type WorkerOptions<T> = {
|
|
|
988
1039
|
maxAttempts?: number;
|
|
989
1040
|
/**
|
|
990
1041
|
* Backoff strategy for retrying failed jobs. Determines delay between retries.
|
|
1042
|
+
* Receives the error object to allow smarter strategies.
|
|
991
1043
|
*
|
|
992
1044
|
* @default Exponential backoff with jitter (500ms, 1s, 2s, 4s, 8s, 16s, 30s max)
|
|
993
|
-
|
|
1045
|
+
* @example (attempt, err) =>
|
|
1046
|
+
* err instanceof RateLimitError ? err.retryAfterMs : Math.min(10000, attempt * 1000)
|
|
994
1047
|
*
|
|
995
1048
|
* **When to adjust:**
|
|
996
1049
|
* - Rate-limited APIs: Use longer delays
|
|
@@ -1237,14 +1290,14 @@ declare class _Worker<T = any> extends TypedEventEmitter<WorkerEvents<T>> {
|
|
|
1237
1290
|
* For concurrency > 1, returns the oldest job in progress
|
|
1238
1291
|
*/
|
|
1239
1292
|
getCurrentJob(): {
|
|
1240
|
-
job:
|
|
1293
|
+
job: Job<T>;
|
|
1241
1294
|
processingTimeMs: number;
|
|
1242
1295
|
} | null;
|
|
1243
1296
|
/**
|
|
1244
1297
|
* Get information about all currently processing jobs
|
|
1245
1298
|
*/
|
|
1246
1299
|
getCurrentJobs(): Array<{
|
|
1247
|
-
job:
|
|
1300
|
+
job: Job<T>;
|
|
1248
1301
|
processingTimeMs: number;
|
|
1249
1302
|
}>;
|
|
1250
1303
|
/**
|
|
@@ -1296,5 +1349,5 @@ declare function getWorkersStatus<T = any>(workers: Worker<T>[]): {
|
|
|
1296
1349
|
}>;
|
|
1297
1350
|
};
|
|
1298
1351
|
//#endregion
|
|
1299
|
-
export { AddOptions, BackoffStrategy, BullBoardGroupMQAdapter, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, Queue, QueueOptions, RepeatOptions, ReservedJob, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1352
|
+
export { AddOptions, BackoffStrategy, BullBoardGroupMQAdapter, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, Queue, QueueOptions, RepeatOptions, ReservedJob, UnrecoverableError, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1300
1353
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ declare class Job<T = any> {
|
|
|
37
37
|
readonly timestamp: number;
|
|
38
38
|
readonly orderMs?: number;
|
|
39
39
|
readonly status: Status$1 | 'unknown';
|
|
40
|
+
readonly parentId?: string;
|
|
40
41
|
constructor(args: {
|
|
41
42
|
queue: Queue<T>;
|
|
42
43
|
id: string;
|
|
@@ -56,6 +57,7 @@ declare class Job<T = any> {
|
|
|
56
57
|
timestamp: number;
|
|
57
58
|
orderMs?: number;
|
|
58
59
|
status?: Status$1 | 'unknown';
|
|
60
|
+
parentId?: string;
|
|
59
61
|
});
|
|
60
62
|
getState(): Promise<Status$1 | 'stuck' | 'waiting-children' | 'prioritized' | 'unknown'>;
|
|
61
63
|
toJSON(): {
|
|
@@ -84,6 +86,31 @@ declare class Job<T = any> {
|
|
|
84
86
|
retry(_state?: Extract<Status$1, 'completed' | 'failed'>): Promise<void>;
|
|
85
87
|
updateData(jobData: T): Promise<void>;
|
|
86
88
|
update(jobData: T): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Wait until this job is completed or failed.
|
|
91
|
+
* @param timeoutMs Optional timeout in milliseconds (0 = no timeout)
|
|
92
|
+
*/
|
|
93
|
+
waitUntilFinished(timeoutMs?: number): Promise<unknown>;
|
|
94
|
+
/**
|
|
95
|
+
* Get all child jobs of this job (if it's a parent in a flow).
|
|
96
|
+
* @returns Array of child Job instances
|
|
97
|
+
*/
|
|
98
|
+
getChildren(): Promise<Job<any>[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Get the return values of all child jobs in a flow.
|
|
101
|
+
* @returns Object mapping child job IDs to their return values
|
|
102
|
+
*/
|
|
103
|
+
getChildrenValues(): Promise<Record<string, any>>;
|
|
104
|
+
/**
|
|
105
|
+
* Get the number of remaining child jobs that haven't completed yet.
|
|
106
|
+
* @returns Number of remaining dependencies, or null if not a parent job
|
|
107
|
+
*/
|
|
108
|
+
getDependenciesCount(): Promise<number | null>;
|
|
109
|
+
/**
|
|
110
|
+
* Get the parent job of this job (if it's a child in a flow).
|
|
111
|
+
* @returns Parent Job instance, or undefined if no parent or parent was deleted
|
|
112
|
+
*/
|
|
113
|
+
getParent(): Promise<Job<any> | undefined>;
|
|
87
114
|
static fromReserved<T = any>(queue: Queue<T>, reserved: ReservedJob<T>, meta?: {
|
|
88
115
|
processedOn?: number;
|
|
89
116
|
finishedOn?: number;
|
|
@@ -493,6 +520,9 @@ declare class Queue<T = any> {
|
|
|
493
520
|
orderingDelayMs: number;
|
|
494
521
|
name: string;
|
|
495
522
|
private _consecutiveEmptyReserves;
|
|
523
|
+
private subscriber?;
|
|
524
|
+
private eventsSubscribed;
|
|
525
|
+
private waitingJobs;
|
|
496
526
|
private promoterRedis?;
|
|
497
527
|
private promoterRunning;
|
|
498
528
|
private promoterLockId?;
|
|
@@ -529,6 +559,18 @@ declare class Queue<T = any> {
|
|
|
529
559
|
* @returns An object mapping child job IDs to their results
|
|
530
560
|
*/
|
|
531
561
|
getFlowResults(parentId: string): Promise<Record<string, any>>;
|
|
562
|
+
/**
|
|
563
|
+
* Gets all child job IDs for a parent job in a flow.
|
|
564
|
+
* @param parentId The ID of the parent job
|
|
565
|
+
* @returns An array of child job IDs
|
|
566
|
+
*/
|
|
567
|
+
getFlowChildrenIds(parentId: string): Promise<string[]>;
|
|
568
|
+
/**
|
|
569
|
+
* Gets all child jobs for a parent job in a flow.
|
|
570
|
+
* @param parentId The ID of the parent job
|
|
571
|
+
* @returns An array of Job instances for all children
|
|
572
|
+
*/
|
|
573
|
+
getFlowChildren(parentId: string): Promise<Job<any>[]>;
|
|
532
574
|
private addSingle;
|
|
533
575
|
private flushBatch;
|
|
534
576
|
reserve(): Promise<ReservedJob<T> | null>;
|
|
@@ -765,6 +807,12 @@ declare class Queue<T = any> {
|
|
|
765
807
|
* Attempts to mimic BullMQ's Job shape for fields commonly used by BullBoard.
|
|
766
808
|
*/
|
|
767
809
|
getJob(id: string): Promise<Job<T>>;
|
|
810
|
+
private setupSubscriber;
|
|
811
|
+
private handleJobEvent;
|
|
812
|
+
/**
|
|
813
|
+
* Wait for a job to complete or fail, similar to BullMQ's waitUntilFinished.
|
|
814
|
+
*/
|
|
815
|
+
waitUntilFinished(jobId: string, timeoutMs?: number): Promise<unknown>;
|
|
768
816
|
/**
|
|
769
817
|
* Fetch jobs by statuses, emulating BullMQ's Queue.getJobs API used by BullBoard.
|
|
770
818
|
* Only getter functionality; ordering is best-effort.
|
|
@@ -917,7 +965,10 @@ interface DispatchStrategy {
|
|
|
917
965
|
}
|
|
918
966
|
//#endregion
|
|
919
967
|
//#region src/worker.d.ts
|
|
920
|
-
|
|
968
|
+
declare class UnrecoverableError extends Error {
|
|
969
|
+
constructor(message?: string);
|
|
970
|
+
}
|
|
971
|
+
type BackoffStrategy = (attempt: number, error: unknown) => number;
|
|
921
972
|
interface WorkerEvents<T = any> extends Record<string, (...args: any[]) => void> {
|
|
922
973
|
error: (error: Error) => void;
|
|
923
974
|
closed: () => void;
|
|
@@ -950,10 +1001,10 @@ type WorkerOptions<T> = {
|
|
|
950
1001
|
name?: string;
|
|
951
1002
|
/**
|
|
952
1003
|
* The function that processes jobs. Must be async and handle job failures gracefully.
|
|
953
|
-
* @param job The
|
|
1004
|
+
* @param job The Job instance to process, with access to all Job methods
|
|
954
1005
|
* @returns Promise that resolves when job is complete
|
|
955
1006
|
*/
|
|
956
|
-
handler: (job:
|
|
1007
|
+
handler: (job: Job<T>) => Promise<unknown>;
|
|
957
1008
|
/**
|
|
958
1009
|
* Heartbeat interval in milliseconds to keep jobs alive during processing.
|
|
959
1010
|
* Prevents jobs from timing out during long-running operations.
|
|
@@ -970,9 +1021,9 @@ type WorkerOptions<T> = {
|
|
|
970
1021
|
/**
|
|
971
1022
|
* Error handler called when job processing fails or worker encounters errors
|
|
972
1023
|
* @param err The error that occurred
|
|
973
|
-
* @param job The
|
|
1024
|
+
* @param job The Job instance that failed (if applicable)
|
|
974
1025
|
*/
|
|
975
|
-
onError?: (err: unknown, job?:
|
|
1026
|
+
onError?: (err: unknown, job?: Job<T>) => void;
|
|
976
1027
|
/**
|
|
977
1028
|
* Maximum number of retry attempts for failed jobs at the worker level.
|
|
978
1029
|
* This overrides the queue's default maxAttempts setting.
|
|
@@ -988,9 +1039,11 @@ type WorkerOptions<T> = {
|
|
|
988
1039
|
maxAttempts?: number;
|
|
989
1040
|
/**
|
|
990
1041
|
* Backoff strategy for retrying failed jobs. Determines delay between retries.
|
|
1042
|
+
* Receives the error object to allow smarter strategies.
|
|
991
1043
|
*
|
|
992
1044
|
* @default Exponential backoff with jitter (500ms, 1s, 2s, 4s, 8s, 16s, 30s max)
|
|
993
|
-
|
|
1045
|
+
* @example (attempt, err) =>
|
|
1046
|
+
* err instanceof RateLimitError ? err.retryAfterMs : Math.min(10000, attempt * 1000)
|
|
994
1047
|
*
|
|
995
1048
|
* **When to adjust:**
|
|
996
1049
|
* - Rate-limited APIs: Use longer delays
|
|
@@ -1237,14 +1290,14 @@ declare class _Worker<T = any> extends TypedEventEmitter<WorkerEvents<T>> {
|
|
|
1237
1290
|
* For concurrency > 1, returns the oldest job in progress
|
|
1238
1291
|
*/
|
|
1239
1292
|
getCurrentJob(): {
|
|
1240
|
-
job:
|
|
1293
|
+
job: Job<T>;
|
|
1241
1294
|
processingTimeMs: number;
|
|
1242
1295
|
} | null;
|
|
1243
1296
|
/**
|
|
1244
1297
|
* Get information about all currently processing jobs
|
|
1245
1298
|
*/
|
|
1246
1299
|
getCurrentJobs(): Array<{
|
|
1247
|
-
job:
|
|
1300
|
+
job: Job<T>;
|
|
1248
1301
|
processingTimeMs: number;
|
|
1249
1302
|
}>;
|
|
1250
1303
|
/**
|
|
@@ -1296,5 +1349,5 @@ declare function getWorkersStatus<T = any>(workers: Worker<T>[]): {
|
|
|
1296
1349
|
}>;
|
|
1297
1350
|
};
|
|
1298
1351
|
//#endregion
|
|
1299
|
-
export { AddOptions, BackoffStrategy, BullBoardGroupMQAdapter, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, Queue, QueueOptions, RepeatOptions, ReservedJob, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1352
|
+
export { AddOptions, BackoffStrategy, BullBoardGroupMQAdapter, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, Queue, QueueOptions, RepeatOptions, ReservedJob, UnrecoverableError, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1300
1353
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -235,6 +235,7 @@ var Job = class Job {
|
|
|
235
235
|
this.timestamp = args.timestamp;
|
|
236
236
|
this.orderMs = args.orderMs;
|
|
237
237
|
this.status = args.status ?? "unknown";
|
|
238
|
+
this.parentId = args.parentId;
|
|
238
239
|
}
|
|
239
240
|
async getState() {
|
|
240
241
|
return this.status ?? "unknown";
|
|
@@ -276,6 +277,46 @@ var Job = class Job {
|
|
|
276
277
|
async update(jobData) {
|
|
277
278
|
await this.updateData(jobData);
|
|
278
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Wait until this job is completed or failed.
|
|
282
|
+
* @param timeoutMs Optional timeout in milliseconds (0 = no timeout)
|
|
283
|
+
*/
|
|
284
|
+
async waitUntilFinished(timeoutMs = 0) {
|
|
285
|
+
return this.queue.waitUntilFinished(this.id, timeoutMs);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get all child jobs of this job (if it's a parent in a flow).
|
|
289
|
+
* @returns Array of child Job instances
|
|
290
|
+
*/
|
|
291
|
+
async getChildren() {
|
|
292
|
+
return this.queue.getFlowChildren(this.id);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get the return values of all child jobs in a flow.
|
|
296
|
+
* @returns Object mapping child job IDs to their return values
|
|
297
|
+
*/
|
|
298
|
+
async getChildrenValues() {
|
|
299
|
+
return this.queue.getFlowResults(this.id);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get the number of remaining child jobs that haven't completed yet.
|
|
303
|
+
* @returns Number of remaining dependencies, or null if not a parent job
|
|
304
|
+
*/
|
|
305
|
+
async getDependenciesCount() {
|
|
306
|
+
return this.queue.getFlowDependencies(this.id);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Get the parent job of this job (if it's a child in a flow).
|
|
310
|
+
* @returns Parent Job instance, or undefined if no parent or parent was deleted
|
|
311
|
+
*/
|
|
312
|
+
async getParent() {
|
|
313
|
+
if (!this.parentId) return void 0;
|
|
314
|
+
try {
|
|
315
|
+
return await this.queue.getJob(this.parentId);
|
|
316
|
+
} catch (_e) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
279
320
|
static fromReserved(queue, reserved, meta) {
|
|
280
321
|
return new Job({
|
|
281
322
|
queue,
|
|
@@ -315,6 +356,7 @@ var Job = class Job {
|
|
|
315
356
|
const failedReason = (raw.failedReason ?? raw.lastErrorMessage) || void 0;
|
|
316
357
|
const stacktrace = (raw.stacktrace ?? raw.lastErrorStack) || void 0;
|
|
317
358
|
const returnvalue = raw.returnvalue ? safeJsonParse$1(raw.returnvalue) : void 0;
|
|
359
|
+
const parentId = raw.parentId || void 0;
|
|
318
360
|
return new Job({
|
|
319
361
|
queue,
|
|
320
362
|
id,
|
|
@@ -333,7 +375,8 @@ var Job = class Job {
|
|
|
333
375
|
returnvalue,
|
|
334
376
|
timestamp: timestampMs || Date.now(),
|
|
335
377
|
orderMs,
|
|
336
|
-
status: knownStatus ?? coerceStatus(raw.status)
|
|
378
|
+
status: knownStatus ?? coerceStatus(raw.status),
|
|
379
|
+
parentId
|
|
337
380
|
});
|
|
338
381
|
}
|
|
339
382
|
static async fromStore(queue, id) {
|
|
@@ -352,6 +395,7 @@ var Job = class Job {
|
|
|
352
395
|
const failedReason = (raw.failedReason ?? raw.lastErrorMessage) || void 0;
|
|
353
396
|
const stacktrace = (raw.stacktrace ?? raw.lastErrorStack) || void 0;
|
|
354
397
|
const returnvalue = raw.returnvalue ? safeJsonParse$1(raw.returnvalue) : void 0;
|
|
398
|
+
const parentId = raw.parentId || void 0;
|
|
355
399
|
const [inProcessing, inDelayed] = await Promise.all([queue.redis.zscore(`${queue.namespace}:processing`, id), queue.redis.zscore(`${queue.namespace}:delayed`, id)]);
|
|
356
400
|
let status = raw.status;
|
|
357
401
|
if (inProcessing !== null) status = "active";
|
|
@@ -377,7 +421,8 @@ var Job = class Job {
|
|
|
377
421
|
returnvalue,
|
|
378
422
|
timestamp: timestampMs || Date.now(),
|
|
379
423
|
orderMs,
|
|
380
|
-
status: coerceStatus(status)
|
|
424
|
+
status: coerceStatus(status),
|
|
425
|
+
parentId
|
|
381
426
|
});
|
|
382
427
|
}
|
|
383
428
|
};
|
|
@@ -469,6 +514,8 @@ function safeJsonParse(input) {
|
|
|
469
514
|
var Queue = class {
|
|
470
515
|
constructor(opts) {
|
|
471
516
|
this._consecutiveEmptyReserves = 0;
|
|
517
|
+
this.eventsSubscribed = false;
|
|
518
|
+
this.waitingJobs = /* @__PURE__ */ new Map();
|
|
472
519
|
this.promoterRunning = false;
|
|
473
520
|
this.batchBuffer = [];
|
|
474
521
|
this.flushing = false;
|
|
@@ -623,6 +670,38 @@ var Queue = class {
|
|
|
623
670
|
}
|
|
624
671
|
return parsed;
|
|
625
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Gets all child job IDs for a parent job in a flow.
|
|
675
|
+
* @param parentId The ID of the parent job
|
|
676
|
+
* @returns An array of child job IDs
|
|
677
|
+
*/
|
|
678
|
+
async getFlowChildrenIds(parentId) {
|
|
679
|
+
return this.r.smembers(`${this.ns}:flow:children:${parentId}`);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Gets all child jobs for a parent job in a flow.
|
|
683
|
+
* @param parentId The ID of the parent job
|
|
684
|
+
* @returns An array of Job instances for all children
|
|
685
|
+
*/
|
|
686
|
+
async getFlowChildren(parentId) {
|
|
687
|
+
const ids = await this.getFlowChildrenIds(parentId);
|
|
688
|
+
if (ids.length === 0) return [];
|
|
689
|
+
const pipe = this.r.multi();
|
|
690
|
+
for (const id of ids) pipe.hgetall(`${this.ns}:job:${id}`);
|
|
691
|
+
const rows = await pipe.exec();
|
|
692
|
+
const jobs = [];
|
|
693
|
+
for (let i = 0; i < ids.length; i++) {
|
|
694
|
+
const id = ids[i];
|
|
695
|
+
const raw = rows?.[i]?.[1] || {};
|
|
696
|
+
if (!raw || Object.keys(raw).length === 0) {
|
|
697
|
+
this.logger.warn(`Skipping child job ${id} - not found (likely cleaned up)`);
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const job = Job.fromRawHash(this, id, raw);
|
|
701
|
+
jobs.push(job);
|
|
702
|
+
}
|
|
703
|
+
return jobs;
|
|
704
|
+
}
|
|
626
705
|
async addSingle(opts) {
|
|
627
706
|
const now = Date.now();
|
|
628
707
|
let delayUntil = 0;
|
|
@@ -1317,6 +1396,91 @@ var Queue = class {
|
|
|
1317
1396
|
async getJob(id) {
|
|
1318
1397
|
return Job.fromStore(this, id);
|
|
1319
1398
|
}
|
|
1399
|
+
async setupSubscriber() {
|
|
1400
|
+
if (this.eventsSubscribed && this.subscriber) return;
|
|
1401
|
+
if (!this.subscriber) {
|
|
1402
|
+
this.subscriber = this.r.duplicate();
|
|
1403
|
+
this.subscriber.on("message", (channel, message) => {
|
|
1404
|
+
if (channel === `${this.ns}:events`) this.handleJobEvent(message);
|
|
1405
|
+
});
|
|
1406
|
+
this.subscriber.on("error", (err) => {
|
|
1407
|
+
this.logger.error("Redis error (events subscriber):", err);
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
await this.subscriber.subscribe(`${this.ns}:events`);
|
|
1411
|
+
this.eventsSubscribed = true;
|
|
1412
|
+
}
|
|
1413
|
+
handleJobEvent(message) {
|
|
1414
|
+
try {
|
|
1415
|
+
const event = safeJsonParse(message);
|
|
1416
|
+
if (!event || typeof event.id !== "string") return;
|
|
1417
|
+
const waiters = this.waitingJobs.get(event.id);
|
|
1418
|
+
if (!waiters || waiters.length === 0) return;
|
|
1419
|
+
if (event.status === "completed") {
|
|
1420
|
+
const parsed = typeof event.result === "string" ? safeJsonParse(event.result) ?? event.result : event.result;
|
|
1421
|
+
waiters.forEach((w) => w.resolve(parsed));
|
|
1422
|
+
} else if (event.status === "failed") {
|
|
1423
|
+
const info = typeof event.result === "string" ? safeJsonParse(event.result) ?? {} : event.result ?? {};
|
|
1424
|
+
const err = new Error(info && info.message || "Job failed");
|
|
1425
|
+
if (info && typeof info === "object") {
|
|
1426
|
+
if (typeof info.name === "string") err.name = info.name;
|
|
1427
|
+
if (typeof info.stack === "string") err.stack = info.stack;
|
|
1428
|
+
}
|
|
1429
|
+
waiters.forEach((w) => w.reject(err));
|
|
1430
|
+
}
|
|
1431
|
+
this.waitingJobs.delete(event.id);
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
this.logger.error("Failed to process job event:", err);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Wait for a job to complete or fail, similar to BullMQ's waitUntilFinished.
|
|
1438
|
+
*/
|
|
1439
|
+
async waitUntilFinished(jobId, timeoutMs = 0) {
|
|
1440
|
+
const job = await this.getJob(jobId);
|
|
1441
|
+
const state = await job.getState();
|
|
1442
|
+
if (state === "completed") return job.returnvalue;
|
|
1443
|
+
if (state === "failed") throw new Error(job.failedReason || "Job failed");
|
|
1444
|
+
await this.setupSubscriber();
|
|
1445
|
+
return new Promise((resolve, reject) => {
|
|
1446
|
+
let timer;
|
|
1447
|
+
let waiter;
|
|
1448
|
+
const cleanup = () => {
|
|
1449
|
+
if (timer) clearTimeout(timer);
|
|
1450
|
+
const current = this.waitingJobs.get(jobId);
|
|
1451
|
+
if (!current) return;
|
|
1452
|
+
const remaining = current.filter((w) => w !== waiter);
|
|
1453
|
+
if (remaining.length === 0) this.waitingJobs.delete(jobId);
|
|
1454
|
+
else this.waitingJobs.set(jobId, remaining);
|
|
1455
|
+
};
|
|
1456
|
+
const wrappedResolve = (value) => {
|
|
1457
|
+
cleanup();
|
|
1458
|
+
resolve(value);
|
|
1459
|
+
};
|
|
1460
|
+
const wrappedReject = (err) => {
|
|
1461
|
+
cleanup();
|
|
1462
|
+
reject(err);
|
|
1463
|
+
};
|
|
1464
|
+
waiter = {
|
|
1465
|
+
resolve: wrappedResolve,
|
|
1466
|
+
reject: wrappedReject
|
|
1467
|
+
};
|
|
1468
|
+
const waiters = this.waitingJobs.get(jobId) ?? [];
|
|
1469
|
+
waiters.push(waiter);
|
|
1470
|
+
this.waitingJobs.set(jobId, waiters);
|
|
1471
|
+
if (timeoutMs > 0) timer = setTimeout(() => {
|
|
1472
|
+
wrappedReject(/* @__PURE__ */ new Error(`Timed out waiting for job ${jobId} to finish`));
|
|
1473
|
+
}, timeoutMs);
|
|
1474
|
+
(async () => {
|
|
1475
|
+
try {
|
|
1476
|
+
const latest = await this.getJob(jobId);
|
|
1477
|
+
const latestState = await latest.getState();
|
|
1478
|
+
if (latestState === "completed") wrappedResolve(latest.returnvalue);
|
|
1479
|
+
else if (latestState === "failed") wrappedReject(new Error(latest.failedReason ?? "Job failed"));
|
|
1480
|
+
} catch (_err) {}
|
|
1481
|
+
})();
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1320
1484
|
/**
|
|
1321
1485
|
* Fetch jobs by statuses, emulating BullMQ's Queue.getJobs API used by BullBoard.
|
|
1322
1486
|
* Only getter functionality; ordering is best-effort.
|
|
@@ -1511,6 +1675,25 @@ var Queue = class {
|
|
|
1511
1675
|
await this.flushBatch();
|
|
1512
1676
|
}
|
|
1513
1677
|
await this.stopPromoter();
|
|
1678
|
+
if (this.subscriber) {
|
|
1679
|
+
try {
|
|
1680
|
+
await this.subscriber.unsubscribe(`${this.ns}:events`);
|
|
1681
|
+
await this.subscriber.quit();
|
|
1682
|
+
} catch (_err) {
|
|
1683
|
+
try {
|
|
1684
|
+
this.subscriber.disconnect();
|
|
1685
|
+
} catch (_e) {}
|
|
1686
|
+
}
|
|
1687
|
+
this.subscriber = void 0;
|
|
1688
|
+
this.eventsSubscribed = false;
|
|
1689
|
+
}
|
|
1690
|
+
if (this.waitingJobs.size > 0) {
|
|
1691
|
+
const err = /* @__PURE__ */ new Error("Queue closed");
|
|
1692
|
+
this.waitingJobs.forEach((waiters) => {
|
|
1693
|
+
waiters.forEach((w) => w.reject(err));
|
|
1694
|
+
});
|
|
1695
|
+
this.waitingJobs.clear();
|
|
1696
|
+
}
|
|
1514
1697
|
try {
|
|
1515
1698
|
await this.r.quit();
|
|
1516
1699
|
} catch (_e) {
|
|
@@ -1938,6 +2121,12 @@ var AsyncFifoQueue = class {
|
|
|
1938
2121
|
|
|
1939
2122
|
//#endregion
|
|
1940
2123
|
//#region src/worker.ts
|
|
2124
|
+
var UnrecoverableError = class extends Error {
|
|
2125
|
+
constructor(message) {
|
|
2126
|
+
super(message);
|
|
2127
|
+
this.name = "UnrecoverableError";
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
1941
2130
|
var TypedEventEmitter = class {
|
|
1942
2131
|
constructor() {
|
|
1943
2132
|
this.listeners = /* @__PURE__ */ new Map();
|
|
@@ -1973,7 +2162,7 @@ var TypedEventEmitter = class {
|
|
|
1973
2162
|
return this;
|
|
1974
2163
|
}
|
|
1975
2164
|
};
|
|
1976
|
-
const defaultBackoff = (attempt) => {
|
|
2165
|
+
const defaultBackoff = (attempt, _error) => {
|
|
1977
2166
|
const base = Math.min(3e4, 2 ** (attempt - 1) * 500);
|
|
1978
2167
|
const jitter = Math.floor(base * .25 * Math.random());
|
|
1979
2168
|
return base + jitter;
|
|
@@ -2396,7 +2585,10 @@ var _Worker = class extends TypedEventEmitter {
|
|
|
2396
2585
|
const oldest = Array.from(this.jobsInProgress)[0];
|
|
2397
2586
|
const now = Date.now();
|
|
2398
2587
|
return {
|
|
2399
|
-
job: oldest.job,
|
|
2588
|
+
job: Job.fromReserved(this.q, oldest.job, {
|
|
2589
|
+
processedOn: oldest.ts,
|
|
2590
|
+
status: "active"
|
|
2591
|
+
}),
|
|
2400
2592
|
processingTimeMs: now - oldest.ts
|
|
2401
2593
|
};
|
|
2402
2594
|
}
|
|
@@ -2406,7 +2598,10 @@ var _Worker = class extends TypedEventEmitter {
|
|
|
2406
2598
|
getCurrentJobs() {
|
|
2407
2599
|
const now = Date.now();
|
|
2408
2600
|
return Array.from(this.jobsInProgress).map((item) => ({
|
|
2409
|
-
job: item.job,
|
|
2601
|
+
job: Job.fromReserved(this.q, item.job, {
|
|
2602
|
+
processedOn: item.ts,
|
|
2603
|
+
status: "active"
|
|
2604
|
+
}),
|
|
2410
2605
|
processingTimeMs: now - item.ts
|
|
2411
2606
|
}));
|
|
2412
2607
|
}
|
|
@@ -2436,7 +2631,7 @@ var _Worker = class extends TypedEventEmitter {
|
|
|
2436
2631
|
} catch (e) {
|
|
2437
2632
|
const isConnErr = this.q.isConnectionError(e);
|
|
2438
2633
|
if (!isConnErr || !this.stopping) this.logger.error(`Heartbeat error for job ${job.id}:`, e instanceof Error ? e.message : String(e));
|
|
2439
|
-
this.onError?.(e, job);
|
|
2634
|
+
this.onError?.(e, Job.fromReserved(this.q, job, { status: "active" }));
|
|
2440
2635
|
if (!isConnErr || !this.stopping) this.emit("error", e instanceof Error ? e : new Error(String(e)));
|
|
2441
2636
|
}
|
|
2442
2637
|
}, minInterval);
|
|
@@ -2447,7 +2642,11 @@ var _Worker = class extends TypedEventEmitter {
|
|
|
2447
2642
|
heartbeatDelayTimer = setTimeout(() => {
|
|
2448
2643
|
startHeartbeat();
|
|
2449
2644
|
}, heartbeatThreshold);
|
|
2450
|
-
const
|
|
2645
|
+
const jobInstance = Job.fromReserved(this.q, job, {
|
|
2646
|
+
processedOn: jobStartWallTime,
|
|
2647
|
+
status: "active"
|
|
2648
|
+
});
|
|
2649
|
+
const handlerResult = await this.handler(jobInstance);
|
|
2451
2650
|
if (heartbeatDelayTimer) clearTimeout(heartbeatDelayTimer);
|
|
2452
2651
|
if (hbTimer) clearInterval(hbTimer);
|
|
2453
2652
|
const finishedAtWall = Date.now();
|
|
@@ -2471,7 +2670,11 @@ var _Worker = class extends TypedEventEmitter {
|
|
|
2471
2670
|
* Handle job failure: emit events, retry or dead-letter
|
|
2472
2671
|
*/
|
|
2473
2672
|
async handleJobFailure(err, job, jobStartWallTime) {
|
|
2474
|
-
this.
|
|
2673
|
+
const jobInstance = Job.fromReserved(this.q, job, {
|
|
2674
|
+
processedOn: jobStartWallTime,
|
|
2675
|
+
status: "active"
|
|
2676
|
+
});
|
|
2677
|
+
this.onError?.(err, jobInstance);
|
|
2475
2678
|
this.blockingStats.consecutiveEmptyReserves = 0;
|
|
2476
2679
|
this.emptyReserveBackoffMs = 0;
|
|
2477
2680
|
try {
|
|
@@ -2486,7 +2689,12 @@ var _Worker = class extends TypedEventEmitter {
|
|
|
2486
2689
|
status: "failed"
|
|
2487
2690
|
}));
|
|
2488
2691
|
const nextAttempt = job.attempts + 1;
|
|
2489
|
-
|
|
2692
|
+
if (err instanceof UnrecoverableError) {
|
|
2693
|
+
this.logger.info(`Unrecoverable error for job ${job.id}: ${err instanceof Error ? err.message : String(err)}. Skipping retries.`);
|
|
2694
|
+
await this.deadLetterJob(err, job, jobStartWallTime, failedAt, nextAttempt);
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
const backoffMs = this.backoff(nextAttempt, err);
|
|
2490
2698
|
if (nextAttempt >= this.maxAttempts) {
|
|
2491
2699
|
await this.deadLetterJob(err, job, jobStartWallTime, failedAt, nextAttempt);
|
|
2492
2700
|
return;
|
|
@@ -2553,5 +2761,5 @@ function sleep(ms) {
|
|
|
2553
2761
|
}
|
|
2554
2762
|
|
|
2555
2763
|
//#endregion
|
|
2556
|
-
export { BullBoardGroupMQAdapter, Job, Queue, Worker, getWorkersStatus, waitForQueueToEmpty };
|
|
2764
|
+
export { BullBoardGroupMQAdapter, Job, Queue, UnrecoverableError, Worker, getWorkersStatus, waitForQueueToEmpty };
|
|
2557
2765
|
//# sourceMappingURL=index.js.map
|