groupmq-plus 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +127 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -3
- package/dist/index.d.ts +22 -3
- package/dist/index.js +127 -3
- package/dist/index.js.map +1 -1
- package/dist/lua/complete-and-reserve-next-with-metadata.lua +8 -0
- package/dist/lua/complete-with-metadata.lua +8 -0
- package/dist/lua/record-job-result.lua +10 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -84,6 +84,11 @@ declare class Job<T = any> {
|
|
|
84
84
|
retry(_state?: Extract<Status$1, 'completed' | 'failed'>): Promise<void>;
|
|
85
85
|
updateData(jobData: T): Promise<void>;
|
|
86
86
|
update(jobData: T): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Wait until this job is completed or failed.
|
|
89
|
+
* @param timeoutMs Optional timeout in milliseconds (0 = no timeout)
|
|
90
|
+
*/
|
|
91
|
+
waitUntilFinished(timeoutMs?: number): Promise<unknown>;
|
|
87
92
|
static fromReserved<T = any>(queue: Queue<T>, reserved: ReservedJob<T>, meta?: {
|
|
88
93
|
processedOn?: number;
|
|
89
94
|
finishedOn?: number;
|
|
@@ -493,6 +498,9 @@ declare class Queue<T = any> {
|
|
|
493
498
|
orderingDelayMs: number;
|
|
494
499
|
name: string;
|
|
495
500
|
private _consecutiveEmptyReserves;
|
|
501
|
+
private subscriber?;
|
|
502
|
+
private eventsSubscribed;
|
|
503
|
+
private waitingJobs;
|
|
496
504
|
private promoterRedis?;
|
|
497
505
|
private promoterRunning;
|
|
498
506
|
private promoterLockId?;
|
|
@@ -765,6 +773,12 @@ declare class Queue<T = any> {
|
|
|
765
773
|
* Attempts to mimic BullMQ's Job shape for fields commonly used by BullBoard.
|
|
766
774
|
*/
|
|
767
775
|
getJob(id: string): Promise<Job<T>>;
|
|
776
|
+
private setupSubscriber;
|
|
777
|
+
private handleJobEvent;
|
|
778
|
+
/**
|
|
779
|
+
* Wait for a job to complete or fail, similar to BullMQ's waitUntilFinished.
|
|
780
|
+
*/
|
|
781
|
+
waitUntilFinished(jobId: string, timeoutMs?: number): Promise<unknown>;
|
|
768
782
|
/**
|
|
769
783
|
* Fetch jobs by statuses, emulating BullMQ's Queue.getJobs API used by BullBoard.
|
|
770
784
|
* Only getter functionality; ordering is best-effort.
|
|
@@ -917,7 +931,10 @@ interface DispatchStrategy {
|
|
|
917
931
|
}
|
|
918
932
|
//#endregion
|
|
919
933
|
//#region src/worker.d.ts
|
|
920
|
-
|
|
934
|
+
declare class UnrecoverableError extends Error {
|
|
935
|
+
constructor(message?: string);
|
|
936
|
+
}
|
|
937
|
+
type BackoffStrategy = (attempt: number, error: unknown) => number;
|
|
921
938
|
interface WorkerEvents<T = any> extends Record<string, (...args: any[]) => void> {
|
|
922
939
|
error: (error: Error) => void;
|
|
923
940
|
closed: () => void;
|
|
@@ -988,9 +1005,11 @@ type WorkerOptions<T> = {
|
|
|
988
1005
|
maxAttempts?: number;
|
|
989
1006
|
/**
|
|
990
1007
|
* Backoff strategy for retrying failed jobs. Determines delay between retries.
|
|
1008
|
+
* Receives the error object to allow smarter strategies.
|
|
991
1009
|
*
|
|
992
1010
|
* @default Exponential backoff with jitter (500ms, 1s, 2s, 4s, 8s, 16s, 30s max)
|
|
993
|
-
|
|
1011
|
+
* @example (attempt, err) =>
|
|
1012
|
+
* err instanceof RateLimitError ? err.retryAfterMs : Math.min(10000, attempt * 1000)
|
|
994
1013
|
*
|
|
995
1014
|
* **When to adjust:**
|
|
996
1015
|
* - Rate-limited APIs: Use longer delays
|
|
@@ -1296,5 +1315,5 @@ declare function getWorkersStatus<T = any>(workers: Worker<T>[]): {
|
|
|
1296
1315
|
}>;
|
|
1297
1316
|
};
|
|
1298
1317
|
//#endregion
|
|
1299
|
-
export { AddOptions, BackoffStrategy, BullBoardGroupMQAdapter, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, Queue, QueueOptions, RepeatOptions, ReservedJob, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1318
|
+
export { AddOptions, BackoffStrategy, BullBoardGroupMQAdapter, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, Queue, QueueOptions, RepeatOptions, ReservedJob, UnrecoverableError, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1300
1319
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,11 @@ declare class Job<T = any> {
|
|
|
84
84
|
retry(_state?: Extract<Status$1, 'completed' | 'failed'>): Promise<void>;
|
|
85
85
|
updateData(jobData: T): Promise<void>;
|
|
86
86
|
update(jobData: T): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Wait until this job is completed or failed.
|
|
89
|
+
* @param timeoutMs Optional timeout in milliseconds (0 = no timeout)
|
|
90
|
+
*/
|
|
91
|
+
waitUntilFinished(timeoutMs?: number): Promise<unknown>;
|
|
87
92
|
static fromReserved<T = any>(queue: Queue<T>, reserved: ReservedJob<T>, meta?: {
|
|
88
93
|
processedOn?: number;
|
|
89
94
|
finishedOn?: number;
|
|
@@ -493,6 +498,9 @@ declare class Queue<T = any> {
|
|
|
493
498
|
orderingDelayMs: number;
|
|
494
499
|
name: string;
|
|
495
500
|
private _consecutiveEmptyReserves;
|
|
501
|
+
private subscriber?;
|
|
502
|
+
private eventsSubscribed;
|
|
503
|
+
private waitingJobs;
|
|
496
504
|
private promoterRedis?;
|
|
497
505
|
private promoterRunning;
|
|
498
506
|
private promoterLockId?;
|
|
@@ -765,6 +773,12 @@ declare class Queue<T = any> {
|
|
|
765
773
|
* Attempts to mimic BullMQ's Job shape for fields commonly used by BullBoard.
|
|
766
774
|
*/
|
|
767
775
|
getJob(id: string): Promise<Job<T>>;
|
|
776
|
+
private setupSubscriber;
|
|
777
|
+
private handleJobEvent;
|
|
778
|
+
/**
|
|
779
|
+
* Wait for a job to complete or fail, similar to BullMQ's waitUntilFinished.
|
|
780
|
+
*/
|
|
781
|
+
waitUntilFinished(jobId: string, timeoutMs?: number): Promise<unknown>;
|
|
768
782
|
/**
|
|
769
783
|
* Fetch jobs by statuses, emulating BullMQ's Queue.getJobs API used by BullBoard.
|
|
770
784
|
* Only getter functionality; ordering is best-effort.
|
|
@@ -917,7 +931,10 @@ interface DispatchStrategy {
|
|
|
917
931
|
}
|
|
918
932
|
//#endregion
|
|
919
933
|
//#region src/worker.d.ts
|
|
920
|
-
|
|
934
|
+
declare class UnrecoverableError extends Error {
|
|
935
|
+
constructor(message?: string);
|
|
936
|
+
}
|
|
937
|
+
type BackoffStrategy = (attempt: number, error: unknown) => number;
|
|
921
938
|
interface WorkerEvents<T = any> extends Record<string, (...args: any[]) => void> {
|
|
922
939
|
error: (error: Error) => void;
|
|
923
940
|
closed: () => void;
|
|
@@ -988,9 +1005,11 @@ type WorkerOptions<T> = {
|
|
|
988
1005
|
maxAttempts?: number;
|
|
989
1006
|
/**
|
|
990
1007
|
* Backoff strategy for retrying failed jobs. Determines delay between retries.
|
|
1008
|
+
* Receives the error object to allow smarter strategies.
|
|
991
1009
|
*
|
|
992
1010
|
* @default Exponential backoff with jitter (500ms, 1s, 2s, 4s, 8s, 16s, 30s max)
|
|
993
|
-
|
|
1011
|
+
* @example (attempt, err) =>
|
|
1012
|
+
* err instanceof RateLimitError ? err.retryAfterMs : Math.min(10000, attempt * 1000)
|
|
994
1013
|
*
|
|
995
1014
|
* **When to adjust:**
|
|
996
1015
|
* - Rate-limited APIs: Use longer delays
|
|
@@ -1296,5 +1315,5 @@ declare function getWorkersStatus<T = any>(workers: Worker<T>[]): {
|
|
|
1296
1315
|
}>;
|
|
1297
1316
|
};
|
|
1298
1317
|
//#endregion
|
|
1299
|
-
export { AddOptions, BackoffStrategy, BullBoardGroupMQAdapter, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, Queue, QueueOptions, RepeatOptions, ReservedJob, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1318
|
+
export { AddOptions, BackoffStrategy, BullBoardGroupMQAdapter, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, Queue, QueueOptions, RepeatOptions, ReservedJob, UnrecoverableError, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1300
1319
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -276,6 +276,13 @@ var Job = class Job {
|
|
|
276
276
|
async update(jobData) {
|
|
277
277
|
await this.updateData(jobData);
|
|
278
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Wait until this job is completed or failed.
|
|
281
|
+
* @param timeoutMs Optional timeout in milliseconds (0 = no timeout)
|
|
282
|
+
*/
|
|
283
|
+
async waitUntilFinished(timeoutMs = 0) {
|
|
284
|
+
return this.queue.waitUntilFinished(this.id, timeoutMs);
|
|
285
|
+
}
|
|
279
286
|
static fromReserved(queue, reserved, meta) {
|
|
280
287
|
return new Job({
|
|
281
288
|
queue,
|
|
@@ -469,6 +476,8 @@ function safeJsonParse(input) {
|
|
|
469
476
|
var Queue = class {
|
|
470
477
|
constructor(opts) {
|
|
471
478
|
this._consecutiveEmptyReserves = 0;
|
|
479
|
+
this.eventsSubscribed = false;
|
|
480
|
+
this.waitingJobs = /* @__PURE__ */ new Map();
|
|
472
481
|
this.promoterRunning = false;
|
|
473
482
|
this.batchBuffer = [];
|
|
474
483
|
this.flushing = false;
|
|
@@ -1317,6 +1326,91 @@ var Queue = class {
|
|
|
1317
1326
|
async getJob(id) {
|
|
1318
1327
|
return Job.fromStore(this, id);
|
|
1319
1328
|
}
|
|
1329
|
+
async setupSubscriber() {
|
|
1330
|
+
if (this.eventsSubscribed && this.subscriber) return;
|
|
1331
|
+
if (!this.subscriber) {
|
|
1332
|
+
this.subscriber = this.r.duplicate();
|
|
1333
|
+
this.subscriber.on("message", (channel, message) => {
|
|
1334
|
+
if (channel === `${this.ns}:events`) this.handleJobEvent(message);
|
|
1335
|
+
});
|
|
1336
|
+
this.subscriber.on("error", (err) => {
|
|
1337
|
+
this.logger.error("Redis error (events subscriber):", err);
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
await this.subscriber.subscribe(`${this.ns}:events`);
|
|
1341
|
+
this.eventsSubscribed = true;
|
|
1342
|
+
}
|
|
1343
|
+
handleJobEvent(message) {
|
|
1344
|
+
try {
|
|
1345
|
+
const event = safeJsonParse(message);
|
|
1346
|
+
if (!event || typeof event.id !== "string") return;
|
|
1347
|
+
const waiters = this.waitingJobs.get(event.id);
|
|
1348
|
+
if (!waiters || waiters.length === 0) return;
|
|
1349
|
+
if (event.status === "completed") {
|
|
1350
|
+
const parsed = typeof event.result === "string" ? safeJsonParse(event.result) ?? event.result : event.result;
|
|
1351
|
+
waiters.forEach((w) => w.resolve(parsed));
|
|
1352
|
+
} else if (event.status === "failed") {
|
|
1353
|
+
const info = typeof event.result === "string" ? safeJsonParse(event.result) ?? {} : event.result ?? {};
|
|
1354
|
+
const err = new Error(info && info.message || "Job failed");
|
|
1355
|
+
if (info && typeof info === "object") {
|
|
1356
|
+
if (typeof info.name === "string") err.name = info.name;
|
|
1357
|
+
if (typeof info.stack === "string") err.stack = info.stack;
|
|
1358
|
+
}
|
|
1359
|
+
waiters.forEach((w) => w.reject(err));
|
|
1360
|
+
}
|
|
1361
|
+
this.waitingJobs.delete(event.id);
|
|
1362
|
+
} catch (err) {
|
|
1363
|
+
this.logger.error("Failed to process job event:", err);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Wait for a job to complete or fail, similar to BullMQ's waitUntilFinished.
|
|
1368
|
+
*/
|
|
1369
|
+
async waitUntilFinished(jobId, timeoutMs = 0) {
|
|
1370
|
+
const job = await this.getJob(jobId);
|
|
1371
|
+
const state = await job.getState();
|
|
1372
|
+
if (state === "completed") return job.returnvalue;
|
|
1373
|
+
if (state === "failed") throw new Error(job.failedReason || "Job failed");
|
|
1374
|
+
await this.setupSubscriber();
|
|
1375
|
+
return new Promise((resolve, reject) => {
|
|
1376
|
+
let timer;
|
|
1377
|
+
let waiter;
|
|
1378
|
+
const cleanup = () => {
|
|
1379
|
+
if (timer) clearTimeout(timer);
|
|
1380
|
+
const current = this.waitingJobs.get(jobId);
|
|
1381
|
+
if (!current) return;
|
|
1382
|
+
const remaining = current.filter((w) => w !== waiter);
|
|
1383
|
+
if (remaining.length === 0) this.waitingJobs.delete(jobId);
|
|
1384
|
+
else this.waitingJobs.set(jobId, remaining);
|
|
1385
|
+
};
|
|
1386
|
+
const wrappedResolve = (value) => {
|
|
1387
|
+
cleanup();
|
|
1388
|
+
resolve(value);
|
|
1389
|
+
};
|
|
1390
|
+
const wrappedReject = (err) => {
|
|
1391
|
+
cleanup();
|
|
1392
|
+
reject(err);
|
|
1393
|
+
};
|
|
1394
|
+
waiter = {
|
|
1395
|
+
resolve: wrappedResolve,
|
|
1396
|
+
reject: wrappedReject
|
|
1397
|
+
};
|
|
1398
|
+
const waiters = this.waitingJobs.get(jobId) ?? [];
|
|
1399
|
+
waiters.push(waiter);
|
|
1400
|
+
this.waitingJobs.set(jobId, waiters);
|
|
1401
|
+
if (timeoutMs > 0) timer = setTimeout(() => {
|
|
1402
|
+
wrappedReject(/* @__PURE__ */ new Error(`Timed out waiting for job ${jobId} to finish`));
|
|
1403
|
+
}, timeoutMs);
|
|
1404
|
+
(async () => {
|
|
1405
|
+
try {
|
|
1406
|
+
const latest = await this.getJob(jobId);
|
|
1407
|
+
const latestState = await latest.getState();
|
|
1408
|
+
if (latestState === "completed") wrappedResolve(latest.returnvalue);
|
|
1409
|
+
else if (latestState === "failed") wrappedReject(new Error(latest.failedReason ?? "Job failed"));
|
|
1410
|
+
} catch (_err) {}
|
|
1411
|
+
})();
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1320
1414
|
/**
|
|
1321
1415
|
* Fetch jobs by statuses, emulating BullMQ's Queue.getJobs API used by BullBoard.
|
|
1322
1416
|
* Only getter functionality; ordering is best-effort.
|
|
@@ -1511,6 +1605,25 @@ var Queue = class {
|
|
|
1511
1605
|
await this.flushBatch();
|
|
1512
1606
|
}
|
|
1513
1607
|
await this.stopPromoter();
|
|
1608
|
+
if (this.subscriber) {
|
|
1609
|
+
try {
|
|
1610
|
+
await this.subscriber.unsubscribe(`${this.ns}:events`);
|
|
1611
|
+
await this.subscriber.quit();
|
|
1612
|
+
} catch (_err) {
|
|
1613
|
+
try {
|
|
1614
|
+
this.subscriber.disconnect();
|
|
1615
|
+
} catch (_e) {}
|
|
1616
|
+
}
|
|
1617
|
+
this.subscriber = void 0;
|
|
1618
|
+
this.eventsSubscribed = false;
|
|
1619
|
+
}
|
|
1620
|
+
if (this.waitingJobs.size > 0) {
|
|
1621
|
+
const err = /* @__PURE__ */ new Error("Queue closed");
|
|
1622
|
+
this.waitingJobs.forEach((waiters) => {
|
|
1623
|
+
waiters.forEach((w) => w.reject(err));
|
|
1624
|
+
});
|
|
1625
|
+
this.waitingJobs.clear();
|
|
1626
|
+
}
|
|
1514
1627
|
try {
|
|
1515
1628
|
await this.r.quit();
|
|
1516
1629
|
} catch (_e) {
|
|
@@ -1938,6 +2051,12 @@ var AsyncFifoQueue = class {
|
|
|
1938
2051
|
|
|
1939
2052
|
//#endregion
|
|
1940
2053
|
//#region src/worker.ts
|
|
2054
|
+
var UnrecoverableError = class extends Error {
|
|
2055
|
+
constructor(message) {
|
|
2056
|
+
super(message);
|
|
2057
|
+
this.name = "UnrecoverableError";
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
1941
2060
|
var TypedEventEmitter = class {
|
|
1942
2061
|
constructor() {
|
|
1943
2062
|
this.listeners = /* @__PURE__ */ new Map();
|
|
@@ -1973,7 +2092,7 @@ var TypedEventEmitter = class {
|
|
|
1973
2092
|
return this;
|
|
1974
2093
|
}
|
|
1975
2094
|
};
|
|
1976
|
-
const defaultBackoff = (attempt) => {
|
|
2095
|
+
const defaultBackoff = (attempt, _error) => {
|
|
1977
2096
|
const base = Math.min(3e4, 2 ** (attempt - 1) * 500);
|
|
1978
2097
|
const jitter = Math.floor(base * .25 * Math.random());
|
|
1979
2098
|
return base + jitter;
|
|
@@ -2486,7 +2605,12 @@ var _Worker = class extends TypedEventEmitter {
|
|
|
2486
2605
|
status: "failed"
|
|
2487
2606
|
}));
|
|
2488
2607
|
const nextAttempt = job.attempts + 1;
|
|
2489
|
-
|
|
2608
|
+
if (err instanceof UnrecoverableError) {
|
|
2609
|
+
this.logger.info(`Unrecoverable error for job ${job.id}: ${err instanceof Error ? err.message : String(err)}. Skipping retries.`);
|
|
2610
|
+
await this.deadLetterJob(err, job, jobStartWallTime, failedAt, nextAttempt);
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
const backoffMs = this.backoff(nextAttempt, err);
|
|
2490
2614
|
if (nextAttempt >= this.maxAttempts) {
|
|
2491
2615
|
await this.deadLetterJob(err, job, jobStartWallTime, failedAt, nextAttempt);
|
|
2492
2616
|
return;
|
|
@@ -2553,5 +2677,5 @@ function sleep(ms) {
|
|
|
2553
2677
|
}
|
|
2554
2678
|
|
|
2555
2679
|
//#endregion
|
|
2556
|
-
export { BullBoardGroupMQAdapter, Job, Queue, Worker, getWorkersStatus, waitForQueueToEmpty };
|
|
2680
|
+
export { BullBoardGroupMQAdapter, Job, Queue, UnrecoverableError, Worker, getWorkersStatus, waitForQueueToEmpty };
|
|
2557
2681
|
//# sourceMappingURL=index.js.map
|