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.
- package/dist/application/backgroundTasks.js +9 -0
- package/dist/application/cleanupTasks.js +31 -11
- package/dist/application/contextFactory.d.ts +3 -0
- package/dist/application/contextFactory.js +6 -0
- package/dist/application/dependencyResultTracker.d.ts +17 -0
- package/dist/application/dependencyResultTracker.js +71 -0
- package/dist/application/operations/ack.d.ts +2 -0
- package/dist/application/operations/ack.js +6 -0
- package/dist/application/operations/ackHelpers.d.ts +2 -1
- package/dist/application/operations/ackHelpers.js +6 -0
- package/dist/application/operations/jobManagement.d.ts +2 -0
- package/dist/application/operations/jobManagement.js +10 -4
- package/dist/application/operations/jobStateTransitions.js +1 -0
- package/dist/application/operations/pull.d.ts +2 -2
- package/dist/application/operations/pull.js +43 -10
- package/dist/application/operations/push.d.ts +2 -0
- package/dist/application/operations/pushInsert.d.ts +4 -0
- package/dist/application/operations/pushInsert.js +6 -0
- package/dist/application/operations/queryOperations.d.ts +2 -0
- package/dist/application/operations/queryOperations.js +5 -1
- package/dist/application/operations/queueControl.d.ts +2 -0
- package/dist/application/operations/queueControl.js +4 -0
- package/dist/application/queueManager.d.ts +5 -4
- package/dist/application/queueManager.js +37 -11
- package/dist/application/types.d.ts +2 -0
- package/dist/cli/client.js +6 -47
- package/dist/cli/commandRegistry.d.ts +27 -0
- package/dist/cli/commandRegistry.js +40 -0
- package/dist/cli/commandRouter.d.ts +3 -0
- package/dist/cli/commandRouter.js +44 -0
- package/dist/cli/commands/backup.js +1 -1
- package/dist/cli/commands/core.js +54 -2
- package/dist/cli/commands/doctor.d.ts +50 -5
- package/dist/cli/commands/doctor.js +125 -108
- package/dist/cli/commands/types.js +5 -1
- package/dist/cli/globalOptions.d.ts +16 -0
- package/dist/cli/globalOptions.js +192 -0
- package/dist/cli/help.d.ts +9 -0
- package/dist/cli/help.js +42 -21
- package/dist/cli/index.d.ts +1 -25
- package/dist/cli/index.js +84 -348
- package/dist/cli/localOutput.d.ts +22 -0
- package/dist/cli/localOutput.js +45 -0
- package/dist/client/queue/dlqOps.js +1 -3
- package/dist/client/tcp/client.js +10 -8
- package/dist/domain/queue/shard.d.ts +2 -2
- package/dist/domain/queue/shard.js +3 -3
- package/dist/domain/queue/waiterManager.d.ts +2 -2
- package/dist/domain/queue/waiterManager.js +26 -5
- package/dist/infrastructure/cloud/wsSender.js +4 -4
- package/dist/infrastructure/persistence/schema.d.ts +2 -2
- package/dist/infrastructure/persistence/schema.js +7 -2
- package/dist/infrastructure/persistence/sqlite.d.ts +5 -1
- package/dist/infrastructure/persistence/sqlite.js +10 -2
- package/dist/infrastructure/persistence/sqliteSerializer.js +1 -1
- package/dist/infrastructure/persistence/statements.d.ts +2 -1
- package/dist/infrastructure/persistence/statements.js +3 -2
- package/dist/infrastructure/server/handlers/core.js +4 -4
- package/dist/infrastructure/server/tcp.d.ts +1 -0
- package/dist/infrastructure/server/tcp.js +23 -12
- package/dist/infrastructure/server/types.d.ts +2 -0
- package/dist/shared/msgpack.d.ts +3 -0
- package/dist/shared/msgpack.js +70 -0
- 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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
65
|
+
const command = commandArgs[0];
|
|
284
66
|
if (options.version) {
|
|
285
|
-
|
|
286
|
-
|
|
67
|
+
finish({
|
|
68
|
+
exitCode: 0,
|
|
69
|
+
json: { ok: true, version: VERSION },
|
|
70
|
+
text: renderVersion(VERSION),
|
|
71
|
+
}, options.json);
|
|
287
72
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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,
|
|
83
|
+
await runServer(serverArgs, false);
|
|
299
84
|
return;
|
|
300
85
|
}
|
|
301
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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((
|
|
370
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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;
|