bunqueue-client 0.1.2 → 0.1.4
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/LICENSE +1 -1
- package/README.md +119 -9
- package/dist/bunqueue/aging.d.ts +17 -0
- package/dist/bunqueue/aging.js +53 -0
- package/dist/bunqueue/batch.d.ts +17 -0
- package/dist/bunqueue/batch.js +60 -0
- package/dist/bunqueue/bunqueue-api.d.ts +87 -0
- package/dist/bunqueue/bunqueue-api.js +116 -0
- package/dist/bunqueue/bunqueue.d.ts +57 -0
- package/dist/bunqueue/bunqueue.js +184 -0
- package/dist/bunqueue/cancellation.d.ts +20 -0
- package/dist/bunqueue/cancellation.js +48 -0
- package/dist/bunqueue/circuit-breaker.d.ts +21 -0
- package/dist/bunqueue/circuit-breaker.js +69 -0
- package/dist/bunqueue/dedup-debounce.d.ts +14 -0
- package/dist/bunqueue/dedup-debounce.js +37 -0
- package/dist/bunqueue/dlq-rate-limit.d.ts +33 -0
- package/dist/bunqueue/dlq-rate-limit.js +65 -0
- package/dist/bunqueue/rate-gate.d.ts +27 -0
- package/dist/bunqueue/rate-gate.js +61 -0
- package/dist/bunqueue/retry.d.ts +9 -0
- package/dist/bunqueue/retry.js +56 -0
- package/dist/bunqueue/triggers.d.ts +18 -0
- package/dist/bunqueue/triggers.js +42 -0
- package/dist/bunqueue/ttl.d.ts +18 -0
- package/dist/bunqueue/ttl.js +31 -0
- package/dist/bunqueue/types.d.ts +151 -0
- package/dist/bunqueue/types.js +6 -0
- package/dist/connection.d.ts +9 -1
- package/dist/connection.js +32 -47
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/job.d.ts +2 -0
- package/dist/job.js +4 -0
- package/dist/queue-admin.js +2 -1
- package/dist/socket-factory.d.ts +4 -0
- package/dist/socket-factory.js +47 -0
- package/dist/worker-base.d.ts +13 -2
- package/dist/worker-base.js +29 -4
- package/dist/worker.js +8 -0
- package/package.json +2 -2
- package/src/bunqueue/aging.ts +62 -0
- package/src/bunqueue/batch.ts +79 -0
- package/src/bunqueue/bunqueue-api.ts +212 -0
- package/src/bunqueue/bunqueue.ts +213 -0
- package/src/bunqueue/cancellation.ts +54 -0
- package/src/bunqueue/circuit-breaker.ts +81 -0
- package/src/bunqueue/dedup-debounce.ts +43 -0
- package/src/bunqueue/dlq-rate-limit.ts +89 -0
- package/src/bunqueue/rate-gate.ts +67 -0
- package/src/bunqueue/retry.ts +72 -0
- package/src/bunqueue/triggers.ts +51 -0
- package/src/bunqueue/ttl.ts +38 -0
- package/src/bunqueue/types.ts +171 -0
- package/src/connection.ts +38 -46
- package/src/index.ts +22 -1
- package/src/job.ts +5 -0
- package/src/queue-admin.ts +2 -1
- package/src/socket-factory.ts +51 -0
- package/src/worker-base.ts +30 -4
- package/src/worker.ts +9 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -8,11 +8,11 @@ The bunqueue server runs on Bun, distributed as a binary or a Docker image. This
|
|
|
8
8
|
|
|
9
9
|
| Runtime | Status | Notes |
|
|
10
10
|
|---|---|---|
|
|
11
|
-
| Node.js 20 or later | Supported,
|
|
12
|
-
| Bun | Supported,
|
|
13
|
-
| Deno 2 or later | Supported,
|
|
11
|
+
| Node.js 20 or later | Supported, 81/81 e2e and 8/8 integration tests | ESM. TypeScript files run directly on Node 22 or later via `--experimental-strip-types` |
|
|
12
|
+
| Bun | Supported, 81/81 e2e and 8/8 integration tests | No additional configuration required |
|
|
13
|
+
| Deno 2 or later | Supported, 81/81 e2e and 8/8 integration tests | Uses `node:` builtins and the npm `msgpackr` package |
|
|
14
14
|
| tsx, ts-node, vitest, jest | Supported | These environments execute on Node.js |
|
|
15
|
-
| Cloudflare Workers | Supported,
|
|
15
|
+
| Cloudflare Workers | Supported, 16/16 e2e tests inside workerd, including Simple Mode and the full API surface | Requires the `nodejs_compat` compatibility flag. The runtime is request scoped, so long lived worker loops are not available: consume in batches from Cron Triggers or Durable Object alarms, a pattern covered by the test suite. TLS connections require a publicly trusted certificate |
|
|
16
16
|
| Browser | Not supported | Raw TCP sockets are unavailable. Use the server HTTP API instead |
|
|
17
17
|
|
|
18
18
|
Portability is guaranteed by design: the client relies exclusively on `node:*` builtins (`net`, `tls`, `events`, `crypto`, `os`), uses no `Bun.*` globals and no runtime specific imports, and carries a single runtime dependency, `msgpackr`.
|
|
@@ -24,15 +24,37 @@ npm install bunqueue-client
|
|
|
24
24
|
# or: bun add bunqueue-client / pnpm add bunqueue-client / deno add npm:bunqueue-client
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
## Quick start
|
|
27
|
+
## Quick start, step by step
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Every step from zero to a production ready queue.
|
|
30
|
+
|
|
31
|
+
### Step 1. Run the bunqueue server
|
|
32
|
+
|
|
33
|
+
The server is the only component that requires [Bun](https://bun.sh). Pick one:
|
|
30
34
|
|
|
31
35
|
```bash
|
|
36
|
+
# Option A: one command, no install (requires Bun)
|
|
32
37
|
bunx bunqueue start
|
|
38
|
+
|
|
39
|
+
# Option B: Docker, with persistent data
|
|
40
|
+
docker run -d --name bunqueue \
|
|
41
|
+
-p 6789:6789 -p 6790:6790 \
|
|
42
|
+
-v bunqueue-data:/app/data \
|
|
43
|
+
ghcr.io/egeominotti/bunqueue:latest
|
|
33
44
|
```
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
Port 6789 is the TCP protocol (what this client uses), port 6790 is the HTTP API with `/health`, `/metrics`, and dashboard endpoints.
|
|
47
|
+
|
|
48
|
+
### Step 2. Install the client
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install bunqueue-client
|
|
52
|
+
# or: bun add bunqueue-client / pnpm add bunqueue-client / deno add npm:bunqueue-client
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Step 3. Add your first job and process it
|
|
56
|
+
|
|
57
|
+
Create `app.ts`, one file for the sake of the demo:
|
|
36
58
|
|
|
37
59
|
```typescript
|
|
38
60
|
import { Queue, Worker } from 'bunqueue-client';
|
|
@@ -51,7 +73,7 @@ await queue.add('greet', { name: 'world' });
|
|
|
51
73
|
queue.close();
|
|
52
74
|
```
|
|
53
75
|
|
|
54
|
-
|
|
76
|
+
Run it with the runtime you already use:
|
|
55
77
|
|
|
56
78
|
```bash
|
|
57
79
|
node --experimental-strip-types app.ts # Node 22 or later
|
|
@@ -66,7 +88,53 @@ processing { name: 'world' }
|
|
|
66
88
|
completed 019f40a5-... { greeted: 'world' }
|
|
67
89
|
```
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
Defaults are `host: 'localhost'` and `port: 6789`, so constructors need no options on a local setup.
|
|
92
|
+
|
|
93
|
+
### Step 4. Split producer and worker
|
|
94
|
+
|
|
95
|
+
In production the producer and the worker are separate services, often in different languages. The producer is typically an API endpoint:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// api-service: adds jobs, no processing
|
|
99
|
+
import { Queue } from 'bunqueue-client';
|
|
100
|
+
const queue = new Queue('emails', { host: 'queue.internal', port: 6789 });
|
|
101
|
+
await queue.add('welcome', { to: 'user@example.com' }, { attempts: 3 });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The worker is a long running process:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// worker-service: processes jobs, no HTTP
|
|
108
|
+
import { Worker } from 'bunqueue-client';
|
|
109
|
+
new Worker('emails', sendEmail, { host: 'queue.internal', port: 6789, concurrency: 10 });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The [Python client](https://github.com/egeominotti/bunqueue/tree/main/sdk/python) speaks the same protocol against the same queue, so the worker can be a Python service instead.
|
|
113
|
+
|
|
114
|
+
### Step 5. Observe and operate
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
await queue.getJobCounts(); // { waiting, active, completed, failed, delayed, ... }
|
|
118
|
+
await queue.getDlq(); // jobs that exhausted their retries
|
|
119
|
+
await queue.retryDlq(); // send them back to the queue
|
|
120
|
+
await queue.getWorkers(); // connected workers
|
|
121
|
+
await queue.getStats(); // throughput and totals
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Or hit the HTTP side: `curl http://localhost:6790/health`.
|
|
125
|
+
|
|
126
|
+
### Step 6. Go to production
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const queue = new Queue('emails', {
|
|
130
|
+
host: 'queue.example.com',
|
|
131
|
+
port: 6789,
|
|
132
|
+
token: process.env.BUNQUEUE_TOKEN, // server started with AUTH_TOKENS=...
|
|
133
|
+
tls: true, // or { caFile: './ca.pem' }
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Checklist: set `AUTH_TOKENS` on the server, enable TLS (`TLS_CERT_FILE`/`TLS_KEY_FILE`), mount a volume for the SQLite data path, monitor `/health` and `/metrics`, and size worker `concurrency` to your workload. Full guide: [bunqueue.dev/guide/deployment](https://bunqueue.dev/guide/deployment/).
|
|
70
138
|
|
|
71
139
|
## Producing jobs
|
|
72
140
|
|
|
@@ -142,6 +210,47 @@ const node = await flow.add({
|
|
|
142
210
|
});
|
|
143
211
|
```
|
|
144
212
|
|
|
213
|
+
## Simple Mode
|
|
214
|
+
|
|
215
|
+
`Bunqueue` bundles a Queue and a Worker in one object, with routes, onion middleware, in process retry strategies, a circuit breaker, batch accumulation, event triggers, job TTL, priority aging, cooperative cancellation, and dedup or debounce defaults. It is a 1:1 port of the official client's Simple Mode, TCP only (the `embedded` option raises).
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { Bunqueue, type Job } from 'bunqueue-client';
|
|
219
|
+
|
|
220
|
+
const app = new Bunqueue('notifications', {
|
|
221
|
+
connection: { host: 'localhost', port: 6789 },
|
|
222
|
+
concurrency: 10,
|
|
223
|
+
routes: {
|
|
224
|
+
'send-email': async (job: Job<{ to: string }>) => ({ channel: 'email' }),
|
|
225
|
+
'send-sms': async (job: Job<{ to: string }>) => ({ channel: 'sms' }),
|
|
226
|
+
},
|
|
227
|
+
retry: { maxAttempts: 5, delay: 1000, strategy: 'jitter' },
|
|
228
|
+
circuitBreaker: { threshold: 5, resetTimeout: 30_000 },
|
|
229
|
+
ttl: { perName: { 'verify-otp': 60_000 } },
|
|
230
|
+
deduplication: { ttl: 5000 },
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
app.use(async (job, next) => {
|
|
234
|
+
const start = Date.now();
|
|
235
|
+
const result = await next();
|
|
236
|
+
console.log(`${job.name}: ${Date.now() - start}ms`);
|
|
237
|
+
return result;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
app.trigger({
|
|
241
|
+
on: 'send-email',
|
|
242
|
+
create: 'send-sms',
|
|
243
|
+
data: (result, job) => job.data,
|
|
244
|
+
condition: (result) => (result as { channel: string }).channel === 'email',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await app.cron('daily-digest', '0 9 * * *', { to: 'all' });
|
|
248
|
+
await app.add('send-email', { to: 'alice@example.com' });
|
|
249
|
+
// later: await app.close();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Use `processor` for a single handler, `routes` to dispatch by job name, or `batch` to accumulate N jobs into one call. Exactly one of the three is required.
|
|
253
|
+
|
|
145
254
|
## Scheduling
|
|
146
255
|
|
|
147
256
|
```typescript
|
|
@@ -173,6 +282,7 @@ Authentication uses server side tokens (`AUTH_TOKENS`). Transport security uses
|
|
|
173
282
|
| Dead letter queue | `getDlq`, `retryDlq`, `purgeDlq`, DLQ configuration |
|
|
174
283
|
| Administration | rate limiting, global concurrency, stall configuration, webhooks, stats, metrics, `listQueues`, `getWorkers` |
|
|
175
284
|
| Worker events | `ready`, `active`, `completed`, `failed`, `progress`, `drained`, `error`, `closed`, with automatic lock heartbeats so that jobs longer than the lock TTL survive |
|
|
285
|
+
| Simple Mode | `Bunqueue`: routes, middleware, in process retry (fixed, exponential, jitter, fibonacci, custom), circuit breaker, batch accumulation, triggers, TTL, priority aging, cancellation via `getSignal`, dedup and debounce defaults, cron shorthands |
|
|
176
286
|
|
|
177
287
|
The following features require the in process Bun runtime and are intentionally out of scope for this client: embedded mode, sandboxed workers, and `QueueEvents`. Use webhooks or the HTTP SSE and WebSocket endpoints for event streaming.
|
|
178
288
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunqueue Simple Mode — priority aging.
|
|
3
|
+
* Port of src/client/bunqueue/aging.ts: automatically boosts the priority of
|
|
4
|
+
* old waiting jobs. Same semantics; the job scan goes over TCP (GetJobs on
|
|
5
|
+
* the waiting and prioritized states) instead of the embedded manager.
|
|
6
|
+
*/
|
|
7
|
+
import type { Queue } from '../queue.js';
|
|
8
|
+
import type { PriorityAgingConfig } from './types.js';
|
|
9
|
+
export declare class PriorityAger<T = unknown> {
|
|
10
|
+
private timer;
|
|
11
|
+
private readonly config;
|
|
12
|
+
private readonly queue;
|
|
13
|
+
constructor(config: PriorityAgingConfig, queue: Queue<T>);
|
|
14
|
+
start(): void;
|
|
15
|
+
private tick;
|
|
16
|
+
destroy(): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunqueue Simple Mode — priority aging.
|
|
3
|
+
* Port of src/client/bunqueue/aging.ts: automatically boosts the priority of
|
|
4
|
+
* old waiting jobs. Same semantics; the job scan goes over TCP (GetJobs on
|
|
5
|
+
* the waiting and prioritized states) instead of the embedded manager.
|
|
6
|
+
*/
|
|
7
|
+
export class PriorityAger {
|
|
8
|
+
timer = null;
|
|
9
|
+
config;
|
|
10
|
+
queue;
|
|
11
|
+
constructor(config, queue) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.queue = queue;
|
|
14
|
+
}
|
|
15
|
+
start() {
|
|
16
|
+
const interval = this.config.interval ?? 60000;
|
|
17
|
+
this.timer = setInterval(() => {
|
|
18
|
+
void this.tick();
|
|
19
|
+
}, interval);
|
|
20
|
+
this.timer.unref?.();
|
|
21
|
+
}
|
|
22
|
+
async tick() {
|
|
23
|
+
const minAge = this.config.minAge ?? 60000;
|
|
24
|
+
const boost = this.config.boost ?? 1;
|
|
25
|
+
const maxPriority = this.config.maxPriority ?? 100;
|
|
26
|
+
const maxScan = this.config.maxScan ?? 100;
|
|
27
|
+
// Both waiting and prioritized jobs (priority > 0 means "prioritized")
|
|
28
|
+
const [waiting, prioritized] = await Promise.all([
|
|
29
|
+
this.queue.getJobs({ state: 'waiting', start: 0, end: maxScan }),
|
|
30
|
+
this.queue.getJobs({ state: 'prioritized', start: 0, end: maxScan }),
|
|
31
|
+
]);
|
|
32
|
+
const jobs = [...waiting, ...prioritized];
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
for (const job of jobs) {
|
|
35
|
+
const age = now - job.timestamp;
|
|
36
|
+
if (age >= minAge && job.priority < maxPriority) {
|
|
37
|
+
const newPriority = Math.min(job.priority + boost, maxPriority);
|
|
38
|
+
try {
|
|
39
|
+
await this.queue.changeJobPriority(job.id, { priority: newPriority });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Best-effort — the job may have been processed meanwhile
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
destroy() {
|
|
48
|
+
if (this.timer) {
|
|
49
|
+
clearInterval(this.timer);
|
|
50
|
+
this.timer = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunqueue Simple Mode — batch processing.
|
|
3
|
+
* 1:1 port of src/client/bunqueue/batch.ts: accumulates jobs and processes
|
|
4
|
+
* them in groups; flushes on size, on timeout, and on destroy (close).
|
|
5
|
+
*/
|
|
6
|
+
import type { Processor } from '../worker-types.js';
|
|
7
|
+
import type { BatchConfig } from './types.js';
|
|
8
|
+
export declare class BatchAccumulator<T = unknown, R = unknown> {
|
|
9
|
+
private readonly buffer;
|
|
10
|
+
private timer;
|
|
11
|
+
private readonly config;
|
|
12
|
+
constructor(config: BatchConfig<T, R>);
|
|
13
|
+
/** Build a Processor that buffers jobs into batches. */
|
|
14
|
+
buildProcessor(): Processor<T, R>;
|
|
15
|
+
flush(): void;
|
|
16
|
+
destroy(): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunqueue Simple Mode — batch processing.
|
|
3
|
+
* 1:1 port of src/client/bunqueue/batch.ts: accumulates jobs and processes
|
|
4
|
+
* them in groups; flushes on size, on timeout, and on destroy (close).
|
|
5
|
+
*/
|
|
6
|
+
export class BatchAccumulator {
|
|
7
|
+
buffer = [];
|
|
8
|
+
timer = null;
|
|
9
|
+
config;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
/** Build a Processor that buffers jobs into batches. */
|
|
14
|
+
buildProcessor() {
|
|
15
|
+
return (job) => {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
this.buffer.push({ job, resolve, reject });
|
|
18
|
+
if (this.buffer.length >= this.config.size) {
|
|
19
|
+
this.flush();
|
|
20
|
+
}
|
|
21
|
+
else if (!this.timer) {
|
|
22
|
+
const timeout = this.config.timeout ?? 5000;
|
|
23
|
+
this.timer = setTimeout(() => {
|
|
24
|
+
this.flush();
|
|
25
|
+
}, timeout);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
flush() {
|
|
31
|
+
if (this.timer) {
|
|
32
|
+
clearTimeout(this.timer);
|
|
33
|
+
this.timer = null;
|
|
34
|
+
}
|
|
35
|
+
const batch = this.buffer.splice(0);
|
|
36
|
+
if (batch.length === 0)
|
|
37
|
+
return;
|
|
38
|
+
const jobs = batch.map((b) => b.job);
|
|
39
|
+
this.config.processor(jobs).then((results) => {
|
|
40
|
+
for (let i = 0; i < batch.length; i++) {
|
|
41
|
+
batch[i].resolve(results[i] ?? undefined);
|
|
42
|
+
}
|
|
43
|
+
}, (err) => {
|
|
44
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
45
|
+
for (const b of batch) {
|
|
46
|
+
b.reject(error);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
destroy() {
|
|
51
|
+
if (this.timer) {
|
|
52
|
+
clearTimeout(this.timer);
|
|
53
|
+
this.timer = null;
|
|
54
|
+
}
|
|
55
|
+
// Flush remaining
|
|
56
|
+
if (this.buffer.length > 0) {
|
|
57
|
+
this.flush();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunqueue Simple Mode — delegation API surface (cron, cancellation, circuit
|
|
3
|
+
* breaker, TTL, DLQ, rate limit, events, control). Merged onto the Bunqueue
|
|
4
|
+
* prototype by bunqueue.ts; split out to keep files small.
|
|
5
|
+
*/
|
|
6
|
+
import type { Job } from '../job.js';
|
|
7
|
+
import type { JobOptions } from '../types.js';
|
|
8
|
+
import type { Bunqueue } from './bunqueue.js';
|
|
9
|
+
import type { DlqFilter, DlqStats } from './dlq-rate-limit.js';
|
|
10
|
+
import type { CircuitState, TriggerRule } from './types.js';
|
|
11
|
+
type Raw = Record<string, unknown>;
|
|
12
|
+
type Ctx = Bunqueue<any, any>;
|
|
13
|
+
export declare const bunqueueApi: {
|
|
14
|
+
cron(this: Ctx, id: string, pattern: string, data?: unknown, opts?: {
|
|
15
|
+
timezone?: string;
|
|
16
|
+
jobOpts?: JobOptions;
|
|
17
|
+
}): Promise<Raw | null>;
|
|
18
|
+
every(this: Ctx, id: string, intervalMs: number, data?: unknown, opts?: {
|
|
19
|
+
jobOpts?: JobOptions;
|
|
20
|
+
}): Promise<Raw | null>;
|
|
21
|
+
removeCron(this: Ctx, id: string): Promise<void>;
|
|
22
|
+
listCrons(this: Ctx): Promise<Raw[]>;
|
|
23
|
+
cancel(this: Ctx, jobId: string, gracePeriodMs?: number): void;
|
|
24
|
+
isCancelled(this: Ctx, jobId: string): boolean;
|
|
25
|
+
getSignal(this: Ctx, jobId: string): AbortSignal | null;
|
|
26
|
+
getCircuitState(this: Ctx): CircuitState;
|
|
27
|
+
resetCircuit(this: Ctx): void;
|
|
28
|
+
trigger(this: Ctx, rule: TriggerRule): Ctx;
|
|
29
|
+
setDefaultTtl(this: Ctx, ttlMs: number): void;
|
|
30
|
+
setNameTtl(this: Ctx, name: string, ttlMs: number): void;
|
|
31
|
+
setDlqConfig(this: Ctx, config: Raw): Promise<void>;
|
|
32
|
+
getDlqConfig(this: Ctx): Promise<Raw>;
|
|
33
|
+
getDlq(this: Ctx, filter?: DlqFilter): Promise<Raw[]>;
|
|
34
|
+
getDlqStats(this: Ctx): Promise<DlqStats>;
|
|
35
|
+
retryDlq(this: Ctx, id?: string): Promise<number>;
|
|
36
|
+
purgeDlq(this: Ctx): Promise<number>;
|
|
37
|
+
setGlobalRateLimit(this: Ctx, max: number, duration?: number): Promise<void>;
|
|
38
|
+
removeGlobalRateLimit(this: Ctx): Promise<void>;
|
|
39
|
+
on(this: Ctx, event: string, listener: (...args: never[]) => void): Ctx;
|
|
40
|
+
once(this: Ctx, event: string, listener: (...args: never[]) => void): Ctx;
|
|
41
|
+
off(this: Ctx, event: string, listener: (...args: never[]) => void): Ctx;
|
|
42
|
+
pause(this: Ctx): void;
|
|
43
|
+
resume(this: Ctx): void;
|
|
44
|
+
close(this: Ctx, force?: boolean): Promise<void>;
|
|
45
|
+
isRunning(this: Ctx): boolean;
|
|
46
|
+
isPaused(this: Ctx): boolean;
|
|
47
|
+
isClosed(this: Ctx): boolean;
|
|
48
|
+
};
|
|
49
|
+
/** Declaration-merged API installed on the Bunqueue prototype. */
|
|
50
|
+
export interface BunqueueApi<T = unknown, R = unknown> {
|
|
51
|
+
cron(id: string, pattern: string, data?: T, opts?: {
|
|
52
|
+
timezone?: string;
|
|
53
|
+
jobOpts?: JobOptions;
|
|
54
|
+
}): Promise<Raw | null>;
|
|
55
|
+
every(id: string, intervalMs: number, data?: T, opts?: {
|
|
56
|
+
jobOpts?: JobOptions;
|
|
57
|
+
}): Promise<Raw | null>;
|
|
58
|
+
removeCron(id: string): Promise<void>;
|
|
59
|
+
listCrons(): Promise<Raw[]>;
|
|
60
|
+
cancel(jobId: string, gracePeriodMs?: number): void;
|
|
61
|
+
isCancelled(jobId: string): boolean;
|
|
62
|
+
getSignal(jobId: string): AbortSignal | null;
|
|
63
|
+
getCircuitState(): CircuitState;
|
|
64
|
+
resetCircuit(): void;
|
|
65
|
+
trigger(rule: TriggerRule<T>): Bunqueue<T, R>;
|
|
66
|
+
setDefaultTtl(ttlMs: number): void;
|
|
67
|
+
setNameTtl(name: string, ttlMs: number): void;
|
|
68
|
+
setDlqConfig(config: Raw): Promise<void>;
|
|
69
|
+
getDlqConfig(): Promise<Raw>;
|
|
70
|
+
getDlq(filter?: DlqFilter): Promise<Raw[]>;
|
|
71
|
+
getDlqStats(): Promise<DlqStats>;
|
|
72
|
+
retryDlq(id?: string): Promise<number>;
|
|
73
|
+
purgeDlq(): Promise<number>;
|
|
74
|
+
setGlobalRateLimit(max: number, duration?: number): Promise<void>;
|
|
75
|
+
removeGlobalRateLimit(): Promise<void>;
|
|
76
|
+
on(event: string, listener: (...args: never[]) => void): Bunqueue<T, R>;
|
|
77
|
+
once(event: string, listener: (...args: never[]) => void): Bunqueue<T, R>;
|
|
78
|
+
off(event: string, listener: (...args: never[]) => void): Bunqueue<T, R>;
|
|
79
|
+
pause(): void;
|
|
80
|
+
resume(): void;
|
|
81
|
+
close(force?: boolean): Promise<void>;
|
|
82
|
+
isRunning(): boolean;
|
|
83
|
+
isPaused(): boolean;
|
|
84
|
+
isClosed(): boolean;
|
|
85
|
+
getJob(id: string): Promise<Job<T> | null>;
|
|
86
|
+
}
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunqueue Simple Mode — delegation API surface (cron, cancellation, circuit
|
|
3
|
+
* breaker, TTL, DLQ, rate limit, events, control). Merged onto the Bunqueue
|
|
4
|
+
* prototype by bunqueue.ts; split out to keep files small.
|
|
5
|
+
*/
|
|
6
|
+
export const bunqueueApi = {
|
|
7
|
+
// --------------------------------------------------------------------- cron
|
|
8
|
+
async cron(id, pattern, data, opts) {
|
|
9
|
+
await this.queue.upsertJobScheduler(id, { pattern, tz: opts?.timezone }, { name: id, data, opts: opts?.jobOpts });
|
|
10
|
+
return this.queue.getJobScheduler(id);
|
|
11
|
+
},
|
|
12
|
+
async every(id, intervalMs, data, opts) {
|
|
13
|
+
await this.queue.upsertJobScheduler(id, { every: intervalMs }, { name: id, data, opts: opts?.jobOpts });
|
|
14
|
+
return this.queue.getJobScheduler(id);
|
|
15
|
+
},
|
|
16
|
+
removeCron(id) {
|
|
17
|
+
return this.queue.removeJobScheduler(id);
|
|
18
|
+
},
|
|
19
|
+
listCrons() {
|
|
20
|
+
return this.queue.getJobSchedulers();
|
|
21
|
+
},
|
|
22
|
+
// ------------------------------------------------------------- cancellation
|
|
23
|
+
cancel(jobId, gracePeriodMs = 0) {
|
|
24
|
+
this.cancellation.cancel(jobId, gracePeriodMs);
|
|
25
|
+
},
|
|
26
|
+
isCancelled(jobId) {
|
|
27
|
+
return this.cancellation.isCancelled(jobId);
|
|
28
|
+
},
|
|
29
|
+
getSignal(jobId) {
|
|
30
|
+
return this.cancellation.getSignal(jobId);
|
|
31
|
+
},
|
|
32
|
+
// ---------------------------------------------------------- circuit breaker
|
|
33
|
+
getCircuitState() {
|
|
34
|
+
return this.cb?.currentState ?? 'closed';
|
|
35
|
+
},
|
|
36
|
+
resetCircuit() {
|
|
37
|
+
this.cb?.reset();
|
|
38
|
+
},
|
|
39
|
+
// ----------------------------------------------------------------- triggers
|
|
40
|
+
trigger(rule) {
|
|
41
|
+
this.triggerMgr.add(rule);
|
|
42
|
+
return this;
|
|
43
|
+
},
|
|
44
|
+
// ---------------------------------------------------------------------- ttl
|
|
45
|
+
setDefaultTtl(ttlMs) {
|
|
46
|
+
this.ttlChecker?.setDefaultTtl(ttlMs);
|
|
47
|
+
},
|
|
48
|
+
setNameTtl(name, ttlMs) {
|
|
49
|
+
this.ttlChecker?.setNameTtl(name, ttlMs);
|
|
50
|
+
},
|
|
51
|
+
// ---------------------------------------------------------------------- dlq
|
|
52
|
+
setDlqConfig(config) {
|
|
53
|
+
return this.dlqrl.setDlqConfig(config);
|
|
54
|
+
},
|
|
55
|
+
getDlqConfig() {
|
|
56
|
+
return this.dlqrl.getDlqConfig();
|
|
57
|
+
},
|
|
58
|
+
getDlq(filter) {
|
|
59
|
+
return this.dlqrl.getDlq(filter);
|
|
60
|
+
},
|
|
61
|
+
getDlqStats() {
|
|
62
|
+
return this.dlqrl.getDlqStats();
|
|
63
|
+
},
|
|
64
|
+
retryDlq(id) {
|
|
65
|
+
return this.dlqrl.retryDlq(id);
|
|
66
|
+
},
|
|
67
|
+
purgeDlq() {
|
|
68
|
+
return this.dlqrl.purgeDlq();
|
|
69
|
+
},
|
|
70
|
+
// ------------------------------------------------------------ rate limiting
|
|
71
|
+
setGlobalRateLimit(max, duration) {
|
|
72
|
+
return this.dlqrl.setGlobalRateLimit(max, duration);
|
|
73
|
+
},
|
|
74
|
+
removeGlobalRateLimit() {
|
|
75
|
+
return this.dlqrl.removeGlobalRateLimit();
|
|
76
|
+
},
|
|
77
|
+
// ------------------------------------------------------------------- events
|
|
78
|
+
on(event, listener) {
|
|
79
|
+
this.worker.on(event, listener);
|
|
80
|
+
return this;
|
|
81
|
+
},
|
|
82
|
+
once(event, listener) {
|
|
83
|
+
this.worker.once(event, listener);
|
|
84
|
+
return this;
|
|
85
|
+
},
|
|
86
|
+
off(event, listener) {
|
|
87
|
+
this.worker.off(event, listener);
|
|
88
|
+
return this;
|
|
89
|
+
},
|
|
90
|
+
// ------------------------------------------------------------------ control
|
|
91
|
+
pause() {
|
|
92
|
+
void this.queue.pause().catch(() => { });
|
|
93
|
+
this.worker.pause();
|
|
94
|
+
},
|
|
95
|
+
resume() {
|
|
96
|
+
void this.queue.resume().catch(() => { });
|
|
97
|
+
this.worker.resume();
|
|
98
|
+
},
|
|
99
|
+
async close(force = false) {
|
|
100
|
+
this.ager?.destroy();
|
|
101
|
+
this.cb?.destroy();
|
|
102
|
+
this.batchAcc?.destroy();
|
|
103
|
+
this.cancellation.destroyAll();
|
|
104
|
+
await this.worker.close(force);
|
|
105
|
+
this.queue.close();
|
|
106
|
+
},
|
|
107
|
+
isRunning() {
|
|
108
|
+
return this.worker.isRunning();
|
|
109
|
+
},
|
|
110
|
+
isPaused() {
|
|
111
|
+
return this.worker.isPaused();
|
|
112
|
+
},
|
|
113
|
+
isClosed() {
|
|
114
|
+
return this.worker.isClosed();
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunqueue — simplified all-in-one Queue + Worker (Simple Mode).
|
|
3
|
+
* 1:1 port of src/client/bunqueue.ts from the official client, TCP mode only
|
|
4
|
+
* (the embedded mode requires the in-process Bun runtime). The delegation
|
|
5
|
+
* API (cron, DLQ, events, control, …) lives in bunqueue-api.ts and is merged
|
|
6
|
+
* onto the prototype below.
|
|
7
|
+
*/
|
|
8
|
+
import type { Job } from '../job.js';
|
|
9
|
+
import { Queue } from '../queue.js';
|
|
10
|
+
import type { JobOptions } from '../types.js';
|
|
11
|
+
import { Worker } from '../worker.js';
|
|
12
|
+
import { PriorityAger } from './aging.js';
|
|
13
|
+
import { BatchAccumulator } from './batch.js';
|
|
14
|
+
import { type BunqueueApi } from './bunqueue-api.js';
|
|
15
|
+
import { CancellationManager } from './cancellation.js';
|
|
16
|
+
import { WorkerCircuitBreaker } from './circuit-breaker.js';
|
|
17
|
+
import { DlqRateLimitManager } from './dlq-rate-limit.js';
|
|
18
|
+
import { TriggerManager } from './triggers.js';
|
|
19
|
+
import { TtlChecker } from './ttl.js';
|
|
20
|
+
import type { BunqueueMiddleware, BunqueueOptions } from './types.js';
|
|
21
|
+
export declare class Bunqueue<T = unknown, R = unknown> {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly queue: Queue<T>;
|
|
24
|
+
readonly worker: Worker<T, R>;
|
|
25
|
+
/** @internal */ readonly cancellation: CancellationManager;
|
|
26
|
+
/** @internal */ readonly cb: WorkerCircuitBreaker | null;
|
|
27
|
+
/** @internal */ readonly triggerMgr: TriggerManager<T, R>;
|
|
28
|
+
/** @internal */ readonly ager: PriorityAger<T> | null;
|
|
29
|
+
/** @internal */ readonly ttlChecker: TtlChecker | null;
|
|
30
|
+
/** @internal */ readonly batchAcc: BatchAccumulator<T, R> | null;
|
|
31
|
+
/** @internal */ readonly dlqrl: DlqRateLimitManager<T>;
|
|
32
|
+
private readonly middlewares;
|
|
33
|
+
private readonly baseProcessor;
|
|
34
|
+
private readonly retryConfig;
|
|
35
|
+
private readonly merger;
|
|
36
|
+
private readonly rateGate;
|
|
37
|
+
private readonly defaultJobOptions;
|
|
38
|
+
constructor(name: string, opts: BunqueueOptions<T, R>);
|
|
39
|
+
private buildRouteProcessor;
|
|
40
|
+
private buildDefaultJobOptions;
|
|
41
|
+
private processJob;
|
|
42
|
+
private runMiddlewareChain;
|
|
43
|
+
use(middleware: BunqueueMiddleware<T, R>): this;
|
|
44
|
+
add(name: string, data: T, opts?: JobOptions): Promise<Job<T>>;
|
|
45
|
+
addBulk(jobs: Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
data: T;
|
|
48
|
+
opts?: JobOptions;
|
|
49
|
+
}>): Promise<Job<T>[]>;
|
|
50
|
+
getJob(id: string): Promise<Job<T> | null>;
|
|
51
|
+
getJobCounts(): Promise<import("../types.js").JobCounts>;
|
|
52
|
+
getJobCountsAsync(): Promise<import("../types.js").JobCounts>;
|
|
53
|
+
count(): Promise<number>;
|
|
54
|
+
countAsync(): Promise<number>;
|
|
55
|
+
}
|
|
56
|
+
export interface Bunqueue<T = unknown, R = unknown> extends BunqueueApi<T, R> {
|
|
57
|
+
}
|