bunqueue 2.8.8 → 2.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwarder — store-and-forward from a local (edge) queue to a remote
|
|
3
|
+
* bunqueue server.
|
|
4
|
+
*
|
|
5
|
+
* The edge pattern: jobs buffer in the local queue (typically embedded
|
|
6
|
+
* SQLite on a gateway); the Forwarder drains them to a central server over
|
|
7
|
+
* TCP/TLS. A remote failure makes the local job fail → local retry/backoff →
|
|
8
|
+
* local DLQ, so nothing is lost while the uplink is down.
|
|
9
|
+
*
|
|
10
|
+
* Idempotency: every forwarded job carries the deterministic remote jobId
|
|
11
|
+
* `fwd:<localQueueKey>:<localJobId>`. The server dedupes custom jobIds, so a
|
|
12
|
+
* re-forward after a crash or retry never duplicates the job remotely.
|
|
13
|
+
*/
|
|
14
|
+
import { EventEmitter } from 'events';
|
|
15
|
+
import type { ConnectionOptions, QueueOptions } from './types';
|
|
16
|
+
/** Options for queue.forward() */
|
|
17
|
+
export interface ForwardOptions {
|
|
18
|
+
/** Remote server connection (host/port/tls/token...) */
|
|
19
|
+
to: ConnectionOptions;
|
|
20
|
+
/** Remote queue name (default: same as the source queue) */
|
|
21
|
+
queue?: string;
|
|
22
|
+
/** Parallel forwards (default: 4) */
|
|
23
|
+
concurrency?: number;
|
|
24
|
+
/** Push to the remote with durable: true (immediate fsync server-side) */
|
|
25
|
+
durable?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Source queue identity + config, provided by Queue.forward() */
|
|
28
|
+
export interface ForwardSource {
|
|
29
|
+
/** Logical source queue name */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Source queue key (prefixKey + name) — used in the deterministic jobId */
|
|
32
|
+
queueKey: string;
|
|
33
|
+
prefixKey?: string;
|
|
34
|
+
embedded: boolean;
|
|
35
|
+
dataPath?: string;
|
|
36
|
+
connection?: ConnectionOptions;
|
|
37
|
+
}
|
|
38
|
+
/** Minimal remote-queue surface the Forwarder needs (avoids a Queue import cycle) */
|
|
39
|
+
interface RemoteQueueLike {
|
|
40
|
+
add(name: string, data: unknown, opts?: {
|
|
41
|
+
jobId?: string;
|
|
42
|
+
priority?: number;
|
|
43
|
+
durable?: boolean;
|
|
44
|
+
}): Promise<unknown>;
|
|
45
|
+
close(): Promise<void> | void;
|
|
46
|
+
}
|
|
47
|
+
/** Constructor for the remote queue, injected by Queue.forward() */
|
|
48
|
+
export type RemoteQueueCtor = new (name: string, opts: QueueOptions) => RemoteQueueLike;
|
|
49
|
+
/** Info emitted with each 'forwarded' event */
|
|
50
|
+
export interface ForwardedInfo {
|
|
51
|
+
/** Local job id */
|
|
52
|
+
id: string;
|
|
53
|
+
/** Deterministic remote job id (fwd:<queueKey>:<id>) */
|
|
54
|
+
remoteId: string;
|
|
55
|
+
/** Job name */
|
|
56
|
+
name: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Drains a local queue into a remote bunqueue server.
|
|
60
|
+
* Create via `queue.forward(options)`; stop with `close()`.
|
|
61
|
+
*/
|
|
62
|
+
export declare class Forwarder extends EventEmitter {
|
|
63
|
+
on(event: 'forwarded', listener: (info: ForwardedInfo) => void): this;
|
|
64
|
+
on(event: 'error', listener: (error: Error) => void): this;
|
|
65
|
+
private readonly remote;
|
|
66
|
+
private readonly worker;
|
|
67
|
+
private closed;
|
|
68
|
+
constructor(source: ForwardSource, options: ForwardOptions, RemoteQueue: RemoteQueueCtor);
|
|
69
|
+
/** Stop forwarding and release connections. Safe to call more than once. */
|
|
70
|
+
close(): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwarder — store-and-forward from a local (edge) queue to a remote
|
|
3
|
+
* bunqueue server.
|
|
4
|
+
*
|
|
5
|
+
* The edge pattern: jobs buffer in the local queue (typically embedded
|
|
6
|
+
* SQLite on a gateway); the Forwarder drains them to a central server over
|
|
7
|
+
* TCP/TLS. A remote failure makes the local job fail → local retry/backoff →
|
|
8
|
+
* local DLQ, so nothing is lost while the uplink is down.
|
|
9
|
+
*
|
|
10
|
+
* Idempotency: every forwarded job carries the deterministic remote jobId
|
|
11
|
+
* `fwd:<localQueueKey>:<localJobId>`. The server dedupes custom jobIds, so a
|
|
12
|
+
* re-forward after a crash or retry never duplicates the job remotely.
|
|
13
|
+
*/
|
|
14
|
+
import { EventEmitter } from 'events';
|
|
15
|
+
import { Worker } from './worker';
|
|
16
|
+
/**
|
|
17
|
+
* Drains a local queue into a remote bunqueue server.
|
|
18
|
+
* Create via `queue.forward(options)`; stop with `close()`.
|
|
19
|
+
*/
|
|
20
|
+
export class Forwarder extends EventEmitter {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
on(event, listener) {
|
|
23
|
+
return super.on(event, listener);
|
|
24
|
+
}
|
|
25
|
+
remote;
|
|
26
|
+
worker;
|
|
27
|
+
closed = false;
|
|
28
|
+
constructor(source, options, RemoteQueue) {
|
|
29
|
+
super();
|
|
30
|
+
this.remote = new RemoteQueue(options.queue ?? source.name, {
|
|
31
|
+
embedded: false,
|
|
32
|
+
connection: options.to,
|
|
33
|
+
autoBatch: { enabled: false },
|
|
34
|
+
});
|
|
35
|
+
this.worker = new Worker(source.name, async (job) => {
|
|
36
|
+
const remoteId = `fwd:${source.queueKey}:${job.id}`;
|
|
37
|
+
await this.remote.add(job.name, job.data, {
|
|
38
|
+
jobId: remoteId,
|
|
39
|
+
...(job.opts.priority !== undefined && { priority: job.opts.priority }),
|
|
40
|
+
...(options.durable && { durable: true }),
|
|
41
|
+
});
|
|
42
|
+
const info = { id: job.id, remoteId, name: job.name };
|
|
43
|
+
// A throwing user listener must not fail a forward that already
|
|
44
|
+
// succeeded remotely (it would trigger a local retry of a done job).
|
|
45
|
+
try {
|
|
46
|
+
this.emit('forwarded', info);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* listener error — forward itself succeeded */
|
|
50
|
+
}
|
|
51
|
+
return info;
|
|
52
|
+
}, {
|
|
53
|
+
embedded: source.embedded,
|
|
54
|
+
dataPath: source.dataPath,
|
|
55
|
+
connection: source.connection,
|
|
56
|
+
prefixKey: source.prefixKey,
|
|
57
|
+
concurrency: options.concurrency ?? 4,
|
|
58
|
+
});
|
|
59
|
+
// A remote failure is already handled by the local retry/DLQ path; the
|
|
60
|
+
// event is observability. Guard: EventEmitter throws on 'error' without
|
|
61
|
+
// listeners, and a transient uplink failure must never crash the process.
|
|
62
|
+
this.worker.on('failed', (_job, err) => {
|
|
63
|
+
if (this.listenerCount('error') > 0)
|
|
64
|
+
this.emit('error', err);
|
|
65
|
+
});
|
|
66
|
+
this.worker.on('error', (err) => {
|
|
67
|
+
if (this.listenerCount('error') > 0)
|
|
68
|
+
this.emit('error', err);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** Stop forwarding and release connections. Safe to call more than once. */
|
|
72
|
+
async close() {
|
|
73
|
+
if (this.closed)
|
|
74
|
+
return;
|
|
75
|
+
this.closed = true;
|
|
76
|
+
await this.worker.close();
|
|
77
|
+
await this.remote.close();
|
|
78
|
+
}
|
|
79
|
+
}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export { Worker } from './worker';
|
|
|
26
26
|
export { Bunqueue } from './bunqueue';
|
|
27
27
|
export type { BunqueueOptions, BunqueueMiddleware, RetryStrategy, RetryConfig, CircuitBreakerConfig, TriggerRule, PriorityAgingConfig, BatchProcessor, BatchConfig, JobTtlConfig, BunqueueDeduplicationConfig, BunqueueDebounceConfig, BunqueueDlqConfig, } from './bunqueue';
|
|
28
28
|
export { SandboxedWorker } from './sandboxedWorker';
|
|
29
|
+
export { Forwarder } from './forwarder';
|
|
30
|
+
export type { ForwardOptions, ForwardedInfo } from './forwarder';
|
|
29
31
|
export { QueueEvents } from './events';
|
|
30
32
|
export { QueueGroup } from './queueGroup';
|
|
31
33
|
export { FlowProducer } from './flow';
|
package/dist/client/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export { Queue } from './queue';
|
|
|
25
25
|
export { Worker } from './worker';
|
|
26
26
|
export { Bunqueue } from './bunqueue';
|
|
27
27
|
export { SandboxedWorker } from './sandboxedWorker';
|
|
28
|
+
export { Forwarder } from './forwarder';
|
|
28
29
|
export { QueueEvents } from './events';
|
|
29
30
|
export { QueueGroup } from './queueGroup';
|
|
30
31
|
export { FlowProducer } from './flow';
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* BullMQ-style queue for job management
|
|
4
4
|
*/
|
|
5
5
|
import type { Job, JobOptions, QueueOptions, StallConfig, DlqConfig, DlqEntry, DlqStats, DlqFilter, ChangePriorityOpts, GetDependenciesOpts, JobDependencies, JobDependenciesCount, JobStateType } from '../types';
|
|
6
|
+
import { Forwarder, type ForwardOptions } from '../forwarder';
|
|
6
7
|
import * as countsOps from './operations/counts';
|
|
7
8
|
import * as schedulerOps from './scheduler';
|
|
8
9
|
import type { RepeatOpts, JobTemplate, SchedulerInfo } from './scheduler';
|
|
@@ -168,6 +169,14 @@ export declare class Queue<T = unknown> {
|
|
|
168
169
|
};
|
|
169
170
|
data: number[];
|
|
170
171
|
}>;
|
|
172
|
+
/**
|
|
173
|
+
* Drain this queue's jobs to a remote bunqueue server (store-and-forward).
|
|
174
|
+
* Typical edge pattern: embedded local queue as offline buffer, forwarded
|
|
175
|
+
* to a central server over TCP/TLS. Remote failures leave jobs local
|
|
176
|
+
* (retry → DLQ) — nothing is lost while the uplink is down. Forwarded jobs
|
|
177
|
+
* carry a deterministic remote jobId so re-forwards never duplicate.
|
|
178
|
+
*/
|
|
179
|
+
forward(options: ForwardOptions): Forwarder;
|
|
171
180
|
disconnect(): Promise<void>;
|
|
172
181
|
close(): void;
|
|
173
182
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { TcpConnectionPool, getSharedPool, releaseSharedPool } from '../tcpPool';
|
|
6
6
|
import { FORCE_EMBEDDED } from './helpers';
|
|
7
7
|
import { AddBatcher } from './addBatcher';
|
|
8
|
+
import { Forwarder } from '../forwarder';
|
|
8
9
|
import { resolveToken } from '../resolveToken';
|
|
9
10
|
import { getSharedManager } from '../manager';
|
|
10
11
|
// Import operation modules
|
|
@@ -483,6 +484,24 @@ export class Queue {
|
|
|
483
484
|
getMetrics(type, start, end) {
|
|
484
485
|
return workersOps.getMetrics(this.ctx, type, start, end);
|
|
485
486
|
}
|
|
487
|
+
// ============ Forwarding ============
|
|
488
|
+
/**
|
|
489
|
+
* Drain this queue's jobs to a remote bunqueue server (store-and-forward).
|
|
490
|
+
* Typical edge pattern: embedded local queue as offline buffer, forwarded
|
|
491
|
+
* to a central server over TCP/TLS. Remote failures leave jobs local
|
|
492
|
+
* (retry → DLQ) — nothing is lost while the uplink is down. Forwarded jobs
|
|
493
|
+
* carry a deterministic remote jobId so re-forwards never duplicate.
|
|
494
|
+
*/
|
|
495
|
+
forward(options) {
|
|
496
|
+
return new Forwarder({
|
|
497
|
+
name: this.name,
|
|
498
|
+
queueKey: this.queueKey,
|
|
499
|
+
prefixKey: this.prefixKey || undefined,
|
|
500
|
+
embedded: this.embedded,
|
|
501
|
+
dataPath: this.opts.dataPath,
|
|
502
|
+
connection: this.opts.connection,
|
|
503
|
+
}, options, Queue);
|
|
504
|
+
}
|
|
486
505
|
// ============ Connection ============
|
|
487
506
|
async disconnect() {
|
|
488
507
|
if (this.addBatcher) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunqueue",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.9",
|
|
4
4
|
"description": "High-performance job queue for Bun & AI agents. SQLite persistence, cron scheduling, priorities, retries, DLQ, webhooks, native MCP server. Zero external dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|