bunqueue 2.8.2 → 2.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main 2.js +250 -0
- package/package.json +3 -2
package/dist/main 2.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* bunqueue - High-performance job queue server for Bun
|
|
4
|
+
* Main entry point - routes to CLI for client commands or starts server
|
|
5
|
+
*/
|
|
6
|
+
// Only run startup dispatch when this file is the program entry point.
|
|
7
|
+
// Issue #85: re-exporting `defineConfig` means user config files import this
|
|
8
|
+
// module; without this guard, the top-level dispatch would re-run the CLI/server
|
|
9
|
+
// on every import and cause "Failed to listen at 0.0.0.0".
|
|
10
|
+
if (import.meta.main) {
|
|
11
|
+
const clientCommands = [
|
|
12
|
+
'push',
|
|
13
|
+
'pull',
|
|
14
|
+
'ack',
|
|
15
|
+
'fail',
|
|
16
|
+
'job',
|
|
17
|
+
'queue',
|
|
18
|
+
'dlq',
|
|
19
|
+
'cron',
|
|
20
|
+
'worker',
|
|
21
|
+
'webhook',
|
|
22
|
+
'rate-limit',
|
|
23
|
+
'concurrency',
|
|
24
|
+
'stats',
|
|
25
|
+
'metrics',
|
|
26
|
+
'health',
|
|
27
|
+
'backup',
|
|
28
|
+
];
|
|
29
|
+
const firstArg = process.argv[2];
|
|
30
|
+
const isClientCommand = firstArg && clientCommands.includes(firstArg);
|
|
31
|
+
const isStartCommand = firstArg === 'start';
|
|
32
|
+
const hasHelpOrVersion = process.argv.includes('--help') || process.argv.includes('--version');
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- process.argv[2] can be undefined at runtime
|
|
34
|
+
const hasFlags = firstArg?.startsWith('-');
|
|
35
|
+
if (isClientCommand || hasHelpOrVersion || isStartCommand || hasFlags) {
|
|
36
|
+
void import('./cli/index').then(({ main }) => main());
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
void startServer();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
import { QueueManager } from './application/queueManager';
|
|
43
|
+
import { createTcpServer } from './infrastructure/server/tcp';
|
|
44
|
+
import { createHttpServer } from './infrastructure/server/http';
|
|
45
|
+
import { Logger, serverLog, statsLog } from './shared/logger';
|
|
46
|
+
import { stopRateLimiter } from './infrastructure/server/rateLimiter';
|
|
47
|
+
import { VERSION } from './shared/version';
|
|
48
|
+
import { S3BackupManager } from './infrastructure/backup';
|
|
49
|
+
import { CloudAgent } from './infrastructure/cloud';
|
|
50
|
+
import { SHARD_COUNT } from './shared/hash';
|
|
51
|
+
import { loadConfigFile, resolveServerConfig, resolveCloudConfig, resolveBackupConfig, } from './config';
|
|
52
|
+
export { defineConfig } from './config';
|
|
53
|
+
/** Print startup banner */
|
|
54
|
+
function printBanner(config, cloudUrl) {
|
|
55
|
+
const dim = '\x1b[2m';
|
|
56
|
+
const reset = '\x1b[0m';
|
|
57
|
+
const bold = '\x1b[1m';
|
|
58
|
+
const magenta = '\x1b[35m';
|
|
59
|
+
const green = '\x1b[32m';
|
|
60
|
+
const yellow = '\x1b[33m';
|
|
61
|
+
// Format TCP endpoint display
|
|
62
|
+
const tcpDisplay = config.tcpSocketPath
|
|
63
|
+
? `${bold}${config.tcpSocketPath}${reset} ${dim}(unix)${reset}`
|
|
64
|
+
: `${bold}${config.hostname}:${config.tcpPort}${reset}`;
|
|
65
|
+
// Format HTTP endpoint display
|
|
66
|
+
const httpDisplay = config.httpSocketPath
|
|
67
|
+
? `${bold}${config.httpSocketPath}${reset} ${dim}(unix)${reset}`
|
|
68
|
+
: `${bold}${config.hostname}:${config.httpPort}${reset}`;
|
|
69
|
+
// Socket mode display
|
|
70
|
+
const hasUnixSockets = config.tcpSocketPath !== undefined || config.httpSocketPath !== undefined;
|
|
71
|
+
const socketDisplay = hasUnixSockets
|
|
72
|
+
? `${green}enabled${reset} ${dim}(${config.tcpSocketPath ? 'TCP' : ''}${config.tcpSocketPath && config.httpSocketPath ? '+' : ''}${config.httpSocketPath ? 'HTTP' : ''})${reset}`
|
|
73
|
+
: `${dim}disabled${reset}`;
|
|
74
|
+
console.log(`
|
|
75
|
+
${magenta} (\\(\\ ${reset}
|
|
76
|
+
${magenta} ( -.-) ${bold}bunqueue${reset} ${dim}v${VERSION}${reset}
|
|
77
|
+
${magenta} o_(")(") ${reset}${dim}High-performance job queue for Bun${reset}
|
|
78
|
+
|
|
79
|
+
${dim}─────────────────────────────────────────────────${reset}
|
|
80
|
+
|
|
81
|
+
${green}●${reset} TCP ${tcpDisplay}
|
|
82
|
+
${green}●${reset} HTTP ${httpDisplay}
|
|
83
|
+
${yellow}●${reset} Socket ${socketDisplay}
|
|
84
|
+
${yellow}●${reset} Data ${config.dataPath ?? 'in-memory'}
|
|
85
|
+
${yellow}●${reset} Auth ${config.authTokens.length > 0 ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
|
|
86
|
+
${yellow}●${reset} S3 Backup ${config.s3BackupEnabled ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
|
|
87
|
+
${yellow}●${reset} Cloud ${cloudUrl ? `${green}enabled${reset} ${dim}→ ${cloudUrl}${reset}` : `${dim}disabled${reset}`}
|
|
88
|
+
${dim}●${reset} Shards ${bold}${SHARD_COUNT}${reset} ${dim}(${navigator.hardwareConcurrency} CPU cores)${reset}
|
|
89
|
+
|
|
90
|
+
${dim}─────────────────────────────────────────────────${reset}
|
|
91
|
+
|
|
92
|
+
`);
|
|
93
|
+
}
|
|
94
|
+
/** Start the server (direct mode) */
|
|
95
|
+
async function startServer() {
|
|
96
|
+
// Load config file (bunqueue.config.ts) if present, then merge with env vars
|
|
97
|
+
const fileConfig = await loadConfigFile();
|
|
98
|
+
const config = resolveServerConfig(fileConfig);
|
|
99
|
+
// Apply logging config before anything else
|
|
100
|
+
const logFormat = fileConfig?.logging?.format ?? Bun.env.LOG_FORMAT;
|
|
101
|
+
const logLevel = fileConfig?.logging?.level ?? Bun.env.LOG_LEVEL?.toLowerCase();
|
|
102
|
+
if (logFormat === 'json')
|
|
103
|
+
Logger.enableJsonMode();
|
|
104
|
+
if (logLevel) {
|
|
105
|
+
const validLevels = ['debug', 'info', 'warn', 'error'];
|
|
106
|
+
if (validLevels.includes(logLevel))
|
|
107
|
+
Logger.setLevel(logLevel);
|
|
108
|
+
}
|
|
109
|
+
// Resolve cloud config
|
|
110
|
+
const cloudConfig = resolveCloudConfig(fileConfig, config.dataPath);
|
|
111
|
+
printBanner(config, cloudConfig?.url);
|
|
112
|
+
// Create queue manager
|
|
113
|
+
const queueManager = new QueueManager({
|
|
114
|
+
dataPath: config.dataPath,
|
|
115
|
+
});
|
|
116
|
+
// Start TCP server
|
|
117
|
+
const tcpServer = createTcpServer(queueManager, {
|
|
118
|
+
port: config.tcpPort,
|
|
119
|
+
hostname: config.hostname,
|
|
120
|
+
authTokens: config.authTokens,
|
|
121
|
+
});
|
|
122
|
+
// Start HTTP server
|
|
123
|
+
const httpServer = createHttpServer(queueManager, {
|
|
124
|
+
port: config.httpPort,
|
|
125
|
+
hostname: config.hostname,
|
|
126
|
+
authTokens: config.authTokens,
|
|
127
|
+
corsOrigins: config.corsOrigins,
|
|
128
|
+
requireAuthForMetrics: config.requireAuthForMetrics,
|
|
129
|
+
});
|
|
130
|
+
// Initialize S3 backup manager
|
|
131
|
+
let backupManager = null;
|
|
132
|
+
if (config.dataPath) {
|
|
133
|
+
const backupConfig = resolveBackupConfig(fileConfig, config.dataPath);
|
|
134
|
+
backupManager = new S3BackupManager(backupConfig);
|
|
135
|
+
backupManager.setDashboardEmit(queueManager.emitDashboardEvent.bind(queueManager));
|
|
136
|
+
backupManager.start();
|
|
137
|
+
}
|
|
138
|
+
// Initialize bunqueue Cloud agent (remote dashboard telemetry)
|
|
139
|
+
const cloudAgent = cloudConfig ? CloudAgent.createFromConfig(queueManager, cloudConfig) : null;
|
|
140
|
+
if (cloudAgent) {
|
|
141
|
+
cloudAgent.setServerHandles({
|
|
142
|
+
getConnectionCount: () => tcpServer.getConnectionCount(),
|
|
143
|
+
getWsClientCount: () => httpServer.getWsClientCount(),
|
|
144
|
+
getSseClientCount: () => httpServer.getSseClientCount(),
|
|
145
|
+
getBackupStatus: () => backupManager?.getStatus() ?? null,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
queueManager.emitDashboardEvent('server:started', {
|
|
149
|
+
tcpPort: config.tcpPort,
|
|
150
|
+
httpPort: config.httpPort,
|
|
151
|
+
shards: SHARD_COUNT,
|
|
152
|
+
});
|
|
153
|
+
// Graceful shutdown
|
|
154
|
+
let shuttingDown = false;
|
|
155
|
+
const shutdown = async (signal) => {
|
|
156
|
+
if (shuttingDown)
|
|
157
|
+
return;
|
|
158
|
+
shuttingDown = true;
|
|
159
|
+
serverLog.info(`Received ${signal}, shutting down...`);
|
|
160
|
+
// Stop stats interval immediately
|
|
161
|
+
clearInterval(statsInterval);
|
|
162
|
+
tcpServer.stop();
|
|
163
|
+
httpServer.stop();
|
|
164
|
+
const shutdownTimeout = config.shutdownTimeoutMs;
|
|
165
|
+
const start = Date.now();
|
|
166
|
+
while (Date.now() - start < shutdownTimeout) {
|
|
167
|
+
const stats = queueManager.getStats();
|
|
168
|
+
if (stats.active === 0)
|
|
169
|
+
break;
|
|
170
|
+
serverLog.info(`Waiting for ${stats.active} active jobs...`);
|
|
171
|
+
await Bun.sleep(1000);
|
|
172
|
+
}
|
|
173
|
+
// Stop backup manager
|
|
174
|
+
if (backupManager) {
|
|
175
|
+
backupManager.stop();
|
|
176
|
+
}
|
|
177
|
+
// Stop Cloud agent (sends final shutdown snapshot)
|
|
178
|
+
if (cloudAgent) {
|
|
179
|
+
await cloudAgent.stop();
|
|
180
|
+
}
|
|
181
|
+
queueManager.emitDashboardEvent('server:shutdown', { signal });
|
|
182
|
+
queueManager.shutdown();
|
|
183
|
+
stopRateLimiter();
|
|
184
|
+
serverLog.info('Shutdown complete');
|
|
185
|
+
process.exit(0);
|
|
186
|
+
};
|
|
187
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
188
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
189
|
+
process.on('uncaughtException', (err) => {
|
|
190
|
+
serverLog.error('Uncaught exception - initiating shutdown', {
|
|
191
|
+
error: err.message,
|
|
192
|
+
stack: err.stack,
|
|
193
|
+
});
|
|
194
|
+
void shutdown('uncaughtException');
|
|
195
|
+
});
|
|
196
|
+
process.on('unhandledRejection', (reason) => {
|
|
197
|
+
serverLog.error('Unhandled promise rejection - initiating shutdown', {
|
|
198
|
+
reason: reason instanceof Error ? reason.message : String(reason),
|
|
199
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
200
|
+
});
|
|
201
|
+
void shutdown('unhandledRejection');
|
|
202
|
+
});
|
|
203
|
+
// Print stats periodically
|
|
204
|
+
const statsInterval = setInterval(() => {
|
|
205
|
+
const stats = queueManager.getStats();
|
|
206
|
+
const memStats = queueManager.getMemoryStats();
|
|
207
|
+
const workerStats = queueManager.workerManager.getStats();
|
|
208
|
+
const mem = process.memoryUsage();
|
|
209
|
+
const now = new Date();
|
|
210
|
+
const timestamp = now.toLocaleTimeString('en-GB', {
|
|
211
|
+
hour: '2-digit',
|
|
212
|
+
minute: '2-digit',
|
|
213
|
+
second: '2-digit',
|
|
214
|
+
});
|
|
215
|
+
statsLog.info('Queue statistics', {
|
|
216
|
+
time: timestamp,
|
|
217
|
+
waiting: stats.waiting,
|
|
218
|
+
active: stats.active,
|
|
219
|
+
delayed: stats.delayed,
|
|
220
|
+
completed: stats.completed,
|
|
221
|
+
dlq: stats.dlq,
|
|
222
|
+
tcp: tcpServer.getConnectionCount(),
|
|
223
|
+
ws: httpServer.getWsClientCount(),
|
|
224
|
+
sse: httpServer.getSseClientCount(),
|
|
225
|
+
workers: `${workerStats.active}/${workerStats.total}`,
|
|
226
|
+
mem: `${Math.round(mem.heapUsed / 1024 / 1024)}MB/${Math.round(mem.heapTotal / 1024 / 1024)}MB`,
|
|
227
|
+
rss: `${Math.round(mem.rss / 1024 / 1024)}MB`,
|
|
228
|
+
// Internal collection sizes (for memory debugging)
|
|
229
|
+
idx: memStats.jobIndex,
|
|
230
|
+
locks: memStats.jobLocks,
|
|
231
|
+
clients: memStats.clientJobsTotal,
|
|
232
|
+
});
|
|
233
|
+
}, config.statsIntervalMs);
|
|
234
|
+
}
|
|
235
|
+
// Logger env-var bootstrap only applies when this file is the entry point.
|
|
236
|
+
// Imported consumers (e.g. user config files using `defineConfig`) must not
|
|
237
|
+
// have their process Logger state mutated as a side effect — see Issue #85.
|
|
238
|
+
if (import.meta.main) {
|
|
239
|
+
if (Bun.env.LOG_FORMAT === 'json') {
|
|
240
|
+
Logger.enableJsonMode();
|
|
241
|
+
}
|
|
242
|
+
if (Bun.env.LOG_LEVEL) {
|
|
243
|
+
const validLevels = ['debug', 'info', 'warn', 'error'];
|
|
244
|
+
const level = Bun.env.LOG_LEVEL.toLowerCase();
|
|
245
|
+
if (validLevels.includes(level)) {
|
|
246
|
+
Logger.setLevel(level);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=main.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunqueue",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.3",
|
|
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",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"./workflow": {
|
|
30
30
|
"types": "./dist/client/workflow/index.d.ts",
|
|
31
31
|
"import": "./dist/client/workflow/index.js"
|
|
32
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"dist/**/*.js",
|