@bitclaw/jobs 1.5.0 → 2.1.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 +355 -31
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/otel.d.ts +39 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +45 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +21 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow.d.ts +36 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +219 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
# @bitclaw/jobs
|
|
2
2
|
|
|
3
|
-
SQLite-backed background job queue for Bun.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Typed** Generic `JobQueue<TMap>` with per-type payload validation
|
|
8
|
-
- **Priority** Jobs ordered by priority DESC then created_at ASC
|
|
9
|
-
- **Retries** Configurable `maxRetries` with automatic dead-letter after exhaustion
|
|
10
|
-
- **Dependencies** Blocked jobs auto-unblock when all dependencies complete
|
|
11
|
-
- **Batches** Group jobs, track progress, fire `then`/`finally` callbacks on completion
|
|
12
|
-
- **Cron** 5-field cron parser with `nextCronOccurrence` and overlap control
|
|
13
|
-
- **Scheduler** Persistent `schedules` table with upsert semantics and cleanup
|
|
14
|
-
- **Rate Limiter** In-memory sliding window per-worker throttling
|
|
15
|
-
- **Worker** `setTimeout`-based poll loop with graceful shutdown and per-job timeout
|
|
3
|
+
SQLite-backed background job queue for Bun. Typed generics, multi-process lease safety, priority aging, middleware pipeline, workflow engine with saga compensation, and zero runtime dependencies.
|
|
16
4
|
|
|
17
5
|
## Installation
|
|
18
6
|
|
|
@@ -20,6 +8,36 @@ SQLite-backed background job queue for Bun. Features priority ordering, retries
|
|
|
20
8
|
bun add @bitclaw/jobs
|
|
21
9
|
```
|
|
22
10
|
|
|
11
|
+
Requires Bun ≥ 1.3.0. Uses `bun:sqlite` — no native build step, no extra packages.
|
|
12
|
+
|
|
13
|
+
## Feature Overview
|
|
14
|
+
|
|
15
|
+
| Feature | Details |
|
|
16
|
+
|---|---|
|
|
17
|
+
| **Typed payloads** | `JobQueue<TMap>` — per-type payload inference, no `any` |
|
|
18
|
+
| **Priority + aging** | Jobs ordered by priority DESC; workers can boost old jobs per minute |
|
|
19
|
+
| **Retries + backoff** | `exponential`, `fixed`, `jitter`, `fibonacci`; `retryIf` predicate to skip retries |
|
|
20
|
+
| **Dead-letter** | Exhausted jobs moved to `failed_jobs`, retryable via `retryFailedJob` |
|
|
21
|
+
| **Dependencies** | Blocked jobs auto-unblock when all deps complete |
|
|
22
|
+
| **Multi-process safety** | Lease column (`claimed_until`) prevents double-claim across processes |
|
|
23
|
+
| **Lease renewal** | Long-running handlers call `ctx.renewLease()` to extend their claim |
|
|
24
|
+
| **Batches** | Group jobs, track progress, fire `then`/`finally` callbacks on completion |
|
|
25
|
+
| **Cron scheduler** | 5-field cron parser, persistent `schedules` table, overlap control |
|
|
26
|
+
| **Rate limiter** | Per-worker sliding-window throttle (`maxRate`) |
|
|
27
|
+
| **Middleware** | Onion-style `queue.use(fn)` wraps all executions (logging, OTel, timing) |
|
|
28
|
+
| **Job graph API** | `getJobGraph(id)` traverses dependency DAG via recursive CTE |
|
|
29
|
+
| **Dedup** | `uniqueKey` + `dedup: 'ignore' | 'replace'` — state-aware, key reusable after completion |
|
|
30
|
+
| **TTL** | `expireAt` — expired jobs silently skipped and purgeable |
|
|
31
|
+
| **Result storage** | Handler return value persisted; `getJobResult<T>(id)` to read it |
|
|
32
|
+
| **Webhook on completion** | `onComplete: { url }` fires a detached POST after job finishes |
|
|
33
|
+
| **Typed events** | `queue.on('job:done' | 'job:failed' | 'job:dead' | ...)` |
|
|
34
|
+
| **Pause / resume** | `worker.pause()` / `worker.resume()` — in-flight job finishes, no new claims |
|
|
35
|
+
| **Admin handler** | `queue.mountAdminHandler()` returns a zero-dep `Request → Response` handler |
|
|
36
|
+
| **Workflow engine** | `WorkflowEngine` — typed DAG of steps, saga compensation, restart-safe `reconcile()` |
|
|
37
|
+
| **OpenTelemetry** | `createOtelMiddleware(tracer)` — zero-dep, structural tracer interface |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
23
41
|
## Quick Start
|
|
24
42
|
|
|
25
43
|
```typescript
|
|
@@ -27,58 +45,364 @@ import { JobQueue } from '@bitclaw/jobs'
|
|
|
27
45
|
|
|
28
46
|
type AppJobs = {
|
|
29
47
|
'email:send': { to: string; subject: string }
|
|
48
|
+
'report:generate': { type: string }
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
const queue = new JobQueue<AppJobs>('./jobs.db')
|
|
33
|
-
queue.add('email:send', { to: 'user@test.com', subject: 'Hello' })
|
|
34
|
-
```
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
// Enqueue
|
|
54
|
+
queue.add('email:send', { to: 'user@example.com', subject: 'Welcome' })
|
|
37
55
|
|
|
38
|
-
|
|
56
|
+
// Worker
|
|
39
57
|
const worker = queue.createWorker({
|
|
40
58
|
type: 'email:send',
|
|
41
59
|
handler: async (job, ctx) => {
|
|
42
60
|
ctx.reportProgress(50)
|
|
43
|
-
await sendEmail(job.data
|
|
61
|
+
await sendEmail(job.data)
|
|
62
|
+
return { sent: true } // stored in job.result
|
|
44
63
|
},
|
|
45
64
|
pollIntervalMs: 1000,
|
|
46
|
-
maxRate: { count:
|
|
65
|
+
maxRate: { count: 20, windowMs: 1000 }
|
|
47
66
|
})
|
|
48
67
|
|
|
49
68
|
worker.start()
|
|
50
|
-
// ... later
|
|
51
|
-
await worker.stop()
|
|
52
69
|
```
|
|
53
70
|
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Retries and Backoff
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
queue.add('email:send', data, {
|
|
77
|
+
maxRetries: 5,
|
|
78
|
+
backoff: { type: 'exponential', delayMs: 1000 }
|
|
79
|
+
// types: 'exponential' | 'fixed' | 'jitter' | 'fibonacci'
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Skip retries for permanent errors
|
|
83
|
+
queue.createWorker({
|
|
84
|
+
type: 'email:send',
|
|
85
|
+
handler: async job => { /* ... */ },
|
|
86
|
+
retryIf: (err, job) => !(err instanceof ValidationError)
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Dependencies
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const depA = queue.add('data:fetch', { source: 'api' })
|
|
96
|
+
const depB = queue.add('data:fetch', { source: 'db' })
|
|
97
|
+
|
|
98
|
+
// Blocked until both depA and depB complete
|
|
99
|
+
queue.add('report:generate', { type: 'combined' }, {
|
|
100
|
+
dependsOn: [depA, depB]
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Multi-Process Lease Safety
|
|
107
|
+
|
|
108
|
+
Two workers against the same DB file cannot claim the same job. `pollAndClaim` atomically sets `claimed_until`. If a worker crashes mid-job, any other worker reclaims after lease expiry.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Default lease: 5 minutes. Override per worker:
|
|
112
|
+
queue.createWorker({
|
|
113
|
+
type: 'server:provision',
|
|
114
|
+
handler: async (job, ctx) => {
|
|
115
|
+
await longStep()
|
|
116
|
+
ctx.renewLease() // extend before expiry
|
|
117
|
+
await anotherStep()
|
|
118
|
+
},
|
|
119
|
+
leaseMs: 600_000 // 10 min
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Middleware
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Logging
|
|
129
|
+
queue.use(async (job, next) => {
|
|
130
|
+
console.info(`[job] ${job.type} #${job.id} start`)
|
|
131
|
+
const result = await next()
|
|
132
|
+
console.info(`[job] ${job.type} #${job.id} done`)
|
|
133
|
+
return result
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Multiple middlewares run in registration order (onion)
|
|
137
|
+
queue.use(timingMiddleware)
|
|
138
|
+
queue.use(loggingMiddleware)
|
|
139
|
+
// execution: timing → logging → handler → logging → timing
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## OpenTelemetry
|
|
145
|
+
|
|
146
|
+
No peer dependency. Pass any OTel-compatible tracer — the interface is structural.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { trace } from '@opentelemetry/api'
|
|
150
|
+
import { createOtelMiddleware } from '@bitclaw/jobs/otel'
|
|
151
|
+
|
|
152
|
+
const tracer = trace.getTracer('my-app', '1.0.0')
|
|
153
|
+
queue.use(createOtelMiddleware(tracer))
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Each job execution becomes a span named `job.<type>` with attributes:
|
|
157
|
+
|
|
158
|
+
| Attribute | Value |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `job.id` | numeric job ID |
|
|
161
|
+
| `job.type` | type string |
|
|
162
|
+
| `job.priority` | priority |
|
|
163
|
+
| `job.retry` | retry count at execution time |
|
|
164
|
+
| `job.error` | error message (failure only) |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Priority Aging
|
|
169
|
+
|
|
170
|
+
Prevents starvation of low-priority jobs under sustained high-priority load.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
queue.createWorker({
|
|
174
|
+
type: 'email:send',
|
|
175
|
+
handler: async job => { /* ... */ },
|
|
176
|
+
aging: {
|
|
177
|
+
boostPerMinute: 10, // +10 priority per minute of wait
|
|
178
|
+
maxBoost: 100 // cap
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Job Graph API
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const nodes = queue.getJobGraph(rootJobId)
|
|
189
|
+
// nodes: Array<{ id, type, status, result, dependsOn: number[], dependents: number[] }>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Traverses the full dependency DAG (ancestors + descendants) via SQLite recursive CTE.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Deduplication
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// Ignore: silently re-uses existing pending job
|
|
200
|
+
queue.add('report:generate', data, { uniqueKey: 'daily-2026-06-27', dedup: 'ignore' })
|
|
201
|
+
|
|
202
|
+
// Replace: updates data on existing pending job
|
|
203
|
+
queue.add('report:generate', freshData, { uniqueKey: 'daily-2026-06-27', dedup: 'replace' })
|
|
204
|
+
|
|
205
|
+
// Cancel pending job by key
|
|
206
|
+
queue.cancelByUniqueKey('report:generate', 'daily-2026-06-27')
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Result Storage
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const id = queue.add('data:process', payload)
|
|
215
|
+
|
|
216
|
+
// In handler — return value is stored automatically
|
|
217
|
+
queue.createWorker({
|
|
218
|
+
type: 'data:process',
|
|
219
|
+
handler: async job => {
|
|
220
|
+
return { count: 42, processed: true }
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Read later
|
|
225
|
+
const result = queue.getJobResult<{ count: number; processed: boolean }>(id)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Typed Events
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
queue.on('job:done', job => console.info('done', job.id))
|
|
234
|
+
queue.on('job:failed', (job, err) => console.warn('retry', err))
|
|
235
|
+
queue.on('job:dead', (job, err) => alert.send(err))
|
|
236
|
+
queue.on('job:progress', (job, pct) => ws.send({ id: job.id, pct }))
|
|
237
|
+
queue.on('batch:complete', batch => notify(batch.id))
|
|
238
|
+
|
|
239
|
+
const unsub = queue.on('job:done', cb)
|
|
240
|
+
unsub() // remove listener
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Workflow Engine
|
|
246
|
+
|
|
247
|
+
Typed DAG of steps where each step is a real job. Dependencies handled by the existing `job_dependencies` mechanism — no separate scheduler. Saga compensation runs in reverse topological order if any step fails permanently.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { WorkflowEngine } from '@bitclaw/jobs'
|
|
251
|
+
|
|
252
|
+
const engine = new WorkflowEngine(queue)
|
|
253
|
+
|
|
254
|
+
const { instanceId, jobIds } = engine
|
|
255
|
+
.workflow('order-flow')
|
|
256
|
+
.step('charge', 'payment:charge', { amount: 100 })
|
|
257
|
+
.step('provision', 'server:provision', { serverId: 'srv-1' }, { dependsOn: ['charge'] })
|
|
258
|
+
.step('notify', 'email:welcome', { userId: 'u1' }, { dependsOn: ['provision'] })
|
|
259
|
+
.onFail('charge', {
|
|
260
|
+
compensate: 'payment:refund',
|
|
261
|
+
compensateData: { amount: 100, reason: 'provision-failed' }
|
|
262
|
+
})
|
|
263
|
+
.run()
|
|
264
|
+
|
|
265
|
+
// Reconcile on startup — resumes any interrupted workflows
|
|
266
|
+
engine.reconcile()
|
|
267
|
+
|
|
268
|
+
// Read a step's result from within a dependent handler
|
|
269
|
+
const chargeResult = engine.getStepResult<ChargeResult>(instanceId, 'charge')
|
|
270
|
+
|
|
271
|
+
// Query executions
|
|
272
|
+
engine.listExecutions({ status: 'running', name: 'order-flow' })
|
|
273
|
+
engine.getExecution(instanceId)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Saga semantics:** `onFail(stepName, { compensate, compensateData })` registers a compensation job for a step that completed successfully. If a later step fails permanently, `reconcile()` enqueues compensation jobs (in reverse topological order) for all completed steps that registered one, then transitions the execution to `compensating`. When all compensation jobs finish, the execution is marked `failed`.
|
|
277
|
+
|
|
278
|
+
**Restart safety:** Call `engine.reconcile()` on boot. It finds all `running`/`compensating` executions and advances their state without re-running completed steps.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
54
282
|
## Cron Scheduler
|
|
55
283
|
|
|
56
284
|
```typescript
|
|
57
285
|
import { Scheduler } from '@bitclaw/jobs'
|
|
58
286
|
|
|
59
287
|
const scheduler = new Scheduler(queue)
|
|
288
|
+
|
|
60
289
|
scheduler.register('daily-report', 'report:generate', '0 2 * * *', {
|
|
61
|
-
data: { type: 'daily' }
|
|
290
|
+
data: { type: 'daily' },
|
|
291
|
+
timezone: 'America/New_York',
|
|
292
|
+
overlap: false // skip if previous run still processing
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
scheduler.start() // ticks every 60s by default
|
|
296
|
+
await scheduler.stop()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Admin Handler
|
|
302
|
+
|
|
303
|
+
Zero-dependency `Request → Response` handler. Mount in any framework.
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// TanStack Start / Hono / bare Bun.serve
|
|
307
|
+
const adminHandler = queue.mountAdminHandler('/admin/jobs')
|
|
308
|
+
|
|
309
|
+
// Routes:
|
|
310
|
+
// GET /admin/jobs/stats
|
|
311
|
+
// GET /admin/jobs/jobs
|
|
312
|
+
// GET /admin/jobs/jobs/:id
|
|
313
|
+
// GET /admin/jobs/jobs/:id/graph
|
|
314
|
+
// POST /admin/jobs/jobs/:id/cancel
|
|
315
|
+
// POST /admin/jobs/jobs/:id/force-retry
|
|
316
|
+
// GET /admin/jobs/failed
|
|
317
|
+
// POST /admin/jobs/failed/:id/retry
|
|
318
|
+
// POST /admin/jobs/failed/retry-by-type
|
|
319
|
+
// GET /admin/jobs/jobs/types
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Batch Processing
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
const { batchId, jobIds } = queue.addBatch('nightly-sync', [
|
|
328
|
+
{ type: 'user:sync', data: { userId: 'u1' } },
|
|
329
|
+
{ type: 'user:sync', data: { userId: 'u2' } }
|
|
330
|
+
], {
|
|
331
|
+
thenType: 'report:generate',
|
|
332
|
+
thenData: { trigger: 'batch-done' }
|
|
62
333
|
})
|
|
63
|
-
|
|
334
|
+
|
|
335
|
+
const batch = queue.getBatch(batchId)
|
|
336
|
+
// { totalJobs: 2, pendingJobs: 1, failedJobs: 0, ... }
|
|
64
337
|
```
|
|
65
338
|
|
|
339
|
+
---
|
|
340
|
+
|
|
66
341
|
## Subpath Exports
|
|
67
342
|
|
|
68
343
|
```typescript
|
|
69
|
-
import { JobQueue }
|
|
70
|
-
import {
|
|
71
|
-
import {
|
|
72
|
-
import { Scheduler }
|
|
73
|
-
import { parseCron }
|
|
74
|
-
import { initializeSchema }
|
|
75
|
-
import { SlidingWindowRateLimiter }
|
|
344
|
+
import { JobQueue, WorkflowEngine } from '@bitclaw/jobs'
|
|
345
|
+
import { createOtelMiddleware } from '@bitclaw/jobs/otel'
|
|
346
|
+
import { WorkflowBuilder } from '@bitclaw/jobs/workflow'
|
|
347
|
+
import { Scheduler } from '@bitclaw/jobs/scheduler'
|
|
348
|
+
import { parseCron } from '@bitclaw/jobs/cron'
|
|
349
|
+
import { initializeSchema } from '@bitclaw/jobs/schema'
|
|
350
|
+
import { SlidingWindowRateLimiter } from '@bitclaw/jobs/rate-limiter'
|
|
351
|
+
import { JobWorker } from '@bitclaw/jobs/worker'
|
|
76
352
|
```
|
|
77
353
|
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Competitor Comparison
|
|
357
|
+
|
|
358
|
+
Analysis against every actively-maintained SQLite job queue as of 2026-06.
|
|
359
|
+
|
|
360
|
+
| Feature | **@bitclaw/jobs** | bunqueue | plainjob | workmatic | liteq (Go) | apalis-sqlite (Rust) |
|
|
361
|
+
|---|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
362
|
+
| Typed generics | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
|
363
|
+
| Priority ordering | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
364
|
+
| Priority aging (starvation prevention) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
365
|
+
| Exponential / jitter / fibonacci backoff | ✅ | ✅ (exp only) | ✅ (exp only) | ❌ | ✅ (fixed) | ✅ (exp only) |
|
|
366
|
+
| `retryIf` predicate | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
367
|
+
| Dead-letter table | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
|
|
368
|
+
| Job dependencies (DAG) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
369
|
+
| Job graph API (recursive CTE) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
370
|
+
| Multi-process lease safety | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
|
|
371
|
+
| Lease renewal (`ctx.renewLease`) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
372
|
+
| Dedup (ignore + replace) | ✅ | ✅ (ignore) | ❌ | ✅ (ignore) | ❌ | ❌ |
|
|
373
|
+
| Job TTL / `expireAt` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
374
|
+
| Result storage | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
375
|
+
| Typed events | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
376
|
+
| Middleware pipeline | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
377
|
+
| OpenTelemetry helper | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
378
|
+
| Pause / resume per worker | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
379
|
+
| Webhook on completion | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
380
|
+
| Batch processing | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
|
381
|
+
| Cron scheduler | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
|
382
|
+
| Admin HTTP handler | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
383
|
+
| Workflow engine | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
384
|
+
| Saga compensation | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
385
|
+
| Restart-safe reconcile | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
386
|
+
| Concurrency per worker | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
387
|
+
| Rate limiting per worker | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
388
|
+
| Zero runtime dependencies | ✅ | ✅ | ✅ | ❌ | n/a | n/a |
|
|
389
|
+
|
|
390
|
+
**Key differentiators:**
|
|
391
|
+
|
|
392
|
+
- **Only queue with a workflow engine.** `WorkflowEngine` composes real jobs into typed DAGs with saga compensation and restart-safe reconciliation. Competitors require external orchestrators (Temporal, Inngest) for this.
|
|
393
|
+
- **Only queue with middleware.** `queue.use(fn)` enables logging, tracing, and auth-checking without modifying job handlers. Every other queue buries these concerns inside worker configuration.
|
|
394
|
+
- **Only queue with lease renewal.** Long-running jobs (provisioning, ML inference) can extend their claim without crashing. Competitors force you to size `leaseMs` conservatively.
|
|
395
|
+
- **Only queue with priority aging.** High-throughput workloads can starve low-priority jobs indefinitely. Aging prevents it.
|
|
396
|
+
- **Only queue with `retryIf`.** Permanent errors (bad config, invalid input) should not consume retry budget. All others retry blindly.
|
|
397
|
+
- **Job graph API.** `getJobGraph(id)` returns the full dependency graph via recursive CTE — useful for admin UIs and debugging complex pipelines. No competitor exposes this.
|
|
398
|
+
- **OTel helper.** `createOtelMiddleware(tracer)` instruments all job executions with zero library coupling. No peer dependency — the tracer interface is structural.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
78
402
|
## Testing
|
|
79
403
|
|
|
80
404
|
```bash
|
|
81
405
|
bun test
|
|
82
406
|
```
|
|
83
407
|
|
|
84
|
-
|
|
408
|
+
204 tests across 9 files.
|
package/dist/index.d.ts
CHANGED
|
@@ -2,11 +2,14 @@ export type { ParsedCron } from './cron';
|
|
|
2
2
|
export { cronMatches, nextCronOccurrence, parseCron } from './cron';
|
|
3
3
|
export type { JobQueueEventMap } from './events';
|
|
4
4
|
export { JobQueueEmitter } from './events';
|
|
5
|
+
export type { OtelSpan, OtelTracer } from './otel';
|
|
6
|
+
export { createOtelMiddleware } from './otel';
|
|
5
7
|
export { JobQueue } from './queue';
|
|
6
8
|
export { SlidingWindowRateLimiter } from './rate-limiter';
|
|
7
9
|
export { Scheduler } from './scheduler';
|
|
8
10
|
export { applyPragmas, initializeSchema } from './schema';
|
|
9
|
-
export type { AddJobOptions, AddScheduleOptions, BackoffConfig, BatchOptions, FailedJob, Job, JobBatch, JobContext, JobGraphNode, JobMap, JobStats, JobStatus, ListJobsOptions, MiddlewareFn, PaginatedResult, PurgeOptions, RateLimit, Schedule, WorkerOptions } from './types';
|
|
11
|
+
export type { AddJobOptions, AddScheduleOptions, BackoffConfig, BatchOptions, FailedJob, Job, JobBatch, JobContext, JobGraphNode, JobMap, JobStats, JobStatus, ListJobsOptions, MiddlewareFn, PaginatedResult, PurgeOptions, RateLimit, Schedule, WorkerOptions, WorkflowExecution, WorkflowExecutionStatus, WorkflowReconcileResult, WorkflowRunResult } from './types';
|
|
10
12
|
export { NonRetryableError } from './types';
|
|
11
13
|
export { JobWorker } from './worker';
|
|
14
|
+
export { WorkflowBuilder, WorkflowEngine } from './workflow';
|
|
12
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACpE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,SAAS,EACT,GAAG,EACH,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,SAAS,EACT,eAAe,EACf,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACpE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,SAAS,EACT,GAAG,EACH,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,SAAS,EACT,eAAe,EACf,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// Barrel export for @bitclaw/jobs
|
|
3
3
|
export { cronMatches, nextCronOccurrence, parseCron } from './cron';
|
|
4
4
|
export { JobQueueEmitter } from './events';
|
|
5
|
+
export { createOtelMiddleware } from './otel';
|
|
5
6
|
export { JobQueue } from './queue';
|
|
6
7
|
export { SlidingWindowRateLimiter } from './rate-limiter';
|
|
7
8
|
export { Scheduler } from './scheduler';
|
|
8
9
|
export { applyPragmas, initializeSchema } from './schema';
|
|
9
10
|
export { NonRetryableError } from './types';
|
|
10
11
|
export { JobWorker } from './worker';
|
|
12
|
+
export { WorkflowBuilder, WorkflowEngine } from './workflow';
|
package/dist/otel.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { MiddlewareFn } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal interface matching @opentelemetry/api Tracer.
|
|
4
|
+
* Typed structurally so @bitclaw/jobs stays dependency-free —
|
|
5
|
+
* pass any OTel-compatible tracer without adding it as a peer dep.
|
|
6
|
+
*/
|
|
7
|
+
export type OtelTracer = {
|
|
8
|
+
startActiveSpan<T>(name: string, fn: (span: OtelSpan) => T): T;
|
|
9
|
+
};
|
|
10
|
+
export type OtelSpan = {
|
|
11
|
+
setAttribute(key: string, value: string | number | boolean): void;
|
|
12
|
+
setStatus(status: {
|
|
13
|
+
code: number;
|
|
14
|
+
message?: string;
|
|
15
|
+
}): void;
|
|
16
|
+
recordException(error: unknown): void;
|
|
17
|
+
end(): void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Returns a middleware that wraps every job execution in an OTel span.
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { trace } from '@opentelemetry/api'
|
|
25
|
+
* import { createOtelMiddleware } from '@bitclaw/jobs/otel'
|
|
26
|
+
*
|
|
27
|
+
* const tracer = trace.getTracer('my-app')
|
|
28
|
+
* queue.use(createOtelMiddleware(tracer))
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Each span is named `job.<type>` and carries these attributes:
|
|
32
|
+
* - `job.id` — numeric job ID
|
|
33
|
+
* - `job.type` — job type string
|
|
34
|
+
* - `job.priority` — job priority
|
|
35
|
+
* - `job.retry` — current retry count
|
|
36
|
+
* - `job.error` — error message (only on failure)
|
|
37
|
+
*/
|
|
38
|
+
export declare function createOtelMiddleware(tracer: OtelTracer): MiddlewareFn;
|
|
39
|
+
//# sourceMappingURL=otel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,CAAC,GAAG,CAAC,CAAC;CAChE,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IAClE,SAAS,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACtC,GAAG,IAAI,IAAI,CAAC;CACb,CAAC;AAMF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAqBrE"}
|
package/dist/otel.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** SpanStatusCode.OK = 1, SpanStatusCode.ERROR = 2 (OTel spec) */
|
|
2
|
+
const STATUS_OK = 1;
|
|
3
|
+
const STATUS_ERROR = 2;
|
|
4
|
+
/**
|
|
5
|
+
* Returns a middleware that wraps every job execution in an OTel span.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { trace } from '@opentelemetry/api'
|
|
10
|
+
* import { createOtelMiddleware } from '@bitclaw/jobs/otel'
|
|
11
|
+
*
|
|
12
|
+
* const tracer = trace.getTracer('my-app')
|
|
13
|
+
* queue.use(createOtelMiddleware(tracer))
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Each span is named `job.<type>` and carries these attributes:
|
|
17
|
+
* - `job.id` — numeric job ID
|
|
18
|
+
* - `job.type` — job type string
|
|
19
|
+
* - `job.priority` — job priority
|
|
20
|
+
* - `job.retry` — current retry count
|
|
21
|
+
* - `job.error` — error message (only on failure)
|
|
22
|
+
*/
|
|
23
|
+
export function createOtelMiddleware(tracer) {
|
|
24
|
+
return (job, next) => tracer.startActiveSpan(`job.${job.type}`, async (span) => {
|
|
25
|
+
span.setAttribute('job.id', job.id);
|
|
26
|
+
span.setAttribute('job.type', job.type);
|
|
27
|
+
span.setAttribute('job.priority', job.priority);
|
|
28
|
+
span.setAttribute('job.retry', job.retryCount);
|
|
29
|
+
try {
|
|
30
|
+
const result = await next();
|
|
31
|
+
span.setStatus({ code: STATUS_OK });
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
36
|
+
span.setAttribute('job.error', message);
|
|
37
|
+
span.recordException(err);
|
|
38
|
+
span.setStatus({ code: STATUS_ERROR, message });
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
span.end();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA6H3C,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAY/C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAoCnD"}
|
package/dist/schema.js
CHANGED
|
@@ -90,6 +90,25 @@ CREATE TABLE IF NOT EXISTS schedules (
|
|
|
90
90
|
const SCHEDULES_NEXT_RUN_INDEX = `
|
|
91
91
|
CREATE INDEX IF NOT EXISTS idx_schedules_next_run
|
|
92
92
|
ON schedules (enabled, next_run_at)`;
|
|
93
|
+
const WORKFLOW_EXECUTIONS_TABLE = `
|
|
94
|
+
CREATE TABLE IF NOT EXISTS workflow_executions (
|
|
95
|
+
id TEXT PRIMARY KEY,
|
|
96
|
+
name TEXT NOT NULL,
|
|
97
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
98
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
99
|
+
completed_at TEXT
|
|
100
|
+
)`;
|
|
101
|
+
const WORKFLOW_STEPS_TABLE = `
|
|
102
|
+
CREATE TABLE IF NOT EXISTS workflow_steps (
|
|
103
|
+
execution_id TEXT NOT NULL REFERENCES workflow_executions(id),
|
|
104
|
+
step_name TEXT NOT NULL,
|
|
105
|
+
job_id INTEGER NOT NULL,
|
|
106
|
+
compensate_type TEXT,
|
|
107
|
+
compensate_data TEXT,
|
|
108
|
+
compensate_job_id INTEGER,
|
|
109
|
+
step_order INTEGER NOT NULL,
|
|
110
|
+
PRIMARY KEY (execution_id, step_name)
|
|
111
|
+
)`;
|
|
93
112
|
export function applyPragmas(db) {
|
|
94
113
|
if (db.filename !== ':memory:' && db.filename !== '') {
|
|
95
114
|
db.run('PRAGMA journal_mode = WAL');
|
|
@@ -112,6 +131,8 @@ export function initializeSchema(db) {
|
|
|
112
131
|
db.run(FAILED_JOBS_TABLE);
|
|
113
132
|
db.run(SCHEDULES_TABLE);
|
|
114
133
|
db.run(SCHEDULES_NEXT_RUN_INDEX);
|
|
134
|
+
db.run(WORKFLOW_EXECUTIONS_TABLE);
|
|
135
|
+
db.run(WORKFLOW_STEPS_TABLE);
|
|
115
136
|
// Migrations for columns added after initial schema creation
|
|
116
137
|
const cols = db.prepare('PRAGMA table_info(jobs)').all();
|
|
117
138
|
if (!cols.some(c => c.name === 'unique_key')) {
|
package/dist/types.d.ts
CHANGED
|
@@ -241,4 +241,21 @@ export type AddScheduleOptions = {
|
|
|
241
241
|
overlap?: boolean;
|
|
242
242
|
maxRetries?: number;
|
|
243
243
|
};
|
|
244
|
+
export type WorkflowExecutionStatus = 'running' | 'completed' | 'compensating' | 'failed';
|
|
245
|
+
export type WorkflowExecution = {
|
|
246
|
+
readonly id: string;
|
|
247
|
+
readonly name: string;
|
|
248
|
+
readonly status: WorkflowExecutionStatus;
|
|
249
|
+
readonly createdAt: string;
|
|
250
|
+
readonly completedAt: string | null;
|
|
251
|
+
};
|
|
252
|
+
export type WorkflowRunResult<TStepNames extends string> = {
|
|
253
|
+
instanceId: string;
|
|
254
|
+
jobIds: Record<TStepNames, number>;
|
|
255
|
+
};
|
|
256
|
+
export type WorkflowReconcileResult = {
|
|
257
|
+
completed: number;
|
|
258
|
+
compensated: number;
|
|
259
|
+
failed: number;
|
|
260
|
+
};
|
|
244
261
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { JobQueue } from './queue';
|
|
2
|
+
import type { AddJobOptions, BackoffConfig, JobMap, WorkflowExecution, WorkflowExecutionStatus, WorkflowReconcileResult, WorkflowRunResult } from './types';
|
|
3
|
+
export type { WorkflowExecution, WorkflowExecutionStatus, WorkflowReconcileResult, WorkflowRunResult };
|
|
4
|
+
type StepStepOptions = Omit<AddJobOptions, 'dependsOn'> & {
|
|
5
|
+
dependsOn?: string[];
|
|
6
|
+
backoff?: BackoffConfig;
|
|
7
|
+
};
|
|
8
|
+
export declare class WorkflowBuilder<TMap extends JobMap, TStepNames extends string = never> {
|
|
9
|
+
private readonly queue;
|
|
10
|
+
private readonly name;
|
|
11
|
+
private readonly specs;
|
|
12
|
+
private readonly compensations;
|
|
13
|
+
constructor(queue: JobQueue<TMap>, name: string);
|
|
14
|
+
step<SName extends string, K extends string & keyof TMap>(stepName: SName, jobType: K, data: TMap[K], options?: StepStepOptions): WorkflowBuilder<TMap, TStepNames | SName>;
|
|
15
|
+
onFail<K extends string & keyof TMap>(stepName: TStepNames, compensation: {
|
|
16
|
+
compensate: K;
|
|
17
|
+
compensateData: TMap[K];
|
|
18
|
+
}): this;
|
|
19
|
+
run(instanceId?: string): WorkflowRunResult<TStepNames>;
|
|
20
|
+
}
|
|
21
|
+
export declare class WorkflowEngine<TMap extends JobMap> {
|
|
22
|
+
private readonly queue;
|
|
23
|
+
constructor(queue: JobQueue<TMap>);
|
|
24
|
+
workflow<TStepNames extends string = never>(name: string): WorkflowBuilder<TMap, TStepNames>;
|
|
25
|
+
getExecution(instanceId: string): WorkflowExecution | null;
|
|
26
|
+
getStepResult<T>(instanceId: string, stepName: string): T | null;
|
|
27
|
+
listExecutions(opts?: {
|
|
28
|
+
status?: WorkflowExecutionStatus;
|
|
29
|
+
name?: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
offset?: number;
|
|
32
|
+
}): WorkflowExecution[];
|
|
33
|
+
reconcile(): WorkflowReconcileResult;
|
|
34
|
+
private reconcileExecution;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,MAAM,EACN,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAGjB,YAAY,EACV,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EAClB,CAAC;AAEF,KAAK,eAAe,GAAG,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,GAAG;IACxD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AA6DF,qBAAa,eAAe,CAC1B,IAAI,SAAS,MAAM,EACnB,UAAU,SAAS,MAAM,GAAG,KAAK;IAS/B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;IARvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAG1B;gBAGe,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,IAAI,EAAE,MAAM;IAG/B,IAAI,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,EACtD,QAAQ,EAAE,KAAK,EACf,OAAO,EAAE,CAAC,EACV,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EACb,OAAO,CAAC,EAAE,eAAe,GACxB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,KAAK,CAAC;IAY5C,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,EAClC,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE;QAAE,UAAU,EAAE,CAAC,CAAC;QAAC,cAAc,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;KAAE,GACvD,IAAI;IAQP,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC,UAAU,CAAC;CA4DxD;AAID,qBAAa,cAAc,CAAC,IAAI,SAAS,MAAM;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC;IAElD,QAAQ,CAAC,UAAU,SAAS,MAAM,GAAG,KAAK,EACxC,IAAI,EAAE,MAAM,GACX,eAAe,CAAC,IAAI,EAAE,UAAU,CAAC;IAIpC,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAO1D,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAUhE,cAAc,CACZ,IAAI,GAAE;QACJ,MAAM,CAAC,EAAE,uBAAuB,CAAC;QACjC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACZ,GACL,iBAAiB,EAAE;IAgCtB,SAAS,IAAI,uBAAuB;IAoBpC,OAAO,CAAC,kBAAkB;CA2F3B"}
|
package/dist/workflow.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { nowISO } from './utils';
|
|
2
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
3
|
+
function topologicalSort(specs) {
|
|
4
|
+
const nameToSpec = new Map(specs.map(s => [s.stepName, s]));
|
|
5
|
+
const visited = new Set();
|
|
6
|
+
const result = [];
|
|
7
|
+
const visit = (name) => {
|
|
8
|
+
if (visited.has(name))
|
|
9
|
+
return;
|
|
10
|
+
visited.add(name);
|
|
11
|
+
const spec = nameToSpec.get(name);
|
|
12
|
+
if (!spec)
|
|
13
|
+
throw new Error(`Unknown step '${name}' in dependsOn`);
|
|
14
|
+
for (const dep of spec.dependsOn)
|
|
15
|
+
visit(dep);
|
|
16
|
+
result.push(spec);
|
|
17
|
+
};
|
|
18
|
+
for (const spec of specs)
|
|
19
|
+
visit(spec.stepName);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
function toWorkflowExecution(row) {
|
|
23
|
+
return {
|
|
24
|
+
id: row.id,
|
|
25
|
+
name: row.name,
|
|
26
|
+
status: row.status,
|
|
27
|
+
createdAt: row.created_at,
|
|
28
|
+
completedAt: row.completed_at
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// ─── WorkflowBuilder ────────────────────────────────────────────────────────
|
|
32
|
+
export class WorkflowBuilder {
|
|
33
|
+
queue;
|
|
34
|
+
name;
|
|
35
|
+
specs = [];
|
|
36
|
+
compensations = new Map();
|
|
37
|
+
constructor(queue, name) {
|
|
38
|
+
this.queue = queue;
|
|
39
|
+
this.name = name;
|
|
40
|
+
}
|
|
41
|
+
step(stepName, jobType, data, options) {
|
|
42
|
+
this.specs.push({
|
|
43
|
+
stepName,
|
|
44
|
+
jobType: jobType,
|
|
45
|
+
data,
|
|
46
|
+
dependsOn: (options?.dependsOn ?? []),
|
|
47
|
+
options: options ?? {},
|
|
48
|
+
compensation: null
|
|
49
|
+
});
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
onFail(stepName, compensation) {
|
|
53
|
+
this.compensations.set(stepName, {
|
|
54
|
+
type: compensation.compensate,
|
|
55
|
+
data: compensation.compensateData
|
|
56
|
+
});
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
run(instanceId) {
|
|
60
|
+
const id = instanceId ?? crypto.randomUUID();
|
|
61
|
+
const jobIds = {};
|
|
62
|
+
this.queue.db.transaction(() => {
|
|
63
|
+
this.queue.db.run(`INSERT INTO workflow_executions (id, name, status, created_at)
|
|
64
|
+
VALUES (?, ?, 'running', ?)`, [id, this.name, nowISO()]);
|
|
65
|
+
const sorted = topologicalSort(this.specs);
|
|
66
|
+
for (let order = 0; order < sorted.length; order++) {
|
|
67
|
+
const spec = sorted[order];
|
|
68
|
+
const depJobIds = spec.dependsOn.map(depName => {
|
|
69
|
+
const depId = jobIds[depName];
|
|
70
|
+
if (depId === undefined)
|
|
71
|
+
throw new Error(`Step '${depName}' not enqueued yet — check dependsOn`);
|
|
72
|
+
return depId;
|
|
73
|
+
});
|
|
74
|
+
const addOpts = {
|
|
75
|
+
priority: spec.options.priority,
|
|
76
|
+
maxRetries: spec.options.maxRetries,
|
|
77
|
+
backoff: spec.options.backoff,
|
|
78
|
+
runAt: spec.options.runAt,
|
|
79
|
+
uniqueKey: spec.options.uniqueKey,
|
|
80
|
+
...(depJobIds.length > 0 ? { dependsOn: depJobIds } : {})
|
|
81
|
+
};
|
|
82
|
+
const jobId = this.queue.add(spec.jobType, spec.data, addOpts);
|
|
83
|
+
jobIds[spec.stepName] = jobId;
|
|
84
|
+
const comp = this.compensations.get(spec.stepName) ?? null;
|
|
85
|
+
this.queue.db.run(`INSERT INTO workflow_steps
|
|
86
|
+
(execution_id, step_name, job_id, compensate_type, compensate_data, step_order)
|
|
87
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
88
|
+
id,
|
|
89
|
+
spec.stepName,
|
|
90
|
+
jobId,
|
|
91
|
+
comp?.type ?? null,
|
|
92
|
+
comp ? JSON.stringify(comp.data) : null,
|
|
93
|
+
order
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
return { instanceId: id, jobIds: jobIds };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ─── WorkflowEngine ─────────────────────────────────────────────────────────
|
|
101
|
+
export class WorkflowEngine {
|
|
102
|
+
queue;
|
|
103
|
+
constructor(queue) {
|
|
104
|
+
this.queue = queue;
|
|
105
|
+
}
|
|
106
|
+
workflow(name) {
|
|
107
|
+
return new WorkflowBuilder(this.queue, name);
|
|
108
|
+
}
|
|
109
|
+
getExecution(instanceId) {
|
|
110
|
+
const row = this.queue.db
|
|
111
|
+
.query('SELECT * FROM workflow_executions WHERE id = ?')
|
|
112
|
+
.get(instanceId);
|
|
113
|
+
return row ? toWorkflowExecution(row) : null;
|
|
114
|
+
}
|
|
115
|
+
getStepResult(instanceId, stepName) {
|
|
116
|
+
const step = this.queue.db
|
|
117
|
+
.query('SELECT job_id FROM workflow_steps WHERE execution_id = ? AND step_name = ?')
|
|
118
|
+
.get(instanceId, stepName);
|
|
119
|
+
if (!step)
|
|
120
|
+
return null;
|
|
121
|
+
return this.queue.getJobResult(step.job_id);
|
|
122
|
+
}
|
|
123
|
+
listExecutions(opts = {}) {
|
|
124
|
+
const conditions = [];
|
|
125
|
+
const params = [];
|
|
126
|
+
if (opts.status) {
|
|
127
|
+
conditions.push('status = ?');
|
|
128
|
+
params.push(opts.status);
|
|
129
|
+
}
|
|
130
|
+
if (opts.name) {
|
|
131
|
+
conditions.push('name = ?');
|
|
132
|
+
params.push(opts.name);
|
|
133
|
+
}
|
|
134
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
135
|
+
const limit = opts.limit ?? 50;
|
|
136
|
+
const offset = opts.offset ?? 0;
|
|
137
|
+
const rows = this.queue.db
|
|
138
|
+
.query(`SELECT * FROM workflow_executions ${where}
|
|
139
|
+
ORDER BY created_at DESC LIMIT ? OFFSET ?`)
|
|
140
|
+
.all(...params, limit, offset);
|
|
141
|
+
return rows.map(toWorkflowExecution);
|
|
142
|
+
}
|
|
143
|
+
reconcile() {
|
|
144
|
+
const stats = {
|
|
145
|
+
completed: 0,
|
|
146
|
+
compensated: 0,
|
|
147
|
+
failed: 0
|
|
148
|
+
};
|
|
149
|
+
const running = this.queue.db
|
|
150
|
+
.query("SELECT * FROM workflow_executions WHERE status IN ('running', 'compensating')")
|
|
151
|
+
.all();
|
|
152
|
+
for (const exec of running) {
|
|
153
|
+
this.reconcileExecution(exec, stats);
|
|
154
|
+
}
|
|
155
|
+
return stats;
|
|
156
|
+
}
|
|
157
|
+
reconcileExecution(exec, stats) {
|
|
158
|
+
const steps = this.queue.db
|
|
159
|
+
.query('SELECT * FROM workflow_steps WHERE execution_id = ? ORDER BY step_order ASC')
|
|
160
|
+
.all(exec.id);
|
|
161
|
+
if (exec.status === 'compensating') {
|
|
162
|
+
// If all comp jobs done (or no comp jobs), mark failed
|
|
163
|
+
const pendingComp = steps.filter(s => {
|
|
164
|
+
if (!s.compensate_job_id)
|
|
165
|
+
return false;
|
|
166
|
+
const job = this.queue.getJob(s.compensate_job_id);
|
|
167
|
+
return job !== null && job.status !== 'done';
|
|
168
|
+
});
|
|
169
|
+
if (pendingComp.length === 0) {
|
|
170
|
+
this.queue.db.run("UPDATE workflow_executions SET status = 'failed', completed_at = ? WHERE id = ?", [nowISO(), exec.id]);
|
|
171
|
+
stats.failed++;
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Build step status map
|
|
176
|
+
const stepStatuses = steps.map(step => {
|
|
177
|
+
const job = this.queue.getJob(step.job_id);
|
|
178
|
+
if (!job) {
|
|
179
|
+
// Job deleted from jobs table — check failed_jobs
|
|
180
|
+
const dead = this.queue.db
|
|
181
|
+
.query('SELECT id FROM failed_jobs WHERE original_job_id = ? LIMIT 1')
|
|
182
|
+
.get(step.job_id);
|
|
183
|
+
return { step, status: dead ? 'dead' : 'done' };
|
|
184
|
+
}
|
|
185
|
+
return { step, status: job.status };
|
|
186
|
+
});
|
|
187
|
+
// Any step dead → start compensation
|
|
188
|
+
const deadStep = stepStatuses.find(s => s.status === 'dead');
|
|
189
|
+
if (deadStep) {
|
|
190
|
+
const stepsWithComp = stepStatuses
|
|
191
|
+
.filter(s => s.status === 'done' && s.step.compensate_type)
|
|
192
|
+
.reverse();
|
|
193
|
+
this.queue.db.transaction(() => {
|
|
194
|
+
if (stepsWithComp.length > 0) {
|
|
195
|
+
this.queue.db.run("UPDATE workflow_executions SET status = 'compensating' WHERE id = ?", [exec.id]);
|
|
196
|
+
for (const { step } of stepsWithComp) {
|
|
197
|
+
const compData = step.compensate_data
|
|
198
|
+
? JSON.parse(step.compensate_data)
|
|
199
|
+
: {};
|
|
200
|
+
const compJobId = this.queue.add(step.compensate_type, compData, {});
|
|
201
|
+
this.queue.db.run('UPDATE workflow_steps SET compensate_job_id = ? WHERE execution_id = ? AND step_name = ?', [compJobId, exec.id, step.step_name]);
|
|
202
|
+
}
|
|
203
|
+
stats.compensated++;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// No compensation needed — immediately fail
|
|
207
|
+
this.queue.db.run("UPDATE workflow_executions SET status = 'failed', completed_at = ? WHERE id = ?", [nowISO(), exec.id]);
|
|
208
|
+
stats.failed++;
|
|
209
|
+
}
|
|
210
|
+
})();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// All done → mark completed
|
|
214
|
+
if (stepStatuses.every(s => s.status === 'done')) {
|
|
215
|
+
this.queue.db.run("UPDATE workflow_executions SET status = 'completed', completed_at = ? WHERE id = ?", [nowISO(), exec.id]);
|
|
216
|
+
stats.completed++;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitclaw/jobs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "SQLite-backed background job queue using bun:sqlite",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -38,6 +38,14 @@
|
|
|
38
38
|
"./scheduler": {
|
|
39
39
|
"types": "./dist/scheduler.d.ts",
|
|
40
40
|
"default": "./dist/scheduler.js"
|
|
41
|
+
},
|
|
42
|
+
"./workflow": {
|
|
43
|
+
"types": "./dist/workflow.d.ts",
|
|
44
|
+
"default": "./dist/workflow.js"
|
|
45
|
+
},
|
|
46
|
+
"./otel": {
|
|
47
|
+
"types": "./dist/otel.d.ts",
|
|
48
|
+
"default": "./dist/otel.js"
|
|
41
49
|
}
|
|
42
50
|
},
|
|
43
51
|
"scripts": {
|