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
|
@@ -31,6 +31,7 @@ export declare class QueueManager {
|
|
|
31
31
|
private readonly depCompletions;
|
|
32
32
|
private readonly timedOutJobs;
|
|
33
33
|
private readonly jobResults;
|
|
34
|
+
private readonly dependencyResults;
|
|
34
35
|
private readonly customIdMap;
|
|
35
36
|
private readonly jobLogs;
|
|
36
37
|
private readonly pendingDepChecks;
|
|
@@ -64,13 +65,13 @@ export declare class QueueManager {
|
|
|
64
65
|
private handleRepeat;
|
|
65
66
|
push(queue: string, input: JobInput): Promise<Job>;
|
|
66
67
|
pushBatch(queue: string, inputs: JobInput[]): Promise<JobId[]>;
|
|
67
|
-
pull(queue: string, timeoutMs?: number): Promise<Job | null>;
|
|
68
|
-
pullWithLock(queue: string, owner: string, timeoutMs?: number, lockTtl?: number): Promise<{
|
|
68
|
+
pull(queue: string, timeoutMs?: number, signal?: AbortSignal): Promise<Job | null>;
|
|
69
|
+
pullWithLock(queue: string, owner: string, timeoutMs?: number, lockTtl?: number, signal?: AbortSignal): Promise<{
|
|
69
70
|
job: Job | null;
|
|
70
71
|
token: string | null;
|
|
71
72
|
}>;
|
|
72
|
-
pullBatch(queue: string, count: number, timeoutMs?: number): Promise<Job[]>;
|
|
73
|
-
pullBatchWithLock(queue: string, count: number, owner: string, timeoutMs?: number, lockTtl?: number): Promise<{
|
|
73
|
+
pullBatch(queue: string, count: number, timeoutMs?: number, signal?: AbortSignal): Promise<Job[]>;
|
|
74
|
+
pullBatchWithLock(queue: string, count: number, owner: string, timeoutMs?: number, lockTtl?: number, signal?: AbortSignal): Promise<{
|
|
74
75
|
jobs: Job[];
|
|
75
76
|
tokens: string[];
|
|
76
77
|
}>;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { DEFAULT_LOCK_TTL } from '../domain/types/job';
|
|
6
6
|
import { DEFAULT_STALL_CONFIG } from '../domain/types/stall';
|
|
7
|
+
import { DEFAULT_DLQ_CONFIG } from '../domain/types/dlq';
|
|
7
8
|
import { Shard } from '../domain/queue/shard';
|
|
8
9
|
import { SqliteStorage } from '../infrastructure/persistence/sqlite';
|
|
9
10
|
import { CronScheduler } from '../infrastructure/scheduler/cronScheduler';
|
|
@@ -32,6 +33,7 @@ import * as statsMgr from './statsManager';
|
|
|
32
33
|
import { ContextFactory } from './contextFactory';
|
|
33
34
|
import { processPendingDependencies } from './dependencyProcessor';
|
|
34
35
|
import { handleTaskError, handleTaskSuccess } from './taskErrorTracking';
|
|
36
|
+
import { DependencyResultTracker } from './dependencyResultTracker';
|
|
35
37
|
/**
|
|
36
38
|
* QueueManager - Central coordinator
|
|
37
39
|
*/
|
|
@@ -58,6 +60,7 @@ export class QueueManager {
|
|
|
58
60
|
// current token and bypasses the stale-token recovery path entirely.
|
|
59
61
|
timedOutJobs;
|
|
60
62
|
jobResults;
|
|
63
|
+
dependencyResults = new DependencyResultTracker();
|
|
61
64
|
customIdMap;
|
|
62
65
|
jobLogs;
|
|
63
66
|
// Deferred dependency resolution queue
|
|
@@ -178,6 +181,7 @@ export class QueueManager {
|
|
|
178
181
|
depCompletions: this.depCompletions,
|
|
179
182
|
timedOutJobs: this.timedOutJobs,
|
|
180
183
|
jobResults: this.jobResults,
|
|
184
|
+
dependencyResults: this.dependencyResults,
|
|
181
185
|
customIdMap: this.customIdMap,
|
|
182
186
|
jobLogs: this.jobLogs,
|
|
183
187
|
jobLocks: this.jobLocks,
|
|
@@ -254,21 +258,22 @@ export class QueueManager {
|
|
|
254
258
|
this.registerQueueName(queue);
|
|
255
259
|
return pushJobBatch(queue, inputs, this.contextFactory.getPushContext());
|
|
256
260
|
}
|
|
257
|
-
async pull(queue, timeoutMs = 0) {
|
|
258
|
-
return pullJob(queue, timeoutMs, this.contextFactory.getPullContext());
|
|
261
|
+
async pull(queue, timeoutMs = 0, signal) {
|
|
262
|
+
return pullJob(queue, timeoutMs, this.contextFactory.getPullContext(), signal);
|
|
259
263
|
}
|
|
260
|
-
async pullWithLock(queue, owner, timeoutMs = 0, lockTtl = DEFAULT_LOCK_TTL) {
|
|
261
|
-
const job = await pullJob(queue, timeoutMs, this.contextFactory.getPullContext());
|
|
264
|
+
async pullWithLock(queue, owner, timeoutMs = 0, lockTtl = DEFAULT_LOCK_TTL, signal) {
|
|
265
|
+
const job = await pullJob(queue, timeoutMs, this.contextFactory.getPullContext(), signal);
|
|
262
266
|
if (!job)
|
|
263
267
|
return { job: null, token: null };
|
|
264
268
|
const token = lockMgr.createLock(job.id, owner, this.contextFactory.getLockContext(), lockTtl);
|
|
265
269
|
return { job, token };
|
|
266
270
|
}
|
|
267
|
-
async pullBatch(queue, count, timeoutMs = 0) {
|
|
268
|
-
return pullJobBatch(queue, count, timeoutMs, this.contextFactory.getPullContext());
|
|
271
|
+
async pullBatch(queue, count, timeoutMs = 0, signal) {
|
|
272
|
+
return pullJobBatch(queue, count, timeoutMs, this.contextFactory.getPullContext(), signal);
|
|
269
273
|
}
|
|
270
|
-
|
|
271
|
-
|
|
274
|
+
// biome-ignore lint/complexity/useMaxParams: preserves the public API while adding cancellation
|
|
275
|
+
async pullBatchWithLock(queue, count, owner, timeoutMs = 0, lockTtl = DEFAULT_LOCK_TTL, signal) {
|
|
276
|
+
const jobs = await pullJobBatch(queue, count, timeoutMs, this.contextFactory.getPullContext(), signal);
|
|
272
277
|
const tokens = [];
|
|
273
278
|
for (const job of jobs) {
|
|
274
279
|
const token = lockMgr.createLock(job.id, owner, this.contextFactory.getLockContext(), lockTtl);
|
|
@@ -541,6 +546,9 @@ export class QueueManager {
|
|
|
541
546
|
ctx.jobIndex.delete(jId);
|
|
542
547
|
ctx.storage?.deleteJob(jId);
|
|
543
548
|
}
|
|
549
|
+
if (result !== undefined)
|
|
550
|
+
ctx.dependencyResults.retain(jId, result);
|
|
551
|
+
ctx.dependencyResults.releaseConsumer(jId);
|
|
544
552
|
ctx.totalCompleted.value++;
|
|
545
553
|
ctx.broadcast({
|
|
546
554
|
eventType: 'completed',
|
|
@@ -769,6 +777,7 @@ export class QueueManager {
|
|
|
769
777
|
this.jobResults.delete(jid);
|
|
770
778
|
this.jobLogs.delete(jid);
|
|
771
779
|
this.jobLocks.delete(jid);
|
|
780
|
+
this.dependencyResults.releaseConsumer(jid);
|
|
772
781
|
this.failedChildrenValues.delete(jid);
|
|
773
782
|
this.ignoredChildrenFailures.delete(jid);
|
|
774
783
|
this.pendingDepChecks.delete(jid);
|
|
@@ -887,10 +896,16 @@ export class QueueManager {
|
|
|
887
896
|
return;
|
|
888
897
|
const state = this.shards[shardIndex(queue)].getState(queue);
|
|
889
898
|
const stallConfig = this.shards[shardIndex(queue)].getStallConfig(queue);
|
|
899
|
+
const dlqConfig = this.shards[shardIndex(queue)].getDlqConfig(queue);
|
|
890
900
|
const hasCustomStallConfig = stallConfig.enabled !== DEFAULT_STALL_CONFIG.enabled ||
|
|
891
901
|
stallConfig.stallInterval !== DEFAULT_STALL_CONFIG.stallInterval ||
|
|
892
902
|
stallConfig.maxStalls !== DEFAULT_STALL_CONFIG.maxStalls ||
|
|
893
903
|
stallConfig.gracePeriod !== DEFAULT_STALL_CONFIG.gracePeriod;
|
|
904
|
+
const hasCustomDlqConfig = dlqConfig.autoRetry !== DEFAULT_DLQ_CONFIG.autoRetry ||
|
|
905
|
+
dlqConfig.autoRetryInterval !== DEFAULT_DLQ_CONFIG.autoRetryInterval ||
|
|
906
|
+
dlqConfig.maxAutoRetries !== DEFAULT_DLQ_CONFIG.maxAutoRetries ||
|
|
907
|
+
dlqConfig.maxAge !== DEFAULT_DLQ_CONFIG.maxAge ||
|
|
908
|
+
dlqConfig.maxEntries !== DEFAULT_DLQ_CONFIG.maxEntries;
|
|
894
909
|
// When control-state returns fully to default (not paused, no limits), drop
|
|
895
910
|
// the row instead of persisting an all-default placeholder. Keeps the table
|
|
896
911
|
// free of noise rows for ephemeral queues that only ever call resume/clear*,
|
|
@@ -898,7 +913,8 @@ export class QueueManager {
|
|
|
898
913
|
if (!state.paused &&
|
|
899
914
|
state.rateLimit === null &&
|
|
900
915
|
state.concurrencyLimit === null &&
|
|
901
|
-
!hasCustomStallConfig
|
|
916
|
+
!hasCustomStallConfig &&
|
|
917
|
+
!hasCustomDlqConfig) {
|
|
902
918
|
this.storage.deleteQueueState(queue);
|
|
903
919
|
return;
|
|
904
920
|
}
|
|
@@ -909,6 +925,7 @@ export class QueueManager {
|
|
|
909
925
|
rateLimitDuration: state.rateLimitDuration,
|
|
910
926
|
rateLimitExpiresAt: state.rateLimitExpiresAt,
|
|
911
927
|
stallConfig,
|
|
928
|
+
dlqConfig,
|
|
912
929
|
});
|
|
913
930
|
}
|
|
914
931
|
/** Get rate limit and concurrency limit for a queue */
|
|
@@ -947,6 +964,7 @@ export class QueueManager {
|
|
|
947
964
|
}
|
|
948
965
|
setDlqConfig(queue, config) {
|
|
949
966
|
this.shards[shardIndex(queue)].setDlqConfig(queue, config);
|
|
967
|
+
this.persistQueueState(queue);
|
|
950
968
|
}
|
|
951
969
|
getDlqConfig(queue) {
|
|
952
970
|
return this.shards[shardIndex(queue)].getDlqConfig(queue);
|
|
@@ -1259,6 +1277,7 @@ export class QueueManager {
|
|
|
1259
1277
|
// Parent reached a terminal (DLQ) state — release its flow-failure tracking.
|
|
1260
1278
|
this.failedChildrenValues.delete(parentId);
|
|
1261
1279
|
this.ignoredChildrenFailures.delete(parentId);
|
|
1280
|
+
this.dependencyResults.releaseConsumer(parentId);
|
|
1262
1281
|
// Broadcast failed event for parent
|
|
1263
1282
|
this.eventsManager.broadcast({
|
|
1264
1283
|
eventType: 'failed',
|
|
@@ -1282,11 +1301,15 @@ export class QueueManager {
|
|
|
1282
1301
|
if (!childJob.parentId)
|
|
1283
1302
|
return;
|
|
1284
1303
|
if (childJob.continueParentOnFailure) {
|
|
1285
|
-
this.continueParentOnChildFailure(childJob, error).catch(() => {
|
|
1304
|
+
this.continueParentOnChildFailure(childJob, error).catch(() => {
|
|
1305
|
+
// Best-effort parent propagation; child failure is already durable.
|
|
1306
|
+
});
|
|
1286
1307
|
}
|
|
1287
1308
|
else {
|
|
1288
1309
|
// removeDependencyOnFailure or ignoreDependencyOnFailure
|
|
1289
|
-
this.removeChildFromParentDeps(childJob, error, childJob.ignoreDependencyOnFailure).catch(() => {
|
|
1310
|
+
this.removeChildFromParentDeps(childJob, error, childJob.ignoreDependencyOnFailure).catch(() => {
|
|
1311
|
+
// Best-effort parent propagation; child failure is already durable.
|
|
1312
|
+
});
|
|
1290
1313
|
}
|
|
1291
1314
|
}
|
|
1292
1315
|
/**
|
|
@@ -1396,6 +1419,7 @@ export class QueueManager {
|
|
|
1396
1419
|
if (depIndex !== -1) {
|
|
1397
1420
|
parentJob.dependsOn.splice(depIndex, 1);
|
|
1398
1421
|
shard.unregisterDependencies(parentId, [childJob.id]);
|
|
1422
|
+
this.dependencyResults.releaseDependency(parentId, childJob.id);
|
|
1399
1423
|
}
|
|
1400
1424
|
// If no more pending deps, the parent is ready to be promoted
|
|
1401
1425
|
readyToPromote =
|
|
@@ -1449,6 +1473,7 @@ export class QueueManager {
|
|
|
1449
1473
|
if (depIndex !== -1) {
|
|
1450
1474
|
parentJob.dependsOn.splice(depIndex, 1);
|
|
1451
1475
|
shard.unregisterDependencies(parentId, [childJobId]);
|
|
1476
|
+
this.dependencyResults.releaseDependency(parentId, childJobId);
|
|
1452
1477
|
}
|
|
1453
1478
|
const allDone = parentJob.dependsOn.length === 0 ||
|
|
1454
1479
|
parentJob.dependsOn.every((dep) => this.completedJobs.has(dep));
|
|
@@ -1615,6 +1640,7 @@ export class QueueManager {
|
|
|
1615
1640
|
this.depCompletions.clear();
|
|
1616
1641
|
this.timedOutJobs.clear();
|
|
1617
1642
|
this.jobResults.clear();
|
|
1643
|
+
this.dependencyResults.clear();
|
|
1618
1644
|
this.jobLogs.clear();
|
|
1619
1645
|
this.customIdMap.clear();
|
|
1620
1646
|
this.pendingDepChecks.clear();
|
|
@@ -12,6 +12,7 @@ import type { EventsManager } from './eventsManager';
|
|
|
12
12
|
import type { WebhookManager } from './webhookManager';
|
|
13
13
|
import type { WorkerManager } from './workerManager';
|
|
14
14
|
import type { MonitoringState } from './monitoringChecks';
|
|
15
|
+
import type { DependencyResultTracker } from './dependencyResultTracker';
|
|
15
16
|
/** Queue Manager configuration */
|
|
16
17
|
export interface QueueManagerConfig {
|
|
17
18
|
dataPath?: string;
|
|
@@ -52,6 +53,7 @@ export interface QueueManagerState {
|
|
|
52
53
|
readonly jobIndex: Map<JobId, JobLocation>;
|
|
53
54
|
readonly completedJobs: BoundedSet<JobId>;
|
|
54
55
|
readonly jobResults: LRUMap<JobId, unknown>;
|
|
56
|
+
readonly dependencyResults: DependencyResultTracker;
|
|
55
57
|
readonly customIdMap: LRUMap<string, JobId>;
|
|
56
58
|
readonly jobLogs: LRUMap<JobId, JobLogEntry[]>;
|
|
57
59
|
readonly jobLocks: Map<JobId, JobLock>;
|
package/dist/cli/client.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Connects to bunQ server and executes commands (msgpack binary protocol)
|
|
4
4
|
*/
|
|
5
5
|
import { formatOutput, formatError } from './output';
|
|
6
|
-
import { pack, unpack } from 'msgpackr';
|
|
7
6
|
import { FrameParser, FrameSizeError } from '../infrastructure/server/protocol';
|
|
8
7
|
import { CommandError } from './commands/types';
|
|
9
8
|
import { buildClientTls } from '../client/tcp/connection';
|
|
9
|
+
import { buildCommand } from './commandRouter';
|
|
10
|
+
import { decodeMessagePack, encodeMessagePack } from '../shared/msgpack';
|
|
10
11
|
/**
|
|
11
12
|
* Client-side timeout for a command: base 30s; ONLY the long-poll commands
|
|
12
13
|
* (PULL/WaitJob, where `timeout` means "how long the server may hold the
|
|
@@ -21,7 +22,7 @@ export function clientTimeoutFor(cmd) {
|
|
|
21
22
|
return Math.max(30000, cmdTimeout + 10000);
|
|
22
23
|
}
|
|
23
24
|
/** Send a command and wait for response */
|
|
24
|
-
|
|
25
|
+
function sendCommand(socket, command, timeoutMs = 30000) {
|
|
25
26
|
return new Promise((resolve, reject) => {
|
|
26
27
|
const timeoutId = setTimeout(() => {
|
|
27
28
|
socket.data.resolve = null;
|
|
@@ -36,11 +37,11 @@ async function sendCommand(socket, command, timeoutMs = 30000) {
|
|
|
36
37
|
clearTimeout(timeoutId);
|
|
37
38
|
reject(error);
|
|
38
39
|
};
|
|
39
|
-
socket.write(FrameParser.frame(
|
|
40
|
+
socket.write(FrameParser.frame(encodeMessagePack(command)));
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
43
|
/** Create TCP connection */
|
|
43
|
-
|
|
44
|
+
function connect(options) {
|
|
44
45
|
return new Promise((resolve, reject) => {
|
|
45
46
|
const socketData = {
|
|
46
47
|
frameParser: new FrameParser(),
|
|
@@ -73,7 +74,7 @@ async function connect(options) {
|
|
|
73
74
|
for (const frame of frames) {
|
|
74
75
|
if (socketData.resolve) {
|
|
75
76
|
try {
|
|
76
|
-
const response =
|
|
77
|
+
const response = decodeMessagePack(frame);
|
|
77
78
|
socketData.resolve(response);
|
|
78
79
|
socketData.resolve = null;
|
|
79
80
|
socketData.reject = null;
|
|
@@ -200,45 +201,3 @@ export async function executeCommand(command, args, options) {
|
|
|
200
201
|
connection?.close();
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
|
-
/** Build a command from CLI arguments */
|
|
204
|
-
async function buildCommand(command, args) {
|
|
205
|
-
// Import command builders
|
|
206
|
-
const { buildCoreCommand } = await import('./commands/core');
|
|
207
|
-
const { buildJobCommand } = await import('./commands/job');
|
|
208
|
-
const { buildQueueCommand } = await import('./commands/queue');
|
|
209
|
-
const { buildDlqCommand } = await import('./commands/dlq');
|
|
210
|
-
const { buildCronCommand } = await import('./commands/cron');
|
|
211
|
-
const { buildWorkerCommand } = await import('./commands/worker');
|
|
212
|
-
const { buildWebhookCommand } = await import('./commands/webhook');
|
|
213
|
-
const { buildRateLimitCommand } = await import('./commands/rateLimit');
|
|
214
|
-
const { buildMonitorCommand } = await import('./commands/monitor');
|
|
215
|
-
switch (command) {
|
|
216
|
-
case 'push':
|
|
217
|
-
case 'pull':
|
|
218
|
-
case 'ack':
|
|
219
|
-
case 'fail':
|
|
220
|
-
return buildCoreCommand(command, args);
|
|
221
|
-
case 'job':
|
|
222
|
-
return buildJobCommand(args);
|
|
223
|
-
case 'queue':
|
|
224
|
-
return buildQueueCommand(args);
|
|
225
|
-
case 'dlq':
|
|
226
|
-
return buildDlqCommand(args);
|
|
227
|
-
case 'cron':
|
|
228
|
-
return buildCronCommand(args);
|
|
229
|
-
case 'worker':
|
|
230
|
-
return buildWorkerCommand(args);
|
|
231
|
-
case 'webhook':
|
|
232
|
-
return buildWebhookCommand(args);
|
|
233
|
-
case 'rate-limit':
|
|
234
|
-
case 'concurrency':
|
|
235
|
-
return buildRateLimitCommand(command, args);
|
|
236
|
-
case 'stats':
|
|
237
|
-
case 'metrics':
|
|
238
|
-
case 'health':
|
|
239
|
-
case 'ping':
|
|
240
|
-
return buildMonitorCommand(command);
|
|
241
|
-
default:
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Canonical CLI command surface used by routing, help conformance, and tests. */
|
|
2
|
+
export declare const CLI_COMMAND_SURFACE: {
|
|
3
|
+
readonly push: readonly [];
|
|
4
|
+
readonly pull: readonly [];
|
|
5
|
+
readonly ack: readonly [];
|
|
6
|
+
readonly fail: readonly [];
|
|
7
|
+
readonly job: readonly ["get", "state", "result", "cancel", "progress", "update", "priority", "promote", "delay", "discard", "logs", "log", "wait"];
|
|
8
|
+
readonly queue: readonly ["list", "pause", "resume", "drain", "obliterate", "clean", "count", "jobs", "paused"];
|
|
9
|
+
readonly 'rate-limit': readonly ["set", "clear"];
|
|
10
|
+
readonly concurrency: readonly ["set", "clear"];
|
|
11
|
+
readonly dlq: readonly ["list", "retry", "purge"];
|
|
12
|
+
readonly cron: readonly ["list", "add", "delete"];
|
|
13
|
+
readonly worker: readonly ["list", "register", "unregister"];
|
|
14
|
+
readonly webhook: readonly ["list", "add", "remove"];
|
|
15
|
+
readonly stats: readonly [];
|
|
16
|
+
readonly metrics: readonly [];
|
|
17
|
+
readonly health: readonly [];
|
|
18
|
+
readonly ping: readonly [];
|
|
19
|
+
};
|
|
20
|
+
export type CliNetworkCommand = keyof typeof CLI_COMMAND_SURFACE;
|
|
21
|
+
/** Commands handled locally instead of being translated to a TCP command. */
|
|
22
|
+
export declare const CLI_LOCAL_COMMAND_SURFACE: {
|
|
23
|
+
readonly start: readonly [];
|
|
24
|
+
readonly version: readonly [];
|
|
25
|
+
readonly doctor: readonly [];
|
|
26
|
+
readonly backup: readonly ["now", "create", "list", "restore", "status"];
|
|
27
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Canonical CLI command surface used by routing, help conformance, and tests. */
|
|
2
|
+
export const CLI_COMMAND_SURFACE = {
|
|
3
|
+
push: [],
|
|
4
|
+
pull: [],
|
|
5
|
+
ack: [],
|
|
6
|
+
fail: [],
|
|
7
|
+
job: [
|
|
8
|
+
'get',
|
|
9
|
+
'state',
|
|
10
|
+
'result',
|
|
11
|
+
'cancel',
|
|
12
|
+
'progress',
|
|
13
|
+
'update',
|
|
14
|
+
'priority',
|
|
15
|
+
'promote',
|
|
16
|
+
'delay',
|
|
17
|
+
'discard',
|
|
18
|
+
'logs',
|
|
19
|
+
'log',
|
|
20
|
+
'wait',
|
|
21
|
+
],
|
|
22
|
+
queue: ['list', 'pause', 'resume', 'drain', 'obliterate', 'clean', 'count', 'jobs', 'paused'],
|
|
23
|
+
'rate-limit': ['set', 'clear'],
|
|
24
|
+
concurrency: ['set', 'clear'],
|
|
25
|
+
dlq: ['list', 'retry', 'purge'],
|
|
26
|
+
cron: ['list', 'add', 'delete'],
|
|
27
|
+
worker: ['list', 'register', 'unregister'],
|
|
28
|
+
webhook: ['list', 'add', 'remove'],
|
|
29
|
+
stats: [],
|
|
30
|
+
metrics: [],
|
|
31
|
+
health: [],
|
|
32
|
+
ping: [],
|
|
33
|
+
};
|
|
34
|
+
/** Commands handled locally instead of being translated to a TCP command. */
|
|
35
|
+
export const CLI_LOCAL_COMMAND_SURFACE = {
|
|
36
|
+
start: [],
|
|
37
|
+
version: [],
|
|
38
|
+
doctor: [],
|
|
39
|
+
backup: ['now', 'create', 'list', 'restore', 'status'],
|
|
40
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const CLI_NETWORK_COMMANDS: readonly ("push" | "queue" | "dlq" | "job" | "cron" | "worker" | "concurrency" | "ping" | "stats" | "metrics" | "pull" | "ack" | "fail" | "rate-limit" | "health" | "webhook")[];
|
|
2
|
+
/** Build the protocol command for a routed network CLI command. */
|
|
3
|
+
export declare function buildCommand(command: string, args: string[]): Promise<Record<string, unknown> | null>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CLI_COMMAND_SURFACE } from './commandRegistry';
|
|
2
|
+
export const CLI_NETWORK_COMMANDS = Object.freeze(Object.keys(CLI_COMMAND_SURFACE));
|
|
3
|
+
/** Build the protocol command for a routed network CLI command. */
|
|
4
|
+
export async function buildCommand(command, args) {
|
|
5
|
+
if (!CLI_NETWORK_COMMANDS.includes(command))
|
|
6
|
+
return null;
|
|
7
|
+
const { buildCoreCommand } = await import('./commands/core');
|
|
8
|
+
const { buildJobCommand } = await import('./commands/job');
|
|
9
|
+
const { buildQueueCommand } = await import('./commands/queue');
|
|
10
|
+
const { buildDlqCommand } = await import('./commands/dlq');
|
|
11
|
+
const { buildCronCommand } = await import('./commands/cron');
|
|
12
|
+
const { buildWorkerCommand } = await import('./commands/worker');
|
|
13
|
+
const { buildWebhookCommand } = await import('./commands/webhook');
|
|
14
|
+
const { buildRateLimitCommand } = await import('./commands/rateLimit');
|
|
15
|
+
const { buildMonitorCommand } = await import('./commands/monitor');
|
|
16
|
+
switch (command) {
|
|
17
|
+
case 'push':
|
|
18
|
+
case 'pull':
|
|
19
|
+
case 'ack':
|
|
20
|
+
case 'fail':
|
|
21
|
+
return buildCoreCommand(command, args);
|
|
22
|
+
case 'job':
|
|
23
|
+
return buildJobCommand(args);
|
|
24
|
+
case 'queue':
|
|
25
|
+
return buildQueueCommand(args);
|
|
26
|
+
case 'dlq':
|
|
27
|
+
return buildDlqCommand(args);
|
|
28
|
+
case 'cron':
|
|
29
|
+
return buildCronCommand(args);
|
|
30
|
+
case 'worker':
|
|
31
|
+
return buildWorkerCommand(args);
|
|
32
|
+
case 'webhook':
|
|
33
|
+
return buildWebhookCommand(args);
|
|
34
|
+
case 'rate-limit':
|
|
35
|
+
case 'concurrency':
|
|
36
|
+
return buildRateLimitCommand(command, args);
|
|
37
|
+
case 'stats':
|
|
38
|
+
case 'metrics':
|
|
39
|
+
case 'health':
|
|
40
|
+
case 'ping':
|
|
41
|
+
return buildMonitorCommand(command);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
@@ -42,7 +42,7 @@ export async function executeBackupCommand(args) {
|
|
|
42
42
|
case 'status':
|
|
43
43
|
return executeBackupStatus(manager);
|
|
44
44
|
default:
|
|
45
|
-
throw new CommandError(`Unknown backup subcommand: ${subcommand}. Use: now, list, restore, status`);
|
|
45
|
+
throw new CommandError(`Unknown backup subcommand: ${subcommand}. Use: now, create, list, restore, status`);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
async function executeBackupNow(manager) {
|
|
@@ -4,6 +4,56 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { parseArgs } from 'node:util';
|
|
6
6
|
import { CommandError, requireArg, parseJsonArg, parseNumberArg, parseBigIntArg } from './types';
|
|
7
|
+
const PUSH_VALUE_FLAGS = new Set([
|
|
8
|
+
'--priority',
|
|
9
|
+
'-P',
|
|
10
|
+
'--delay',
|
|
11
|
+
'-d',
|
|
12
|
+
'--max-attempts',
|
|
13
|
+
'--backoff',
|
|
14
|
+
'--ttl',
|
|
15
|
+
'--timeout',
|
|
16
|
+
'--unique-key',
|
|
17
|
+
'-u',
|
|
18
|
+
'--job-id',
|
|
19
|
+
'--depends-on',
|
|
20
|
+
'--tags',
|
|
21
|
+
'--group-id',
|
|
22
|
+
'-g',
|
|
23
|
+
]);
|
|
24
|
+
function protectNegativeJsonData(args) {
|
|
25
|
+
const protectedArgs = [...args];
|
|
26
|
+
const restore = new Map();
|
|
27
|
+
let positionals = 0;
|
|
28
|
+
for (let index = 0; index < args.length; index++) {
|
|
29
|
+
const arg = args[index];
|
|
30
|
+
if (arg === '--')
|
|
31
|
+
break;
|
|
32
|
+
if (PUSH_VALUE_FLAGS.has(arg)) {
|
|
33
|
+
index++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (arg.startsWith('-')) {
|
|
37
|
+
if (positionals !== 1)
|
|
38
|
+
continue;
|
|
39
|
+
try {
|
|
40
|
+
if (typeof JSON.parse(arg) !== 'number')
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const sentinel = `bunqueue-negative-json-${index}`;
|
|
47
|
+
protectedArgs[index] = sentinel;
|
|
48
|
+
restore.set(sentinel, arg);
|
|
49
|
+
positionals++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!arg.startsWith('--') && !/^-[a-zA-Z]/.test(arg))
|
|
53
|
+
positionals++;
|
|
54
|
+
}
|
|
55
|
+
return { args: protectedArgs, restore };
|
|
56
|
+
}
|
|
7
57
|
/** Build a core command from CLI args */
|
|
8
58
|
export function buildCoreCommand(command, args) {
|
|
9
59
|
switch (command) {
|
|
@@ -21,8 +71,9 @@ export function buildCoreCommand(command, args) {
|
|
|
21
71
|
}
|
|
22
72
|
/** Build PUSH command */
|
|
23
73
|
function buildPush(args) {
|
|
74
|
+
const protectedInput = protectNegativeJsonData(args);
|
|
24
75
|
const { values, positionals } = parseArgs({
|
|
25
|
-
args,
|
|
76
|
+
args: protectedInput.args,
|
|
26
77
|
options: {
|
|
27
78
|
priority: { type: 'string', short: 'P' },
|
|
28
79
|
delay: { type: 'string', short: 'd' },
|
|
@@ -43,7 +94,8 @@ function buildPush(args) {
|
|
|
43
94
|
strict: false,
|
|
44
95
|
});
|
|
45
96
|
const queue = requireArg(positionals, 0, 'queue');
|
|
46
|
-
const
|
|
97
|
+
const protectedData = requireArg(positionals, 1, 'data');
|
|
98
|
+
const data = parseJsonArg(protectedInput.restore.get(protectedData) ?? protectedData, 'data');
|
|
47
99
|
const cmd = {
|
|
48
100
|
cmd: 'PUSH',
|
|
49
101
|
queue,
|
|
@@ -1,5 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export interface HealthData {
|
|
2
|
+
version?: string;
|
|
3
|
+
status?: string;
|
|
4
|
+
uptime?: number;
|
|
5
|
+
queues?: {
|
|
6
|
+
waiting?: number;
|
|
7
|
+
active?: number;
|
|
8
|
+
delayed?: number;
|
|
9
|
+
completed?: number;
|
|
10
|
+
dlq?: number;
|
|
11
|
+
};
|
|
12
|
+
connections?: {
|
|
13
|
+
tcp?: number;
|
|
14
|
+
ws?: number;
|
|
15
|
+
sse?: number;
|
|
16
|
+
};
|
|
17
|
+
memory?: {
|
|
18
|
+
heapUsed?: number;
|
|
19
|
+
heapTotal?: number;
|
|
20
|
+
rss?: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export type DoctorInput = {
|
|
24
|
+
kind: 'ok';
|
|
25
|
+
endpoint: string;
|
|
26
|
+
health: HealthData;
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'http-error';
|
|
29
|
+
endpoint: string;
|
|
30
|
+
status: number;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'network-error';
|
|
33
|
+
endpoint: string;
|
|
34
|
+
message: string;
|
|
35
|
+
};
|
|
36
|
+
export interface DoctorReport {
|
|
37
|
+
clientVersion: string;
|
|
38
|
+
endpoint: string;
|
|
39
|
+
reachable: boolean;
|
|
40
|
+
health?: HealthData;
|
|
41
|
+
error?: string;
|
|
42
|
+
issues: number;
|
|
43
|
+
fatal: boolean;
|
|
44
|
+
exitCode: 0 | 1;
|
|
45
|
+
}
|
|
46
|
+
export declare function fetchDoctorHealth(host: string, httpPort: number): Promise<DoctorInput>;
|
|
47
|
+
/** Evaluate diagnostics without performing I/O or reading clocks. */
|
|
48
|
+
export declare function evaluateDoctor(input: DoctorInput): DoctorReport;
|
|
49
|
+
export declare function formatDoctorText(report: DoctorReport): string;
|
|
50
|
+
export declare function runDoctor(host: string, httpPort: number): Promise<DoctorReport>;
|