bunqueue 2.8.37 โ 2.8.39
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 +168 -756
- package/dist/application/backgroundTasks.js +6 -0
- package/dist/application/cleanupTasks.js +31 -11
- package/dist/application/contextFactory.d.ts +3 -0
- package/dist/application/contextFactory.js +6 -0
- package/dist/application/dependencyResultTracker.d.ts +17 -0
- package/dist/application/dependencyResultTracker.js +71 -0
- package/dist/application/operations/ack.d.ts +2 -0
- package/dist/application/operations/ack.js +6 -0
- package/dist/application/operations/ackHelpers.d.ts +2 -1
- package/dist/application/operations/ackHelpers.js +6 -0
- package/dist/application/operations/jobManagement.d.ts +2 -0
- package/dist/application/operations/jobManagement.js +3 -0
- package/dist/application/operations/push.d.ts +2 -0
- package/dist/application/operations/pushInsert.d.ts +4 -0
- package/dist/application/operations/pushInsert.js +6 -0
- package/dist/application/operations/queryOperations.d.ts +2 -0
- package/dist/application/operations/queryOperations.js +5 -1
- package/dist/application/operations/queueControl.d.ts +2 -0
- package/dist/application/operations/queueControl.js +4 -0
- package/dist/application/queueManager.d.ts +1 -0
- package/dist/application/queueManager.js +11 -0
- package/dist/application/types.d.ts +2 -0
- package/dist/infrastructure/server/tcp.js +14 -7
- 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> ·
|
|
22
|
+
<a href="https://bunqueue.dev/guide/quickstart/">Quick Start</a> ·
|
|
22
23
|
<a href="https://bunqueue.dev/guide/benchmarks/">Benchmarks</a> ·
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
57
|
+
The queue also runs as a standalone server โ one command, nothing else to
|
|
58
|
+
operate:
|
|
85
59
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
70
|
+
Then produce and process from the language you already use:
|
|
117
71
|
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
+
const queue = new Queue('emails'); // localhost:6789 by default
|
|
80
|
+
await queue.add('welcome', { to: 'user@example.com' });
|
|
167
81
|
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
+
## Two Modes
|
|
237
118
|
|
|
238
|
-
|
|
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
|
-
|
|
127
|
+
### Embedded
|
|
241
128
|
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
+
const queue = new Queue('emails', { embedded: true, dataPath: './data/app.db' });
|
|
267
136
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
+
const queue = new Queue('tasks', { connection: { host: 'localhost', port: 6789 } });
|
|
344
160
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
+
[Running the server โ](https://bunqueue.dev/guide/server/) ยท
|
|
173
|
+
[Deployment guide โ](https://bunqueue.dev/guide/deployment/)
|
|
400
174
|
|
|
401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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
|
-
|
|
194
|
+
const queue = new Queue('emails', { host: 'localhost', port: 6789 });
|
|
195
|
+
await queue.add('welcome', { to: 'user@example.com' });
|
|
435
196
|
|
|
436
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
204
|
+
queue = Queue("emails", host="localhost", port=6789)
|
|
205
|
+
queue.add("welcome", {"to": "user@example.com"})
|
|
460
206
|
|
|
461
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
+
[SDK guide (all six languages) โ](https://bunqueue.dev/guide/sdks/)
|
|
471
214
|
|
|
472
|
-
|
|
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
|
-
|
|
217
|
+
`Bunqueue` bundles Queue + Worker + routes + middleware + cron in one object:
|
|
491
218
|
|
|
492
219
|
```typescript
|
|
493
|
-
import { Bunqueue
|
|
220
|
+
import { Bunqueue } from 'bunqueue/client';
|
|
494
221
|
|
|
495
|
-
const app = new Bunqueue
|
|
222
|
+
const app = new Bunqueue('notifications', {
|
|
496
223
|
embedded: true,
|
|
497
224
|
routes: {
|
|
498
|
-
'
|
|
499
|
-
'
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
shutdownManager();
|
|
529
|
-
});
|
|
244
|
+
app.on('completed', (job, result) => console.log(result));
|
|
245
|
+
await app.close();
|
|
530
246
|
```
|
|
531
247
|
|
|
532
|
-
|
|
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
|
-
|
|
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(), //
|
|
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
|
-
|
|
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'
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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 — 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> ·
|
|
646
|
-
<a href="https://egeominotti.github.io/bunqueue-dashboard/docs/user-guide">User guide</a> ·
|
|
647
|
-
<a href="https://github.com/egeominotti/bunqueue-dashboard">GitHub</a> ·
|
|
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
|
-
|
|
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
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
794
|
-
```
|
|
326
|
+
[MCP guide โ](https://bunqueue.dev/guide/mcp/)
|
|
795
327
|
|
|
796
|
-
|
|
328
|
+
## Dashboard
|
|
797
329
|
|
|
798
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**[
|
|
945
|
-
|
|
946
|
-
- [Quick Start](https://bunqueue.dev/guide/quickstart/)
|
|
947
|
-
- [
|
|
948
|
-
- [
|
|
949
|
-
- [
|
|
950
|
-
- [
|
|
951
|
-
- [
|
|
952
|
-
- [Server
|
|
953
|
-
- [
|
|
954
|
-
- [
|
|
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
|
|