@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 CHANGED
@@ -1,18 +1,6 @@
1
1
  # @bitclaw/jobs
2
2
 
3
- SQLite-backed background job queue for Bun. Features priority ordering, retries with backoff, cron scheduling, job dependencies, batch processing, rate limiting, and a dead-letter table.
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
- ## Worker
53
+ // Enqueue
54
+ queue.add('email:send', { to: 'user@example.com', subject: 'Welcome' })
37
55
 
38
- ```typescript
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.to, job.data.subject)
61
+ await sendEmail(job.data)
62
+ return { sent: true } // stored in job.result
44
63
  },
45
64
  pollIntervalMs: 1000,
46
- maxRate: { count: 10, windowMs: 1000 }
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
- scheduler.start() // ticks every 60s by default
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 } from '@bitclaw/jobs'
70
- import { JobWorker } from '@bitclaw/jobs/worker'
71
- import { JobQueue } from '@bitclaw/jobs/queue'
72
- import { Scheduler } from '@bitclaw/jobs/scheduler'
73
- import { parseCron } from '@bitclaw/jobs/cron'
74
- import { initializeSchema } from '@bitclaw/jobs/schema'
75
- import { SlidingWindowRateLimiter } from '@bitclaw/jobs/rate-limiter'
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
- 118 tests across 7 files.
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
@@ -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,EACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC"}
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
+ }
@@ -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;AAwG3C,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAY/C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAkCnD"}
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
@@ -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"}
@@ -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.5.0",
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": {