bunqueue-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/dist/connection-types.d.ts +24 -0
  4. package/dist/connection-types.js +2 -0
  5. package/dist/connection.d.ts +44 -0
  6. package/dist/connection.js +229 -0
  7. package/dist/errors.d.ts +22 -0
  8. package/dist/errors.js +25 -0
  9. package/dist/flow-types.d.ts +34 -0
  10. package/dist/flow-types.js +2 -0
  11. package/dist/flow.d.ts +31 -0
  12. package/dist/flow.js +186 -0
  13. package/dist/frame.d.ts +16 -0
  14. package/dist/frame.js +57 -0
  15. package/dist/index.d.ts +19 -0
  16. package/dist/index.js +12 -0
  17. package/dist/job.d.ts +56 -0
  18. package/dist/job.js +151 -0
  19. package/dist/queue-admin.d.ts +63 -0
  20. package/dist/queue-admin.js +143 -0
  21. package/dist/queue-control.d.ts +44 -0
  22. package/dist/queue-control.js +85 -0
  23. package/dist/queue-query.d.ts +58 -0
  24. package/dist/queue-query.js +165 -0
  25. package/dist/queue.d.ts +50 -0
  26. package/dist/queue.js +77 -0
  27. package/dist/types.d.ts +74 -0
  28. package/dist/types.js +50 -0
  29. package/dist/worker-base.d.ts +51 -0
  30. package/dist/worker-base.js +122 -0
  31. package/dist/worker-types.d.ts +27 -0
  32. package/dist/worker-types.js +5 -0
  33. package/dist/worker.d.ts +20 -0
  34. package/dist/worker.js +151 -0
  35. package/package.json +64 -0
  36. package/src/connection-types.ts +21 -0
  37. package/src/connection.ts +242 -0
  38. package/src/errors.ts +26 -0
  39. package/src/flow-types.ts +40 -0
  40. package/src/flow.ts +236 -0
  41. package/src/frame.ts +62 -0
  42. package/src/index.ts +41 -0
  43. package/src/job.ts +194 -0
  44. package/src/queue-admin.ts +213 -0
  45. package/src/queue-control.ts +145 -0
  46. package/src/queue-query.ts +220 -0
  47. package/src/queue.ts +112 -0
  48. package/src/types.ts +133 -0
  49. package/src/worker-base.ts +132 -0
  50. package/src/worker-types.ts +32 -0
  51. package/src/worker.ts +174 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Egeo Minotti
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # bunqueue-client — TypeScript SDK (Node / Bun / Deno)
2
+
3
+ Cross-runtime TypeScript client for [bunqueue](https://github.com/egeominotti/bunqueue),
4
+ the high-performance job queue server. Talks the native TCP protocol
5
+ (msgpack, pipelined, port 6789) — feature parity with the built-in Bun client,
6
+ but runs on **any** modern JS/TS runtime.
7
+
8
+ The bunqueue **server** runs on Bun (binary or Docker). This SDK lets any
9
+ Node, Bun, or Deno service produce and consume jobs on it: *one queue, any
10
+ language, any runtime*.
11
+
12
+ ## Runtime support
13
+
14
+ | Runtime | Status | Notes |
15
+ |---|---|---|
16
+ | Node.js ≥ 20 | ✅ tested (58/58 e2e + 8/8 integration) | ESM; TS files run directly on Node ≥ 22 via `--experimental-strip-types` |
17
+ | Bun | ✅ tested (58/58 e2e + 8/8 integration) | works out of the box |
18
+ | Deno ≥ 2 | ✅ tested (58/58 e2e + 8/8 integration) | `node:` builtins + npm `msgpackr` |
19
+ | tsx / ts-node / vitest / jest | ✅ | they run on Node underneath |
20
+ | Cloudflare Workers | ✅ tested (11/11 e2e inside workerd) | needs `nodejs_compat` flag; no long-lived worker loops (request-scoped runtime) — consume via Cron Triggers / Durable Object alarms with batch pulls (covered by the suite); TLS requires a publicly trusted cert |
21
+ | Browser | ❌ | no raw TCP sockets — use the server's HTTP API instead |
22
+
23
+ The rule that makes this possible: the SDK uses **only `node:*` builtins**
24
+ (`net`, `tls`, `events`, `crypto`, `os`) — no `Bun.*` globals, no
25
+ `bun:`/`deno:` imports. Single runtime dependency: `msgpackr`.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install bunqueue-client # or: bun add / pnpm add / deno add npm:bunqueue-client
31
+ ```
32
+
33
+ ## Producer
34
+
35
+ ```typescript
36
+ import { Queue } from 'bunqueue-client';
37
+
38
+ const queue = new Queue('emails', { host: 'localhost', port: 6789 });
39
+
40
+ await queue.add('send', { to: 'user@example.com' }, { priority: 5, attempts: 3 });
41
+ await queue.addBulk([
42
+ { name: 'send', data: { to: 'a@b.c' } },
43
+ { name: 'send', data: { to: 'x@y.z' }, opts: { delay: 5000 } },
44
+ ]);
45
+
46
+ const counts = await queue.getJobCounts();
47
+ queue.close();
48
+ ```
49
+
50
+ ## Worker
51
+
52
+ ```typescript
53
+ import { Worker, UnrecoverableError } from 'bunqueue-client';
54
+
55
+ const worker = new Worker(
56
+ 'emails',
57
+ async (job) => {
58
+ await job.updateProgress(50);
59
+ if (job.data.invalid) throw new UnrecoverableError('bad payload'); // no retries → DLQ
60
+ return { sent: true };
61
+ },
62
+ { host: 'localhost', port: 6789, concurrency: 10 }
63
+ );
64
+
65
+ worker.on('completed', (job, result) => console.log(job.id, result));
66
+ worker.on('failed', (job, err) => console.error(job.id, err.message));
67
+
68
+ // later: await worker.close(); // graceful — waits for in-flight jobs
69
+ ```
70
+
71
+ Retry, backoff, DLQ, stall detection, priorities and rate limiting all run
72
+ **server-side** — the worker only pulls, heartbeats and acks.
73
+
74
+ ## Flows
75
+
76
+ ```typescript
77
+ import { FlowProducer } from 'bunqueue-client';
78
+
79
+ const flow = new FlowProducer({ host: 'localhost', port: 6789 });
80
+
81
+ // sequential chain: step1 → step2 → step3
82
+ await flow.addChain([
83
+ { name: 'step1', queueName: 'pipeline' },
84
+ { name: 'step2', queueName: 'pipeline' },
85
+ { name: 'step3', queueName: 'pipeline' },
86
+ ]);
87
+
88
+ // fan-in: parallel jobs converge into a final job that reads their results
89
+ const { finalId } = await flow.addBulkThen(
90
+ [
91
+ { name: 'part1', queueName: 'pipeline' },
92
+ { name: 'part2', queueName: 'pipeline' },
93
+ ],
94
+ { name: 'merge', queueName: 'pipeline' }
95
+ );
96
+ // inside the 'merge' processor: await job.getChildrenValues()
97
+
98
+ // parent/child tree (children run BEFORE the parent)
99
+ const node = await flow.add({
100
+ name: 'assemble', queueName: 'orders',
101
+ children: [
102
+ { name: 'reserve-stock', queueName: 'orders' },
103
+ { name: 'charge-card', queueName: 'orders' },
104
+ ],
105
+ });
106
+ ```
107
+
108
+ ## Schedulers (cron)
109
+
110
+ ```typescript
111
+ await queue.addCron('daily-report', '0 9 * * *', { type: 'report' });
112
+ await queue.every('health-ping', 30_000, { type: 'ping' });
113
+ await queue.removeJobScheduler('daily-report');
114
+ ```
115
+
116
+ ## TLS + Auth
117
+
118
+ ```typescript
119
+ const queue = new Queue('emails', {
120
+ host: 'queue.example.com',
121
+ port: 6789,
122
+ token: process.env.BUNQUEUE_TOKEN,
123
+ tls: { caFile: './ca.pem' }, // or `true` for system CAs
124
+ });
125
+ ```
126
+
127
+ ## Feature surface
128
+
129
+ - **Queue** — add/addBulk with full `JobOptions` (priority, delay, attempts,
130
+ backoff, ttl, timeout, jobId, deduplication, dependsOn, tags, groupId, lifo,
131
+ removeOnComplete/Fail, durable, repeat, debounce, …)
132
+ - **Query** — getJob, getJobByCustomId, getJobs + per-state helpers, state,
133
+ result, progress, waitForJob, counts (+ per priority), children values, logs
134
+ - **Control** — pause/resume/drain/obliterate/clean, remove, discard, promote,
135
+ retryJob/retryJobs, move to wait/delayed, change priority/delay, update
136
+ data, extend lock
137
+ - **DLQ** — getDlq, retryDlq, purgeDlq, DLQ config
138
+ - **Admin** — rate limit, global concurrency, stall config, webhooks,
139
+ stats/metrics/listQueues/getWorkers
140
+ - **Worker events** — `ready`, `active`, `completed`, `failed`, `progress`,
141
+ `drained`, `error`, `closed`; automatic lock heartbeats (jobs longer than
142
+ the lock TTL survive)
143
+
144
+ Not applicable outside Bun (by design): embedded mode, sandboxed workers,
145
+ `QueueEvents` (in-process subscription — use webhooks or the HTTP SSE/WS
146
+ endpoints instead).
147
+
148
+ ## Development
149
+
150
+ ```bash
151
+ bun install
152
+ bun run build # tsc → dist/
153
+ bun run check # biome check
154
+
155
+ # Test suites (each spawns a real bunqueue server from the repo root)
156
+ bun tests/integration.ts # smoke
157
+ bun tests/e2e.ts # full surface + edge cases + realistic load
158
+ node --experimental-strip-types tests/e2e.ts # same file on Node ≥22
159
+ deno run -A tests/e2e.ts # same file on Deno ≥2
160
+ bun run test:workers # full suite INSIDE workerd (Cloudflare Workers)
161
+ ```
162
+
163
+ Style rules: Biome, max 250 lines per file, relative imports with explicit
164
+ `.js` extension (NodeNext resolution — required for Node ESM). See `CLAUDE.md`
165
+ for the full development guide and wire-protocol gotchas.
@@ -0,0 +1,24 @@
1
+ /** Connection option and message types. */
2
+ export type TlsOption = boolean | {
3
+ caFile?: string;
4
+ rejectUnauthorized?: boolean;
5
+ } | undefined;
6
+ export interface ConnectionOptions {
7
+ host?: string;
8
+ port?: number;
9
+ token?: string;
10
+ tls?: TlsOption;
11
+ connectTimeoutMs?: number;
12
+ commandTimeoutMs?: number;
13
+ }
14
+ export type Command = Record<string, unknown> & {
15
+ cmd: string;
16
+ };
17
+ export type Response = Record<string, unknown> & {
18
+ ok: boolean;
19
+ };
20
+ export interface Pending {
21
+ resolve: (response: Response) => void;
22
+ reject: (error: Error) => void;
23
+ timer: ReturnType<typeof setTimeout>;
24
+ }
@@ -0,0 +1,2 @@
1
+ /** Connection option and message types. */
2
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * TCP connection to a bunqueue server — cross-runtime (Node.js, Bun, Deno).
3
+ *
4
+ * Requests carry a `reqId` string; the server echoes it back, enabling
5
+ * pipelining (many in-flight commands per socket). Uses only `node:`
6
+ * builtins (net/tls), which Node, Bun and Deno all support.
7
+ */
8
+ import type { Command, ConnectionOptions, Response, TlsOption } from './connection-types.js';
9
+ export type { Command, ConnectionOptions, Response, TlsOption } from './connection-types.js';
10
+ /** A single pipelined TCP connection to a bunqueue server. */
11
+ export declare class Connection {
12
+ readonly host: string;
13
+ readonly port: number;
14
+ readonly token: string | undefined;
15
+ readonly tls: TlsOption;
16
+ readonly connectTimeoutMs: number;
17
+ readonly commandTimeoutMs: number;
18
+ private socket;
19
+ private connected;
20
+ private closed;
21
+ private connecting;
22
+ private pending;
23
+ private reqCounter;
24
+ private parser;
25
+ constructor(options?: ConnectionOptions);
26
+ get isConnected(): boolean;
27
+ /** Open the socket (and authenticate) if not already connected. */
28
+ connect(): Promise<void>;
29
+ private doConnect;
30
+ private openSocket;
31
+ /**
32
+ * Send a command and await its response. Rejects with CommandError when
33
+ * the server answers ok=false. Reconnects lazily if the link was lost.
34
+ */
35
+ call(command: Command, timeoutMs?: number): Promise<Response>;
36
+ /** Ping the server; returns true when it answers pong. */
37
+ ping(): Promise<boolean>;
38
+ /** Protocol negotiation; returns server name/version/protocolVersion. */
39
+ hello(): Promise<Response>;
40
+ /** Close permanently; in-flight commands reject. */
41
+ close(): void;
42
+ private handleData;
43
+ private teardown;
44
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * TCP connection to a bunqueue server — cross-runtime (Node.js, Bun, Deno).
3
+ *
4
+ * Requests carry a `reqId` string; the server echoes it back, enabling
5
+ * pipelining (many in-flight commands per socket). Uses only `node:`
6
+ * builtins (net/tls), which Node, Bun and Deno all support.
7
+ */
8
+ import { readFileSync } from 'node:fs';
9
+ import { connect as netConnect } from 'node:net';
10
+ import { connect as tlsConnect } from 'node:tls';
11
+ import { pack, unpack } from 'msgpackr';
12
+ import { AuthError, CommandError, CommandTimeoutError, ConnectionClosedError } from './errors.js';
13
+ import { compact, FrameParser, frame, PROTOCOL_VERSION } from './frame.js';
14
+ /** A single pipelined TCP connection to a bunqueue server. */
15
+ export class Connection {
16
+ host;
17
+ port;
18
+ token;
19
+ tls;
20
+ connectTimeoutMs;
21
+ commandTimeoutMs;
22
+ socket = null;
23
+ connected = false;
24
+ closed = false;
25
+ connecting = null;
26
+ pending = new Map();
27
+ reqCounter = 0;
28
+ parser = new FrameParser();
29
+ constructor(options = {}) {
30
+ this.host = options.host ?? 'localhost';
31
+ this.port = options.port ?? 6789;
32
+ this.token = options.token;
33
+ this.tls = options.tls;
34
+ this.connectTimeoutMs = options.connectTimeoutMs ?? 5000;
35
+ this.commandTimeoutMs = options.commandTimeoutMs ?? 10_000;
36
+ }
37
+ get isConnected() {
38
+ return this.connected;
39
+ }
40
+ /** Open the socket (and authenticate) if not already connected. */
41
+ async connect() {
42
+ if (this.connected)
43
+ return;
44
+ if (this.closed)
45
+ throw new ConnectionClosedError('connection closed by client');
46
+ if (this.connecting)
47
+ return this.connecting;
48
+ this.connecting = this.doConnect().finally(() => {
49
+ this.connecting = null;
50
+ });
51
+ return this.connecting;
52
+ }
53
+ async doConnect() {
54
+ const socket = await this.openSocket();
55
+ socket.setNoDelay(true);
56
+ this.parser.clear();
57
+ this.socket = socket;
58
+ socket.on('data', (chunk) => this.handleData(chunk));
59
+ socket.on('error', () => this.teardown());
60
+ socket.on('close', () => this.teardown());
61
+ this.connected = true;
62
+ if (this.token) {
63
+ try {
64
+ await this.call({ cmd: 'Auth', token: this.token });
65
+ }
66
+ catch (err) {
67
+ this.teardown();
68
+ if (err instanceof CommandError)
69
+ throw new AuthError(err.message);
70
+ throw err;
71
+ }
72
+ }
73
+ }
74
+ openSocket() {
75
+ return new Promise((resolve, reject) => {
76
+ let settled = false;
77
+ const timer = setTimeout(() => {
78
+ if (settled)
79
+ return;
80
+ settled = true;
81
+ socket.destroy();
82
+ reject(new ConnectionClosedError(`connect timeout to ${this.host}:${this.port}`));
83
+ }, this.connectTimeoutMs);
84
+ const onError = (err) => {
85
+ if (settled)
86
+ return;
87
+ settled = true;
88
+ clearTimeout(timer);
89
+ reject(new ConnectionClosedError(`connect failed: ${err.message}`));
90
+ };
91
+ const onReady = () => {
92
+ if (settled)
93
+ return;
94
+ settled = true;
95
+ clearTimeout(timer);
96
+ socket.off('error', onError);
97
+ resolve(socket);
98
+ };
99
+ let socket;
100
+ if (this.tls) {
101
+ const tlsOpts = { host: this.host, port: this.port };
102
+ if (typeof this.tls === 'object') {
103
+ if (this.tls.caFile)
104
+ tlsOpts.ca = readFileSync(this.tls.caFile);
105
+ if (this.tls.rejectUnauthorized === false)
106
+ tlsOpts.rejectUnauthorized = false;
107
+ }
108
+ socket = tlsConnect(tlsOpts, onReady);
109
+ }
110
+ else {
111
+ socket = netConnect({ host: this.host, port: this.port }, onReady);
112
+ }
113
+ socket.on('error', onError);
114
+ });
115
+ }
116
+ /**
117
+ * Send a command and await its response. Rejects with CommandError when
118
+ * the server answers ok=false. Reconnects lazily if the link was lost.
119
+ */
120
+ async call(command, timeoutMs) {
121
+ if (!this.connected)
122
+ await this.connect();
123
+ const socket = this.socket;
124
+ if (!socket)
125
+ throw new ConnectionClosedError('not connected');
126
+ this.reqCounter = (this.reqCounter + 1) & 0x7fffffff;
127
+ const reqId = String(this.reqCounter);
128
+ const payload = pack({ ...compact(command), reqId });
129
+ return new Promise((resolve, reject) => {
130
+ const timer = setTimeout(() => {
131
+ this.pending.delete(reqId);
132
+ reject(new CommandTimeoutError(`no response for ${command.cmd} within timeout`));
133
+ }, timeoutMs ?? this.commandTimeoutMs);
134
+ this.pending.set(reqId, {
135
+ resolve: (response) => {
136
+ clearTimeout(timer);
137
+ if (!response.ok) {
138
+ reject(new CommandError(String(response.error ?? 'unknown server error')));
139
+ }
140
+ else {
141
+ resolve(response);
142
+ }
143
+ },
144
+ reject: (err) => {
145
+ clearTimeout(timer);
146
+ reject(err);
147
+ },
148
+ timer,
149
+ });
150
+ socket.write(frame(payload), (err) => {
151
+ if (err) {
152
+ const entry = this.pending.get(reqId);
153
+ this.pending.delete(reqId);
154
+ entry?.reject(new ConnectionClosedError(`send failed: ${err.message}`));
155
+ this.teardown();
156
+ }
157
+ });
158
+ });
159
+ }
160
+ /** Ping the server; returns true when it answers pong. */
161
+ async ping() {
162
+ try {
163
+ const response = await this.call({ cmd: 'Ping' });
164
+ const data = response.data;
165
+ return data?.pong === true;
166
+ }
167
+ catch {
168
+ return false;
169
+ }
170
+ }
171
+ /** Protocol negotiation; returns server name/version/protocolVersion. */
172
+ hello() {
173
+ return this.call({
174
+ cmd: 'Hello',
175
+ protocolVersion: PROTOCOL_VERSION,
176
+ capabilities: ['pipelining'],
177
+ });
178
+ }
179
+ /** Close permanently; in-flight commands reject. */
180
+ close() {
181
+ this.closed = true;
182
+ this.teardown();
183
+ }
184
+ handleData(chunk) {
185
+ let frames;
186
+ try {
187
+ frames = this.parser.addData(chunk);
188
+ }
189
+ catch {
190
+ this.teardown();
191
+ return;
192
+ }
193
+ for (const framePayload of frames) {
194
+ let message;
195
+ try {
196
+ message = unpack(framePayload);
197
+ }
198
+ catch {
199
+ continue; // skip unparseable frame; a desynced stream dies via socket error
200
+ }
201
+ if (typeof message !== 'object' || message === null)
202
+ continue;
203
+ const response = message;
204
+ const reqId = response.reqId;
205
+ if (reqId === undefined || reqId === null)
206
+ continue; // server-push unsupported
207
+ const entry = this.pending.get(String(reqId));
208
+ if (entry) {
209
+ this.pending.delete(String(reqId));
210
+ entry.resolve(response);
211
+ }
212
+ }
213
+ }
214
+ teardown() {
215
+ this.connected = false;
216
+ const socket = this.socket;
217
+ this.socket = null;
218
+ if (socket) {
219
+ socket.removeAllListeners();
220
+ socket.destroy();
221
+ }
222
+ const pending = this.pending;
223
+ this.pending = new Map();
224
+ for (const entry of pending.values()) {
225
+ clearTimeout(entry.timer);
226
+ entry.reject(new ConnectionClosedError('connection lost'));
227
+ }
228
+ }
229
+ }
@@ -0,0 +1,22 @@
1
+ /** Exception hierarchy for the bunqueue cross-runtime SDK. */
2
+ export declare class BunqueueError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /** The TCP connection is closed or was lost mid-command. */
6
+ export declare class ConnectionClosedError extends BunqueueError {
7
+ }
8
+ /** No response received for a command within the timeout. */
9
+ export declare class CommandTimeoutError extends BunqueueError {
10
+ }
11
+ /** The server answered ok=false; message carries the server error. */
12
+ export declare class CommandError extends BunqueueError {
13
+ }
14
+ /** Authentication with the server failed. */
15
+ export declare class AuthError extends BunqueueError {
16
+ }
17
+ /**
18
+ * Throw inside a Worker processor to fail the job terminally: skips all
19
+ * remaining retry attempts and sends the job straight to the DLQ.
20
+ */
21
+ export declare class UnrecoverableError extends BunqueueError {
22
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ /** Exception hierarchy for the bunqueue cross-runtime SDK. */
2
+ export class BunqueueError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = new.target.name;
6
+ }
7
+ }
8
+ /** The TCP connection is closed or was lost mid-command. */
9
+ export class ConnectionClosedError extends BunqueueError {
10
+ }
11
+ /** No response received for a command within the timeout. */
12
+ export class CommandTimeoutError extends BunqueueError {
13
+ }
14
+ /** The server answered ok=false; message carries the server error. */
15
+ export class CommandError extends BunqueueError {
16
+ }
17
+ /** Authentication with the server failed. */
18
+ export class AuthError extends BunqueueError {
19
+ }
20
+ /**
21
+ * Throw inside a Worker processor to fail the job terminally: skips all
22
+ * remaining retry attempts and sends the job straight to the DLQ.
23
+ */
24
+ export class UnrecoverableError extends BunqueueError {
25
+ }
@@ -0,0 +1,34 @@
1
+ /** FlowProducer types. */
2
+ import type { Connection, TlsOption } from './connection.js';
3
+ import type { Job } from './job.js';
4
+ import type { JobOptions } from './types.js';
5
+ export interface FlowJob<T = unknown> {
6
+ name: string;
7
+ queueName: string;
8
+ data?: T;
9
+ opts?: JobOptions;
10
+ children?: FlowJob<T>[];
11
+ }
12
+ export interface JobNode<T = unknown> {
13
+ job: Job<T>;
14
+ children?: JobNode<T>[];
15
+ }
16
+ export interface FlowStep<T = unknown> {
17
+ name: string;
18
+ queueName: string;
19
+ data?: T;
20
+ opts?: JobOptions;
21
+ }
22
+ export interface FlowProducerOptions {
23
+ host?: string;
24
+ port?: number;
25
+ token?: string;
26
+ tls?: TlsOption;
27
+ connection?: Connection;
28
+ }
29
+ export interface GetFlowOptions {
30
+ id: string;
31
+ queueName?: string;
32
+ depth?: number;
33
+ maxChildren?: number;
34
+ }
@@ -0,0 +1,2 @@
1
+ /** FlowProducer types. */
2
+ export {};
package/dist/flow.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * FlowProducer: parent/children job trees, chains and fan-in flows.
3
+ * Mirrors the official client's TCP flow logic (bottom-up creation,
4
+ * UpdateParent fix-up, rollback via Cancel on failure).
5
+ */
6
+ import { Connection } from './connection.js';
7
+ import type { FlowJob, FlowProducerOptions, FlowStep, GetFlowOptions, JobNode } from './flow-types.js';
8
+ export declare class FlowProducer {
9
+ readonly connection: Connection;
10
+ private readonly ownsConnection;
11
+ constructor(opts?: FlowProducerOptions);
12
+ /** Add a flow tree. Children are created (and processed) BEFORE their parent. */
13
+ add<T = unknown>(flow: FlowJob<T>): Promise<JobNode<T>>;
14
+ addBulk<T = unknown>(flows: FlowJob<T>[]): Promise<JobNode<T>[]>;
15
+ /** Fetch a flow tree starting from a job id (recursive over childrenIds). */
16
+ getFlow<T = unknown>(opts: GetFlowOptions): Promise<JobNode<T> | null>;
17
+ /** Add a sequential chain: step[0] → step[1] → ... via dependsOn. */
18
+ addChain<T = unknown>(steps: FlowStep<T>[]): Promise<{
19
+ jobIds: string[];
20
+ }>;
21
+ /** Parallel jobs converging into a final fan-in job. */
22
+ addBulkThen<T = unknown>(parallel: FlowStep<T>[], final: FlowStep<T>): Promise<{
23
+ parallelIds: string[];
24
+ finalId: string;
25
+ }>;
26
+ close(): void;
27
+ private addNode;
28
+ private pushWithParent;
29
+ private fetchNode;
30
+ private rollback;
31
+ }