queen-mq 0.1.2 → 0.1.4

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