@boringnode/queue 0.0.1-alpha.3 → 0.1.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.
@@ -1,4 +1,12 @@
1
+ import {
2
+ calculateScore
3
+ } from "../../chunk-NPQKBCCY.js";
4
+ import {
5
+ DEFAULT_PRIORITY
6
+ } from "../../chunk-SMOKFZ46.js";
7
+
1
8
  // src/drivers/knex_adapter.ts
9
+ import { randomUUID } from "crypto";
2
10
  import KnexPkg from "knex";
3
11
  function knex(config, tableName) {
4
12
  return () => {
@@ -9,26 +17,32 @@ function knex(config, tableName) {
9
17
  }
10
18
  var KnexAdapter = class {
11
19
  #connection;
12
- #tableName;
20
+ #jobsTable;
21
+ #schedulesTable;
13
22
  #ownsConnection;
14
23
  #workerId = "";
15
24
  #initialized = false;
16
25
  constructor(config) {
17
26
  this.#connection = config.connection;
18
- this.#tableName = config.tableName ?? "queue_jobs";
27
+ this.#jobsTable = config.tableName ?? "queue_jobs";
28
+ this.#schedulesTable = config.schedulesTableName ?? "queue_schedules";
19
29
  this.#ownsConnection = config.ownsConnection ?? false;
20
30
  }
21
31
  setWorkerId(workerId) {
22
32
  this.#workerId = workerId;
23
33
  }
24
34
  /**
25
- * Ensure the jobs table exists.
26
- * Creates it if not exists, handles race conditions.
35
+ * Ensure all required tables exist.
36
+ * Creates them if not exists, handles race conditions.
27
37
  */
28
- async #ensureTableExists() {
38
+ async #ensureTables() {
29
39
  if (this.#initialized) return;
40
+ await Promise.all([this.#createJobsTable(), this.#createSchedulesTable()]);
41
+ this.#initialized = true;
42
+ }
43
+ async #createJobsTable() {
30
44
  try {
31
- await this.#connection.schema.createTable(this.#tableName, (table) => {
45
+ await this.#connection.schema.createTable(this.#jobsTable, (table) => {
32
46
  table.string("id", 255).notNullable();
33
47
  table.string("queue", 255).notNullable();
34
48
  table.enu("status", ["pending", "active", "delayed"]).notNullable();
@@ -42,12 +56,37 @@ var KnexAdapter = class {
42
56
  table.index(["queue", "status", "execute_at"]);
43
57
  });
44
58
  } catch {
45
- const hasTable = await this.#connection.schema.hasTable(this.#tableName);
59
+ const hasTable = await this.#connection.schema.hasTable(this.#jobsTable);
46
60
  if (!hasTable) {
47
- throw new Error(`Failed to create table "${this.#tableName}"`);
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("job_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}"`);
48
88
  }
49
89
  }
50
- this.#initialized = true;
51
90
  }
52
91
  async destroy() {
53
92
  if (this.#ownsConnection) {
@@ -58,15 +97,19 @@ var KnexAdapter = class {
58
97
  return this.popFrom("default");
59
98
  }
60
99
  async popFrom(queue) {
61
- await this.#ensureTableExists();
100
+ await this.#ensureTables();
62
101
  const now = Date.now();
63
102
  await this.#processDelayedJobs(queue, now);
64
103
  return this.#connection.transaction(async (trx) => {
65
- const job = await trx(this.#tableName).where("queue", queue).where("status", "pending").orderBy("score", "asc").first();
104
+ let query = trx(this.#jobsTable).where("queue", queue).where("status", "pending").orderBy("score", "asc");
105
+ if (this.#supportsSkipLocked()) {
106
+ query = query.forUpdate().skipLocked();
107
+ }
108
+ const job = await query.first();
66
109
  if (!job) {
67
110
  return null;
68
111
  }
69
- await trx(this.#tableName).where("id", job.id).where("queue", queue).update({
112
+ await trx(this.#jobsTable).where("id", job.id).where("queue", queue).update({
70
113
  status: "active",
71
114
  worker_id: this.#workerId,
72
115
  acquired_at: now
@@ -78,38 +121,53 @@ var KnexAdapter = class {
78
121
  };
79
122
  });
80
123
  }
124
+ /**
125
+ * Check if the database supports FOR UPDATE SKIP LOCKED.
126
+ * PostgreSQL 9.5+, MySQL 8.0+, and MariaDB 10.6+ support it.
127
+ * SQLite does not, but it's single-writer so it doesn't need it.
128
+ */
129
+ #supportsSkipLocked() {
130
+ const client = this.#connection.client.config.client;
131
+ return client === "pg" || client === "mysql" || client === "mysql2" || client === "mariadb";
132
+ }
81
133
  async #processDelayedJobs(queue, now) {
82
- const delayedJobs = await this.#connection(this.#tableName).where("queue", queue).where("status", "delayed").where("execute_at", "<=", now).select("id", "data");
83
- if (delayedJobs.length === 0) return;
84
- for (const job of delayedJobs) {
85
- const jobData = JSON.parse(job.data);
86
- const priority = jobData.priority ?? 5;
87
- const score = priority * 1e13 + now;
88
- await this.#connection(this.#tableName).where("id", job.id).where("queue", queue).update({
89
- status: "pending",
90
- score,
91
- execute_at: null
92
- });
93
- }
134
+ await this.#connection.transaction(async (trx) => {
135
+ let query = trx(this.#jobsTable).where("queue", queue).where("status", "delayed").where("execute_at", "<=", now).select("id", "data");
136
+ if (this.#supportsSkipLocked()) {
137
+ query = query.forUpdate().skipLocked();
138
+ }
139
+ const delayedJobs = await query;
140
+ if (delayedJobs.length === 0) return;
141
+ for (const job of delayedJobs) {
142
+ const jobData = JSON.parse(job.data);
143
+ const priority = jobData.priority ?? DEFAULT_PRIORITY;
144
+ const score = calculateScore(priority, now);
145
+ await trx(this.#jobsTable).where("id", job.id).where("queue", queue).update({
146
+ status: "pending",
147
+ score,
148
+ execute_at: null
149
+ });
150
+ }
151
+ });
94
152
  }
95
153
  async completeJob(jobId, queue) {
96
- await this.#ensureTableExists();
97
- await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).delete();
154
+ await this.#ensureTables();
155
+ await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).delete();
98
156
  }
99
157
  async failJob(jobId, queue, _error) {
100
- await this.#ensureTableExists();
101
- await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).delete();
158
+ await this.#ensureTables();
159
+ await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).delete();
102
160
  }
103
161
  async retryJob(jobId, queue, retryAt) {
104
- await this.#ensureTableExists();
162
+ await this.#ensureTables();
105
163
  const now = Date.now();
106
- const activeJob = await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).where("status", "active").first();
164
+ const activeJob = await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).where("status", "active").first();
107
165
  if (!activeJob) return;
108
166
  const jobData = JSON.parse(activeJob.data);
109
167
  jobData.attempts = (jobData.attempts || 0) + 1;
110
168
  const updatedData = JSON.stringify(jobData);
111
169
  if (retryAt && retryAt.getTime() > now) {
112
- await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).update({
170
+ await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).update({
113
171
  status: "delayed",
114
172
  data: updatedData,
115
173
  worker_id: null,
@@ -118,9 +176,9 @@ var KnexAdapter = class {
118
176
  execute_at: retryAt.getTime()
119
177
  });
120
178
  } else {
121
- const priority = jobData.priority ?? 5;
122
- const score = priority * 1e13 + now;
123
- await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).update({
179
+ const priority = jobData.priority ?? DEFAULT_PRIORITY;
180
+ const score = calculateScore(priority, now);
181
+ await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).update({
124
182
  status: "pending",
125
183
  data: updatedData,
126
184
  worker_id: null,
@@ -134,11 +192,11 @@ var KnexAdapter = class {
134
192
  return this.pushOn("default", jobData);
135
193
  }
136
194
  async pushOn(queue, jobData) {
137
- await this.#ensureTableExists();
138
- const priority = jobData.priority ?? 5;
195
+ await this.#ensureTables();
196
+ const priority = jobData.priority ?? DEFAULT_PRIORITY;
139
197
  const timestamp = Date.now();
140
- const score = priority * 1e13 + timestamp;
141
- await this.#connection(this.#tableName).insert({
198
+ const score = calculateScore(priority, timestamp);
199
+ await this.#connection(this.#jobsTable).insert({
142
200
  id: jobData.id,
143
201
  queue,
144
202
  status: "pending",
@@ -150,9 +208,9 @@ var KnexAdapter = class {
150
208
  return this.pushLaterOn("default", jobData, delay);
151
209
  }
152
210
  async pushLaterOn(queue, jobData, delay) {
153
- await this.#ensureTableExists();
211
+ await this.#ensureTables();
154
212
  const executeAt = Date.now() + delay;
155
- await this.#connection(this.#tableName).insert({
213
+ await this.#connection(this.#jobsTable).insert({
156
214
  id: jobData.id,
157
215
  queue,
158
216
  status: "delayed",
@@ -164,10 +222,163 @@ var KnexAdapter = class {
164
222
  return this.sizeOf("default");
165
223
  }
166
224
  async sizeOf(queue) {
167
- await this.#ensureTableExists();
168
- const result = await this.#connection(this.#tableName).where("queue", queue).where("status", "pending").count("* as count").first();
225
+ await this.#ensureTables();
226
+ const result = await this.#connection(this.#jobsTable).where("queue", queue).where("status", "pending").count("* as count").first();
169
227
  return Number(result?.count ?? 0);
170
228
  }
229
+ async recoverStalledJobs(queue, stalledThreshold, maxStalledCount) {
230
+ await this.#ensureTables();
231
+ const now = Date.now();
232
+ const stalledCutoff = now - stalledThreshold;
233
+ return this.#connection.transaction(async (trx) => {
234
+ let recovered = 0;
235
+ let query = trx(this.#jobsTable).where("queue", queue).where("status", "active").where("acquired_at", "<", stalledCutoff).select("id", "data");
236
+ if (this.#supportsSkipLocked()) {
237
+ query = query.forUpdate().skipLocked();
238
+ }
239
+ const stalledJobs = await query;
240
+ for (const row of stalledJobs) {
241
+ const jobData = JSON.parse(row.data);
242
+ const currentStalledCount = jobData.stalledCount ?? 0;
243
+ if (currentStalledCount >= maxStalledCount) {
244
+ await trx(this.#jobsTable).where("id", row.id).where("queue", queue).delete();
245
+ } else {
246
+ jobData.stalledCount = currentStalledCount + 1;
247
+ const priority = jobData.priority ?? DEFAULT_PRIORITY;
248
+ const score = calculateScore(priority, now);
249
+ await trx(this.#jobsTable).where("id", row.id).where("queue", queue).update({
250
+ status: "pending",
251
+ data: JSON.stringify(jobData),
252
+ worker_id: null,
253
+ acquired_at: null,
254
+ score
255
+ });
256
+ recovered++;
257
+ }
258
+ }
259
+ return recovered;
260
+ });
261
+ }
262
+ async createSchedule(config) {
263
+ await this.#ensureTables();
264
+ const id = config.id ?? randomUUID();
265
+ const data = {
266
+ id,
267
+ job_name: config.jobName,
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
+ job_name: data.job_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
+ jobName: row.job_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
+ }
171
382
  };
172
383
  export {
173
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'\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 // Select the highest priority job (lowest score)\n const job = await trx(this.#tableName)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n .first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n await trx(this.#tableName)\n .where('id', job.id)\n .where('queue', queue)\n .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 async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Get all ready delayed jobs\n const delayedJobs = await this.#connection(this.#tableName)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\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 ?? 5\n const score = priority * 1e13 + now\n\n await this.#connection(this.#tableName)\n .where('id', job.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n }\n\n async completeJob(jobId: string, queue: string): Promise<void> {\n await this.#ensureTableExists()\n\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .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)\n .where('id', jobId)\n .where('queue', queue)\n .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)\n .where('id', jobId)\n .where('queue', queue)\n .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 ?? 5\n const score = priority * 1e13 + now\n\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .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 ?? 5\n const timestamp = Date.now()\n const score = priority * 1e13 + 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"],"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('job_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 job_name: config.jobName,\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 job_name: data.job_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 jobName: row.job_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, b as JobData } from '../../job-Bd_c2lFK.js';
2
+ import { A as Adapter, a as AcquiredJob, J as JobData, S as ScheduleConfig, b as ScheduleData, c as ScheduleListOptions } from '../../index-2Ng_OpVK.js';
3
3
 
4
4
  type RedisConfig = Redis | RedisOptions;
5
5
  /**
@@ -20,7 +20,6 @@ declare class RedisAdapter implements Adapter {
20
20
  destroy(): Promise<void>;
21
21
  pop(): Promise<AcquiredJob | null>;
22
22
  popFrom(queue: string): Promise<AcquiredJob | null>;
23
- popAndWait(queue: string, timeout: number): Promise<AcquiredJob | null>;
24
23
  completeJob(jobId: string, queue: string): Promise<void>;
25
24
  failJob(jobId: string, queue: string, _error?: Error): Promise<void>;
26
25
  retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void>;
@@ -30,6 +29,13 @@ declare class RedisAdapter implements Adapter {
30
29
  pushOn(queue: string, jobData: JobData): Promise<void>;
31
30
  size(): Promise<number>;
32
31
  sizeOf(queue: string): Promise<number>;
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 };