bunqueue-client 0.1.1 → 0.1.3
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 +91 -3
- 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 +182 -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 +21 -0
- package/dist/bunqueue/rate-gate.js +48 -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 +210 -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 +53 -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/src/job.ts
CHANGED
|
@@ -62,6 +62,11 @@ export class Job<T = unknown> {
|
|
|
62
62
|
return this.raw.createdAt === undefined ? undefined : Number(this.raw.createdAt);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/** Creation time in epoch ms (BullMQ-parity alias of createdAt). */
|
|
66
|
+
get timestamp(): number {
|
|
67
|
+
return this.createdAt ?? Date.now();
|
|
68
|
+
}
|
|
69
|
+
|
|
65
70
|
get startedAt(): number | null {
|
|
66
71
|
return this.raw.startedAt === undefined || this.raw.startedAt === null
|
|
67
72
|
? null
|
package/src/queue-admin.ts
CHANGED
|
@@ -206,7 +206,8 @@ export const adminMethods = {
|
|
|
206
206
|
},
|
|
207
207
|
|
|
208
208
|
async setWebhookEnabled(this: Ctx, webhookId: string, enabled: boolean): Promise<void> {
|
|
209
|
-
|
|
209
|
+
// wire field is `id` (SetWebhookEnabledCommand), unlike RemoveWebhook's `webhookId`
|
|
210
|
+
await this.call({ cmd: 'SetWebhookEnabled', id: webhookId, enabled });
|
|
210
211
|
},
|
|
211
212
|
};
|
|
212
213
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Socket factory: opens a plain or TLS socket with a connect timeout. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { connect as netConnect, type Socket } from 'node:net';
|
|
5
|
+
import { type ConnectionOptions as TlsConnectOptions, connect as tlsConnect } from 'node:tls';
|
|
6
|
+
import type { TlsOption } from './connection-types.js';
|
|
7
|
+
import { ConnectionClosedError } from './errors.js';
|
|
8
|
+
|
|
9
|
+
export function openSocket(
|
|
10
|
+
host: string,
|
|
11
|
+
port: number,
|
|
12
|
+
tls: TlsOption,
|
|
13
|
+
connectTimeoutMs: number
|
|
14
|
+
): Promise<Socket> {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
let settled = false;
|
|
17
|
+
const timer = setTimeout(() => {
|
|
18
|
+
if (settled) return;
|
|
19
|
+
settled = true;
|
|
20
|
+
socket.destroy();
|
|
21
|
+
reject(new ConnectionClosedError(`connect timeout to ${host}:${port}`));
|
|
22
|
+
}, connectTimeoutMs);
|
|
23
|
+
|
|
24
|
+
const onError = (err: Error) => {
|
|
25
|
+
if (settled) return;
|
|
26
|
+
settled = true;
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
reject(new ConnectionClosedError(`connect failed: ${err.message}`));
|
|
29
|
+
};
|
|
30
|
+
const onReady = () => {
|
|
31
|
+
if (settled) return;
|
|
32
|
+
settled = true;
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
socket.off('error', onError);
|
|
35
|
+
resolve(socket);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let socket: Socket;
|
|
39
|
+
if (tls) {
|
|
40
|
+
const tlsOpts: TlsConnectOptions = { host: host, port: port };
|
|
41
|
+
if (typeof tls === 'object') {
|
|
42
|
+
if (tls.caFile) tlsOpts.ca = readFileSync(tls.caFile);
|
|
43
|
+
if (tls.rejectUnauthorized === false) tlsOpts.rejectUnauthorized = false;
|
|
44
|
+
}
|
|
45
|
+
socket = tlsConnect(tlsOpts, onReady);
|
|
46
|
+
} else {
|
|
47
|
+
socket = netConnect({ host: host, port: port }, onReady);
|
|
48
|
+
}
|
|
49
|
+
socket.on('error', onError);
|
|
50
|
+
});
|
|
51
|
+
}
|
package/src/worker-base.ts
CHANGED
|
@@ -31,8 +31,10 @@ export class WorkerBase extends EventEmitter {
|
|
|
31
31
|
protected failedCount = 0;
|
|
32
32
|
protected readyPromise: Promise<void>;
|
|
33
33
|
protected readyResolve: (() => void) | null = null;
|
|
34
|
+
protected readyFired = false;
|
|
34
35
|
protected loopPromise: Promise<void> | null = null;
|
|
35
36
|
protected heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
37
|
+
protected registeredGeneration = -1;
|
|
36
38
|
|
|
37
39
|
constructor(queue: string, opts: WorkerOptions = {}) {
|
|
38
40
|
super();
|
|
@@ -80,6 +82,24 @@ export class WorkerBase extends EventEmitter {
|
|
|
80
82
|
await this.readyPromise;
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
/**
|
|
86
|
+
* 'ready' is replayed to listeners attached after it fired: with autorun the
|
|
87
|
+
* loop starts inside the constructor, so a plain once-only event could be
|
|
88
|
+
* missed by `new Worker(...).on('ready', ...)` patterns.
|
|
89
|
+
*/
|
|
90
|
+
override on(event: string | symbol, listener: (...args: unknown[]) => void): this {
|
|
91
|
+
if (event === 'ready' && this.readyFired) listener();
|
|
92
|
+
return super.on(event, listener);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override once(event: string | symbol, listener: (...args: unknown[]) => void): this {
|
|
96
|
+
if (event === 'ready' && this.readyFired) {
|
|
97
|
+
listener();
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
return super.once(event, listener);
|
|
101
|
+
}
|
|
102
|
+
|
|
83
103
|
/**
|
|
84
104
|
* Cooperative cancel of a locally active job (mirrors the official client):
|
|
85
105
|
* marks the job and emits 'cancelled'; the processor is expected to check
|
|
@@ -105,17 +125,23 @@ export class WorkerBase extends EventEmitter {
|
|
|
105
125
|
return this.cancelledJobs.has(jobId);
|
|
106
126
|
}
|
|
107
127
|
|
|
108
|
-
/** Graceful shutdown: stop pulling, wait for in-flight jobs.
|
|
109
|
-
|
|
128
|
+
/** Graceful shutdown: stop pulling, wait for in-flight jobs.
|
|
129
|
+
* With `force` the wait for in-flight jobs is skipped (parity with the
|
|
130
|
+
* official client's `close(force)`). */
|
|
131
|
+
async close(force = false): Promise<void> {
|
|
110
132
|
if (this.closedFlag) return;
|
|
111
133
|
this.stopped = true;
|
|
112
134
|
if (this.loopPromise) await this.loopPromise;
|
|
113
|
-
while (this.active.size > 0) await sleep(20);
|
|
135
|
+
while (!force && this.active.size > 0) await sleep(20);
|
|
114
136
|
if (this.heartbeatTimer) {
|
|
115
137
|
clearInterval(this.heartbeatTimer);
|
|
116
138
|
this.heartbeatTimer = null;
|
|
117
139
|
}
|
|
118
|
-
|
|
140
|
+
// Only unregister when the loop actually registered (autorun: false and
|
|
141
|
+
// never run() means the server does not know this worker).
|
|
142
|
+
if (this.registeredGeneration >= 0) {
|
|
143
|
+
await this.safeCall({ cmd: 'UnregisterWorker', workerId: this.workerId });
|
|
144
|
+
}
|
|
119
145
|
this.connection.close();
|
|
120
146
|
this.closedFlag = true;
|
|
121
147
|
this.running = false;
|
package/src/worker.ts
CHANGED
|
@@ -42,6 +42,7 @@ export class Worker<T = unknown, R = unknown> extends WorkerBase {
|
|
|
42
42
|
private async loop(): Promise<void> {
|
|
43
43
|
await this.register();
|
|
44
44
|
this.readyResolve?.();
|
|
45
|
+
this.readyFired = true;
|
|
45
46
|
this.emit('ready');
|
|
46
47
|
this.startHeartbeat();
|
|
47
48
|
|
|
@@ -74,6 +75,13 @@ export class Worker<T = unknown, R = unknown> extends WorkerBase {
|
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
// The registration is per-connection server state: after a reconnect the
|
|
79
|
+
// server no longer knows this worker (ListWorkers, skipIfNoWorker crons).
|
|
80
|
+
// Detect the new connection generation and re-register before pulling.
|
|
81
|
+
if (this.connection.isConnected && this.connection.generation !== this.registeredGeneration) {
|
|
82
|
+
await this.register();
|
|
83
|
+
}
|
|
84
|
+
|
|
77
85
|
const response = await this.connection.call(
|
|
78
86
|
{
|
|
79
87
|
cmd: 'PULLB',
|
|
@@ -170,5 +178,6 @@ export class Worker<T = unknown, R = unknown> extends WorkerBase {
|
|
|
170
178
|
pid: process.pid,
|
|
171
179
|
startedAt: Date.now(),
|
|
172
180
|
});
|
|
181
|
+
this.registeredGeneration = this.connection.generation;
|
|
173
182
|
}
|
|
174
183
|
}
|