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.
Files changed (47) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +43 -0
  3. package/dist/ack-batcher.d.ts +28 -0
  4. package/dist/ack-batcher.js +67 -0
  5. package/dist/backpressure.d.ts +22 -0
  6. package/dist/backpressure.js +39 -0
  7. package/dist/connection-pool.d.ts +30 -0
  8. package/dist/connection-pool.js +60 -0
  9. package/dist/connection-types.d.ts +21 -1
  10. package/dist/connection.d.ts +12 -3
  11. package/dist/connection.js +39 -3
  12. package/dist/flow-types.d.ts +2 -1
  13. package/dist/flow.js +12 -3
  14. package/dist/index.d.ts +7 -2
  15. package/dist/index.js +3 -1
  16. package/dist/job.d.ts +2 -2
  17. package/dist/observability.d.ts +95 -0
  18. package/dist/observability.js +110 -0
  19. package/dist/queue-control.js +3 -4
  20. package/dist/queue-query.js +8 -18
  21. package/dist/queue.d.ts +13 -4
  22. package/dist/queue.js +14 -7
  23. package/dist/responses.d.ts +79 -0
  24. package/dist/responses.js +10 -0
  25. package/dist/worker-base.d.ts +2 -0
  26. package/dist/worker-base.js +5 -0
  27. package/dist/worker-types.d.ts +15 -1
  28. package/dist/worker.d.ts +4 -0
  29. package/dist/worker.js +43 -6
  30. package/package.json +2 -1
  31. package/src/ack-batcher.ts +76 -0
  32. package/src/backpressure.ts +40 -0
  33. package/src/connection-pool.ts +71 -0
  34. package/src/connection-types.ts +23 -1
  35. package/src/connection.ts +42 -6
  36. package/src/flow-types.ts +2 -1
  37. package/src/flow.ts +24 -16
  38. package/src/index.ts +28 -2
  39. package/src/job.ts +3 -3
  40. package/src/observability.ts +158 -0
  41. package/src/queue-control.ts +6 -6
  42. package/src/queue-query.ts +35 -27
  43. package/src/queue.ts +30 -11
  44. package/src/responses.ts +96 -0
  45. package/src/worker-base.ts +6 -0
  46. package/src/worker-types.ts +16 -1
  47. 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 = (response.jobs ?? []) as Record<string, unknown>[];
98
- const tokens = (response.tokens ?? []) as string[];
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 {