@boringnode/queue 0.0.1-alpha.4 → 0.2.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/README.md +156 -32
- package/build/{chunk-5PDDRF5O.js → chunk-NPQKBCCY.js} +2 -2
- package/build/{chunk-CD45GT6E.js → chunk-RIXMQXYJ.js} +5 -4
- package/build/chunk-RIXMQXYJ.js.map +1 -0
- package/build/{chunk-HMGNQSSG.js → chunk-SMOKFZ46.js} +15 -1
- package/build/chunk-SMOKFZ46.js.map +1 -0
- package/build/{index-C3_tlebh.d.ts → index-C0Xg6F4E.d.ts} +433 -35
- package/build/index.d.ts +81 -4
- package/build/index.js +428 -30
- package/build/index.js.map +1 -1
- package/build/src/contracts/adapter.d.ts +1 -1
- package/build/src/drivers/knex_adapter.d.ts +8 -1
- package/build/src/drivers/knex_adapter.js +186 -34
- package/build/src/drivers/knex_adapter.js.map +1 -1
- package/build/src/drivers/redis_adapter.d.ts +7 -1
- package/build/src/drivers/redis_adapter.js +193 -2
- package/build/src/drivers/redis_adapter.js.map +1 -1
- package/build/src/drivers/sync_adapter.d.ts +7 -1
- package/build/src/drivers/sync_adapter.js +24 -5
- package/build/src/drivers/sync_adapter.js.map +1 -1
- package/build/src/types/index.d.ts +1 -1
- package/build/src/types/main.d.ts +1 -1
- package/package.json +3 -2
- package/build/chunk-CD45GT6E.js.map +0 -1
- package/build/chunk-HMGNQSSG.js.map +0 -1
- /package/build/{chunk-5PDDRF5O.js.map → chunk-NPQKBCCY.js.map} +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
calculateScore
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-NPQKBCCY.js";
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_PRIORITY
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-SMOKFZ46.js";
|
|
7
7
|
|
|
8
8
|
// src/drivers/knex_adapter.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
9
10
|
import KnexPkg from "knex";
|
|
10
11
|
function knex(config, tableName) {
|
|
11
12
|
return () => {
|
|
@@ -16,26 +17,32 @@ function knex(config, tableName) {
|
|
|
16
17
|
}
|
|
17
18
|
var KnexAdapter = class {
|
|
18
19
|
#connection;
|
|
19
|
-
#
|
|
20
|
+
#jobsTable;
|
|
21
|
+
#schedulesTable;
|
|
20
22
|
#ownsConnection;
|
|
21
23
|
#workerId = "";
|
|
22
24
|
#initialized = false;
|
|
23
25
|
constructor(config) {
|
|
24
26
|
this.#connection = config.connection;
|
|
25
|
-
this.#
|
|
27
|
+
this.#jobsTable = config.tableName ?? "queue_jobs";
|
|
28
|
+
this.#schedulesTable = config.schedulesTableName ?? "queue_schedules";
|
|
26
29
|
this.#ownsConnection = config.ownsConnection ?? false;
|
|
27
30
|
}
|
|
28
31
|
setWorkerId(workerId) {
|
|
29
32
|
this.#workerId = workerId;
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
|
-
* Ensure
|
|
33
|
-
* Creates
|
|
35
|
+
* Ensure all required tables exist.
|
|
36
|
+
* Creates them if not exists, handles race conditions.
|
|
34
37
|
*/
|
|
35
|
-
async #
|
|
38
|
+
async #ensureTables() {
|
|
36
39
|
if (this.#initialized) return;
|
|
40
|
+
await Promise.all([this.#createJobsTable(), this.#createSchedulesTable()]);
|
|
41
|
+
this.#initialized = true;
|
|
42
|
+
}
|
|
43
|
+
async #createJobsTable() {
|
|
37
44
|
try {
|
|
38
|
-
await this.#connection.schema.createTable(this.#
|
|
45
|
+
await this.#connection.schema.createTable(this.#jobsTable, (table) => {
|
|
39
46
|
table.string("id", 255).notNullable();
|
|
40
47
|
table.string("queue", 255).notNullable();
|
|
41
48
|
table.enu("status", ["pending", "active", "delayed"]).notNullable();
|
|
@@ -49,12 +56,37 @@ var KnexAdapter = class {
|
|
|
49
56
|
table.index(["queue", "status", "execute_at"]);
|
|
50
57
|
});
|
|
51
58
|
} catch {
|
|
52
|
-
const hasTable = await this.#connection.schema.hasTable(this.#
|
|
59
|
+
const hasTable = await this.#connection.schema.hasTable(this.#jobsTable);
|
|
53
60
|
if (!hasTable) {
|
|
54
|
-
throw new Error(`Failed to create table "${this.#
|
|
61
|
+
throw new Error(`Failed to create table "${this.#jobsTable}"`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async #createSchedulesTable() {
|
|
66
|
+
try {
|
|
67
|
+
await this.#connection.schema.createTable(this.#schedulesTable, (table) => {
|
|
68
|
+
table.string("id", 255).primary();
|
|
69
|
+
table.string("status", 50).notNullable().defaultTo("active");
|
|
70
|
+
table.string("name", 255).notNullable();
|
|
71
|
+
table.text("payload").notNullable();
|
|
72
|
+
table.string("cron_expression", 255).nullable();
|
|
73
|
+
table.bigint("every_ms").unsigned().nullable();
|
|
74
|
+
table.string("timezone", 100).notNullable().defaultTo("UTC");
|
|
75
|
+
table.timestamp("from_date").nullable();
|
|
76
|
+
table.timestamp("to_date").nullable();
|
|
77
|
+
table.integer("run_limit").unsigned().nullable();
|
|
78
|
+
table.integer("run_count").unsigned().notNullable().defaultTo(0);
|
|
79
|
+
table.timestamp("next_run_at").nullable();
|
|
80
|
+
table.timestamp("last_run_at").nullable();
|
|
81
|
+
table.timestamp("created_at").notNullable().defaultTo(this.#connection.fn.now());
|
|
82
|
+
table.index(["status", "next_run_at"]);
|
|
83
|
+
});
|
|
84
|
+
} catch {
|
|
85
|
+
const hasTable = await this.#connection.schema.hasTable(this.#schedulesTable);
|
|
86
|
+
if (!hasTable) {
|
|
87
|
+
throw new Error(`Failed to create table "${this.#schedulesTable}"`);
|
|
55
88
|
}
|
|
56
89
|
}
|
|
57
|
-
this.#initialized = true;
|
|
58
90
|
}
|
|
59
91
|
async destroy() {
|
|
60
92
|
if (this.#ownsConnection) {
|
|
@@ -65,11 +97,11 @@ var KnexAdapter = class {
|
|
|
65
97
|
return this.popFrom("default");
|
|
66
98
|
}
|
|
67
99
|
async popFrom(queue) {
|
|
68
|
-
await this.#
|
|
100
|
+
await this.#ensureTables();
|
|
69
101
|
const now = Date.now();
|
|
70
102
|
await this.#processDelayedJobs(queue, now);
|
|
71
103
|
return this.#connection.transaction(async (trx) => {
|
|
72
|
-
let query = trx(this.#
|
|
104
|
+
let query = trx(this.#jobsTable).where("queue", queue).where("status", "pending").orderBy("score", "asc");
|
|
73
105
|
if (this.#supportsSkipLocked()) {
|
|
74
106
|
query = query.forUpdate().skipLocked();
|
|
75
107
|
}
|
|
@@ -77,7 +109,7 @@ var KnexAdapter = class {
|
|
|
77
109
|
if (!job) {
|
|
78
110
|
return null;
|
|
79
111
|
}
|
|
80
|
-
await trx(this.#
|
|
112
|
+
await trx(this.#jobsTable).where("id", job.id).where("queue", queue).update({
|
|
81
113
|
status: "active",
|
|
82
114
|
worker_id: this.#workerId,
|
|
83
115
|
acquired_at: now
|
|
@@ -100,7 +132,7 @@ var KnexAdapter = class {
|
|
|
100
132
|
}
|
|
101
133
|
async #processDelayedJobs(queue, now) {
|
|
102
134
|
await this.#connection.transaction(async (trx) => {
|
|
103
|
-
let query = trx(this.#
|
|
135
|
+
let query = trx(this.#jobsTable).where("queue", queue).where("status", "delayed").where("execute_at", "<=", now).select("id", "data");
|
|
104
136
|
if (this.#supportsSkipLocked()) {
|
|
105
137
|
query = query.forUpdate().skipLocked();
|
|
106
138
|
}
|
|
@@ -110,7 +142,7 @@ var KnexAdapter = class {
|
|
|
110
142
|
const jobData = JSON.parse(job.data);
|
|
111
143
|
const priority = jobData.priority ?? DEFAULT_PRIORITY;
|
|
112
144
|
const score = calculateScore(priority, now);
|
|
113
|
-
await trx(this.#
|
|
145
|
+
await trx(this.#jobsTable).where("id", job.id).where("queue", queue).update({
|
|
114
146
|
status: "pending",
|
|
115
147
|
score,
|
|
116
148
|
execute_at: null
|
|
@@ -119,23 +151,23 @@ var KnexAdapter = class {
|
|
|
119
151
|
});
|
|
120
152
|
}
|
|
121
153
|
async completeJob(jobId, queue) {
|
|
122
|
-
await this.#
|
|
123
|
-
await this.#connection(this.#
|
|
154
|
+
await this.#ensureTables();
|
|
155
|
+
await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).delete();
|
|
124
156
|
}
|
|
125
157
|
async failJob(jobId, queue, _error) {
|
|
126
|
-
await this.#
|
|
127
|
-
await this.#connection(this.#
|
|
158
|
+
await this.#ensureTables();
|
|
159
|
+
await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).delete();
|
|
128
160
|
}
|
|
129
161
|
async retryJob(jobId, queue, retryAt) {
|
|
130
|
-
await this.#
|
|
162
|
+
await this.#ensureTables();
|
|
131
163
|
const now = Date.now();
|
|
132
|
-
const activeJob = await this.#connection(this.#
|
|
164
|
+
const activeJob = await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).where("status", "active").first();
|
|
133
165
|
if (!activeJob) return;
|
|
134
166
|
const jobData = JSON.parse(activeJob.data);
|
|
135
167
|
jobData.attempts = (jobData.attempts || 0) + 1;
|
|
136
168
|
const updatedData = JSON.stringify(jobData);
|
|
137
169
|
if (retryAt && retryAt.getTime() > now) {
|
|
138
|
-
await this.#connection(this.#
|
|
170
|
+
await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).update({
|
|
139
171
|
status: "delayed",
|
|
140
172
|
data: updatedData,
|
|
141
173
|
worker_id: null,
|
|
@@ -146,7 +178,7 @@ var KnexAdapter = class {
|
|
|
146
178
|
} else {
|
|
147
179
|
const priority = jobData.priority ?? DEFAULT_PRIORITY;
|
|
148
180
|
const score = calculateScore(priority, now);
|
|
149
|
-
await this.#connection(this.#
|
|
181
|
+
await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).update({
|
|
150
182
|
status: "pending",
|
|
151
183
|
data: updatedData,
|
|
152
184
|
worker_id: null,
|
|
@@ -160,11 +192,11 @@ var KnexAdapter = class {
|
|
|
160
192
|
return this.pushOn("default", jobData);
|
|
161
193
|
}
|
|
162
194
|
async pushOn(queue, jobData) {
|
|
163
|
-
await this.#
|
|
195
|
+
await this.#ensureTables();
|
|
164
196
|
const priority = jobData.priority ?? DEFAULT_PRIORITY;
|
|
165
197
|
const timestamp = Date.now();
|
|
166
198
|
const score = calculateScore(priority, timestamp);
|
|
167
|
-
await this.#connection(this.#
|
|
199
|
+
await this.#connection(this.#jobsTable).insert({
|
|
168
200
|
id: jobData.id,
|
|
169
201
|
queue,
|
|
170
202
|
status: "pending",
|
|
@@ -176,9 +208,9 @@ var KnexAdapter = class {
|
|
|
176
208
|
return this.pushLaterOn("default", jobData, delay);
|
|
177
209
|
}
|
|
178
210
|
async pushLaterOn(queue, jobData, delay) {
|
|
179
|
-
await this.#
|
|
211
|
+
await this.#ensureTables();
|
|
180
212
|
const executeAt = Date.now() + delay;
|
|
181
|
-
await this.#connection(this.#
|
|
213
|
+
await this.#connection(this.#jobsTable).insert({
|
|
182
214
|
id: jobData.id,
|
|
183
215
|
queue,
|
|
184
216
|
status: "delayed",
|
|
@@ -190,17 +222,17 @@ var KnexAdapter = class {
|
|
|
190
222
|
return this.sizeOf("default");
|
|
191
223
|
}
|
|
192
224
|
async sizeOf(queue) {
|
|
193
|
-
await this.#
|
|
194
|
-
const result = await this.#connection(this.#
|
|
225
|
+
await this.#ensureTables();
|
|
226
|
+
const result = await this.#connection(this.#jobsTable).where("queue", queue).where("status", "pending").count("* as count").first();
|
|
195
227
|
return Number(result?.count ?? 0);
|
|
196
228
|
}
|
|
197
229
|
async recoverStalledJobs(queue, stalledThreshold, maxStalledCount) {
|
|
198
|
-
await this.#
|
|
230
|
+
await this.#ensureTables();
|
|
199
231
|
const now = Date.now();
|
|
200
232
|
const stalledCutoff = now - stalledThreshold;
|
|
201
233
|
return this.#connection.transaction(async (trx) => {
|
|
202
234
|
let recovered = 0;
|
|
203
|
-
let query = trx(this.#
|
|
235
|
+
let query = trx(this.#jobsTable).where("queue", queue).where("status", "active").where("acquired_at", "<", stalledCutoff).select("id", "data");
|
|
204
236
|
if (this.#supportsSkipLocked()) {
|
|
205
237
|
query = query.forUpdate().skipLocked();
|
|
206
238
|
}
|
|
@@ -209,12 +241,12 @@ var KnexAdapter = class {
|
|
|
209
241
|
const jobData = JSON.parse(row.data);
|
|
210
242
|
const currentStalledCount = jobData.stalledCount ?? 0;
|
|
211
243
|
if (currentStalledCount >= maxStalledCount) {
|
|
212
|
-
await trx(this.#
|
|
244
|
+
await trx(this.#jobsTable).where("id", row.id).where("queue", queue).delete();
|
|
213
245
|
} else {
|
|
214
246
|
jobData.stalledCount = currentStalledCount + 1;
|
|
215
247
|
const priority = jobData.priority ?? DEFAULT_PRIORITY;
|
|
216
248
|
const score = calculateScore(priority, now);
|
|
217
|
-
await trx(this.#
|
|
249
|
+
await trx(this.#jobsTable).where("id", row.id).where("queue", queue).update({
|
|
218
250
|
status: "pending",
|
|
219
251
|
data: JSON.stringify(jobData),
|
|
220
252
|
worker_id: null,
|
|
@@ -227,6 +259,126 @@ var KnexAdapter = class {
|
|
|
227
259
|
return recovered;
|
|
228
260
|
});
|
|
229
261
|
}
|
|
262
|
+
async createSchedule(config) {
|
|
263
|
+
await this.#ensureTables();
|
|
264
|
+
const id = config.id ?? randomUUID();
|
|
265
|
+
const data = {
|
|
266
|
+
id,
|
|
267
|
+
name: config.name,
|
|
268
|
+
payload: JSON.stringify(config.payload),
|
|
269
|
+
cron_expression: config.cronExpression ?? null,
|
|
270
|
+
every_ms: config.everyMs ?? null,
|
|
271
|
+
timezone: config.timezone,
|
|
272
|
+
from_date: config.from ?? null,
|
|
273
|
+
to_date: config.to ?? null,
|
|
274
|
+
run_limit: config.limit ?? null,
|
|
275
|
+
status: "active"
|
|
276
|
+
};
|
|
277
|
+
await this.#connection(this.#schedulesTable).insert({
|
|
278
|
+
...data,
|
|
279
|
+
run_count: 0,
|
|
280
|
+
created_at: this.#connection.fn.now()
|
|
281
|
+
}).onConflict("id").merge({
|
|
282
|
+
name: data.name,
|
|
283
|
+
payload: data.payload,
|
|
284
|
+
cron_expression: data.cron_expression,
|
|
285
|
+
every_ms: data.every_ms,
|
|
286
|
+
timezone: data.timezone,
|
|
287
|
+
from_date: data.from_date,
|
|
288
|
+
to_date: data.to_date,
|
|
289
|
+
run_limit: data.run_limit,
|
|
290
|
+
status: "active"
|
|
291
|
+
});
|
|
292
|
+
return id;
|
|
293
|
+
}
|
|
294
|
+
async getSchedule(id) {
|
|
295
|
+
await this.#ensureTables();
|
|
296
|
+
const row = await this.#connection(this.#schedulesTable).where("id", id).first();
|
|
297
|
+
if (!row) return null;
|
|
298
|
+
return this.#rowToScheduleData(row);
|
|
299
|
+
}
|
|
300
|
+
async listSchedules(options) {
|
|
301
|
+
await this.#ensureTables();
|
|
302
|
+
let query = this.#connection(this.#schedulesTable).whereNot("status", "cancelled");
|
|
303
|
+
if (options?.status) {
|
|
304
|
+
query = query.where("status", options.status);
|
|
305
|
+
}
|
|
306
|
+
const rows = await query;
|
|
307
|
+
return rows.map((row) => this.#rowToScheduleData(row));
|
|
308
|
+
}
|
|
309
|
+
async updateSchedule(id, updates) {
|
|
310
|
+
await this.#ensureTables();
|
|
311
|
+
const data = {};
|
|
312
|
+
if (updates.status !== void 0) data.status = updates.status;
|
|
313
|
+
if (updates.nextRunAt !== void 0) data.next_run_at = updates.nextRunAt;
|
|
314
|
+
if (updates.lastRunAt !== void 0) data.last_run_at = updates.lastRunAt;
|
|
315
|
+
if (updates.runCount !== void 0) data.run_count = updates.runCount;
|
|
316
|
+
if (Object.keys(data).length > 0) {
|
|
317
|
+
await this.#connection(this.#schedulesTable).where("id", id).update(data);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async deleteSchedule(id) {
|
|
321
|
+
await this.#ensureTables();
|
|
322
|
+
await this.#connection(this.#schedulesTable).where("id", id).delete();
|
|
323
|
+
}
|
|
324
|
+
async claimDueSchedule() {
|
|
325
|
+
await this.#ensureTables();
|
|
326
|
+
const now = /* @__PURE__ */ new Date();
|
|
327
|
+
return this.#connection.transaction(async (trx) => {
|
|
328
|
+
let query = trx(this.#schedulesTable).where("status", "active").whereNotNull("next_run_at").where("next_run_at", "<=", now).where((builder) => {
|
|
329
|
+
builder.whereNull("run_limit").orWhereRaw("run_count < run_limit");
|
|
330
|
+
}).where((builder) => {
|
|
331
|
+
builder.whereNull("to_date").orWhere("to_date", ">=", now);
|
|
332
|
+
}).orderBy("next_run_at", "asc").limit(1);
|
|
333
|
+
if (this.#supportsSkipLocked()) {
|
|
334
|
+
query = query.forUpdate().skipLocked();
|
|
335
|
+
}
|
|
336
|
+
const row = await query.first();
|
|
337
|
+
if (!row) return null;
|
|
338
|
+
let nextRunAt = null;
|
|
339
|
+
const newRunCount = (row.run_count ?? 0) + 1;
|
|
340
|
+
if (row.every_ms) {
|
|
341
|
+
nextRunAt = new Date(now.getTime() + Number(row.every_ms));
|
|
342
|
+
} else if (row.cron_expression) {
|
|
343
|
+
const { CronExpressionParser } = await import("cron-parser");
|
|
344
|
+
const cron = CronExpressionParser.parse(row.cron_expression, {
|
|
345
|
+
currentDate: now,
|
|
346
|
+
tz: row.timezone || "UTC"
|
|
347
|
+
});
|
|
348
|
+
nextRunAt = cron.next().toDate();
|
|
349
|
+
}
|
|
350
|
+
if (row.run_limit !== null && newRunCount >= row.run_limit) {
|
|
351
|
+
nextRunAt = null;
|
|
352
|
+
}
|
|
353
|
+
if (nextRunAt && row.to_date && nextRunAt > new Date(row.to_date)) {
|
|
354
|
+
nextRunAt = null;
|
|
355
|
+
}
|
|
356
|
+
await trx(this.#schedulesTable).where("id", row.id).update({
|
|
357
|
+
next_run_at: nextRunAt,
|
|
358
|
+
last_run_at: now,
|
|
359
|
+
run_count: newRunCount
|
|
360
|
+
});
|
|
361
|
+
return this.#rowToScheduleData(row);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
#rowToScheduleData(row) {
|
|
365
|
+
return {
|
|
366
|
+
id: row.id,
|
|
367
|
+
name: row.name,
|
|
368
|
+
payload: typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload,
|
|
369
|
+
cronExpression: row.cron_expression ?? null,
|
|
370
|
+
everyMs: row.every_ms ? Number(row.every_ms) : null,
|
|
371
|
+
timezone: row.timezone ?? "UTC",
|
|
372
|
+
from: row.from_date ? new Date(row.from_date) : null,
|
|
373
|
+
to: row.to_date ? new Date(row.to_date) : null,
|
|
374
|
+
limit: row.run_limit ? Number(row.run_limit) : null,
|
|
375
|
+
runCount: Number(row.run_count ?? 0),
|
|
376
|
+
nextRunAt: row.next_run_at ? new Date(row.next_run_at) : null,
|
|
377
|
+
lastRunAt: row.last_run_at ? new Date(row.last_run_at) : null,
|
|
378
|
+
status: row.status === "cancelled" ? "paused" : row.status,
|
|
379
|
+
createdAt: row.created_at ? new Date(row.created_at) : /* @__PURE__ */ new Date()
|
|
380
|
+
};
|
|
381
|
+
}
|
|
230
382
|
};
|
|
231
383
|
export {
|
|
232
384
|
KnexAdapter,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/drivers/knex_adapter.ts"],"sourcesContent":["import KnexPkg from 'knex'\nimport type { Knex } from 'knex'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type { JobData } from '../types/main.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore } from '../utils.js'\n\nexport interface KnexAdapterOptions {\n connection: Knex\n tableName?: string\n ownsConnection?: boolean\n}\n\ntype KnexConfig = Knex | Knex.Config\n\n/**\n * Create a new Knex adapter factory.\n * Accepts either a Knex instance or a Knex configuration object.\n *\n * When passing a config object, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Knex instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function knex(config: KnexConfig, tableName?: string) {\n return () => {\n const isKnexInstance = typeof config === 'function'\n const connection = isKnexInstance ? config : KnexPkg(config)\n return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance })\n }\n}\n\n/**\n * Knex adapter for the queue system.\n * Stores jobs in a SQL database using Knex.\n */\nexport class KnexAdapter implements Adapter {\n readonly #connection: Knex\n readonly #tableName: string\n readonly #ownsConnection: boolean\n #workerId: string = ''\n #initialized: boolean = false\n\n constructor(config: KnexAdapterOptions) {\n this.#connection = config.connection\n this.#tableName = config.tableName ?? 'queue_jobs'\n this.#ownsConnection = config.ownsConnection ?? false\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n /**\n * Ensure the jobs table exists.\n * Creates it if not exists, handles race conditions.\n */\n async #ensureTableExists(): Promise<void> {\n if (this.#initialized) return\n\n try {\n await this.#connection.schema.createTable(this.#tableName, (table) => {\n table.string('id', 255).notNullable()\n table.string('queue', 255).notNullable()\n table.enu('status', ['pending', 'active', 'delayed']).notNullable()\n table.text('data').notNullable()\n table.bigint('score').unsigned().nullable()\n table.string('worker_id', 255).nullable()\n table.bigint('acquired_at').unsigned().nullable()\n table.bigint('execute_at').unsigned().nullable()\n table.primary(['id', 'queue'])\n table.index(['queue', 'status', 'score'])\n table.index(['queue', 'status', 'execute_at'])\n })\n } catch {\n /**\n * If table creation fails, verify the table actually exists.\n * This handles race conditions where multiple instances try to create\n * the table simultaneously.\n */\n const hasTable = await this.#connection.schema.hasTable(this.#tableName)\n if (!hasTable) {\n throw new Error(`Failed to create table \"${this.#tableName}\"`)\n }\n }\n\n this.#initialized = true\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.destroy()\n }\n }\n\n async pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n await this.#ensureTableExists()\n\n const now = Date.now()\n\n // First, move ready delayed jobs to pending\n await this.#processDelayedJobs(queue, now)\n\n // Use a transaction to atomically pop a job\n return this.#connection.transaction(async (trx) => {\n // Build the query for highest priority job (lowest score)\n let query = trx(this.#tableName)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const job = await query.first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n await trx(this.#tableName).where('id', job.id).where('queue', queue).update({\n status: 'active',\n worker_id: this.#workerId,\n acquired_at: now,\n })\n\n const jobData: JobData = JSON.parse(job.data)\n\n return {\n ...jobData,\n acquiredAt: now,\n }\n })\n }\n\n /**\n * Check if the database supports FOR UPDATE SKIP LOCKED.\n * PostgreSQL 9.5+, MySQL 8.0+, and MariaDB 10.6+ support it.\n * SQLite does not, but it's single-writer so it doesn't need it.\n */\n #supportsSkipLocked(): boolean {\n const client = this.#connection.client.config.client\n return client === 'pg' || client === 'mysql' || client === 'mysql2' || client === 'mariadb'\n }\n\n async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Use a transaction with row locking to prevent race conditions\n await this.#connection.transaction(async (trx) => {\n let query = trx(this.#tableName)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const delayedJobs = await query\n\n if (delayedJobs.length === 0) return\n\n // Move them to pending\n for (const job of delayedJobs) {\n const jobData: JobData = JSON.parse(job.data)\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#tableName).where('id', job.id).where('queue', queue).update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n })\n }\n\n async completeJob(jobId: string, queue: string): Promise<void> {\n await this.#ensureTableExists()\n\n await this.#connection(this.#tableName).where('id', jobId).where('queue', queue).delete()\n }\n\n async failJob(jobId: string, queue: string, _error?: Error): Promise<void> {\n await this.#ensureTableExists()\n\n await this.#connection(this.#tableName).where('id', jobId).where('queue', queue).delete()\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n await this.#ensureTableExists()\n\n const now = Date.now()\n\n // Get the active job\n const activeJob = await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .first()\n\n if (!activeJob) return\n\n const jobData: JobData = JSON.parse(activeJob.data)\n jobData.attempts = (jobData.attempts || 0) + 1\n\n const updatedData = JSON.stringify(jobData)\n\n if (retryAt && retryAt.getTime() > now) {\n // Move to delayed\n await this.#connection(this.#tableName).where('id', jobId).where('queue', queue).update({\n status: 'delayed',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score: null,\n execute_at: retryAt.getTime(),\n })\n } else {\n // Move back to pending\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await this.#connection(this.#tableName).where('id', jobId).where('queue', queue).update({\n status: 'pending',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score,\n execute_at: null,\n })\n }\n }\n\n async push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n await this.#ensureTableExists()\n\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const timestamp = Date.now()\n const score = calculateScore(priority, timestamp)\n\n await this.#connection(this.#tableName).insert({\n id: jobData.id,\n queue,\n status: 'pending',\n data: JSON.stringify(jobData),\n score,\n })\n }\n\n async pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n await this.#ensureTableExists()\n\n const executeAt = Date.now() + delay\n\n await this.#connection(this.#tableName).insert({\n id: jobData.id,\n queue,\n status: 'delayed',\n data: JSON.stringify(jobData),\n execute_at: executeAt,\n })\n }\n\n async size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n async sizeOf(queue: string): Promise<number> {\n await this.#ensureTableExists()\n\n const result = await this.#connection(this.#tableName)\n .where('queue', queue)\n .where('status', 'pending')\n .count('* as count')\n .first()\n\n return Number(result?.count ?? 0)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n await this.#ensureTableExists()\n\n const now = Date.now()\n const stalledCutoff = now - stalledThreshold\n\n // Use a transaction with row locking to prevent race conditions\n return this.#connection.transaction(async (trx) => {\n let recovered = 0\n\n let query = trx(this.#tableName)\n .where('queue', queue)\n .where('status', 'active')\n .where('acquired_at', '<', stalledCutoff)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const stalledJobs = await query\n\n for (const row of stalledJobs) {\n const jobData: JobData = JSON.parse(row.data)\n const currentStalledCount = jobData.stalledCount ?? 0\n\n if (currentStalledCount >= maxStalledCount) {\n // Fail permanently - remove the job\n await trx(this.#tableName).where('id', row.id).where('queue', queue).delete()\n } else {\n // Recover: increment stalledCount and put back in pending\n jobData.stalledCount = currentStalledCount + 1\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#tableName)\n .where('id', row.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: JSON.stringify(jobData),\n worker_id: null,\n acquired_at: null,\n score,\n })\n\n recovered++\n }\n }\n\n return recovered\n })\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/drivers/knex_adapter.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport KnexPkg from 'knex'\nimport type { Knex } from 'knex'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type { JobData, ScheduleConfig, ScheduleData, ScheduleListOptions } from '../types/main.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore } from '../utils.js'\n\nexport interface KnexAdapterOptions {\n connection: Knex\n tableName?: string\n schedulesTableName?: string\n ownsConnection?: boolean\n}\n\ntype KnexConfig = Knex | Knex.Config\n\n/**\n * Create a new Knex adapter factory.\n * Accepts either a Knex instance or a Knex configuration object.\n *\n * When passing a config object, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Knex instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function knex(config: KnexConfig, tableName?: string) {\n return () => {\n const isKnexInstance = typeof config === 'function'\n const connection = isKnexInstance ? config : KnexPkg(config)\n return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance })\n }\n}\n\n/**\n * Knex adapter for the queue system.\n * Stores jobs in a SQL database using Knex.\n */\nexport class KnexAdapter implements Adapter {\n readonly #connection: Knex\n readonly #jobsTable: string\n readonly #schedulesTable: string\n readonly #ownsConnection: boolean\n #workerId: string = ''\n #initialized: boolean = false\n\n constructor(config: KnexAdapterOptions) {\n this.#connection = config.connection\n this.#jobsTable = config.tableName ?? 'queue_jobs'\n this.#schedulesTable = config.schedulesTableName ?? 'queue_schedules'\n this.#ownsConnection = config.ownsConnection ?? false\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n /**\n * Ensure all required tables exist.\n * Creates them if not exists, handles race conditions.\n */\n async #ensureTables(): Promise<void> {\n if (this.#initialized) return\n\n await Promise.all([this.#createJobsTable(), this.#createSchedulesTable()])\n\n this.#initialized = true\n }\n\n async #createJobsTable(): Promise<void> {\n try {\n await this.#connection.schema.createTable(this.#jobsTable, (table) => {\n table.string('id', 255).notNullable()\n table.string('queue', 255).notNullable()\n table.enu('status', ['pending', 'active', 'delayed']).notNullable()\n table.text('data').notNullable()\n table.bigint('score').unsigned().nullable()\n table.string('worker_id', 255).nullable()\n table.bigint('acquired_at').unsigned().nullable()\n table.bigint('execute_at').unsigned().nullable()\n table.primary(['id', 'queue'])\n table.index(['queue', 'status', 'score'])\n table.index(['queue', 'status', 'execute_at'])\n })\n } catch {\n /**\n * If table creation fails, verify the table actually exists.\n * This handles race conditions where multiple instances try to create\n * the table simultaneously.\n */\n const hasTable = await this.#connection.schema.hasTable(this.#jobsTable)\n if (!hasTable) {\n throw new Error(`Failed to create table \"${this.#jobsTable}\"`)\n }\n }\n }\n\n async #createSchedulesTable(): Promise<void> {\n try {\n await this.#connection.schema.createTable(this.#schedulesTable, (table) => {\n table.string('id', 255).primary()\n table.string('status', 50).notNullable().defaultTo('active')\n table.string('name', 255).notNullable()\n table.text('payload').notNullable()\n table.string('cron_expression', 255).nullable()\n table.bigint('every_ms').unsigned().nullable()\n table.string('timezone', 100).notNullable().defaultTo('UTC')\n table.timestamp('from_date').nullable()\n table.timestamp('to_date').nullable()\n table.integer('run_limit').unsigned().nullable()\n table.integer('run_count').unsigned().notNullable().defaultTo(0)\n table.timestamp('next_run_at').nullable()\n table.timestamp('last_run_at').nullable()\n table.timestamp('created_at').notNullable().defaultTo(this.#connection.fn.now())\n // Indexes\n table.index(['status', 'next_run_at'])\n })\n } catch {\n /**\n * If table creation fails, verify the table actually exists.\n * This handles race conditions where multiple instances try to create\n * the table simultaneously.\n */\n const hasTable = await this.#connection.schema.hasTable(this.#schedulesTable)\n if (!hasTable) {\n throw new Error(`Failed to create table \"${this.#schedulesTable}\"`)\n }\n }\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.destroy()\n }\n }\n\n async pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n await this.#ensureTables()\n\n const now = Date.now()\n\n // First, move ready delayed jobs to pending\n await this.#processDelayedJobs(queue, now)\n\n // Use a transaction to atomically pop a job\n return this.#connection.transaction(async (trx) => {\n // Build the query for highest priority job (lowest score)\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const job = await query.first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n await trx(this.#jobsTable).where('id', job.id).where('queue', queue).update({\n status: 'active',\n worker_id: this.#workerId,\n acquired_at: now,\n })\n\n const jobData: JobData = JSON.parse(job.data)\n\n return {\n ...jobData,\n acquiredAt: now,\n }\n })\n }\n\n /**\n * Check if the database supports FOR UPDATE SKIP LOCKED.\n * PostgreSQL 9.5+, MySQL 8.0+, and MariaDB 10.6+ support it.\n * SQLite does not, but it's single-writer so it doesn't need it.\n */\n #supportsSkipLocked(): boolean {\n const client = this.#connection.client.config.client\n return client === 'pg' || client === 'mysql' || client === 'mysql2' || client === 'mariadb'\n }\n\n async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Use a transaction with row locking to prevent race conditions\n await this.#connection.transaction(async (trx) => {\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const delayedJobs = await query\n\n if (delayedJobs.length === 0) return\n\n // Move them to pending\n for (const job of delayedJobs) {\n const jobData: JobData = JSON.parse(job.data)\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable).where('id', job.id).where('queue', queue).update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n })\n }\n\n async completeJob(jobId: string, queue: string): Promise<void> {\n await this.#ensureTables()\n\n await this.#connection(this.#jobsTable).where('id', jobId).where('queue', queue).delete()\n }\n\n async failJob(jobId: string, queue: string, _error?: Error): Promise<void> {\n await this.#ensureTables()\n\n await this.#connection(this.#jobsTable).where('id', jobId).where('queue', queue).delete()\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n await this.#ensureTables()\n\n const now = Date.now()\n\n // Get the active job\n const activeJob = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .first()\n\n if (!activeJob) return\n\n const jobData: JobData = JSON.parse(activeJob.data)\n jobData.attempts = (jobData.attempts || 0) + 1\n\n const updatedData = JSON.stringify(jobData)\n\n if (retryAt && retryAt.getTime() > now) {\n // Move to delayed\n await this.#connection(this.#jobsTable).where('id', jobId).where('queue', queue).update({\n status: 'delayed',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score: null,\n execute_at: retryAt.getTime(),\n })\n } else {\n // Move back to pending\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await this.#connection(this.#jobsTable).where('id', jobId).where('queue', queue).update({\n status: 'pending',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score,\n execute_at: null,\n })\n }\n }\n\n async push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n await this.#ensureTables()\n\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const timestamp = Date.now()\n const score = calculateScore(priority, timestamp)\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'pending',\n data: JSON.stringify(jobData),\n score,\n })\n }\n\n async pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n await this.#ensureTables()\n\n const executeAt = Date.now() + delay\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'delayed',\n data: JSON.stringify(jobData),\n execute_at: executeAt,\n })\n }\n\n async size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n async sizeOf(queue: string): Promise<number> {\n await this.#ensureTables()\n\n const result = await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .count('* as count')\n .first()\n\n return Number(result?.count ?? 0)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n await this.#ensureTables()\n\n const now = Date.now()\n const stalledCutoff = now - stalledThreshold\n\n // Use a transaction with row locking to prevent race conditions\n return this.#connection.transaction(async (trx) => {\n let recovered = 0\n\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'active')\n .where('acquired_at', '<', stalledCutoff)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const stalledJobs = await query\n\n for (const row of stalledJobs) {\n const jobData: JobData = JSON.parse(row.data)\n const currentStalledCount = jobData.stalledCount ?? 0\n\n if (currentStalledCount >= maxStalledCount) {\n // Fail permanently - remove the job\n await trx(this.#jobsTable).where('id', row.id).where('queue', queue).delete()\n } else {\n // Recover: increment stalledCount and put back in pending\n jobData.stalledCount = currentStalledCount + 1\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable)\n .where('id', row.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: JSON.stringify(jobData),\n worker_id: null,\n acquired_at: null,\n score,\n })\n\n recovered++\n }\n }\n\n return recovered\n })\n }\n\n async createSchedule(config: ScheduleConfig): Promise<string> {\n await this.#ensureTables()\n\n const id = config.id ?? randomUUID()\n\n const data = {\n id,\n name: config.name,\n payload: JSON.stringify(config.payload),\n cron_expression: config.cronExpression ?? null,\n every_ms: config.everyMs ?? null,\n timezone: config.timezone,\n from_date: config.from ?? null,\n to_date: config.to ?? null,\n run_limit: config.limit ?? null,\n status: 'active',\n }\n\n // Atomic upsert\n await this.#connection(this.#schedulesTable)\n .insert({\n ...data,\n run_count: 0,\n created_at: this.#connection.fn.now(),\n })\n .onConflict('id')\n .merge({\n name: data.name,\n payload: data.payload,\n cron_expression: data.cron_expression,\n every_ms: data.every_ms,\n timezone: data.timezone,\n from_date: data.from_date,\n to_date: data.to_date,\n run_limit: data.run_limit,\n status: 'active',\n })\n\n return id\n }\n\n async getSchedule(id: string): Promise<ScheduleData | null> {\n await this.#ensureTables()\n\n const row = await this.#connection(this.#schedulesTable).where('id', id).first()\n if (!row) return null\n\n return this.#rowToScheduleData(row)\n }\n\n async listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]> {\n await this.#ensureTables()\n\n let query = this.#connection(this.#schedulesTable).whereNot('status', 'cancelled')\n\n if (options?.status) {\n query = query.where('status', options.status)\n }\n\n const rows = await query\n return rows.map((row: any) => this.#rowToScheduleData(row))\n }\n\n async updateSchedule(\n id: string,\n updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n await this.#ensureTables()\n\n const data: Record<string, any> = {}\n\n if (updates.status !== undefined) data.status = updates.status\n if (updates.nextRunAt !== undefined) data.next_run_at = updates.nextRunAt\n if (updates.lastRunAt !== undefined) data.last_run_at = updates.lastRunAt\n if (updates.runCount !== undefined) data.run_count = updates.runCount\n\n if (Object.keys(data).length > 0) {\n await this.#connection(this.#schedulesTable).where('id', id).update(data)\n }\n }\n\n async deleteSchedule(id: string): Promise<void> {\n await this.#ensureTables()\n\n await this.#connection(this.#schedulesTable).where('id', id).delete()\n }\n\n async claimDueSchedule(): Promise<ScheduleData | null> {\n await this.#ensureTables()\n\n const now = new Date()\n\n return this.#connection.transaction(async (trx) => {\n // Find one due schedule with row locking\n let query = trx(this.#schedulesTable)\n .where('status', 'active')\n .whereNotNull('next_run_at')\n .where('next_run_at', '<=', now)\n .where((builder) => {\n builder.whereNull('run_limit').orWhereRaw('run_count < run_limit')\n })\n .where((builder) => {\n builder.whereNull('to_date').orWhere('to_date', '>=', now)\n })\n .orderBy('next_run_at', 'asc')\n .limit(1)\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const row = await query.first()\n if (!row) return null\n\n // Calculate next run time\n let nextRunAt: Date | null = null\n const newRunCount = (row.run_count ?? 0) + 1\n\n if (row.every_ms) {\n nextRunAt = new Date(now.getTime() + Number(row.every_ms))\n } else if (row.cron_expression) {\n // Import cron-parser dynamically to calculate next run\n const { CronExpressionParser } = await import('cron-parser')\n const cron = CronExpressionParser.parse(row.cron_expression, {\n currentDate: now,\n tz: row.timezone || 'UTC',\n })\n nextRunAt = cron.next().toDate()\n }\n\n // Check if limit will be reached\n if (row.run_limit !== null && newRunCount >= row.run_limit) {\n nextRunAt = null\n }\n\n // Check if past end date\n if (nextRunAt && row.to_date && nextRunAt > new Date(row.to_date)) {\n nextRunAt = null\n }\n\n // Update atomically\n await trx(this.#schedulesTable).where('id', row.id).update({\n next_run_at: nextRunAt,\n last_run_at: now,\n run_count: newRunCount,\n })\n\n // Return schedule data (before update state for payload)\n return this.#rowToScheduleData(row)\n })\n }\n\n #rowToScheduleData(row: any): ScheduleData {\n return {\n id: row.id,\n name: row.name,\n payload: typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload,\n cronExpression: row.cron_expression ?? null,\n everyMs: row.every_ms ? Number(row.every_ms) : null,\n timezone: row.timezone ?? 'UTC',\n from: row.from_date ? new Date(row.from_date) : null,\n to: row.to_date ? new Date(row.to_date) : null,\n limit: row.run_limit ? Number(row.run_limit) : null,\n runCount: Number(row.run_count ?? 0),\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : null,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : null,\n status: row.status === 'cancelled' ? 'paused' : row.status,\n createdAt: row.created_at ? new Date(row.created_at) : new Date(),\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Redis, RedisOptions } from 'ioredis';
|
|
2
|
-
import { A as Adapter, a as AcquiredJob, J as JobData } from '../../index-
|
|
2
|
+
import { A as Adapter, a as AcquiredJob, J as JobData, S as ScheduleConfig, b as ScheduleData, c as ScheduleListOptions } from '../../index-C0Xg6F4E.js';
|
|
3
3
|
|
|
4
4
|
type RedisConfig = Redis | RedisOptions;
|
|
5
5
|
/**
|
|
@@ -30,6 +30,12 @@ declare class RedisAdapter implements Adapter {
|
|
|
30
30
|
size(): Promise<number>;
|
|
31
31
|
sizeOf(queue: string): Promise<number>;
|
|
32
32
|
recoverStalledJobs(queue: string, stalledThreshold: number, maxStalledCount: number): Promise<number>;
|
|
33
|
+
createSchedule(config: ScheduleConfig): Promise<string>;
|
|
34
|
+
getSchedule(id: string): Promise<ScheduleData | null>;
|
|
35
|
+
listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]>;
|
|
36
|
+
updateSchedule(id: string, updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>): Promise<void>;
|
|
37
|
+
deleteSchedule(id: string): Promise<void>;
|
|
38
|
+
claimDueSchedule(): Promise<ScheduleData | null>;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
export { RedisAdapter, redis };
|