bunqueue-client 0.1.5 → 0.1.6
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/CHANGELOG.md +77 -0
- package/README.md +43 -0
- package/dist/ack-batcher.d.ts +28 -0
- package/dist/ack-batcher.js +67 -0
- package/dist/backpressure.d.ts +22 -0
- package/dist/backpressure.js +39 -0
- package/dist/connection-pool.d.ts +30 -0
- package/dist/connection-pool.js +60 -0
- package/dist/connection-types.d.ts +21 -1
- package/dist/connection.d.ts +12 -3
- package/dist/connection.js +39 -3
- package/dist/flow-types.d.ts +2 -1
- package/dist/flow.js +12 -3
- package/dist/index.d.ts +7 -2
- package/dist/index.js +3 -1
- package/dist/job.d.ts +2 -2
- package/dist/observability.d.ts +95 -0
- package/dist/observability.js +110 -0
- package/dist/queue-control.js +3 -4
- package/dist/queue-query.js +8 -18
- package/dist/queue.d.ts +13 -4
- package/dist/queue.js +14 -7
- package/dist/responses.d.ts +79 -0
- package/dist/responses.js +10 -0
- package/dist/worker-base.d.ts +2 -0
- package/dist/worker-base.js +5 -0
- package/dist/worker-types.d.ts +15 -1
- package/dist/worker.d.ts +4 -0
- package/dist/worker.js +43 -6
- package/package.json +2 -1
- package/src/ack-batcher.ts +76 -0
- package/src/backpressure.ts +40 -0
- package/src/connection-pool.ts +71 -0
- package/src/connection-types.ts +23 -1
- package/src/connection.ts +42 -6
- package/src/flow-types.ts +2 -1
- package/src/flow.ts +24 -16
- package/src/index.ts +28 -2
- package/src/job.ts +3 -3
- package/src/observability.ts +158 -0
- package/src/queue-control.ts +6 -6
- package/src/queue-query.ts +35 -27
- package/src/queue.ts +30 -11
- package/src/responses.ts +96 -0
- package/src/worker-base.ts +6 -0
- package/src/worker-types.ts +16 -1
- package/src/worker.ts +45 -6
package/src/worker.ts
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { hostname } from 'node:os';
|
|
10
|
+
import { AckBatcher } from './ack-batcher.js';
|
|
10
11
|
import { CommandTimeoutError, ConnectionClosedError, UnrecoverableError } from './errors.js';
|
|
11
12
|
import { compact } from './frame.js';
|
|
12
13
|
import { Job } from './job.js';
|
|
14
|
+
import type { PulledJobsResponse } from './responses.js';
|
|
13
15
|
import { WorkerBase } from './worker-base.js';
|
|
14
16
|
import {
|
|
15
17
|
MAX_STACK_LINES,
|
|
@@ -21,13 +23,23 @@ import {
|
|
|
21
23
|
|
|
22
24
|
export class Worker<T = unknown, R = unknown> extends WorkerBase {
|
|
23
25
|
private readonly processor: Processor<T, R>;
|
|
26
|
+
private readonly ackBatcher: AckBatcher | null;
|
|
24
27
|
|
|
25
28
|
constructor(queue: string, processor: Processor<T, R>, opts: WorkerOptions = {}) {
|
|
26
29
|
super(queue, opts);
|
|
27
30
|
this.processor = processor;
|
|
31
|
+
const ab = opts.ackBatch;
|
|
32
|
+
this.ackBatcher = ab?.enabled
|
|
33
|
+
? new AckBatcher(this.connection, ab.maxSize ?? 50, ab.maxDelayMs ?? 5)
|
|
34
|
+
: null;
|
|
28
35
|
if (opts.autorun !== false) this.run();
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
/** Flush batched ACKs before the base class drains in-flight jobs. */
|
|
39
|
+
protected override async beforeClose(): Promise<void> {
|
|
40
|
+
if (this.ackBatcher) await this.ackBatcher.flush();
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
/** Start the pull loop (no-op if already running). */
|
|
32
44
|
run(): void {
|
|
33
45
|
if (this.running || this.closedFlag) return;
|
|
@@ -82,7 +94,7 @@ export class Worker<T = unknown, R = unknown> extends WorkerBase {
|
|
|
82
94
|
await this.register();
|
|
83
95
|
}
|
|
84
96
|
|
|
85
|
-
const response = await this.connection.call(
|
|
97
|
+
const response = await this.connection.call<PulledJobsResponse>(
|
|
86
98
|
{
|
|
87
99
|
cmd: 'PULLB',
|
|
88
100
|
queue: this.queue,
|
|
@@ -94,8 +106,8 @@ export class Worker<T = unknown, R = unknown> extends WorkerBase {
|
|
|
94
106
|
this.pollTimeoutMs + 10_000
|
|
95
107
|
);
|
|
96
108
|
|
|
97
|
-
const jobs =
|
|
98
|
-
const tokens =
|
|
109
|
+
const jobs = response.jobs ?? [];
|
|
110
|
+
const tokens = response.tokens ?? [];
|
|
99
111
|
|
|
100
112
|
if (jobs.length === 0) {
|
|
101
113
|
if (this.wasBusy && this.active.size === 0) {
|
|
@@ -119,10 +131,34 @@ export class Worker<T = unknown, R = unknown> extends WorkerBase {
|
|
|
119
131
|
this.emit('active', job);
|
|
120
132
|
try {
|
|
121
133
|
const result = await this.processor(job);
|
|
134
|
+
if (this.ackBatcher) {
|
|
135
|
+
// Defer the ACK into a batch; the job stays active (lock renewed) until
|
|
136
|
+
// the ACKB settles. onSettled frees the slot FIRST — a throwing
|
|
137
|
+
// listener (e.g. an unhandled 'error' emit) must never leak the slot
|
|
138
|
+
// and permanently shrink the worker's effective concurrency.
|
|
139
|
+
this.ackBatcher.add({
|
|
140
|
+
id: job.id,
|
|
141
|
+
token,
|
|
142
|
+
result: result ?? undefined,
|
|
143
|
+
onSettled: (err) => {
|
|
144
|
+
this.finishJob(job.id);
|
|
145
|
+
if (err) {
|
|
146
|
+
this.emit('error', err);
|
|
147
|
+
} else {
|
|
148
|
+
this.processed += 1;
|
|
149
|
+
this.emit('completed', job, result);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
122
155
|
this.processed += 1;
|
|
123
156
|
await this.safeCall(
|
|
124
157
|
compact({ cmd: 'ACK', id: job.id, token, result: result ?? undefined }) as { cmd: string }
|
|
125
158
|
);
|
|
159
|
+
// Free the slot BEFORE emitting: a throwing 'completed' listener must
|
|
160
|
+
// not leak the active slot (same rationale as the batched path).
|
|
161
|
+
this.finishJob(job.id);
|
|
126
162
|
this.emit('completed', job, result);
|
|
127
163
|
} catch (err) {
|
|
128
164
|
this.failedCount += 1;
|
|
@@ -140,13 +176,16 @@ export class Worker<T = unknown, R = unknown> extends WorkerBase {
|
|
|
140
176
|
unrecoverable: err instanceof UnrecoverableError ? true : undefined,
|
|
141
177
|
}) as { cmd: string }
|
|
142
178
|
);
|
|
179
|
+
this.finishJob(job.id);
|
|
143
180
|
this.emit('failed', job, error);
|
|
144
|
-
} finally {
|
|
145
|
-
this.active.delete(job.id);
|
|
146
|
-
this.cancelledJobs.delete(job.id);
|
|
147
181
|
}
|
|
148
182
|
}
|
|
149
183
|
|
|
184
|
+
private finishJob(id: string): void {
|
|
185
|
+
this.active.delete(id);
|
|
186
|
+
this.cancelledJobs.delete(id);
|
|
187
|
+
}
|
|
188
|
+
|
|
150
189
|
// --------------------------------------------------------------- heartbeat
|
|
151
190
|
|
|
152
191
|
private startHeartbeat(): void {
|