workmatic 1.0.0
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/README.md +508 -0
- package/dashboard/app.js +249 -0
- package/dashboard/index.html +143 -0
- package/dashboard/style.css +464 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +505 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +99 -0
- package/dist/client.js.map +1 -0
- package/dist/dashboard.d.ts +48 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +412 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/database.d.ts +29 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +99 -0
- package/dist/database.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +218 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +45 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +64 -0
- package/dist/utils.js.map +1 -0
- package/dist/worker.d.ts +26 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +331 -0
- package/dist/worker.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# Workmatic
|
|
2
|
+
|
|
3
|
+
A persistent job queue for Node.js using SQLite. Simple, reliable, and zero external dependencies beyond SQLite.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
I love [fastq](https://github.com/mcollina/fastq) - it's fast, simple, and has a great API. But it's in-memory only, which can be frustrating when you need jobs to survive process restarts or crashes.
|
|
8
|
+
|
|
9
|
+
Workmatic combines the simplicity of fastq with SQLite persistence. No Redis, no external services - just a single file that keeps your jobs safe. Perfect for small to medium workloads where you want durability without infrastructure complexity.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Persistent**: Jobs survive restarts via SQLite storage
|
|
14
|
+
- **Concurrent**: Process multiple jobs simultaneously with fastq
|
|
15
|
+
- **Priority**: Process high-priority jobs first
|
|
16
|
+
- **Delayed**: Schedule jobs to run in the future
|
|
17
|
+
- **Retries**: Automatic retries with exponential backoff
|
|
18
|
+
- **Lease-based**: Prevents double processing with lease locks
|
|
19
|
+
- **Dashboard**: Built-in web UI for monitoring
|
|
20
|
+
- **Type-safe**: Full TypeScript support with Kysely
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install workmatic
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { createDatabase, createClient, createWorker } from 'workmatic';
|
|
32
|
+
|
|
33
|
+
// Create database (use file path for persistence)
|
|
34
|
+
const db = createDatabase({ filename: './jobs.db' });
|
|
35
|
+
|
|
36
|
+
// Create a client to add jobs
|
|
37
|
+
const client = createClient({ db, queue: 'emails' });
|
|
38
|
+
|
|
39
|
+
// Add a job
|
|
40
|
+
const { id } = await client.add({
|
|
41
|
+
to: 'user@example.com',
|
|
42
|
+
subject: 'Hello!'
|
|
43
|
+
});
|
|
44
|
+
console.log(`Job created: ${id}`);
|
|
45
|
+
|
|
46
|
+
// Create a worker to process jobs
|
|
47
|
+
const worker = createWorker({
|
|
48
|
+
db,
|
|
49
|
+
queue: 'emails',
|
|
50
|
+
concurrency: 4,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Define the processor
|
|
54
|
+
worker.process(async (job) => {
|
|
55
|
+
console.log(`Sending email to ${job.payload.to}`);
|
|
56
|
+
await sendEmail(job.payload);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Start processing
|
|
60
|
+
worker.start();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API Reference
|
|
64
|
+
|
|
65
|
+
### `createDatabase(options)`
|
|
66
|
+
|
|
67
|
+
Initialize the database connection and schema.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const db = createDatabase({
|
|
71
|
+
// Option 1: File path (creates or opens existing)
|
|
72
|
+
filename: './jobs.db',
|
|
73
|
+
|
|
74
|
+
// Option 2: In-memory (for testing)
|
|
75
|
+
filename: ':memory:',
|
|
76
|
+
|
|
77
|
+
// Option 3: Existing better-sqlite3 instance
|
|
78
|
+
db: existingSqliteInstance,
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `createClient(options)`
|
|
83
|
+
|
|
84
|
+
Create a client for adding jobs to a queue.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const client = createClient({
|
|
88
|
+
db, // Required: Database instance
|
|
89
|
+
queue: 'default', // Optional: Queue name (default: 'default')
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### `client.add(payload, options?)`
|
|
94
|
+
|
|
95
|
+
Add a job to the queue.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const { ok, id } = await client.add(
|
|
99
|
+
{ email: 'user@example.com' }, // Payload (must be JSON-serializable)
|
|
100
|
+
{
|
|
101
|
+
priority: 0, // Lower = higher priority (default: 0)
|
|
102
|
+
delayMs: 5000, // Delay before job becomes available (default: 0)
|
|
103
|
+
maxAttempts: 3, // Max retry attempts (default: 3)
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### `client.stats()`
|
|
109
|
+
|
|
110
|
+
Get job statistics for the queue.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const stats = await client.stats();
|
|
114
|
+
// { ready: 5, running: 2, done: 100, failed: 0, dead: 1, total: 108 }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `createWorker(options)`
|
|
118
|
+
|
|
119
|
+
Create a worker to process jobs from a queue.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const worker = createWorker({
|
|
123
|
+
db, // Required: Database instance
|
|
124
|
+
queue: 'default', // Optional: Queue name (default: 'default')
|
|
125
|
+
concurrency: 1, // Optional: Parallel job count (default: 1)
|
|
126
|
+
leaseMs: 30000, // Optional: Job lease duration in ms (default: 30000)
|
|
127
|
+
pollMs: 1000, // Optional: Poll interval when idle (default: 1000)
|
|
128
|
+
timeoutMs: 60000, // Optional: Job execution timeout in ms (default: none)
|
|
129
|
+
backoff: (n) => 1000 * Math.pow(2, n), // Optional: Retry backoff function
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `worker.process(fn)`
|
|
134
|
+
|
|
135
|
+
Set the job processor function.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
worker.process(async (job) => {
|
|
139
|
+
console.log(`Processing job ${job.id}`);
|
|
140
|
+
console.log(`Payload:`, job.payload);
|
|
141
|
+
console.log(`Attempt ${job.attempts + 1} of ${job.maxAttempts}`);
|
|
142
|
+
|
|
143
|
+
// Do work here
|
|
144
|
+
// Throw an error to trigger retry
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### `worker.start()`
|
|
149
|
+
|
|
150
|
+
Start processing jobs.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
worker.start();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### `worker.stop()`
|
|
157
|
+
|
|
158
|
+
Stop processing and wait for current jobs to finish.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
await worker.stop();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### `worker.pause()` / `worker.resume()`
|
|
165
|
+
|
|
166
|
+
Pause and resume job processing.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
worker.pause(); // Stop claiming new jobs
|
|
170
|
+
worker.resume(); // Resume claiming jobs
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `worker.stats()`
|
|
174
|
+
|
|
175
|
+
Get job statistics for the queue.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const stats = await worker.stats();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Worker properties
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
worker.isRunning; // boolean
|
|
185
|
+
worker.isPaused; // boolean
|
|
186
|
+
worker.queue; // string
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `createDashboard(options)`
|
|
190
|
+
|
|
191
|
+
Create a standalone web dashboard server for monitoring and control.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const dashboard = createDashboard({
|
|
195
|
+
db, // Required: Database instance
|
|
196
|
+
port: 3000, // Optional: HTTP port (default: 3000)
|
|
197
|
+
workers: [worker1], // Optional: Workers to control
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
console.log(`Dashboard at http://localhost:${dashboard.port}`);
|
|
201
|
+
|
|
202
|
+
// Later, close the server
|
|
203
|
+
await dashboard.close();
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `createDashboardMiddleware(options)`
|
|
207
|
+
|
|
208
|
+
Create an Express-compatible middleware to mount the dashboard on an existing app.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import express from 'express';
|
|
212
|
+
import { createDashboardMiddleware } from 'workmatic';
|
|
213
|
+
|
|
214
|
+
const app = express();
|
|
215
|
+
|
|
216
|
+
// Mount dashboard at /workmatic
|
|
217
|
+
app.use(createDashboardMiddleware({
|
|
218
|
+
db, // Required: Database instance
|
|
219
|
+
basePath: '/workmatic', // Optional: URL prefix (default: '')
|
|
220
|
+
workers: [worker], // Optional: Workers to control
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
app.listen(3000);
|
|
224
|
+
// Dashboard available at http://localhost:3000/workmatic
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Works with any framework that supports Node.js `(req, res, next)` middleware:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Fastify
|
|
231
|
+
import fastify from 'fastify';
|
|
232
|
+
import middie from '@fastify/middie';
|
|
233
|
+
|
|
234
|
+
const app = fastify();
|
|
235
|
+
await app.register(middie);
|
|
236
|
+
app.use(createDashboardMiddleware({ db, basePath: '/jobs' }));
|
|
237
|
+
|
|
238
|
+
// Hono
|
|
239
|
+
import { Hono } from 'hono';
|
|
240
|
+
import { handle } from 'hono/node-server';
|
|
241
|
+
|
|
242
|
+
const app = new Hono();
|
|
243
|
+
app.use('/workmatic/*', (c) => {
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
const middleware = createDashboardMiddleware({ db, basePath: '/workmatic' });
|
|
246
|
+
middleware(c.env.incoming, c.env.outgoing, resolve);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Job Object
|
|
252
|
+
|
|
253
|
+
The job object passed to processors has these properties:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
interface Job<TPayload> {
|
|
257
|
+
id: string; // Unique public ID (nanoid)
|
|
258
|
+
queue: string; // Queue name
|
|
259
|
+
payload: TPayload; // Your job data
|
|
260
|
+
status: JobStatus; // 'ready' | 'running' | 'done' | 'failed' | 'dead'
|
|
261
|
+
priority: number; // Priority value
|
|
262
|
+
attempts: number; // Current attempt count (starts at 0)
|
|
263
|
+
maxAttempts: number; // Maximum attempts allowed
|
|
264
|
+
createdAt: number; // Unix timestamp (ms)
|
|
265
|
+
lastError: string | null; // Last error message
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Durability Model
|
|
270
|
+
|
|
271
|
+
Workmatic provides **at-least-once** delivery:
|
|
272
|
+
|
|
273
|
+
- Jobs are persisted to SQLite before `add()` returns
|
|
274
|
+
- A job may be processed multiple times if:
|
|
275
|
+
- The worker crashes during processing
|
|
276
|
+
- The lease expires before completion
|
|
277
|
+
- Jobs are only marked `done` after successful processing
|
|
278
|
+
|
|
279
|
+
### Idempotency Recommendation
|
|
280
|
+
|
|
281
|
+
Design your job handlers to be idempotent (safe to run multiple times):
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
worker.process(async (job) => {
|
|
285
|
+
// Check if already processed
|
|
286
|
+
const exists = await db.checkProcessed(job.id);
|
|
287
|
+
if (exists) return;
|
|
288
|
+
|
|
289
|
+
// Process the job
|
|
290
|
+
await processPayment(job.payload);
|
|
291
|
+
|
|
292
|
+
// Mark as processed
|
|
293
|
+
await db.markProcessed(job.id);
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Job Lifecycle
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
┌─────────┐ add() ┌─────────┐
|
|
301
|
+
│ NEW │ ─────────────▶ │ READY │
|
|
302
|
+
└─────────┘ └────┬────┘
|
|
303
|
+
│
|
|
304
|
+
claim │
|
|
305
|
+
▼
|
|
306
|
+
┌─────────┐
|
|
307
|
+
│ RUNNING │
|
|
308
|
+
└────┬────┘
|
|
309
|
+
│
|
|
310
|
+
┌────────────────┼────────────────┐
|
|
311
|
+
│ │ │
|
|
312
|
+
success failure failure
|
|
313
|
+
│ (retries (max
|
|
314
|
+
│ left) attempts)
|
|
315
|
+
▼ │ │
|
|
316
|
+
┌─────────┐ │ ▼
|
|
317
|
+
│ DONE │ │ ┌─────────┐
|
|
318
|
+
└─────────┘ │ │ DEAD │
|
|
319
|
+
│ └─────────┘
|
|
320
|
+
│
|
|
321
|
+
▼
|
|
322
|
+
┌─────────────────┐
|
|
323
|
+
│ READY (retry) │
|
|
324
|
+
│ with backoff │
|
|
325
|
+
└─────────────────┘
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Options Glossary
|
|
329
|
+
|
|
330
|
+
| Option | Default | Description |
|
|
331
|
+
|--------|---------|-------------|
|
|
332
|
+
| `queue` | `'default'` | Queue name for job isolation |
|
|
333
|
+
| `concurrency` | `1` | Number of jobs to process in parallel |
|
|
334
|
+
| `leaseMs` | `30000` | How long a job is "locked" during processing |
|
|
335
|
+
| `pollMs` | `1000` | How often to check for new jobs when idle |
|
|
336
|
+
| `timeoutMs` | `undefined` | Job execution timeout (fails job if exceeded) |
|
|
337
|
+
| `priority` | `0` | Job priority (lower = processed first) |
|
|
338
|
+
| `delayMs` | `0` | Delay before job becomes available |
|
|
339
|
+
| `maxAttempts` | `3` | Maximum processing attempts |
|
|
340
|
+
| `backoff` | `2^n * 1000` | Function returning retry delay in ms |
|
|
341
|
+
|
|
342
|
+
## Dashboard
|
|
343
|
+
|
|
344
|
+
The built-in dashboard provides:
|
|
345
|
+
|
|
346
|
+
- Real-time job statistics
|
|
347
|
+
- Job list with filtering and pagination
|
|
348
|
+
- Worker status and control (pause/resume)
|
|
349
|
+
- Auto-refresh every 2 seconds
|
|
350
|
+
|
|
351
|
+

|
|
352
|
+
|
|
353
|
+
## Examples
|
|
354
|
+
|
|
355
|
+
See the `examples/` directory:
|
|
356
|
+
|
|
357
|
+
- `basic.ts` - Simple job processing
|
|
358
|
+
- `advanced.ts` - Priority, delays, retries
|
|
359
|
+
- `with-dashboard.ts` - Dashboard monitoring
|
|
360
|
+
|
|
361
|
+
Run examples:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
npx tsx examples/basic.ts
|
|
365
|
+
npx tsx examples/with-dashboard.ts
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Benchmarks
|
|
369
|
+
|
|
370
|
+
Run performance benchmarks:
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
# In-memory (fastest, for testing)
|
|
374
|
+
npm run bench
|
|
375
|
+
|
|
376
|
+
# File-based (realistic, persistent)
|
|
377
|
+
npm run bench -- --file
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Results Comparison
|
|
381
|
+
|
|
382
|
+
| Benchmark | In-Memory | File-based |
|
|
383
|
+
|-----------|-----------|------------|
|
|
384
|
+
| Sequential Insert | 27,000/s | 13,000/s |
|
|
385
|
+
| Parallel Insert | 23,000/s | 12,000/s |
|
|
386
|
+
| Process (concurrency=1) | 1,100/s | 1,100/s |
|
|
387
|
+
| Process (concurrency=4) | 4,800/s | 4,800/s |
|
|
388
|
+
| Process (concurrency=8) | 10,000/s | 8,300/s |
|
|
389
|
+
| Process (concurrency=16) | 18,000/s | 5,700/s |
|
|
390
|
+
| Mixed Insert+Process | 7,500/s | 3,500/s |
|
|
391
|
+
| Claim + Process Batch | 23,600/s | 11,800/s |
|
|
392
|
+
|
|
393
|
+
**Note**: File-based performance degrades at high concurrency due to disk I/O. For file-based databases, `concurrency=8` is often optimal. Performance varies by hardware.
|
|
394
|
+
|
|
395
|
+
## CLI
|
|
396
|
+
|
|
397
|
+
Workmatic includes a command-line tool for managing jobs directly from the database file.
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
# Show job statistics
|
|
401
|
+
npx workmatic stats ./jobs.db
|
|
402
|
+
|
|
403
|
+
# List queues with pause status
|
|
404
|
+
npx workmatic queues ./jobs.db
|
|
405
|
+
|
|
406
|
+
# Pause/resume a queue (workers stop/start claiming new jobs)
|
|
407
|
+
npx workmatic pause ./jobs.db emails
|
|
408
|
+
npx workmatic resume ./jobs.db emails
|
|
409
|
+
|
|
410
|
+
# List jobs (with filters)
|
|
411
|
+
npx workmatic list ./jobs.db --status=dead --limit=10
|
|
412
|
+
|
|
413
|
+
# Export jobs to CSV
|
|
414
|
+
npx workmatic export ./jobs.db backup.csv
|
|
415
|
+
npx workmatic export ./jobs.db --status=failed > failed.csv
|
|
416
|
+
|
|
417
|
+
# Import jobs from CSV
|
|
418
|
+
npx workmatic import ./jobs.db backup.csv
|
|
419
|
+
|
|
420
|
+
# Delete jobs by status
|
|
421
|
+
npx workmatic purge ./jobs.db --status=done
|
|
422
|
+
|
|
423
|
+
# Retry dead/failed jobs (reset to ready)
|
|
424
|
+
npx workmatic retry ./jobs.db --status=dead
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### CLI Commands
|
|
428
|
+
|
|
429
|
+
| Command | Description |
|
|
430
|
+
|---------|-------------|
|
|
431
|
+
| `stats <db>` | Show job counts by status and queue |
|
|
432
|
+
| `queues <db>` | List all queues with pause status |
|
|
433
|
+
| `pause <db> <queue>` | Pause a queue (workers stop claiming) |
|
|
434
|
+
| `resume <db> <queue>` | Resume a paused queue |
|
|
435
|
+
| `list <db>` | List jobs with optional filters |
|
|
436
|
+
| `export <db> [file]` | Export jobs to CSV (stdout if no file) |
|
|
437
|
+
| `import <db> <file>` | Import jobs from CSV |
|
|
438
|
+
| `purge <db> --status=X` | Delete jobs with specific status |
|
|
439
|
+
| `retry <db> --status=X` | Reset jobs to ready status |
|
|
440
|
+
|
|
441
|
+
### CLI Options
|
|
442
|
+
|
|
443
|
+
| Option | Description |
|
|
444
|
+
|--------|-------------|
|
|
445
|
+
| `--status=<status>` | Filter by status (ready/running/done/failed/dead) |
|
|
446
|
+
| `--queue=<queue>` | Filter by queue name |
|
|
447
|
+
| `--limit=<n>` | Limit results (default: 100) |
|
|
448
|
+
|
|
449
|
+
### Live Pause/Resume
|
|
450
|
+
|
|
451
|
+
The `pause` and `resume` commands work on running workers in real-time. When you pause a queue:
|
|
452
|
+
- Running workers immediately stop claiming new jobs
|
|
453
|
+
- Jobs currently being processed will complete
|
|
454
|
+
- The queue resumes when you run `resume`
|
|
455
|
+
|
|
456
|
+
This allows you to manage workers without restarting your application.
|
|
457
|
+
|
|
458
|
+
### CSV Import for AI Workflows
|
|
459
|
+
|
|
460
|
+
The CSV import feature makes Workmatic particularly useful for AI-powered automation:
|
|
461
|
+
|
|
462
|
+
```csv
|
|
463
|
+
public_id,queue,payload,status,priority,run_at,attempts,max_attempts,lease_until,created_at,updated_at,last_error
|
|
464
|
+
job_001,emails,"{""to"":""user@example.com"",""template"":""welcome""}",ready,0,1704067200000,0,3,0,1704067200000,1704067200000,
|
|
465
|
+
job_002,emails,"{""to"":""other@example.com"",""template"":""reminder""}",ready,5,1704067200000,0,3,0,1704067200000,1704067200000,
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Use cases:**
|
|
469
|
+
|
|
470
|
+
- **AI Agents**: Tools like Claude, GPT, or custom agents can generate CSV files with batch jobs. Simply ask an AI to "create 100 email jobs for these users" and import the result.
|
|
471
|
+
- **Spreadsheet workflows**: Edit jobs in Excel/Google Sheets, export to CSV, and import into the queue.
|
|
472
|
+
- **Migration**: Move jobs between environments or recover from backups.
|
|
473
|
+
- **Testing**: Generate test datasets with specific job configurations.
|
|
474
|
+
- **Bulk operations**: Create thousands of jobs without writing code.
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
# AI generates jobs.csv, then:
|
|
478
|
+
npx workmatic import ./jobs.db jobs.csv
|
|
479
|
+
|
|
480
|
+
# Or pipe directly from another tool:
|
|
481
|
+
cat ai-generated-jobs.csv | npx workmatic import ./jobs.db /dev/stdin
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
The simple CSV format means any tool that can output text can create jobs for your queue.
|
|
485
|
+
|
|
486
|
+
## Architecture
|
|
487
|
+
|
|
488
|
+
```
|
|
489
|
+
┌──────────────────────────────────────────────────────────┐
|
|
490
|
+
│ Your App │
|
|
491
|
+
├────────────────────┬─────────────────────────────────────┤
|
|
492
|
+
│ Client │ Worker │
|
|
493
|
+
│ ┌─────────────┐ │ ┌─────────────┐ ┌─────────────┐ │
|
|
494
|
+
│ │ add() │ │ │ pump() │ │ fastq │ │
|
|
495
|
+
│ │ stats() │ │ │ claim() │ │ pool │ │
|
|
496
|
+
│ └─────────────┘ │ └─────────────┘ └─────────────┘ │
|
|
497
|
+
├────────────────────┴─────────────────────────────────────┤
|
|
498
|
+
│ Kysely (Query Builder) │
|
|
499
|
+
├──────────────────────────────────────────────────────────┤
|
|
500
|
+
│ better-sqlite3 (SQLite) │
|
|
501
|
+
├──────────────────────────────────────────────────────────┤
|
|
502
|
+
│ jobs.db (File) │
|
|
503
|
+
└──────────────────────────────────────────────────────────┘
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## License
|
|
507
|
+
|
|
508
|
+
MIT
|