bunqueue 2.8.20 → 2.8.21
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/monitoringChecks.js +5 -5
- package/dist/cli/commands/server.js +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/client/queue/operations/management.d.ts +1 -1
- package/dist/client/queue/operations/management.js +4 -4
- package/dist/domain/queue/temporalManager.js +11 -1
- package/dist/infrastructure/cloud/cloudAgent.d.ts +0 -1
- package/dist/infrastructure/cloud/cloudAgent.js +0 -2
- package/dist/infrastructure/server/http.js +2 -2
- package/dist/infrastructure/server/httpRouteQueues.js +3 -3
- package/dist/infrastructure/server/sseHandler.js +7 -0
- package/package.json +1 -1
|
@@ -14,11 +14,11 @@ export function createMonitoringState() {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
/** Config from env vars */
|
|
17
|
-
const QUEUE_IDLE_THRESHOLD_MS = parseInt(process.env.QUEUE_IDLE_THRESHOLD_MS ?? '30000');
|
|
18
|
-
const QUEUE_SIZE_THRESHOLD = parseInt(process.env.QUEUE_SIZE_THRESHOLD ?? '0'); // 0 = disabled
|
|
19
|
-
const MEMORY_WARNING_MB = parseInt(process.env.MEMORY_WARNING_MB ?? '0'); // 0 = disabled
|
|
20
|
-
const STORAGE_WARNING_MB = parseInt(process.env.STORAGE_WARNING_MB ?? '0'); // 0 = disabled
|
|
21
|
-
const WORKER_OVERLOAD_THRESHOLD_MS = parseInt(process.env.WORKER_OVERLOAD_THRESHOLD_MS ?? '30000');
|
|
17
|
+
const QUEUE_IDLE_THRESHOLD_MS = parseInt(process.env.QUEUE_IDLE_THRESHOLD_MS ?? '30000', 10);
|
|
18
|
+
const QUEUE_SIZE_THRESHOLD = parseInt(process.env.QUEUE_SIZE_THRESHOLD ?? '0', 10); // 0 = disabled
|
|
19
|
+
const MEMORY_WARNING_MB = parseInt(process.env.MEMORY_WARNING_MB ?? '0', 10); // 0 = disabled
|
|
20
|
+
const STORAGE_WARNING_MB = parseInt(process.env.STORAGE_WARNING_MB ?? '0', 10); // 0 = disabled
|
|
21
|
+
const WORKER_OVERLOAD_THRESHOLD_MS = parseInt(process.env.WORKER_OVERLOAD_THRESHOLD_MS ?? '30000', 10);
|
|
22
22
|
/** Check all monitoring conditions — called from cleanup interval (10s) */
|
|
23
23
|
export function runMonitoringChecks(ctx) {
|
|
24
24
|
if (!ctx.dashboardEmit)
|
|
@@ -11,7 +11,7 @@ import { bootServer } from '../../infrastructure/server/bootstrap';
|
|
|
11
11
|
/** Validate port number */
|
|
12
12
|
function validatePort(value, name, defaultPort) {
|
|
13
13
|
const port = parseInt(value, 10);
|
|
14
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
14
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
15
15
|
console.warn(`Warning: Invalid ${name} "${value}". Using default ${defaultPort}.`);
|
|
16
16
|
return defaultPort;
|
|
17
17
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -22,7 +22,7 @@ function resolveEnvPort(currentPort) {
|
|
|
22
22
|
if (!envPort)
|
|
23
23
|
return currentPort;
|
|
24
24
|
const parsed = parseInt(envPort, 10);
|
|
25
|
-
if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
25
|
+
if (Number.isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
26
26
|
console.warn(`Warning: Invalid env port "${envPort}". Using ${currentPort}.`);
|
|
27
27
|
return currentPort;
|
|
28
28
|
}
|
|
@@ -129,7 +129,7 @@ function applyPortFlag(allArgs, i, state) {
|
|
|
129
129
|
return i;
|
|
130
130
|
}
|
|
131
131
|
const parsed = parseInt(nextArg, 10);
|
|
132
|
-
if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
132
|
+
if (Number.isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
133
133
|
console.warn(`Warning: Invalid port "${nextArg}". Using default port 6789.`);
|
|
134
134
|
state.port = 6789;
|
|
135
135
|
}
|
|
@@ -208,7 +208,7 @@ export function parseGlobalOptions() {
|
|
|
208
208
|
else if (arg.startsWith('--port=')) {
|
|
209
209
|
const raw = arg.slice(7);
|
|
210
210
|
const parsed = parseInt(raw, 10);
|
|
211
|
-
if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
211
|
+
if (Number.isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
212
212
|
console.warn(`Warning: Invalid port "${raw}". Using default port 6789.`);
|
|
213
213
|
hp.port = 6789;
|
|
214
214
|
}
|
|
@@ -8,7 +8,7 @@ interface ManagementContext {
|
|
|
8
8
|
embedded: boolean;
|
|
9
9
|
tcp: TcpConnectionPool | null;
|
|
10
10
|
}
|
|
11
|
-
/** Remove a job (sync) */
|
|
11
|
+
/** Remove a job (sync, fire-and-forget; use removeAsync to await the removal) */
|
|
12
12
|
export declare function remove(ctx: ManagementContext, id: string): void;
|
|
13
13
|
/** Remove a job (async) */
|
|
14
14
|
export declare function removeAsync(ctx: ManagementContext, id: string): Promise<void>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-floating-promises, @typescript-eslint/no-unnecessary-condition */
|
|
2
1
|
/**
|
|
3
2
|
* Queue Management Operations
|
|
4
3
|
* remove, retry, clean, promote, updateProgress, logs
|
|
@@ -6,12 +5,13 @@
|
|
|
6
5
|
import { getSharedManager } from '../../manager';
|
|
7
6
|
import { jobId } from '../../../domain/types/job';
|
|
8
7
|
// ============ Remove Operations ============
|
|
9
|
-
/** Remove a job (sync) */
|
|
8
|
+
/** Remove a job (sync, fire-and-forget; use removeAsync to await the removal) */
|
|
10
9
|
export function remove(ctx, id) {
|
|
10
|
+
// `void`: the cancellation is intentionally not awaited here (sync API).
|
|
11
11
|
if (ctx.embedded)
|
|
12
|
-
getSharedManager().cancel(jobId(id));
|
|
12
|
+
void getSharedManager().cancel(jobId(id));
|
|
13
13
|
else
|
|
14
|
-
ctx.tcp.send({ cmd: 'Cancel', id });
|
|
14
|
+
void ctx.tcp.send({ cmd: 'Cancel', id });
|
|
15
15
|
}
|
|
16
16
|
/** Remove a job (async) */
|
|
17
17
|
export async function removeAsync(ctx, id) {
|
|
@@ -17,7 +17,17 @@ export class TemporalManager {
|
|
|
17
17
|
* Ordered by createdAt for efficient cleanQueue range queries
|
|
18
18
|
* Uses equality check on jobId to prevent duplicate entries for the same job
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
// Total-order comparator: createdAt first, then jobId as a tie-break. Without
|
|
21
|
+
// the tie-break, a batch of jobs sharing one createdAt (the addBulk case —
|
|
22
|
+
// `now` is captured once) makes every node compare-equal, which (a) turns
|
|
23
|
+
// SkipList.insert's duplicate-check scan into O(n) per insert => O(n²) for the
|
|
24
|
+
// batch, and (b) makes SkipList.delete remove the WRONG same-createdAt node
|
|
25
|
+
// (it stops at the first compare-equal node). A total order fixes both: the
|
|
26
|
+
// dedup scan and delete both resolve to the exact (createdAt, jobId) node in
|
|
27
|
+
// O(log n). jobId is a string (UUIDv7 by default, or any custom id), so its
|
|
28
|
+
// lexicographic comparison is a valid total order in every case. Equality is
|
|
29
|
+
// still by jobId (now reached only for a true duplicate).
|
|
30
|
+
temporalIndex = new SkipList((a, b) => a.createdAt - b.createdAt || (a.jobId < b.jobId ? -1 : a.jobId > b.jobId ? 1 : 0), 16, 0.5, (a, b) => a.jobId === b.jobId);
|
|
21
31
|
/** Set of delayed job IDs for tracking when they become ready */
|
|
22
32
|
delayedJobIds = new Set();
|
|
23
33
|
/**
|
|
@@ -27,7 +27,6 @@ export class CloudAgent {
|
|
|
27
27
|
statsUpdateTimer = null;
|
|
28
28
|
unsubscribeEvents = null;
|
|
29
29
|
sequenceId = 0;
|
|
30
|
-
snapshotCount = 0;
|
|
31
30
|
stopped = false;
|
|
32
31
|
serverHandles;
|
|
33
32
|
/** Event buffer — flushed into each HTTP snapshot */
|
|
@@ -164,7 +163,6 @@ export class CloudAgent {
|
|
|
164
163
|
/** Collect and send a snapshot via HTTP */
|
|
165
164
|
async sendSnapshot(_forceHeavy = false) {
|
|
166
165
|
try {
|
|
167
|
-
this.snapshotCount++;
|
|
168
166
|
const snapshot = await collectSnapshot({
|
|
169
167
|
queueManager: this.queueManager,
|
|
170
168
|
instanceId: this.instanceId,
|
|
@@ -253,8 +253,8 @@ async function routeRequest(req, path, ctx, corsOrigins) {
|
|
|
253
253
|
}
|
|
254
254
|
if (path === '/dashboard/queues' && method === 'GET') {
|
|
255
255
|
const url = new URL(req.url);
|
|
256
|
-
const limit = Math.min(Math.max(parseInt(url.searchParams.get('limit') ?? '100') || 100, 1), 500);
|
|
257
|
-
const offset = Math.max(parseInt(url.searchParams.get('offset') ?? '0') || 0, 0);
|
|
256
|
+
const limit = Math.min(Math.max(parseInt(url.searchParams.get('limit') ?? '100', 10) || 100, 1), 500);
|
|
257
|
+
const offset = Math.max(parseInt(url.searchParams.get('offset') ?? '0', 10) || 0, 0);
|
|
258
258
|
return dashboardQueuesEndpoint(ctx.queueManager, limit, offset, corsOrigins);
|
|
259
259
|
}
|
|
260
260
|
const dashQueueMatch = path.match(RE_DASHBOARD_QUEUE_DETAIL);
|
|
@@ -60,7 +60,7 @@ async function routeJobOps(req, path, method, ctx, cors) {
|
|
|
60
60
|
return jsonResponse(r, r.ok ? 200 : 400, cors);
|
|
61
61
|
}
|
|
62
62
|
if (method === 'GET') {
|
|
63
|
-
const timeout = parseInt(new URL(req.url).searchParams.get('timeout') ?? '0');
|
|
63
|
+
const timeout = parseInt(new URL(req.url).searchParams.get('timeout') ?? '0', 10);
|
|
64
64
|
const r = await handleCommand({ cmd: 'PULL', queue, timeout }, ctx);
|
|
65
65
|
return jsonResponse(r, 200, cors);
|
|
66
66
|
}
|
|
@@ -125,8 +125,8 @@ async function routeJobOps(req, path, method, ctx, cors) {
|
|
|
125
125
|
: stateValues;
|
|
126
126
|
const limitParam = url.searchParams.get('limit');
|
|
127
127
|
const offsetParam = url.searchParams.get('offset');
|
|
128
|
-
const limit = limitParam ? parseInt(limitParam) : undefined;
|
|
129
|
-
const offset = offsetParam ? parseInt(offsetParam) : undefined;
|
|
128
|
+
const limit = limitParam ? parseInt(limitParam, 10) : undefined;
|
|
129
|
+
const offset = offsetParam ? parseInt(offsetParam, 10) : undefined;
|
|
130
130
|
const r = await handleCommand({
|
|
131
131
|
cmd: 'GetJobs',
|
|
132
132
|
queue,
|
|
@@ -116,6 +116,13 @@ export class SseHandler {
|
|
|
116
116
|
// ── Job event broadcasting ─────────────────────────────────
|
|
117
117
|
/** Broadcast job event to matching clients (with typed SSE event field) */
|
|
118
118
|
broadcast(event) {
|
|
119
|
+
// No clients => nothing to send, and nothing to buffer that anyone could
|
|
120
|
+
// replay. Mirrors wsHandler.broadcast. Without this, every job event still
|
|
121
|
+
// paid JSON.stringify + encode + ring buffer + an O(queue size) per-event
|
|
122
|
+
// getQueueJobCounts, turning a bulk push into O(N²) even with no dashboard
|
|
123
|
+
// attached (the common high-throughput case).
|
|
124
|
+
if (this.clients.size === 0)
|
|
125
|
+
return;
|
|
119
126
|
const id = ++this.eventId;
|
|
120
127
|
const eventName = EVENT_MAP[event.eventType] ?? `job:${event.eventType}`;
|
|
121
128
|
const eventData = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunqueue",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.21",
|
|
4
4
|
"description": "High-performance job queue for Bun & AI agents. SQLite persistence, cron scheduling, priorities, retries, DLQ, webhooks, native MCP server. Zero external dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|