@bitclaw/jobs 1.3.0 → 1.4.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.
@@ -0,0 +1,20 @@
1
+ import type { Job, JobBatch } from './types';
2
+ export type JobQueueEventMap = {
3
+ 'job:done': (job: Job) => void;
4
+ 'job:failed': (job: Job, error: string) => void;
5
+ 'job:dead': (job: Job, error: string) => void;
6
+ 'job:progress': (job: Job, progress: number) => void;
7
+ 'job:stale': (count: number) => void;
8
+ 'batch:complete': (batch: JobBatch) => void;
9
+ 'batch:failed': (batch: JobBatch) => void;
10
+ };
11
+ type Handler<K extends keyof JobQueueEventMap> = JobQueueEventMap[K];
12
+ export declare class JobQueueEmitter {
13
+ private readonly listeners;
14
+ on<K extends keyof JobQueueEventMap>(event: K, handler: Handler<K>): () => void;
15
+ off<K extends keyof JobQueueEventMap>(event: K, handler: Handler<K>): void;
16
+ once<K extends keyof JobQueueEventMap>(event: K, handler: Handler<K>): void;
17
+ emit<K extends keyof JobQueueEventMap>(event: K, ...args: Parameters<JobQueueEventMap[K]>): void;
18
+ }
19
+ export {};
20
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,YAAY,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,cAAc,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC5C,cAAc,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC3C,CAAC;AAEF,KAAK,OAAO,CAAC,CAAC,SAAS,MAAM,gBAAgB,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAErE,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAGtB;IAEJ,EAAE,CAAC,CAAC,SAAS,MAAM,gBAAgB,EACjC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAClB,MAAM,IAAI;IAQb,GAAG,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAI1E,IAAI,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAQ3E,IAAI,CAAC,CAAC,SAAS,MAAM,gBAAgB,EACnC,KAAK,EAAE,CAAC,EACR,GAAG,IAAI,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GACvC,IAAI;CAWR"}
package/dist/events.js ADDED
@@ -0,0 +1,33 @@
1
+ export class JobQueueEmitter {
2
+ listeners = new Map();
3
+ on(event, handler) {
4
+ if (!this.listeners.has(event)) {
5
+ this.listeners.set(event, new Set());
6
+ }
7
+ this.listeners.get(event).add(handler);
8
+ return () => this.off(event, handler);
9
+ }
10
+ off(event, handler) {
11
+ this.listeners.get(event)?.delete(handler);
12
+ }
13
+ once(event, handler) {
14
+ const wrapper = ((...args) => {
15
+ this.off(event, wrapper);
16
+ handler(...args);
17
+ });
18
+ this.on(event, wrapper);
19
+ }
20
+ emit(event, ...args) {
21
+ const handlers = this.listeners.get(event);
22
+ if (!handlers)
23
+ return;
24
+ for (const handler of handlers) {
25
+ try {
26
+ handler(...args);
27
+ }
28
+ catch {
29
+ // silently swallow listener errors
30
+ }
31
+ }
32
+ }
33
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export type { ParsedCron } from './cron';
2
2
  export { cronMatches, nextCronOccurrence, parseCron } from './cron';
3
+ export type { JobQueueEventMap } from './events';
4
+ export { JobQueueEmitter } from './events';
3
5
  export { JobQueue } from './queue';
4
6
  export { SlidingWindowRateLimiter } from './rate-limiter';
5
7
  export { Scheduler } from './scheduler';
@@ -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,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,MAAM,EACN,QAAQ,EACR,SAAS,EACT,eAAe,EACf,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,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,MAAM,EACN,QAAQ,EACR,SAAS,EACT,eAAe,EACf,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"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // packages/jobs/src/index.ts
2
2
  // Barrel export for @bitclaw/jobs
3
3
  export { cronMatches, nextCronOccurrence, parseCron } from './cron';
4
+ export { JobQueueEmitter } from './events';
4
5
  export { JobQueue } from './queue';
5
6
  export { SlidingWindowRateLimiter } from './rate-limiter';
6
7
  export { Scheduler } from './scheduler';
package/dist/queue.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Database } from 'bun:sqlite';
2
+ import { JobQueueEmitter } from './events';
2
3
  import type { AddJobOptions, BatchOptions, FailedJob, Job, JobBatch, JobMap, JobStats, ListJobsOptions, PaginatedResult, PurgeOptions, WorkerOptions } from './types';
3
4
  import { JobWorker } from './worker';
4
- export declare class JobQueue<TMap extends JobMap = Record<string, unknown>> {
5
+ export declare class JobQueue<TMap extends JobMap = Record<string, unknown>> extends JobQueueEmitter {
5
6
  readonly db: Database;
6
7
  private readonly insertJobStmt;
7
8
  private readonly selectDedupedJobStmt;
@@ -20,6 +21,7 @@ export declare class JobQueue<TMap extends JobMap = Record<string, unknown>> {
20
21
  private readonly countUnmetDepsStmt;
21
22
  private readonly unblockJobStmt;
22
23
  private readonly lastInsertRowIdStmt;
24
+ private readonly renewLeaseStmt;
23
25
  private readonly insertBatchStmt;
24
26
  private readonly selectBatchStmt;
25
27
  private readonly decrementBatchPendingStmt;
@@ -44,10 +46,12 @@ export declare class JobQueue<TMap extends JobMap = Record<string, unknown>> {
44
46
  retryFailedJob(failedJobId: number): number;
45
47
  purgeFailedJobs(olderThanMs: number): number;
46
48
  purge(options: PurgeOptions): number;
47
- pollAndClaim(type: string): Job | null;
48
- markJobDone(id: number): void;
49
+ pollAndClaim(type: string, leaseMs?: number): Job | null;
50
+ renewLease(id: number, leaseMs: number): void;
51
+ markJobDone(id: number, result?: unknown): void;
49
52
  markJobDead(id: number, error: string): void;
50
53
  markJobFailed(id: number, error: string): void;
54
+ private fib;
51
55
  updateProgress(id: number, progress: number): void;
52
56
  createBatch(name: string, options?: BatchOptions): string;
53
57
  addToBatch<K extends string & keyof TMap>(batchId: string, type: K, data: TMap[K], options?: AddJobOptions): number;
@@ -69,6 +73,10 @@ export declare class JobQueue<TMap extends JobMap = Record<string, unknown>> {
69
73
  * Returns null if no such job exists (completed, dead-lettered, or never queued).
70
74
  */
71
75
  getJobByUniqueKey(type: string, uniqueKey: string): Job | null;
76
+ getJobResult<T>(id: number): T | null;
77
+ cancelByUniqueKey(type: string, uniqueKey: string): boolean;
78
+ retryFailedJobsByType(type: string): number;
79
+ purgeExpiredJobs(): number;
72
80
  close(): void;
73
81
  private unblockDependents;
74
82
  private handleBatchJobComplete;
@@ -1 +1 @@
1
- {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
package/dist/queue.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { Database } from 'bun:sqlite';
4
4
  import { mkdirSync } from 'node:fs';
5
5
  import { dirname } from 'node:path';
6
+ import { JobQueueEmitter } from './events';
6
7
  import { applyPragmas, initializeSchema } from './schema';
7
8
  import { nowISO } from './utils';
8
9
  import { JobWorker } from './worker';
@@ -24,7 +25,11 @@ function toJob(row) {
24
25
  error: row.error,
25
26
  batchId: row.batch_id,
26
27
  requestLog: row.request_log,
27
- responseLog: row.response_log
28
+ responseLog: row.response_log,
29
+ uniqueKey: row.unique_key,
30
+ claimedUntil: row.claimed_until,
31
+ result: row.result ? JSON.parse(row.result) : null,
32
+ expireAt: row.expire_at
28
33
  };
29
34
  }
30
35
  function toBatch(row) {
@@ -56,7 +61,7 @@ function toFailedJob(row) {
56
61
  responseLog: row.response_log
57
62
  };
58
63
  }
59
- export class JobQueue {
64
+ export class JobQueue extends JobQueueEmitter {
60
65
  db;
61
66
  insertJobStmt;
62
67
  selectDedupedJobStmt;
@@ -75,6 +80,7 @@ export class JobQueue {
75
80
  countUnmetDepsStmt;
76
81
  unblockJobStmt;
77
82
  lastInsertRowIdStmt;
83
+ renewLeaseStmt;
78
84
  // Batch statements
79
85
  insertBatchStmt;
80
86
  selectBatchStmt;
@@ -84,13 +90,14 @@ export class JobQueue {
84
90
  cancelBatchStmt;
85
91
  cancelBatchJobsStmt;
86
92
  constructor(dbPath) {
93
+ super();
87
94
  mkdirSync(dirname(dbPath), { recursive: true });
88
95
  this.db = new Database(dbPath, { create: true });
89
96
  applyPragmas(this.db);
90
97
  initializeSchema(this.db);
91
98
  this.insertJobStmt = this.db.query(`
92
- INSERT OR IGNORE INTO jobs (type, data, status, priority, max_retries, run_at, batch_id, unique_key, backoff_config)
93
- VALUES ($type, $data, $status, $priority, $maxRetries, $runAt, $batchId, $uniqueKey, $backoffConfig)
99
+ INSERT OR IGNORE INTO jobs (type, data, status, priority, max_retries, run_at, batch_id, unique_key, backoff_config, expire_at, webhook_config)
100
+ VALUES ($type, $data, $status, $priority, $maxRetries, $runAt, $batchId, $uniqueKey, $backoffConfig, $expireAt, $webhookConfig)
94
101
  `);
95
102
  this.selectDedupedJobStmt = this.db.query(`
96
103
  SELECT id FROM jobs
@@ -104,16 +111,21 @@ export class JobQueue {
104
111
  this.selectJobStmt = this.db.query('SELECT * FROM jobs WHERE id = $id');
105
112
  this.selectPendingStmt = this.db.query(`
106
113
  SELECT * FROM jobs
107
- WHERE status = 'pending' AND type = $type AND run_at <= $now
114
+ WHERE type = $type AND run_at <= $now
115
+ AND (expire_at IS NULL OR expire_at > $now)
116
+ AND (
117
+ (status = 'pending')
118
+ OR (status = 'processing' AND claimed_until IS NOT NULL AND claimed_until < $now)
119
+ )
108
120
  ORDER BY priority DESC, created_at ASC
109
121
  LIMIT 1
110
122
  `);
111
123
  this.markProcessingStmt = this.db.query(`
112
- UPDATE jobs SET status = 'processing', started_at = $now, updated_at = $now
124
+ UPDATE jobs SET status = 'processing', started_at = $now, updated_at = $now, claimed_until = $claimedUntil
113
125
  WHERE id = $id
114
126
  `);
115
127
  this.markDoneStmt = this.db.query(`
116
- UPDATE jobs SET status = 'done', completed_at = $now, updated_at = $now, progress = 100
128
+ UPDATE jobs SET status = 'done', completed_at = $now, updated_at = $now, progress = 100, result = $result
117
129
  WHERE id = $id
118
130
  `);
119
131
  this.markFailedStmt = this.db.query(`
@@ -148,6 +160,10 @@ export class JobQueue {
148
160
  WHERE id = $id AND status = 'blocked'
149
161
  `);
150
162
  this.lastInsertRowIdStmt = this.db.query('SELECT last_insert_rowid() as id');
163
+ this.renewLeaseStmt = this.db.query(`
164
+ UPDATE jobs SET claimed_until = $claimedUntil, updated_at = $now
165
+ WHERE id = $id AND status = 'processing'
166
+ `);
151
167
  // Batch statements
152
168
  this.insertBatchStmt = this.db.query(`
153
169
  INSERT INTO job_batches (id, name, options, created_at)
@@ -289,7 +305,9 @@ export class JobQueue {
289
305
  $runAt: now,
290
306
  $batchId: null,
291
307
  $uniqueKey: null,
292
- $backoffConfig: null
308
+ $backoffConfig: null,
309
+ $expireAt: null,
310
+ $webhookConfig: null
293
311
  });
294
312
  const newJobId = this.lastInsertRowIdStmt.get();
295
313
  this.db
@@ -311,8 +329,9 @@ export class JobQueue {
311
329
  .run({ $status: options.status, $cutoff: cutoff });
312
330
  return result.changes;
313
331
  }
314
- pollAndClaim(type) {
332
+ pollAndClaim(type, leaseMs = 300_000) {
315
333
  const now = nowISO();
334
+ const claimedUntil = new Date(Date.now() + leaseMs).toISOString();
316
335
  const claimTx = this.db.transaction(() => {
317
336
  const row = this.selectPendingStmt.get({
318
337
  $type: type,
@@ -320,28 +339,54 @@ export class JobQueue {
320
339
  });
321
340
  if (!row)
322
341
  return null;
323
- this.markProcessingStmt.run({ $id: row.id, $now: now });
342
+ this.markProcessingStmt.run({
343
+ $id: row.id,
344
+ $now: now,
345
+ $claimedUntil: claimedUntil
346
+ });
324
347
  return row;
325
348
  });
326
349
  const row = claimTx.immediate();
327
350
  return row ? toJob(row) : null;
328
351
  }
329
- markJobDone(id) {
352
+ renewLease(id, leaseMs) {
353
+ const claimedUntil = new Date(Date.now() + leaseMs).toISOString();
354
+ this.renewLeaseStmt.run({
355
+ $id: id,
356
+ $claimedUntil: claimedUntil,
357
+ $now: nowISO()
358
+ });
359
+ }
360
+ markJobDone(id, result) {
361
+ let doneJob = null;
330
362
  this.db.transaction(() => {
331
363
  const now = nowISO();
332
364
  const row = this.selectJobStmt.get({ $id: id });
333
- this.markDoneStmt.run({ $id: id, $now: now });
365
+ this.markDoneStmt.run({
366
+ $id: id,
367
+ $now: now,
368
+ $result: result !== undefined ? JSON.stringify(result) : null
369
+ });
334
370
  this.unblockDependents(id);
335
371
  if (row?.batch_id) {
336
372
  this.handleBatchJobComplete(row.batch_id);
337
373
  }
374
+ // capture for post-tx emit
375
+ const updatedRow = this.selectJobStmt.get({ $id: id });
376
+ if (updatedRow)
377
+ doneJob = toJob(updatedRow);
338
378
  })();
379
+ if (doneJob)
380
+ this.emit('job:done', doneJob);
339
381
  }
340
382
  markJobDead(id, error) {
383
+ let deadJob = null;
341
384
  this.db.transaction(() => {
342
385
  const row = this.selectJobStmt.get({ $id: id });
343
386
  if (!row)
344
387
  return;
388
+ // capture before delete
389
+ deadJob = toJob(row);
345
390
  this.insertFailedJobStmt.run({
346
391
  $originalJobId: row.id,
347
392
  $type: row.type,
@@ -362,8 +407,11 @@ export class JobQueue {
362
407
  this.handleBatchJobComplete(row.batch_id);
363
408
  }
364
409
  })();
410
+ if (deadJob)
411
+ this.emit('job:dead', deadJob, error);
365
412
  }
366
413
  markJobFailed(id, error) {
414
+ let failedJob = null;
367
415
  this.db.transaction(() => {
368
416
  const now = nowISO();
369
417
  const row = this.selectJobStmt.get({ $id: id });
@@ -397,9 +445,20 @@ export class JobQueue {
397
445
  : null;
398
446
  let retryRunAt = now;
399
447
  if (backoff) {
400
- const delayMs = backoff.type === 'exponential'
401
- ? Math.min(backoff.delayMs * 2 ** row.retry_count, 3_600_000)
402
- : backoff.delayMs;
448
+ let delayMs;
449
+ switch (backoff.type) {
450
+ case 'exponential':
451
+ delayMs = Math.min(backoff.delayMs * 2 ** row.retry_count, 3_600_000);
452
+ break;
453
+ case 'jitter':
454
+ delayMs = Math.min(backoff.delayMs * 2 ** row.retry_count * (0.5 + Math.random()), 3_600_000);
455
+ break;
456
+ case 'fibonacci':
457
+ delayMs = Math.min(backoff.delayMs * this.fib(row.retry_count), 3_600_000);
458
+ break;
459
+ default: // 'fixed'
460
+ delayMs = backoff.delayMs;
461
+ }
403
462
  retryRunAt = new Date(Date.now() + delayMs).toISOString();
404
463
  }
405
464
  this.markFailedStmt.run({
@@ -408,8 +467,23 @@ export class JobQueue {
408
467
  $runAt: retryRunAt,
409
468
  $now: now
410
469
  });
470
+ // capture updated job for post-tx emit
471
+ const updatedRow = this.selectJobStmt.get({ $id: id });
472
+ if (updatedRow)
473
+ failedJob = toJob(updatedRow);
411
474
  }
412
475
  })();
476
+ if (failedJob)
477
+ this.emit('job:failed', failedJob, error);
478
+ }
479
+ fib(n) {
480
+ if (n <= 1)
481
+ return 1;
482
+ let a = 1, b = 1;
483
+ for (let i = 2; i <= n; i++) {
484
+ [a, b] = [b, a + b];
485
+ }
486
+ return b;
413
487
  }
414
488
  updateProgress(id, progress) {
415
489
  this.updateProgressStmt.run({
@@ -417,6 +491,9 @@ export class JobQueue {
417
491
  $progress: progress,
418
492
  $now: nowISO()
419
493
  });
494
+ const row = this.selectJobStmt.get({ $id: id });
495
+ if (row)
496
+ this.emit('job:progress', toJob(row), progress);
420
497
  }
421
498
  // --- Batch API ---
422
499
  createBatch(name, options) {
@@ -455,6 +532,28 @@ export class JobQueue {
455
532
  const runAt = options?.runAt ? options.runAt.toISOString() : now;
456
533
  const hasDeps = options?.dependsOn && options.dependsOn.length > 0;
457
534
  const status = hasDeps ? 'blocked' : 'pending';
535
+ const expireAt = options?.expireAt ? options.expireAt.toISOString() : null;
536
+ const webhookConfig = options?.onComplete
537
+ ? JSON.stringify(options.onComplete)
538
+ : null;
539
+ // dedup='replace': update existing pending job's data + run_at
540
+ if (options?.dedup === 'replace' && options.uniqueKey) {
541
+ const existing = this.selectDedupedJobStmt.get({
542
+ $type: type,
543
+ $uniqueKey: options.uniqueKey
544
+ });
545
+ if (existing) {
546
+ this.db
547
+ .query('UPDATE jobs SET data = $data, run_at = $runAt, updated_at = $now WHERE id = $id')
548
+ .run({
549
+ $id: existing.id,
550
+ $data: JSON.stringify(data),
551
+ $runAt: runAt,
552
+ $now: now
553
+ });
554
+ return existing.id;
555
+ }
556
+ }
458
557
  const result = this.insertJobStmt.run({
459
558
  $type: type,
460
559
  $data: JSON.stringify(data),
@@ -464,7 +563,9 @@ export class JobQueue {
464
563
  $runAt: runAt,
465
564
  $batchId: batchId,
466
565
  $uniqueKey: options?.uniqueKey ?? null,
467
- $backoffConfig: options?.backoff ? JSON.stringify(options.backoff) : null
566
+ $backoffConfig: options?.backoff ? JSON.stringify(options.backoff) : null,
567
+ $expireAt: expireAt,
568
+ $webhookConfig: webhookConfig
468
569
  });
469
570
  // INSERT OR IGNORE: if a pending/processing job with same (type, uniqueKey)
470
571
  // already exists, the insert is a no-op. Return the existing job id.
@@ -505,7 +606,10 @@ export class JobQueue {
505
606
  updated_at = $now
506
607
  WHERE status = 'processing' AND updated_at < $cutoff`)
507
608
  .run({ $now: nowISO(), $cutoff: cutoff });
508
- return result.changes;
609
+ const count = result.changes;
610
+ if (count > 0)
611
+ this.emit('job:stale', count);
612
+ return count;
509
613
  }
510
614
  /**
511
615
  * Look up a pending or processing job by its uniqueKey.
@@ -520,6 +624,53 @@ export class JobQueue {
520
624
  .get({ $type: type, $uniqueKey: uniqueKey });
521
625
  return row ? toJob(row) : null;
522
626
  }
627
+ getJobResult(id) {
628
+ const row = this.selectJobStmt.get({ $id: id });
629
+ if (!row?.result)
630
+ return null;
631
+ return JSON.parse(row.result);
632
+ }
633
+ cancelByUniqueKey(type, uniqueKey) {
634
+ const result = this.db
635
+ .query("UPDATE jobs SET status = 'cancelled', updated_at = $now WHERE type = $type AND unique_key = $uniqueKey AND status IN ('pending', 'blocked')")
636
+ .run({ $type: type, $uniqueKey: uniqueKey, $now: nowISO() });
637
+ return result.changes > 0;
638
+ }
639
+ retryFailedJobsByType(type) {
640
+ const rows = this.db
641
+ .query('SELECT * FROM failed_jobs WHERE type = $type')
642
+ .all({ $type: type });
643
+ if (rows.length === 0)
644
+ return 0;
645
+ const now = nowISO();
646
+ this.db.transaction(() => {
647
+ for (const row of rows) {
648
+ this.insertJobStmt.run({
649
+ $type: row.type,
650
+ $data: row.data,
651
+ $status: 'pending',
652
+ $priority: 0,
653
+ $maxRetries: row.max_retries,
654
+ $runAt: now,
655
+ $batchId: null,
656
+ $uniqueKey: null,
657
+ $backoffConfig: null,
658
+ $expireAt: null,
659
+ $webhookConfig: null
660
+ });
661
+ this.db
662
+ .query('DELETE FROM failed_jobs WHERE id = $id')
663
+ .run({ $id: row.id });
664
+ }
665
+ })();
666
+ return rows.length;
667
+ }
668
+ purgeExpiredJobs() {
669
+ const result = this.db
670
+ .query("DELETE FROM jobs WHERE expire_at IS NOT NULL AND expire_at <= $now AND status = 'pending'")
671
+ .run({ $now: nowISO() });
672
+ return result.changes;
673
+ }
523
674
  close() {
524
675
  try {
525
676
  if (this.db.filename !== ':memory:' && this.db.filename !== '') {
@@ -558,35 +709,51 @@ export class JobQueue {
558
709
  const options = batch.options
559
710
  ? JSON.parse(batch.options)
560
711
  : null;
561
- if (!options)
562
- return;
563
- // Enqueue "then" callback job only if zero failures
564
- if (batch.failed_jobs === 0 && options.thenType) {
565
- this.insertJobStmt.run({
566
- $type: options.thenType,
567
- $data: JSON.stringify(options.thenData ?? {}),
568
- $status: 'pending',
569
- $priority: 0,
570
- $maxRetries: 3,
571
- $runAt: now,
572
- $batchId: null,
573
- $uniqueKey: null,
574
- $backoffConfig: null
575
- });
712
+ if (options) {
713
+ // Enqueue "then" callback job only if zero failures
714
+ if (batch.failed_jobs === 0 && options.thenType) {
715
+ this.insertJobStmt.run({
716
+ $type: options.thenType,
717
+ $data: JSON.stringify(options.thenData ?? {}),
718
+ $status: 'pending',
719
+ $priority: 0,
720
+ $maxRetries: 3,
721
+ $runAt: now,
722
+ $batchId: null,
723
+ $uniqueKey: null,
724
+ $backoffConfig: null,
725
+ $expireAt: null,
726
+ $webhookConfig: null
727
+ });
728
+ }
729
+ // Enqueue "finally" callback job regardless of failures
730
+ if (options.finallyType) {
731
+ this.insertJobStmt.run({
732
+ $type: options.finallyType,
733
+ $data: JSON.stringify(options.finallyData ?? {}),
734
+ $status: 'pending',
735
+ $priority: 0,
736
+ $maxRetries: 3,
737
+ $runAt: now,
738
+ $batchId: null,
739
+ $uniqueKey: null,
740
+ $backoffConfig: null,
741
+ $expireAt: null,
742
+ $webhookConfig: null
743
+ });
744
+ }
576
745
  }
577
- // Enqueue "finally" callback job regardless of failures
578
- if (options.finallyType) {
579
- this.insertJobStmt.run({
580
- $type: options.finallyType,
581
- $data: JSON.stringify(options.finallyData ?? {}),
582
- $status: 'pending',
583
- $priority: 0,
584
- $maxRetries: 3,
585
- $runAt: now,
586
- $batchId: null,
587
- $uniqueKey: null,
588
- $backoffConfig: null
589
- });
746
+ const finishedBatch = this.selectBatchStmt.get({
747
+ $id: batchId
748
+ });
749
+ if (finishedBatch) {
750
+ const b = toBatch(finishedBatch);
751
+ if (b.failedJobs === 0) {
752
+ this.emit('batch:complete', b);
753
+ }
754
+ else {
755
+ this.emit('batch:failed', b);
756
+ }
590
757
  }
591
758
  }
592
759
  }
@@ -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;AAoG3C,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAY/C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAsBnD"}
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"}
package/dist/schema.js CHANGED
@@ -18,7 +18,11 @@ CREATE TABLE IF NOT EXISTS jobs (
18
18
  response_log TEXT,
19
19
  batch_id TEXT REFERENCES job_batches(id),
20
20
  unique_key TEXT,
21
- backoff_config TEXT
21
+ backoff_config TEXT,
22
+ claimed_until TEXT,
23
+ result TEXT,
24
+ expire_at TEXT,
25
+ webhook_config TEXT
22
26
  )`;
23
27
  // State-aware dedup: same (type, unique_key) cannot be both pending/processing at once.
24
28
  // Once the job completes, the same key can be re-enqueued.
@@ -116,5 +120,17 @@ export function initializeSchema(db) {
116
120
  if (!cols.some(c => c.name === 'backoff_config')) {
117
121
  db.run('ALTER TABLE jobs ADD COLUMN backoff_config TEXT');
118
122
  }
123
+ if (!cols.some(c => c.name === 'claimed_until')) {
124
+ db.run('ALTER TABLE jobs ADD COLUMN claimed_until TEXT');
125
+ }
126
+ if (!cols.some(c => c.name === 'result')) {
127
+ db.run('ALTER TABLE jobs ADD COLUMN result TEXT');
128
+ }
129
+ if (!cols.some(c => c.name === 'expire_at')) {
130
+ db.run('ALTER TABLE jobs ADD COLUMN expire_at TEXT');
131
+ }
132
+ if (!cols.some(c => c.name === 'webhook_config')) {
133
+ db.run('ALTER TABLE jobs ADD COLUMN webhook_config TEXT');
134
+ }
119
135
  db.run(JOBS_UNIQUE_KEY_INDEX);
120
136
  }
package/dist/types.d.ts CHANGED
@@ -27,6 +27,10 @@ export type Job<T = unknown> = {
27
27
  readonly batchId: string | null;
28
28
  readonly requestLog: string | null;
29
29
  readonly responseLog: string | null;
30
+ readonly uniqueKey: string | null;
31
+ readonly claimedUntil: string | null;
32
+ readonly result: unknown | null;
33
+ readonly expireAt: string | null;
30
34
  };
31
35
  export type FailedJob = {
32
36
  readonly id: number;
@@ -42,7 +46,7 @@ export type FailedJob = {
42
46
  readonly responseLog: string | null;
43
47
  };
44
48
  export type BackoffConfig = {
45
- type: 'exponential' | 'fixed';
49
+ type: 'exponential' | 'fixed' | 'jitter' | 'fibonacci';
46
50
  /** Base delay in ms. Exponential: delayMs * 2^retryCount. Fixed: always delayMs. Max 1h. */
47
51
  delayMs: number;
48
52
  };
@@ -62,10 +66,18 @@ export type AddJobOptions = {
62
66
  * Fixed: always delayMs between retries. Default: retry immediately.
63
67
  */
64
68
  backoff?: BackoffConfig;
69
+ dedup?: 'ignore' | 'replace';
70
+ expireAt?: Date;
71
+ onComplete?: {
72
+ url: string;
73
+ method?: string;
74
+ headers?: Record<string, string>;
75
+ };
65
76
  };
66
77
  export type JobContext = {
67
78
  reportProgress: (percent: number) => void;
68
79
  signal: AbortSignal;
80
+ renewLease(): void;
69
81
  };
70
82
  export type RateLimit = {
71
83
  count: number;
@@ -73,7 +85,7 @@ export type RateLimit = {
73
85
  };
74
86
  export type WorkerOptions<T = unknown> = {
75
87
  type: string;
76
- handler: (job: Job<T>, ctx: JobContext) => Promise<void>;
88
+ handler: (job: Job<T>, ctx: JobContext) => Promise<unknown>;
77
89
  pollIntervalMs?: number;
78
90
  maxRate?: RateLimit;
79
91
  onError?: (job: Job<T>, error: unknown) => void;
@@ -81,6 +93,12 @@ export type WorkerOptions<T = unknown> = {
81
93
  timeoutMs?: number;
82
94
  /** Max concurrent jobs this worker runs simultaneously. Default: 1. */
83
95
  concurrency?: number;
96
+ leaseMs?: number;
97
+ retryIf?: (error: unknown, job: Job<T>) => boolean;
98
+ aging?: {
99
+ boostPerMinute: number;
100
+ maxBoost: number;
101
+ };
84
102
  };
85
103
  export type JobStats = {
86
104
  pending: number;
@@ -126,6 +144,10 @@ export type JobRow = {
126
144
  response_log: string | null;
127
145
  unique_key: string | null;
128
146
  backoff_config: string | null;
147
+ claimed_until: string | null;
148
+ result: string | null;
149
+ expire_at: string | null;
150
+ webhook_config: string | null;
129
151
  };
130
152
  export type FailedJobRow = {
131
153
  id: number;
@@ -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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"}
package/dist/worker.d.ts CHANGED
@@ -7,14 +7,18 @@ export declare class JobWorker<TMap extends JobMap = Record<string, unknown>, K
7
7
  private abortController;
8
8
  private timer;
9
9
  private running;
10
+ private paused;
10
11
  private activeCount;
11
12
  private stopResolve;
12
13
  constructor(queue: JobQueue<TMap>, options: WorkerOptions<TMap[K]> & {
13
14
  type: K;
14
15
  });
15
16
  get isRunning(): boolean;
17
+ get isPaused(): boolean;
16
18
  start(): void;
17
19
  stop(): Promise<void>;
20
+ pause(): void;
21
+ resume(): void;
18
22
  private scheduleNext;
19
23
  private poll;
20
24
  private runJob;
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,KAAK,EAAmB,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGtE,qBAAa,SAAS,CACpB,IAAI,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7C,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,IAAI;IAEnD,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAA6B;gBAG9C,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE;IAY/C,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,OAAO,CAAC,YAAY;YAMN,IAAI;YAoBJ,MAAM;CAiDrB"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,KAAK,EAAmB,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGtE,qBAAa,SAAS,CACpB,IAAI,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7C,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,IAAI;IAEnD,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAA6B;gBAG9C,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE;IAY/C,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,IAAI;IAYd,OAAO,CAAC,YAAY;YAMN,IAAI;YAyBJ,MAAM;CA0DrB"}
package/dist/worker.js CHANGED
@@ -7,6 +7,7 @@ export class JobWorker {
7
7
  abortController = null;
8
8
  timer = null;
9
9
  running = false;
10
+ paused = false;
10
11
  activeCount = 0;
11
12
  stopResolve = null;
12
13
  constructor(queue, options) {
@@ -19,6 +20,9 @@ export class JobWorker {
19
20
  get isRunning() {
20
21
  return this.running;
21
22
  }
23
+ get isPaused() {
24
+ return this.paused;
25
+ }
22
26
  start() {
23
27
  if (this.running)
24
28
  return;
@@ -41,6 +45,20 @@ export class JobWorker {
41
45
  });
42
46
  }
43
47
  }
48
+ pause() {
49
+ this.paused = true;
50
+ }
51
+ resume() {
52
+ this.paused = false;
53
+ if (this.running) {
54
+ // Cancel existing timer and poll immediately
55
+ if (this.timer) {
56
+ clearTimeout(this.timer);
57
+ this.timer = null;
58
+ }
59
+ void this.poll();
60
+ }
61
+ }
44
62
  scheduleNext() {
45
63
  if (!this.running)
46
64
  return;
@@ -50,12 +68,17 @@ export class JobWorker {
50
68
  async poll() {
51
69
  if (!this.running)
52
70
  return;
71
+ if (this.paused) {
72
+ this.scheduleNext();
73
+ return;
74
+ }
53
75
  const concurrency = this.options.concurrency ?? 1;
76
+ const leaseMs = this.options.leaseMs ?? 300_000;
54
77
  // Drain available jobs up to available capacity in one poll tick
55
78
  while (this.activeCount < concurrency) {
56
79
  if (this.rateLimiter && !this.rateLimiter.canProceed())
57
80
  break;
58
- const job = this.queue.pollAndClaim(this.options.type);
81
+ const job = this.queue.pollAndClaim(this.options.type, leaseMs);
59
82
  if (!job)
60
83
  break;
61
84
  this.rateLimiter?.record();
@@ -70,20 +93,24 @@ export class JobWorker {
70
93
  reportProgress: (percent) => {
71
94
  this.queue.updateProgress(job.id, percent);
72
95
  },
96
+ renewLease: () => {
97
+ this.queue.renewLease(job.id, this.options.leaseMs ?? 300_000);
98
+ },
73
99
  signal: this.abortController.signal
74
100
  };
75
101
  const handlerPromise = this.options.handler(job, ctx);
102
+ let handlerResult;
76
103
  if (this.options.timeoutMs) {
77
104
  const timeoutMs = this.options.timeoutMs;
78
- await Promise.race([
105
+ handlerResult = await Promise.race([
79
106
  handlerPromise,
80
107
  new Promise((_, reject) => setTimeout(() => reject(new Error(`Job timed out after ${timeoutMs}ms`)), timeoutMs))
81
108
  ]);
82
109
  }
83
110
  else {
84
- await handlerPromise;
111
+ handlerResult = await handlerPromise;
85
112
  }
86
- this.queue.markJobDone(job.id);
113
+ this.queue.markJobDone(job.id, handlerResult);
87
114
  }
88
115
  catch (error) {
89
116
  const message = error instanceof Error ? error.message : String(error);
@@ -93,7 +120,10 @@ export class JobWorker {
93
120
  (typeof error === 'object' &&
94
121
  error !== null &&
95
122
  error.isNonRetryable === true);
96
- if (isNonRetryable) {
123
+ const shouldRetry = this.options.retryIf
124
+ ? this.options.retryIf(error, job)
125
+ : true;
126
+ if (isNonRetryable || !shouldRetry) {
97
127
  this.queue.markJobDead(job.id, message);
98
128
  }
99
129
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitclaw/jobs",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "SQLite-backed background job queue using bun:sqlite",
5
5
  "files": [
6
6
  "dist",