bunqueue 2.8.8 → 2.8.10

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.js CHANGED
@@ -8,241 +8,29 @@
8
8
  // module; without this guard, the top-level dispatch would re-run the CLI/server
9
9
  // on every import and cause "Failed to listen at 0.0.0.0".
10
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
11
  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());
12
+ // The server boots ONLY for a bare `bunqueue` invocation. `start` and
13
+ // flag-led argv go through the CLI, which detects server mode itself and
14
+ // boots the same full server (shared bootstrap). Every other first argument
15
+ // is a CLI command: known ones execute, unknown ones print
16
+ // "Unknown command: X" and exit 1 — a typo must never silently boot a server.
17
+ if (!firstArg) {
18
+ void startServer();
37
19
  }
38
20
  else {
39
- void startServer();
21
+ void import('./cli/index').then(({ main }) => main());
40
22
  }
41
23
  }
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, resolveTlsServerOptions, } from './config';
24
+ import { Logger } from './shared/logger';
25
+ import { loadConfigFile, resolveServerConfig } from './config';
26
+ import { bootServer } from './infrastructure/server/bootstrap';
52
27
  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} TLS ${config.tlsCertFile ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
86
- ${yellow}●${reset} Auth ${config.authTokens.length > 0 ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
87
- ${yellow}●${reset} S3 Backup ${config.s3BackupEnabled ? `${green}enabled${reset}` : `${dim}disabled${reset}`}
88
- ${yellow}●${reset} Cloud ${cloudUrl ? `${green}enabled${reset} ${dim}→ ${cloudUrl}${reset}` : `${dim}disabled${reset}`}
89
- ${dim}●${reset} Shards ${bold}${SHARD_COUNT}${reset} ${dim}(${navigator.hardwareConcurrency} CPU cores)${reset}
90
-
91
- ${dim}─────────────────────────────────────────────────${reset}
92
-
93
- `);
94
- }
95
28
  /** Start the server (direct mode) */
96
29
  async function startServer() {
97
30
  // Load config file (bunqueue.config.ts) if present, then merge with env vars
98
31
  const fileConfig = await loadConfigFile();
99
32
  const config = resolveServerConfig(fileConfig);
100
- // Apply logging config before anything else
101
- const logFormat = fileConfig?.logging?.format ?? Bun.env.LOG_FORMAT;
102
- const logLevel = fileConfig?.logging?.level ?? Bun.env.LOG_LEVEL?.toLowerCase();
103
- if (logFormat === 'json')
104
- Logger.enableJsonMode();
105
- if (logLevel) {
106
- const validLevels = ['debug', 'info', 'warn', 'error'];
107
- if (validLevels.includes(logLevel))
108
- Logger.setLevel(logLevel);
109
- }
110
- // Resolve cloud config
111
- const cloudConfig = resolveCloudConfig(fileConfig, config.dataPath);
112
- // Resolve TLS config — fail fast on partial cert/key before binding anything
113
- let tlsConfig;
114
- try {
115
- tlsConfig = resolveTlsServerOptions(config);
116
- }
117
- catch (err) {
118
- serverLog.error(err instanceof Error ? err.message : String(err));
119
- process.exit(1);
120
- }
121
- printBanner(config, cloudConfig?.url);
122
- // Create queue manager
123
- const queueManager = new QueueManager({
124
- dataPath: config.dataPath,
125
- });
126
- // Start TCP server
127
- const tcpServer = createTcpServer(queueManager, {
128
- port: config.tcpPort,
129
- hostname: config.hostname,
130
- authTokens: config.authTokens,
131
- ...(tlsConfig && { tls: tlsConfig }),
132
- });
133
- // Start HTTP server
134
- const httpServer = createHttpServer(queueManager, {
135
- port: config.httpPort,
136
- hostname: config.hostname,
137
- authTokens: config.authTokens,
138
- corsOrigins: config.corsOrigins,
139
- requireAuthForMetrics: config.requireAuthForMetrics,
140
- ...(tlsConfig && { tls: tlsConfig }),
141
- });
142
- // Initialize S3 backup manager
143
- let backupManager = null;
144
- if (config.dataPath) {
145
- const backupConfig = resolveBackupConfig(fileConfig, config.dataPath);
146
- backupManager = new S3BackupManager(backupConfig);
147
- backupManager.setDashboardEmit(queueManager.emitDashboardEvent.bind(queueManager));
148
- backupManager.start();
149
- }
150
- // Initialize bunqueue Cloud agent (remote dashboard telemetry)
151
- const cloudAgent = cloudConfig ? CloudAgent.createFromConfig(queueManager, cloudConfig) : null;
152
- if (cloudAgent) {
153
- cloudAgent.setServerHandles({
154
- getConnectionCount: () => tcpServer.getConnectionCount(),
155
- getWsClientCount: () => httpServer.getWsClientCount(),
156
- getSseClientCount: () => httpServer.getSseClientCount(),
157
- getBackupStatus: () => backupManager?.getStatus() ?? null,
158
- });
159
- }
160
- queueManager.emitDashboardEvent('server:started', {
161
- tcpPort: config.tcpPort,
162
- httpPort: config.httpPort,
163
- shards: SHARD_COUNT,
164
- });
165
- // Graceful shutdown
166
- let shuttingDown = false;
167
- const shutdown = async (signal) => {
168
- if (shuttingDown)
169
- return;
170
- shuttingDown = true;
171
- serverLog.info(`Received ${signal}, shutting down...`);
172
- // Stop stats interval immediately
173
- clearInterval(statsInterval);
174
- tcpServer.stop();
175
- httpServer.stop();
176
- const shutdownTimeout = config.shutdownTimeoutMs;
177
- const start = Date.now();
178
- while (Date.now() - start < shutdownTimeout) {
179
- const stats = queueManager.getStats();
180
- if (stats.active === 0)
181
- break;
182
- serverLog.info(`Waiting for ${stats.active} active jobs...`);
183
- await Bun.sleep(1000);
184
- }
185
- // Stop backup manager
186
- if (backupManager) {
187
- backupManager.stop();
188
- }
189
- // Stop Cloud agent (sends final shutdown snapshot)
190
- if (cloudAgent) {
191
- await cloudAgent.stop();
192
- }
193
- queueManager.emitDashboardEvent('server:shutdown', { signal });
194
- queueManager.shutdown();
195
- stopRateLimiter();
196
- serverLog.info('Shutdown complete');
197
- process.exit(0);
198
- };
199
- process.on('SIGINT', () => void shutdown('SIGINT'));
200
- process.on('SIGTERM', () => void shutdown('SIGTERM'));
201
- process.on('uncaughtException', (err) => {
202
- serverLog.error('Uncaught exception - initiating shutdown', {
203
- error: err.message,
204
- stack: err.stack,
205
- });
206
- void shutdown('uncaughtException');
207
- });
208
- process.on('unhandledRejection', (reason) => {
209
- serverLog.error('Unhandled promise rejection - initiating shutdown', {
210
- reason: reason instanceof Error ? reason.message : String(reason),
211
- stack: reason instanceof Error ? reason.stack : undefined,
212
- });
213
- void shutdown('unhandledRejection');
214
- });
215
- // Print stats periodically
216
- const statsInterval = setInterval(() => {
217
- const stats = queueManager.getStats();
218
- const memStats = queueManager.getMemoryStats();
219
- const workerStats = queueManager.workerManager.getStats();
220
- const mem = process.memoryUsage();
221
- const now = new Date();
222
- const timestamp = now.toLocaleTimeString('en-GB', {
223
- hour: '2-digit',
224
- minute: '2-digit',
225
- second: '2-digit',
226
- });
227
- statsLog.info('Queue statistics', {
228
- time: timestamp,
229
- waiting: stats.waiting,
230
- active: stats.active,
231
- delayed: stats.delayed,
232
- completed: stats.completed,
233
- dlq: stats.dlq,
234
- tcp: tcpServer.getConnectionCount(),
235
- ws: httpServer.getWsClientCount(),
236
- sse: httpServer.getSseClientCount(),
237
- workers: `${workerStats.active}/${workerStats.total}`,
238
- mem: `${Math.round(mem.heapUsed / 1024 / 1024)}MB/${Math.round(mem.heapTotal / 1024 / 1024)}MB`,
239
- rss: `${Math.round(mem.rss / 1024 / 1024)}MB`,
240
- // Internal collection sizes (for memory debugging)
241
- idx: memStats.jobIndex,
242
- locks: memStats.jobLocks,
243
- clients: memStats.clientJobsTotal,
244
- });
245
- }, config.statsIntervalMs);
33
+ bootServer(fileConfig, config);
246
34
  }
247
35
  // Logger env-var bootstrap only applies when this file is the entry point.
248
36
  // Imported consumers (e.g. user config files using `defineConfig`) must not
@@ -4,19 +4,11 @@
4
4
  */
5
5
  import { z } from 'zod';
6
6
  import { withErrorHandler } from './withErrorHandler';
7
+ import { WEBHOOK_EVENTS } from '../../domain/types/webhook';
7
8
  export function registerWebhookTools(server, backend) {
8
9
  server.tool('bunqueue_add_webhook', 'Add a webhook to receive notifications for job events.', {
9
10
  url: z.string().url().describe('Webhook URL to receive POST requests'),
10
- events: z
11
- .array(z.enum([
12
- 'job.completed',
13
- 'job.failed',
14
- 'job.progress',
15
- 'job.active',
16
- 'job.waiting',
17
- 'job.delayed',
18
- ]))
19
- .describe('Events to subscribe to'),
11
+ events: z.array(z.enum(WEBHOOK_EVENTS)).describe('Events to subscribe to'),
20
12
  queue: z.string().optional().describe('Limit to a specific queue (omit for all queues)'),
21
13
  }, withErrorHandler('bunqueue_add_webhook', async ({ url, events, queue }) => {
22
14
  const webhook = await backend.addWebhook(url, events, queue);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunqueue",
3
- "version": "2.8.8",
3
+ "version": "2.8.10",
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",