@monque/core 1.6.0 → 1.7.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.
- package/dist/CHANGELOG.md +30 -0
- package/dist/index.cjs +281 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +62 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +279 -125
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +3 -0
- package/src/scheduler/monque.ts +87 -26
- package/src/scheduler/services/index.ts +1 -1
- package/src/scheduler/services/job-manager.ts +151 -117
- package/src/scheduler/services/job-processor.ts +67 -49
- package/src/scheduler/services/job-scheduler.ts +24 -5
- package/src/scheduler/services/lifecycle-manager.ts +6 -1
- package/src/scheduler/services/types.ts +3 -0
- package/src/shared/errors.ts +29 -0
- package/src/shared/index.ts +3 -0
- package/src/shared/utils/index.ts +1 -0
- package/src/shared/utils/job-identifiers.ts +71 -0
package/dist/index.mjs
CHANGED
|
@@ -454,6 +454,30 @@ var InvalidCursorError = class InvalidCursorError extends MonqueError {
|
|
|
454
454
|
}
|
|
455
455
|
};
|
|
456
456
|
/**
|
|
457
|
+
* Error thrown when a public job identifier fails validation.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```typescript
|
|
461
|
+
* try {
|
|
462
|
+
* await monque.enqueue('invalid job name', {});
|
|
463
|
+
* } catch (error) {
|
|
464
|
+
* if (error instanceof InvalidJobIdentifierError) {
|
|
465
|
+
* console.error(`Invalid ${error.field}: ${error.message}`);
|
|
466
|
+
* }
|
|
467
|
+
* }
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
var InvalidJobIdentifierError = class InvalidJobIdentifierError extends MonqueError {
|
|
471
|
+
constructor(field, value, message) {
|
|
472
|
+
super(message);
|
|
473
|
+
this.field = field;
|
|
474
|
+
this.value = value;
|
|
475
|
+
this.name = "InvalidJobIdentifierError";
|
|
476
|
+
/* istanbul ignore next -- @preserve captureStackTrace is always available in Node.js */
|
|
477
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, InvalidJobIdentifierError);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
457
481
|
* Error thrown when a statistics aggregation times out.
|
|
458
482
|
*
|
|
459
483
|
* @example
|
|
@@ -647,6 +671,34 @@ function toError(value) {
|
|
|
647
671
|
}
|
|
648
672
|
}
|
|
649
673
|
//#endregion
|
|
674
|
+
//#region src/shared/utils/job-identifiers.ts
|
|
675
|
+
const JOB_NAME_PATTERN = /^[^\s\p{Cc}]+$/u;
|
|
676
|
+
const CONTROL_CHARACTER_PATTERN = /\p{Cc}/u;
|
|
677
|
+
const MAX_JOB_NAME_LENGTH = 255;
|
|
678
|
+
const MAX_UNIQUE_KEY_LENGTH = 1024;
|
|
679
|
+
/**
|
|
680
|
+
* Validate a public job name before it is registered or scheduled.
|
|
681
|
+
*
|
|
682
|
+
* @param name - The job name to validate
|
|
683
|
+
* @throws {InvalidJobIdentifierError} If the job name is empty, too long, or contains unsupported characters
|
|
684
|
+
*/
|
|
685
|
+
function validateJobName(name) {
|
|
686
|
+
if (name.length === 0 || name.trim().length === 0) throw new InvalidJobIdentifierError("name", name, "Job name cannot be empty or whitespace only.");
|
|
687
|
+
if (name.length > MAX_JOB_NAME_LENGTH) throw new InvalidJobIdentifierError("name", name, `Job name cannot exceed ${MAX_JOB_NAME_LENGTH} characters.`);
|
|
688
|
+
if (!JOB_NAME_PATTERN.test(name)) throw new InvalidJobIdentifierError("name", name, "Job name cannot contain whitespace or control characters.");
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Validate a deduplication key before it is stored or used in a unique query.
|
|
692
|
+
*
|
|
693
|
+
* @param uniqueKey - The unique key to validate
|
|
694
|
+
* @throws {InvalidJobIdentifierError} If the key is empty, too long, or contains control characters
|
|
695
|
+
*/
|
|
696
|
+
function validateUniqueKey(uniqueKey) {
|
|
697
|
+
if (uniqueKey.length === 0 || uniqueKey.trim().length === 0) throw new InvalidJobIdentifierError("uniqueKey", uniqueKey, "Unique key cannot be empty or whitespace only.");
|
|
698
|
+
if (uniqueKey.length > MAX_UNIQUE_KEY_LENGTH) throw new InvalidJobIdentifierError("uniqueKey", uniqueKey, `Unique key cannot exceed ${MAX_UNIQUE_KEY_LENGTH} characters.`);
|
|
699
|
+
if (CONTROL_CHARACTER_PATTERN.test(uniqueKey)) throw new InvalidJobIdentifierError("uniqueKey", uniqueKey, "Unique key cannot contain control characters.");
|
|
700
|
+
}
|
|
701
|
+
//#endregion
|
|
650
702
|
//#region src/scheduler/helpers.ts
|
|
651
703
|
/**
|
|
652
704
|
* Build a MongoDB query filter from a JobSelector.
|
|
@@ -1011,21 +1063,28 @@ var JobManager = class {
|
|
|
1011
1063
|
async cancelJob(jobId) {
|
|
1012
1064
|
if (!ObjectId.isValid(jobId)) return null;
|
|
1013
1065
|
const _id = new ObjectId(jobId);
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1066
|
+
try {
|
|
1067
|
+
const now = /* @__PURE__ */ new Date();
|
|
1068
|
+
const result = await this.ctx.collection.findOneAndUpdate({
|
|
1069
|
+
_id,
|
|
1070
|
+
status: JobStatus.PENDING
|
|
1071
|
+
}, { $set: {
|
|
1072
|
+
status: JobStatus.CANCELLED,
|
|
1073
|
+
updatedAt: now
|
|
1074
|
+
} }, { returnDocument: "after" });
|
|
1075
|
+
if (!result) {
|
|
1076
|
+
const jobDoc = await this.ctx.collection.findOne({ _id });
|
|
1077
|
+
if (!jobDoc) return null;
|
|
1078
|
+
if (jobDoc["status"] === JobStatus.CANCELLED) return this.ctx.documentToPersistedJob(jobDoc);
|
|
1079
|
+
throw new JobStateError(`Cannot cancel job in status '${jobDoc["status"]}'`, jobId, jobDoc["status"], "cancel");
|
|
1080
|
+
}
|
|
1081
|
+
const job = this.ctx.documentToPersistedJob(result);
|
|
1082
|
+
this.ctx.emit("job:cancelled", { job });
|
|
1083
|
+
return job;
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
if (error instanceof MonqueError) throw error;
|
|
1086
|
+
throw new ConnectionError(`Failed to cancel job: ${error instanceof Error ? error.message : "Unknown error during cancelJob"}`, error instanceof Error ? { cause: error } : void 0);
|
|
1087
|
+
}
|
|
1029
1088
|
}
|
|
1030
1089
|
/**
|
|
1031
1090
|
* Retry a failed or cancelled job.
|
|
@@ -1048,36 +1107,51 @@ var JobManager = class {
|
|
|
1048
1107
|
async retryJob(jobId) {
|
|
1049
1108
|
if (!ObjectId.isValid(jobId)) return null;
|
|
1050
1109
|
const _id = new ObjectId(jobId);
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1110
|
+
try {
|
|
1111
|
+
const now = /* @__PURE__ */ new Date();
|
|
1112
|
+
const result = await this.ctx.collection.findOneAndUpdate({
|
|
1113
|
+
_id,
|
|
1114
|
+
status: { $in: [JobStatus.FAILED, JobStatus.CANCELLED] }
|
|
1115
|
+
}, {
|
|
1116
|
+
$set: {
|
|
1117
|
+
status: JobStatus.PENDING,
|
|
1118
|
+
failCount: 0,
|
|
1119
|
+
nextRunAt: now,
|
|
1120
|
+
updatedAt: now
|
|
1121
|
+
},
|
|
1122
|
+
$unset: {
|
|
1123
|
+
failReason: "",
|
|
1124
|
+
lockedAt: "",
|
|
1125
|
+
claimedBy: "",
|
|
1126
|
+
lastHeartbeat: ""
|
|
1127
|
+
}
|
|
1128
|
+
}, { returnDocument: "before" });
|
|
1129
|
+
if (!result) {
|
|
1130
|
+
const currentJob = await this.ctx.collection.findOne({ _id });
|
|
1131
|
+
if (!currentJob) return null;
|
|
1132
|
+
throw new JobStateError(`Cannot retry job in status '${currentJob["status"]}'`, jobId, currentJob["status"], "retry");
|
|
1071
1133
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1134
|
+
const previousStatus = result["status"];
|
|
1135
|
+
const updatedDoc = { ...result };
|
|
1136
|
+
updatedDoc["status"] = JobStatus.PENDING;
|
|
1137
|
+
updatedDoc["failCount"] = 0;
|
|
1138
|
+
updatedDoc["nextRunAt"] = now;
|
|
1139
|
+
updatedDoc["updatedAt"] = now;
|
|
1140
|
+
delete updatedDoc["failReason"];
|
|
1141
|
+
delete updatedDoc["lockedAt"];
|
|
1142
|
+
delete updatedDoc["claimedBy"];
|
|
1143
|
+
delete updatedDoc["lastHeartbeat"];
|
|
1144
|
+
const job = this.ctx.documentToPersistedJob(updatedDoc);
|
|
1145
|
+
this.ctx.notifyPendingJob(job.name, job.nextRunAt);
|
|
1146
|
+
this.ctx.emit("job:retried", {
|
|
1147
|
+
job,
|
|
1148
|
+
previousStatus
|
|
1149
|
+
});
|
|
1150
|
+
return job;
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
if (error instanceof MonqueError) throw error;
|
|
1153
|
+
throw new ConnectionError(`Failed to retry job: ${error instanceof Error ? error.message : "Unknown error during retryJob"}`, error instanceof Error ? { cause: error } : void 0);
|
|
1154
|
+
}
|
|
1081
1155
|
}
|
|
1082
1156
|
/**
|
|
1083
1157
|
* Reschedule a pending job to run at a different time.
|
|
@@ -1098,20 +1172,27 @@ var JobManager = class {
|
|
|
1098
1172
|
async rescheduleJob(jobId, runAt) {
|
|
1099
1173
|
if (!ObjectId.isValid(jobId)) return null;
|
|
1100
1174
|
const _id = new ObjectId(jobId);
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1175
|
+
try {
|
|
1176
|
+
const now = /* @__PURE__ */ new Date();
|
|
1177
|
+
const result = await this.ctx.collection.findOneAndUpdate({
|
|
1178
|
+
_id,
|
|
1179
|
+
status: JobStatus.PENDING
|
|
1180
|
+
}, { $set: {
|
|
1181
|
+
nextRunAt: runAt,
|
|
1182
|
+
updatedAt: now
|
|
1183
|
+
} }, { returnDocument: "after" });
|
|
1184
|
+
if (!result) {
|
|
1185
|
+
const currentJobDoc = await this.ctx.collection.findOne({ _id });
|
|
1186
|
+
if (!currentJobDoc) return null;
|
|
1187
|
+
throw new JobStateError(`Cannot reschedule job in status '${currentJobDoc["status"]}'`, jobId, currentJobDoc["status"], "reschedule");
|
|
1188
|
+
}
|
|
1189
|
+
const job = this.ctx.documentToPersistedJob(result);
|
|
1190
|
+
this.ctx.notifyPendingJob(job.name, job.nextRunAt);
|
|
1191
|
+
return job;
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
if (error instanceof MonqueError) throw error;
|
|
1194
|
+
throw new ConnectionError(`Failed to reschedule job: ${error instanceof Error ? error.message : "Unknown error during rescheduleJob"}`, error instanceof Error ? { cause: error } : void 0);
|
|
1195
|
+
}
|
|
1115
1196
|
}
|
|
1116
1197
|
/**
|
|
1117
1198
|
* Permanently delete a job.
|
|
@@ -1133,11 +1214,16 @@ var JobManager = class {
|
|
|
1133
1214
|
async deleteJob(jobId) {
|
|
1134
1215
|
if (!ObjectId.isValid(jobId)) return false;
|
|
1135
1216
|
const _id = new ObjectId(jobId);
|
|
1136
|
-
|
|
1137
|
-
this.ctx.
|
|
1138
|
-
|
|
1217
|
+
try {
|
|
1218
|
+
if ((await this.ctx.collection.deleteOne({ _id })).deletedCount > 0) {
|
|
1219
|
+
this.ctx.emit("job:deleted", { jobId });
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
return false;
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
if (error instanceof MonqueError) throw error;
|
|
1225
|
+
throw new ConnectionError(`Failed to delete job: ${error instanceof Error ? error.message : "Unknown error during deleteJob"}`, error instanceof Error ? { cause: error } : void 0);
|
|
1139
1226
|
}
|
|
1140
|
-
return false;
|
|
1141
1227
|
}
|
|
1142
1228
|
/**
|
|
1143
1229
|
* Cancel multiple jobs matching the given filter via a single updateMany call.
|
|
@@ -1169,9 +1255,10 @@ var JobManager = class {
|
|
|
1169
1255
|
}
|
|
1170
1256
|
query["status"] = JobStatus.PENDING;
|
|
1171
1257
|
try {
|
|
1258
|
+
const now = /* @__PURE__ */ new Date();
|
|
1172
1259
|
const count = (await this.ctx.collection.updateMany(query, { $set: {
|
|
1173
1260
|
status: JobStatus.CANCELLED,
|
|
1174
|
-
updatedAt:
|
|
1261
|
+
updatedAt: now
|
|
1175
1262
|
} })).modifiedCount;
|
|
1176
1263
|
if (count > 0) this.ctx.emit("jobs:cancelled", { count });
|
|
1177
1264
|
return {
|
|
@@ -1215,17 +1302,17 @@ var JobManager = class {
|
|
|
1215
1302
|
} else query["status"] = { $in: retryable };
|
|
1216
1303
|
const spreadWindowMs = 3e4;
|
|
1217
1304
|
try {
|
|
1305
|
+
const now = /* @__PURE__ */ new Date();
|
|
1218
1306
|
const count = (await this.ctx.collection.updateMany(query, [{ $set: {
|
|
1219
1307
|
status: JobStatus.PENDING,
|
|
1220
1308
|
failCount: 0,
|
|
1221
|
-
nextRunAt: { $add: [
|
|
1222
|
-
updatedAt:
|
|
1309
|
+
nextRunAt: { $add: [now, { $multiply: [{ $rand: {} }, spreadWindowMs] }] },
|
|
1310
|
+
updatedAt: now
|
|
1223
1311
|
} }, { $unset: [
|
|
1224
1312
|
"failReason",
|
|
1225
1313
|
"lockedAt",
|
|
1226
1314
|
"claimedBy",
|
|
1227
|
-
"lastHeartbeat"
|
|
1228
|
-
"heartbeatInterval"
|
|
1315
|
+
"lastHeartbeat"
|
|
1229
1316
|
] }])).modifiedCount;
|
|
1230
1317
|
if (count > 0) this.ctx.emit("jobs:retried", { count });
|
|
1231
1318
|
return {
|
|
@@ -1287,18 +1374,16 @@ var JobProcessor = class {
|
|
|
1287
1374
|
_isPolling = false;
|
|
1288
1375
|
/** Flag to request a re-poll after the current poll finishes */
|
|
1289
1376
|
_repollRequested = false;
|
|
1290
|
-
constructor(ctx) {
|
|
1291
|
-
this.ctx = ctx;
|
|
1292
|
-
}
|
|
1293
1377
|
/**
|
|
1294
|
-
*
|
|
1378
|
+
* O(1) counter tracking the total number of active jobs across all workers.
|
|
1295
1379
|
*
|
|
1296
|
-
*
|
|
1380
|
+
* Incremented when a job is added to `worker.activeJobs` in `_doPoll`,
|
|
1381
|
+
* decremented in the `processJob` finally block. Replaces the previous
|
|
1382
|
+
* O(workers) loop in `getTotalActiveJobs()` for instance-level throttling.
|
|
1297
1383
|
*/
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
return total;
|
|
1384
|
+
_totalActiveJobs = 0;
|
|
1385
|
+
constructor(ctx) {
|
|
1386
|
+
this.ctx = ctx;
|
|
1302
1387
|
}
|
|
1303
1388
|
/**
|
|
1304
1389
|
* Get the number of available slots considering the global instanceConcurrency limit.
|
|
@@ -1309,7 +1394,7 @@ var JobProcessor = class {
|
|
|
1309
1394
|
getGloballyAvailableSlots(workerAvailableSlots) {
|
|
1310
1395
|
const { instanceConcurrency } = this.ctx.options;
|
|
1311
1396
|
if (instanceConcurrency === void 0) return workerAvailableSlots;
|
|
1312
|
-
const globalAvailable = instanceConcurrency - this.
|
|
1397
|
+
const globalAvailable = instanceConcurrency - this._totalActiveJobs;
|
|
1313
1398
|
return Math.min(workerAvailableSlots, globalAvailable);
|
|
1314
1399
|
}
|
|
1315
1400
|
/**
|
|
@@ -1349,27 +1434,49 @@ var JobProcessor = class {
|
|
|
1349
1434
|
*/
|
|
1350
1435
|
async _doPoll(targetNames) {
|
|
1351
1436
|
const { instanceConcurrency } = this.ctx.options;
|
|
1352
|
-
if (instanceConcurrency !== void 0 && this.
|
|
1437
|
+
if (instanceConcurrency !== void 0 && this._totalActiveJobs >= instanceConcurrency) return;
|
|
1353
1438
|
for (const [name, worker] of this.ctx.workers) {
|
|
1354
1439
|
if (targetNames && !targetNames.has(name)) continue;
|
|
1355
1440
|
const workerAvailableSlots = worker.concurrency - worker.activeJobs.size;
|
|
1356
1441
|
if (workerAvailableSlots <= 0) continue;
|
|
1357
1442
|
const availableSlots = this.getGloballyAvailableSlots(workerAvailableSlots);
|
|
1358
1443
|
if (availableSlots <= 0) return;
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
if (
|
|
1444
|
+
if (!this.ctx.isRunning()) return;
|
|
1445
|
+
const acquisitionPromises = [];
|
|
1446
|
+
for (let i = 0; i < availableSlots; i++) acquisitionPromises.push(this.acquireJob(name).then(async (job) => {
|
|
1447
|
+
if (!job) return;
|
|
1448
|
+
if (this.ctx.isRunning()) {
|
|
1364
1449
|
worker.activeJobs.set(job._id.toString(), job);
|
|
1450
|
+
this._totalActiveJobs++;
|
|
1365
1451
|
this.processJob(job, worker).catch((error) => {
|
|
1366
1452
|
this.ctx.emit("job:error", {
|
|
1367
1453
|
error: toError(error),
|
|
1368
1454
|
job
|
|
1369
1455
|
});
|
|
1370
1456
|
});
|
|
1371
|
-
} else
|
|
1372
|
-
|
|
1457
|
+
} else try {
|
|
1458
|
+
await this.ctx.collection.updateOne({
|
|
1459
|
+
_id: job._id,
|
|
1460
|
+
status: JobStatus.PROCESSING,
|
|
1461
|
+
claimedBy: this.ctx.instanceId
|
|
1462
|
+
}, {
|
|
1463
|
+
$set: {
|
|
1464
|
+
status: JobStatus.PENDING,
|
|
1465
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1466
|
+
},
|
|
1467
|
+
$unset: {
|
|
1468
|
+
lockedAt: "",
|
|
1469
|
+
claimedBy: "",
|
|
1470
|
+
lastHeartbeat: ""
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
this.ctx.emit("job:error", { error: toError(error) });
|
|
1475
|
+
}
|
|
1476
|
+
}).catch((error) => {
|
|
1477
|
+
this.ctx.emit("job:error", { error: toError(error) });
|
|
1478
|
+
}));
|
|
1479
|
+
await Promise.allSettled(acquisitionPromises);
|
|
1373
1480
|
}
|
|
1374
1481
|
}
|
|
1375
1482
|
/**
|
|
@@ -1405,7 +1512,6 @@ var JobProcessor = class {
|
|
|
1405
1512
|
sort: { nextRunAt: 1 },
|
|
1406
1513
|
returnDocument: "after"
|
|
1407
1514
|
});
|
|
1408
|
-
if (!this.ctx.isRunning()) return null;
|
|
1409
1515
|
if (!result) return null;
|
|
1410
1516
|
return this.ctx.documentToPersistedJob(result);
|
|
1411
1517
|
}
|
|
@@ -1448,6 +1554,8 @@ var JobProcessor = class {
|
|
|
1448
1554
|
}
|
|
1449
1555
|
} finally {
|
|
1450
1556
|
worker.activeJobs.delete(jobId);
|
|
1557
|
+
this._totalActiveJobs--;
|
|
1558
|
+
this.ctx.notifyJobFinished();
|
|
1451
1559
|
}
|
|
1452
1560
|
}
|
|
1453
1561
|
/**
|
|
@@ -1467,6 +1575,7 @@ var JobProcessor = class {
|
|
|
1467
1575
|
*/
|
|
1468
1576
|
async completeJob(job) {
|
|
1469
1577
|
if (!isPersistedJob(job)) return null;
|
|
1578
|
+
const now = /* @__PURE__ */ new Date();
|
|
1470
1579
|
if (job.repeatInterval) {
|
|
1471
1580
|
const nextRunAt = getNextCronDate(job.repeatInterval);
|
|
1472
1581
|
const result = await this.ctx.collection.findOneAndUpdate({
|
|
@@ -1478,13 +1587,12 @@ var JobProcessor = class {
|
|
|
1478
1587
|
status: JobStatus.PENDING,
|
|
1479
1588
|
nextRunAt,
|
|
1480
1589
|
failCount: 0,
|
|
1481
|
-
updatedAt:
|
|
1590
|
+
updatedAt: now
|
|
1482
1591
|
},
|
|
1483
1592
|
$unset: {
|
|
1484
1593
|
lockedAt: "",
|
|
1485
1594
|
claimedBy: "",
|
|
1486
1595
|
lastHeartbeat: "",
|
|
1487
|
-
heartbeatInterval: "",
|
|
1488
1596
|
failReason: ""
|
|
1489
1597
|
}
|
|
1490
1598
|
}, { returnDocument: "after" });
|
|
@@ -1500,13 +1608,12 @@ var JobProcessor = class {
|
|
|
1500
1608
|
}, {
|
|
1501
1609
|
$set: {
|
|
1502
1610
|
status: JobStatus.COMPLETED,
|
|
1503
|
-
updatedAt:
|
|
1611
|
+
updatedAt: now
|
|
1504
1612
|
},
|
|
1505
1613
|
$unset: {
|
|
1506
1614
|
lockedAt: "",
|
|
1507
1615
|
claimedBy: "",
|
|
1508
1616
|
lastHeartbeat: "",
|
|
1509
|
-
heartbeatInterval: "",
|
|
1510
1617
|
failReason: ""
|
|
1511
1618
|
}
|
|
1512
1619
|
}, { returnDocument: "after" });
|
|
@@ -1533,6 +1640,7 @@ var JobProcessor = class {
|
|
|
1533
1640
|
*/
|
|
1534
1641
|
async failJob(job, error) {
|
|
1535
1642
|
if (!isPersistedJob(job)) return null;
|
|
1643
|
+
const now = /* @__PURE__ */ new Date();
|
|
1536
1644
|
const newFailCount = job.failCount + 1;
|
|
1537
1645
|
if (newFailCount >= this.ctx.options.maxRetries) {
|
|
1538
1646
|
const result = await this.ctx.collection.findOneAndUpdate({
|
|
@@ -1544,13 +1652,12 @@ var JobProcessor = class {
|
|
|
1544
1652
|
status: JobStatus.FAILED,
|
|
1545
1653
|
failCount: newFailCount,
|
|
1546
1654
|
failReason: error.message,
|
|
1547
|
-
updatedAt:
|
|
1655
|
+
updatedAt: now
|
|
1548
1656
|
},
|
|
1549
1657
|
$unset: {
|
|
1550
1658
|
lockedAt: "",
|
|
1551
1659
|
claimedBy: "",
|
|
1552
|
-
lastHeartbeat: ""
|
|
1553
|
-
heartbeatInterval: ""
|
|
1660
|
+
lastHeartbeat: ""
|
|
1554
1661
|
}
|
|
1555
1662
|
}, { returnDocument: "after" });
|
|
1556
1663
|
return result ? this.ctx.documentToPersistedJob(result) : null;
|
|
@@ -1566,13 +1673,12 @@ var JobProcessor = class {
|
|
|
1566
1673
|
failCount: newFailCount,
|
|
1567
1674
|
failReason: error.message,
|
|
1568
1675
|
nextRunAt,
|
|
1569
|
-
updatedAt:
|
|
1676
|
+
updatedAt: now
|
|
1570
1677
|
},
|
|
1571
1678
|
$unset: {
|
|
1572
1679
|
lockedAt: "",
|
|
1573
1680
|
claimedBy: "",
|
|
1574
|
-
lastHeartbeat: ""
|
|
1575
|
-
heartbeatInterval: ""
|
|
1681
|
+
lastHeartbeat: ""
|
|
1576
1682
|
}
|
|
1577
1683
|
}, { returnDocument: "after" });
|
|
1578
1684
|
return result ? this.ctx.documentToPersistedJob(result) : null;
|
|
@@ -1915,6 +2021,10 @@ var JobScheduler = class {
|
|
|
1915
2021
|
constructor(ctx) {
|
|
1916
2022
|
this.ctx = ctx;
|
|
1917
2023
|
}
|
|
2024
|
+
validateJobIdentifiers(name, uniqueKey) {
|
|
2025
|
+
validateJobName(name);
|
|
2026
|
+
if (uniqueKey !== void 0) validateUniqueKey(uniqueKey);
|
|
2027
|
+
}
|
|
1918
2028
|
/**
|
|
1919
2029
|
* Validate that the job data payload does not exceed the configured maximum BSON byte size.
|
|
1920
2030
|
*
|
|
@@ -1952,6 +2062,7 @@ var JobScheduler = class {
|
|
|
1952
2062
|
* @param data - Job payload, will be passed to the worker handler
|
|
1953
2063
|
* @param options - Scheduling and deduplication options
|
|
1954
2064
|
* @returns Promise resolving to the created or existing job document
|
|
2065
|
+
* @throws {InvalidJobIdentifierError} If `name` or `uniqueKey` fails public identifier validation
|
|
1955
2066
|
* @throws {ConnectionError} If database operation fails or scheduler not initialized
|
|
1956
2067
|
* @throws {PayloadTooLargeError} If payload exceeds configured `maxPayloadSize`
|
|
1957
2068
|
*
|
|
@@ -1981,6 +2092,7 @@ var JobScheduler = class {
|
|
|
1981
2092
|
* ```
|
|
1982
2093
|
*/
|
|
1983
2094
|
async enqueue(name, data, options = {}) {
|
|
2095
|
+
this.validateJobIdentifiers(name, options.uniqueKey);
|
|
1984
2096
|
this.validatePayloadSize(data);
|
|
1985
2097
|
const now = /* @__PURE__ */ new Date();
|
|
1986
2098
|
const job = {
|
|
@@ -1992,9 +2104,9 @@ var JobScheduler = class {
|
|
|
1992
2104
|
createdAt: now,
|
|
1993
2105
|
updatedAt: now
|
|
1994
2106
|
};
|
|
1995
|
-
if (options.uniqueKey) job.uniqueKey = options.uniqueKey;
|
|
2107
|
+
if (options.uniqueKey !== void 0) job.uniqueKey = options.uniqueKey;
|
|
1996
2108
|
try {
|
|
1997
|
-
if (options.uniqueKey) {
|
|
2109
|
+
if (options.uniqueKey !== void 0) {
|
|
1998
2110
|
const result = await this.ctx.collection.findOneAndUpdate({
|
|
1999
2111
|
name,
|
|
2000
2112
|
uniqueKey: options.uniqueKey,
|
|
@@ -2069,6 +2181,7 @@ var JobScheduler = class {
|
|
|
2069
2181
|
* @param data - Job payload, will be passed to the worker handler on each run
|
|
2070
2182
|
* @param options - Scheduling options (uniqueKey for deduplication)
|
|
2071
2183
|
* @returns Promise resolving to the created job document with `repeatInterval` set
|
|
2184
|
+
* @throws {InvalidJobIdentifierError} If `name` or `uniqueKey` fails public identifier validation
|
|
2072
2185
|
* @throws {InvalidCronError} If cron expression is invalid
|
|
2073
2186
|
* @throws {ConnectionError} If database operation fails or scheduler not initialized
|
|
2074
2187
|
* @throws {PayloadTooLargeError} If payload exceeds configured `maxPayloadSize`
|
|
@@ -2097,6 +2210,7 @@ var JobScheduler = class {
|
|
|
2097
2210
|
* ```
|
|
2098
2211
|
*/
|
|
2099
2212
|
async schedule(cron, name, data, options = {}) {
|
|
2213
|
+
this.validateJobIdentifiers(name, options.uniqueKey);
|
|
2100
2214
|
this.validatePayloadSize(data);
|
|
2101
2215
|
const nextRunAt = getNextCronDate(cron);
|
|
2102
2216
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2110,9 +2224,9 @@ var JobScheduler = class {
|
|
|
2110
2224
|
createdAt: now,
|
|
2111
2225
|
updatedAt: now
|
|
2112
2226
|
};
|
|
2113
|
-
if (options.uniqueKey) job.uniqueKey = options.uniqueKey;
|
|
2227
|
+
if (options.uniqueKey !== void 0) job.uniqueKey = options.uniqueKey;
|
|
2114
2228
|
try {
|
|
2115
|
-
if (options.uniqueKey) {
|
|
2229
|
+
if (options.uniqueKey !== void 0) {
|
|
2116
2230
|
const result = await this.ctx.collection.findOneAndUpdate({
|
|
2117
2231
|
name,
|
|
2118
2232
|
uniqueKey: options.uniqueKey,
|
|
@@ -2146,6 +2260,10 @@ var JobScheduler = class {
|
|
|
2146
2260
|
*/
|
|
2147
2261
|
const DEFAULT_RETENTION_INTERVAL = 36e5;
|
|
2148
2262
|
/**
|
|
2263
|
+
* Statuses that are eligible for cleanup by the retention policy.
|
|
2264
|
+
*/
|
|
2265
|
+
const CLEANUP_STATUSES = [JobStatus.COMPLETED, JobStatus.FAILED];
|
|
2266
|
+
/**
|
|
2149
2267
|
* Manages scheduler lifecycle timers and job cleanup.
|
|
2150
2268
|
*
|
|
2151
2269
|
* Owns poll scheduling, heartbeat interval, cleanup interval, and the
|
|
@@ -2230,7 +2348,7 @@ var LifecycleManager = class {
|
|
|
2230
2348
|
if (!this.callbacks) return;
|
|
2231
2349
|
this.callbacks.poll().catch((error) => {
|
|
2232
2350
|
this.ctx.emit("job:error", { error: toError(error) });
|
|
2233
|
-
}).
|
|
2351
|
+
}).finally(() => {
|
|
2234
2352
|
this.scheduleNextPoll();
|
|
2235
2353
|
});
|
|
2236
2354
|
}
|
|
@@ -2372,6 +2490,14 @@ var Monque = class extends EventEmitter {
|
|
|
2372
2490
|
workers = /* @__PURE__ */ new Map();
|
|
2373
2491
|
isRunning = false;
|
|
2374
2492
|
isInitialized = false;
|
|
2493
|
+
/**
|
|
2494
|
+
* Resolve function for the reactive shutdown drain promise.
|
|
2495
|
+
* Set during stop() when active jobs need to finish; called by
|
|
2496
|
+
* onJobFinished() when the last active job completes.
|
|
2497
|
+
*
|
|
2498
|
+
* @private
|
|
2499
|
+
*/
|
|
2500
|
+
_drainResolve = null;
|
|
2375
2501
|
_scheduler = null;
|
|
2376
2502
|
_manager = null;
|
|
2377
2503
|
_query = null;
|
|
@@ -2380,6 +2506,7 @@ var Monque = class extends EventEmitter {
|
|
|
2380
2506
|
_lifecycleManager = null;
|
|
2381
2507
|
constructor(db, options = {}) {
|
|
2382
2508
|
super();
|
|
2509
|
+
this.setMaxListeners(20);
|
|
2383
2510
|
this.db = db;
|
|
2384
2511
|
this.options = {
|
|
2385
2512
|
collectionName: options.collectionName ?? DEFAULTS.collectionName,
|
|
@@ -2400,6 +2527,8 @@ var Monque = class extends EventEmitter {
|
|
|
2400
2527
|
maxPayloadSize: options.maxPayloadSize,
|
|
2401
2528
|
statsCacheTtlMs: options.statsCacheTtlMs ?? 5e3
|
|
2402
2529
|
};
|
|
2530
|
+
if (options.defaultConcurrency !== void 0) console.warn("[@monque/core] \"defaultConcurrency\" is deprecated and will be removed in a future major version. Use \"workerConcurrency\" instead.");
|
|
2531
|
+
if (options.maxConcurrency !== void 0) console.warn("[@monque/core] \"maxConcurrency\" is deprecated and will be removed in a future major version. Use \"instanceConcurrency\" instead.");
|
|
2403
2532
|
}
|
|
2404
2533
|
/**
|
|
2405
2534
|
* Initialize the scheduler by setting up the MongoDB collection and indexes.
|
|
@@ -2456,6 +2585,10 @@ var Monque = class extends EventEmitter {
|
|
|
2456
2585
|
if (!this._lifecycleManager) throw new ConnectionError("Monque not initialized. Call initialize() first.");
|
|
2457
2586
|
return this._lifecycleManager;
|
|
2458
2587
|
}
|
|
2588
|
+
validateSchedulingIdentifiers(name, uniqueKey) {
|
|
2589
|
+
validateJobName(name);
|
|
2590
|
+
if (uniqueKey !== void 0) validateUniqueKey(uniqueKey);
|
|
2591
|
+
}
|
|
2459
2592
|
/**
|
|
2460
2593
|
* Handle a change-stream-triggered poll and reset the safety poll timer.
|
|
2461
2594
|
*
|
|
@@ -2483,6 +2616,7 @@ var Monque = class extends EventEmitter {
|
|
|
2483
2616
|
if (!this.isRunning || !this._changeStreamHandler) return;
|
|
2484
2617
|
this._changeStreamHandler.notifyPendingJob(name, nextRunAt);
|
|
2485
2618
|
},
|
|
2619
|
+
notifyJobFinished: () => this.onJobFinished(),
|
|
2486
2620
|
documentToPersistedJob: (doc) => documentToPersistedJob(doc)
|
|
2487
2621
|
};
|
|
2488
2622
|
}
|
|
@@ -2556,7 +2690,18 @@ var Monque = class extends EventEmitter {
|
|
|
2556
2690
|
lastHeartbeat: 1
|
|
2557
2691
|
},
|
|
2558
2692
|
background: true
|
|
2559
|
-
}
|
|
2693
|
+
},
|
|
2694
|
+
...this.options.jobRetention ? [{
|
|
2695
|
+
key: {
|
|
2696
|
+
status: 1,
|
|
2697
|
+
updatedAt: 1
|
|
2698
|
+
},
|
|
2699
|
+
background: true,
|
|
2700
|
+
partialFilterExpression: {
|
|
2701
|
+
status: { $in: CLEANUP_STATUSES },
|
|
2702
|
+
updatedAt: { $exists: true }
|
|
2703
|
+
}
|
|
2704
|
+
}] : []
|
|
2560
2705
|
]);
|
|
2561
2706
|
}
|
|
2562
2707
|
/**
|
|
@@ -2578,8 +2723,7 @@ var Monque = class extends EventEmitter {
|
|
|
2578
2723
|
$unset: {
|
|
2579
2724
|
lockedAt: "",
|
|
2580
2725
|
claimedBy: "",
|
|
2581
|
-
lastHeartbeat: ""
|
|
2582
|
-
heartbeatInterval: ""
|
|
2726
|
+
lastHeartbeat: ""
|
|
2583
2727
|
}
|
|
2584
2728
|
});
|
|
2585
2729
|
if (result.modifiedCount > 0) this.emit("stale:recovered", { count: result.modifiedCount });
|
|
@@ -2620,6 +2764,7 @@ var Monque = class extends EventEmitter {
|
|
|
2620
2764
|
* @param data - Job payload, will be passed to the worker handler
|
|
2621
2765
|
* @param options - Scheduling and deduplication options
|
|
2622
2766
|
* @returns Promise resolving to the created or existing job document
|
|
2767
|
+
* @throws {InvalidJobIdentifierError} If `name` or `uniqueKey` fails public identifier validation
|
|
2623
2768
|
* @throws {ConnectionError} If database operation fails or scheduler not initialized
|
|
2624
2769
|
* @throws {PayloadTooLargeError} If payload exceeds configured `maxPayloadSize`
|
|
2625
2770
|
*
|
|
@@ -2652,6 +2797,7 @@ var Monque = class extends EventEmitter {
|
|
|
2652
2797
|
*/
|
|
2653
2798
|
async enqueue(name, data, options = {}) {
|
|
2654
2799
|
this.ensureInitialized();
|
|
2800
|
+
this.validateSchedulingIdentifiers(name, options.uniqueKey);
|
|
2655
2801
|
return this.scheduler.enqueue(name, data, options);
|
|
2656
2802
|
}
|
|
2657
2803
|
/**
|
|
@@ -2664,6 +2810,7 @@ var Monque = class extends EventEmitter {
|
|
|
2664
2810
|
* @param name - Job type identifier, must match a registered worker
|
|
2665
2811
|
* @param data - Job payload, will be passed to the worker handler
|
|
2666
2812
|
* @returns Promise resolving to the created job document
|
|
2813
|
+
* @throws {InvalidJobIdentifierError} If `name` fails public identifier validation
|
|
2667
2814
|
* @throws {ConnectionError} If database operation fails or scheduler not initialized
|
|
2668
2815
|
*
|
|
2669
2816
|
* @example Send email immediately
|
|
@@ -2686,6 +2833,7 @@ var Monque = class extends EventEmitter {
|
|
|
2686
2833
|
*/
|
|
2687
2834
|
async now(name, data) {
|
|
2688
2835
|
this.ensureInitialized();
|
|
2836
|
+
validateJobName(name);
|
|
2689
2837
|
return this.scheduler.now(name, data);
|
|
2690
2838
|
}
|
|
2691
2839
|
/**
|
|
@@ -2706,6 +2854,7 @@ var Monque = class extends EventEmitter {
|
|
|
2706
2854
|
* @param data - Job payload, will be passed to the worker handler on each run
|
|
2707
2855
|
* @param options - Scheduling options (uniqueKey for deduplication)
|
|
2708
2856
|
* @returns Promise resolving to the created job document with `repeatInterval` set
|
|
2857
|
+
* @throws {InvalidJobIdentifierError} If `name` or `uniqueKey` fails public identifier validation
|
|
2709
2858
|
* @throws {InvalidCronError} If cron expression is invalid
|
|
2710
2859
|
* @throws {ConnectionError} If database operation fails or scheduler not initialized
|
|
2711
2860
|
* @throws {PayloadTooLargeError} If payload exceeds configured `maxPayloadSize`
|
|
@@ -2737,6 +2886,7 @@ var Monque = class extends EventEmitter {
|
|
|
2737
2886
|
*/
|
|
2738
2887
|
async schedule(cron, name, data, options = {}) {
|
|
2739
2888
|
this.ensureInitialized();
|
|
2889
|
+
this.validateSchedulingIdentifiers(name, options.uniqueKey);
|
|
2740
2890
|
return this.scheduler.schedule(cron, name, data, options);
|
|
2741
2891
|
}
|
|
2742
2892
|
/**
|
|
@@ -3082,6 +3232,7 @@ var Monque = class extends EventEmitter {
|
|
|
3082
3232
|
* @param options - Worker configuration
|
|
3083
3233
|
* @param options.concurrency - Maximum concurrent jobs for this worker (default: `defaultConcurrency`)
|
|
3084
3234
|
* @param options.replace - When `true`, replace existing worker instead of throwing error
|
|
3235
|
+
* @throws {InvalidJobIdentifierError} If `name` fails public identifier validation
|
|
3085
3236
|
* @throws {WorkerRegistrationError} When a worker is already registered for `name` and `replace` is not `true`
|
|
3086
3237
|
*
|
|
3087
3238
|
* @example Basic email worker
|
|
@@ -3125,6 +3276,7 @@ var Monque = class extends EventEmitter {
|
|
|
3125
3276
|
* ```
|
|
3126
3277
|
*/
|
|
3127
3278
|
register(name, handler, options = {}) {
|
|
3279
|
+
validateJobName(name);
|
|
3128
3280
|
const concurrency = options.concurrency ?? this.options.workerConcurrency;
|
|
3129
3281
|
if (this.workers.has(name) && options.replace !== true) throw new WorkerRegistrationError(`Worker already registered for job name "${name}". Use { replace: true } to replace.`, name);
|
|
3130
3282
|
this.workers.set(name, {
|
|
@@ -3228,25 +3380,15 @@ var Monque = class extends EventEmitter {
|
|
|
3228
3380
|
try {
|
|
3229
3381
|
await this.changeStreamHandler.close();
|
|
3230
3382
|
} catch {}
|
|
3231
|
-
if (this.
|
|
3232
|
-
let checkInterval;
|
|
3383
|
+
if (this.getActiveJobCount() === 0) return;
|
|
3233
3384
|
const waitForJobs = new Promise((resolve) => {
|
|
3234
|
-
|
|
3235
|
-
if (this.getActiveJobs().length === 0) {
|
|
3236
|
-
clearInterval(checkInterval);
|
|
3237
|
-
resolve(void 0);
|
|
3238
|
-
}
|
|
3239
|
-
}, 100);
|
|
3385
|
+
this._drainResolve = () => resolve(void 0);
|
|
3240
3386
|
});
|
|
3241
3387
|
const timeout = new Promise((resolve) => {
|
|
3242
3388
|
setTimeout(() => resolve("timeout"), this.options.shutdownTimeout);
|
|
3243
3389
|
});
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
result = await Promise.race([waitForJobs, timeout]);
|
|
3247
|
-
} finally {
|
|
3248
|
-
if (checkInterval) clearInterval(checkInterval);
|
|
3249
|
-
}
|
|
3390
|
+
const result = await Promise.race([waitForJobs, timeout]);
|
|
3391
|
+
this._drainResolve = null;
|
|
3250
3392
|
if (result === "timeout") {
|
|
3251
3393
|
const incompleteJobs = this.getActiveJobsList();
|
|
3252
3394
|
const error = new ShutdownTimeoutError(`Shutdown timed out after ${this.options.shutdownTimeout}ms with ${incompleteJobs.length} incomplete jobs`, incompleteJobs);
|
|
@@ -3303,6 +3445,15 @@ var Monque = class extends EventEmitter {
|
|
|
3303
3445
|
return this.isRunning && this.isInitialized && this.collection !== null;
|
|
3304
3446
|
}
|
|
3305
3447
|
/**
|
|
3448
|
+
* Called when a job finishes processing. If a shutdown drain is pending
|
|
3449
|
+
* and no active jobs remain, resolves the drain promise.
|
|
3450
|
+
*
|
|
3451
|
+
* @private
|
|
3452
|
+
*/
|
|
3453
|
+
onJobFinished() {
|
|
3454
|
+
if (this._drainResolve && this.getActiveJobCount() === 0) this._drainResolve();
|
|
3455
|
+
}
|
|
3456
|
+
/**
|
|
3306
3457
|
* Ensure the scheduler is initialized before operations.
|
|
3307
3458
|
*
|
|
3308
3459
|
* @private
|
|
@@ -3312,15 +3463,18 @@ var Monque = class extends EventEmitter {
|
|
|
3312
3463
|
if (!this.isInitialized || !this.collection) throw new ConnectionError("Monque not initialized. Call initialize() first.");
|
|
3313
3464
|
}
|
|
3314
3465
|
/**
|
|
3315
|
-
* Get
|
|
3466
|
+
* Get total count of active jobs across all workers.
|
|
3467
|
+
*
|
|
3468
|
+
* Returns only the count (O(workers)) instead of allocating
|
|
3469
|
+
* a throw-away array of IDs, since callers only need `.length`.
|
|
3316
3470
|
*
|
|
3317
3471
|
* @private
|
|
3318
|
-
* @returns
|
|
3472
|
+
* @returns Number of jobs currently being processed
|
|
3319
3473
|
*/
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
for (const worker of this.workers.values())
|
|
3323
|
-
return
|
|
3474
|
+
getActiveJobCount() {
|
|
3475
|
+
let count = 0;
|
|
3476
|
+
for (const worker of this.workers.values()) count += worker.activeJobs.size;
|
|
3477
|
+
return count;
|
|
3324
3478
|
}
|
|
3325
3479
|
/**
|
|
3326
3480
|
* Get list of active job documents (for shutdown timeout error).
|
|
@@ -3350,6 +3504,6 @@ var Monque = class extends EventEmitter {
|
|
|
3350
3504
|
}
|
|
3351
3505
|
};
|
|
3352
3506
|
//#endregion
|
|
3353
|
-
export { AggregationTimeoutError, ConnectionError, CursorDirection, DEFAULT_BASE_INTERVAL, DEFAULT_MAX_BACKOFF_DELAY, InvalidCronError, InvalidCursorError, JobStateError, JobStatus, Monque, MonqueError, PayloadTooLargeError, ShutdownTimeoutError, WorkerRegistrationError, calculateBackoff, calculateBackoffDelay, getNextCronDate, isCancelledJob, isCompletedJob, isFailedJob, isPendingJob, isPersistedJob, isProcessingJob, isRecurringJob, isValidJobStatus, validateCronExpression };
|
|
3507
|
+
export { AggregationTimeoutError, ConnectionError, CursorDirection, DEFAULT_BASE_INTERVAL, DEFAULT_MAX_BACKOFF_DELAY, InvalidCronError, InvalidCursorError, InvalidJobIdentifierError, JobStateError, JobStatus, Monque, MonqueError, PayloadTooLargeError, ShutdownTimeoutError, WorkerRegistrationError, calculateBackoff, calculateBackoffDelay, getNextCronDate, isCancelledJob, isCompletedJob, isFailedJob, isPendingJob, isPersistedJob, isProcessingJob, isRecurringJob, isValidJobStatus, validateCronExpression, validateJobName, validateUniqueKey };
|
|
3354
3508
|
|
|
3355
3509
|
//# sourceMappingURL=index.mjs.map
|