bunqueue 2.8.37 โ†’ 2.8.38

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.
Files changed (2) hide show
  1. package/README.md +168 -756
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -19,24 +19,13 @@
19
19
 
20
20
  <p align="center">
21
21
  <a href="https://bunqueue.dev/">Documentation</a> &middot;
22
+ <a href="https://bunqueue.dev/guide/quickstart/">Quick Start</a> &middot;
22
23
  <a href="https://bunqueue.dev/guide/benchmarks/">Benchmarks</a> &middot;
23
24
  <a href="https://www.npmjs.com/package/bunqueue">npm</a>
24
25
  </p>
25
26
 
26
27
  ---
27
28
 
28
- ## Requirements
29
-
30
- The bunqueue **server** (and the embedded mode) is **Bun-only** (`bun >= 1.3.9`):
31
- persistence and transport rely on Bun's runtime APIs. Install Bun from
32
- [bun.sh](https://bun.sh) and run with `bun`. Importing the `bunqueue` package from
33
- Node fails fast with a clear error rather than a cryptic resolver crash.
34
-
35
- **Your producers and workers don't have to run on Bun.** Official client SDKs
36
- connect to the server from **Node.js, Deno, Cloudflare Workers**
37
- ([`bunqueue-client`](https://www.npmjs.com/package/bunqueue-client) on npm) and
38
- **Python** (`bunqueue-client`, PyPI coming soon) โ€” see [One Queue, Any Language](#one-queue-any-language-sdks).
39
-
40
29
  ## Quickstart
41
30
 
42
31
  ```bash
@@ -48,6 +37,7 @@ import { Bunqueue } from 'bunqueue/client';
48
37
 
49
38
  const app = new Bunqueue('emails', {
50
39
  embedded: true,
40
+ dataPath: './data/emails.db', // omit to run in-memory (lost on restart)
51
41
  processor: async (job) => {
52
42
  console.log(`Sending to ${job.data.to}`);
53
43
  return { sent: true };
@@ -57,458 +47,190 @@ const app = new Bunqueue('emails', {
57
47
  await app.add('send', { to: 'alice@example.com' });
58
48
  ```
59
49
 
60
- That's it. Queue + Worker in one object. No Redis, no config, no setup.
61
-
62
- ---
63
-
64
- ## ๐Ÿชถ Featherweight install
50
+ That's it. Queue + Worker in one object, persisted to a single SQLite file.
51
+ No Redis, no config, no setup. The install is 5.5 MB, 7 packages, 2 runtime
52
+ dependencies (`croner` + `msgpackr`) โ€” SQLite, S3, HTTP and WebSocket are Bun
53
+ built-ins.
65
54
 
66
- `bun add bunqueue` pulls **7 packages and 5.5 MB** โ€” and that includes the binary
67
- queue server, the CLI, and the MCP server. Most queue libraries pull a Redis
68
- client and its dependency tree before you've queued a single job.
69
-
70
- | | Before (2.7.x) | Now | |
71
- | --- | --- | --- | --- |
72
- | **`node_modules`** | 93 MB | **5.5 MB** | **โˆ’94%** |
73
- | **packages** | 117 | **7** | **โˆ’110** |
74
- | **cold install** | ~6 s | **~1.7 s** | **~3.5ร— faster** |
75
-
76
- Just **2 runtime dependencies** (`croner` + `msgpackr`). SQLite, S3, HTTP and
77
- WebSocket are all Bun built-ins. The MCP SDK is an optional peer dependency โ€”
78
- queue users never download it. ([details โ†’](https://bunqueue.dev/changelog/))
79
-
80
- ---
81
-
82
- ## Simple Mode
55
+ ### Not on Bun? Run the server, connect from anywhere
83
56
 
84
- Simple Mode gives you a Queue and a Worker in a single object. Add jobs, process them, add middleware, schedule crons โ€” all from one place.
57
+ The queue also runs as a standalone server โ€” one command, nothing else to
58
+ operate:
85
59
 
86
- Use `Bunqueue` when producer and consumer are in the same process. For distributed systems, use `Queue` + `Worker` separately. For AI agent workflows, use the [MCP Server](#built-for-ai-agents-mcp-server) instead โ€” agents control queues via natural language without writing code.
87
-
88
- <details>
89
- <summary><b>Architecture</b></summary>
90
-
91
- ```
92
- new Bunqueue('emails', opts)
93
- โ”‚
94
- โ”œโ”€โ”€ this.queue = new Queue('emails', ...)
95
- โ”œโ”€โ”€ this.worker = new Worker('emails', ...)
96
- โ”‚
97
- โ””โ”€โ”€ Subsystems (all optional):
98
- โ”œโ”€โ”€ RetryEngine โ”€โ”€ jitter, fibonacci, exponential, custom
99
- โ”œโ”€โ”€ CircuitBreaker โ”€โ”€ pauses worker after N failures
100
- โ”œโ”€โ”€ BatchAccumulator โ”€โ”€ groups N jobs into one call
101
- โ”œโ”€โ”€ TriggerManager โ”€โ”€ "on complete โ†’ create job B"
102
- โ”œโ”€โ”€ TtlChecker โ”€โ”€ rejects expired jobs
103
- โ”œโ”€โ”€ PriorityAger โ”€โ”€ boosts old jobs' priority
104
- โ”œโ”€โ”€ CancellationManager โ”€โ”€ AbortController per job
105
- โ””โ”€โ”€ DedupDebounceMerger โ”€โ”€ deduplication & debounce
106
- ```
107
-
108
- Processing pipeline per job:
60
+ ```bash
61
+ # in-memory without --data-path; pass it to persist jobs to SQLite
62
+ bunx bunqueue start --data-path ./data/bunq.db # TCP :6789, HTTP :6790
109
63
 
64
+ # or, with no runtime at all (the volume persists /app/data):
65
+ docker run -d -p 6789:6789 -p 6790:6790 \
66
+ -v bunqueue-data:/app/data \
67
+ ghcr.io/egeominotti/bunqueue:latest
110
68
  ```
111
- Job โ†’ Circuit Breaker โ†’ TTL check โ†’ AbortController โ†’ Retry โ†’ Middleware โ†’ Processor
112
- ```
113
-
114
- </details>
115
69
 
116
- ### Routes
70
+ Then produce and process from the language you already use:
117
71
 
118
- Route jobs to different handlers by name:
119
-
120
- ```typescript
121
- const app = new Bunqueue('notifications', {
122
- embedded: true,
123
- routes: {
124
- 'send-email': async (job) => {
125
- await sendEmail(job.data.to);
126
- return { channel: 'email' };
127
- },
128
- 'send-sms': async (job) => {
129
- await sendSMS(job.data.to);
130
- return { channel: 'sms' };
131
- },
132
- },
133
- });
134
-
135
- await app.add('send-email', { to: 'alice' });
136
- await app.add('send-sms', { to: 'bob' });
72
+ ```bash
73
+ npm install bunqueue-client # Node.js โ‰ฅ 20, Deno โ‰ฅ 2, Bun, Cloudflare Workers
137
74
  ```
138
75
 
139
- > **Note:** Use one of `processor`, `routes`, or `batch`. Passing multiple or none throws an error.
140
-
141
- ### Middleware
142
-
143
- Wraps every job execution. Execution order is onion-style: `mw1 โ†’ mw2 โ†’ processor โ†’ mw2 โ†’ mw1`. When no middleware is added, zero overhead.
144
-
145
76
  ```typescript
146
- // Timing middleware
147
- app.use(async (job, next) => {
148
- const start = Date.now();
149
- const result = await next();
150
- console.log(`${job.name}: ${Date.now() - start}ms`);
151
- return result;
152
- });
153
-
154
- // Error recovery middleware
155
- app.use(async (job, next) => {
156
- try {
157
- return await next();
158
- } catch (err) {
159
- return { recovered: true, error: err.message };
160
- }
161
- });
162
- ```
163
-
164
- ### Batch Processing
77
+ import { Queue, Worker } from 'bunqueue-client';
165
78
 
166
- Accumulates N jobs and processes them together. Flushes when buffer reaches `size` or `timeout` expires. On `close()`, remaining jobs are flushed.
79
+ const queue = new Queue('emails'); // localhost:6789 by default
80
+ await queue.add('welcome', { to: 'user@example.com' });
167
81
 
168
- ```typescript
169
- const app = new Bunqueue('db-inserts', {
170
- embedded: true,
171
- batch: {
172
- size: 50,
173
- timeout: 2000,
174
- processor: async (jobs) => {
175
- const rows = jobs.map(j => j.data.row);
176
- await db.insertMany('table', rows);
177
- return jobs.map(() => ({ inserted: true }));
178
- },
179
- },
180
- });
82
+ new Worker('emails', async (job) => ({ sent: true }), { concurrency: 10 });
181
83
  ```
182
84
 
183
- ### Advanced Retry
85
+ Python, PHP, Go, Rust and Elixir clients speak the same protocol โ€” see
86
+ [One Queue, Any Language](#one-queue-any-language-sdks).
184
87
 
185
- 5 strategies + retry predicate:
88
+ > Only the server and embedded mode are **Bun-only** (`bun >= 1.3.9`,
89
+ > [bun.sh](https://bun.sh)); producers and workers can run anywhere.
186
90
 
187
- ```typescript
188
- const app = new Bunqueue('api-calls', {
189
- embedded: true,
190
- processor: async (job) => {
191
- const res = await fetch(job.data.url);
192
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
193
- return { status: res.status };
194
- },
195
- retry: {
196
- maxAttempts: 5,
197
- delay: 1000,
198
- strategy: 'jitter',
199
- retryIf: (error) => error.message.includes('503'),
200
- },
201
- });
202
- ```
91
+ [Quick Start guide โ†’](https://bunqueue.dev/guide/quickstart/)
203
92
 
204
- | Strategy | Formula | Use case |
205
- | --- | --- | --- |
206
- | `fixed` | `delay` every time | Rate-limited APIs |
207
- | `exponential` | `delay ร— 2^attempt` | General purpose |
208
- | `jitter` | `delay ร— 2^attempt ร— random(0.5-1.0)` | Thundering herd prevention |
209
- | `fibonacci` | `delay ร— fib(attempt)` | Gradual backoff |
210
- | `custom` | `customBackoff(attempt, error) โ†’ ms` | Anything |
93
+ ## Why bunqueue?
211
94
 
212
- > This is in-process retry โ€” the job stays active. Different from core `attempts`/`backoff` which re-queues.
95
+ | Library | Requires | AI-native |
96
+ | ------------ | ----------- | --------- |
97
+ | BullMQ | Redis | No |
98
+ | Agenda | MongoDB | No |
99
+ | pg-boss | PostgreSQL | No |
100
+ | **bunqueue** | **Nothing** | **Yes** |
213
101
 
214
- ### Graceful Cancellation
102
+ - **Zero external infrastructure** โ€” one process, one SQLite file. `cp` to back up
103
+ - **BullMQ-compatible API** โ€” same `Queue`, `Worker`, `QueueEvents`; [migrating takes minutes](https://bunqueue.dev/guide/migration/)
104
+ - **MCP server included** โ€” 73 tools; AI agents get full queue control out of the box
105
+ - **Everything server-side** โ€” retries with backoff, priorities, cron, rate limits, dead letter queue
106
+ - **Up to 630K ops/sec** โ€” [verified benchmarks](https://bunqueue.dev/guide/benchmarks/) with methodology
215
107
 
216
- Cancel running jobs with AbortController:
108
+ **Great for:** single-server deployments, AI agents that need a scheduler,
109
+ prototypes and MVPs, embedded use cases (CLI tools, edge, serverless), teams
110
+ that don't want to operate Redis.
217
111
 
218
- ```typescript
219
- const app = new Bunqueue('encoding', {
220
- embedded: true,
221
- processor: async (job) => {
222
- const signal = app.getSignal(job.id);
223
- for (const chunk of chunks) {
224
- if (signal?.aborted) throw new Error('Cancelled');
225
- await encode(chunk);
226
- }
227
- return { done: true };
228
- },
229
- });
112
+ **Not ideal for:** multi-region distributed systems requiring HA or automatic
113
+ failover today. If you already run Redis and BullMQ works for you, keep it.
230
114
 
231
- const job = await app.add('video', { file: 'big.mp4' });
232
- app.cancel(job.id); // cancel immediately
233
- app.cancel(job.id, 5000); // cancel after 5s grace period
234
- ```
115
+ [When to choose bunqueue โ†’](https://bunqueue.dev/guide/comparison/)
235
116
 
236
- The signal works with `fetch` too: `await fetch(url, { signal })`.
117
+ ## Two Modes
237
118
 
238
- ### Circuit Breaker
119
+ | | Embedded | Server (TCP) |
120
+ | ---------------- | ------------------------------------- | -------------------------------------------- |
121
+ | **How it works** | Queue runs inside your process | Standalone server, clients connect via TCP |
122
+ | **Setup** | `bun add bunqueue` | `docker run` or `bunqueue start` |
123
+ | **Performance** | 630K ops/sec (bulk push) | 90K ops/sec (push) |
124
+ | **Best for** | Single-process apps, CLIs, serverless | Multiple workers, separate producer/consumer |
125
+ | **Scaling** | Same process only | Multiple clients across machines |
239
126
 
240
- Pauses the worker after too many consecutive failures:
127
+ ### Embedded
241
128
 
242
- ```
243
- CLOSED โ”€โ”€โ†’ failures โ‰ฅ threshold โ”€โ”€โ†’ OPEN (worker paused)
244
- โ”‚
245
- โ†โ”€โ”€ success โ”€โ”€โ”€โ”€ HALF-OPEN โ†โ”€โ”€ timeout expires
246
- ```
129
+ Everything in your process. Without a data path the queue is in-memory: pass
130
+ `dataPath` (or set `BUNQUEUE_DATA_PATH`) to persist jobs.
247
131
 
248
132
  ```typescript
249
- const app = new Bunqueue('payments', {
250
- embedded: true,
251
- processor: async (job) => paymentGateway.charge(job.data),
252
- circuitBreaker: {
253
- threshold: 5,
254
- resetTimeout: 30000,
255
- onOpen: () => alert('Gateway down!'),
256
- onClose: () => alert('Gateway recovered'),
257
- },
258
- });
259
-
260
- app.getCircuitState(); // 'closed' | 'open' | 'half-open'
261
- app.resetCircuit(); // force close + resume worker
262
- ```
263
-
264
- ### Event Triggers
133
+ import { Queue, Worker } from 'bunqueue/client';
265
134
 
266
- Create follow-up jobs automatically when a job completes or fails:
135
+ const queue = new Queue('emails', { embedded: true, dataPath: './data/app.db' });
267
136
 
268
- ```typescript
269
- const app = new Bunqueue('orders', {
270
- embedded: true,
271
- routes: {
272
- 'place-order': async (job) => ({ orderId: job.data.id, total: 99 }),
273
- 'send-receipt': async (job) => ({ sent: true }),
274
- 'fraud-alert': async (job) => ({ alerted: true }),
137
+ const worker = new Worker(
138
+ 'emails',
139
+ async (job) => {
140
+ return { sent: true };
275
141
  },
276
- });
277
-
278
- app.trigger({
279
- on: 'place-order',
280
- create: 'send-receipt',
281
- data: (result, job) => ({ id: job.data.id }),
282
- });
283
-
284
- // Conditional trigger (only for large orders)
285
- app.trigger({
286
- on: 'place-order',
287
- create: 'fraud-alert',
288
- data: (result) => ({ amount: result.total }),
289
- condition: (result) => result.total > 1000,
290
- });
142
+ { embedded: true }
143
+ );
291
144
 
292
- // Chain triggers
293
- app
294
- .trigger({ on: 'step-1', create: 'step-2', data: (r) => r })
295
- .trigger({ on: 'step-2', create: 'step-3', data: (r) => r });
145
+ await queue.add('welcome', { to: 'user@example.com' });
296
146
  ```
297
147
 
298
- ### Job TTL
299
-
300
- Expire unprocessed jobs. Checked when the worker picks up the job:
301
-
302
- ```typescript
303
- const app = new Bunqueue('otp', {
304
- embedded: true,
305
- processor: async (job) => verifyOTP(job.data.code),
306
- ttl: {
307
- defaultTtl: 300000,
308
- perName: {
309
- 'verify-otp': 60000,
310
- 'daily-report': 0,
311
- },
312
- },
313
- });
148
+ ### Server (TCP)
314
149
 
315
- app.setDefaultTtl(120000);
316
- app.setNameTtl('flash-sale', 30000);
150
+ ```bash
151
+ docker run -d -p 6789:6789 -p 6790:6790 \
152
+ -v bunqueue-data:/app/data \
153
+ ghcr.io/egeominotti/bunqueue:latest
317
154
  ```
318
155
 
319
- Resolution: `perName[job.name]` โ†’ `defaultTtl` โ†’ `0` (no TTL).
320
-
321
- ### Priority Aging
322
-
323
- Automatically boosts priority of old waiting jobs to prevent starvation:
324
-
325
156
  ```typescript
326
- const app = new Bunqueue('tasks', {
327
- embedded: true,
328
- processor: async (job) => ({ done: true }),
329
- priorityAging: {
330
- interval: 60000,
331
- minAge: 300000,
332
- boost: 2,
333
- maxPriority: 100,
334
- maxScan: 200,
335
- },
336
- });
337
- ```
338
-
339
- A job with priority 1 becomes eligible after 5 min, then gains +2 on every 60s tick: 3, 5, 7, โ€ฆ capped at 100.
340
-
341
- ### Deduplication
157
+ import { Queue, Worker } from 'bunqueue/client';
342
158
 
343
- Prevent duplicate jobs. Jobs with the same name + data get the same dedup ID:
159
+ const queue = new Queue('tasks', { connection: { host: 'localhost', port: 6789 } });
344
160
 
345
- ```typescript
346
- const app = new Bunqueue('webhooks', {
347
- embedded: true,
348
- processor: async (job) => processWebhook(job.data),
349
- deduplication: {
350
- ttl: 60000,
351
- extend: false,
352
- replace: false,
161
+ const worker = new Worker(
162
+ 'tasks',
163
+ async (job) => {
164
+ return { done: true };
353
165
  },
354
- });
355
-
356
- await app.add('hook', { event: 'user.created', userId: '123' });
357
- await app.add('hook', { event: 'user.created', userId: '123' }); // deduplicated!
358
- await app.add('hook', { event: 'user.updated', userId: '123' }); // different data โ†’ new job
359
- ```
360
-
361
- Override per-job: `await app.add('task', data, { deduplication: { id: 'my-id', ttl: 5000 } })`.
362
-
363
- ### Debouncing
364
-
365
- Attach a default debounce id to same-name jobs. Each job gets `debounce: { id: jobName, ttl }` unless the per-job options already set one:
366
-
367
- ```typescript
368
- const app = new Bunqueue('search', {
369
- embedded: true,
370
- processor: async (job) => executeSearch(job.data.query),
371
- debounce: { ttl: 500 },
372
- });
373
-
374
- await app.add('search', { query: 'h' });
375
- await app.add('search', { query: 'he' });
376
- await app.add('search', { query: 'hello' }); // all three share the debounce id 'search'
377
- ```
378
-
379
- The debounce id and TTL are stored on the job (BullMQ v5 compatible metadata, visible via `job.opts.debounce`), but they do not suppress duplicates by themselves in the current engine. To actually coalesce rapid duplicates, use [Deduplication](#deduplication) with `replace: true`: a duplicate arriving within the TTL window replaces the pending job, giving last-write-wins semantics.
380
-
381
- ### Rate Limiting
382
-
383
- ```typescript
384
- const app = new Bunqueue('api', {
385
- embedded: true,
386
- processor: async (job) => callExternalAPI(job.data),
387
- rateLimit: { max: 100, duration: 1000 },
388
- });
166
+ { connection: { host: 'localhost', port: 6789 } }
167
+ );
389
168
 
390
- // Per-group cap (e.g., per customer): with groupKey set, max limits
391
- // concurrently active jobs per group (duration is not applied in this mode)
392
- const app2 = new Bunqueue('api', {
393
- embedded: true,
394
- processor: async (job) => callAPI(job.data),
395
- rateLimit: { max: 10, duration: 1000, groupKey: 'customerId' },
396
- });
169
+ await queue.add('process', { data: 'hello' });
397
170
  ```
398
171
 
399
- ### DLQ (Dead Letter Queue)
172
+ [Running the server โ†’](https://bunqueue.dev/guide/server/) ยท
173
+ [Deployment guide โ†’](https://bunqueue.dev/guide/deployment/)
400
174
 
401
- ```typescript
402
- const app = new Bunqueue('critical', {
403
- embedded: true,
404
- processor: async (job) => riskyOperation(job.data),
405
- dlq: {
406
- autoRetry: true,
407
- autoRetryInterval: 3600000,
408
- maxAutoRetries: 3,
409
- maxAge: 604800000,
410
- maxEntries: 10000,
411
- },
412
- });
413
-
414
- app.getDlq(); // all entries
415
- app.getDlqStats(); // { total, byReason, ... }
416
- app.getDlq({ reason: 'timeout' }); // filter by reason
417
- app.retryDlq(); // retry all
418
- app.purgeDlq(); // clear all
419
- ```
175
+ ## One Queue, Any Language (SDKs)
420
176
 
421
- Failure reasons: `explicit_fail`, `max_attempts_exceeded`, `timeout`, `stalled`, `ttl_expired`, `worker_lost`, `unknown`.
177
+ The server does all the heavy lifting. Official client SDKs speak the native
178
+ TCP protocol with full feature parity, so producers and workers can live
179
+ anywhere in your stack โ€” add a job from TypeScript, process it from Python:
422
180
 
423
- ### Cron Jobs
181
+ | Where your code runs | Install |
182
+ | -------------------- | ------- |
183
+ | **Node.js โ‰ฅ 20, Deno โ‰ฅ 2, Bun, Cloudflare Workers** | [`npm install bunqueue-client`](https://www.npmjs.com/package/bunqueue-client) |
184
+ | **Python โ‰ฅ 3.9** | PyPI coming soon โ€” today: `pip install "git+https://github.com/egeominotti/bunqueue.git#subdirectory=sdk/python"` |
185
+ | **PHP โ‰ฅ 8.1** | Packagist coming soon โ€” today: use [`sdk/php`](./sdk/php) |
186
+ | **Go โ‰ฅ 1.26.5** | `go get github.com/egeominotti/bunqueue/sdk/go` |
187
+ | **Rust โ‰ฅ 1.85** | crates.io coming soon โ€” today: use [`sdk/rust`](./sdk/rust) as a path dependency |
188
+ | **Elixir โ‰ฅ 1.15** | Hex coming soon โ€” today: use [`sdk/elixir`](./sdk/elixir) as a path dependency |
424
189
 
425
190
  ```typescript
426
- await app.cron('daily-report', '0 9 * * *', { type: 'report' });
427
- await app.cron('eu-digest', '0 8 * * 1', { type: 'weekly' }, { timezone: 'Europe/Rome' });
428
- await app.every('healthcheck', 30000, { type: 'ping' });
429
-
430
- await app.listCrons();
431
- await app.removeCron('healthcheck');
432
- ```
191
+ // Node.js / Deno / Cloudflare Workers
192
+ import { Queue, Worker } from 'bunqueue-client';
433
193
 
434
- ### Events
194
+ const queue = new Queue('emails', { host: 'localhost', port: 6789 });
195
+ await queue.add('welcome', { to: 'user@example.com' });
435
196
 
436
- ```typescript
437
- app.on('completed', (job, result) => { });
438
- app.on('failed', (job, error) => { });
439
- app.on('active', (job) => { });
440
- app.on('progress', (job, progress) => { });
441
- app.on('stalled', (jobId, reason) => { });
442
- app.on('error', (error) => { });
443
- app.on('ready', () => { });
444
- app.on('drained', () => { });
445
- app.on('closed', () => { });
197
+ new Worker('emails', async (job) => ({ sent: true }), { concurrency: 10 });
446
198
  ```
447
199
 
448
- ### Adding Jobs
449
-
450
- ```typescript
451
- await app.add('task', { key: 'value' });
452
- await app.add('urgent', data, { priority: 10, delay: 5000, attempts: 5, durable: true });
453
- await app.addBulk([
454
- { name: 'email', data: { to: 'alice' } },
455
- { name: 'email', data: { to: 'bob' }, opts: { priority: 10 } },
456
- ]);
457
- ```
200
+ ```python
201
+ # Python
202
+ from bunqueue import Queue, Worker
458
203
 
459
- ### Control
204
+ queue = Queue("emails", host="localhost", port=6789)
205
+ queue.add("welcome", {"to": "user@example.com"})
460
206
 
461
- ```typescript
462
- app.pause(); // pause queue + worker
463
- app.resume(); // resume both
464
- await app.close(); // graceful shutdown
465
- await app.close(true); // force shutdown
207
+ Worker("emails", lambda job: {"sent": True}, concurrency=10).run()
466
208
  ```
467
209
 
468
- ### Inspecting jobs & counts
210
+ Every SDK is certified against the same public
211
+ [wire protocol](https://github.com/egeominotti/bunqueue/blob/main/docs/protocol.md) and conformance suite.
469
212
 
470
- `getJobCounts()` and the per-state lists follow BullMQ semantics:
213
+ [SDK guide (all six languages) โ†’](https://bunqueue.dev/guide/sdks/)
471
214
 
472
- ```typescript
473
- const counts = await queue.getJobCountsAsync();
474
- // { waiting, prioritized, active, completed, failed, delayed,
475
- // 'waiting-children', paused }
476
-
477
- // Failed jobs (those that exhausted their attempts) are enumerable by state โ€”
478
- // the failed list reflects the same jobs that `failed` counts.
479
- const failed = await queue.getFailedAsync(0, 50);
480
- const alsoFailed = await queue.getJobsAsync({ state: 'failed', start: 0, end: 50 });
481
-
482
- // Paused queue: ready jobs are reported under `paused`, never double-counted as
483
- // `waiting`. While paused, getJobCounts() returns waiting:0 / paused:N, and the
484
- // jobs are listed by `getJobsAsync({ state: 'paused' })` (not by `waiting`).
485
- queue.pause();
486
- const c = await queue.getJobCountsAsync(); // waiting: 0, paused: N
487
- const pausedJobs = await queue.getJobsAsync({ state: 'paused', start: 0, end: 50 });
488
- ```
215
+ ## Simple Mode
489
216
 
490
- ### Full Example
217
+ `Bunqueue` bundles Queue + Worker + routes + middleware + cron in one object:
491
218
 
492
219
  ```typescript
493
- import { Bunqueue, shutdownManager } from 'bunqueue/client';
220
+ import { Bunqueue } from 'bunqueue/client';
494
221
 
495
- const app = new Bunqueue<{ payload: string }>('my-app', {
222
+ const app = new Bunqueue('notifications', {
496
223
  embedded: true,
497
224
  routes: {
498
- 'process': async (job) => ({ id: job.data.payload, status: 'done' }),
499
- 'notify': async (job) => ({ sent: true }),
500
- 'alert': async (job) => ({ alerted: true }),
225
+ 'send-email': async (job) => ({ sent: true }),
226
+ 'send-sms': async (job) => ({ sent: true }),
501
227
  },
502
228
  concurrency: 10,
503
- retry: { maxAttempts: 3, delay: 1000, strategy: 'jitter' },
229
+ retry: { maxAttempts: 5, strategy: 'jitter' },
504
230
  circuitBreaker: { threshold: 5, resetTimeout: 30000 },
505
- ttl: { defaultTtl: 600000, perName: { 'verify-otp': 60000 } },
506
- priorityAging: { interval: 60000, minAge: 300000, boost: 1 },
507
- deduplication: { ttl: 5000 },
508
- rateLimit: { max: 100, duration: 1000 },
509
- dlq: { autoRetry: true, maxAge: 604800000 },
510
231
  });
511
232
 
233
+ // Onion middleware around every job
512
234
  app.use(async (job, next) => {
513
235
  const start = Date.now();
514
236
  const result = await next();
@@ -516,79 +238,50 @@ app.use(async (job, next) => {
516
238
  return result;
517
239
  });
518
240
 
519
- app
520
- .trigger({ on: 'process', create: 'notify', data: (r) => ({ payload: r.id }) })
521
- .trigger({ on: 'process', event: 'failed', create: 'alert', data: (_, j) => j.data });
522
-
523
- await app.cron('cleanup', '0 2 * * *', { payload: 'nightly' });
524
- await app.add('process', { payload: 'ORD-001' });
241
+ await app.cron('daily-report', '0 9 * * *', { type: 'summary' });
242
+ await app.add('send-email', { to: 'alice@example.com' });
525
243
 
526
- process.on('SIGINT', async () => {
527
- await app.close();
528
- shutdownManager();
529
- });
244
+ app.on('completed', (job, result) => console.log(result));
245
+ await app.close();
530
246
  ```
531
247
 
532
- [Simple Mode docs โ†’](https://bunqueue.dev/guide/simple-mode/)
248
+ Also included: batch processing, event triggers (job A completes โ†’ create job
249
+ B), job TTL, priority aging, deduplication, per-group rate limiting, DLQ with
250
+ auto-retry, graceful cancellation via `AbortController`.
533
251
 
534
- ---
252
+ [Simple Mode reference โ†’](https://bunqueue.dev/guide/simple-mode/)
535
253
 
536
254
  ## Workflow Engine
537
255
 
538
- Orchestrate multi-step business processes with branching, saga compensation, and human-in-the-loop signals. Built on top of bunqueue โ€” no new infrastructure.
256
+ Multi-step orchestration with saga compensation, branching, parallel steps and
257
+ human-in-the-loop signals โ€” built on bunqueue, no new infrastructure:
539
258
 
540
259
  ```typescript
541
260
  import { Workflow, Engine } from 'bunqueue/workflow';
542
261
 
543
262
  const orderFlow = new Workflow('order-pipeline')
544
- .step('validate', async (ctx) => {
545
- const { orderId, amount } = ctx.input as { orderId: string; amount: number };
546
- if (amount <= 0) throw new Error('Invalid amount');
547
- return { orderId };
548
- })
549
263
  .step('reserve-stock', async () => {
550
264
  await inventory.reserve();
551
265
  return { reserved: true };
552
266
  }, {
553
- compensate: async () => await inventory.release(), // Auto-rollback on failure
267
+ compensate: async () => await inventory.release(), // auto-rollback on failure
554
268
  })
555
269
  .step('charge', async () => {
556
270
  return { txId: await payments.charge() };
557
271
  }, {
558
272
  compensate: async () => await payments.refund(),
559
273
  })
274
+ .waitFor('manager-approval', { timeout: 86_400_000 }) // human-in-the-loop
560
275
  .step('confirm', async (ctx) => {
561
- const { txId } = ctx.steps['charge'] as { txId: string };
562
- return { emailSent: true, txId };
276
+ return { txId: (ctx.steps['charge'] as { txId: string }).txId };
563
277
  });
564
278
 
565
279
  const engine = new Engine({ embedded: true });
566
280
  engine.register(orderFlow);
567
- await engine.start('order-pipeline', { orderId: 'ORD-1', amount: 99.99 });
281
+ const run = await engine.start('order-pipeline', { orderId: 'ORD-1' });
282
+ await engine.signal(run.id, 'manager-approval', { approved: true });
568
283
  ```
569
284
 
570
- **Features:**
571
-
572
- - **Saga pattern** โ€” Compensation handlers run in reverse when a step fails
573
- - **Branching** โ€” Route to different paths based on runtime conditions
574
- - **Parallel steps** โ€” Run independent steps concurrently with `.parallel()`
575
- - **Human-in-the-loop** โ€” `waitFor('event')` pauses execution, `engine.signal()` resumes it
576
- - **Signal timeout** โ€” `waitFor('event', { timeout })` fails if signal doesn't arrive in time
577
- - **Step retry** โ€” Automatic retry with exponential backoff and jitter
578
- - **Nested workflows** โ€” Compose workflows with `.subWorkflow()`, child results passed back
579
- - **Loops** โ€” `doUntil()` and `doWhile()` for conditional iteration with safety limits
580
- - **forEach** โ€” Iterate over dynamic item lists with indexed step results (`step:0`, `step:1`, ...)
581
- - **Map** โ€” Synchronous data transforms between steps with `.map()`
582
- - **Schema validation** โ€” Validate step input/output with Zod, ArkType, Valibot, or any `.parse()` schema
583
- - **Subscribe** โ€” `engine.subscribe(id, callback)` to monitor a specific execution's events
584
- - **Observability** โ€” Typed event emitter with 11 event types (`engine.on/onAny`)
585
- - **Cleanup & archival** โ€” `engine.cleanup()` / `engine.archive()` for execution history management
586
- - **Step timeouts** โ€” Prevent steps from running indefinitely
587
- - **Context passing** โ€” Each step accesses input and all previous step results
588
- - **SQLite persistence** โ€” Execution state survives restarts
589
-
590
- **vs Competitors:**
591
-
592
285
  | | **bunqueue** | **Temporal** | **Inngest** | **Trigger.dev** |
593
286
  |---|---|---|---|---|
594
287
  | **Infrastructure** | None (embedded) | PostgreSQL + 7 services | Cloud-only | Redis + PostgreSQL |
@@ -597,111 +290,26 @@ await engine.start('order-pipeline', { orderId: 'ORD-1', amount: 99.99 });
597
290
  | **Self-hosted** | Zero-config | Complex | No | Complex |
598
291
  | **Pricing** | Free (MIT) | Free / Cloud $$ | Per-execution | Free tier, then $50/mo+ |
599
292
 
600
- ```typescript
601
- // Branching
602
- const flow = new Workflow('tiered')
603
- .step('classify', async (ctx) => {
604
- const { amount } = ctx.input as { amount: number };
605
- return { tier: amount > 1000 ? 'vip' : 'basic' };
606
- })
607
- .branch((ctx) => (ctx.steps['classify'] as { tier: string }).tier)
608
- .path('vip', (w) => w.step('vip-handler', async () => ({ discount: 20 })))
609
- .path('basic', (w) => w.step('basic-handler', async () => ({ discount: 0 })))
610
- .step('done', async () => ({ processed: true }));
611
-
612
- // Human-in-the-loop
613
- const approvalFlow = new Workflow('expense')
614
- .step('submit', async (ctx) => {
615
- const { amount } = ctx.input as { amount: number };
616
- return { amount };
617
- })
618
- .waitFor('manager-approval')
619
- .step('reimburse', async (ctx) => {
620
- const decision = ctx.signals['manager-approval'] as { approved: boolean };
621
- return { status: decision.approved ? 'paid' : 'rejected' };
622
- });
623
-
624
- // Signal a waiting workflow
625
- await engine.signal(run.id, 'manager-approval', { approved: true });
626
- ```
627
-
628
- [Workflow Engine docs โ†’](https://bunqueue.dev/guide/workflow/)
629
-
630
- ---
631
-
632
- <p align="center">
633
- <strong>bunqueue Dashboard</strong><br/>
634
- <sub>A web dashboard that fully drives your bunqueue server &mdash; queues, jobs, DLQ, cron, webhooks, workers, live activity, SQLite inspector, and an AI Copilot. Open source, currently in beta. Requires Bun.</sub>
635
- </p>
636
-
637
- https://github.com/user-attachments/assets/e8a8d38e-b4a6-4dc8-8360-876c0f24d116
638
-
639
- ```bash
640
- bunx bunqueue-dashboard
641
- ```
642
-
643
- <p align="center">
644
- <sub>
645
- <a href="https://egeominotti.github.io/bunqueue-dashboard/">Live demo</a> &middot;
646
- <a href="https://egeominotti.github.io/bunqueue-dashboard/docs/user-guide">User guide</a> &middot;
647
- <a href="https://github.com/egeominotti/bunqueue-dashboard">GitHub</a> &middot;
648
- <a href="https://www.npmjs.com/package/bunqueue-dashboard">npm: bunqueue-dashboard</a>
649
- </sub>
650
- </p>
651
-
652
- ---
653
-
654
- ## Why bunqueue?
655
-
656
- | Library | Requires | AI-native |
657
- | ------------ | ----------- | --------- |
658
- | BullMQ | Redis | No |
659
- | Agenda | MongoDB | No |
660
- | pg-boss | PostgreSQL | No |
661
- | **bunqueue** | **Nothing** | **Yes** |
293
+ Also included: nested workflows, `doUntil`/`doWhile` loops, `forEach` over
294
+ dynamic lists, schema validation (Zod, ArkType, Valibot or any `.parse()`),
295
+ step timeouts, typed events, SQLite-persisted execution state.
662
296
 
663
- - **MCP server included** โ€” 73 tools, 5 resources, 3 prompts. AI agents get full control out of the box
664
- - **BullMQ-compatible API** โ€” Same `Queue`, `Worker`, `QueueEvents`
665
- - **Zero external infrastructure** โ€” No Redis, no MongoDB
666
- - **Tiny footprint** โ€” 5.5 MB installed, 7 packages (just 2 runtime deps: `croner` + `msgpackr`)
667
- - **SQLite persistence** โ€” Survives restarts, WAL mode for concurrent access
668
- - **Up to 630K ops/sec** โ€” [Verified benchmarks](https://bunqueue.dev/guide/benchmarks/)
297
+ [Workflow Engine guide โ†’](https://bunqueue.dev/guide/workflow/)
669
298
 
670
299
  ## Built for AI Agents (MCP Server)
671
300
 
672
- <p align="center">
673
- <img src=".github/mcp-flow.svg" alt="HTTP Handler Flow: Cron/Add Job โ†’ Queue โ†’ Embedded Worker โ†’ HTTP API โ†’ Job Result" width="700" />
674
- </p>
675
-
676
- bunqueue is the **first job queue with native MCP support**. AI agents get a full-featured scheduler, task queue, and monitoring system โ€” no glue code needed.
677
-
678
- **HTTP Handlers** solve a fundamental problem: an AI agent can schedule jobs and manage queues, but it cannot run a persistent worker. When the agent registers an HTTP handler, bunqueue spawns an embedded Worker that continuously pulls jobs and calls your HTTP endpoint. Responses are saved as results. Failed calls retry automatically via DLQ.
679
-
680
- **What AI agents can do with bunqueue:**
681
-
682
- - **Schedule tasks** โ€” cron jobs, delayed execution, recurring workflows
683
- - **Manage job pipelines** โ€” push jobs, monitor progress, retry failures
684
- - **Full pull/ack/fail cycle** โ€” agents can consume and process jobs directly
685
- - **Monitor everything** โ€” stats, memory, Prometheus metrics, logs, DLQ
686
- - **Control flow** โ€” pause/resume queues, set rate limits, manage concurrency
687
- - **73 MCP tools + 5 resources + 3 prompts** โ€” complete control over every feature
688
- - **HTTP handlers** โ€” register a URL, bunqueue auto-processes jobs via HTTP calls
301
+ bunqueue ships a native MCP server: 73 tools, 5 resources, 3 prompts. Agents
302
+ schedule cron jobs, push and process jobs, retry failures, set rate limits,
303
+ and read stats โ€” no glue code. HTTP handlers let an agent register a URL and
304
+ have an embedded worker call it for every job.
689
305
 
690
306
  ```bash
691
- # bunqueue-mcp is a binary bundled inside the bunqueue package (not a standalone npm package),
692
- # so install bunqueue first, then connect Claude Code:
693
- bun add bunqueue
694
- # Since v2.8.1 the MCP SDK is an optional peer dependency. Queue-only users skip it entirely:
695
- # a clean install drops from 93 MB / 117 packages to 5.5 MB / 7 packages (โˆ’94%, ~3.5ร— faster on a cold install).
696
- # bunx --package=bunqueue does NOT auto-install optional peers, so install the SDK once to run the MCP server:
697
- bun add @modelcontextprotocol/sdk
307
+ bun add bunqueue @modelcontextprotocol/sdk # the MCP SDK is an optional peer
698
308
  claude mcp add bunqueue -- bunx bunqueue-mcp
699
309
  ```
700
310
 
701
311
  ```json
702
- // Claude Desktop / Cursor / Windsurf โ€” --package=bunqueue lets bunx resolve the bundled
703
- // bunqueue-mcp binary without a separate install (the @modelcontextprotocol/sdk optional
704
- // peer dependency must still be installed once: bun add @modelcontextprotocol/sdk):
312
+ // Claude Desktop / Cursor / Windsurf
705
313
  {
706
314
  "mcpServers": {
707
315
  "bunqueue": {
@@ -712,246 +320,50 @@ claude mcp add bunqueue -- bunx bunqueue-mcp
712
320
  }
713
321
  ```
714
322
 
715
- **Example agent interactions:**
716
-
717
- - *"Schedule a cleanup job every day at 3 AM"*
718
- - *"Add 500 email jobs to the queue with priority 10"*
719
- - *"Show me all failed jobs and retry them"*
720
- - *"Set rate limit to 50/sec on the api-calls queue"*
721
- - *"What's the memory usage and queue throughput?"*
722
-
723
- **Plugin ecosystem** โ€” bunqueue ships with auto-discovery (`.mcp.json`), a custom Claude Code agent for bunqueue tasks, and installable skills for setup, API reference, and real-world patterns. Drop bunqueue into any project and your AI tools discover it automatically.
724
-
725
- Supports **embedded** (local SQLite) and **TCP** (remote server) modes. [Full MCP documentation โ†’](https://bunqueue.dev/guide/mcp/)
726
-
727
- ## When to use bunqueue
728
-
729
- **Great for:**
730
-
731
- - **AI agents that need a scheduler** โ€” cron jobs, delayed tasks, retries, all via MCP
732
- - **Agentic workflows** โ€” agents push jobs, workers process, agents monitor results
733
- - Single-server deployments
734
- - Prototypes and MVPs
735
- - Moderate to high workloads (up to 630K ops/sec)
736
- - Teams that want to avoid Redis operational overhead
737
- - Embedded use cases (CLI tools, edge functions, serverless)
738
-
739
- **Not ideal for:**
740
-
741
- - Multi-region distributed systems requiring HA
742
- - Workloads that need automatic failover today
743
- - Systems already running Redis with existing infrastructure
744
-
745
- ## Why not just use BullMQ?
746
-
747
- If you're already running Redis, BullMQ is great โ€” battle-tested and feature-rich.
748
-
749
- bunqueue is for when you **don't want to run Redis**. SQLite with WAL mode handles surprisingly high throughput for single-node deployments (tested up to 630K ops/sec on bulk push). You get persistence, priorities, delays, retries, cron jobs, and DLQ โ€” without the operational overhead of another service.
750
-
751
- ## Install
752
-
753
- ```bash
754
- bun add bunqueue
755
- ```
756
-
757
- > The server and embedded mode require the [Bun](https://bun.sh) runtime. To
758
- > connect **clients** from Node.js, Deno, Cloudflare Workers or Python, use the
759
- > [SDKs](#one-queue-any-language-sdks) instead.
760
-
761
- ## Two Modes
762
-
763
- bunqueue runs in two modes depending on your architecture:
764
-
765
- | | Embedded | Server (TCP) |
766
- | ---------------- | ------------------------------------- | -------------------------------------------- |
767
- | **How it works** | Queue runs inside your process | Standalone server, clients connect via TCP |
768
- | **Setup** | `bun add bunqueue` | `docker run` or `bunqueue start` |
769
- | **Performance** | 630K ops/sec (bulk push) | 90K ops/sec (push) |
770
- | **Best for** | Single-process apps, CLIs, serverless | Multiple workers, separate producer/consumer |
771
- | **Scaling** | Same process only | Multiple clients across machines |
772
-
773
- ### Embedded Mode
774
-
775
- Everything runs in your process. No server, no network, no setup. Without a
776
- data path the queue is in-memory and lost on restart: pass
777
- `dataPath: './data/app.db'` (or set `BUNQUEUE_DATA_PATH`) to persist jobs.
778
-
779
- ```typescript
780
- import { Queue, Worker } from 'bunqueue/client';
781
-
782
- const queue = new Queue('emails', { embedded: true });
783
-
784
- const worker = new Worker(
785
- 'emails',
786
- async (job) => {
787
- console.log('Processing:', job.data);
788
- return { sent: true };
789
- },
790
- { embedded: true }
791
- );
323
+ Then just ask: *"Schedule a cleanup job every day at 3 AM"* ยท *"Show me all
324
+ failed jobs and retry them"* ยท *"Set rate limit to 50/sec on api-calls"*.
792
325
 
793
- await queue.add('welcome', { to: 'user@example.com' });
794
- ```
326
+ [MCP guide โ†’](https://bunqueue.dev/guide/mcp/)
795
327
 
796
- ### Server Mode (TCP)
328
+ ## Dashboard
797
329
 
798
- Run bunqueue as a standalone server. Multiple workers and producers connect via TCP.
330
+ A web dashboard that fully drives your server โ€” queues, jobs, DLQ, cron,
331
+ webhooks, workers, live activity, SQLite inspector and an AI copilot. Open
332
+ source, currently in beta:
799
333
 
800
334
  ```bash
801
- # Start with persistent data
802
- docker run -d -p 6789:6789 -p 6790:6790 \
803
- -v bunqueue-data:/app/data \
804
- ghcr.io/egeominotti/bunqueue:latest
805
- ```
806
-
807
- Connect from your app:
808
-
809
- ```typescript
810
- import { Queue, Worker } from 'bunqueue/client';
811
-
812
- const queue = new Queue('tasks', { connection: { host: 'localhost', port: 6789 } });
813
-
814
- const worker = new Worker(
815
- 'tasks',
816
- async (job) => {
817
- return { done: true };
818
- },
819
- { connection: { host: 'localhost', port: 6789 } }
820
- );
821
-
822
- await queue.add('process', { data: 'hello' });
823
- ```
824
-
825
- ## One Queue, Any Language (SDKs)
826
-
827
- The server does all the heavy lifting โ€” retries, priorities, scheduling, DLQ.
828
- Official client SDKs speak the native TCP protocol (msgpack, pipelined) with
829
- full feature parity, so producers and workers can live anywhere in your stack:
830
-
831
- | Where your code runs | Install | Status |
832
- | -------------------- | ------- | ------ |
833
- | **Node.js โ‰ฅ 20, Deno โ‰ฅ 2, Bun, Cloudflare Workers** | [`npm install bunqueue-client`](https://www.npmjs.com/package/bunqueue-client) | Native integration/e2e + protocol conformance |
834
- | **Python โ‰ฅ 3.9** | PyPI coming soon โ€” today: `pip install "git+https://github.com/egeominotti/bunqueue.git#subdirectory=sdk/python"` | Native integration/e2e + protocol conformance |
835
- | **PHP โ‰ฅ 8.1** | Packagist coming soon โ€” today: use [`sdk/php`](./sdk/php) | Native e2e + protocol conformance |
836
- | **Go 1.26.5** | `go get github.com/egeominotti/bunqueue/sdk/go` | Native tests + protocol conformance |
837
- | **Rust โ‰ฅ 1.85** | crates.io coming soon โ€” today: use [`sdk/rust`](./sdk/rust) as a path dependency | Native unit/integration + protocol conformance |
838
- | **Elixir โ‰ฅ 1.15** | Hex coming soon โ€” today: use [`sdk/elixir`](./sdk/elixir) as a path dependency | ExUnit integration + protocol conformance |
839
-
840
- All six SDKs run the same production hardening matrix: independent-connection
841
- idempotency and single-lease races, generated payload invariants,
842
- malformed-input fuzz corpora, broker SIGKILL/reconnect durability, bounded
843
- spikes, structured telemetry, and 17 shared protocol checks. Weekly CI adds
844
- runtime compatibility, live dependency advisories, native Go race/fuzz checks,
845
- and a 15-minute sustained profile per SDK. See
846
- [`docs/testing.md`](./docs/testing.md#sdk-hardening-matrix).
847
-
848
- ```typescript
849
- // Node.js / Deno / Cloudflare Workers
850
- import { Queue, Worker } from 'bunqueue-client';
851
-
852
- const queue = new Queue('emails', { host: 'localhost', port: 6789 });
853
- await queue.add('welcome', { to: 'user@example.com' });
854
-
855
- new Worker('emails', async (job) => ({ sent: true }), { concurrency: 10 });
856
- ```
857
-
858
- ```python
859
- # Python
860
- from bunqueue import Queue, Worker
861
-
862
- queue = Queue("emails", host="localhost", port=6789)
863
- queue.add("welcome", {"to": "user@example.com"})
864
-
865
- Worker("emails", lambda job: {"sent": True}, concurrency=10).run()
335
+ bunx bunqueue-dashboard
866
336
  ```
867
337
 
868
- Mix and match: a Next.js API adds jobs, a Python service processes them.
869
- Docs: [bunqueue.dev/guide/sdks](https://bunqueue.dev/guide/sdks/) ยท Sources: [`sdk/`](./sdk/)
870
-
871
- ### Simple Mode
872
-
873
- One object. Queue + Worker + Routes + Middleware + Cron. Zero boilerplate.
874
-
875
- ```typescript
876
- import { Bunqueue } from 'bunqueue/client';
877
-
878
- const app = new Bunqueue('notifications', {
879
- embedded: true,
880
-
881
- // Route jobs by name
882
- routes: {
883
- 'send-email': async (job) => {
884
- console.log(`Email to ${job.data.to}`);
885
- return { sent: true };
886
- },
887
- 'send-sms': async (job) => {
888
- console.log(`SMS to ${job.data.to}`);
889
- return { sent: true };
890
- },
891
- },
892
- concurrency: 10,
893
- });
894
-
895
- // Middleware โ€” wraps every job (logging, timing, error recovery)
896
- app.use(async (job, next) => {
897
- const start = Date.now();
898
- const result = await next();
899
- console.log(`${job.name} took ${Date.now() - start}ms`);
900
- return result;
901
- });
902
-
903
- // Cron โ€” scheduled jobs
904
- await app.cron('daily-report', '0 9 * * *', { type: 'summary' });
905
- await app.every('healthcheck', 30000, { type: 'ping' });
906
-
907
- // Events
908
- app.on('completed', (job, result) => console.log(result));
909
- app.on('failed', (job, err) => console.error(err));
910
-
911
- // Add jobs
912
- await app.add('send-email', { to: 'alice@example.com' });
913
- await app.add('send-sms', { to: '+1234567890' });
914
-
915
- // Graceful shutdown
916
- await app.close();
917
- ```
338
+ https://github.com/user-attachments/assets/e8a8d38e-b4a6-4dc8-8360-876c0f24d116
918
339
 
919
- Works with both embedded and TCP mode. [Simple Mode docs โ†’](https://bunqueue.dev/guide/simple-mode/)
340
+ [Live demo](https://egeominotti.github.io/bunqueue-dashboard/) ยท
341
+ [User guide](https://egeominotti.github.io/bunqueue-dashboard/docs/user-guide) ยท
342
+ [GitHub](https://github.com/egeominotti/bunqueue-dashboard)
920
343
 
921
344
  ## Performance
922
345
 
923
- SQLite handles surprisingly high throughput for single-node deployments:
924
-
925
- | Mode | Peak Throughput | Use Case |
926
- | -------- | --------------- | ------------------- |
346
+ | Mode | Peak Throughput | Use Case |
347
+ | -------- | ------------------------ | ------------------- |
927
348
  | Embedded | 630K ops/sec (bulk push) | Same process |
928
349
  | TCP | 90K ops/sec (push) | Distributed workers |
929
350
 
930
- > Run `bun run bench` to verify on your hardware. [Full benchmark methodology โ†’](https://bunqueue.dev/guide/benchmarks/)
931
-
932
- ## Monitoring
933
-
934
- ```bash
935
- # Start with Prometheus + Grafana
936
- docker compose --profile monitoring up -d
937
- ```
938
-
939
- - **Grafana**: http://localhost:3000 (admin/bunqueue)
940
- - **Prometheus**: http://localhost:9090
351
+ Run `bun run bench` to verify on your hardware.
352
+ [Benchmark methodology โ†’](https://bunqueue.dev/guide/benchmarks/)
941
353
 
942
354
  ## Documentation
943
355
 
944
- **[Read the full documentation โ†’](https://bunqueue.dev/)**
945
-
946
- - [Quick Start](https://bunqueue.dev/guide/quickstart/)
947
- - [Workflow Engine](https://bunqueue.dev/guide/workflow/)
948
- - [MCP Server (AI Agents)](https://bunqueue.dev/guide/mcp/)
949
- - [Simple Mode](https://bunqueue.dev/guide/simple-mode/)
950
- - [Queue API](https://bunqueue.dev/guide/queue/)
951
- - [Worker API](https://bunqueue.dev/guide/worker/)
952
- - [Server Mode](https://bunqueue.dev/guide/server/)
953
- - [Benchmarks](https://bunqueue.dev/guide/benchmarks/)
954
- - [CLI Reference](https://bunqueue.dev/guide/cli/)
356
+ **[bunqueue.dev โ†’](https://bunqueue.dev/)**
357
+
358
+ - [Quick Start](https://bunqueue.dev/guide/quickstart/) โ€” install to working queue in under a minute
359
+ - [Queue API](https://bunqueue.dev/guide/queue/) ยท [Worker API](https://bunqueue.dev/guide/worker/) โ€” every option explained
360
+ - [Simple Mode](https://bunqueue.dev/guide/simple-mode/) โ€” routes, middleware, triggers, TTL, dedup
361
+ - [Workflow Engine](https://bunqueue.dev/guide/workflow/) โ€” sagas, branching, signals
362
+ - [SDKs](https://bunqueue.dev/guide/sdks/) โ€” TypeScript, Python, PHP, Go, Rust, Elixir
363
+ - [MCP Server](https://bunqueue.dev/guide/mcp/) โ€” AI agent integration
364
+ - [Server & Deployment](https://bunqueue.dev/guide/deployment/) โ€” Docker, TLS, auth, monitoring
365
+ - [CLI Reference](https://bunqueue.dev/guide/cli/) โ€” run and manage from the terminal
366
+ - [Migrate from BullMQ](https://bunqueue.dev/guide/migration/)
955
367
 
956
368
  ## License
957
369
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunqueue",
3
- "version": "2.8.37",
3
+ "version": "2.8.38",
4
4
  "description": "High-performance job queue for Bun & AI agents. SQLite persistence, cron scheduling, priorities, retries, DLQ, webhooks, native MCP server. Zero external infrastructure.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",