queen-mq 0.1.2 → 0.1.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/src/server.js CHANGED
@@ -9,7 +9,7 @@ import { createResourceCache } from './managers/resourceCache.js';
9
9
  import { createEventManager } from './managers/eventManager.js';
10
10
  import { SystemEventManager, SYSTEM_QUEUE } from './managers/systemEventManager.js';
11
11
  import { syncSystemEvents } from './services/startupSync.js';
12
- import { createQueenClient } from './client/queenClient.js';
12
+ //import { createQueenClient } from './client/queenClient.js';
13
13
  import { createPushRoute } from './routes/push.js';
14
14
  import { createPopRoute } from './routes/pop.js';
15
15
  import { createAckRoute } from './routes/ack.js';
@@ -26,379 +26,388 @@ import { streamJSON, sendJSON, sendError } from './utils/streaming.js';
26
26
 
27
27
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
28
 
29
- const PORT = config.SERVER.PORT;
30
- const HOST = config.SERVER.HOST;
29
+ /**
30
+ * Start Queen MQ Server
31
+ * @param {Object} options - Server configuration options
32
+ * @param {number} options.port - Server port (default: from config)
33
+ * @param {string} options.host - Server host (default: from config)
34
+ * @param {string} options.workerId - Worker ID for clustering (default: from config)
35
+ * @returns {Promise<Object>} Server instance with shutdown method
36
+ */
37
+ export async function QueenServer(options = {}) {
38
+ const PORT = options.port || config.SERVER.PORT;
39
+ const HOST = options.host || config.SERVER.HOST;
31
40
 
32
- // Use WORKER_ID from config as the server instance ID for consumer groups
33
- // This ensures the same server (identified by WORKER_ID) maintains its position in the event stream
34
- const SERVER_INSTANCE_ID = config.SERVER.WORKER_ID;
41
+ // Use WORKER_ID from config as the server instance ID for consumer groups
42
+ // This ensures the same server (identified by WORKER_ID) maintains its position in the event stream
43
+ const SERVER_INSTANCE_ID = options.workerId || config.SERVER.WORKER_ID;
35
44
 
36
- // Performance monitoring
37
- let requestCount = 0;
38
- let messageCount = 0;
39
- const startTime = Date.now();
45
+ // Performance monitoring
46
+ let requestCount = 0;
47
+ let messageCount = 0;
48
+ const startTime = Date.now();
40
49
 
41
- // Shutdown flag to prevent background jobs from running during shutdown
42
- let isShuttingDown = false;
50
+ // Shutdown flag to prevent background jobs from running during shutdown
51
+ let isShuttingDown = false;
43
52
 
44
- // Initialize components
45
- const pool = createPool();
46
- const resourceCache = createResourceCache();
47
- const eventManager = createEventManager();
53
+ // Initialize components
54
+ const pool = createPool();
55
+ const resourceCache = createResourceCache();
56
+ const eventManager = createEventManager();
48
57
 
49
- // Initialize system event manager
50
- const systemEventManager = new SystemEventManager(pool, SERVER_INSTANCE_ID);
58
+ // Initialize system event manager
59
+ const systemEventManager = new SystemEventManager(pool, SERVER_INSTANCE_ID);
51
60
 
52
- // Register cache event handlers
53
- resourceCache.registerEventHandlers(systemEventManager);
61
+ // Register cache event handlers
62
+ resourceCache.registerEventHandlers(systemEventManager);
54
63
 
55
- // Always use optimized queue manager - it's better for all scenarios
56
- const queueManager = createOptimizedQueueManager(pool, resourceCache, eventManager, systemEventManager);
57
- const messagesRoutes = createMessagesRoutes(pool, queueManager);
58
- const resourcesRoutes = createResourcesRoutes(pool, systemEventManager);
59
- const statusRoutes = createStatusRoutes(pool);
64
+ // Always use optimized queue manager - it's better for all scenarios
65
+ const queueManager = createOptimizedQueueManager(pool, resourceCache, eventManager, systemEventManager);
66
+ const messagesRoutes = createMessagesRoutes(pool, queueManager);
67
+ const resourcesRoutes = createResourcesRoutes(pool, systemEventManager);
68
+ const statusRoutes = createStatusRoutes(pool);
60
69
 
61
- // Initialize database with migrations
62
- const initDatabaseWithMigrations = async () => {
63
- // Run base schema
64
- await initDatabase(pool);
65
-
66
- // Run extension migrations
67
- const migrationPath = path.join(__dirname, '..', 'migrations', '001-extend-features.sql');
68
- if (fs.existsSync(migrationPath)) {
69
- const migration = fs.readFileSync(migrationPath, 'utf8');
70
- try {
71
- await pool.query(migration);
72
- log('✅ Extension features migration applied');
73
- } catch (error) {
74
- if (!error.message.includes('already exists')) {
75
- log('Migration error:', error);
70
+ // Initialize database with migrations
71
+ const initDatabaseWithMigrations = async () => {
72
+ // Run base schema
73
+ await initDatabase(pool);
74
+
75
+ // Run extension migrations
76
+ const migrationPath = path.join(__dirname, '..', 'migrations', '001-extend-features.sql');
77
+ if (fs.existsSync(migrationPath)) {
78
+ const migration = fs.readFileSync(migrationPath, 'utf8');
79
+ try {
80
+ await pool.query(migration);
81
+ log('✅ Extension features migration applied');
82
+ } catch (error) {
83
+ if (!error.message.includes('already exists')) {
84
+ log('Migration error:', error);
85
+ }
76
86
  }
77
87
  }
78
- }
79
- };
88
+ };
80
89
 
81
- await initDatabaseWithMigrations();
90
+ await initDatabaseWithMigrations();
82
91
 
83
- // Initialize system queue
84
- async function initializeSystemQueue() {
85
- try {
86
- // Direct database operation to create system queue
87
- await pool.query(`
88
- INSERT INTO queen.queues (name, ttl, priority, max_queue_size)
89
- VALUES ($1, 300, 100, 10000)
90
- ON CONFLICT (name) DO UPDATE SET
91
- ttl = EXCLUDED.ttl,
92
- priority = EXCLUDED.priority,
93
- max_queue_size = EXCLUDED.max_queue_size
94
- `, [SYSTEM_QUEUE]);
95
-
96
- await pool.query(`
97
- INSERT INTO queen.partitions (queue_id, name)
98
- SELECT id, 'Default' FROM queen.queues WHERE name = $1
99
- ON CONFLICT (queue_id, name) DO NOTHING
100
- `, [SYSTEM_QUEUE]);
101
-
102
- log('✅ System event queue initialized');
103
- } catch (error) {
104
- console.error('Failed to initialize system queue:', error);
105
- throw error;
92
+ // Initialize system queue
93
+ async function initializeSystemQueue() {
94
+ try {
95
+ // Direct database operation to create system queue
96
+ await pool.query(`
97
+ INSERT INTO queen.queues (name, ttl, priority, max_queue_size)
98
+ VALUES ($1, 300, 100, 10000)
99
+ ON CONFLICT (name) DO UPDATE SET
100
+ ttl = EXCLUDED.ttl,
101
+ priority = EXCLUDED.priority,
102
+ max_queue_size = EXCLUDED.max_queue_size
103
+ `, [SYSTEM_QUEUE]);
104
+
105
+ await pool.query(`
106
+ INSERT INTO queen.partitions (queue_id, name)
107
+ SELECT id, 'Default' FROM queen.queues WHERE name = $1
108
+ ON CONFLICT (queue_id, name) DO NOTHING
109
+ `, [SYSTEM_QUEUE]);
110
+
111
+ log('✅ System event queue initialized');
112
+ } catch (error) {
113
+ console.error('Failed to initialize system queue:', error);
114
+ throw error;
115
+ }
106
116
  }
107
- }
108
117
 
109
- await initializeSystemQueue();
118
+ await initializeSystemQueue();
110
119
 
111
- // Initialize extended features
112
- log('🚀 Initializing Queen Services...');
120
+ // Initialize extended features
121
+ log('🚀 Initializing Queen Services...');
113
122
 
114
- // 1. Encryption service
115
- const encryptionEnabled = initEncryption();
123
+ // 1. Encryption service
124
+ const encryptionEnabled = initEncryption();
116
125
 
117
- // 2. Retention service
118
- const stopRetention = startRetentionJob(pool);
126
+ // 2. Retention service
127
+ const stopRetention = startRetentionJob(pool);
119
128
 
120
- // 3. Eviction service
121
- const stopEviction = startEvictionJob(pool, eventManager);
129
+ // 3. Eviction service
130
+ const stopEviction = startEvictionJob(pool, eventManager);
122
131
 
123
- // Background job for lease reclamation
124
- const leaseReclaimInterval = setInterval(async () => {
125
- if (isShuttingDown) return;
126
- try {
127
- const reclaimed = await queueManager.reclaimExpiredLeases();
128
- if (reclaimed > 0) {
129
- log(`Reclaimed ${reclaimed} expired leases`);
130
- }
131
- } catch (error) {
132
- if (!isShuttingDown) {
133
- log('Error reclaiming leases:', error);
132
+ // Background job for lease reclamation
133
+ const leaseReclaimInterval = setInterval(async () => {
134
+ if (isShuttingDown) return;
135
+ try {
136
+ const reclaimed = await queueManager.reclaimExpiredLeases();
137
+ if (reclaimed > 0) {
138
+ log(`Reclaimed ${reclaimed} expired leases`);
139
+ }
140
+ } catch (error) {
141
+ if (!isShuttingDown) {
142
+ log('Error reclaiming leases:', error);
143
+ }
134
144
  }
135
- }
136
- }, config.JOBS.LEASE_RECLAIM_INTERVAL);
145
+ }, config.JOBS.LEASE_RECLAIM_INTERVAL);
137
146
 
138
- // Create server
139
- const app = uWS.App();
147
+ // Create server
148
+ const app = uWS.App();
140
149
 
141
- // Initialize WebSocket server
142
- const wsServer = createWebSocketServer(app, eventManager);
150
+ // Initialize WebSocket server
151
+ const wsServer = createWebSocketServer(app, eventManager);
143
152
 
144
- // Background job for queue depth updates
145
- const queueDepthInterval = setInterval(async () => {
146
- if (isShuttingDown) return;
147
- try {
148
- await wsServer.updateQueueDepths(queueManager);
149
- } catch (error) {
150
- if (!isShuttingDown) {
151
- log('Error updating queue depths:', error);
153
+ // Background job for queue depth updates
154
+ const queueDepthInterval = setInterval(async () => {
155
+ if (isShuttingDown) return;
156
+ try {
157
+ await wsServer.updateQueueDepths(queueManager);
158
+ } catch (error) {
159
+ if (!isShuttingDown) {
160
+ log('Error updating queue depths:', error);
161
+ }
152
162
  }
153
- }
154
- }, config.JOBS.QUEUE_DEPTH_UPDATE_INTERVAL);
163
+ }, config.JOBS.QUEUE_DEPTH_UPDATE_INTERVAL);
155
164
 
156
- // Background job for system stats
157
- const systemStatsInterval = setInterval(async () => {
158
- if (isShuttingDown) return;
159
- try {
160
- await wsServer.sendSystemStats(pool);
161
- } catch (error) {
162
- if (!isShuttingDown) {
163
- log('Error sending system stats:', error);
165
+ // Background job for system stats
166
+ const systemStatsInterval = setInterval(async () => {
167
+ if (isShuttingDown) return;
168
+ try {
169
+ await wsServer.sendSystemStats(pool);
170
+ } catch (error) {
171
+ if (!isShuttingDown) {
172
+ log('Error sending system stats:', error);
173
+ }
164
174
  }
165
- }
166
- }, config.JOBS.SYSTEM_STATS_UPDATE_INTERVAL);
175
+ }, config.JOBS.SYSTEM_STATS_UPDATE_INTERVAL);
167
176
 
168
- // CORS headers helper
169
- const setCorsHeaders = (res) => {
170
- res.writeHeader('Access-Control-Allow-Origin', config.API.CORS_ALLOWED_ORIGINS);
171
- res.writeHeader('Access-Control-Allow-Methods', config.API.CORS_ALLOWED_METHODS);
172
- res.writeHeader('Access-Control-Allow-Headers', config.API.CORS_ALLOWED_HEADERS);
173
- res.writeHeader('Access-Control-Max-Age', config.API.CORS_MAX_AGE.toString());
174
- };
177
+ // CORS headers helper
178
+ const setCorsHeaders = (res) => {
179
+ res.writeHeader('Access-Control-Allow-Origin', config.API.CORS_ALLOWED_ORIGINS);
180
+ res.writeHeader('Access-Control-Allow-Methods', config.API.CORS_ALLOWED_METHODS);
181
+ res.writeHeader('Access-Control-Allow-Headers', config.API.CORS_ALLOWED_HEADERS);
182
+ res.writeHeader('Access-Control-Max-Age', config.API.CORS_MAX_AGE.toString());
183
+ };
175
184
 
176
- // Helper to read JSON body
177
- const readJson = (res, cb, abortedRef) => {
178
- let buffer;
179
- const maxBodySize = config.API.MAX_BODY_SIZE; // 10MB max body size
180
-
181
- res.onData((ab, isLast) => {
182
- const chunk = Buffer.from(ab);
183
-
184
- // Check size limit
185
- const currentSize = (buffer ? buffer.length : 0) + chunk.length;
186
- if (currentSize > maxBodySize) {
187
- if (abortedRef && abortedRef.aborted) return;
188
- res.writeStatus(config.HTTP_STATUS.BAD_REQUEST.toString()).end(JSON.stringify({ error: 'Request body too large' }));
189
- return;
190
- }
185
+ // Helper to read JSON body
186
+ const readJson = (res, cb, abortedRef) => {
187
+ let buffer;
188
+ const maxBodySize = config.API.MAX_BODY_SIZE; // 10MB max body size
191
189
 
192
- if (isLast) {
193
- let json;
194
- try {
195
- let fullData;
196
- if (buffer) {
197
- fullData = Buffer.concat([buffer, chunk]);
198
- } else {
199
- fullData = chunk;
200
- }
201
-
202
- const jsonString = fullData.toString('utf8');
203
-
204
- // Debug large payloads
205
- if (jsonString.length > 1000000) {
206
- log(`Parsing large JSON: ${jsonString.length} bytes, starts with: ${jsonString.substring(0, 100)}`);
207
- }
208
-
209
- json = JSON.parse(jsonString);
210
- } catch (e) {
190
+ res.onData((ab, isLast) => {
191
+ const chunk = Buffer.from(ab);
192
+
193
+ // Check size limit
194
+ const currentSize = (buffer ? buffer.length : 0) + chunk.length;
195
+ if (currentSize > maxBodySize) {
211
196
  if (abortedRef && abortedRef.aborted) return;
212
- // Log the actual error for debugging
213
- log(`JSON parse error: ${e.message}`);
214
-
215
- // Log what we're trying to parse for debugging
216
- let sample = '';
217
- if (buffer) {
218
- const fullData = Buffer.concat([buffer, chunk]);
219
- sample = fullData.toString('utf8').substring(0, 200);
220
- } else {
221
- sample = chunk.toString('utf8').substring(0, 200);
222
- }
223
- log(`Failed to parse data starting with: ${sample}`);
224
-
225
- res.writeStatus(config.HTTP_STATUS.BAD_REQUEST.toString()).end(JSON.stringify({ error: 'Invalid JSON' }));
197
+ res.writeStatus(config.HTTP_STATUS.BAD_REQUEST.toString()).end(JSON.stringify({ error: 'Request body too large' }));
226
198
  return;
227
199
  }
228
- cb(json);
229
- } else {
230
- if (buffer) {
231
- buffer = Buffer.concat([buffer, chunk]);
200
+
201
+ if (isLast) {
202
+ let json;
203
+ try {
204
+ let fullData;
205
+ if (buffer) {
206
+ fullData = Buffer.concat([buffer, chunk]);
207
+ } else {
208
+ fullData = chunk;
209
+ }
210
+
211
+ const jsonString = fullData.toString('utf8');
212
+
213
+ // Debug large payloads
214
+ if (jsonString.length > 1000000) {
215
+ log(`Parsing large JSON: ${jsonString.length} bytes, starts with: ${jsonString.substring(0, 100)}`);
216
+ }
217
+
218
+ json = JSON.parse(jsonString);
219
+ } catch (e) {
220
+ if (abortedRef && abortedRef.aborted) return;
221
+ // Log the actual error for debugging
222
+ log(`JSON parse error: ${e.message}`);
223
+
224
+ // Log what we're trying to parse for debugging
225
+ let sample = '';
226
+ if (buffer) {
227
+ const fullData = Buffer.concat([buffer, chunk]);
228
+ sample = fullData.toString('utf8').substring(0, 200);
229
+ } else {
230
+ sample = chunk.toString('utf8').substring(0, 200);
231
+ }
232
+ log(`Failed to parse data starting with: ${sample}`);
233
+
234
+ res.writeStatus(config.HTTP_STATUS.BAD_REQUEST.toString()).end(JSON.stringify({ error: 'Invalid JSON' }));
235
+ return;
236
+ }
237
+ cb(json);
232
238
  } else {
233
- buffer = Buffer.from(chunk); // Ensure it's a Buffer
239
+ if (buffer) {
240
+ buffer = Buffer.concat([buffer, chunk]);
241
+ } else {
242
+ buffer = Buffer.from(chunk); // Ensure it's a Buffer
243
+ }
234
244
  }
235
- }
236
- });
237
- };
245
+ });
246
+ };
238
247
 
239
- // Handle OPTIONS requests for CORS preflight
240
- app.options('/*', (res, req) => {
241
- res.onAborted(() => {
242
- log('OPTIONS request aborted');
243
- });
244
-
245
- res.cork(() => {
246
- setCorsHeaders(res);
247
- res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
248
+ // Handle OPTIONS requests for CORS preflight
249
+ app.options('/*', (res, req) => {
250
+ res.onAborted(() => {
251
+ log('OPTIONS request aborted');
252
+ });
253
+
254
+ res.cork(() => {
255
+ setCorsHeaders(res);
256
+ res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
257
+ });
248
258
  });
249
- });
250
259
 
251
- // Configure route
252
- app.post('/api/v1/configure', (res, req) => {
253
- const abortedRef = { aborted: false };
254
- res.onAborted(() => {
255
- abortedRef.aborted = true;
256
- log('Configure request aborted');
257
- });
258
-
259
- readJson(res, async (body) => {
260
- if (abortedRef.aborted) return;
261
- try {
262
- const result = await createConfigureRoute(queueManager)(body);
263
- if (abortedRef.aborted) return;
264
- eventManager.emit('queue.created', result);
265
- res.cork(() => {
266
- setCorsHeaders(res);
267
- res.writeStatus(config.HTTP_STATUS.CREATED.toString()).end(JSON.stringify(result));
268
- });
269
- } catch (error) {
260
+ // Configure route
261
+ app.post('/api/v1/configure', (res, req) => {
262
+ const abortedRef = { aborted: false };
263
+ res.onAborted(() => {
264
+ abortedRef.aborted = true;
265
+ log('Configure request aborted');
266
+ });
267
+
268
+ readJson(res, async (body) => {
270
269
  if (abortedRef.aborted) return;
271
- log('Configure error:', error);
272
- res.cork(() => {
273
- setCorsHeaders(res);
274
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
275
- });
276
- }
277
- }, abortedRef);
278
- });
279
-
280
- // Push route
281
- app.post('/api/v1/push', (res, req) => {
282
- const abortedRef = { aborted: false };
283
- res.onAborted(() => {
284
- abortedRef.aborted = true;
285
- log('Push request aborted');
270
+ try {
271
+ const result = await createConfigureRoute(queueManager)(body);
272
+ if (abortedRef.aborted) return;
273
+ eventManager.emit('queue.created', result);
274
+ res.cork(() => {
275
+ setCorsHeaders(res);
276
+ res.writeStatus(config.HTTP_STATUS.CREATED.toString()).end(JSON.stringify(result));
277
+ });
278
+ } catch (error) {
279
+ if (abortedRef.aborted) return;
280
+ log('Configure error:', error);
281
+ res.cork(() => {
282
+ setCorsHeaders(res);
283
+ res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
284
+ });
285
+ }
286
+ }, abortedRef);
286
287
  });
287
-
288
- readJson(res, async (body) => {
289
- if (abortedRef.aborted) return;
290
- try {
291
- const result = await createPushRoute(queueManager)(body);
292
- messageCount += result.messages.length;
293
- requestCount++;
288
+
289
+ // Push route
290
+ app.post('/api/v1/push', (res, req) => {
291
+ const abortedRef = { aborted: false };
292
+ res.onAborted(() => {
293
+ abortedRef.aborted = true;
294
+ log('Push request aborted');
295
+ });
296
+
297
+ readJson(res, async (body) => {
294
298
  if (abortedRef.aborted) return;
295
-
296
- // Emit events for pushed messages
297
- for (const msg of result.messages) {
298
- if (msg.status === 'queued') {
299
- const item = body.items.find(i =>
300
- i.transactionId === msg.transactionId || !i.transactionId
301
- );
302
- if (item) {
303
- const partition = item.partition ?? 'Default';
304
- const queuePath = `${item.queue}/${partition}`;
305
- eventManager.emit('message.pushed', {
306
- queue: item.queue,
307
- partition: partition,
308
- transactionId: msg.transactionId
309
- });
310
- eventManager.notifyMessageAvailable(queuePath);
299
+ try {
300
+ const result = await createPushRoute(queueManager)(body);
301
+ messageCount += result.messages.length;
302
+ requestCount++;
303
+ if (abortedRef.aborted) return;
304
+
305
+ // Emit events for pushed messages
306
+ for (const msg of result.messages) {
307
+ if (msg.status === 'queued') {
308
+ const item = body.items.find(i =>
309
+ i.transactionId === msg.transactionId || !i.transactionId
310
+ );
311
+ if (item) {
312
+ const partition = item.partition ?? 'Default';
313
+ const queuePath = `${item.queue}/${partition}`;
314
+ eventManager.emit('message.pushed', {
315
+ queue: item.queue,
316
+ partition: partition,
317
+ transactionId: msg.transactionId
318
+ });
319
+ eventManager.notifyMessageAvailable(queuePath);
320
+ }
311
321
  }
312
322
  }
323
+
324
+ res.cork(() => {
325
+ setCorsHeaders(res);
326
+ res.writeStatus(config.HTTP_STATUS.CREATED.toString()).end(JSON.stringify(result));
327
+ });
328
+ } catch (error) {
329
+ if (abortedRef.aborted) return;
330
+ log('Push error:', error.message || error);
331
+ res.cork(() => {
332
+ setCorsHeaders(res);
333
+ res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
334
+ });
313
335
  }
314
-
315
- res.cork(() => {
316
- setCorsHeaders(res);
317
- res.writeStatus(config.HTTP_STATUS.CREATED.toString()).end(JSON.stringify(result));
318
- });
319
- } catch (error) {
320
- if (abortedRef.aborted) return;
321
- log('Push error:', error.message || error);
322
- res.cork(() => {
323
- setCorsHeaders(res);
324
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
325
- });
326
- }
327
- }, abortedRef);
328
- });
329
-
330
- // Pop routes with new structure
331
- app.get('/api/v1/pop/queue/:queue/partition/:partition', (res, req) => {
332
- const queue = req.getParameter(0);
333
- const partition = req.getParameter(1);
334
- const query = new URLSearchParams(req.getQuery());
335
-
336
- let aborted = false;
337
- res.onAborted(() => {
338
- aborted = true;
339
- log('Pop request aborted');
336
+ }, abortedRef);
340
337
  });
341
-
342
- createPopRoute(queueManager, eventManager)(
343
- {
344
- queue,
345
- partition,
346
- consumerGroup: query.get('consumerGroup') || query.get('consumer_group')
347
- },
348
- {
349
- wait: query.get('wait') === 'true',
350
- timeout: parseInt(query.get('timeout') || config.QUEUE.DEFAULT_TIMEOUT.toString()),
351
- batch: parseInt(query.get('batch') || config.QUEUE.DEFAULT_BATCH_SIZE.toString()),
352
- subscriptionMode: query.get('subscriptionMode'),
353
- subscriptionFrom: query.get('subscriptionFrom')
354
- }
355
- ).then(result => {
356
- if (aborted) return;
338
+
339
+ // Pop routes with new structure
340
+ app.get('/api/v1/pop/queue/:queue/partition/:partition', (res, req) => {
341
+ const queue = req.getParameter(0);
342
+ const partition = req.getParameter(1);
343
+ const query = new URLSearchParams(req.getQuery());
357
344
 
358
- // Emit events for processing messages
359
- for (const msg of result.messages) {
360
- eventManager.emit('message.processing', {
361
- queue: msg.queue,
362
- partition: msg.partition,
363
- transactionId: msg.transactionId,
364
- workerId: `worker-${process.pid}`
365
- });
366
- }
345
+ let aborted = false;
346
+ res.onAborted(() => {
347
+ aborted = true;
348
+ log('Pop request aborted');
349
+ });
367
350
 
368
- // Use streaming with backpressure for large responses
369
- if (result.messages.length === 0) {
370
- res.cork(() => {
371
- setCorsHeaders(res);
372
- res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
373
- });
374
- } else {
351
+ createPopRoute(queueManager, eventManager)(
352
+ {
353
+ queue,
354
+ partition,
355
+ consumerGroup: query.get('consumerGroup') || query.get('consumer_group')
356
+ },
357
+ {
358
+ wait: query.get('wait') === 'true',
359
+ timeout: parseInt(query.get('timeout') || config.QUEUE.DEFAULT_TIMEOUT.toString()),
360
+ batch: parseInt(query.get('batch') || config.QUEUE.DEFAULT_BATCH_SIZE.toString()),
361
+ subscriptionMode: query.get('subscriptionMode'),
362
+ subscriptionFrom: query.get('subscriptionFrom')
363
+ }
364
+ ).then(result => {
365
+ if (aborted) return;
366
+
367
+ // Emit events for processing messages
368
+ for (const msg of result.messages) {
369
+ eventManager.emit('message.processing', {
370
+ queue: msg.queue,
371
+ partition: msg.partition,
372
+ transactionId: msg.transactionId,
373
+ workerId: `worker-${process.pid}`
374
+ });
375
+ }
376
+
377
+ // Use streaming with backpressure for large responses
378
+ if (result.messages.length === 0) {
379
+ res.cork(() => {
380
+ setCorsHeaders(res);
381
+ res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
382
+ });
383
+ } else {
384
+ res.cork(() => {
385
+ setCorsHeaders(res);
386
+ res.writeStatus(config.HTTP_STATUS.OK.toString());
387
+ });
388
+ streamJSON(res, result, { aborted });
389
+ }
390
+ }).catch(error => {
391
+ if (aborted) return;
392
+ log('Pop error:', error);
375
393
  res.cork(() => {
376
394
  setCorsHeaders(res);
377
- res.writeStatus(config.HTTP_STATUS.OK.toString());
378
395
  });
379
- streamJSON(res, result, { aborted });
380
- }
381
- }).catch(error => {
382
- if (aborted) return;
383
- log('Pop error:', error);
384
- res.cork(() => {
385
- setCorsHeaders(res);
396
+ sendError(res, error, config.HTTP_STATUS.INTERNAL_SERVER_ERROR);
386
397
  });
387
- sendError(res, error, config.HTTP_STATUS.INTERNAL_SERVER_ERROR);
388
398
  });
389
- });
390
399
 
391
- app.get('/api/v1/pop/queue/:queue', (res, req) => {
392
- const queue = req.getParameter(0);
393
- const query = new URLSearchParams(req.getQuery());
400
+ app.get('/api/v1/pop/queue/:queue', (res, req) => {
401
+ const queue = req.getParameter(0);
402
+ const query = new URLSearchParams(req.getQuery());
394
403
 
395
- let aborted = false;
396
- res.onAborted(() => {
404
+ let aborted = false;
405
+ res.onAborted(() => {
397
406
  aborted = true;
398
407
  log('Pop request aborted');
399
408
  });
400
409
 
401
- createPopRoute(queueManager, eventManager)(
410
+ createPopRoute(queueManager, eventManager)(
402
411
  {
403
412
  queue,
404
413
  consumerGroup: query.get('consumerGroup') || query.get('consumer_group')
@@ -446,18 +455,18 @@ app.get('/api/v1/pop/queue/:queue', (res, req) => {
446
455
  });
447
456
 
448
457
  // Pop with filters (namespace or task)
449
- app.get('/api/v1/pop', (res, req) => {
450
- const query = new URLSearchParams(req.getQuery());
451
- const namespace = query.get('namespace');
452
- const task = query.get('task');
458
+ app.get('/api/v1/pop', (res, req) => {
459
+ const query = new URLSearchParams(req.getQuery());
460
+ const namespace = query.get('namespace');
461
+ const task = query.get('task');
453
462
 
454
- let aborted = false;
455
- res.onAborted(() => {
463
+ let aborted = false;
464
+ res.onAborted(() => {
456
465
  aborted = true;
457
466
  log('Pop request aborted');
458
467
  });
459
468
 
460
- createPopRoute(queueManager, eventManager)(
469
+ createPopRoute(queueManager, eventManager)(
461
470
  {
462
471
  namespace,
463
472
  task,
@@ -506,14 +515,14 @@ app.get('/api/v1/pop', (res, req) => {
506
515
  });
507
516
 
508
517
  // ACK route
509
- app.post('/api/v1/ack', (res, req) => {
510
- const abortedRef = { aborted: false };
511
- res.onAborted(() => {
518
+ app.post('/api/v1/ack', (res, req) => {
519
+ const abortedRef = { aborted: false };
520
+ res.onAborted(() => {
512
521
  abortedRef.aborted = true;
513
522
  log('ACK request aborted');
514
523
  });
515
524
 
516
- readJson(res, async (body) => {
525
+ readJson(res, async (body) => {
517
526
  if (abortedRef.aborted) return;
518
527
  try {
519
528
  const result = await createAckRoute(queueManager)(body);
@@ -547,14 +556,14 @@ app.post('/api/v1/ack', (res, req) => {
547
556
  });
548
557
 
549
558
  // Batch ACK route
550
- app.post('/api/v1/ack/batch', (res, req) => {
551
- const abortedRef = { aborted: false };
552
- res.onAborted(() => {
559
+ app.post('/api/v1/ack/batch', (res, req) => {
560
+ const abortedRef = { aborted: false };
561
+ res.onAborted(() => {
553
562
  abortedRef.aborted = true;
554
563
  log('Batch ACK request aborted');
555
564
  });
556
565
 
557
- readJson(res, async (body) => {
566
+ readJson(res, async (body) => {
558
567
  if (abortedRef.aborted) return;
559
568
  try {
560
569
  const results = await queueManager.acknowledgeMessages(body.acknowledgments, body.consumerGroup);
@@ -593,16 +602,16 @@ app.post('/api/v1/ack/batch', (res, req) => {
593
602
  });
594
603
 
595
604
  // Messages routes
596
- app.get('/api/v1/messages', (res, req) => {
597
- const query = new URLSearchParams(req.getQuery());
605
+ app.get('/api/v1/messages', (res, req) => {
606
+ const query = new URLSearchParams(req.getQuery());
598
607
 
599
- let aborted = false;
600
- res.onAborted(() => {
608
+ let aborted = false;
609
+ res.onAborted(() => {
601
610
  aborted = true;
602
611
  log('Messages list request aborted');
603
612
  });
604
613
 
605
- const filters = {
614
+ const filters = {
606
615
  ns: query.get('ns'),
607
616
  task: query.get('task'),
608
617
  queue: query.get('queue'),
@@ -611,7 +620,7 @@ app.get('/api/v1/messages', (res, req) => {
611
620
  offset: parseInt(query.get('offset') || config.API.DEFAULT_OFFSET.toString())
612
621
  };
613
622
 
614
- messagesRoutes.listMessages(filters).then(result => {
623
+ messagesRoutes.listMessages(filters).then(result => {
615
624
  if (aborted) return;
616
625
  res.cork(() => {
617
626
  setCorsHeaders(res);
@@ -627,16 +636,16 @@ app.get('/api/v1/messages', (res, req) => {
627
636
  });
628
637
  });
629
638
 
630
- app.get('/api/v1/messages/:transactionId', (res, req) => {
631
- const transactionId = req.getParameter(0);
639
+ app.get('/api/v1/messages/:transactionId', (res, req) => {
640
+ const transactionId = req.getParameter(0);
632
641
 
633
- let aborted = false;
634
- res.onAborted(() => {
642
+ let aborted = false;
643
+ res.onAborted(() => {
635
644
  aborted = true;
636
645
  log('Get message request aborted');
637
646
  });
638
647
 
639
- messagesRoutes.getMessage(transactionId).then(result => {
648
+ messagesRoutes.getMessage(transactionId).then(result => {
640
649
  if (aborted) return;
641
650
  res.cork(() => {
642
651
  setCorsHeaders(res);
@@ -653,16 +662,16 @@ app.get('/api/v1/messages/:transactionId', (res, req) => {
653
662
  });
654
663
  });
655
664
 
656
- app.del('/api/v1/messages/:transactionId', (res, req) => {
657
- const transactionId = req.getParameter(0);
665
+ app.del('/api/v1/messages/:transactionId', (res, req) => {
666
+ const transactionId = req.getParameter(0);
658
667
 
659
- let aborted = false;
660
- res.onAborted(() => {
668
+ let aborted = false;
669
+ res.onAborted(() => {
661
670
  aborted = true;
662
671
  log('Delete message request aborted');
663
672
  });
664
673
 
665
- messagesRoutes.deleteMessage(transactionId).then(result => {
674
+ messagesRoutes.deleteMessage(transactionId).then(result => {
666
675
  if (aborted) return;
667
676
  res.cork(() => {
668
677
  setCorsHeaders(res);
@@ -679,16 +688,16 @@ app.del('/api/v1/messages/:transactionId', (res, req) => {
679
688
  });
680
689
  });
681
690
 
682
- app.post('/api/v1/messages/:transactionId/retry', (res, req) => {
683
- const transactionId = req.getParameter(0);
691
+ app.post('/api/v1/messages/:transactionId/retry', (res, req) => {
692
+ const transactionId = req.getParameter(0);
684
693
 
685
- let aborted = false;
686
- res.onAborted(() => {
694
+ let aborted = false;
695
+ res.onAborted(() => {
687
696
  aborted = true;
688
697
  log('Retry message request aborted');
689
698
  });
690
699
 
691
- messagesRoutes.retryMessage(transactionId).then(result => {
700
+ messagesRoutes.retryMessage(transactionId).then(result => {
692
701
  if (aborted) return;
693
702
  res.cork(() => {
694
703
  setCorsHeaders(res);
@@ -704,16 +713,16 @@ app.post('/api/v1/messages/:transactionId/retry', (res, req) => {
704
713
  });
705
714
  });
706
715
 
707
- app.post('/api/v1/messages/:transactionId/dlq', (res, req) => {
708
- const transactionId = req.getParameter(0);
716
+ app.post('/api/v1/messages/:transactionId/dlq', (res, req) => {
717
+ const transactionId = req.getParameter(0);
709
718
 
710
- let aborted = false;
711
- res.onAborted(() => {
719
+ let aborted = false;
720
+ res.onAborted(() => {
712
721
  aborted = true;
713
722
  log('Move to DLQ request aborted');
714
723
  });
715
724
 
716
- messagesRoutes.moveToDLQ(transactionId).then(result => {
725
+ messagesRoutes.moveToDLQ(transactionId).then(result => {
717
726
  if (aborted) return;
718
727
  res.cork(() => {
719
728
  setCorsHeaders(res);
@@ -729,16 +738,16 @@ app.post('/api/v1/messages/:transactionId/dlq', (res, req) => {
729
738
  });
730
739
  });
731
740
 
732
- app.get('/api/v1/messages/:transactionId/related', (res, req) => {
733
- const transactionId = req.getParameter(0);
741
+ app.get('/api/v1/messages/:transactionId/related', (res, req) => {
742
+ const transactionId = req.getParameter(0);
734
743
 
735
- let aborted = false;
736
- res.onAborted(() => {
744
+ let aborted = false;
745
+ res.onAborted(() => {
737
746
  aborted = true;
738
747
  log('Get related messages request aborted');
739
748
  });
740
749
 
741
- messagesRoutes.getRelatedMessages(transactionId).then(result => {
750
+ messagesRoutes.getRelatedMessages(transactionId).then(result => {
742
751
  if (aborted) return;
743
752
  res.cork(() => {
744
753
  setCorsHeaders(res);
@@ -754,18 +763,18 @@ app.get('/api/v1/messages/:transactionId/related', (res, req) => {
754
763
  });
755
764
  });
756
765
 
757
- app.del('/api/v1/queues/:queue/clear', (res, req) => {
758
- const queue = req.getParameter(0);
759
- const query = new URLSearchParams(req.getQuery());
760
- const partition = query.get('partition');
766
+ app.del('/api/v1/queues/:queue/clear', (res, req) => {
767
+ const queue = req.getParameter(0);
768
+ const query = new URLSearchParams(req.getQuery());
769
+ const partition = query.get('partition');
761
770
 
762
- let aborted = false;
763
- res.onAborted(() => {
771
+ let aborted = false;
772
+ res.onAborted(() => {
764
773
  aborted = true;
765
774
  log('Clear queue request aborted');
766
775
  });
767
776
 
768
- messagesRoutes.clearQueue(queue, partition).then(result => {
777
+ messagesRoutes.clearQueue(queue, partition).then(result => {
769
778
  if (aborted) return;
770
779
  res.cork(() => {
771
780
  setCorsHeaders(res);
@@ -782,18 +791,18 @@ app.del('/api/v1/queues/:queue/clear', (res, req) => {
782
791
  });
783
792
 
784
793
  // Resources routes
785
- app.get('/api/v1/resources/queues', (res, req) => {
786
- const query = new URLSearchParams(req.getQuery());
787
- const namespace = query.get('namespace');
788
- const task = query.get('task');
794
+ app.get('/api/v1/resources/queues', (res, req) => {
795
+ const query = new URLSearchParams(req.getQuery());
796
+ const namespace = query.get('namespace');
797
+ const task = query.get('task');
789
798
 
790
- let aborted = false;
791
- res.onAborted(() => {
799
+ let aborted = false;
800
+ res.onAborted(() => {
792
801
  aborted = true;
793
802
  log('Get queues request aborted');
794
803
  });
795
804
 
796
- resourcesRoutes.getQueues({ namespace, task }).then(result => {
805
+ resourcesRoutes.getQueues({ namespace, task }).then(result => {
797
806
  if (aborted) return;
798
807
  res.cork(() => {
799
808
  setCorsHeaders(res);
@@ -809,16 +818,16 @@ app.get('/api/v1/resources/queues', (res, req) => {
809
818
  });
810
819
  });
811
820
 
812
- app.get('/api/v1/resources/queues/:queue', (res, req) => {
813
- const queue = req.getParameter(0);
821
+ app.get('/api/v1/resources/queues/:queue', (res, req) => {
822
+ const queue = req.getParameter(0);
814
823
 
815
- let aborted = false;
816
- res.onAborted(() => {
824
+ let aborted = false;
825
+ res.onAborted(() => {
817
826
  aborted = true;
818
827
  log('Get queue request aborted');
819
828
  });
820
829
 
821
- resourcesRoutes.getQueue(queue).then(result => {
830
+ resourcesRoutes.getQueue(queue).then(result => {
822
831
  if (aborted) return;
823
832
  res.cork(() => {
824
833
  setCorsHeaders(res);
@@ -835,18 +844,18 @@ app.get('/api/v1/resources/queues/:queue', (res, req) => {
835
844
  });
836
845
  });
837
846
 
838
- app.get('/api/v1/resources/partitions', (res, req) => {
839
- const query = new URLSearchParams(req.getQuery());
840
- const queue = query.get('queue');
841
- const minDepth = query.get('minDepth');
847
+ app.get('/api/v1/resources/partitions', (res, req) => {
848
+ const query = new URLSearchParams(req.getQuery());
849
+ const queue = query.get('queue');
850
+ const minDepth = query.get('minDepth');
842
851
 
843
- let aborted = false;
844
- res.onAborted(() => {
852
+ let aborted = false;
853
+ res.onAborted(() => {
845
854
  aborted = true;
846
855
  log('Get partitions request aborted');
847
856
  });
848
857
 
849
- resourcesRoutes.getPartitions({ queue, minDepth: minDepth ? parseInt(minDepth) : null }).then(result => {
858
+ resourcesRoutes.getPartitions({ queue, minDepth: minDepth ? parseInt(minDepth) : null }).then(result => {
850
859
  if (aborted) return;
851
860
  res.cork(() => {
852
861
  setCorsHeaders(res);
@@ -862,14 +871,14 @@ app.get('/api/v1/resources/partitions', (res, req) => {
862
871
  });
863
872
  });
864
873
 
865
- app.get('/api/v1/resources/namespaces', (res, req) => {
866
- let aborted = false;
867
- res.onAborted(() => {
874
+ app.get('/api/v1/resources/namespaces', (res, req) => {
875
+ let aborted = false;
876
+ res.onAborted(() => {
868
877
  aborted = true;
869
878
  log('Get namespaces request aborted');
870
879
  });
871
880
 
872
- resourcesRoutes.getNamespaces().then(result => {
881
+ resourcesRoutes.getNamespaces().then(result => {
873
882
  if (aborted) return;
874
883
  res.cork(() => {
875
884
  setCorsHeaders(res);
@@ -885,14 +894,14 @@ app.get('/api/v1/resources/namespaces', (res, req) => {
885
894
  });
886
895
  });
887
896
 
888
- app.get('/api/v1/resources/tasks', (res, req) => {
889
- let aborted = false;
890
- res.onAborted(() => {
897
+ app.get('/api/v1/resources/tasks', (res, req) => {
898
+ let aborted = false;
899
+ res.onAborted(() => {
891
900
  aborted = true;
892
901
  log('Get tasks request aborted');
893
902
  });
894
903
 
895
- resourcesRoutes.getTasks().then(result => {
904
+ resourcesRoutes.getTasks().then(result => {
896
905
  if (aborted) return;
897
906
  res.cork(() => {
898
907
  setCorsHeaders(res);
@@ -908,14 +917,14 @@ app.get('/api/v1/resources/tasks', (res, req) => {
908
917
  });
909
918
  });
910
919
 
911
- app.get('/api/v1/resources/overview', (res, req) => {
912
- let aborted = false;
913
- res.onAborted(() => {
920
+ app.get('/api/v1/resources/overview', (res, req) => {
921
+ let aborted = false;
922
+ res.onAborted(() => {
914
923
  aborted = true;
915
924
  log('Get overview request aborted');
916
925
  });
917
926
 
918
- resourcesRoutes.getSystemOverview().then(result => {
927
+ resourcesRoutes.getSystemOverview().then(result => {
919
928
  if (aborted) return;
920
929
  res.cork(() => {
921
930
  setCorsHeaders(res);
@@ -932,16 +941,16 @@ app.get('/api/v1/resources/overview', (res, req) => {
932
941
  });
933
942
 
934
943
  // Delete queue
935
- app.del('/api/v1/resources/queues/:queue', (res, req) => {
936
- const queue = req.getParameter(0);
944
+ app.del('/api/v1/resources/queues/:queue', (res, req) => {
945
+ const queue = req.getParameter(0);
937
946
 
938
- let aborted = false;
939
- res.onAborted(() => {
947
+ let aborted = false;
948
+ res.onAborted(() => {
940
949
  aborted = true;
941
950
  log('Delete queue request aborted');
942
951
  });
943
952
 
944
- resourcesRoutes.deleteQueue(queue).then(result => {
953
+ resourcesRoutes.deleteQueue(queue).then(result => {
945
954
  if (aborted) return;
946
955
  res.cork(() => {
947
956
  setCorsHeaders(res);
@@ -962,16 +971,16 @@ app.del('/api/v1/resources/queues/:queue', (res, req) => {
962
971
  // ============================================================================
963
972
 
964
973
  // 1. Dashboard Status - GET /api/v1/status
965
- app.get('/api/v1/status', (res, req) => {
966
- const query = new URLSearchParams(req.getQuery());
974
+ app.get('/api/v1/status', (res, req) => {
975
+ const query = new URLSearchParams(req.getQuery());
967
976
 
968
- let aborted = false;
969
- res.onAborted(() => {
977
+ let aborted = false;
978
+ res.onAborted(() => {
970
979
  aborted = true;
971
980
  log('Status request aborted');
972
981
  });
973
982
 
974
- const filters = {
983
+ const filters = {
975
984
  from: query.get('from'),
976
985
  to: query.get('to'),
977
986
  queue: query.get('queue'),
@@ -979,7 +988,7 @@ app.get('/api/v1/status', (res, req) => {
979
988
  task: query.get('task')
980
989
  };
981
990
 
982
- statusRoutes.getStatus(filters).then(result => {
991
+ statusRoutes.getStatus(filters).then(result => {
983
992
  if (aborted) return;
984
993
  res.cork(() => {
985
994
  setCorsHeaders(res);
@@ -996,16 +1005,16 @@ app.get('/api/v1/status', (res, req) => {
996
1005
  });
997
1006
 
998
1007
  // 2. Queues List - GET /api/v1/status/queues
999
- app.get('/api/v1/status/queues', (res, req) => {
1000
- const query = new URLSearchParams(req.getQuery());
1008
+ app.get('/api/v1/status/queues', (res, req) => {
1009
+ const query = new URLSearchParams(req.getQuery());
1001
1010
 
1002
- let aborted = false;
1003
- res.onAborted(() => {
1011
+ let aborted = false;
1012
+ res.onAborted(() => {
1004
1013
  aborted = true;
1005
1014
  log('Status queues request aborted');
1006
1015
  });
1007
1016
 
1008
- const filters = {
1017
+ const filters = {
1009
1018
  from: query.get('from'),
1010
1019
  to: query.get('to'),
1011
1020
  namespace: query.get('namespace'),
@@ -1014,7 +1023,7 @@ app.get('/api/v1/status/queues', (res, req) => {
1014
1023
  offset: query.get('offset')
1015
1024
  };
1016
1025
 
1017
- statusRoutes.getQueues(filters).then(result => {
1026
+ statusRoutes.getQueues(filters).then(result => {
1018
1027
  if (aborted) return;
1019
1028
  res.cork(() => {
1020
1029
  setCorsHeaders(res);
@@ -1031,22 +1040,22 @@ app.get('/api/v1/status/queues', (res, req) => {
1031
1040
  });
1032
1041
 
1033
1042
  // 3. Queue Detail - GET /api/v1/status/queues/:queueName
1034
- app.get('/api/v1/status/queues/:queue', (res, req) => {
1035
- const queueName = req.getParameter(0);
1036
- const query = new URLSearchParams(req.getQuery());
1043
+ app.get('/api/v1/status/queues/:queue', (res, req) => {
1044
+ const queueName = req.getParameter(0);
1045
+ const query = new URLSearchParams(req.getQuery());
1037
1046
 
1038
- let aborted = false;
1039
- res.onAborted(() => {
1047
+ let aborted = false;
1048
+ res.onAborted(() => {
1040
1049
  aborted = true;
1041
1050
  log('Status queue detail request aborted');
1042
1051
  });
1043
1052
 
1044
- const filters = {
1053
+ const filters = {
1045
1054
  from: query.get('from'),
1046
1055
  to: query.get('to')
1047
1056
  };
1048
1057
 
1049
- statusRoutes.getQueueDetail(queueName, filters).then(result => {
1058
+ statusRoutes.getQueueDetail(queueName, filters).then(result => {
1050
1059
  if (aborted) return;
1051
1060
  res.cork(() => {
1052
1061
  setCorsHeaders(res);
@@ -1066,17 +1075,17 @@ app.get('/api/v1/status/queues/:queue', (res, req) => {
1066
1075
  });
1067
1076
 
1068
1077
  // 4. Queue Messages - GET /api/v1/status/queues/:queueName/messages
1069
- app.get('/api/v1/status/queues/:queue/messages', (res, req) => {
1070
- const queueName = req.getParameter(0);
1071
- const query = new URLSearchParams(req.getQuery());
1078
+ app.get('/api/v1/status/queues/:queue/messages', (res, req) => {
1079
+ const queueName = req.getParameter(0);
1080
+ const query = new URLSearchParams(req.getQuery());
1072
1081
 
1073
- let aborted = false;
1074
- res.onAborted(() => {
1082
+ let aborted = false;
1083
+ res.onAborted(() => {
1075
1084
  aborted = true;
1076
1085
  log('Status queue messages request aborted');
1077
1086
  });
1078
1087
 
1079
- const filters = {
1088
+ const filters = {
1080
1089
  status: query.get('status'),
1081
1090
  partition: query.get('partition'),
1082
1091
  from: query.get('from'),
@@ -1085,7 +1094,7 @@ app.get('/api/v1/status/queues/:queue/messages', (res, req) => {
1085
1094
  offset: query.get('offset')
1086
1095
  };
1087
1096
 
1088
- statusRoutes.getQueueMessages(queueName, filters).then(result => {
1097
+ statusRoutes.getQueueMessages(queueName, filters).then(result => {
1089
1098
  if (aborted) return;
1090
1099
  res.cork(() => {
1091
1100
  setCorsHeaders(res);
@@ -1102,16 +1111,16 @@ app.get('/api/v1/status/queues/:queue/messages', (res, req) => {
1102
1111
  });
1103
1112
 
1104
1113
  // 5. Analytics - GET /api/v1/status/analytics
1105
- app.get('/api/v1/status/analytics', (res, req) => {
1106
- const query = new URLSearchParams(req.getQuery());
1114
+ app.get('/api/v1/status/analytics', (res, req) => {
1115
+ const query = new URLSearchParams(req.getQuery());
1107
1116
 
1108
- let aborted = false;
1109
- res.onAborted(() => {
1117
+ let aborted = false;
1118
+ res.onAborted(() => {
1110
1119
  aborted = true;
1111
1120
  log('Status analytics request aborted');
1112
1121
  });
1113
1122
 
1114
- const filters = {
1123
+ const filters = {
1115
1124
  from: query.get('from'),
1116
1125
  to: query.get('to'),
1117
1126
  queue: query.get('queue'),
@@ -1120,7 +1129,7 @@ app.get('/api/v1/status/analytics', (res, req) => {
1120
1129
  interval: query.get('interval') || 'hour'
1121
1130
  };
1122
1131
 
1123
- statusRoutes.getAnalytics(filters).then(result => {
1132
+ statusRoutes.getAnalytics(filters).then(result => {
1124
1133
  if (aborted) return;
1125
1134
  res.cork(() => {
1126
1135
  setCorsHeaders(res);
@@ -1137,14 +1146,14 @@ app.get('/api/v1/status/analytics', (res, req) => {
1137
1146
  });
1138
1147
 
1139
1148
  // Health check with performance stats
1140
- app.get('/health', async (res, req) => {
1141
- let aborted = false;
1142
- res.onAborted(() => {
1149
+ app.get('/health', async (res, req) => {
1150
+ let aborted = false;
1151
+ res.onAborted(() => {
1143
1152
  aborted = true;
1144
1153
  log('Health check aborted');
1145
1154
  });
1146
1155
 
1147
- try {
1156
+ try {
1148
1157
  await pool.query('SELECT 1');
1149
1158
  if (aborted) return;
1150
1159
 
@@ -1181,13 +1190,13 @@ app.get('/health', async (res, req) => {
1181
1190
  });
1182
1191
 
1183
1192
  // Metrics endpoint for performance monitoring
1184
- app.get('/metrics', (res, req) => {
1185
- res.onAborted(() => {
1193
+ app.get('/metrics', (res, req) => {
1194
+ res.onAborted(() => {
1186
1195
  log('Metrics request aborted');
1187
1196
  });
1188
1197
 
1189
- const uptime = (Date.now() - startTime) / 1000;
1190
- const metrics = {
1198
+ const uptime = (Date.now() - startTime) / 1000;
1199
+ const metrics = {
1191
1200
  uptime: uptime,
1192
1201
  requests: {
1193
1202
  total: requestCount,
@@ -1206,7 +1215,7 @@ app.get('/metrics', (res, req) => {
1206
1215
  cpu: process.cpuUsage()
1207
1216
  };
1208
1217
 
1209
- res.cork(() => {
1218
+ res.cork(() => {
1210
1219
  setCorsHeaders(res);
1211
1220
  res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(metrics));
1212
1221
  });
@@ -1229,7 +1238,7 @@ app.listen(HOST, PORT, async (token) => {
1229
1238
 
1230
1239
  // Start system event synchronization and consumption (only if enabled)
1231
1240
  if (config.SYSTEM_EVENTS.ENABLED) {
1232
- try {
1241
+ /*try { // TODO: Uncomment this when we have a way to synchronize system events
1233
1242
  // Create temporary client for synchronization
1234
1243
  const syncClient = createQueenClient({
1235
1244
  baseUrl: `http://localhost:${PORT}`
@@ -1244,7 +1253,7 @@ app.listen(HOST, PORT, async (token) => {
1244
1253
  queue: SYSTEM_QUEUE,
1245
1254
  consumerGroup: SERVER_INSTANCE_ID,
1246
1255
  handler: async (message) => {
1247
- await systemEventManager.processSystemEvent(message.payload);
1256
+ await systemEventManager.processSystemEvent(message.data);
1248
1257
  },
1249
1258
  options: {
1250
1259
  batch: 10,
@@ -1255,7 +1264,7 @@ app.listen(HOST, PORT, async (token) => {
1255
1264
  log(`✅ System event consumer started`);
1256
1265
  } catch (error) {
1257
1266
  log(`⚠️ Failed to start system event consumer: ${error.message}`);
1258
- }
1267
+ }*/
1259
1268
  }
1260
1269
 
1261
1270
  log(`🎯 Ready to process messages with high performance!`);
@@ -1265,58 +1274,75 @@ app.listen(HOST, PORT, async (token) => {
1265
1274
  }
1266
1275
  });
1267
1276
 
1268
- // Graceful shutdown
1269
- const shutdown = async (signal) => {
1270
- log(`\n🛑 Received ${signal}, shutting down gracefully...`);
1271
-
1272
- // Set shutdown flag immediately to prevent new operations
1273
- isShuttingDown = true;
1274
-
1275
- // Stop system event consumer
1276
- if (stopSystemEventConsumer) {
1277
- stopSystemEventConsumer();
1278
- log('✅ System event consumer stopped');
1279
- }
1280
-
1281
- // Stop background jobs
1282
- if (stopRetention) {
1283
- stopRetention();
1284
- log('✅ Retention job stopped');
1285
- }
1286
- if (stopEviction) {
1287
- stopEviction();
1288
- log('✅ Eviction job stopped');
1289
- }
1290
-
1291
- // Clear background intervals
1292
- if (leaseReclaimInterval) {
1293
- clearInterval(leaseReclaimInterval);
1294
- log('✅ Lease reclaim interval stopped');
1295
- }
1296
- if (queueDepthInterval) {
1297
- clearInterval(queueDepthInterval);
1298
- log('✅ Queue depth update interval stopped');
1299
- }
1300
- if (systemStatsInterval) {
1301
- clearInterval(systemStatsInterval);
1302
- log('✅ System stats interval stopped');
1303
- }
1304
-
1305
- // Wait a moment for any in-flight operations to complete
1306
- await new Promise(resolve => setTimeout(resolve, 100));
1307
-
1308
- const uptime = (Date.now() - startTime) / 1000;
1309
- if (messageCount > 0) {
1310
- log(`📊 Final stats: ${messageCount} messages processed in ${uptime.toFixed(1)}s`);
1311
- log(` Average: ${(messageCount / uptime).toFixed(2)} messages/second`);
1312
- }
1313
-
1314
- // Close database pool last
1315
- await pool.end();
1316
- log('✅ Database pool closed');
1317
-
1318
- process.exit(0);
1319
- };
1277
+ // Graceful shutdown
1278
+ const shutdown = async (signal) => {
1279
+ log(`\n🛑 Received ${signal}, shutting down gracefully...`);
1280
+
1281
+ // Set shutdown flag immediately to prevent new operations
1282
+ isShuttingDown = true;
1283
+
1284
+ // Stop system event consumer
1285
+ if (stopSystemEventConsumer) {
1286
+ stopSystemEventConsumer();
1287
+ log('✅ System event consumer stopped');
1288
+ }
1289
+
1290
+ // Stop background jobs
1291
+ if (stopRetention) {
1292
+ stopRetention();
1293
+ log('✅ Retention job stopped');
1294
+ }
1295
+ if (stopEviction) {
1296
+ stopEviction();
1297
+ log('✅ Eviction job stopped');
1298
+ }
1299
+
1300
+ // Clear background intervals
1301
+ if (leaseReclaimInterval) {
1302
+ clearInterval(leaseReclaimInterval);
1303
+ log('✅ Lease reclaim interval stopped');
1304
+ }
1305
+ if (queueDepthInterval) {
1306
+ clearInterval(queueDepthInterval);
1307
+ log('✅ Queue depth update interval stopped');
1308
+ }
1309
+ if (systemStatsInterval) {
1310
+ clearInterval(systemStatsInterval);
1311
+ log('✅ System stats interval stopped');
1312
+ }
1313
+
1314
+ // Wait a moment for any in-flight operations to complete
1315
+ await new Promise(resolve => setTimeout(resolve, 100));
1316
+
1317
+ const uptime = (Date.now() - startTime) / 1000;
1318
+ if (messageCount > 0) {
1319
+ log(`📊 Final stats: ${messageCount} messages processed in ${uptime.toFixed(1)}s`);
1320
+ log(` Average: ${(messageCount / uptime).toFixed(2)} messages/second`);
1321
+ }
1322
+
1323
+ // Close database pool last
1324
+ await pool.end();
1325
+ log('✅ Database pool closed');
1326
+
1327
+ process.exit(0);
1328
+ };
1329
+
1330
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
1331
+ process.on('SIGINT', () => shutdown('SIGINT'));
1320
1332
 
1321
- process.on('SIGTERM', () => shutdown('SIGTERM'));
1322
- process.on('SIGINT', () => shutdown('SIGINT'));
1333
+ // Return server instance with shutdown method
1334
+ return {
1335
+ port: PORT,
1336
+ host: HOST,
1337
+ shutdown,
1338
+ app
1339
+ };
1340
+ }
1341
+
1342
+ // Auto-start server if run directly (not imported)
1343
+ if (import.meta.url === `file://${process.argv[1]}`) {
1344
+ QueenServer().catch(error => {
1345
+ console.error('Failed to start Queen server:', error);
1346
+ process.exit(1);
1347
+ });
1348
+ }