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.cjs
CHANGED
|
@@ -281,6 +281,13 @@ var Job = class Job {
|
|
|
281
281
|
async update(jobData) {
|
|
282
282
|
await this.updateData(jobData);
|
|
283
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Wait until this job is completed or failed.
|
|
286
|
+
* @param timeoutMs Optional timeout in milliseconds (0 = no timeout)
|
|
287
|
+
*/
|
|
288
|
+
async waitUntilFinished(timeoutMs = 0) {
|
|
289
|
+
return this.queue.waitUntilFinished(this.id, timeoutMs);
|
|
290
|
+
}
|
|
284
291
|
static fromReserved(queue, reserved, meta) {
|
|
285
292
|
return new Job({
|
|
286
293
|
queue,
|
|
@@ -474,6 +481,8 @@ function safeJsonParse(input) {
|
|
|
474
481
|
var Queue = class {
|
|
475
482
|
constructor(opts) {
|
|
476
483
|
this._consecutiveEmptyReserves = 0;
|
|
484
|
+
this.eventsSubscribed = false;
|
|
485
|
+
this.waitingJobs = /* @__PURE__ */ new Map();
|
|
477
486
|
this.promoterRunning = false;
|
|
478
487
|
this.batchBuffer = [];
|
|
479
488
|
this.flushing = false;
|
|
@@ -1322,6 +1331,91 @@ var Queue = class {
|
|
|
1322
1331
|
async getJob(id) {
|
|
1323
1332
|
return Job.fromStore(this, id);
|
|
1324
1333
|
}
|
|
1334
|
+
async setupSubscriber() {
|
|
1335
|
+
if (this.eventsSubscribed && this.subscriber) return;
|
|
1336
|
+
if (!this.subscriber) {
|
|
1337
|
+
this.subscriber = this.r.duplicate();
|
|
1338
|
+
this.subscriber.on("message", (channel, message) => {
|
|
1339
|
+
if (channel === `${this.ns}:events`) this.handleJobEvent(message);
|
|
1340
|
+
});
|
|
1341
|
+
this.subscriber.on("error", (err) => {
|
|
1342
|
+
this.logger.error("Redis error (events subscriber):", err);
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
await this.subscriber.subscribe(`${this.ns}:events`);
|
|
1346
|
+
this.eventsSubscribed = true;
|
|
1347
|
+
}
|
|
1348
|
+
handleJobEvent(message) {
|
|
1349
|
+
try {
|
|
1350
|
+
const event = safeJsonParse(message);
|
|
1351
|
+
if (!event || typeof event.id !== "string") return;
|
|
1352
|
+
const waiters = this.waitingJobs.get(event.id);
|
|
1353
|
+
if (!waiters || waiters.length === 0) return;
|
|
1354
|
+
if (event.status === "completed") {
|
|
1355
|
+
const parsed = typeof event.result === "string" ? safeJsonParse(event.result) ?? event.result : event.result;
|
|
1356
|
+
waiters.forEach((w) => w.resolve(parsed));
|
|
1357
|
+
} else if (event.status === "failed") {
|
|
1358
|
+
const info = typeof event.result === "string" ? safeJsonParse(event.result) ?? {} : event.result ?? {};
|
|
1359
|
+
const err = new Error(info && info.message || "Job failed");
|
|
1360
|
+
if (info && typeof info === "object") {
|
|
1361
|
+
if (typeof info.name === "string") err.name = info.name;
|
|
1362
|
+
if (typeof info.stack === "string") err.stack = info.stack;
|
|
1363
|
+
}
|
|
1364
|
+
waiters.forEach((w) => w.reject(err));
|
|
1365
|
+
}
|
|
1366
|
+
this.waitingJobs.delete(event.id);
|
|
1367
|
+
} catch (err) {
|
|
1368
|
+
this.logger.error("Failed to process job event:", err);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Wait for a job to complete or fail, similar to BullMQ's waitUntilFinished.
|
|
1373
|
+
*/
|
|
1374
|
+
async waitUntilFinished(jobId, timeoutMs = 0) {
|
|
1375
|
+
const job = await this.getJob(jobId);
|
|
1376
|
+
const state = await job.getState();
|
|
1377
|
+
if (state === "completed") return job.returnvalue;
|
|
1378
|
+
if (state === "failed") throw new Error(job.failedReason || "Job failed");
|
|
1379
|
+
await this.setupSubscriber();
|
|
1380
|
+
return new Promise((resolve, reject) => {
|
|
1381
|
+
let timer;
|
|
1382
|
+
let waiter;
|
|
1383
|
+
const cleanup = () => {
|
|
1384
|
+
if (timer) clearTimeout(timer);
|
|
1385
|
+
const current = this.waitingJobs.get(jobId);
|
|
1386
|
+
if (!current) return;
|
|
1387
|
+
const remaining = current.filter((w) => w !== waiter);
|
|
1388
|
+
if (remaining.length === 0) this.waitingJobs.delete(jobId);
|
|
1389
|
+
else this.waitingJobs.set(jobId, remaining);
|
|
1390
|
+
};
|
|
1391
|
+
const wrappedResolve = (value) => {
|
|
1392
|
+
cleanup();
|
|
1393
|
+
resolve(value);
|
|
1394
|
+
};
|
|
1395
|
+
const wrappedReject = (err) => {
|
|
1396
|
+
cleanup();
|
|
1397
|
+
reject(err);
|
|
1398
|
+
};
|
|
1399
|
+
waiter = {
|
|
1400
|
+
resolve: wrappedResolve,
|
|
1401
|
+
reject: wrappedReject
|
|
1402
|
+
};
|
|
1403
|
+
const waiters = this.waitingJobs.get(jobId) ?? [];
|
|
1404
|
+
waiters.push(waiter);
|
|
1405
|
+
this.waitingJobs.set(jobId, waiters);
|
|
1406
|
+
if (timeoutMs > 0) timer = setTimeout(() => {
|
|
1407
|
+
wrappedReject(/* @__PURE__ */ new Error(`Timed out waiting for job ${jobId} to finish`));
|
|
1408
|
+
}, timeoutMs);
|
|
1409
|
+
(async () => {
|
|
1410
|
+
try {
|
|
1411
|
+
const latest = await this.getJob(jobId);
|
|
1412
|
+
const latestState = await latest.getState();
|
|
1413
|
+
if (latestState === "completed") wrappedResolve(latest.returnvalue);
|
|
1414
|
+
else if (latestState === "failed") wrappedReject(new Error(latest.failedReason ?? "Job failed"));
|
|
1415
|
+
} catch (_err) {}
|
|
1416
|
+
})();
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1325
1419
|
/**
|
|
1326
1420
|
* Fetch jobs by statuses, emulating BullMQ's Queue.getJobs API used by BullBoard.
|
|
1327
1421
|
* Only getter functionality; ordering is best-effort.
|
|
@@ -1516,6 +1610,25 @@ var Queue = class {
|
|
|
1516
1610
|
await this.flushBatch();
|
|
1517
1611
|
}
|
|
1518
1612
|
await this.stopPromoter();
|
|
1613
|
+
if (this.subscriber) {
|
|
1614
|
+
try {
|
|
1615
|
+
await this.subscriber.unsubscribe(`${this.ns}:events`);
|
|
1616
|
+
await this.subscriber.quit();
|
|
1617
|
+
} catch (_err) {
|
|
1618
|
+
try {
|
|
1619
|
+
this.subscriber.disconnect();
|
|
1620
|
+
} catch (_e) {}
|
|
1621
|
+
}
|
|
1622
|
+
this.subscriber = void 0;
|
|
1623
|
+
this.eventsSubscribed = false;
|
|
1624
|
+
}
|
|
1625
|
+
if (this.waitingJobs.size > 0) {
|
|
1626
|
+
const err = /* @__PURE__ */ new Error("Queue closed");
|
|
1627
|
+
this.waitingJobs.forEach((waiters) => {
|
|
1628
|
+
waiters.forEach((w) => w.reject(err));
|
|
1629
|
+
});
|
|
1630
|
+
this.waitingJobs.clear();
|
|
1631
|
+
}
|
|
1519
1632
|
try {
|
|
1520
1633
|
await this.r.quit();
|
|
1521
1634
|
} catch (_e) {
|
|
@@ -1943,6 +2056,12 @@ var AsyncFifoQueue = class {
|
|
|
1943
2056
|
|
|
1944
2057
|
//#endregion
|
|
1945
2058
|
//#region src/worker.ts
|
|
2059
|
+
var UnrecoverableError = class extends Error {
|
|
2060
|
+
constructor(message) {
|
|
2061
|
+
super(message);
|
|
2062
|
+
this.name = "UnrecoverableError";
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
1946
2065
|
var TypedEventEmitter = class {
|
|
1947
2066
|
constructor() {
|
|
1948
2067
|
this.listeners = /* @__PURE__ */ new Map();
|
|
@@ -1978,7 +2097,7 @@ var TypedEventEmitter = class {
|
|
|
1978
2097
|
return this;
|
|
1979
2098
|
}
|
|
1980
2099
|
};
|
|
1981
|
-
const defaultBackoff = (attempt) => {
|
|
2100
|
+
const defaultBackoff = (attempt, _error) => {
|
|
1982
2101
|
const base = Math.min(3e4, 2 ** (attempt - 1) * 500);
|
|
1983
2102
|
const jitter = Math.floor(base * .25 * Math.random());
|
|
1984
2103
|
return base + jitter;
|
|
@@ -2491,7 +2610,12 @@ var _Worker = class extends TypedEventEmitter {
|
|
|
2491
2610
|
status: "failed"
|
|
2492
2611
|
}));
|
|
2493
2612
|
const nextAttempt = job.attempts + 1;
|
|
2494
|
-
|
|
2613
|
+
if (err instanceof UnrecoverableError) {
|
|
2614
|
+
this.logger.info(`Unrecoverable error for job ${job.id}: ${err instanceof Error ? err.message : String(err)}. Skipping retries.`);
|
|
2615
|
+
await this.deadLetterJob(err, job, jobStartWallTime, failedAt, nextAttempt);
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
const backoffMs = this.backoff(nextAttempt, err);
|
|
2495
2619
|
if (nextAttempt >= this.maxAttempts) {
|
|
2496
2620
|
await this.deadLetterJob(err, job, jobStartWallTime, failedAt, nextAttempt);
|
|
2497
2621
|
return;
|
|
@@ -2561,6 +2685,7 @@ function sleep(ms) {
|
|
|
2561
2685
|
exports.BullBoardGroupMQAdapter = BullBoardGroupMQAdapter;
|
|
2562
2686
|
exports.Job = Job;
|
|
2563
2687
|
exports.Queue = Queue;
|
|
2688
|
+
exports.UnrecoverableError = UnrecoverableError;
|
|
2564
2689
|
exports.Worker = Worker;
|
|
2565
2690
|
exports.getWorkersStatus = getWorkersStatus;
|
|
2566
2691
|
exports.waitForQueueToEmpty = waitForQueueToEmpty;
|