@nicnocquee/dataqueue 1.30.0 → 1.31.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/index.cjs +769 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +216 -1
- package/dist/index.d.ts +216 -1
- package/dist/index.js +768 -4
- package/dist/index.js.map +1 -1
- package/migrations/1781200000004_create_cron_schedules_table.sql +33 -0
- package/package.json +3 -2
- package/src/backend.ts +69 -0
- package/src/backends/postgres.ts +331 -1
- package/src/backends/redis.test.ts +350 -0
- package/src/backends/redis.ts +389 -1
- package/src/cron.test.ts +126 -0
- package/src/cron.ts +40 -0
- package/src/index.test.ts +361 -0
- package/src/index.ts +157 -4
- package/src/processor.ts +22 -4
- package/src/types.ts +149 -0
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Pool } from 'pg';
|
|
|
5
5
|
import { parse } from 'pg-connection-string';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
|
+
import { Cron } from 'croner';
|
|
8
9
|
|
|
9
10
|
// src/types.ts
|
|
10
11
|
var JobEventType = /* @__PURE__ */ ((JobEventType2) => {
|
|
@@ -998,6 +999,297 @@ var PostgresBackend = class {
|
|
|
998
999
|
client.release();
|
|
999
1000
|
}
|
|
1000
1001
|
}
|
|
1002
|
+
// ── Cron schedules ──────────────────────────────────────────────────
|
|
1003
|
+
/** Create a cron schedule and return its ID. */
|
|
1004
|
+
async addCronSchedule(input) {
|
|
1005
|
+
const client = await this.pool.connect();
|
|
1006
|
+
try {
|
|
1007
|
+
const result = await client.query(
|
|
1008
|
+
`INSERT INTO cron_schedules
|
|
1009
|
+
(schedule_name, cron_expression, job_type, payload, max_attempts,
|
|
1010
|
+
priority, timeout_ms, force_kill_on_timeout, tags, timezone,
|
|
1011
|
+
allow_overlap, next_run_at)
|
|
1012
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
1013
|
+
RETURNING id`,
|
|
1014
|
+
[
|
|
1015
|
+
input.scheduleName,
|
|
1016
|
+
input.cronExpression,
|
|
1017
|
+
input.jobType,
|
|
1018
|
+
input.payload,
|
|
1019
|
+
input.maxAttempts,
|
|
1020
|
+
input.priority,
|
|
1021
|
+
input.timeoutMs,
|
|
1022
|
+
input.forceKillOnTimeout,
|
|
1023
|
+
input.tags ?? null,
|
|
1024
|
+
input.timezone,
|
|
1025
|
+
input.allowOverlap,
|
|
1026
|
+
input.nextRunAt
|
|
1027
|
+
]
|
|
1028
|
+
);
|
|
1029
|
+
const id = result.rows[0].id;
|
|
1030
|
+
log(`Added cron schedule ${id}: "${input.scheduleName}"`);
|
|
1031
|
+
return id;
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
if (error?.code === "23505") {
|
|
1034
|
+
throw new Error(
|
|
1035
|
+
`Cron schedule with name "${input.scheduleName}" already exists`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
log(`Error adding cron schedule: ${error}`);
|
|
1039
|
+
throw error;
|
|
1040
|
+
} finally {
|
|
1041
|
+
client.release();
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
/** Get a cron schedule by ID. */
|
|
1045
|
+
async getCronSchedule(id) {
|
|
1046
|
+
const client = await this.pool.connect();
|
|
1047
|
+
try {
|
|
1048
|
+
const result = await client.query(
|
|
1049
|
+
`SELECT id, schedule_name AS "scheduleName", cron_expression AS "cronExpression",
|
|
1050
|
+
job_type AS "jobType", payload, max_attempts AS "maxAttempts",
|
|
1051
|
+
priority, timeout_ms AS "timeoutMs",
|
|
1052
|
+
force_kill_on_timeout AS "forceKillOnTimeout", tags,
|
|
1053
|
+
timezone, allow_overlap AS "allowOverlap", status,
|
|
1054
|
+
last_enqueued_at AS "lastEnqueuedAt", last_job_id AS "lastJobId",
|
|
1055
|
+
next_run_at AS "nextRunAt",
|
|
1056
|
+
created_at AS "createdAt", updated_at AS "updatedAt"
|
|
1057
|
+
FROM cron_schedules WHERE id = $1`,
|
|
1058
|
+
[id]
|
|
1059
|
+
);
|
|
1060
|
+
if (result.rows.length === 0) return null;
|
|
1061
|
+
return result.rows[0];
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
log(`Error getting cron schedule ${id}: ${error}`);
|
|
1064
|
+
throw error;
|
|
1065
|
+
} finally {
|
|
1066
|
+
client.release();
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
/** Get a cron schedule by its unique name. */
|
|
1070
|
+
async getCronScheduleByName(name) {
|
|
1071
|
+
const client = await this.pool.connect();
|
|
1072
|
+
try {
|
|
1073
|
+
const result = await client.query(
|
|
1074
|
+
`SELECT id, schedule_name AS "scheduleName", cron_expression AS "cronExpression",
|
|
1075
|
+
job_type AS "jobType", payload, max_attempts AS "maxAttempts",
|
|
1076
|
+
priority, timeout_ms AS "timeoutMs",
|
|
1077
|
+
force_kill_on_timeout AS "forceKillOnTimeout", tags,
|
|
1078
|
+
timezone, allow_overlap AS "allowOverlap", status,
|
|
1079
|
+
last_enqueued_at AS "lastEnqueuedAt", last_job_id AS "lastJobId",
|
|
1080
|
+
next_run_at AS "nextRunAt",
|
|
1081
|
+
created_at AS "createdAt", updated_at AS "updatedAt"
|
|
1082
|
+
FROM cron_schedules WHERE schedule_name = $1`,
|
|
1083
|
+
[name]
|
|
1084
|
+
);
|
|
1085
|
+
if (result.rows.length === 0) return null;
|
|
1086
|
+
return result.rows[0];
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
log(`Error getting cron schedule by name "${name}": ${error}`);
|
|
1089
|
+
throw error;
|
|
1090
|
+
} finally {
|
|
1091
|
+
client.release();
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
/** List cron schedules, optionally filtered by status. */
|
|
1095
|
+
async listCronSchedules(status) {
|
|
1096
|
+
const client = await this.pool.connect();
|
|
1097
|
+
try {
|
|
1098
|
+
let query = `SELECT id, schedule_name AS "scheduleName", cron_expression AS "cronExpression",
|
|
1099
|
+
job_type AS "jobType", payload, max_attempts AS "maxAttempts",
|
|
1100
|
+
priority, timeout_ms AS "timeoutMs",
|
|
1101
|
+
force_kill_on_timeout AS "forceKillOnTimeout", tags,
|
|
1102
|
+
timezone, allow_overlap AS "allowOverlap", status,
|
|
1103
|
+
last_enqueued_at AS "lastEnqueuedAt", last_job_id AS "lastJobId",
|
|
1104
|
+
next_run_at AS "nextRunAt",
|
|
1105
|
+
created_at AS "createdAt", updated_at AS "updatedAt"
|
|
1106
|
+
FROM cron_schedules`;
|
|
1107
|
+
const params = [];
|
|
1108
|
+
if (status) {
|
|
1109
|
+
query += ` WHERE status = $1`;
|
|
1110
|
+
params.push(status);
|
|
1111
|
+
}
|
|
1112
|
+
query += ` ORDER BY created_at ASC`;
|
|
1113
|
+
const result = await client.query(query, params);
|
|
1114
|
+
return result.rows;
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
log(`Error listing cron schedules: ${error}`);
|
|
1117
|
+
throw error;
|
|
1118
|
+
} finally {
|
|
1119
|
+
client.release();
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
/** Delete a cron schedule by ID. */
|
|
1123
|
+
async removeCronSchedule(id) {
|
|
1124
|
+
const client = await this.pool.connect();
|
|
1125
|
+
try {
|
|
1126
|
+
await client.query(`DELETE FROM cron_schedules WHERE id = $1`, [id]);
|
|
1127
|
+
log(`Removed cron schedule ${id}`);
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
log(`Error removing cron schedule ${id}: ${error}`);
|
|
1130
|
+
throw error;
|
|
1131
|
+
} finally {
|
|
1132
|
+
client.release();
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
/** Pause a cron schedule. */
|
|
1136
|
+
async pauseCronSchedule(id) {
|
|
1137
|
+
const client = await this.pool.connect();
|
|
1138
|
+
try {
|
|
1139
|
+
await client.query(
|
|
1140
|
+
`UPDATE cron_schedules SET status = 'paused', updated_at = NOW() WHERE id = $1`,
|
|
1141
|
+
[id]
|
|
1142
|
+
);
|
|
1143
|
+
log(`Paused cron schedule ${id}`);
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
log(`Error pausing cron schedule ${id}: ${error}`);
|
|
1146
|
+
throw error;
|
|
1147
|
+
} finally {
|
|
1148
|
+
client.release();
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
/** Resume a paused cron schedule. */
|
|
1152
|
+
async resumeCronSchedule(id) {
|
|
1153
|
+
const client = await this.pool.connect();
|
|
1154
|
+
try {
|
|
1155
|
+
await client.query(
|
|
1156
|
+
`UPDATE cron_schedules SET status = 'active', updated_at = NOW() WHERE id = $1`,
|
|
1157
|
+
[id]
|
|
1158
|
+
);
|
|
1159
|
+
log(`Resumed cron schedule ${id}`);
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
log(`Error resuming cron schedule ${id}: ${error}`);
|
|
1162
|
+
throw error;
|
|
1163
|
+
} finally {
|
|
1164
|
+
client.release();
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/** Edit a cron schedule. */
|
|
1168
|
+
async editCronSchedule(id, updates, nextRunAt) {
|
|
1169
|
+
const client = await this.pool.connect();
|
|
1170
|
+
try {
|
|
1171
|
+
const updateFields = [];
|
|
1172
|
+
const params = [];
|
|
1173
|
+
let paramIdx = 1;
|
|
1174
|
+
if (updates.cronExpression !== void 0) {
|
|
1175
|
+
updateFields.push(`cron_expression = $${paramIdx++}`);
|
|
1176
|
+
params.push(updates.cronExpression);
|
|
1177
|
+
}
|
|
1178
|
+
if (updates.payload !== void 0) {
|
|
1179
|
+
updateFields.push(`payload = $${paramIdx++}`);
|
|
1180
|
+
params.push(updates.payload);
|
|
1181
|
+
}
|
|
1182
|
+
if (updates.maxAttempts !== void 0) {
|
|
1183
|
+
updateFields.push(`max_attempts = $${paramIdx++}`);
|
|
1184
|
+
params.push(updates.maxAttempts);
|
|
1185
|
+
}
|
|
1186
|
+
if (updates.priority !== void 0) {
|
|
1187
|
+
updateFields.push(`priority = $${paramIdx++}`);
|
|
1188
|
+
params.push(updates.priority);
|
|
1189
|
+
}
|
|
1190
|
+
if (updates.timeoutMs !== void 0) {
|
|
1191
|
+
updateFields.push(`timeout_ms = $${paramIdx++}`);
|
|
1192
|
+
params.push(updates.timeoutMs);
|
|
1193
|
+
}
|
|
1194
|
+
if (updates.forceKillOnTimeout !== void 0) {
|
|
1195
|
+
updateFields.push(`force_kill_on_timeout = $${paramIdx++}`);
|
|
1196
|
+
params.push(updates.forceKillOnTimeout);
|
|
1197
|
+
}
|
|
1198
|
+
if (updates.tags !== void 0) {
|
|
1199
|
+
updateFields.push(`tags = $${paramIdx++}`);
|
|
1200
|
+
params.push(updates.tags);
|
|
1201
|
+
}
|
|
1202
|
+
if (updates.timezone !== void 0) {
|
|
1203
|
+
updateFields.push(`timezone = $${paramIdx++}`);
|
|
1204
|
+
params.push(updates.timezone);
|
|
1205
|
+
}
|
|
1206
|
+
if (updates.allowOverlap !== void 0) {
|
|
1207
|
+
updateFields.push(`allow_overlap = $${paramIdx++}`);
|
|
1208
|
+
params.push(updates.allowOverlap);
|
|
1209
|
+
}
|
|
1210
|
+
if (nextRunAt !== void 0) {
|
|
1211
|
+
updateFields.push(`next_run_at = $${paramIdx++}`);
|
|
1212
|
+
params.push(nextRunAt);
|
|
1213
|
+
}
|
|
1214
|
+
if (updateFields.length === 0) {
|
|
1215
|
+
log(`No fields to update for cron schedule ${id}`);
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
updateFields.push(`updated_at = NOW()`);
|
|
1219
|
+
params.push(id);
|
|
1220
|
+
const query = `UPDATE cron_schedules SET ${updateFields.join(", ")} WHERE id = $${paramIdx}`;
|
|
1221
|
+
await client.query(query, params);
|
|
1222
|
+
log(`Edited cron schedule ${id}`);
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
log(`Error editing cron schedule ${id}: ${error}`);
|
|
1225
|
+
throw error;
|
|
1226
|
+
} finally {
|
|
1227
|
+
client.release();
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Atomically fetch all active cron schedules whose nextRunAt <= NOW().
|
|
1232
|
+
* Uses FOR UPDATE SKIP LOCKED to prevent duplicate enqueuing across workers.
|
|
1233
|
+
*/
|
|
1234
|
+
async getDueCronSchedules() {
|
|
1235
|
+
const client = await this.pool.connect();
|
|
1236
|
+
try {
|
|
1237
|
+
const result = await client.query(
|
|
1238
|
+
`SELECT id, schedule_name AS "scheduleName", cron_expression AS "cronExpression",
|
|
1239
|
+
job_type AS "jobType", payload, max_attempts AS "maxAttempts",
|
|
1240
|
+
priority, timeout_ms AS "timeoutMs",
|
|
1241
|
+
force_kill_on_timeout AS "forceKillOnTimeout", tags,
|
|
1242
|
+
timezone, allow_overlap AS "allowOverlap", status,
|
|
1243
|
+
last_enqueued_at AS "lastEnqueuedAt", last_job_id AS "lastJobId",
|
|
1244
|
+
next_run_at AS "nextRunAt",
|
|
1245
|
+
created_at AS "createdAt", updated_at AS "updatedAt"
|
|
1246
|
+
FROM cron_schedules
|
|
1247
|
+
WHERE status = 'active'
|
|
1248
|
+
AND next_run_at IS NOT NULL
|
|
1249
|
+
AND next_run_at <= NOW()
|
|
1250
|
+
ORDER BY next_run_at ASC
|
|
1251
|
+
FOR UPDATE SKIP LOCKED`
|
|
1252
|
+
);
|
|
1253
|
+
log(`Found ${result.rows.length} due cron schedules`);
|
|
1254
|
+
return result.rows;
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
if (error?.code === "42P01") {
|
|
1257
|
+
log("cron_schedules table does not exist, skipping cron enqueue");
|
|
1258
|
+
return [];
|
|
1259
|
+
}
|
|
1260
|
+
log(`Error getting due cron schedules: ${error}`);
|
|
1261
|
+
throw error;
|
|
1262
|
+
} finally {
|
|
1263
|
+
client.release();
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Update a cron schedule after a job has been enqueued.
|
|
1268
|
+
* Sets lastEnqueuedAt, lastJobId, and advances nextRunAt.
|
|
1269
|
+
*/
|
|
1270
|
+
async updateCronScheduleAfterEnqueue(id, lastEnqueuedAt, lastJobId, nextRunAt) {
|
|
1271
|
+
const client = await this.pool.connect();
|
|
1272
|
+
try {
|
|
1273
|
+
await client.query(
|
|
1274
|
+
`UPDATE cron_schedules
|
|
1275
|
+
SET last_enqueued_at = $2,
|
|
1276
|
+
last_job_id = $3,
|
|
1277
|
+
next_run_at = $4,
|
|
1278
|
+
updated_at = NOW()
|
|
1279
|
+
WHERE id = $1`,
|
|
1280
|
+
[id, lastEnqueuedAt, lastJobId, nextRunAt]
|
|
1281
|
+
);
|
|
1282
|
+
log(
|
|
1283
|
+
`Updated cron schedule ${id}: lastJobId=${lastJobId}, nextRunAt=${nextRunAt?.toISOString() ?? "null"}`
|
|
1284
|
+
);
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
log(`Error updating cron schedule ${id} after enqueue: ${error}`);
|
|
1287
|
+
throw error;
|
|
1288
|
+
} finally {
|
|
1289
|
+
client.release();
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
// ── Internal helpers ──────────────────────────────────────────────────
|
|
1001
1293
|
async setPendingReasonForUnpickedJobs(reason, jobType) {
|
|
1002
1294
|
const client = await this.pool.connect();
|
|
1003
1295
|
try {
|
|
@@ -1728,7 +2020,7 @@ async function processBatchWithHandlers(backend, workerId, batchSize, jobType, j
|
|
|
1728
2020
|
next();
|
|
1729
2021
|
});
|
|
1730
2022
|
}
|
|
1731
|
-
var createProcessor = (backend, handlers, options = {}) => {
|
|
2023
|
+
var createProcessor = (backend, handlers, options = {}, onBeforeBatch) => {
|
|
1732
2024
|
const {
|
|
1733
2025
|
workerId = `worker-${Math.random().toString(36).substring(2, 9)}`,
|
|
1734
2026
|
batchSize = 10,
|
|
@@ -1743,6 +2035,18 @@ var createProcessor = (backend, handlers, options = {}) => {
|
|
|
1743
2035
|
setLogContext(options.verbose ?? false);
|
|
1744
2036
|
const processJobs = async () => {
|
|
1745
2037
|
if (!running) return 0;
|
|
2038
|
+
if (onBeforeBatch) {
|
|
2039
|
+
try {
|
|
2040
|
+
await onBeforeBatch();
|
|
2041
|
+
} catch (hookError) {
|
|
2042
|
+
log(`onBeforeBatch hook error: ${hookError}`);
|
|
2043
|
+
if (onError) {
|
|
2044
|
+
onError(
|
|
2045
|
+
hookError instanceof Error ? hookError : new Error(String(hookError))
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
1746
2050
|
log(
|
|
1747
2051
|
`Processing jobs with workerId: ${workerId}${jobType ? ` and jobType: ${Array.isArray(jobType) ? jobType.join(",") : jobType}` : ""}`
|
|
1748
2052
|
);
|
|
@@ -2961,6 +3265,332 @@ var RedisBackend = class {
|
|
|
2961
3265
|
return true;
|
|
2962
3266
|
});
|
|
2963
3267
|
}
|
|
3268
|
+
// ── Cron schedules ──────────────────────────────────────────────────
|
|
3269
|
+
/** Create a cron schedule and return its ID. */
|
|
3270
|
+
async addCronSchedule(input) {
|
|
3271
|
+
const existingId = await this.client.get(
|
|
3272
|
+
`${this.prefix}cron_name:${input.scheduleName}`
|
|
3273
|
+
);
|
|
3274
|
+
if (existingId !== null) {
|
|
3275
|
+
throw new Error(
|
|
3276
|
+
`Cron schedule with name "${input.scheduleName}" already exists`
|
|
3277
|
+
);
|
|
3278
|
+
}
|
|
3279
|
+
const id = await this.client.incr(`${this.prefix}cron_id_seq`);
|
|
3280
|
+
const now = this.nowMs();
|
|
3281
|
+
const key = `${this.prefix}cron:${id}`;
|
|
3282
|
+
const fields = [
|
|
3283
|
+
"id",
|
|
3284
|
+
id.toString(),
|
|
3285
|
+
"scheduleName",
|
|
3286
|
+
input.scheduleName,
|
|
3287
|
+
"cronExpression",
|
|
3288
|
+
input.cronExpression,
|
|
3289
|
+
"jobType",
|
|
3290
|
+
input.jobType,
|
|
3291
|
+
"payload",
|
|
3292
|
+
JSON.stringify(input.payload),
|
|
3293
|
+
"maxAttempts",
|
|
3294
|
+
input.maxAttempts.toString(),
|
|
3295
|
+
"priority",
|
|
3296
|
+
input.priority.toString(),
|
|
3297
|
+
"timeoutMs",
|
|
3298
|
+
input.timeoutMs !== null ? input.timeoutMs.toString() : "null",
|
|
3299
|
+
"forceKillOnTimeout",
|
|
3300
|
+
input.forceKillOnTimeout ? "true" : "false",
|
|
3301
|
+
"tags",
|
|
3302
|
+
input.tags ? JSON.stringify(input.tags) : "null",
|
|
3303
|
+
"timezone",
|
|
3304
|
+
input.timezone,
|
|
3305
|
+
"allowOverlap",
|
|
3306
|
+
input.allowOverlap ? "true" : "false",
|
|
3307
|
+
"status",
|
|
3308
|
+
"active",
|
|
3309
|
+
"lastEnqueuedAt",
|
|
3310
|
+
"null",
|
|
3311
|
+
"lastJobId",
|
|
3312
|
+
"null",
|
|
3313
|
+
"nextRunAt",
|
|
3314
|
+
input.nextRunAt ? input.nextRunAt.getTime().toString() : "null",
|
|
3315
|
+
"createdAt",
|
|
3316
|
+
now.toString(),
|
|
3317
|
+
"updatedAt",
|
|
3318
|
+
now.toString()
|
|
3319
|
+
];
|
|
3320
|
+
await this.client.hmset(key, ...fields);
|
|
3321
|
+
await this.client.set(
|
|
3322
|
+
`${this.prefix}cron_name:${input.scheduleName}`,
|
|
3323
|
+
id.toString()
|
|
3324
|
+
);
|
|
3325
|
+
await this.client.sadd(`${this.prefix}crons`, id.toString());
|
|
3326
|
+
await this.client.sadd(`${this.prefix}cron_status:active`, id.toString());
|
|
3327
|
+
if (input.nextRunAt) {
|
|
3328
|
+
await this.client.zadd(
|
|
3329
|
+
`${this.prefix}cron_due`,
|
|
3330
|
+
input.nextRunAt.getTime(),
|
|
3331
|
+
id.toString()
|
|
3332
|
+
);
|
|
3333
|
+
}
|
|
3334
|
+
log(`Added cron schedule ${id}: "${input.scheduleName}"`);
|
|
3335
|
+
return id;
|
|
3336
|
+
}
|
|
3337
|
+
/** Get a cron schedule by ID. */
|
|
3338
|
+
async getCronSchedule(id) {
|
|
3339
|
+
const data = await this.client.hgetall(`${this.prefix}cron:${id}`);
|
|
3340
|
+
if (!data || Object.keys(data).length === 0) return null;
|
|
3341
|
+
return this.deserializeCronSchedule(data);
|
|
3342
|
+
}
|
|
3343
|
+
/** Get a cron schedule by its unique name. */
|
|
3344
|
+
async getCronScheduleByName(name) {
|
|
3345
|
+
const id = await this.client.get(`${this.prefix}cron_name:${name}`);
|
|
3346
|
+
if (id === null) return null;
|
|
3347
|
+
return this.getCronSchedule(Number(id));
|
|
3348
|
+
}
|
|
3349
|
+
/** List cron schedules, optionally filtered by status. */
|
|
3350
|
+
async listCronSchedules(status) {
|
|
3351
|
+
let ids;
|
|
3352
|
+
if (status) {
|
|
3353
|
+
ids = await this.client.smembers(`${this.prefix}cron_status:${status}`);
|
|
3354
|
+
} else {
|
|
3355
|
+
ids = await this.client.smembers(`${this.prefix}crons`);
|
|
3356
|
+
}
|
|
3357
|
+
if (ids.length === 0) return [];
|
|
3358
|
+
const pipeline = this.client.pipeline();
|
|
3359
|
+
for (const id of ids) {
|
|
3360
|
+
pipeline.hgetall(`${this.prefix}cron:${id}`);
|
|
3361
|
+
}
|
|
3362
|
+
const results = await pipeline.exec();
|
|
3363
|
+
const schedules = [];
|
|
3364
|
+
if (results) {
|
|
3365
|
+
for (const [err, data] of results) {
|
|
3366
|
+
if (!err && data && typeof data === "object" && Object.keys(data).length > 0) {
|
|
3367
|
+
schedules.push(
|
|
3368
|
+
this.deserializeCronSchedule(data)
|
|
3369
|
+
);
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
schedules.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
3374
|
+
return schedules;
|
|
3375
|
+
}
|
|
3376
|
+
/** Delete a cron schedule by ID. */
|
|
3377
|
+
async removeCronSchedule(id) {
|
|
3378
|
+
const data = await this.client.hgetall(`${this.prefix}cron:${id}`);
|
|
3379
|
+
if (!data || Object.keys(data).length === 0) return;
|
|
3380
|
+
const name = data.scheduleName;
|
|
3381
|
+
const status = data.status;
|
|
3382
|
+
await this.client.del(`${this.prefix}cron:${id}`);
|
|
3383
|
+
await this.client.del(`${this.prefix}cron_name:${name}`);
|
|
3384
|
+
await this.client.srem(`${this.prefix}crons`, id.toString());
|
|
3385
|
+
await this.client.srem(
|
|
3386
|
+
`${this.prefix}cron_status:${status}`,
|
|
3387
|
+
id.toString()
|
|
3388
|
+
);
|
|
3389
|
+
await this.client.zrem(`${this.prefix}cron_due`, id.toString());
|
|
3390
|
+
log(`Removed cron schedule ${id}`);
|
|
3391
|
+
}
|
|
3392
|
+
/** Pause a cron schedule. */
|
|
3393
|
+
async pauseCronSchedule(id) {
|
|
3394
|
+
const now = this.nowMs();
|
|
3395
|
+
await this.client.hset(
|
|
3396
|
+
`${this.prefix}cron:${id}`,
|
|
3397
|
+
"status",
|
|
3398
|
+
"paused",
|
|
3399
|
+
"updatedAt",
|
|
3400
|
+
now.toString()
|
|
3401
|
+
);
|
|
3402
|
+
await this.client.srem(`${this.prefix}cron_status:active`, id.toString());
|
|
3403
|
+
await this.client.sadd(`${this.prefix}cron_status:paused`, id.toString());
|
|
3404
|
+
await this.client.zrem(`${this.prefix}cron_due`, id.toString());
|
|
3405
|
+
log(`Paused cron schedule ${id}`);
|
|
3406
|
+
}
|
|
3407
|
+
/** Resume a paused cron schedule. */
|
|
3408
|
+
async resumeCronSchedule(id) {
|
|
3409
|
+
const now = this.nowMs();
|
|
3410
|
+
await this.client.hset(
|
|
3411
|
+
`${this.prefix}cron:${id}`,
|
|
3412
|
+
"status",
|
|
3413
|
+
"active",
|
|
3414
|
+
"updatedAt",
|
|
3415
|
+
now.toString()
|
|
3416
|
+
);
|
|
3417
|
+
await this.client.srem(`${this.prefix}cron_status:paused`, id.toString());
|
|
3418
|
+
await this.client.sadd(`${this.prefix}cron_status:active`, id.toString());
|
|
3419
|
+
const nextRunAt = await this.client.hget(
|
|
3420
|
+
`${this.prefix}cron:${id}`,
|
|
3421
|
+
"nextRunAt"
|
|
3422
|
+
);
|
|
3423
|
+
if (nextRunAt && nextRunAt !== "null") {
|
|
3424
|
+
await this.client.zadd(
|
|
3425
|
+
`${this.prefix}cron_due`,
|
|
3426
|
+
Number(nextRunAt),
|
|
3427
|
+
id.toString()
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
log(`Resumed cron schedule ${id}`);
|
|
3431
|
+
}
|
|
3432
|
+
/** Edit a cron schedule. */
|
|
3433
|
+
async editCronSchedule(id, updates, nextRunAt) {
|
|
3434
|
+
const now = this.nowMs();
|
|
3435
|
+
const fields = [];
|
|
3436
|
+
if (updates.cronExpression !== void 0) {
|
|
3437
|
+
fields.push("cronExpression", updates.cronExpression);
|
|
3438
|
+
}
|
|
3439
|
+
if (updates.payload !== void 0) {
|
|
3440
|
+
fields.push("payload", JSON.stringify(updates.payload));
|
|
3441
|
+
}
|
|
3442
|
+
if (updates.maxAttempts !== void 0) {
|
|
3443
|
+
fields.push("maxAttempts", updates.maxAttempts.toString());
|
|
3444
|
+
}
|
|
3445
|
+
if (updates.priority !== void 0) {
|
|
3446
|
+
fields.push("priority", updates.priority.toString());
|
|
3447
|
+
}
|
|
3448
|
+
if (updates.timeoutMs !== void 0) {
|
|
3449
|
+
fields.push(
|
|
3450
|
+
"timeoutMs",
|
|
3451
|
+
updates.timeoutMs !== null ? updates.timeoutMs.toString() : "null"
|
|
3452
|
+
);
|
|
3453
|
+
}
|
|
3454
|
+
if (updates.forceKillOnTimeout !== void 0) {
|
|
3455
|
+
fields.push(
|
|
3456
|
+
"forceKillOnTimeout",
|
|
3457
|
+
updates.forceKillOnTimeout ? "true" : "false"
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
if (updates.tags !== void 0) {
|
|
3461
|
+
fields.push(
|
|
3462
|
+
"tags",
|
|
3463
|
+
updates.tags !== null ? JSON.stringify(updates.tags) : "null"
|
|
3464
|
+
);
|
|
3465
|
+
}
|
|
3466
|
+
if (updates.timezone !== void 0) {
|
|
3467
|
+
fields.push("timezone", updates.timezone);
|
|
3468
|
+
}
|
|
3469
|
+
if (updates.allowOverlap !== void 0) {
|
|
3470
|
+
fields.push("allowOverlap", updates.allowOverlap ? "true" : "false");
|
|
3471
|
+
}
|
|
3472
|
+
if (nextRunAt !== void 0) {
|
|
3473
|
+
const val = nextRunAt !== null ? nextRunAt.getTime().toString() : "null";
|
|
3474
|
+
fields.push("nextRunAt", val);
|
|
3475
|
+
if (nextRunAt !== null) {
|
|
3476
|
+
await this.client.zadd(
|
|
3477
|
+
`${this.prefix}cron_due`,
|
|
3478
|
+
nextRunAt.getTime(),
|
|
3479
|
+
id.toString()
|
|
3480
|
+
);
|
|
3481
|
+
} else {
|
|
3482
|
+
await this.client.zrem(`${this.prefix}cron_due`, id.toString());
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
if (fields.length === 0) {
|
|
3486
|
+
log(`No fields to update for cron schedule ${id}`);
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
fields.push("updatedAt", now.toString());
|
|
3490
|
+
await this.client.hmset(`${this.prefix}cron:${id}`, ...fields);
|
|
3491
|
+
log(`Edited cron schedule ${id}`);
|
|
3492
|
+
}
|
|
3493
|
+
/**
|
|
3494
|
+
* Fetch all active cron schedules whose nextRunAt <= now.
|
|
3495
|
+
* Uses a sorted set (cron_due) for efficient range query.
|
|
3496
|
+
*/
|
|
3497
|
+
async getDueCronSchedules() {
|
|
3498
|
+
const now = this.nowMs();
|
|
3499
|
+
const ids = await this.client.zrangebyscore(
|
|
3500
|
+
`${this.prefix}cron_due`,
|
|
3501
|
+
0,
|
|
3502
|
+
now
|
|
3503
|
+
);
|
|
3504
|
+
if (ids.length === 0) {
|
|
3505
|
+
log("Found 0 due cron schedules");
|
|
3506
|
+
return [];
|
|
3507
|
+
}
|
|
3508
|
+
const schedules = [];
|
|
3509
|
+
for (const id of ids) {
|
|
3510
|
+
const data = await this.client.hgetall(`${this.prefix}cron:${id}`);
|
|
3511
|
+
if (data && Object.keys(data).length > 0 && data.status === "active") {
|
|
3512
|
+
schedules.push(this.deserializeCronSchedule(data));
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
log(`Found ${schedules.length} due cron schedules`);
|
|
3516
|
+
return schedules;
|
|
3517
|
+
}
|
|
3518
|
+
/**
|
|
3519
|
+
* Update a cron schedule after a job has been enqueued.
|
|
3520
|
+
* Sets lastEnqueuedAt, lastJobId, and advances nextRunAt.
|
|
3521
|
+
*/
|
|
3522
|
+
async updateCronScheduleAfterEnqueue(id, lastEnqueuedAt, lastJobId, nextRunAt) {
|
|
3523
|
+
const fields = [
|
|
3524
|
+
"lastEnqueuedAt",
|
|
3525
|
+
lastEnqueuedAt.getTime().toString(),
|
|
3526
|
+
"lastJobId",
|
|
3527
|
+
lastJobId.toString(),
|
|
3528
|
+
"nextRunAt",
|
|
3529
|
+
nextRunAt ? nextRunAt.getTime().toString() : "null",
|
|
3530
|
+
"updatedAt",
|
|
3531
|
+
this.nowMs().toString()
|
|
3532
|
+
];
|
|
3533
|
+
await this.client.hmset(`${this.prefix}cron:${id}`, ...fields);
|
|
3534
|
+
if (nextRunAt) {
|
|
3535
|
+
await this.client.zadd(
|
|
3536
|
+
`${this.prefix}cron_due`,
|
|
3537
|
+
nextRunAt.getTime(),
|
|
3538
|
+
id.toString()
|
|
3539
|
+
);
|
|
3540
|
+
} else {
|
|
3541
|
+
await this.client.zrem(`${this.prefix}cron_due`, id.toString());
|
|
3542
|
+
}
|
|
3543
|
+
log(
|
|
3544
|
+
`Updated cron schedule ${id}: lastJobId=${lastJobId}, nextRunAt=${nextRunAt?.toISOString() ?? "null"}`
|
|
3545
|
+
);
|
|
3546
|
+
}
|
|
3547
|
+
/** Deserialize a Redis hash into a CronScheduleRecord. */
|
|
3548
|
+
deserializeCronSchedule(h) {
|
|
3549
|
+
const nullish = (v) => v === void 0 || v === "null" || v === "" ? null : v;
|
|
3550
|
+
const numOrNull = (v) => {
|
|
3551
|
+
const n = nullish(v);
|
|
3552
|
+
return n === null ? null : Number(n);
|
|
3553
|
+
};
|
|
3554
|
+
const dateOrNull = (v) => {
|
|
3555
|
+
const n = numOrNull(v);
|
|
3556
|
+
return n === null ? null : new Date(n);
|
|
3557
|
+
};
|
|
3558
|
+
let payload;
|
|
3559
|
+
try {
|
|
3560
|
+
payload = JSON.parse(h.payload);
|
|
3561
|
+
} catch {
|
|
3562
|
+
payload = h.payload;
|
|
3563
|
+
}
|
|
3564
|
+
let tags;
|
|
3565
|
+
try {
|
|
3566
|
+
const raw = h.tags;
|
|
3567
|
+
if (raw && raw !== "null") {
|
|
3568
|
+
tags = JSON.parse(raw);
|
|
3569
|
+
}
|
|
3570
|
+
} catch {
|
|
3571
|
+
}
|
|
3572
|
+
return {
|
|
3573
|
+
id: Number(h.id),
|
|
3574
|
+
scheduleName: h.scheduleName,
|
|
3575
|
+
cronExpression: h.cronExpression,
|
|
3576
|
+
jobType: h.jobType,
|
|
3577
|
+
payload,
|
|
3578
|
+
maxAttempts: Number(h.maxAttempts),
|
|
3579
|
+
priority: Number(h.priority),
|
|
3580
|
+
timeoutMs: numOrNull(h.timeoutMs),
|
|
3581
|
+
forceKillOnTimeout: h.forceKillOnTimeout === "true",
|
|
3582
|
+
tags,
|
|
3583
|
+
timezone: h.timezone,
|
|
3584
|
+
allowOverlap: h.allowOverlap === "true",
|
|
3585
|
+
status: h.status,
|
|
3586
|
+
lastEnqueuedAt: dateOrNull(h.lastEnqueuedAt),
|
|
3587
|
+
lastJobId: numOrNull(h.lastJobId),
|
|
3588
|
+
nextRunAt: dateOrNull(h.nextRunAt),
|
|
3589
|
+
createdAt: new Date(Number(h.createdAt)),
|
|
3590
|
+
updatedAt: new Date(Number(h.updatedAt))
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
// ── Private helpers (filters) ─────────────────────────────────────────
|
|
2964
3594
|
async applyFilters(ids, filters) {
|
|
2965
3595
|
let result = ids;
|
|
2966
3596
|
if (filters.jobType) {
|
|
@@ -2990,6 +3620,19 @@ var RedisBackend = class {
|
|
|
2990
3620
|
return result;
|
|
2991
3621
|
}
|
|
2992
3622
|
};
|
|
3623
|
+
function getNextCronOccurrence(cronExpression, timezone = "UTC", after, CronImpl = Cron) {
|
|
3624
|
+
const cron = new CronImpl(cronExpression, { timezone });
|
|
3625
|
+
const next = cron.nextRun(after ?? /* @__PURE__ */ new Date());
|
|
3626
|
+
return next ?? null;
|
|
3627
|
+
}
|
|
3628
|
+
function validateCronExpression(cronExpression, CronImpl = Cron) {
|
|
3629
|
+
try {
|
|
3630
|
+
new CronImpl(cronExpression);
|
|
3631
|
+
return true;
|
|
3632
|
+
} catch {
|
|
3633
|
+
return false;
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
2993
3636
|
|
|
2994
3637
|
// src/handler-validation.ts
|
|
2995
3638
|
function validateHandlerSerializable2(handler, jobType) {
|
|
@@ -3084,6 +3727,49 @@ var initJobQueue = (config) => {
|
|
|
3084
3727
|
}
|
|
3085
3728
|
return pool;
|
|
3086
3729
|
};
|
|
3730
|
+
const enqueueDueCronJobsImpl = async () => {
|
|
3731
|
+
const dueSchedules = await backend.getDueCronSchedules();
|
|
3732
|
+
let count = 0;
|
|
3733
|
+
for (const schedule of dueSchedules) {
|
|
3734
|
+
if (!schedule.allowOverlap && schedule.lastJobId !== null) {
|
|
3735
|
+
const lastJob = await backend.getJob(schedule.lastJobId);
|
|
3736
|
+
if (lastJob && (lastJob.status === "pending" || lastJob.status === "processing" || lastJob.status === "waiting")) {
|
|
3737
|
+
const nextRunAt2 = getNextCronOccurrence(
|
|
3738
|
+
schedule.cronExpression,
|
|
3739
|
+
schedule.timezone
|
|
3740
|
+
);
|
|
3741
|
+
await backend.updateCronScheduleAfterEnqueue(
|
|
3742
|
+
schedule.id,
|
|
3743
|
+
/* @__PURE__ */ new Date(),
|
|
3744
|
+
schedule.lastJobId,
|
|
3745
|
+
nextRunAt2
|
|
3746
|
+
);
|
|
3747
|
+
continue;
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
const jobId = await backend.addJob({
|
|
3751
|
+
jobType: schedule.jobType,
|
|
3752
|
+
payload: schedule.payload,
|
|
3753
|
+
maxAttempts: schedule.maxAttempts,
|
|
3754
|
+
priority: schedule.priority,
|
|
3755
|
+
timeoutMs: schedule.timeoutMs ?? void 0,
|
|
3756
|
+
forceKillOnTimeout: schedule.forceKillOnTimeout,
|
|
3757
|
+
tags: schedule.tags
|
|
3758
|
+
});
|
|
3759
|
+
const nextRunAt = getNextCronOccurrence(
|
|
3760
|
+
schedule.cronExpression,
|
|
3761
|
+
schedule.timezone
|
|
3762
|
+
);
|
|
3763
|
+
await backend.updateCronScheduleAfterEnqueue(
|
|
3764
|
+
schedule.id,
|
|
3765
|
+
/* @__PURE__ */ new Date(),
|
|
3766
|
+
jobId,
|
|
3767
|
+
nextRunAt
|
|
3768
|
+
);
|
|
3769
|
+
count++;
|
|
3770
|
+
}
|
|
3771
|
+
return count;
|
|
3772
|
+
};
|
|
3087
3773
|
return {
|
|
3088
3774
|
// Job queue operations
|
|
3089
3775
|
addJob: withLogContext(
|
|
@@ -3136,8 +3822,10 @@ var initJobQueue = (config) => {
|
|
|
3136
3822
|
(tags, mode = "all", limit, offset) => backend.getJobsByTags(tags, mode, limit, offset),
|
|
3137
3823
|
config.verbose ?? false
|
|
3138
3824
|
),
|
|
3139
|
-
// Job processing
|
|
3140
|
-
createProcessor: (handlers, options) => createProcessor(backend, handlers, options)
|
|
3825
|
+
// Job processing — automatically enqueues due cron jobs before each batch
|
|
3826
|
+
createProcessor: (handlers, options) => createProcessor(backend, handlers, options, async () => {
|
|
3827
|
+
await enqueueDueCronJobsImpl();
|
|
3828
|
+
}),
|
|
3141
3829
|
// Job events
|
|
3142
3830
|
getJobEvents: withLogContext(
|
|
3143
3831
|
(jobId) => backend.getJobEvents(jobId),
|
|
@@ -3160,6 +3848,82 @@ var initJobQueue = (config) => {
|
|
|
3160
3848
|
() => expireTimedOutWaitpoints(requirePool()),
|
|
3161
3849
|
config.verbose ?? false
|
|
3162
3850
|
),
|
|
3851
|
+
// Cron schedule operations
|
|
3852
|
+
addCronJob: withLogContext(
|
|
3853
|
+
(options) => {
|
|
3854
|
+
if (!validateCronExpression(options.cronExpression)) {
|
|
3855
|
+
return Promise.reject(
|
|
3856
|
+
new Error(`Invalid cron expression: "${options.cronExpression}"`)
|
|
3857
|
+
);
|
|
3858
|
+
}
|
|
3859
|
+
const nextRunAt = getNextCronOccurrence(
|
|
3860
|
+
options.cronExpression,
|
|
3861
|
+
options.timezone ?? "UTC"
|
|
3862
|
+
);
|
|
3863
|
+
const input = {
|
|
3864
|
+
scheduleName: options.scheduleName,
|
|
3865
|
+
cronExpression: options.cronExpression,
|
|
3866
|
+
jobType: options.jobType,
|
|
3867
|
+
payload: options.payload,
|
|
3868
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
3869
|
+
priority: options.priority ?? 0,
|
|
3870
|
+
timeoutMs: options.timeoutMs ?? null,
|
|
3871
|
+
forceKillOnTimeout: options.forceKillOnTimeout ?? false,
|
|
3872
|
+
tags: options.tags,
|
|
3873
|
+
timezone: options.timezone ?? "UTC",
|
|
3874
|
+
allowOverlap: options.allowOverlap ?? false,
|
|
3875
|
+
nextRunAt
|
|
3876
|
+
};
|
|
3877
|
+
return backend.addCronSchedule(input);
|
|
3878
|
+
},
|
|
3879
|
+
config.verbose ?? false
|
|
3880
|
+
),
|
|
3881
|
+
getCronJob: withLogContext(
|
|
3882
|
+
(id) => backend.getCronSchedule(id),
|
|
3883
|
+
config.verbose ?? false
|
|
3884
|
+
),
|
|
3885
|
+
getCronJobByName: withLogContext(
|
|
3886
|
+
(name) => backend.getCronScheduleByName(name),
|
|
3887
|
+
config.verbose ?? false
|
|
3888
|
+
),
|
|
3889
|
+
listCronJobs: withLogContext(
|
|
3890
|
+
(status) => backend.listCronSchedules(status),
|
|
3891
|
+
config.verbose ?? false
|
|
3892
|
+
),
|
|
3893
|
+
removeCronJob: withLogContext(
|
|
3894
|
+
(id) => backend.removeCronSchedule(id),
|
|
3895
|
+
config.verbose ?? false
|
|
3896
|
+
),
|
|
3897
|
+
pauseCronJob: withLogContext(
|
|
3898
|
+
(id) => backend.pauseCronSchedule(id),
|
|
3899
|
+
config.verbose ?? false
|
|
3900
|
+
),
|
|
3901
|
+
resumeCronJob: withLogContext(
|
|
3902
|
+
(id) => backend.resumeCronSchedule(id),
|
|
3903
|
+
config.verbose ?? false
|
|
3904
|
+
),
|
|
3905
|
+
editCronJob: withLogContext(
|
|
3906
|
+
async (id, updates) => {
|
|
3907
|
+
if (updates.cronExpression !== void 0 && !validateCronExpression(updates.cronExpression)) {
|
|
3908
|
+
throw new Error(
|
|
3909
|
+
`Invalid cron expression: "${updates.cronExpression}"`
|
|
3910
|
+
);
|
|
3911
|
+
}
|
|
3912
|
+
let nextRunAt;
|
|
3913
|
+
if (updates.cronExpression !== void 0 || updates.timezone !== void 0) {
|
|
3914
|
+
const existing = await backend.getCronSchedule(id);
|
|
3915
|
+
const expr = updates.cronExpression ?? existing?.cronExpression ?? "";
|
|
3916
|
+
const tz = updates.timezone ?? existing?.timezone ?? "UTC";
|
|
3917
|
+
nextRunAt = getNextCronOccurrence(expr, tz);
|
|
3918
|
+
}
|
|
3919
|
+
await backend.editCronSchedule(id, updates, nextRunAt);
|
|
3920
|
+
},
|
|
3921
|
+
config.verbose ?? false
|
|
3922
|
+
),
|
|
3923
|
+
enqueueDueCronJobs: withLogContext(
|
|
3924
|
+
() => enqueueDueCronJobsImpl(),
|
|
3925
|
+
config.verbose ?? false
|
|
3926
|
+
),
|
|
3163
3927
|
// Advanced access
|
|
3164
3928
|
getPool: () => {
|
|
3165
3929
|
if (backendType !== "postgres") {
|
|
@@ -3184,6 +3948,6 @@ var withLogContext = (fn, verbose) => (...args) => {
|
|
|
3184
3948
|
return fn(...args);
|
|
3185
3949
|
};
|
|
3186
3950
|
|
|
3187
|
-
export { FailureReason, JobEventType, PostgresBackend, WaitSignal, initJobQueue, testHandlerSerialization, validateHandlerSerializable2 as validateHandlerSerializable };
|
|
3951
|
+
export { FailureReason, JobEventType, PostgresBackend, WaitSignal, getNextCronOccurrence, initJobQueue, testHandlerSerialization, validateCronExpression, validateHandlerSerializable2 as validateHandlerSerializable };
|
|
3188
3952
|
//# sourceMappingURL=index.js.map
|
|
3189
3953
|
//# sourceMappingURL=index.js.map
|