@happyvertical/jobs 0.80.0 → 0.80.2

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,28 +1,36 @@
1
+ import { r as validateTableName, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
1
2
  import { getDatabase } from "@happyvertical/sql";
2
- import { B as BaseJobStore, v as validateTableName } from "../chunks/base-store-DlNksWvQ.js";
3
- class PostgresJobStore extends BaseJobStore {
4
- db = null;
5
- url;
6
- externalDb;
7
- tableName;
8
- enableNotify;
9
- notifyChannel;
10
- notifyListeners = /* @__PURE__ */ new Set();
11
- listening = false;
12
- constructor(config = {}) {
13
- super();
14
- this.url = config.url ?? process.env.DATABASE_URL ?? "";
15
- this.externalDb = config.db ?? null;
16
- this.tableName = validateTableName(config.tableName ?? "_jobs");
17
- this.enableNotify = config.enableNotify ?? true;
18
- this.notifyChannel = validateTableName(
19
- config.notifyChannel ?? "job_events"
20
- );
21
- }
22
- async initialize() {
23
- if (this.initialized) return;
24
- this.db = this.externalDb ?? await getDatabase({ type: "postgres", url: this.url });
25
- await this.db.query(`
3
+ //#region src/adapters/postgres.ts
4
+ /**
5
+ * PostgreSQL-based job store with NOTIFY/LISTEN support
6
+ *
7
+ * Uses PostgreSQL for job persistence with optional push-based
8
+ * notifications via NOTIFY/LISTEN for efficient job retrieval.
9
+ */
10
+ var PostgresJobStore = class extends BaseJobStore {
11
+ db = null;
12
+ url;
13
+ externalDb;
14
+ tableName;
15
+ enableNotify;
16
+ notifyChannel;
17
+ notifyListeners = /* @__PURE__ */ new Set();
18
+ listening = false;
19
+ constructor(config = {}) {
20
+ super();
21
+ this.url = config.url ?? process.env.DATABASE_URL ?? "";
22
+ this.externalDb = config.db ?? null;
23
+ this.tableName = validateTableName(config.tableName ?? "_jobs");
24
+ this.enableNotify = config.enableNotify ?? true;
25
+ this.notifyChannel = validateTableName(config.notifyChannel ?? "job_events");
26
+ }
27
+ async initialize() {
28
+ if (this.initialized) return;
29
+ this.db = this.externalDb ?? await getDatabase({
30
+ type: "postgres",
31
+ url: this.url
32
+ });
33
+ await this.db.query(`
26
34
  CREATE TABLE IF NOT EXISTS ${this.tableName} (
27
35
  id TEXT PRIMARY KEY,
28
36
  queue TEXT NOT NULL DEFAULT 'default',
@@ -45,27 +53,25 @@ class PostgresJobStore extends BaseJobStore {
45
53
  updated_at TIMESTAMPTZ NOT NULL
46
54
  )
47
55
  `);
48
- await this.db.query(`
56
+ await this.db.query(`
49
57
  CREATE INDEX IF NOT EXISTS idx_${this.tableName}_dequeue
50
58
  ON ${this.tableName} (status, queue, run_at, priority DESC)
51
59
  WHERE status = 'pending'
52
60
  `);
53
- await this.db.query(`
61
+ await this.db.query(`
54
62
  CREATE INDEX IF NOT EXISTS idx_${this.tableName}_created_at
55
63
  ON ${this.tableName} (created_at)
56
64
  `);
57
- await this.db.query(`
65
+ await this.db.query(`
58
66
  CREATE INDEX IF NOT EXISTS idx_${this.tableName}_queue
59
67
  ON ${this.tableName} (queue)
60
68
  `);
61
- if (this.enableNotify) {
62
- await this.setupNotifyTriggers();
63
- }
64
- this.initialized = true;
65
- }
66
- async setupNotifyTriggers() {
67
- if (!this.db) return;
68
- await this.db.query(`
69
+ if (this.enableNotify) await this.setupNotifyTriggers();
70
+ this.initialized = true;
71
+ }
72
+ async setupNotifyTriggers() {
73
+ if (!this.db) return;
74
+ await this.db.query(`
69
75
  CREATE OR REPLACE FUNCTION ${this.tableName}_notify_created()
70
76
  RETURNS TRIGGER AS $$
71
77
  BEGIN
@@ -80,15 +86,13 @@ class PostgresJobStore extends BaseJobStore {
80
86
  END;
81
87
  $$ LANGUAGE plpgsql
82
88
  `);
83
- await this.db.query(
84
- `DROP TRIGGER IF EXISTS ${this.tableName}_created_trigger ON ${this.tableName}`
85
- );
86
- await this.db.query(`
89
+ await this.db.query(`DROP TRIGGER IF EXISTS ${this.tableName}_created_trigger ON ${this.tableName}`);
90
+ await this.db.query(`
87
91
  CREATE TRIGGER ${this.tableName}_created_trigger
88
92
  AFTER INSERT ON ${this.tableName}
89
93
  FOR EACH ROW EXECUTE FUNCTION ${this.tableName}_notify_created()
90
94
  `);
91
- await this.db.query(`
95
+ await this.db.query(`
92
96
  CREATE OR REPLACE FUNCTION ${this.tableName}_notify_ready()
93
97
  RETURNS TRIGGER AS $$
94
98
  BEGIN
@@ -105,72 +109,66 @@ class PostgresJobStore extends BaseJobStore {
105
109
  END;
106
110
  $$ LANGUAGE plpgsql
107
111
  `);
108
- await this.db.query(
109
- `DROP TRIGGER IF EXISTS ${this.tableName}_ready_trigger ON ${this.tableName}`
110
- );
111
- await this.db.query(`
112
+ await this.db.query(`DROP TRIGGER IF EXISTS ${this.tableName}_ready_trigger ON ${this.tableName}`);
113
+ await this.db.query(`
112
114
  CREATE TRIGGER ${this.tableName}_ready_trigger
113
115
  AFTER UPDATE ON ${this.tableName}
114
116
  FOR EACH ROW EXECUTE FUNCTION ${this.tableName}_notify_ready()
115
117
  `);
116
- }
117
- /**
118
- * Start listening for PostgreSQL notifications
119
- * This enables push-based job retrieval
120
- */
121
- async startListening() {
122
- if (!this.db || !this.enableNotify || this.listening) return;
123
- try {
124
- await this.db.query(`LISTEN ${this.notifyChannel}`);
125
- this.listening = true;
126
- } catch (error) {
127
- console.warn("Could not start LISTEN, falling back to polling:", error);
128
- }
129
- }
130
- async stopListening() {
131
- if (!this.db || !this.listening) return;
132
- try {
133
- await this.db.query(`UNLISTEN ${this.notifyChannel}`);
134
- this.listening = false;
135
- } catch (error) {
136
- }
137
- }
138
- async enqueue(options) {
139
- if (!this.db) throw new Error("Store not initialized");
140
- const job = this.createJobRecord(options);
141
- await this.db.insert(this.tableName, {
142
- id: job.id,
143
- queue: job.queue,
144
- payload: JSON.stringify(job.payload),
145
- status: job.status,
146
- priority: job.priority,
147
- attempts: job.attempts,
148
- max_attempts: job.maxAttempts,
149
- run_at: job.runAt.toISOString(),
150
- started_at: job.startedAt?.toISOString() ?? null,
151
- completed_at: job.completedAt?.toISOString() ?? null,
152
- timeout: job.timeout,
153
- timeout_behavior: job.timeoutBehavior,
154
- last_error: job.lastError,
155
- result_pointer: job.resultPointer,
156
- retry_strategy: JSON.stringify(job.retryStrategy),
157
- worker_id: job.workerId,
158
- worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,
159
- created_at: job.createdAt.toISOString(),
160
- updated_at: job.updatedAt.toISOString()
161
- });
162
- await this.emitEvent("job.created", job);
163
- if (job.runAt <= /* @__PURE__ */ new Date()) {
164
- await this.emitEvent("job.ready", job);
165
- }
166
- return job;
167
- }
168
- async dequeue(queues, limit, workerId) {
169
- if (!this.db) throw new Error("Store not initialized");
170
- const now = (/* @__PURE__ */ new Date()).toISOString();
171
- const queuePlaceholders = queues.map((_, i) => `$${i + 1}`).join(", ");
172
- const { rows } = await this.db.query(
173
- `
118
+ }
119
+ /**
120
+ * Start listening for PostgreSQL notifications
121
+ * This enables push-based job retrieval
122
+ */
123
+ async startListening() {
124
+ if (!this.db || !this.enableNotify || this.listening) return;
125
+ try {
126
+ await this.db.query(`LISTEN ${this.notifyChannel}`);
127
+ this.listening = true;
128
+ } catch (error) {
129
+ console.warn("Could not start LISTEN, falling back to polling:", error);
130
+ }
131
+ }
132
+ async stopListening() {
133
+ if (!this.db || !this.listening) return;
134
+ try {
135
+ await this.db.query(`UNLISTEN ${this.notifyChannel}`);
136
+ this.listening = false;
137
+ } catch (error) {}
138
+ }
139
+ async enqueue(options) {
140
+ if (!this.db) throw new Error("Store not initialized");
141
+ const job = this.createJobRecord(options);
142
+ await this.db.insert(this.tableName, {
143
+ id: job.id,
144
+ queue: job.queue,
145
+ payload: JSON.stringify(job.payload),
146
+ status: job.status,
147
+ priority: job.priority,
148
+ attempts: job.attempts,
149
+ max_attempts: job.maxAttempts,
150
+ run_at: job.runAt.toISOString(),
151
+ started_at: job.startedAt?.toISOString() ?? null,
152
+ completed_at: job.completedAt?.toISOString() ?? null,
153
+ timeout: job.timeout,
154
+ timeout_behavior: job.timeoutBehavior,
155
+ last_error: job.lastError,
156
+ result_pointer: job.resultPointer,
157
+ retry_strategy: JSON.stringify(job.retryStrategy),
158
+ worker_id: job.workerId,
159
+ worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,
160
+ created_at: job.createdAt.toISOString(),
161
+ updated_at: job.updatedAt.toISOString()
162
+ });
163
+ await this.emitEvent("job.created", job);
164
+ if (job.runAt <= /* @__PURE__ */ new Date()) await this.emitEvent("job.ready", job);
165
+ return job;
166
+ }
167
+ async dequeue(queues, limit, workerId) {
168
+ if (!this.db) throw new Error("Store not initialized");
169
+ const now = (/* @__PURE__ */ new Date()).toISOString();
170
+ const queuePlaceholders = queues.map((_, i) => `$${i + 1}`).join(", ");
171
+ const { rows } = await this.db.query(`
174
172
  UPDATE ${this.tableName}
175
173
  SET status = 'running',
176
174
  worker_id = $${queues.length + 1},
@@ -188,178 +186,149 @@ class PostgresJobStore extends BaseJobStore {
188
186
  FOR UPDATE SKIP LOCKED
189
187
  )
190
188
  RETURNING *
191
- `,
192
- [...queues, workerId, now, limit]
193
- );
194
- const jobs = rows.map(
195
- (row) => this.parseJobRow(row)
196
- );
197
- for (const job of jobs) {
198
- await this.emitEvent("job.started", job);
199
- }
200
- return jobs;
201
- }
202
- async update(id, updates) {
203
- if (!this.db) throw new Error("Store not initialized");
204
- const setClause = [];
205
- const params = [];
206
- let paramIndex = 1;
207
- const fieldMap = {
208
- queue: "queue",
209
- payload: "payload",
210
- status: "status",
211
- priority: "priority",
212
- attempts: "attempts",
213
- maxAttempts: "max_attempts",
214
- runAt: "run_at",
215
- startedAt: "started_at",
216
- completedAt: "completed_at",
217
- timeout: "timeout",
218
- timeoutBehavior: "timeout_behavior",
219
- lastError: "last_error",
220
- resultPointer: "result_pointer",
221
- retryStrategy: "retry_strategy",
222
- workerId: "worker_id",
223
- workerHeartbeat: "worker_heartbeat"
224
- };
225
- for (const [key, value] of Object.entries(updates)) {
226
- const column = fieldMap[key];
227
- if (!column) continue;
228
- setClause.push(`${column} = $${paramIndex}`);
229
- if (value instanceof Date) {
230
- params.push(value.toISOString());
231
- } else if (typeof value === "object" && value !== null) {
232
- params.push(JSON.stringify(value));
233
- } else {
234
- params.push(value);
235
- }
236
- paramIndex++;
237
- }
238
- if (setClause.length === 0) {
239
- const job2 = await this.get(id);
240
- if (!job2) throw new Error(`Job not found: ${id}`);
241
- return job2;
242
- }
243
- setClause.push(`updated_at = $${paramIndex}`);
244
- params.push((/* @__PURE__ */ new Date()).toISOString());
245
- paramIndex++;
246
- params.push(id);
247
- await this.db.query(
248
- `UPDATE ${this.tableName} SET ${setClause.join(", ")} WHERE id = $${paramIndex}`,
249
- params
250
- );
251
- const job = await this.get(id);
252
- if (!job) throw new Error(`Job not found after update: ${id}`);
253
- if (updates.status === "completed") {
254
- await this.emitEvent("job.completed", job, {
255
- resultPointer: job.resultPointer ?? void 0
256
- });
257
- } else if (updates.status === "failed") {
258
- await this.emitEvent("job.failed", job, {
259
- error: job.lastError ?? void 0
260
- });
261
- } else if (updates.status === "cancelled") {
262
- await this.emitEvent("job.cancelled", job);
263
- }
264
- return job;
265
- }
266
- async get(id) {
267
- if (!this.db) throw new Error("Store not initialized");
268
- const row = await this.db.get(this.tableName, { id });
269
- if (!row) return null;
270
- return this.parseJobRow(row);
271
- }
272
- async list(filter) {
273
- if (!this.db) throw new Error("Store not initialized");
274
- const conditions = [];
275
- const params = [];
276
- let paramIndex = 1;
277
- if (filter.queue) {
278
- conditions.push(`queue = $${paramIndex++}`);
279
- params.push(filter.queue);
280
- }
281
- if (filter.status) {
282
- if (Array.isArray(filter.status)) {
283
- const placeholders = filter.status.map(() => `$${paramIndex++}`).join(", ");
284
- conditions.push(`status IN (${placeholders})`);
285
- params.push(...filter.status);
286
- } else {
287
- conditions.push(`status = $${paramIndex++}`);
288
- params.push(filter.status);
289
- }
290
- }
291
- if (filter.objectType) {
292
- conditions.push(`payload->>'objectType' = $${paramIndex++}`);
293
- params.push(filter.objectType);
294
- }
295
- if (filter.method) {
296
- conditions.push(`payload->>'method' = $${paramIndex++}`);
297
- params.push(filter.method);
298
- }
299
- if (filter.createdAfter) {
300
- conditions.push(`created_at > $${paramIndex++}`);
301
- params.push(filter.createdAfter.toISOString());
302
- }
303
- if (filter.createdBefore) {
304
- conditions.push(`created_at < $${paramIndex++}`);
305
- params.push(filter.createdBefore.toISOString());
306
- }
307
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
308
- const orderBy = this.buildOrderBy(filter);
309
- let limitOffset = "";
310
- if (filter.limit) {
311
- limitOffset = `LIMIT $${paramIndex++}`;
312
- params.push(filter.limit);
313
- if (filter.offset) {
314
- limitOffset += ` OFFSET $${paramIndex++}`;
315
- params.push(filter.offset);
316
- }
317
- }
318
- const { rows } = await this.db.query(
319
- `SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`,
320
- params
321
- );
322
- return rows.map((row) => this.parseJobRow(row));
323
- }
324
- async cancel(id) {
325
- if (!this.db) throw new Error("Store not initialized");
326
- const job = await this.get(id);
327
- if (!job) throw new Error(`Job not found: ${id}`);
328
- if (job.status === "completed" || job.status === "cancelled") {
329
- throw new Error(`Cannot cancel job with status: ${job.status}`);
330
- }
331
- await this.update(id, {
332
- status: "cancelled",
333
- completedAt: /* @__PURE__ */ new Date()
334
- });
335
- }
336
- async cleanup(options) {
337
- if (!this.db) throw new Error("Store not initialized");
338
- const conditions = [];
339
- const params = [];
340
- let paramIndex = 1;
341
- if (options.completedBefore) {
342
- conditions.push(
343
- `(status = 'completed' AND completed_at < $${paramIndex++})`
344
- );
345
- params.push(options.completedBefore.toISOString());
346
- }
347
- if (options.failedBefore) {
348
- conditions.push(
349
- `(status = 'failed' AND completed_at < $${paramIndex++})`
350
- );
351
- params.push(options.failedBefore.toISOString());
352
- }
353
- if (options.cancelledBefore) {
354
- conditions.push(
355
- `(status = 'cancelled' AND completed_at < $${paramIndex++})`
356
- );
357
- params.push(options.cancelledBefore.toISOString());
358
- }
359
- if (conditions.length === 0) return 0;
360
- let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(" OR ")})`;
361
- if (options.limit) {
362
- query = `
189
+ `, [
190
+ ...queues,
191
+ workerId,
192
+ now,
193
+ limit
194
+ ]);
195
+ const jobs = rows.map((row) => this.parseJobRow(row));
196
+ for (const job of jobs) await this.emitEvent("job.started", job);
197
+ return jobs;
198
+ }
199
+ async update(id, updates) {
200
+ if (!this.db) throw new Error("Store not initialized");
201
+ const setClause = [];
202
+ const params = [];
203
+ let paramIndex = 1;
204
+ const fieldMap = {
205
+ queue: "queue",
206
+ payload: "payload",
207
+ status: "status",
208
+ priority: "priority",
209
+ attempts: "attempts",
210
+ maxAttempts: "max_attempts",
211
+ runAt: "run_at",
212
+ startedAt: "started_at",
213
+ completedAt: "completed_at",
214
+ timeout: "timeout",
215
+ timeoutBehavior: "timeout_behavior",
216
+ lastError: "last_error",
217
+ resultPointer: "result_pointer",
218
+ retryStrategy: "retry_strategy",
219
+ workerId: "worker_id",
220
+ workerHeartbeat: "worker_heartbeat"
221
+ };
222
+ for (const [key, value] of Object.entries(updates)) {
223
+ const column = fieldMap[key];
224
+ if (!column) continue;
225
+ setClause.push(`${column} = $${paramIndex}`);
226
+ if (value instanceof Date) params.push(value.toISOString());
227
+ else if (typeof value === "object" && value !== null) params.push(JSON.stringify(value));
228
+ else params.push(value);
229
+ paramIndex++;
230
+ }
231
+ if (setClause.length === 0) {
232
+ const job = await this.get(id);
233
+ if (!job) throw new Error(`Job not found: ${id}`);
234
+ return job;
235
+ }
236
+ setClause.push(`updated_at = $${paramIndex}`);
237
+ params.push((/* @__PURE__ */ new Date()).toISOString());
238
+ paramIndex++;
239
+ params.push(id);
240
+ await this.db.query(`UPDATE ${this.tableName} SET ${setClause.join(", ")} WHERE id = $${paramIndex}`, params);
241
+ const job = await this.get(id);
242
+ if (!job) throw new Error(`Job not found after update: ${id}`);
243
+ if (updates.status === "completed") await this.emitEvent("job.completed", job, { resultPointer: job.resultPointer ?? void 0 });
244
+ else if (updates.status === "failed") await this.emitEvent("job.failed", job, { error: job.lastError ?? void 0 });
245
+ else if (updates.status === "cancelled") await this.emitEvent("job.cancelled", job);
246
+ return job;
247
+ }
248
+ async get(id) {
249
+ if (!this.db) throw new Error("Store not initialized");
250
+ const row = await this.db.get(this.tableName, { id });
251
+ if (!row) return null;
252
+ return this.parseJobRow(row);
253
+ }
254
+ async list(filter) {
255
+ if (!this.db) throw new Error("Store not initialized");
256
+ const conditions = [];
257
+ const params = [];
258
+ let paramIndex = 1;
259
+ if (filter.queue) {
260
+ conditions.push(`queue = $${paramIndex++}`);
261
+ params.push(filter.queue);
262
+ }
263
+ if (filter.status) if (Array.isArray(filter.status)) {
264
+ const placeholders = filter.status.map(() => `$${paramIndex++}`).join(", ");
265
+ conditions.push(`status IN (${placeholders})`);
266
+ params.push(...filter.status);
267
+ } else {
268
+ conditions.push(`status = $${paramIndex++}`);
269
+ params.push(filter.status);
270
+ }
271
+ if (filter.objectType) {
272
+ conditions.push(`payload->>'objectType' = $${paramIndex++}`);
273
+ params.push(filter.objectType);
274
+ }
275
+ if (filter.method) {
276
+ conditions.push(`payload->>'method' = $${paramIndex++}`);
277
+ params.push(filter.method);
278
+ }
279
+ if (filter.createdAfter) {
280
+ conditions.push(`created_at > $${paramIndex++}`);
281
+ params.push(filter.createdAfter.toISOString());
282
+ }
283
+ if (filter.createdBefore) {
284
+ conditions.push(`created_at < $${paramIndex++}`);
285
+ params.push(filter.createdBefore.toISOString());
286
+ }
287
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
288
+ const orderBy = this.buildOrderBy(filter);
289
+ let limitOffset = "";
290
+ if (filter.limit) {
291
+ limitOffset = `LIMIT $${paramIndex++}`;
292
+ params.push(filter.limit);
293
+ if (filter.offset) {
294
+ limitOffset += ` OFFSET $${paramIndex++}`;
295
+ params.push(filter.offset);
296
+ }
297
+ }
298
+ const { rows } = await this.db.query(`SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`, params);
299
+ return rows.map((row) => this.parseJobRow(row));
300
+ }
301
+ async cancel(id) {
302
+ if (!this.db) throw new Error("Store not initialized");
303
+ const job = await this.get(id);
304
+ if (!job) throw new Error(`Job not found: ${id}`);
305
+ if (job.status === "completed" || job.status === "cancelled") throw new Error(`Cannot cancel job with status: ${job.status}`);
306
+ await this.update(id, {
307
+ status: "cancelled",
308
+ completedAt: /* @__PURE__ */ new Date()
309
+ });
310
+ }
311
+ async cleanup(options) {
312
+ if (!this.db) throw new Error("Store not initialized");
313
+ const conditions = [];
314
+ const params = [];
315
+ let paramIndex = 1;
316
+ if (options.completedBefore) {
317
+ conditions.push(`(status = 'completed' AND completed_at < $${paramIndex++})`);
318
+ params.push(options.completedBefore.toISOString());
319
+ }
320
+ if (options.failedBefore) {
321
+ conditions.push(`(status = 'failed' AND completed_at < $${paramIndex++})`);
322
+ params.push(options.failedBefore.toISOString());
323
+ }
324
+ if (options.cancelledBefore) {
325
+ conditions.push(`(status = 'cancelled' AND completed_at < $${paramIndex++})`);
326
+ params.push(options.cancelledBefore.toISOString());
327
+ }
328
+ if (conditions.length === 0) return 0;
329
+ let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(" OR ")})`;
330
+ if (options.limit) {
331
+ query = `
363
332
  DELETE FROM ${this.tableName}
364
333
  WHERE id IN (
365
334
  SELECT id FROM ${this.tableName}
@@ -367,71 +336,62 @@ class PostgresJobStore extends BaseJobStore {
367
336
  LIMIT $${paramIndex}
368
337
  )
369
338
  `;
370
- params.push(options.limit);
371
- }
372
- const result = await this.db.query(query, params);
373
- return result.rowCount ?? 0;
374
- }
375
- async heartbeat(jobId, workerId) {
376
- if (!this.db) throw new Error("Store not initialized");
377
- const now = (/* @__PURE__ */ new Date()).toISOString();
378
- await this.db.query(
379
- `
339
+ params.push(options.limit);
340
+ }
341
+ return (await this.db.query(query, params)).rowCount ?? 0;
342
+ }
343
+ async heartbeat(jobId, workerId) {
344
+ if (!this.db) throw new Error("Store not initialized");
345
+ const now = (/* @__PURE__ */ new Date()).toISOString();
346
+ await this.db.query(`
380
347
  UPDATE ${this.tableName}
381
348
  SET worker_heartbeat = $1, updated_at = $1
382
349
  WHERE id = $2 AND worker_id = $3 AND status = 'running'
383
- `,
384
- [now, jobId, workerId]
385
- );
386
- }
387
- async stats(queue) {
388
- if (!this.db) throw new Error("Store not initialized");
389
- const params = [];
390
- let paramIndex = 1;
391
- const queueFilter = queue ? `WHERE queue = $${paramIndex++}` : "";
392
- if (queue) params.push(queue);
393
- const { rows: countRows } = await this.db.query(
394
- `
350
+ `, [
351
+ now,
352
+ jobId,
353
+ workerId
354
+ ]);
355
+ }
356
+ async stats(queue) {
357
+ if (!this.db) throw new Error("Store not initialized");
358
+ const params = [];
359
+ let paramIndex = 1;
360
+ const queueFilter = queue ? `WHERE queue = $${paramIndex++}` : "";
361
+ if (queue) params.push(queue);
362
+ const { rows: countRows } = await this.db.query(`
395
363
  SELECT status, COUNT(*)::int as count
396
364
  FROM ${this.tableName}
397
365
  ${queueFilter}
398
366
  GROUP BY status
399
- `,
400
- params
401
- );
402
- const counts = {};
403
- for (const row of countRows) {
404
- counts[row.status] = row.count;
405
- }
406
- const { rows: durationRows } = await this.db.query(
407
- `
367
+ `, params);
368
+ const counts = {};
369
+ for (const row of countRows) counts[row.status] = row.count;
370
+ const { rows: durationRows } = await this.db.query(`
408
371
  SELECT AVG(EXTRACT(EPOCH FROM (completed_at - started_at)) * 1000)::float as avg_duration
409
372
  FROM ${this.tableName}
410
373
  WHERE status = 'completed'
411
374
  AND started_at IS NOT NULL
412
375
  AND completed_at IS NOT NULL
413
376
  ${queue ? `AND queue = $1` : ""}
414
- `,
415
- queue ? [queue] : []
416
- );
417
- const avgDuration = durationRows[0]?.avg_duration ?? null;
418
- return {
419
- pending: counts["pending"] ?? 0,
420
- running: counts["running"] ?? 0,
421
- completed: counts["completed"] ?? 0,
422
- failed: counts["failed"] ?? 0,
423
- cancelled: counts["cancelled"] ?? 0,
424
- avgDuration: avgDuration ? Math.round(avgDuration) : null
425
- };
426
- }
427
- async close() {
428
- await this.stopListening();
429
- this.db = null;
430
- this.initialized = false;
431
- }
432
- }
433
- export {
434
- PostgresJobStore,
435
- PostgresJobStore as default
377
+ `, queue ? [queue] : []);
378
+ const avgDuration = durationRows[0]?.avg_duration ?? null;
379
+ return {
380
+ pending: counts["pending"] ?? 0,
381
+ running: counts["running"] ?? 0,
382
+ completed: counts["completed"] ?? 0,
383
+ failed: counts["failed"] ?? 0,
384
+ cancelled: counts["cancelled"] ?? 0,
385
+ avgDuration: avgDuration ? Math.round(avgDuration) : null
386
+ };
387
+ }
388
+ async close() {
389
+ await this.stopListening();
390
+ this.db = null;
391
+ this.initialized = false;
392
+ }
436
393
  };
437
- //# sourceMappingURL=postgres.js.map
394
+ //#endregion
395
+ export { PostgresJobStore, PostgresJobStore as default };
396
+
397
+ //# sourceMappingURL=postgres.js.map