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