bunqueue 2.8.38 → 2.8.40

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 (64) hide show
  1. package/dist/application/backgroundTasks.js +9 -0
  2. package/dist/application/cleanupTasks.js +31 -11
  3. package/dist/application/contextFactory.d.ts +3 -0
  4. package/dist/application/contextFactory.js +6 -0
  5. package/dist/application/dependencyResultTracker.d.ts +17 -0
  6. package/dist/application/dependencyResultTracker.js +71 -0
  7. package/dist/application/operations/ack.d.ts +2 -0
  8. package/dist/application/operations/ack.js +6 -0
  9. package/dist/application/operations/ackHelpers.d.ts +2 -1
  10. package/dist/application/operations/ackHelpers.js +6 -0
  11. package/dist/application/operations/jobManagement.d.ts +2 -0
  12. package/dist/application/operations/jobManagement.js +10 -4
  13. package/dist/application/operations/jobStateTransitions.js +1 -0
  14. package/dist/application/operations/pull.d.ts +2 -2
  15. package/dist/application/operations/pull.js +43 -10
  16. package/dist/application/operations/push.d.ts +2 -0
  17. package/dist/application/operations/pushInsert.d.ts +4 -0
  18. package/dist/application/operations/pushInsert.js +6 -0
  19. package/dist/application/operations/queryOperations.d.ts +2 -0
  20. package/dist/application/operations/queryOperations.js +5 -1
  21. package/dist/application/operations/queueControl.d.ts +2 -0
  22. package/dist/application/operations/queueControl.js +4 -0
  23. package/dist/application/queueManager.d.ts +5 -4
  24. package/dist/application/queueManager.js +37 -11
  25. package/dist/application/types.d.ts +2 -0
  26. package/dist/cli/client.js +6 -47
  27. package/dist/cli/commandRegistry.d.ts +27 -0
  28. package/dist/cli/commandRegistry.js +40 -0
  29. package/dist/cli/commandRouter.d.ts +3 -0
  30. package/dist/cli/commandRouter.js +44 -0
  31. package/dist/cli/commands/backup.js +1 -1
  32. package/dist/cli/commands/core.js +54 -2
  33. package/dist/cli/commands/doctor.d.ts +50 -5
  34. package/dist/cli/commands/doctor.js +125 -108
  35. package/dist/cli/commands/types.js +5 -1
  36. package/dist/cli/globalOptions.d.ts +16 -0
  37. package/dist/cli/globalOptions.js +192 -0
  38. package/dist/cli/help.d.ts +9 -0
  39. package/dist/cli/help.js +42 -21
  40. package/dist/cli/index.d.ts +1 -25
  41. package/dist/cli/index.js +84 -348
  42. package/dist/cli/localOutput.d.ts +22 -0
  43. package/dist/cli/localOutput.js +45 -0
  44. package/dist/client/queue/dlqOps.js +1 -3
  45. package/dist/client/tcp/client.js +10 -8
  46. package/dist/domain/queue/shard.d.ts +2 -2
  47. package/dist/domain/queue/shard.js +3 -3
  48. package/dist/domain/queue/waiterManager.d.ts +2 -2
  49. package/dist/domain/queue/waiterManager.js +26 -5
  50. package/dist/infrastructure/cloud/wsSender.js +4 -4
  51. package/dist/infrastructure/persistence/schema.d.ts +2 -2
  52. package/dist/infrastructure/persistence/schema.js +7 -2
  53. package/dist/infrastructure/persistence/sqlite.d.ts +5 -1
  54. package/dist/infrastructure/persistence/sqlite.js +10 -2
  55. package/dist/infrastructure/persistence/sqliteSerializer.js +1 -1
  56. package/dist/infrastructure/persistence/statements.d.ts +2 -1
  57. package/dist/infrastructure/persistence/statements.js +3 -2
  58. package/dist/infrastructure/server/handlers/core.js +4 -4
  59. package/dist/infrastructure/server/tcp.d.ts +1 -0
  60. package/dist/infrastructure/server/tcp.js +23 -12
  61. package/dist/infrastructure/server/types.d.ts +2 -0
  62. package/dist/shared/msgpack.d.ts +3 -0
  63. package/dist/shared/msgpack.js +70 -0
  64. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -1,373 +1,109 @@
1
1
  #!/usr/bin/env bun
2
- /**
3
- * bunqueue CLI Entry Point
4
- * Routes to server or client mode based on arguments
5
- */
6
- import { runServer } from './commands/server';
7
2
  import { executeCommand } from './client';
8
- import { printHelp, printVersion, printPushHelp, printCronAddHelp } from './help';
9
- import { isBackupCommand, executeBackupCommand } from './commands/backup';
10
- import { runDoctor } from './commands/doctor';
3
+ import { executeBackupCommand, isBackupCommand } from './commands/backup';
4
+ import { formatDoctorText, runDoctor } from './commands/doctor';
5
+ import { runServer } from './commands/server';
6
+ import { renderCronAddHelp, renderHelp, renderPushHelp, renderServerHelp, renderVersion, } from './help';
7
+ import { parseGlobalOptions } from './globalOptions';
8
+ import { collectVersionInfo, emitLocalOutput, formatVersionInfoText, } from './localOutput';
11
9
  import { VERSION } from '../shared/version';
12
- import { resolveToken } from '../client/resolveToken';
13
- /**
14
- * Resolve TCP port from env when not set via --port flag.
15
- * Priority: explicit CLI flag (caller-checked) > TCP_PORT (server's primary var)
16
- * > BUNQUEUE_TCP_PORT > BQ_TCP_PORT. Reads TCP_PORT first so users
17
- * running both server and client in the same shell with TCP_PORT=X get
18
- * consistent behavior — same var binds the server AND routes the client.
19
- */
20
- function resolveEnvPort(currentPort) {
21
- const envPort = Bun.env.TCP_PORT ?? Bun.env.BUNQUEUE_TCP_PORT ?? Bun.env.BQ_TCP_PORT;
22
- if (!envPort)
23
- return currentPort;
24
- const parsed = parseInt(envPort, 10);
25
- if (Number.isNaN(parsed) || parsed < 1 || parsed > 65535) {
26
- console.warn(`Warning: Invalid env port "${envPort}". Using ${currentPort}.`);
27
- return currentPort;
28
- }
29
- return parsed;
30
- }
31
- /**
32
- * Resolve host from env when not set via --host flag.
33
- * Priority: HOST (server's primary var) > BUNQUEUE_HOST > BQ_HOST.
34
- */
35
- function resolveEnvHost(currentHost) {
36
- return Bun.env.HOST ?? Bun.env.BUNQUEUE_HOST ?? Bun.env.BQ_HOST ?? currentHost;
37
- }
38
- /**
39
- * Handle a `--tls*` global client flag and return the index to continue
40
- * scanning from. Flags that are NOT client TLS flags (e.g. the server's
41
- * --tls-cert/--tls-key) are passed through to `commandArgs`.
42
- */
43
- function applyTlsFlag(arg, allArgs, i, state, commandArgs) {
44
- if (arg === '--tls') {
45
- state.enabled = true;
46
- return i;
47
- }
48
- if (arg === '--tls-no-verify') {
49
- state.noVerify = true;
50
- return i;
51
- }
52
- if (arg === '--tls-ca') {
53
- const nextArg = allArgs[i + 1];
54
- // A following flag is not a path: don't swallow it (--tls-ca --json ...)
55
- if (nextArg === undefined || nextArg.startsWith('-')) {
56
- console.warn('Warning: --tls-ca requires a file path. Option ignored.');
57
- return i;
58
- }
59
- state.caFile = nextArg;
60
- return i + 1;
61
- }
62
- if (arg.startsWith('--tls-ca=')) {
63
- const val = arg.slice(9);
64
- if (!val) {
65
- console.warn('Warning: --tls-ca= requires a file path. Option ignored.');
66
- }
67
- else {
68
- state.caFile = val;
69
- }
70
- return i;
71
- }
72
- commandArgs.push(arg); // --tls-cert / --tls-key etc. → server flags, pass through
73
- return i;
74
- }
75
- /** Build the GlobalOptions.tls value: --tls-ca / --tls-no-verify imply TLS */
76
- function buildTlsOption(state) {
77
- if (state.noVerify || state.caFile !== undefined) {
78
- return {
79
- ...(state.noVerify && { rejectUnauthorized: false }),
80
- ...(state.caFile !== undefined && { caFile: state.caFile }),
81
- };
82
- }
83
- return state.enabled ? true : undefined;
10
+ export { parseGlobalOptions } from './globalOptions';
11
+ function finish(output, json) {
12
+ emitLocalOutput(output, json);
13
+ process.exit(output.exitCode);
84
14
  }
85
- /**
86
- * Exactly two subcommands define their own short `-t` (--timeout):
87
- * `pull` and `job wait`. ONLY there `-t` is passed through instead of being
88
- * consumed as the global token flag — every other command keeps `-t TOKEN`
89
- * working anywhere on the line. The long form `--token` is global everywhere.
90
- */
91
- function commandOwnsShortT(commandArgs) {
92
- return commandArgs[0] === 'pull' || (commandArgs[0] === 'job' && commandArgs[1] === 'wait');
15
+ function helpFor(command, color) {
16
+ if (command === 'start')
17
+ return renderServerHelp();
18
+ if (command === 'push')
19
+ return renderPushHelp();
20
+ if (command === 'cron')
21
+ return renderCronAddHelp();
22
+ return renderHelp(color);
93
23
  }
94
- /**
95
- * Handle `--token` / global `-t` and return the index to continue scanning
96
- * from. When the command already parsed owns short `-t` (pull / job wait),
97
- * the flag is passed through to the subcommand instead of being consumed.
98
- */
99
- function applyTokenFlag(arg, allArgs, i, state, commandArgs) {
100
- if (arg === '-t' && commandOwnsShortT(commandArgs)) {
101
- commandArgs.push(arg); // pull / job wait own -t (timeout): pass through
102
- return i;
103
- }
104
- const nextArg = allArgs[i + 1];
105
- // A following flag is not a token: don't swallow it (--token --json ...)
106
- if (nextArg === undefined || nextArg.startsWith('-')) {
107
- console.warn('Warning: --token requires a value. Token not set.');
108
- return i;
109
- }
110
- state.token = nextArg;
111
- return i + 1;
112
- }
113
- /** Handle `--host`/`-H <value>`; refuses to swallow a following flag. */
114
- function applyHostFlag(allArgs, i, state) {
115
- const nextArg = allArgs[i + 1];
116
- if (nextArg === undefined || nextArg.startsWith('-')) {
117
- console.warn('Warning: --host requires a value. Using localhost.');
118
- return i;
119
- }
120
- state.host = nextArg;
121
- state.hostExplicit = true;
122
- return i + 1;
123
- }
124
- /** Handle `--port`/`-p <value>`; refuses to swallow a following flag. */
125
- function applyPortFlag(allArgs, i, state) {
126
- const nextArg = allArgs[i + 1];
127
- if (nextArg === undefined || nextArg.startsWith('-')) {
128
- console.warn('Warning: --port requires a value. Using default port 6789.');
129
- return i;
130
- }
131
- const parsed = parseInt(nextArg, 10);
132
- if (Number.isNaN(parsed) || parsed < 1 || parsed > 65535) {
133
- console.warn(`Warning: Invalid port "${nextArg}". Using default port 6789.`);
134
- state.port = 6789;
135
- }
136
- else {
137
- state.port = parsed;
138
- state.portExplicit = true;
139
- }
140
- return i + 1;
141
- }
142
- /**
143
- * Attached-value short flags (-p10, -Hfoo) shadowing a global flag letter are
144
- * silently mis-parsed downstream (strict:false expands them to garbage
145
- * booleans — e.g. push -p10 dropped the priority and pushed anyway). Warn so
146
- * the user switches to the separated/long form. Exception: -t<value> after
147
- * pull/job wait, where parseArgs handles the attached form correctly.
148
- */
149
- function warnAmbiguousAttachedShort(arg, commandArgs) {
150
- if (!/^-[Hpthv][^-\s]/.test(arg))
151
- return;
152
- if (arg.startsWith('-t') && commandOwnsShortT(commandArgs))
153
- return;
154
- console.warn(`Warning: "${arg}" looks like a short flag with an attached value; ` +
155
- `use the separated form ("${arg.slice(0, 2)} ${arg.slice(2)}") or the long form.`);
156
- }
157
- /** Parse global options from process.argv */
158
- export function parseGlobalOptions() {
159
- const allArgs = process.argv.slice(2);
160
- // Extract global options manually to preserve subcommand flags
161
- const hp = {
162
- host: 'localhost',
163
- port: 6789,
164
- hostExplicit: false,
165
- portExplicit: false,
166
- };
167
- const tokenState = {};
168
- let json = false;
169
- let help = false;
170
- let version = false;
171
- const tlsState = { enabled: false, noVerify: false };
172
- const commandArgs = [];
173
- let i = 0;
174
- while (i < allArgs.length) {
175
- const arg = allArgs[i];
176
- if (arg === '--') {
177
- // Separator: everything after -- is opaque to the global parser
178
- commandArgs.push(...allArgs.slice(i + 1));
179
- break;
180
- }
181
- if (arg === '--host' || arg === '-H') {
182
- i = applyHostFlag(allArgs, i, hp);
183
- }
184
- else if (arg === '--port' || arg === '-p') {
185
- i = applyPortFlag(allArgs, i, hp);
186
- }
187
- else if (arg === '--token' || arg === '-t') {
188
- i = applyTokenFlag(arg, allArgs, i, tokenState, commandArgs);
189
- }
190
- else if (arg.startsWith('--tls')) {
191
- i = applyTlsFlag(arg, allArgs, i, tlsState, commandArgs);
192
- }
193
- else if (arg === '--json') {
194
- json = true;
195
- }
196
- else if (arg === '--help' || (arg === '-h' && commandArgs.length === 0)) {
197
- // Short -h is global help ONLY before the command: after it, a typo of
198
- // -H (host) must not suppress execution with a false-success exit 0.
199
- help = true;
200
- }
201
- else if (arg === '--version' || (arg === '-v' && commandArgs.length === 0)) {
202
- version = true;
203
- }
204
- else if (arg.startsWith('--host=')) {
205
- hp.host = arg.slice(7);
206
- hp.hostExplicit = true;
207
- }
208
- else if (arg.startsWith('--port=')) {
209
- const raw = arg.slice(7);
210
- const parsed = parseInt(raw, 10);
211
- if (Number.isNaN(parsed) || parsed < 1 || parsed > 65535) {
212
- console.warn(`Warning: Invalid port "${raw}". Using default port 6789.`);
213
- hp.port = 6789;
214
- }
215
- else {
216
- hp.port = parsed;
217
- hp.portExplicit = true;
218
- }
219
- }
220
- else if (arg.startsWith('--token=')) {
221
- const val = arg.slice(8);
222
- if (!val) {
223
- console.warn('Warning: --token= requires a value. Token not set.');
224
- }
225
- else {
226
- tokenState.token = val;
227
- }
228
- }
229
- else {
230
- warnAmbiguousAttachedShort(arg, commandArgs);
231
- // Not a global option, pass to command
232
- commandArgs.push(arg);
233
- }
234
- i++;
235
- }
236
- // Detect server mode: explicit 'start', no args, or first arg is a flag (server flags)
237
- const isServerMode = commandArgs[0] === 'start' || commandArgs.length === 0 || commandArgs[0]?.startsWith('-');
238
- // Re-inject explicitly-set --host and --port into commandArgs
239
- // so they reach parseServerArgs in runServer().
240
- // Global -p/--port maps to --tcp-port for the server command.
241
- if (isServerMode) {
242
- if (hp.hostExplicit) {
243
- commandArgs.push('--host', hp.host);
244
- }
245
- if (hp.portExplicit) {
246
- commandArgs.push('--tcp-port', String(hp.port));
247
- }
24
+ async function executeLocalCommand(command, args, host, port, json) {
25
+ if (command === 'version') {
26
+ const info = await collectVersionInfo(host, port + 1);
27
+ finish({
28
+ exitCode: 0,
29
+ json: { ok: true, ...info },
30
+ text: formatVersionInfoText(info),
31
+ }, json);
248
32
  }
249
- // Fall back to environment variables for token if not set via CLI flag
250
- // Priority: --token flag > BQ_TOKEN > BUNQUEUE_TOKEN
251
- const token = resolveToken(tokenState.token);
252
- const port = hp.portExplicit ? hp.port : resolveEnvPort(hp.port);
253
- const host = hp.hostExplicit ? hp.host : resolveEnvHost(hp.host);
254
- return {
255
- options: { host, port, token, tls: buildTlsOption(tlsState), json, help, version },
256
- commandArgs,
257
- };
258
- }
259
- /** Show client + server version */
260
- async function printVersionInfo(host, httpPort) {
261
- console.log(`Client: bunqueue v${VERSION}`);
33
+ if (command === 'doctor') {
34
+ const report = await runDoctor(host, port + 1);
35
+ finish({
36
+ exitCode: report.exitCode,
37
+ json: { ok: report.exitCode === 0, ...report },
38
+ text: formatDoctorText(report),
39
+ }, json);
40
+ }
41
+ if (!isBackupCommand(command))
42
+ return false;
262
43
  try {
263
- const resp = await fetch(`http://${host}:${httpPort}/health`, {
264
- signal: AbortSignal.timeout(3000),
265
- });
266
- if (!resp.ok)
267
- return;
268
- const data = (await resp.json());
269
- if (!data.version)
270
- return;
271
- console.log(`Server: bunqueue v${data.version}`);
272
- if (data.version !== VERSION) {
273
- console.log(`\n⚠ Version mismatch! Update server or client to match.`);
274
- }
275
- }
276
- catch {
277
- console.log(`Server: not reachable (${host}:${httpPort})`);
44
+ const result = await executeBackupCommand(args);
45
+ finish({
46
+ exitCode: result.success ? 0 : 1,
47
+ json: result,
48
+ text: result.data
49
+ ? `${result.message}\n${JSON.stringify(result.data, null, 2)}`
50
+ : result.message,
51
+ }, json);
52
+ }
53
+ catch (error) {
54
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
55
+ finish({
56
+ exitCode: 1,
57
+ json: { success: false, message },
58
+ text: `Error: ${message}`,
59
+ stream: 'stderr',
60
+ }, json);
278
61
  }
279
62
  }
280
- /** Main CLI entry */
281
63
  export async function main() {
282
64
  const { options, commandArgs } = parseGlobalOptions();
283
- // Handle --version
65
+ const command = commandArgs[0];
284
66
  if (options.version) {
285
- printVersion(VERSION);
286
- process.exit(0);
67
+ finish({
68
+ exitCode: 0,
69
+ json: { ok: true, version: VERSION },
70
+ text: renderVersion(VERSION),
71
+ }, options.json);
287
72
  }
288
- // Handle --help with no command
289
- if (options.help && commandArgs.length === 0) {
290
- printHelp();
291
- process.exit(0);
73
+ if (options.help) {
74
+ const help = helpFor(command, !options.json);
75
+ finish({
76
+ exitCode: 0,
77
+ json: { ok: true, help },
78
+ text: help,
79
+ }, options.json);
292
80
  }
293
- // Get the command (first positional argument)
294
- const command = commandArgs[0];
295
- // No command, 'start', or first arg is a flag (server flags like --tcp-port) = server mode
296
81
  if (!command || command === 'start' || command.startsWith('-')) {
297
82
  const serverArgs = command === 'start' ? commandArgs.slice(1) : commandArgs;
298
- await runServer(serverArgs, options.help);
83
+ await runServer(serverArgs, false);
299
84
  return;
300
85
  }
301
- // Help for specific command
302
- if (options.help) {
303
- if (command === 'push')
304
- printPushHelp();
305
- else if (command === 'cron')
306
- printCronAddHelp();
307
- else
308
- printHelp();
309
- process.exit(0);
310
- }
311
- // Version command - shows client version + server version if reachable
312
- if (command === 'version') {
313
- await printVersionInfo(options.host, options.port + 1);
314
- process.exit(0);
315
- }
316
- // Doctor command - diagnostics, not via TCP
317
- if (command === 'doctor') {
318
- await runDoctor(options.host, options.port + 1);
319
- return;
320
- }
321
- // Backup command - executed locally, not via TCP
322
- if (isBackupCommand(command)) {
323
- try {
324
- const result = await executeBackupCommand(commandArgs.slice(1));
325
- if (options.json) {
326
- console.log(JSON.stringify(result, null, 2));
327
- }
328
- else {
329
- console.log(result.message);
330
- if (result.data) {
331
- console.log(JSON.stringify(result.data, null, 2));
332
- }
333
- }
334
- process.exit(result.success ? 0 : 1);
335
- }
336
- catch (err) {
337
- if (err instanceof Error) {
338
- console.error(`Error: ${err.message}`);
339
- }
340
- else {
341
- console.error('Unknown error occurred');
342
- }
343
- process.exit(1);
344
- }
86
+ if (await executeLocalCommand(command, commandArgs.slice(1), options.host, options.port, options.json)) {
345
87
  return;
346
88
  }
347
- // Client mode - execute command against server
348
- try {
349
- await executeCommand(command, commandArgs.slice(1), {
350
- host: options.host,
351
- port: options.port,
352
- token: options.token,
353
- tls: options.tls,
354
- json: options.json,
355
- });
356
- }
357
- catch (err) {
358
- if (err instanceof Error) {
359
- console.error(`Error: ${err.message}`);
360
- }
361
- else {
362
- console.error('Unknown error occurred');
363
- }
364
- process.exit(1);
365
- }
89
+ await executeCommand(command, commandArgs.slice(1), {
90
+ host: options.host,
91
+ port: options.port,
92
+ token: options.token,
93
+ tls: options.tls,
94
+ json: options.json,
95
+ });
366
96
  }
367
- // Run only if this file is the direct entry point
368
97
  if (import.meta.main) {
369
- main().catch((err) => {
370
- console.error('Fatal error:', err);
98
+ main().catch((error) => {
99
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
100
+ const json = process.argv.includes('--json');
101
+ emitLocalOutput({
102
+ exitCode: 1,
103
+ json: { ok: false, error: message },
104
+ text: `Fatal error: ${message}`,
105
+ stream: 'stderr',
106
+ }, json);
371
107
  process.exit(1);
372
108
  });
373
109
  }
@@ -0,0 +1,22 @@
1
+ export interface LocalOutput {
2
+ exitCode: number;
3
+ json: unknown;
4
+ text: string;
5
+ stream?: 'stdout' | 'stderr';
6
+ }
7
+ export interface VersionInfo {
8
+ client: {
9
+ name: 'bunqueue';
10
+ version: string;
11
+ };
12
+ server: {
13
+ endpoint: string;
14
+ reachable: boolean;
15
+ version?: string;
16
+ };
17
+ mismatch: boolean;
18
+ }
19
+ /** The only writer used by local commands, keeping JSON to one stream and document. */
20
+ export declare function emitLocalOutput(output: LocalOutput, json: boolean): void;
21
+ export declare function collectVersionInfo(host: string, httpPort: number): Promise<VersionInfo>;
22
+ export declare function formatVersionInfoText(info: VersionInfo): string;
@@ -0,0 +1,45 @@
1
+ import { VERSION } from '../shared/version';
2
+ /** The only writer used by local commands, keeping JSON to one stream and document. */
3
+ export function emitLocalOutput(output, json) {
4
+ const text = json ? JSON.stringify(output.json, null, 2) : output.text;
5
+ const writer = output.stream === 'stderr' ? console.error : console.log;
6
+ writer(text);
7
+ }
8
+ export async function collectVersionInfo(host, httpPort) {
9
+ const server = {
10
+ endpoint: `${host}:${httpPort}`,
11
+ reachable: false,
12
+ };
13
+ try {
14
+ const response = await fetch(`http://${server.endpoint}/health`, {
15
+ signal: AbortSignal.timeout(3000),
16
+ });
17
+ if (response.ok) {
18
+ const data = (await response.json());
19
+ server.reachable = true;
20
+ if (data.version)
21
+ server.version = data.version;
22
+ }
23
+ }
24
+ catch {
25
+ // Unreachable is part of the version result, not an exceptional path.
26
+ }
27
+ return {
28
+ client: { name: 'bunqueue', version: VERSION },
29
+ server,
30
+ mismatch: server.version !== undefined && server.version !== VERSION,
31
+ };
32
+ }
33
+ export function formatVersionInfoText(info) {
34
+ const lines = [`Client: bunqueue v${info.client.version}`];
35
+ if (!info.server.reachable) {
36
+ lines.push(`Server: not reachable (${info.server.endpoint})`);
37
+ }
38
+ else if (info.server.version) {
39
+ lines.push(`Server: bunqueue v${info.server.version}`);
40
+ if (info.mismatch) {
41
+ lines.push('', '⚠ Version mismatch! Update server or client to match.');
42
+ }
43
+ }
44
+ return lines.join('\n');
45
+ }
@@ -9,9 +9,7 @@ import { getShard, getDlqContext, toDomainFilter, toDomainDlqConfig } from './he
9
9
  import * as dlqOps from '../../application/dlqManager';
10
10
  /** Set DLQ configuration (embedded only) */
11
11
  export function setDlqConfig(queue, config) {
12
- const manager = getSharedManager();
13
- const ctx = getDlqContext(manager);
14
- dlqOps.configureDlq(queue, ctx, toDomainDlqConfig(config));
12
+ getSharedManager().setDlqConfig(queue, toDomainDlqConfig(config));
15
13
  }
16
14
  /** Get DLQ configuration (embedded only) */
17
15
  export function getDlqConfigEmbedded(queue) {
@@ -8,8 +8,8 @@ import { DEFAULT_CONNECTION } from './types';
8
8
  import { HealthTracker } from './health';
9
9
  import { ReconnectManager } from './reconnect';
10
10
  import { createConnection, CommandQueue } from './connection';
11
- import { pack, unpack } from 'msgpackr';
12
11
  import { FrameParser } from '../../infrastructure/server/protocol';
12
+ import { decodeMessagePack, encodeMessagePack } from '../../shared/msgpack';
13
13
  /**
14
14
  * Sentinel error class for the synthetic rejection issued by close()/rejectAll().
15
15
  * Using a dedicated subclass (instead of matching `error.message`) ensures the
@@ -55,11 +55,11 @@ function installClientClosedFilter() {
55
55
  * Supports pipelining for high-throughput operations
56
56
  */
57
57
  export class TcpClient extends EventEmitter {
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ // biome-ignore lint/suspicious/noExplicitAny: EventEmitter's fallback overload requires any[]
59
59
  on(event, listener) {
60
60
  return super.on(event, listener);
61
61
  }
62
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ // biome-ignore lint/suspicious/noExplicitAny: EventEmitter's fallback overload requires any[]
63
63
  once(event, listener) {
64
64
  return super.once(event, listener);
65
65
  }
@@ -174,7 +174,7 @@ export class TcpClient extends EventEmitter {
174
174
  */
175
175
  handleData(frame) {
176
176
  try {
177
- const response = unpack(frame);
177
+ const response = decodeMessagePack(frame);
178
178
  const reqId = response.reqId;
179
179
  // Try pipelining mode first (reqId-based matching)
180
180
  if (reqId) {
@@ -352,7 +352,7 @@ export class TcpClient extends EventEmitter {
352
352
  pendingRef.promise = promise;
353
353
  this.commands.addInFlight(pendingRef);
354
354
  // Send immediately (this.socket null-checked at entry of sendDirect)
355
- this.socket.write(FrameParser.frame(pack(commandWithReqId)));
355
+ this.socket.write(FrameParser.frame(encodeMessagePack(commandWithReqId)));
356
356
  return promise;
357
357
  }
358
358
  /**
@@ -381,14 +381,14 @@ export class TcpClient extends EventEmitter {
381
381
  next.timeout = newTimeout;
382
382
  // Add to in-flight tracking and send
383
383
  this.commands.addInFlight(next);
384
- this.socket.write(FrameParser.frame(pack(next.command)));
384
+ this.socket.write(FrameParser.frame(encodeMessagePack(next.command)));
385
385
  }
386
386
  }
387
387
  /**
388
388
  * Send command and wait for response
389
389
  * Uses pipelining for high throughput
390
390
  */
391
- async send(command) {
391
+ send(command) {
392
392
  const startTime = Date.now();
393
393
  this.health.recordCommandSent();
394
394
  const reqId = this.generateReqId();
@@ -435,7 +435,9 @@ export class TcpClient extends EventEmitter {
435
435
  // Connect if needed, then process queue
436
436
  if (!this.connected && !this.connecting) {
437
437
  // Connection errors during send are handled by command timeout/rejection
438
- this.connect().catch(() => { });
438
+ this.connect().catch(() => {
439
+ // The queued command owns timeout and rejection reporting.
440
+ });
439
441
  }
440
442
  else if (this.connected) {
441
443
  this.processQueue();
@@ -51,8 +51,8 @@ export declare class Shard {
51
51
  notify(queue?: string): void;
52
52
  notifyBatch(count: number): void;
53
53
  notifyBatch(queue: string, count: number): void;
54
- waitForJob(timeoutMs: number): Promise<void>;
55
- waitForJob(queue: string, timeoutMs: number): Promise<void>;
54
+ waitForJob(timeoutMs: number, signal?: AbortSignal): Promise<void>;
55
+ waitForJob(queue: string, timeoutMs: number, signal?: AbortSignal): Promise<void>;
56
56
  getQueue(name: string): IndexedPriorityQueue;
57
57
  getState(name: string): QueueState;
58
58
  isPaused(name: string): boolean;
@@ -69,11 +69,11 @@ export class Shard {
69
69
  this.waiterManager.notifyBatch(queueOrCount, maybeCount ?? 0);
70
70
  }
71
71
  }
72
- waitForJob(queueOrTimeout, maybeTimeout) {
72
+ waitForJob(queueOrTimeout, maybeTimeoutOrSignal, maybeSignal) {
73
73
  if (typeof queueOrTimeout === 'number') {
74
- return this.waiterManager.waitForJob(queueOrTimeout);
74
+ return this.waiterManager.waitForJob(queueOrTimeout, maybeTimeoutOrSignal);
75
75
  }
76
- return this.waiterManager.waitForJob(queueOrTimeout, maybeTimeout ?? 0);
76
+ return this.waiterManager.waitForJob(queueOrTimeout, typeof maybeTimeoutOrSignal === 'number' ? maybeTimeoutOrSignal : 0, maybeSignal);
77
77
  }
78
78
  // ============ Queue Operations ============
79
79
  getQueue(name) {
@@ -8,8 +8,8 @@ export declare class WaiterManager {
8
8
  notify(queue?: string): void;
9
9
  notifyBatch(count: number): void;
10
10
  notifyBatch(queue: string, count: number): void;
11
- waitForJob(timeoutMs: number): Promise<void>;
12
- waitForJob(queue: string, timeoutMs: number): Promise<void>;
11
+ waitForJob(timeoutMs: number, signal?: AbortSignal): Promise<void>;
12
+ waitForJob(queue: string, timeoutMs: number, signal?: AbortSignal): Promise<void>;
13
13
  private notifyQueue;
14
14
  private createQueue;
15
15
  private compact;