bunqueue-client 0.1.2 → 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.
Files changed (60) hide show
  1. package/README.md +45 -3
  2. package/dist/bunqueue/aging.d.ts +17 -0
  3. package/dist/bunqueue/aging.js +53 -0
  4. package/dist/bunqueue/batch.d.ts +17 -0
  5. package/dist/bunqueue/batch.js +60 -0
  6. package/dist/bunqueue/bunqueue-api.d.ts +87 -0
  7. package/dist/bunqueue/bunqueue-api.js +116 -0
  8. package/dist/bunqueue/bunqueue.d.ts +57 -0
  9. package/dist/bunqueue/bunqueue.js +182 -0
  10. package/dist/bunqueue/cancellation.d.ts +20 -0
  11. package/dist/bunqueue/cancellation.js +48 -0
  12. package/dist/bunqueue/circuit-breaker.d.ts +21 -0
  13. package/dist/bunqueue/circuit-breaker.js +69 -0
  14. package/dist/bunqueue/dedup-debounce.d.ts +14 -0
  15. package/dist/bunqueue/dedup-debounce.js +37 -0
  16. package/dist/bunqueue/dlq-rate-limit.d.ts +33 -0
  17. package/dist/bunqueue/dlq-rate-limit.js +65 -0
  18. package/dist/bunqueue/rate-gate.d.ts +21 -0
  19. package/dist/bunqueue/rate-gate.js +48 -0
  20. package/dist/bunqueue/retry.d.ts +9 -0
  21. package/dist/bunqueue/retry.js +56 -0
  22. package/dist/bunqueue/triggers.d.ts +18 -0
  23. package/dist/bunqueue/triggers.js +42 -0
  24. package/dist/bunqueue/ttl.d.ts +18 -0
  25. package/dist/bunqueue/ttl.js +31 -0
  26. package/dist/bunqueue/types.d.ts +151 -0
  27. package/dist/bunqueue/types.js +6 -0
  28. package/dist/connection.d.ts +9 -1
  29. package/dist/connection.js +32 -47
  30. package/dist/index.d.ts +4 -1
  31. package/dist/index.js +3 -1
  32. package/dist/job.d.ts +2 -0
  33. package/dist/job.js +4 -0
  34. package/dist/queue-admin.js +2 -1
  35. package/dist/socket-factory.d.ts +4 -0
  36. package/dist/socket-factory.js +47 -0
  37. package/dist/worker-base.d.ts +13 -2
  38. package/dist/worker-base.js +29 -4
  39. package/dist/worker.js +8 -0
  40. package/package.json +1 -1
  41. package/src/bunqueue/aging.ts +62 -0
  42. package/src/bunqueue/batch.ts +79 -0
  43. package/src/bunqueue/bunqueue-api.ts +212 -0
  44. package/src/bunqueue/bunqueue.ts +210 -0
  45. package/src/bunqueue/cancellation.ts +54 -0
  46. package/src/bunqueue/circuit-breaker.ts +81 -0
  47. package/src/bunqueue/dedup-debounce.ts +43 -0
  48. package/src/bunqueue/dlq-rate-limit.ts +89 -0
  49. package/src/bunqueue/rate-gate.ts +53 -0
  50. package/src/bunqueue/retry.ts +72 -0
  51. package/src/bunqueue/triggers.ts +51 -0
  52. package/src/bunqueue/ttl.ts +38 -0
  53. package/src/bunqueue/types.ts +171 -0
  54. package/src/connection.ts +38 -46
  55. package/src/index.ts +22 -1
  56. package/src/job.ts +5 -0
  57. package/src/queue-admin.ts +2 -1
  58. package/src/socket-factory.ts +51 -0
  59. package/src/worker-base.ts +30 -4
  60. package/src/worker.ts +9 -0
@@ -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
+ }
@@ -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
- async close(): Promise<void> {
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
- await this.safeCall({ cmd: 'UnregisterWorker', workerId: this.workerId });
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
  }