workmatic 1.0.0 → 1.0.1
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/cli.cjs +511 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +0 -14
- package/dist/cli.js +342 -359
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +736 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +368 -0
- package/dist/index.d.ts +365 -9
- package/dist/index.js +688 -12
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/dist/cli.d.ts.map +0 -1
- package/dist/client.d.ts +0 -28
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -99
- package/dist/client.js.map +0 -1
- package/dist/dashboard.d.ts +0 -48
- package/dist/dashboard.d.ts.map +0 -1
- package/dist/dashboard.js +0 -412
- package/dist/dashboard.js.map +0 -1
- package/dist/database.d.ts +0 -29
- package/dist/database.d.ts.map +0 -1
- package/dist/database.js +0 -99
- package/dist/database.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -218
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -45
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -64
- package/dist/utils.js.map +0 -1
- package/dist/worker.d.ts +0 -26
- package/dist/worker.d.ts.map +0 -1
- package/dist/worker.js +0 -331
- package/dist/worker.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* purge <db> [--status=done] Delete jobs by status
|
|
10
|
-
* retry <db> [--status=dead] Retry failed/dead jobs
|
|
11
|
-
* pause <db> <queue> Pause a queue (workers stop claiming)
|
|
12
|
-
* resume <db> <queue> Resume a paused queue
|
|
13
|
-
*/
|
|
14
|
-
import Database from 'better-sqlite3';
|
|
15
|
-
import { createReadStream, createWriteStream } from 'fs';
|
|
16
|
-
import { createInterface } from 'readline';
|
|
17
|
-
// Parse command line arguments
|
|
18
|
-
const args = process.argv.slice(2);
|
|
19
|
-
const command = args[0];
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
import { createReadStream, createWriteStream } from "fs";
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
var args = process.argv.slice(2);
|
|
8
|
+
var command = args[0];
|
|
20
9
|
function printUsage() {
|
|
21
|
-
|
|
10
|
+
console.log(`
|
|
22
11
|
Workmatic CLI - Job Queue Management
|
|
23
12
|
|
|
24
13
|
Usage:
|
|
@@ -52,200 +41,195 @@ Examples:
|
|
|
52
41
|
workmatic resume ./jobs.db emails
|
|
53
42
|
`);
|
|
54
43
|
}
|
|
55
|
-
function parseOptions(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
44
|
+
function parseOptions(args2) {
|
|
45
|
+
const options = {};
|
|
46
|
+
for (const arg of args2) {
|
|
47
|
+
if (arg.startsWith("--")) {
|
|
48
|
+
const [key, value] = arg.slice(2).split("=");
|
|
49
|
+
options[key] = value ?? "true";
|
|
62
50
|
}
|
|
63
|
-
|
|
51
|
+
}
|
|
52
|
+
return options;
|
|
64
53
|
}
|
|
65
|
-
function getPositionalArgs(
|
|
66
|
-
|
|
54
|
+
function getPositionalArgs(args2) {
|
|
55
|
+
return args2.filter((arg) => !arg.startsWith("--"));
|
|
67
56
|
}
|
|
68
|
-
// CSV helpers
|
|
69
57
|
function escapeCSV(value) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return str;
|
|
58
|
+
if (value === null) return "";
|
|
59
|
+
const str = String(value);
|
|
60
|
+
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
|
|
61
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
62
|
+
}
|
|
63
|
+
return str;
|
|
77
64
|
}
|
|
78
65
|
function parseCSVLine(line) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
inQuotes = false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
current += char;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
if (char === '"') {
|
|
100
|
-
inQuotes = true;
|
|
101
|
-
}
|
|
102
|
-
else if (char === ',') {
|
|
103
|
-
result.push(current);
|
|
104
|
-
current = '';
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
current += char;
|
|
108
|
-
}
|
|
66
|
+
const result = [];
|
|
67
|
+
let current = "";
|
|
68
|
+
let inQuotes = false;
|
|
69
|
+
for (let i = 0; i < line.length; i++) {
|
|
70
|
+
const char = line[i];
|
|
71
|
+
if (inQuotes) {
|
|
72
|
+
if (char === '"') {
|
|
73
|
+
if (line[i + 1] === '"') {
|
|
74
|
+
current += '"';
|
|
75
|
+
i++;
|
|
76
|
+
} else {
|
|
77
|
+
inQuotes = false;
|
|
109
78
|
}
|
|
79
|
+
} else {
|
|
80
|
+
current += char;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
if (char === '"') {
|
|
84
|
+
inQuotes = true;
|
|
85
|
+
} else if (char === ",") {
|
|
86
|
+
result.push(current);
|
|
87
|
+
current = "";
|
|
88
|
+
} else {
|
|
89
|
+
current += char;
|
|
90
|
+
}
|
|
110
91
|
}
|
|
111
|
-
|
|
112
|
-
|
|
92
|
+
}
|
|
93
|
+
result.push(current);
|
|
94
|
+
return result;
|
|
113
95
|
}
|
|
114
|
-
// Commands
|
|
115
96
|
async function cmdStats(dbPath) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
97
|
+
const db = new Database(dbPath, { readonly: true });
|
|
98
|
+
try {
|
|
99
|
+
const stats = db.prepare(`
|
|
119
100
|
SELECT status, COUNT(*) as count
|
|
120
101
|
FROM workmatic_jobs
|
|
121
102
|
GROUP BY status
|
|
122
103
|
`).all();
|
|
123
|
-
|
|
104
|
+
const queues = db.prepare(`
|
|
124
105
|
SELECT queue, COUNT(*) as count
|
|
125
106
|
FROM workmatic_jobs
|
|
126
107
|
GROUP BY queue
|
|
127
108
|
`).all();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
console.log('─'.repeat(30));
|
|
147
|
-
console.log(` Total: ${total.toLocaleString().padStart(8)}`);
|
|
148
|
-
if (queues.length > 0) {
|
|
149
|
-
console.log('\nBy Queue:');
|
|
150
|
-
console.log('─'.repeat(30));
|
|
151
|
-
for (const q of queues) {
|
|
152
|
-
console.log(` ${q.queue.padEnd(15)} ${q.count.toLocaleString().padStart(8)}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
console.log();
|
|
109
|
+
const total = stats.reduce((sum, s) => sum + s.count, 0);
|
|
110
|
+
console.log("\n\u{1F4CA} Job Statistics\n");
|
|
111
|
+
console.log("By Status:");
|
|
112
|
+
console.log("\u2500".repeat(30));
|
|
113
|
+
const statusOrder = ["ready", "running", "done", "failed", "dead"];
|
|
114
|
+
const statusEmoji = {
|
|
115
|
+
ready: "\u23F3",
|
|
116
|
+
running: "\u25B6\uFE0F ",
|
|
117
|
+
done: "\u2705",
|
|
118
|
+
failed: "\u26A0\uFE0F ",
|
|
119
|
+
dead: "\u{1F480}"
|
|
120
|
+
};
|
|
121
|
+
for (const status of statusOrder) {
|
|
122
|
+
const stat = stats.find((s) => s.status === status);
|
|
123
|
+
const count = stat?.count ?? 0;
|
|
124
|
+
const emoji = statusEmoji[status] || " ";
|
|
125
|
+
console.log(` ${emoji} ${status.padEnd(10)} ${count.toLocaleString().padStart(8)}`);
|
|
156
126
|
}
|
|
157
|
-
|
|
158
|
-
|
|
127
|
+
console.log("\u2500".repeat(30));
|
|
128
|
+
console.log(` Total: ${total.toLocaleString().padStart(8)}`);
|
|
129
|
+
if (queues.length > 0) {
|
|
130
|
+
console.log("\nBy Queue:");
|
|
131
|
+
console.log("\u2500".repeat(30));
|
|
132
|
+
for (const q of queues) {
|
|
133
|
+
console.log(` ${q.queue.padEnd(15)} ${q.count.toLocaleString().padStart(8)}`);
|
|
134
|
+
}
|
|
159
135
|
}
|
|
136
|
+
console.log();
|
|
137
|
+
} finally {
|
|
138
|
+
db.close();
|
|
139
|
+
}
|
|
160
140
|
}
|
|
161
141
|
async function cmdList(dbPath, options) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
console.log('\nID Queue Status Pri Attempts Created');
|
|
182
|
-
console.log('─'.repeat(85));
|
|
183
|
-
for (const job of jobs) {
|
|
184
|
-
const id = job.public_id.slice(0, 21);
|
|
185
|
-
const queue = job.queue.slice(0, 15).padEnd(15);
|
|
186
|
-
const status = job.status.padEnd(9);
|
|
187
|
-
const pri = String(job.priority).padStart(3);
|
|
188
|
-
const attempts = `${job.attempts}/${job.max_attempts}`.padStart(8);
|
|
189
|
-
const created = new Date(job.created_at).toISOString().slice(0, 19);
|
|
190
|
-
console.log(`${id} ${queue} ${status} ${pri} ${attempts} ${created}`);
|
|
191
|
-
}
|
|
192
|
-
console.log(`\nShowing ${jobs.length} jobs\n`);
|
|
142
|
+
const db = new Database(dbPath, { readonly: true });
|
|
143
|
+
try {
|
|
144
|
+
let query = "SELECT public_id, queue, status, priority, attempts, max_attempts, created_at, last_error FROM workmatic_jobs WHERE 1=1";
|
|
145
|
+
const params = [];
|
|
146
|
+
if (options.status) {
|
|
147
|
+
query += " AND status = ?";
|
|
148
|
+
params.push(options.status);
|
|
149
|
+
}
|
|
150
|
+
if (options.queue) {
|
|
151
|
+
query += " AND queue = ?";
|
|
152
|
+
params.push(options.queue);
|
|
153
|
+
}
|
|
154
|
+
query += " ORDER BY created_at DESC LIMIT ?";
|
|
155
|
+
params.push(parseInt(options.limit || "100", 10));
|
|
156
|
+
const jobs = db.prepare(query).all(...params);
|
|
157
|
+
if (jobs.length === 0) {
|
|
158
|
+
console.log("No jobs found.");
|
|
159
|
+
return;
|
|
193
160
|
}
|
|
194
|
-
|
|
195
|
-
|
|
161
|
+
console.log("\nID Queue Status Pri Attempts Created");
|
|
162
|
+
console.log("\u2500".repeat(85));
|
|
163
|
+
for (const job of jobs) {
|
|
164
|
+
const id = job.public_id.slice(0, 21);
|
|
165
|
+
const queue = job.queue.slice(0, 15).padEnd(15);
|
|
166
|
+
const status = job.status.padEnd(9);
|
|
167
|
+
const pri = String(job.priority).padStart(3);
|
|
168
|
+
const attempts = `${job.attempts}/${job.max_attempts}`.padStart(8);
|
|
169
|
+
const created = new Date(job.created_at).toISOString().slice(0, 19);
|
|
170
|
+
console.log(`${id} ${queue} ${status} ${pri} ${attempts} ${created}`);
|
|
196
171
|
}
|
|
172
|
+
console.log(`
|
|
173
|
+
Showing ${jobs.length} jobs
|
|
174
|
+
`);
|
|
175
|
+
} finally {
|
|
176
|
+
db.close();
|
|
177
|
+
}
|
|
197
178
|
}
|
|
198
179
|
async function cmdExport(dbPath, outputPath, options) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
else {
|
|
233
|
-
console.log(row.join(','));
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
if (outputPath) {
|
|
237
|
-
output.end();
|
|
238
|
-
console.error(`✅ Exported ${jobs.length} jobs to ${outputPath}`);
|
|
239
|
-
}
|
|
180
|
+
const db = new Database(dbPath, { readonly: true });
|
|
181
|
+
try {
|
|
182
|
+
let query = "SELECT * FROM workmatic_jobs WHERE 1=1";
|
|
183
|
+
const params = [];
|
|
184
|
+
if (options.status) {
|
|
185
|
+
query += " AND status = ?";
|
|
186
|
+
params.push(options.status);
|
|
187
|
+
}
|
|
188
|
+
if (options.queue) {
|
|
189
|
+
query += " AND queue = ?";
|
|
190
|
+
params.push(options.queue);
|
|
191
|
+
}
|
|
192
|
+
query += " ORDER BY id";
|
|
193
|
+
const jobs = db.prepare(query).all(...params);
|
|
194
|
+
const output = outputPath ? createWriteStream(outputPath) : process.stdout;
|
|
195
|
+
const columns = [
|
|
196
|
+
"public_id",
|
|
197
|
+
"queue",
|
|
198
|
+
"payload",
|
|
199
|
+
"status",
|
|
200
|
+
"priority",
|
|
201
|
+
"run_at",
|
|
202
|
+
"attempts",
|
|
203
|
+
"max_attempts",
|
|
204
|
+
"lease_until",
|
|
205
|
+
"created_at",
|
|
206
|
+
"updated_at",
|
|
207
|
+
"last_error"
|
|
208
|
+
];
|
|
209
|
+
if (outputPath) {
|
|
210
|
+
output.write(columns.join(",") + "\n");
|
|
211
|
+
} else {
|
|
212
|
+
console.log(columns.join(","));
|
|
240
213
|
}
|
|
241
|
-
|
|
242
|
-
|
|
214
|
+
for (const job of jobs) {
|
|
215
|
+
const row = columns.map((col) => escapeCSV(job[col]));
|
|
216
|
+
if (outputPath) {
|
|
217
|
+
output.write(row.join(",") + "\n");
|
|
218
|
+
} else {
|
|
219
|
+
console.log(row.join(","));
|
|
220
|
+
}
|
|
243
221
|
}
|
|
222
|
+
if (outputPath) {
|
|
223
|
+
output.end();
|
|
224
|
+
console.error(`\u2705 Exported ${jobs.length} jobs to ${outputPath}`);
|
|
225
|
+
}
|
|
226
|
+
} finally {
|
|
227
|
+
db.close();
|
|
228
|
+
}
|
|
244
229
|
}
|
|
245
230
|
async function cmdImport(dbPath, inputPath) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
db.exec(`
|
|
231
|
+
const db = new Database(dbPath);
|
|
232
|
+
db.exec(`
|
|
249
233
|
CREATE TABLE IF NOT EXISTS workmatic_jobs (
|
|
250
234
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
251
235
|
public_id TEXT UNIQUE NOT NULL,
|
|
@@ -262,79 +246,87 @@ async function cmdImport(dbPath, inputPath) {
|
|
|
262
246
|
last_error TEXT
|
|
263
247
|
)
|
|
264
248
|
`);
|
|
265
|
-
|
|
249
|
+
const insert = db.prepare(`
|
|
266
250
|
INSERT OR REPLACE INTO workmatic_jobs
|
|
267
251
|
(public_id, queue, payload, status, priority, run_at, attempts, max_attempts, lease_until, created_at, updated_at, last_error)
|
|
268
252
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
269
253
|
`);
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
254
|
+
const rl = createInterface({
|
|
255
|
+
input: createReadStream(inputPath),
|
|
256
|
+
crlfDelay: Infinity
|
|
257
|
+
});
|
|
258
|
+
let lineNum = 0;
|
|
259
|
+
let imported = 0;
|
|
260
|
+
let columns = [];
|
|
261
|
+
try {
|
|
262
|
+
const insertMany = db.transaction((rows) => {
|
|
263
|
+
for (const row of rows) {
|
|
264
|
+
insert.run(
|
|
265
|
+
row.public_id,
|
|
266
|
+
row.queue,
|
|
267
|
+
row.payload,
|
|
268
|
+
row.status,
|
|
269
|
+
parseInt(row.priority, 10) || 0,
|
|
270
|
+
parseInt(row.run_at, 10) || Date.now(),
|
|
271
|
+
parseInt(row.attempts, 10) || 0,
|
|
272
|
+
parseInt(row.max_attempts, 10) || 3,
|
|
273
|
+
parseInt(row.lease_until, 10) || 0,
|
|
274
|
+
parseInt(row.created_at, 10) || Date.now(),
|
|
275
|
+
parseInt(row.updated_at, 10) || Date.now(),
|
|
276
|
+
row.last_error || null
|
|
277
|
+
);
|
|
278
|
+
imported++;
|
|
279
|
+
}
|
|
273
280
|
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
continue;
|
|
294
|
-
const values = parseCSVLine(line);
|
|
295
|
-
const row = {};
|
|
296
|
-
for (let i = 0; i < columns.length; i++) {
|
|
297
|
-
row[columns[i]] = values[i] ?? '';
|
|
298
|
-
}
|
|
299
|
-
batch.push(row);
|
|
300
|
-
// Batch insert every 1000 rows
|
|
301
|
-
if (batch.length >= 1000) {
|
|
302
|
-
insertMany(batch);
|
|
303
|
-
batch.length = 0;
|
|
304
|
-
process.stderr.write(`\rImported ${imported} jobs...`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
// Insert remaining
|
|
308
|
-
if (batch.length > 0) {
|
|
309
|
-
insertMany(batch);
|
|
310
|
-
}
|
|
311
|
-
console.log(`\n✅ Imported ${imported} jobs from ${inputPath}`);
|
|
281
|
+
const batch = [];
|
|
282
|
+
for await (const line of rl) {
|
|
283
|
+
lineNum++;
|
|
284
|
+
if (lineNum === 1) {
|
|
285
|
+
columns = parseCSVLine(line);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!line.trim()) continue;
|
|
289
|
+
const values = parseCSVLine(line);
|
|
290
|
+
const row = {};
|
|
291
|
+
for (let i = 0; i < columns.length; i++) {
|
|
292
|
+
row[columns[i]] = values[i] ?? "";
|
|
293
|
+
}
|
|
294
|
+
batch.push(row);
|
|
295
|
+
if (batch.length >= 1e3) {
|
|
296
|
+
insertMany(batch);
|
|
297
|
+
batch.length = 0;
|
|
298
|
+
process.stderr.write(`\rImported ${imported} jobs...`);
|
|
299
|
+
}
|
|
312
300
|
}
|
|
313
|
-
|
|
314
|
-
|
|
301
|
+
if (batch.length > 0) {
|
|
302
|
+
insertMany(batch);
|
|
315
303
|
}
|
|
304
|
+
console.log(`
|
|
305
|
+
\u2705 Imported ${imported} jobs from ${inputPath}`);
|
|
306
|
+
} finally {
|
|
307
|
+
db.close();
|
|
308
|
+
}
|
|
316
309
|
}
|
|
317
310
|
async function cmdPurge(dbPath, options) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
311
|
+
if (!options.status) {
|
|
312
|
+
console.error("Error: --status is required for purge command");
|
|
313
|
+
console.error("Example: workmatic purge ./jobs.db --status=done");
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
const db = new Database(dbPath);
|
|
317
|
+
try {
|
|
318
|
+
const result = db.prepare("DELETE FROM workmatic_jobs WHERE status = ?").run(options.status);
|
|
319
|
+
console.log(`\u{1F5D1}\uFE0F Deleted ${result.changes} jobs with status '${options.status}'`);
|
|
320
|
+
} finally {
|
|
321
|
+
db.close();
|
|
322
|
+
}
|
|
331
323
|
}
|
|
332
324
|
async function cmdRetry(dbPath, options) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
325
|
+
const status = options.status || "dead";
|
|
326
|
+
const db = new Database(dbPath);
|
|
327
|
+
try {
|
|
328
|
+
const now = Date.now();
|
|
329
|
+
const result = db.prepare(`
|
|
338
330
|
UPDATE workmatic_jobs
|
|
339
331
|
SET status = 'ready',
|
|
340
332
|
attempts = 0,
|
|
@@ -343,65 +335,59 @@ async function cmdRetry(dbPath, options) {
|
|
|
343
335
|
updated_at = ?
|
|
344
336
|
WHERE status = ?
|
|
345
337
|
`).run(now, now, status);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
338
|
+
console.log(`\u{1F504} Reset ${result.changes} jobs from '${status}' to 'ready'`);
|
|
339
|
+
} finally {
|
|
340
|
+
db.close();
|
|
341
|
+
}
|
|
351
342
|
}
|
|
352
343
|
async function cmdPause(dbPath, queueName) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
db.exec(`
|
|
344
|
+
const db = new Database(dbPath);
|
|
345
|
+
try {
|
|
346
|
+
db.exec(`
|
|
357
347
|
CREATE TABLE IF NOT EXISTS workmatic_settings (
|
|
358
348
|
queue TEXT PRIMARY KEY,
|
|
359
349
|
paused INTEGER NOT NULL DEFAULT 0,
|
|
360
350
|
updated_at INTEGER NOT NULL
|
|
361
351
|
)
|
|
362
352
|
`);
|
|
363
|
-
|
|
364
|
-
|
|
353
|
+
const now = Date.now();
|
|
354
|
+
db.prepare(`
|
|
365
355
|
INSERT INTO workmatic_settings (queue, paused, updated_at)
|
|
366
356
|
VALUES (?, 1, ?)
|
|
367
357
|
ON CONFLICT(queue) DO UPDATE SET paused = 1, updated_at = ?
|
|
368
358
|
`).run(queueName, now, now);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
359
|
+
console.log(`\u23F8\uFE0F Paused queue '${queueName}'`);
|
|
360
|
+
console.log(` Running workers will stop claiming new jobs.`);
|
|
361
|
+
} finally {
|
|
362
|
+
db.close();
|
|
363
|
+
}
|
|
375
364
|
}
|
|
376
365
|
async function cmdResume(dbPath, queueName) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
db.exec(`
|
|
366
|
+
const db = new Database(dbPath);
|
|
367
|
+
try {
|
|
368
|
+
db.exec(`
|
|
381
369
|
CREATE TABLE IF NOT EXISTS workmatic_settings (
|
|
382
370
|
queue TEXT PRIMARY KEY,
|
|
383
371
|
paused INTEGER NOT NULL DEFAULT 0,
|
|
384
372
|
updated_at INTEGER NOT NULL
|
|
385
373
|
)
|
|
386
374
|
`);
|
|
387
|
-
|
|
388
|
-
|
|
375
|
+
const now = Date.now();
|
|
376
|
+
db.prepare(`
|
|
389
377
|
INSERT INTO workmatic_settings (queue, paused, updated_at)
|
|
390
378
|
VALUES (?, 0, ?)
|
|
391
379
|
ON CONFLICT(queue) DO UPDATE SET paused = 0, updated_at = ?
|
|
392
380
|
`).run(queueName, now, now);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
381
|
+
console.log(`\u25B6\uFE0F Resumed queue '${queueName}'`);
|
|
382
|
+
console.log(` Workers will start claiming jobs again.`);
|
|
383
|
+
} finally {
|
|
384
|
+
db.close();
|
|
385
|
+
}
|
|
399
386
|
}
|
|
400
387
|
async function cmdQueues(dbPath) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const queues = db.prepare(`
|
|
388
|
+
const db = new Database(dbPath, { readonly: true });
|
|
389
|
+
try {
|
|
390
|
+
const queues = db.prepare(`
|
|
405
391
|
SELECT
|
|
406
392
|
j.queue,
|
|
407
393
|
COUNT(*) as total,
|
|
@@ -412,94 +398,91 @@ async function cmdQueues(dbPath) {
|
|
|
412
398
|
LEFT JOIN workmatic_settings s ON j.queue = s.queue
|
|
413
399
|
GROUP BY j.queue
|
|
414
400
|
`).all();
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
console.log('\n📋 Queues\n');
|
|
420
|
-
console.log('Queue Status Ready Running Total');
|
|
421
|
-
console.log('─'.repeat(55));
|
|
422
|
-
for (const q of queues) {
|
|
423
|
-
const name = q.queue.padEnd(16);
|
|
424
|
-
const status = q.paused ? '⏸️ PAUSED' : '▶️ ACTIVE';
|
|
425
|
-
const ready = String(q.ready).padStart(5);
|
|
426
|
-
const running = String(q.running).padStart(8);
|
|
427
|
-
const total = String(q.total).padStart(6);
|
|
428
|
-
console.log(`${name} ${status} ${ready} ${running} ${total}`);
|
|
429
|
-
}
|
|
430
|
-
console.log();
|
|
401
|
+
if (queues.length === 0) {
|
|
402
|
+
console.log("No queues found.");
|
|
403
|
+
return;
|
|
431
404
|
}
|
|
432
|
-
|
|
433
|
-
|
|
405
|
+
console.log("\n\u{1F4CB} Queues\n");
|
|
406
|
+
console.log("Queue Status Ready Running Total");
|
|
407
|
+
console.log("\u2500".repeat(55));
|
|
408
|
+
for (const q of queues) {
|
|
409
|
+
const name = q.queue.padEnd(16);
|
|
410
|
+
const status = q.paused ? "\u23F8\uFE0F PAUSED" : "\u25B6\uFE0F ACTIVE";
|
|
411
|
+
const ready = String(q.ready).padStart(5);
|
|
412
|
+
const running = String(q.running).padStart(8);
|
|
413
|
+
const total = String(q.total).padStart(6);
|
|
414
|
+
console.log(`${name} ${status} ${ready} ${running} ${total}`);
|
|
434
415
|
}
|
|
416
|
+
console.log();
|
|
417
|
+
} finally {
|
|
418
|
+
db.close();
|
|
419
|
+
}
|
|
435
420
|
}
|
|
436
|
-
// Main
|
|
437
421
|
async function main() {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
await cmdImport(dbPath, positionalArgs[1]);
|
|
467
|
-
break;
|
|
468
|
-
case 'purge':
|
|
469
|
-
await cmdPurge(dbPath, options);
|
|
470
|
-
break;
|
|
471
|
-
case 'retry':
|
|
472
|
-
await cmdRetry(dbPath, options);
|
|
473
|
-
break;
|
|
474
|
-
case 'pause':
|
|
475
|
-
if (!positionalArgs[1]) {
|
|
476
|
-
console.error('Error: Queue name is required');
|
|
477
|
-
console.error('Example: workmatic pause ./jobs.db emails');
|
|
478
|
-
process.exit(1);
|
|
479
|
-
}
|
|
480
|
-
await cmdPause(dbPath, positionalArgs[1]);
|
|
481
|
-
break;
|
|
482
|
-
case 'resume':
|
|
483
|
-
if (!positionalArgs[1]) {
|
|
484
|
-
console.error('Error: Queue name is required');
|
|
485
|
-
console.error('Example: workmatic resume ./jobs.db emails');
|
|
486
|
-
process.exit(1);
|
|
487
|
-
}
|
|
488
|
-
await cmdResume(dbPath, positionalArgs[1]);
|
|
489
|
-
break;
|
|
490
|
-
case 'queues':
|
|
491
|
-
await cmdQueues(dbPath);
|
|
492
|
-
break;
|
|
493
|
-
default:
|
|
494
|
-
console.error(`Unknown command: ${command}`);
|
|
495
|
-
printUsage();
|
|
496
|
-
process.exit(1);
|
|
422
|
+
if (!command || command === "--help" || command === "-h") {
|
|
423
|
+
printUsage();
|
|
424
|
+
process.exit(0);
|
|
425
|
+
}
|
|
426
|
+
const positionalArgs = getPositionalArgs(args.slice(1));
|
|
427
|
+
const options = parseOptions(args);
|
|
428
|
+
const dbPath = positionalArgs[0];
|
|
429
|
+
if (!dbPath) {
|
|
430
|
+
console.error("Error: Database path is required");
|
|
431
|
+
printUsage();
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
switch (command) {
|
|
436
|
+
case "stats":
|
|
437
|
+
await cmdStats(dbPath);
|
|
438
|
+
break;
|
|
439
|
+
case "list":
|
|
440
|
+
await cmdList(dbPath, options);
|
|
441
|
+
break;
|
|
442
|
+
case "export":
|
|
443
|
+
await cmdExport(dbPath, positionalArgs[1], options);
|
|
444
|
+
break;
|
|
445
|
+
case "import":
|
|
446
|
+
if (!positionalArgs[1]) {
|
|
447
|
+
console.error("Error: Input CSV file is required");
|
|
448
|
+
process.exit(1);
|
|
497
449
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
450
|
+
await cmdImport(dbPath, positionalArgs[1]);
|
|
451
|
+
break;
|
|
452
|
+
case "purge":
|
|
453
|
+
await cmdPurge(dbPath, options);
|
|
454
|
+
break;
|
|
455
|
+
case "retry":
|
|
456
|
+
await cmdRetry(dbPath, options);
|
|
457
|
+
break;
|
|
458
|
+
case "pause":
|
|
459
|
+
if (!positionalArgs[1]) {
|
|
460
|
+
console.error("Error: Queue name is required");
|
|
461
|
+
console.error("Example: workmatic pause ./jobs.db emails");
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
await cmdPause(dbPath, positionalArgs[1]);
|
|
465
|
+
break;
|
|
466
|
+
case "resume":
|
|
467
|
+
if (!positionalArgs[1]) {
|
|
468
|
+
console.error("Error: Queue name is required");
|
|
469
|
+
console.error("Example: workmatic resume ./jobs.db emails");
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
await cmdResume(dbPath, positionalArgs[1]);
|
|
473
|
+
break;
|
|
474
|
+
case "queues":
|
|
475
|
+
await cmdQueues(dbPath);
|
|
476
|
+
break;
|
|
477
|
+
default:
|
|
478
|
+
console.error(`Unknown command: ${command}`);
|
|
479
|
+
printUsage();
|
|
501
480
|
process.exit(1);
|
|
502
481
|
}
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
503
486
|
}
|
|
504
487
|
main();
|
|
505
488
|
//# sourceMappingURL=cli.js.map
|