@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.
- package/dist/adapters/bull.js +343 -346
- package/dist/adapters/bull.js.map +1 -1
- package/dist/adapters/bullmq.js +351 -388
- package/dist/adapters/bullmq.js.map +1 -1
- package/dist/adapters/cloud-tasks.js +307 -333
- package/dist/adapters/cloud-tasks.js.map +1 -1
- package/dist/adapters/postgres.js +288 -328
- package/dist/adapters/postgres.js.map +1 -1
- package/dist/adapters/sqlite.js +236 -258
- package/dist/adapters/sqlite.js.map +1 -1
- package/dist/adapters/sqs.js +363 -408
- package/dist/adapters/sqs.js.map +1 -1
- package/dist/chunks/base-store-DIasEzL0.js +386 -0
- package/dist/chunks/base-store-DIasEzL0.js.map +1 -0
- package/dist/cli/claude-context.js +17 -17
- package/dist/cli/claude-context.js.map +1 -1
- package/dist/index.js +219 -242
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/dist/chunks/base-store-DlNksWvQ.js +0 -324
- package/dist/chunks/base-store-DlNksWvQ.js.map +0 -1
package/dist/adapters/sqlite.js
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
|
+
import { r as validateTableName, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
|
|
1
2
|
import { getDatabase, syncSchema } from "@happyvertical/sql";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
3
|
+
//#region src/adapters/sqlite.ts
|
|
4
|
+
/**
|
|
5
|
+
* SQLite-based job store
|
|
6
|
+
*
|
|
7
|
+
* Uses SQLite for job persistence. Supports polling-based job retrieval.
|
|
8
|
+
* Good for single-instance deployments or development.
|
|
9
|
+
*/
|
|
10
|
+
var SqliteJobStore = class extends BaseJobStore {
|
|
11
|
+
db = null;
|
|
12
|
+
url;
|
|
13
|
+
externalDb;
|
|
14
|
+
tableName;
|
|
15
|
+
capabilities;
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
super();
|
|
18
|
+
this.url = config.url ?? ":memory:";
|
|
19
|
+
this.externalDb = config.db ?? null;
|
|
20
|
+
this.tableName = validateTableName(config.tableName ?? "_jobs");
|
|
21
|
+
this.capabilities = config.capabilities;
|
|
22
|
+
}
|
|
23
|
+
async initialize() {
|
|
24
|
+
if (this.initialized) return;
|
|
25
|
+
this.db = this.externalDb ?? await getDatabase({
|
|
26
|
+
type: "sqlite",
|
|
27
|
+
url: this.url,
|
|
28
|
+
capabilities: this.capabilities
|
|
29
|
+
});
|
|
30
|
+
const schema = `
|
|
24
31
|
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|
25
32
|
id TEXT PRIMARY KEY,
|
|
26
33
|
queue TEXT NOT NULL DEFAULT 'default',
|
|
@@ -47,59 +54,60 @@ class SqliteJobStore extends BaseJobStore {
|
|
|
47
54
|
CREATE INDEX IF NOT EXISTS "idx_${this.tableName}_created_at" ON "${this.tableName}" (created_at);
|
|
48
55
|
CREATE INDEX IF NOT EXISTS "idx_${this.tableName}_queue" ON "${this.tableName}" (queue);
|
|
49
56
|
`;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
57
|
+
await syncSchema({
|
|
58
|
+
db: this.db,
|
|
59
|
+
schema
|
|
60
|
+
});
|
|
61
|
+
this.initialized = true;
|
|
62
|
+
}
|
|
63
|
+
async enqueue(options) {
|
|
64
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
65
|
+
const job = this.createJobRecord(options);
|
|
66
|
+
await this.db.insert(this.tableName, {
|
|
67
|
+
id: job.id,
|
|
68
|
+
queue: job.queue,
|
|
69
|
+
payload: JSON.stringify(job.payload),
|
|
70
|
+
status: job.status,
|
|
71
|
+
priority: job.priority,
|
|
72
|
+
attempts: job.attempts,
|
|
73
|
+
max_attempts: job.maxAttempts,
|
|
74
|
+
run_at: job.runAt.toISOString(),
|
|
75
|
+
started_at: job.startedAt?.toISOString() ?? null,
|
|
76
|
+
completed_at: job.completedAt?.toISOString() ?? null,
|
|
77
|
+
timeout: job.timeout,
|
|
78
|
+
timeout_behavior: job.timeoutBehavior,
|
|
79
|
+
last_error: job.lastError,
|
|
80
|
+
result_pointer: job.resultPointer,
|
|
81
|
+
retry_strategy: JSON.stringify(job.retryStrategy),
|
|
82
|
+
worker_id: job.workerId,
|
|
83
|
+
worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,
|
|
84
|
+
created_at: job.createdAt.toISOString(),
|
|
85
|
+
updated_at: job.updatedAt.toISOString()
|
|
86
|
+
});
|
|
87
|
+
await this.emitEvent("job.created", job);
|
|
88
|
+
if (job.runAt <= /* @__PURE__ */ new Date()) await this.emitEvent("job.ready", job);
|
|
89
|
+
return job;
|
|
90
|
+
}
|
|
91
|
+
async dequeue(queues, limit, workerId) {
|
|
92
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
93
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
94
|
+
const queuePlaceholders = queues.map(() => "?").join(", ");
|
|
95
|
+
const { rows } = await this.db.query(`
|
|
89
96
|
SELECT * FROM ${this.tableName}
|
|
90
97
|
WHERE status = 'pending'
|
|
91
98
|
AND queue IN (${queuePlaceholders})
|
|
92
99
|
AND run_at <= ?
|
|
93
100
|
ORDER BY priority DESC, run_at ASC
|
|
94
101
|
LIMIT ?
|
|
95
|
-
`,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
`, [
|
|
103
|
+
...queues,
|
|
104
|
+
now,
|
|
105
|
+
limit
|
|
106
|
+
]);
|
|
107
|
+
if (!rows.length) return [];
|
|
108
|
+
const jobIds = rows.map((r) => r.id);
|
|
109
|
+
const idPlaceholders = jobIds.map(() => "?").join(", ");
|
|
110
|
+
await this.db.query(`
|
|
103
111
|
UPDATE ${this.tableName}
|
|
104
112
|
SET status = 'running',
|
|
105
113
|
worker_id = ?,
|
|
@@ -109,133 +117,115 @@ class SqliteJobStore extends BaseJobStore {
|
|
|
109
117
|
updated_at = ?
|
|
110
118
|
WHERE id IN (${idPlaceholders})
|
|
111
119
|
AND status = 'pending'
|
|
112
|
-
`,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const conditions = [];
|
|
222
|
-
const params = [];
|
|
223
|
-
if (options.completedBefore) {
|
|
224
|
-
conditions.push("(status = 'completed' AND completed_at < ?)");
|
|
225
|
-
params.push(options.completedBefore.toISOString());
|
|
226
|
-
}
|
|
227
|
-
if (options.failedBefore) {
|
|
228
|
-
conditions.push("(status = 'failed' AND completed_at < ?)");
|
|
229
|
-
params.push(options.failedBefore.toISOString());
|
|
230
|
-
}
|
|
231
|
-
if (options.cancelledBefore) {
|
|
232
|
-
conditions.push("(status = 'cancelled' AND completed_at < ?)");
|
|
233
|
-
params.push(options.cancelledBefore.toISOString());
|
|
234
|
-
}
|
|
235
|
-
if (conditions.length === 0) return 0;
|
|
236
|
-
let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(" OR ")})`;
|
|
237
|
-
if (options.limit) {
|
|
238
|
-
query = `
|
|
120
|
+
`, [
|
|
121
|
+
workerId,
|
|
122
|
+
now,
|
|
123
|
+
now,
|
|
124
|
+
now,
|
|
125
|
+
...jobIds
|
|
126
|
+
]);
|
|
127
|
+
const jobs = rows.map((row) => {
|
|
128
|
+
const job = this.parseJobRow(row);
|
|
129
|
+
job.status = "running";
|
|
130
|
+
job.workerId = workerId;
|
|
131
|
+
job.workerHeartbeat = /* @__PURE__ */ new Date();
|
|
132
|
+
job.startedAt = /* @__PURE__ */ new Date();
|
|
133
|
+
job.attempts += 1;
|
|
134
|
+
return job;
|
|
135
|
+
});
|
|
136
|
+
for (const job of jobs) await this.emitEvent("job.started", job);
|
|
137
|
+
return jobs;
|
|
138
|
+
}
|
|
139
|
+
async update(id, updates) {
|
|
140
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
141
|
+
const setClause = [];
|
|
142
|
+
const params = [];
|
|
143
|
+
const fieldMap = {
|
|
144
|
+
queue: "queue",
|
|
145
|
+
payload: "payload",
|
|
146
|
+
status: "status",
|
|
147
|
+
priority: "priority",
|
|
148
|
+
attempts: "attempts",
|
|
149
|
+
maxAttempts: "max_attempts",
|
|
150
|
+
runAt: "run_at",
|
|
151
|
+
startedAt: "started_at",
|
|
152
|
+
completedAt: "completed_at",
|
|
153
|
+
timeout: "timeout",
|
|
154
|
+
timeoutBehavior: "timeout_behavior",
|
|
155
|
+
lastError: "last_error",
|
|
156
|
+
resultPointer: "result_pointer",
|
|
157
|
+
retryStrategy: "retry_strategy",
|
|
158
|
+
workerId: "worker_id",
|
|
159
|
+
workerHeartbeat: "worker_heartbeat"
|
|
160
|
+
};
|
|
161
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
162
|
+
const column = fieldMap[key];
|
|
163
|
+
if (!column) continue;
|
|
164
|
+
setClause.push(`${column} = ?`);
|
|
165
|
+
if (value instanceof Date) params.push(value.toISOString());
|
|
166
|
+
else if (typeof value === "object" && value !== null) params.push(JSON.stringify(value));
|
|
167
|
+
else params.push(value);
|
|
168
|
+
}
|
|
169
|
+
if (setClause.length === 0) {
|
|
170
|
+
const job = await this.get(id);
|
|
171
|
+
if (!job) throw new Error(`Job not found: ${id}`);
|
|
172
|
+
return job;
|
|
173
|
+
}
|
|
174
|
+
setClause.push("updated_at = ?");
|
|
175
|
+
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
176
|
+
params.push(id);
|
|
177
|
+
await this.db.query(`UPDATE ${this.tableName} SET ${setClause.join(", ")} WHERE id = ?`, params);
|
|
178
|
+
const job = await this.get(id);
|
|
179
|
+
if (!job) throw new Error(`Job not found after update: ${id}`);
|
|
180
|
+
if (updates.status === "completed") await this.emitEvent("job.completed", job, { resultPointer: job.resultPointer ?? void 0 });
|
|
181
|
+
else if (updates.status === "failed") await this.emitEvent("job.failed", job, { error: job.lastError ?? void 0 });
|
|
182
|
+
else if (updates.status === "cancelled") await this.emitEvent("job.cancelled", job);
|
|
183
|
+
return job;
|
|
184
|
+
}
|
|
185
|
+
async get(id) {
|
|
186
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
187
|
+
const row = await this.db.get(this.tableName, { id });
|
|
188
|
+
if (!row) return null;
|
|
189
|
+
return this.parseJobRow(row);
|
|
190
|
+
}
|
|
191
|
+
async list(filter) {
|
|
192
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
193
|
+
const { where, params: whereParams } = this.buildFilterWhere(filter);
|
|
194
|
+
const orderBy = this.buildOrderBy(filter);
|
|
195
|
+
const { clause: limitOffset, params: limitParams } = this.buildLimitOffset(filter);
|
|
196
|
+
const { rows } = await this.db.query(`SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`, [...whereParams, ...limitParams]);
|
|
197
|
+
return rows.map((row) => this.parseJobRow(row));
|
|
198
|
+
}
|
|
199
|
+
async cancel(id) {
|
|
200
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
201
|
+
const job = await this.get(id);
|
|
202
|
+
if (!job) throw new Error(`Job not found: ${id}`);
|
|
203
|
+
if (job.status === "completed" || job.status === "cancelled") throw new Error(`Cannot cancel job with status: ${job.status}`);
|
|
204
|
+
await this.update(id, {
|
|
205
|
+
status: "cancelled",
|
|
206
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async cleanup(options) {
|
|
210
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
211
|
+
const conditions = [];
|
|
212
|
+
const params = [];
|
|
213
|
+
if (options.completedBefore) {
|
|
214
|
+
conditions.push("(status = 'completed' AND completed_at < ?)");
|
|
215
|
+
params.push(options.completedBefore.toISOString());
|
|
216
|
+
}
|
|
217
|
+
if (options.failedBefore) {
|
|
218
|
+
conditions.push("(status = 'failed' AND completed_at < ?)");
|
|
219
|
+
params.push(options.failedBefore.toISOString());
|
|
220
|
+
}
|
|
221
|
+
if (options.cancelledBefore) {
|
|
222
|
+
conditions.push("(status = 'cancelled' AND completed_at < ?)");
|
|
223
|
+
params.push(options.cancelledBefore.toISOString());
|
|
224
|
+
}
|
|
225
|
+
if (conditions.length === 0) return 0;
|
|
226
|
+
let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(" OR ")})`;
|
|
227
|
+
if (options.limit) {
|
|
228
|
+
query = `
|
|
239
229
|
DELETE FROM ${this.tableName}
|
|
240
230
|
WHERE id IN (
|
|
241
231
|
SELECT id FROM ${this.tableName}
|
|
@@ -243,41 +233,36 @@ class SqliteJobStore extends BaseJobStore {
|
|
|
243
233
|
LIMIT ?
|
|
244
234
|
)
|
|
245
235
|
`;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
await this.db.query(
|
|
254
|
-
`
|
|
236
|
+
params.push(options.limit);
|
|
237
|
+
}
|
|
238
|
+
return (await this.db.query(query, params)).rowCount ?? 0;
|
|
239
|
+
}
|
|
240
|
+
async heartbeat(jobId, workerId) {
|
|
241
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
242
|
+
await this.db.query(`
|
|
255
243
|
UPDATE ${this.tableName}
|
|
256
244
|
SET worker_heartbeat = ?, updated_at = ?
|
|
257
245
|
WHERE id = ? AND worker_id = ? AND status = 'running'
|
|
258
|
-
`,
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
246
|
+
`, [
|
|
247
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
248
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
249
|
+
jobId,
|
|
250
|
+
workerId
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
async stats(queue) {
|
|
254
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
255
|
+
const queueFilter = queue ? "WHERE queue = ?" : "";
|
|
256
|
+
const params = queue ? [queue] : [];
|
|
257
|
+
const { rows: countRows } = await this.db.query(`
|
|
268
258
|
SELECT status, COUNT(*) as count
|
|
269
259
|
FROM ${this.tableName}
|
|
270
260
|
${queueFilter}
|
|
271
261
|
GROUP BY status
|
|
272
|
-
`,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
for (const row of countRows) {
|
|
277
|
-
counts[row.status] = row.count;
|
|
278
|
-
}
|
|
279
|
-
const { rows: durationRows } = await this.db.query(
|
|
280
|
-
`
|
|
262
|
+
`, params);
|
|
263
|
+
const counts = {};
|
|
264
|
+
for (const row of countRows) counts[row.status] = row.count;
|
|
265
|
+
const { rows: durationRows } = await this.db.query(`
|
|
281
266
|
SELECT AVG(
|
|
282
267
|
(julianday(completed_at) - julianday(started_at)) * 86400000
|
|
283
268
|
) as avg_duration
|
|
@@ -286,38 +271,31 @@ class SqliteJobStore extends BaseJobStore {
|
|
|
286
271
|
AND started_at IS NOT NULL
|
|
287
272
|
AND completed_at IS NOT NULL
|
|
288
273
|
${queue ? "AND queue = ?" : ""}
|
|
289
|
-
`,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
await new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
313
|
-
}
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
return this.db.notifications.waitForUpdate({ timeoutMs });
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
export {
|
|
320
|
-
SqliteJobStore,
|
|
321
|
-
SqliteJobStore as default
|
|
274
|
+
`, params);
|
|
275
|
+
const avgDuration = durationRows[0]?.avg_duration ?? null;
|
|
276
|
+
return {
|
|
277
|
+
pending: counts["pending"] ?? 0,
|
|
278
|
+
running: counts["running"] ?? 0,
|
|
279
|
+
completed: counts["completed"] ?? 0,
|
|
280
|
+
failed: counts["failed"] ?? 0,
|
|
281
|
+
cancelled: counts["cancelled"] ?? 0,
|
|
282
|
+
avgDuration: avgDuration ? Math.round(avgDuration) : null
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async close() {
|
|
286
|
+
if (this.db && !this.externalDb) await this.db.close?.();
|
|
287
|
+
this.db = null;
|
|
288
|
+
this.initialized = false;
|
|
289
|
+
}
|
|
290
|
+
async waitForUpdate(timeoutMs) {
|
|
291
|
+
if (!this.db?.notifications) {
|
|
292
|
+
if (timeoutMs && timeoutMs > 0) await new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
return this.db.notifications.waitForUpdate({ timeoutMs });
|
|
296
|
+
}
|
|
322
297
|
};
|
|
323
|
-
//#
|
|
298
|
+
//#endregion
|
|
299
|
+
export { SqliteJobStore, SqliteJobStore as default };
|
|
300
|
+
|
|
301
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.js","sources":["../../src/adapters/sqlite.ts"],"sourcesContent":["import {\n type DatabaseInterface,\n getDatabase,\n type SqliteCapabilitiesOptions,\n syncSchema,\n} from '@happyvertical/sql';\nimport { BaseJobStore, validateTableName } from '../base-store.js';\nimport type {\n CleanupOptions,\n Job,\n JobCreateOptions,\n JobFilter,\n QueueStats,\n} from '../types.js';\n\n/**\n * SQLite job store configuration\n */\nexport interface SqliteJobStoreConfig {\n /** Database URL or path (default: ':memory:') */\n url?: string;\n /** Existing database instance to use */\n db?: DatabaseInterface;\n /** Table name for jobs (default: '_jobs') */\n tableName?: string;\n /** Optional SQLite native capabilities for development/test modes */\n capabilities?: SqliteCapabilitiesOptions;\n}\n\n/**\n * SQLite-based job store\n *\n * Uses SQLite for job persistence. Supports polling-based job retrieval.\n * Good for single-instance deployments or development.\n */\nexport class SqliteJobStore extends BaseJobStore {\n private db: DatabaseInterface | null = null;\n private readonly url: string;\n private readonly externalDb: DatabaseInterface | null;\n private readonly tableName: string;\n private readonly capabilities: SqliteCapabilitiesOptions | undefined;\n\n constructor(config: SqliteJobStoreConfig = {}) {\n super();\n this.url = config.url ?? ':memory:';\n this.externalDb = config.db ?? null;\n this.tableName = validateTableName(config.tableName ?? '_jobs');\n this.capabilities = config.capabilities;\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Use existing database or create new one\n this.db =\n this.externalDb ??\n (await getDatabase({\n type: 'sqlite',\n url: this.url,\n capabilities: this.capabilities,\n }));\n\n // Create jobs table and indexes using syncSchema\n const schema = `\n CREATE TABLE IF NOT EXISTS \"${this.tableName}\" (\n id TEXT PRIMARY KEY,\n queue TEXT NOT NULL DEFAULT 'default',\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n priority INTEGER NOT NULL DEFAULT 50,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n run_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n timeout INTEGER NOT NULL DEFAULT 300000,\n timeout_behavior TEXT NOT NULL DEFAULT 'fail',\n last_error TEXT,\n result_pointer TEXT,\n retry_strategy TEXT NOT NULL,\n worker_id TEXT,\n worker_heartbeat TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_status_queue\" ON \"${this.tableName}\" (status, queue, run_at, priority DESC);\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_created_at\" ON \"${this.tableName}\" (created_at);\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_queue\" ON \"${this.tableName}\" (queue);\n `;\n\n await syncSchema({ db: this.db, schema });\n\n this.initialized = true;\n }\n\n async enqueue(options: JobCreateOptions): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = this.createJobRecord(options);\n\n await this.db.insert(this.tableName, {\n id: job.id,\n queue: job.queue,\n payload: JSON.stringify(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n max_attempts: job.maxAttempts,\n run_at: job.runAt.toISOString(),\n started_at: job.startedAt?.toISOString() ?? null,\n completed_at: job.completedAt?.toISOString() ?? null,\n timeout: job.timeout,\n timeout_behavior: job.timeoutBehavior,\n last_error: job.lastError,\n result_pointer: job.resultPointer,\n retry_strategy: JSON.stringify(job.retryStrategy),\n worker_id: job.workerId,\n worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,\n created_at: job.createdAt.toISOString(),\n updated_at: job.updatedAt.toISOString(),\n });\n\n await this.emitEvent('job.created', job);\n\n // Also emit ready if job should run immediately\n if (job.runAt <= new Date()) {\n await this.emitEvent('job.ready', job);\n }\n\n return job;\n }\n\n async dequeue(\n queues: string[],\n limit: number,\n workerId: string,\n ): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const now = new Date().toISOString();\n const queuePlaceholders = queues.map(() => '?').join(', ');\n\n // Find jobs ready to process\n const { rows } = await this.db.query(\n `\n SELECT * FROM ${this.tableName}\n WHERE status = 'pending'\n AND queue IN (${queuePlaceholders})\n AND run_at <= ?\n ORDER BY priority DESC, run_at ASC\n LIMIT ?\n `,\n [...queues, now, limit],\n );\n\n if (!rows.length) return [];\n\n // Claim the jobs by updating their status\n const jobIds = rows.map((r) => r.id as string);\n const idPlaceholders = jobIds.map(() => '?').join(', ');\n\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET status = 'running',\n worker_id = ?,\n worker_heartbeat = ?,\n started_at = ?,\n attempts = attempts + 1,\n updated_at = ?\n WHERE id IN (${idPlaceholders})\n AND status = 'pending'\n `,\n [workerId, now, now, now, ...jobIds],\n );\n\n // Return the claimed jobs\n const jobs = rows.map((row) => {\n const job = this.parseJobRow(row as Record<string, unknown>);\n job.status = 'running';\n job.workerId = workerId;\n job.workerHeartbeat = new Date();\n job.startedAt = new Date();\n job.attempts += 1;\n return job;\n });\n\n // Emit started events\n for (const job of jobs) {\n await this.emitEvent('job.started', job);\n }\n\n return jobs;\n }\n\n async update(id: string, updates: Partial<Job>): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const setClause: string[] = [];\n const params: unknown[] = [];\n\n // Map Job fields to database columns\n const fieldMap: Record<string, string> = {\n queue: 'queue',\n payload: 'payload',\n status: 'status',\n priority: 'priority',\n attempts: 'attempts',\n maxAttempts: 'max_attempts',\n runAt: 'run_at',\n startedAt: 'started_at',\n completedAt: 'completed_at',\n timeout: 'timeout',\n timeoutBehavior: 'timeout_behavior',\n lastError: 'last_error',\n resultPointer: 'result_pointer',\n retryStrategy: 'retry_strategy',\n workerId: 'worker_id',\n workerHeartbeat: 'worker_heartbeat',\n };\n\n for (const [key, value] of Object.entries(updates)) {\n const column = fieldMap[key];\n if (!column) continue;\n\n setClause.push(`${column} = ?`);\n\n if (value instanceof Date) {\n params.push(value.toISOString());\n } else if (typeof value === 'object' && value !== null) {\n params.push(JSON.stringify(value));\n } else {\n params.push(value);\n }\n }\n\n if (setClause.length === 0) {\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n return job;\n }\n\n // Always update updated_at\n setClause.push('updated_at = ?');\n params.push(new Date().toISOString());\n\n params.push(id);\n\n await this.db.query(\n `UPDATE ${this.tableName} SET ${setClause.join(', ')} WHERE id = ?`,\n params,\n );\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found after update: ${id}`);\n\n // Emit events based on status changes\n if (updates.status === 'completed') {\n await this.emitEvent('job.completed', job, {\n resultPointer: job.resultPointer ?? undefined,\n });\n } else if (updates.status === 'failed') {\n await this.emitEvent('job.failed', job, {\n error: job.lastError ?? undefined,\n });\n } else if (updates.status === 'cancelled') {\n await this.emitEvent('job.cancelled', job);\n }\n\n return job;\n }\n\n async get(id: string): Promise<Job | null> {\n if (!this.db) throw new Error('Store not initialized');\n\n const row = await this.db.get(this.tableName, { id });\n if (!row) return null;\n\n return this.parseJobRow(row as Record<string, unknown>);\n }\n\n async list(filter: JobFilter): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const { where, params: whereParams } = this.buildFilterWhere(filter);\n const orderBy = this.buildOrderBy(filter);\n const { clause: limitOffset, params: limitParams } =\n this.buildLimitOffset(filter);\n\n const { rows } = await this.db.query(\n `SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`,\n [...whereParams, ...limitParams],\n );\n\n return rows.map((row) => this.parseJobRow(row as Record<string, unknown>));\n }\n\n async cancel(id: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n\n if (job.status === 'completed' || job.status === 'cancelled') {\n throw new Error(`Cannot cancel job with status: ${job.status}`);\n }\n\n await this.update(id, {\n status: 'cancelled',\n completedAt: new Date(),\n });\n }\n\n async cleanup(options: CleanupOptions): Promise<number> {\n if (!this.db) throw new Error('Store not initialized');\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (options.completedBefore) {\n conditions.push(\"(status = 'completed' AND completed_at < ?)\");\n params.push(options.completedBefore.toISOString());\n }\n\n if (options.failedBefore) {\n conditions.push(\"(status = 'failed' AND completed_at < ?)\");\n params.push(options.failedBefore.toISOString());\n }\n\n if (options.cancelledBefore) {\n conditions.push(\"(status = 'cancelled' AND completed_at < ?)\");\n params.push(options.cancelledBefore.toISOString());\n }\n\n if (conditions.length === 0) return 0;\n\n let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(' OR ')})`;\n\n if (options.limit) {\n // SQLite doesn't support LIMIT in DELETE directly, so we need a subquery\n query = `\n DELETE FROM ${this.tableName}\n WHERE id IN (\n SELECT id FROM ${this.tableName}\n WHERE (${conditions.join(' OR ')})\n LIMIT ?\n )\n `;\n params.push(options.limit);\n }\n\n const result = await this.db.query(query, params);\n\n return result.rowCount ?? 0;\n }\n\n async heartbeat(jobId: string, workerId: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET worker_heartbeat = ?, updated_at = ?\n WHERE id = ? AND worker_id = ? AND status = 'running'\n `,\n [new Date().toISOString(), new Date().toISOString(), jobId, workerId],\n );\n }\n\n async stats(queue?: string): Promise<QueueStats> {\n if (!this.db) throw new Error('Store not initialized');\n\n const queueFilter = queue ? 'WHERE queue = ?' : '';\n const params = queue ? [queue] : [];\n\n // Get status counts\n const { rows: countRows } = await this.db.query(\n `\n SELECT status, COUNT(*) as count\n FROM ${this.tableName}\n ${queueFilter}\n GROUP BY status\n `,\n params,\n );\n\n const counts: Record<string, number> = {};\n for (const row of countRows) {\n counts[row.status as string] = row.count as number;\n }\n\n // Get average duration for completed jobs\n const { rows: durationRows } = await this.db.query(\n `\n SELECT AVG(\n (julianday(completed_at) - julianday(started_at)) * 86400000\n ) as avg_duration\n FROM ${this.tableName}\n WHERE status = 'completed'\n AND started_at IS NOT NULL\n AND completed_at IS NOT NULL\n ${queue ? 'AND queue = ?' : ''}\n `,\n params,\n );\n\n const avgDuration =\n (durationRows[0] as { avg_duration: number | null })?.avg_duration ??\n null;\n\n return {\n pending: counts['pending'] ?? 0,\n running: counts['running'] ?? 0,\n completed: counts['completed'] ?? 0,\n failed: counts['failed'] ?? 0,\n cancelled: counts['cancelled'] ?? 0,\n avgDuration: avgDuration ? Math.round(avgDuration) : null,\n };\n }\n\n async close(): Promise<void> {\n if (this.db && !this.externalDb) {\n await this.db.close?.();\n }\n this.db = null;\n this.initialized = false;\n }\n\n async waitForUpdate(timeoutMs?: number): Promise<boolean> {\n if (!this.db?.notifications) {\n if (timeoutMs && timeoutMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, timeoutMs));\n }\n return false;\n }\n\n return this.db.notifications.waitForUpdate({ timeoutMs });\n }\n}\n\nexport default SqliteJobStore;\n"],"names":["job"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sqlite.js","names":[],"sources":["../../src/adapters/sqlite.ts"],"sourcesContent":["import {\n type DatabaseInterface,\n getDatabase,\n type SqliteCapabilitiesOptions,\n syncSchema,\n} from '@happyvertical/sql';\nimport { BaseJobStore, validateTableName } from '../base-store.js';\nimport type {\n CleanupOptions,\n Job,\n JobCreateOptions,\n JobFilter,\n QueueStats,\n} from '../types.js';\n\n/**\n * SQLite job store configuration\n */\nexport interface SqliteJobStoreConfig {\n /** Database URL or path (default: ':memory:') */\n url?: string;\n /** Existing database instance to use */\n db?: DatabaseInterface;\n /** Table name for jobs (default: '_jobs') */\n tableName?: string;\n /** Optional SQLite native capabilities for development/test modes */\n capabilities?: SqliteCapabilitiesOptions;\n}\n\n/**\n * SQLite-based job store\n *\n * Uses SQLite for job persistence. Supports polling-based job retrieval.\n * Good for single-instance deployments or development.\n */\nexport class SqliteJobStore extends BaseJobStore {\n private db: DatabaseInterface | null = null;\n private readonly url: string;\n private readonly externalDb: DatabaseInterface | null;\n private readonly tableName: string;\n private readonly capabilities: SqliteCapabilitiesOptions | undefined;\n\n constructor(config: SqliteJobStoreConfig = {}) {\n super();\n this.url = config.url ?? ':memory:';\n this.externalDb = config.db ?? null;\n this.tableName = validateTableName(config.tableName ?? '_jobs');\n this.capabilities = config.capabilities;\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Use existing database or create new one\n this.db =\n this.externalDb ??\n (await getDatabase({\n type: 'sqlite',\n url: this.url,\n capabilities: this.capabilities,\n }));\n\n // Create jobs table and indexes using syncSchema\n const schema = `\n CREATE TABLE IF NOT EXISTS \"${this.tableName}\" (\n id TEXT PRIMARY KEY,\n queue TEXT NOT NULL DEFAULT 'default',\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n priority INTEGER NOT NULL DEFAULT 50,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n run_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n timeout INTEGER NOT NULL DEFAULT 300000,\n timeout_behavior TEXT NOT NULL DEFAULT 'fail',\n last_error TEXT,\n result_pointer TEXT,\n retry_strategy TEXT NOT NULL,\n worker_id TEXT,\n worker_heartbeat TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_status_queue\" ON \"${this.tableName}\" (status, queue, run_at, priority DESC);\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_created_at\" ON \"${this.tableName}\" (created_at);\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_queue\" ON \"${this.tableName}\" (queue);\n `;\n\n await syncSchema({ db: this.db, schema });\n\n this.initialized = true;\n }\n\n async enqueue(options: JobCreateOptions): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = this.createJobRecord(options);\n\n await this.db.insert(this.tableName, {\n id: job.id,\n queue: job.queue,\n payload: JSON.stringify(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n max_attempts: job.maxAttempts,\n run_at: job.runAt.toISOString(),\n started_at: job.startedAt?.toISOString() ?? null,\n completed_at: job.completedAt?.toISOString() ?? null,\n timeout: job.timeout,\n timeout_behavior: job.timeoutBehavior,\n last_error: job.lastError,\n result_pointer: job.resultPointer,\n retry_strategy: JSON.stringify(job.retryStrategy),\n worker_id: job.workerId,\n worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,\n created_at: job.createdAt.toISOString(),\n updated_at: job.updatedAt.toISOString(),\n });\n\n await this.emitEvent('job.created', job);\n\n // Also emit ready if job should run immediately\n if (job.runAt <= new Date()) {\n await this.emitEvent('job.ready', job);\n }\n\n return job;\n }\n\n async dequeue(\n queues: string[],\n limit: number,\n workerId: string,\n ): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const now = new Date().toISOString();\n const queuePlaceholders = queues.map(() => '?').join(', ');\n\n // Find jobs ready to process\n const { rows } = await this.db.query(\n `\n SELECT * FROM ${this.tableName}\n WHERE status = 'pending'\n AND queue IN (${queuePlaceholders})\n AND run_at <= ?\n ORDER BY priority DESC, run_at ASC\n LIMIT ?\n `,\n [...queues, now, limit],\n );\n\n if (!rows.length) return [];\n\n // Claim the jobs by updating their status\n const jobIds = rows.map((r) => r.id as string);\n const idPlaceholders = jobIds.map(() => '?').join(', ');\n\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET status = 'running',\n worker_id = ?,\n worker_heartbeat = ?,\n started_at = ?,\n attempts = attempts + 1,\n updated_at = ?\n WHERE id IN (${idPlaceholders})\n AND status = 'pending'\n `,\n [workerId, now, now, now, ...jobIds],\n );\n\n // Return the claimed jobs\n const jobs = rows.map((row) => {\n const job = this.parseJobRow(row as Record<string, unknown>);\n job.status = 'running';\n job.workerId = workerId;\n job.workerHeartbeat = new Date();\n job.startedAt = new Date();\n job.attempts += 1;\n return job;\n });\n\n // Emit started events\n for (const job of jobs) {\n await this.emitEvent('job.started', job);\n }\n\n return jobs;\n }\n\n async update(id: string, updates: Partial<Job>): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const setClause: string[] = [];\n const params: unknown[] = [];\n\n // Map Job fields to database columns\n const fieldMap: Record<string, string> = {\n queue: 'queue',\n payload: 'payload',\n status: 'status',\n priority: 'priority',\n attempts: 'attempts',\n maxAttempts: 'max_attempts',\n runAt: 'run_at',\n startedAt: 'started_at',\n completedAt: 'completed_at',\n timeout: 'timeout',\n timeoutBehavior: 'timeout_behavior',\n lastError: 'last_error',\n resultPointer: 'result_pointer',\n retryStrategy: 'retry_strategy',\n workerId: 'worker_id',\n workerHeartbeat: 'worker_heartbeat',\n };\n\n for (const [key, value] of Object.entries(updates)) {\n const column = fieldMap[key];\n if (!column) continue;\n\n setClause.push(`${column} = ?`);\n\n if (value instanceof Date) {\n params.push(value.toISOString());\n } else if (typeof value === 'object' && value !== null) {\n params.push(JSON.stringify(value));\n } else {\n params.push(value);\n }\n }\n\n if (setClause.length === 0) {\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n return job;\n }\n\n // Always update updated_at\n setClause.push('updated_at = ?');\n params.push(new Date().toISOString());\n\n params.push(id);\n\n await this.db.query(\n `UPDATE ${this.tableName} SET ${setClause.join(', ')} WHERE id = ?`,\n params,\n );\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found after update: ${id}`);\n\n // Emit events based on status changes\n if (updates.status === 'completed') {\n await this.emitEvent('job.completed', job, {\n resultPointer: job.resultPointer ?? undefined,\n });\n } else if (updates.status === 'failed') {\n await this.emitEvent('job.failed', job, {\n error: job.lastError ?? undefined,\n });\n } else if (updates.status === 'cancelled') {\n await this.emitEvent('job.cancelled', job);\n }\n\n return job;\n }\n\n async get(id: string): Promise<Job | null> {\n if (!this.db) throw new Error('Store not initialized');\n\n const row = await this.db.get(this.tableName, { id });\n if (!row) return null;\n\n return this.parseJobRow(row as Record<string, unknown>);\n }\n\n async list(filter: JobFilter): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const { where, params: whereParams } = this.buildFilterWhere(filter);\n const orderBy = this.buildOrderBy(filter);\n const { clause: limitOffset, params: limitParams } =\n this.buildLimitOffset(filter);\n\n const { rows } = await this.db.query(\n `SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`,\n [...whereParams, ...limitParams],\n );\n\n return rows.map((row) => this.parseJobRow(row as Record<string, unknown>));\n }\n\n async cancel(id: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n\n if (job.status === 'completed' || job.status === 'cancelled') {\n throw new Error(`Cannot cancel job with status: ${job.status}`);\n }\n\n await this.update(id, {\n status: 'cancelled',\n completedAt: new Date(),\n });\n }\n\n async cleanup(options: CleanupOptions): Promise<number> {\n if (!this.db) throw new Error('Store not initialized');\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (options.completedBefore) {\n conditions.push(\"(status = 'completed' AND completed_at < ?)\");\n params.push(options.completedBefore.toISOString());\n }\n\n if (options.failedBefore) {\n conditions.push(\"(status = 'failed' AND completed_at < ?)\");\n params.push(options.failedBefore.toISOString());\n }\n\n if (options.cancelledBefore) {\n conditions.push(\"(status = 'cancelled' AND completed_at < ?)\");\n params.push(options.cancelledBefore.toISOString());\n }\n\n if (conditions.length === 0) return 0;\n\n let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(' OR ')})`;\n\n if (options.limit) {\n // SQLite doesn't support LIMIT in DELETE directly, so we need a subquery\n query = `\n DELETE FROM ${this.tableName}\n WHERE id IN (\n SELECT id FROM ${this.tableName}\n WHERE (${conditions.join(' OR ')})\n LIMIT ?\n )\n `;\n params.push(options.limit);\n }\n\n const result = await this.db.query(query, params);\n\n return result.rowCount ?? 0;\n }\n\n async heartbeat(jobId: string, workerId: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET worker_heartbeat = ?, updated_at = ?\n WHERE id = ? AND worker_id = ? AND status = 'running'\n `,\n [new Date().toISOString(), new Date().toISOString(), jobId, workerId],\n );\n }\n\n async stats(queue?: string): Promise<QueueStats> {\n if (!this.db) throw new Error('Store not initialized');\n\n const queueFilter = queue ? 'WHERE queue = ?' : '';\n const params = queue ? [queue] : [];\n\n // Get status counts\n const { rows: countRows } = await this.db.query(\n `\n SELECT status, COUNT(*) as count\n FROM ${this.tableName}\n ${queueFilter}\n GROUP BY status\n `,\n params,\n );\n\n const counts: Record<string, number> = {};\n for (const row of countRows) {\n counts[row.status as string] = row.count as number;\n }\n\n // Get average duration for completed jobs\n const { rows: durationRows } = await this.db.query(\n `\n SELECT AVG(\n (julianday(completed_at) - julianday(started_at)) * 86400000\n ) as avg_duration\n FROM ${this.tableName}\n WHERE status = 'completed'\n AND started_at IS NOT NULL\n AND completed_at IS NOT NULL\n ${queue ? 'AND queue = ?' : ''}\n `,\n params,\n );\n\n const avgDuration =\n (durationRows[0] as { avg_duration: number | null })?.avg_duration ??\n null;\n\n return {\n pending: counts['pending'] ?? 0,\n running: counts['running'] ?? 0,\n completed: counts['completed'] ?? 0,\n failed: counts['failed'] ?? 0,\n cancelled: counts['cancelled'] ?? 0,\n avgDuration: avgDuration ? Math.round(avgDuration) : null,\n };\n }\n\n async close(): Promise<void> {\n if (this.db && !this.externalDb) {\n await this.db.close?.();\n }\n this.db = null;\n this.initialized = false;\n }\n\n async waitForUpdate(timeoutMs?: number): Promise<boolean> {\n if (!this.db?.notifications) {\n if (timeoutMs && timeoutMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, timeoutMs));\n }\n return false;\n }\n\n return this.db.notifications.waitForUpdate({ timeoutMs });\n }\n}\n\nexport default SqliteJobStore;\n"],"mappings":"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"}
|