@happyvertical/jobs 0.74.8
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/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/dist/adapters/bull.d.ts +103 -0
- package/dist/adapters/bull.d.ts.map +1 -0
- package/dist/adapters/bull.js +349 -0
- package/dist/adapters/bull.js.map +1 -0
- package/dist/adapters/bullmq.d.ts +85 -0
- package/dist/adapters/bullmq.d.ts.map +1 -0
- package/dist/adapters/bullmq.js +391 -0
- package/dist/adapters/bullmq.js.map +1 -0
- package/dist/adapters/cloud-tasks.d.ts +110 -0
- package/dist/adapters/cloud-tasks.d.ts.map +1 -0
- package/dist/adapters/cloud-tasks.js +336 -0
- package/dist/adapters/cloud-tasks.js.map +1 -0
- package/dist/adapters/postgres.d.ts +55 -0
- package/dist/adapters/postgres.d.ts.map +1 -0
- package/dist/adapters/postgres.js +437 -0
- package/dist/adapters/postgres.js.map +1 -0
- package/dist/adapters/sqlite.d.ts +44 -0
- package/dist/adapters/sqlite.d.ts.map +1 -0
- package/dist/adapters/sqlite.js +323 -0
- package/dist/adapters/sqlite.js.map +1 -0
- package/dist/adapters/sqs.d.ts +112 -0
- package/dist/adapters/sqs.d.ts.map +1 -0
- package/dist/adapters/sqs.js +411 -0
- package/dist/adapters/sqs.js.map +1 -0
- package/dist/base-store.d.ts +69 -0
- package/dist/base-store.d.ts.map +1 -0
- package/dist/chunks/base-store-DlNksWvQ.js +324 -0
- package/dist/chunks/base-store-DlNksWvQ.js.map +1 -0
- package/dist/cli/claude-context.d.ts +3 -0
- package/dist/cli/claude-context.d.ts.map +1 -0
- package/dist/cli/claude-context.js +21 -0
- package/dist/cli/claude-context.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +252 -0
- package/dist/index.js.map +1 -0
- package/dist/retry.d.ts +84 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/types.d.ts +311 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/worker.d.ts +74 -0
- package/dist/worker.d.ts.map +1 -0
- package/metadata.json +34 -0
- package/package.json +114 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { getDatabase, syncSchema } from "@happyvertical/sql";
|
|
2
|
+
import { B as BaseJobStore, v as validateTableName } from "../chunks/base-store-DlNksWvQ.js";
|
|
3
|
+
class SqliteJobStore extends BaseJobStore {
|
|
4
|
+
db = null;
|
|
5
|
+
url;
|
|
6
|
+
externalDb;
|
|
7
|
+
tableName;
|
|
8
|
+
capabilities;
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super();
|
|
11
|
+
this.url = config.url ?? ":memory:";
|
|
12
|
+
this.externalDb = config.db ?? null;
|
|
13
|
+
this.tableName = validateTableName(config.tableName ?? "_jobs");
|
|
14
|
+
this.capabilities = config.capabilities;
|
|
15
|
+
}
|
|
16
|
+
async initialize() {
|
|
17
|
+
if (this.initialized) return;
|
|
18
|
+
this.db = this.externalDb ?? await getDatabase({
|
|
19
|
+
type: "sqlite",
|
|
20
|
+
url: this.url,
|
|
21
|
+
capabilities: this.capabilities
|
|
22
|
+
});
|
|
23
|
+
const schema = `
|
|
24
|
+
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|
25
|
+
id TEXT PRIMARY KEY,
|
|
26
|
+
queue TEXT NOT NULL DEFAULT 'default',
|
|
27
|
+
payload TEXT NOT NULL,
|
|
28
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
29
|
+
priority INTEGER NOT NULL DEFAULT 50,
|
|
30
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
31
|
+
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
32
|
+
run_at TEXT NOT NULL,
|
|
33
|
+
started_at TEXT,
|
|
34
|
+
completed_at TEXT,
|
|
35
|
+
timeout INTEGER NOT NULL DEFAULT 300000,
|
|
36
|
+
timeout_behavior TEXT NOT NULL DEFAULT 'fail',
|
|
37
|
+
last_error TEXT,
|
|
38
|
+
result_pointer TEXT,
|
|
39
|
+
retry_strategy TEXT NOT NULL,
|
|
40
|
+
worker_id TEXT,
|
|
41
|
+
worker_heartbeat TEXT,
|
|
42
|
+
created_at TEXT NOT NULL,
|
|
43
|
+
updated_at TEXT NOT NULL
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE INDEX IF NOT EXISTS "idx_${this.tableName}_status_queue" ON "${this.tableName}" (status, queue, run_at, priority DESC);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS "idx_${this.tableName}_created_at" ON "${this.tableName}" (created_at);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS "idx_${this.tableName}_queue" ON "${this.tableName}" (queue);
|
|
49
|
+
`;
|
|
50
|
+
await syncSchema({ db: this.db, schema });
|
|
51
|
+
this.initialized = true;
|
|
52
|
+
}
|
|
53
|
+
async enqueue(options) {
|
|
54
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
55
|
+
const job = this.createJobRecord(options);
|
|
56
|
+
await this.db.insert(this.tableName, {
|
|
57
|
+
id: job.id,
|
|
58
|
+
queue: job.queue,
|
|
59
|
+
payload: JSON.stringify(job.payload),
|
|
60
|
+
status: job.status,
|
|
61
|
+
priority: job.priority,
|
|
62
|
+
attempts: job.attempts,
|
|
63
|
+
max_attempts: job.maxAttempts,
|
|
64
|
+
run_at: job.runAt.toISOString(),
|
|
65
|
+
started_at: job.startedAt?.toISOString() ?? null,
|
|
66
|
+
completed_at: job.completedAt?.toISOString() ?? null,
|
|
67
|
+
timeout: job.timeout,
|
|
68
|
+
timeout_behavior: job.timeoutBehavior,
|
|
69
|
+
last_error: job.lastError,
|
|
70
|
+
result_pointer: job.resultPointer,
|
|
71
|
+
retry_strategy: JSON.stringify(job.retryStrategy),
|
|
72
|
+
worker_id: job.workerId,
|
|
73
|
+
worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,
|
|
74
|
+
created_at: job.createdAt.toISOString(),
|
|
75
|
+
updated_at: job.updatedAt.toISOString()
|
|
76
|
+
});
|
|
77
|
+
await this.emitEvent("job.created", job);
|
|
78
|
+
if (job.runAt <= /* @__PURE__ */ new Date()) {
|
|
79
|
+
await this.emitEvent("job.ready", job);
|
|
80
|
+
}
|
|
81
|
+
return job;
|
|
82
|
+
}
|
|
83
|
+
async dequeue(queues, limit, workerId) {
|
|
84
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
85
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
86
|
+
const queuePlaceholders = queues.map(() => "?").join(", ");
|
|
87
|
+
const { rows } = await this.db.query(
|
|
88
|
+
`
|
|
89
|
+
SELECT * FROM ${this.tableName}
|
|
90
|
+
WHERE status = 'pending'
|
|
91
|
+
AND queue IN (${queuePlaceholders})
|
|
92
|
+
AND run_at <= ?
|
|
93
|
+
ORDER BY priority DESC, run_at ASC
|
|
94
|
+
LIMIT ?
|
|
95
|
+
`,
|
|
96
|
+
[...queues, now, limit]
|
|
97
|
+
);
|
|
98
|
+
if (!rows.length) return [];
|
|
99
|
+
const jobIds = rows.map((r) => r.id);
|
|
100
|
+
const idPlaceholders = jobIds.map(() => "?").join(", ");
|
|
101
|
+
await this.db.query(
|
|
102
|
+
`
|
|
103
|
+
UPDATE ${this.tableName}
|
|
104
|
+
SET status = 'running',
|
|
105
|
+
worker_id = ?,
|
|
106
|
+
worker_heartbeat = ?,
|
|
107
|
+
started_at = ?,
|
|
108
|
+
attempts = attempts + 1,
|
|
109
|
+
updated_at = ?
|
|
110
|
+
WHERE id IN (${idPlaceholders})
|
|
111
|
+
AND status = 'pending'
|
|
112
|
+
`,
|
|
113
|
+
[workerId, now, now, now, ...jobIds]
|
|
114
|
+
);
|
|
115
|
+
const jobs = rows.map((row) => {
|
|
116
|
+
const job = this.parseJobRow(row);
|
|
117
|
+
job.status = "running";
|
|
118
|
+
job.workerId = workerId;
|
|
119
|
+
job.workerHeartbeat = /* @__PURE__ */ new Date();
|
|
120
|
+
job.startedAt = /* @__PURE__ */ new Date();
|
|
121
|
+
job.attempts += 1;
|
|
122
|
+
return job;
|
|
123
|
+
});
|
|
124
|
+
for (const job of jobs) {
|
|
125
|
+
await this.emitEvent("job.started", job);
|
|
126
|
+
}
|
|
127
|
+
return jobs;
|
|
128
|
+
}
|
|
129
|
+
async update(id, updates) {
|
|
130
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
131
|
+
const setClause = [];
|
|
132
|
+
const params = [];
|
|
133
|
+
const fieldMap = {
|
|
134
|
+
queue: "queue",
|
|
135
|
+
payload: "payload",
|
|
136
|
+
status: "status",
|
|
137
|
+
priority: "priority",
|
|
138
|
+
attempts: "attempts",
|
|
139
|
+
maxAttempts: "max_attempts",
|
|
140
|
+
runAt: "run_at",
|
|
141
|
+
startedAt: "started_at",
|
|
142
|
+
completedAt: "completed_at",
|
|
143
|
+
timeout: "timeout",
|
|
144
|
+
timeoutBehavior: "timeout_behavior",
|
|
145
|
+
lastError: "last_error",
|
|
146
|
+
resultPointer: "result_pointer",
|
|
147
|
+
retryStrategy: "retry_strategy",
|
|
148
|
+
workerId: "worker_id",
|
|
149
|
+
workerHeartbeat: "worker_heartbeat"
|
|
150
|
+
};
|
|
151
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
152
|
+
const column = fieldMap[key];
|
|
153
|
+
if (!column) continue;
|
|
154
|
+
setClause.push(`${column} = ?`);
|
|
155
|
+
if (value instanceof Date) {
|
|
156
|
+
params.push(value.toISOString());
|
|
157
|
+
} else if (typeof value === "object" && value !== null) {
|
|
158
|
+
params.push(JSON.stringify(value));
|
|
159
|
+
} else {
|
|
160
|
+
params.push(value);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (setClause.length === 0) {
|
|
164
|
+
const job2 = await this.get(id);
|
|
165
|
+
if (!job2) throw new Error(`Job not found: ${id}`);
|
|
166
|
+
return job2;
|
|
167
|
+
}
|
|
168
|
+
setClause.push("updated_at = ?");
|
|
169
|
+
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
170
|
+
params.push(id);
|
|
171
|
+
await this.db.query(
|
|
172
|
+
`UPDATE ${this.tableName} SET ${setClause.join(", ")} WHERE id = ?`,
|
|
173
|
+
params
|
|
174
|
+
);
|
|
175
|
+
const job = await this.get(id);
|
|
176
|
+
if (!job) throw new Error(`Job not found after update: ${id}`);
|
|
177
|
+
if (updates.status === "completed") {
|
|
178
|
+
await this.emitEvent("job.completed", job, {
|
|
179
|
+
resultPointer: job.resultPointer ?? void 0
|
|
180
|
+
});
|
|
181
|
+
} else if (updates.status === "failed") {
|
|
182
|
+
await this.emitEvent("job.failed", job, {
|
|
183
|
+
error: job.lastError ?? void 0
|
|
184
|
+
});
|
|
185
|
+
} else if (updates.status === "cancelled") {
|
|
186
|
+
await this.emitEvent("job.cancelled", job);
|
|
187
|
+
}
|
|
188
|
+
return job;
|
|
189
|
+
}
|
|
190
|
+
async get(id) {
|
|
191
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
192
|
+
const row = await this.db.get(this.tableName, { id });
|
|
193
|
+
if (!row) return null;
|
|
194
|
+
return this.parseJobRow(row);
|
|
195
|
+
}
|
|
196
|
+
async list(filter) {
|
|
197
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
198
|
+
const { where, params: whereParams } = this.buildFilterWhere(filter);
|
|
199
|
+
const orderBy = this.buildOrderBy(filter);
|
|
200
|
+
const { clause: limitOffset, params: limitParams } = this.buildLimitOffset(filter);
|
|
201
|
+
const { rows } = await this.db.query(
|
|
202
|
+
`SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`,
|
|
203
|
+
[...whereParams, ...limitParams]
|
|
204
|
+
);
|
|
205
|
+
return rows.map((row) => this.parseJobRow(row));
|
|
206
|
+
}
|
|
207
|
+
async cancel(id) {
|
|
208
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
209
|
+
const job = await this.get(id);
|
|
210
|
+
if (!job) throw new Error(`Job not found: ${id}`);
|
|
211
|
+
if (job.status === "completed" || job.status === "cancelled") {
|
|
212
|
+
throw new Error(`Cannot cancel job with status: ${job.status}`);
|
|
213
|
+
}
|
|
214
|
+
await this.update(id, {
|
|
215
|
+
status: "cancelled",
|
|
216
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async cleanup(options) {
|
|
220
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
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 = `
|
|
239
|
+
DELETE FROM ${this.tableName}
|
|
240
|
+
WHERE id IN (
|
|
241
|
+
SELECT id FROM ${this.tableName}
|
|
242
|
+
WHERE (${conditions.join(" OR ")})
|
|
243
|
+
LIMIT ?
|
|
244
|
+
)
|
|
245
|
+
`;
|
|
246
|
+
params.push(options.limit);
|
|
247
|
+
}
|
|
248
|
+
const result = await this.db.query(query, params);
|
|
249
|
+
return result.rowCount ?? 0;
|
|
250
|
+
}
|
|
251
|
+
async heartbeat(jobId, workerId) {
|
|
252
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
253
|
+
await this.db.query(
|
|
254
|
+
`
|
|
255
|
+
UPDATE ${this.tableName}
|
|
256
|
+
SET worker_heartbeat = ?, updated_at = ?
|
|
257
|
+
WHERE id = ? AND worker_id = ? AND status = 'running'
|
|
258
|
+
`,
|
|
259
|
+
[(/* @__PURE__ */ new Date()).toISOString(), (/* @__PURE__ */ new Date()).toISOString(), jobId, workerId]
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
async stats(queue) {
|
|
263
|
+
if (!this.db) throw new Error("Store not initialized");
|
|
264
|
+
const queueFilter = queue ? "WHERE queue = ?" : "";
|
|
265
|
+
const params = queue ? [queue] : [];
|
|
266
|
+
const { rows: countRows } = await this.db.query(
|
|
267
|
+
`
|
|
268
|
+
SELECT status, COUNT(*) as count
|
|
269
|
+
FROM ${this.tableName}
|
|
270
|
+
${queueFilter}
|
|
271
|
+
GROUP BY status
|
|
272
|
+
`,
|
|
273
|
+
params
|
|
274
|
+
);
|
|
275
|
+
const counts = {};
|
|
276
|
+
for (const row of countRows) {
|
|
277
|
+
counts[row.status] = row.count;
|
|
278
|
+
}
|
|
279
|
+
const { rows: durationRows } = await this.db.query(
|
|
280
|
+
`
|
|
281
|
+
SELECT AVG(
|
|
282
|
+
(julianday(completed_at) - julianday(started_at)) * 86400000
|
|
283
|
+
) as avg_duration
|
|
284
|
+
FROM ${this.tableName}
|
|
285
|
+
WHERE status = 'completed'
|
|
286
|
+
AND started_at IS NOT NULL
|
|
287
|
+
AND completed_at IS NOT NULL
|
|
288
|
+
${queue ? "AND queue = ?" : ""}
|
|
289
|
+
`,
|
|
290
|
+
params
|
|
291
|
+
);
|
|
292
|
+
const avgDuration = durationRows[0]?.avg_duration ?? null;
|
|
293
|
+
return {
|
|
294
|
+
pending: counts["pending"] ?? 0,
|
|
295
|
+
running: counts["running"] ?? 0,
|
|
296
|
+
completed: counts["completed"] ?? 0,
|
|
297
|
+
failed: counts["failed"] ?? 0,
|
|
298
|
+
cancelled: counts["cancelled"] ?? 0,
|
|
299
|
+
avgDuration: avgDuration ? Math.round(avgDuration) : null
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async close() {
|
|
303
|
+
if (this.db && !this.externalDb) {
|
|
304
|
+
await this.db.close?.();
|
|
305
|
+
}
|
|
306
|
+
this.db = null;
|
|
307
|
+
this.initialized = false;
|
|
308
|
+
}
|
|
309
|
+
async waitForUpdate(timeoutMs) {
|
|
310
|
+
if (!this.db?.notifications) {
|
|
311
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
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
|
|
322
|
+
};
|
|
323
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { BaseJobStore } from '../base-store.js';
|
|
2
|
+
import { CleanupOptions, Job, JobCreateOptions, JobFilter, QueueStats } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* AWS credentials configuration
|
|
5
|
+
*/
|
|
6
|
+
export interface AWSCredentials {
|
|
7
|
+
accessKeyId: string;
|
|
8
|
+
secretAccessKey: string;
|
|
9
|
+
sessionToken?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* SQS adapter configuration
|
|
13
|
+
*/
|
|
14
|
+
export interface SQSJobStoreConfig {
|
|
15
|
+
/** AWS region */
|
|
16
|
+
region?: string;
|
|
17
|
+
/** AWS credentials (optional - uses default chain if not provided) */
|
|
18
|
+
credentials?: AWSCredentials;
|
|
19
|
+
/** Queue URL prefix (queues will be named {prefix}{queueName}) */
|
|
20
|
+
queueUrlPrefix: string;
|
|
21
|
+
/** Default visibility timeout in seconds */
|
|
22
|
+
visibilityTimeout?: number;
|
|
23
|
+
/** Message retention in days (1-14, default: 4) */
|
|
24
|
+
messageRetentionDays?: number;
|
|
25
|
+
/** Use FIFO queues for ordering guarantees */
|
|
26
|
+
useFifo?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* SQS-based job store implementation
|
|
30
|
+
*
|
|
31
|
+
* Note: SQS has limitations that make some operations different:
|
|
32
|
+
* - `update()` throws error (messages are immutable)
|
|
33
|
+
* - `list()` only returns pending jobs from SQS
|
|
34
|
+
* - `cancel()` requires the job to have been dequeued first
|
|
35
|
+
* - For full job tracking, consider using DynamoDB alongside SQS
|
|
36
|
+
*/
|
|
37
|
+
export declare class SQSJobStore extends BaseJobStore {
|
|
38
|
+
private config;
|
|
39
|
+
private client;
|
|
40
|
+
private awsSdkModule;
|
|
41
|
+
private queueUrls;
|
|
42
|
+
private jobStates;
|
|
43
|
+
constructor(config: SQSJobStoreConfig);
|
|
44
|
+
/**
|
|
45
|
+
* Initialize the store - dynamically imports AWS SDK
|
|
46
|
+
*/
|
|
47
|
+
initialize(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Get or create queue URL for a queue name
|
|
50
|
+
*/
|
|
51
|
+
private getQueueUrl;
|
|
52
|
+
/**
|
|
53
|
+
* Convert SQS message to Job format
|
|
54
|
+
*/
|
|
55
|
+
private sqsMessageToJob;
|
|
56
|
+
/**
|
|
57
|
+
* Enqueue a new job
|
|
58
|
+
*/
|
|
59
|
+
enqueue(options: JobCreateOptions): Promise<Job>;
|
|
60
|
+
/**
|
|
61
|
+
* Dequeue jobs ready for processing
|
|
62
|
+
*/
|
|
63
|
+
dequeue(queues: string[], limit: number, workerId: string): Promise<Job[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Update a job - NOT SUPPORTED in SQS (messages are immutable)
|
|
66
|
+
*/
|
|
67
|
+
update(_id: string, _updates: Partial<Job>): Promise<Job>;
|
|
68
|
+
/**
|
|
69
|
+
* Get a job by ID (from in-memory state only)
|
|
70
|
+
*/
|
|
71
|
+
get(id: string): Promise<Job | null>;
|
|
72
|
+
/**
|
|
73
|
+
* List jobs with filtering
|
|
74
|
+
* Note: SQS doesn't support efficient listing - this only returns in-memory state
|
|
75
|
+
*/
|
|
76
|
+
list(filter: JobFilter): Promise<Job[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Cancel a job by deleting its message from SQS
|
|
79
|
+
*/
|
|
80
|
+
cancel(id: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Mark job as completed
|
|
83
|
+
*/
|
|
84
|
+
markCompleted(id: string, resultPointer?: string): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Mark job as failed
|
|
87
|
+
*/
|
|
88
|
+
markFailed(id: string, error: string): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Clean up old jobs from in-memory state
|
|
91
|
+
* Note: SQS handles message retention automatically
|
|
92
|
+
*/
|
|
93
|
+
cleanup(options: CleanupOptions): Promise<number>;
|
|
94
|
+
/**
|
|
95
|
+
* Update visibility timeout for a job (extends processing time)
|
|
96
|
+
*/
|
|
97
|
+
heartbeat(jobId: string, _workerId: string): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Get queue statistics
|
|
100
|
+
*/
|
|
101
|
+
stats(queue?: string): Promise<QueueStats>;
|
|
102
|
+
/**
|
|
103
|
+
* Close the SQS client
|
|
104
|
+
*/
|
|
105
|
+
close(): Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Create an SQS job store instance
|
|
109
|
+
*/
|
|
110
|
+
export declare function createSQSJobStore(config: SQSJobStoreConfig): SQSJobStore;
|
|
111
|
+
export default SQSJobStore;
|
|
112
|
+
//# sourceMappingURL=sqs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqs.d.ts","sourceRoot":"","sources":["../../src/adapters/sqs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,EAAE,YAAY,EAAoB,MAAM,kBAAkB,CAAC;AAClE,OAAO,KAAK,EACV,cAAc,EACd,GAAG,EACH,gBAAgB,EAChB,SAAS,EACT,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,iBAAiB;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,kEAAkE;IAClE,cAAc,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA0BD;;;;;;;;GAQG;AACH,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,MAAM,CAA8B;IAE5C,OAAO,CAAC,YAAY,CAAqD;IACzE,OAAO,CAAC,SAAS,CAAkC;IAEnD,OAAO,CAAC,SAAS,CAAoC;gBAEzC,MAAM,EAAE,iBAAiB;IAUrC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBjC;;OAEG;IACH,OAAO,CAAC,WAAW;IAWnB;;OAEG;IACH,OAAO,CAAC,eAAe;IAiDvB;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAoFtD;;OAEG;IACG,OAAO,CACX,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,EAAE,CAAC;IAwCjB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAO/D;;OAEG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAK1C;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IA6B7C;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BvC;;OAEG;IACG,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BtE;;OAEG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB1D;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IA6CvD;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBhE;;OAEG;IACG,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAwChD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAS7B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAExE;AAED,eAAe,WAAW,CAAC"}
|