queen-mq 0.1.0 → 0.1.2

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.
Files changed (160) hide show
  1. package/API.md +862 -752
  2. package/AUTH.md +2044 -0
  3. package/LICENSE.md +202 -0
  4. package/README.md +1705 -1051
  5. package/WEBAPP.md +1889 -0
  6. package/assets/dashboard-01.png +0 -0
  7. package/assets/queen-logo-blue.svg +210 -0
  8. package/assets/queen-logo-cyan.svg +210 -0
  9. package/assets/queen-logo-indigo.svg +210 -0
  10. package/assets/queen-logo-orange.svg +210 -0
  11. package/assets/queen-logo-pink.svg +210 -0
  12. package/assets/queen-logo-purple.svg +210 -0
  13. package/assets/queen-logo-rose.svg +239 -0
  14. package/assets/queen-logo.svg +263 -0
  15. package/examples/batch-processing.js +58 -0
  16. package/examples/test-complete-client.js +260 -0
  17. package/examples/test-dashboard-api.js +200 -0
  18. package/examples/test-traceid.js +147 -0
  19. package/package.json +17 -4
  20. package/server.log +1 -0
  21. package/src/benchmark/consumer.js +207 -0
  22. package/src/benchmark/consumer_multi.js +216 -0
  23. package/src/benchmark/producer.js +75 -0
  24. package/src/benchmark/producer_multi.js +115 -0
  25. package/src/client/client.js +300 -31
  26. package/src/client/queenClient.js +5 -0
  27. package/src/cluster-server.js +242 -0
  28. package/src/config.js +19 -5
  29. package/src/database/connection.js +42 -16
  30. package/src/database/poolManager.js +7 -0
  31. package/src/database/schema-v2.sql +194 -130
  32. package/src/managers/queueManagerOptimized.js +823 -933
  33. package/src/managers/systemEventManager.js +8 -3
  34. package/src/routes/messages.js +127 -57
  35. package/src/routes/pop.js +27 -43
  36. package/src/routes/resources.js +61 -27
  37. package/src/routes/status.js +1037 -0
  38. package/src/server.js +308 -272
  39. package/src/services/evictionService.js +57 -28
  40. package/src/services/retentionService.js +44 -11
  41. package/src/test/MIGRATION_ISSUES.md +174 -0
  42. package/src/test/README.md +203 -0
  43. package/src/test/advanced-pattern-tests.js +1137 -0
  44. package/src/test/bus-mode-tests.js +361 -0
  45. package/src/test/core-tests.js +342 -0
  46. package/src/test/edge-case-tests.js +561 -0
  47. package/src/test/enterprise-tests.js +637 -0
  48. package/src/test/partition-locking-tests.js +545 -0
  49. package/src/test/test-new.js +278 -0
  50. package/src/test/test.js +6 -3
  51. package/src/test/utils.js +169 -0
  52. package/src/utils/streaming.js +231 -0
  53. package/src/utils/uuid.js +2 -2
  54. package/src/websocket/wsServer.js +10 -3
  55. package/test-keepalive-v2.sh +22 -0
  56. package/webapp/COLOR_GUIDE.md +118 -0
  57. package/webapp/README.md +143 -0
  58. package/webapp/index.html +14 -0
  59. package/webapp/package-lock.json +3184 -0
  60. package/webapp/package.json +25 -0
  61. package/webapp/postcss.config.js +7 -0
  62. package/webapp/public/assets/queen-logo-blue.svg +210 -0
  63. package/webapp/public/assets/queen-logo-cyan.svg +210 -0
  64. package/webapp/public/assets/queen-logo-indigo.svg +210 -0
  65. package/webapp/public/assets/queen-logo-orange.svg +210 -0
  66. package/webapp/public/assets/queen-logo-pink.svg +210 -0
  67. package/webapp/public/assets/queen-logo-purple.svg +210 -0
  68. package/webapp/public/assets/queen-logo-rose.svg +239 -0
  69. package/webapp/public/assets/queen-logo.svg +263 -0
  70. package/webapp/src/App.vue +19 -0
  71. package/webapp/src/api/analytics.js +10 -0
  72. package/webapp/src/api/client.js +29 -0
  73. package/webapp/src/api/consumers.js +52 -0
  74. package/webapp/src/api/health.js +7 -0
  75. package/webapp/src/api/messages.js +26 -0
  76. package/webapp/src/api/queues.js +14 -0
  77. package/webapp/src/api/resources.js +8 -0
  78. package/webapp/src/assets/styles/main.css +357 -0
  79. package/webapp/src/components/analytics/AnalyticsFilters.vue +87 -0
  80. package/webapp/src/components/analytics/AnalyticsMetrics.vue +57 -0
  81. package/webapp/src/components/analytics/MessageDistributionChart.vue +111 -0
  82. package/webapp/src/components/analytics/MessageFlowChart.vue +173 -0
  83. package/webapp/src/components/analytics/TimeRangeSelector.vue +27 -0
  84. package/webapp/src/components/analytics/TopQueuesChart.vue +132 -0
  85. package/webapp/src/components/common/ConfirmDialog.vue +56 -0
  86. package/webapp/src/components/common/LoadingSpinner.vue +6 -0
  87. package/webapp/src/components/common/MetricCard.vue +43 -0
  88. package/webapp/src/components/common/StatusBadge.vue +45 -0
  89. package/webapp/src/components/dashboard/MessageStatusCard.vue +50 -0
  90. package/webapp/src/components/dashboard/PerformanceCard.vue +38 -0
  91. package/webapp/src/components/dashboard/ThroughputChart.vue +182 -0
  92. package/webapp/src/components/dashboard/TopQueuesTable.vue +53 -0
  93. package/webapp/src/components/layout/AppLayout.vue +110 -0
  94. package/webapp/src/components/layout/AppSidebar.vue +304 -0
  95. package/webapp/src/components/messages/MessageDetailPanel.vue +242 -0
  96. package/webapp/src/components/messages/MessageFilters.vue +114 -0
  97. package/webapp/src/components/queue-detail/PartitionList.vue +79 -0
  98. package/webapp/src/components/queue-detail/PushMessageModal.vue +175 -0
  99. package/webapp/src/components/queue-detail/QueueConfig.vue +63 -0
  100. package/webapp/src/components/queue-detail/QueueDetailHeader.vue +53 -0
  101. package/webapp/src/components/queue-detail/RecentMessages.vue +76 -0
  102. package/webapp/src/components/queues/CreateQueueModal.vue +193 -0
  103. package/webapp/src/components/queues/QueueFilters.vue +90 -0
  104. package/webapp/src/composables/useApi.js +34 -0
  105. package/webapp/src/composables/useTheme.js +36 -0
  106. package/webapp/src/main.js +11 -0
  107. package/webapp/src/router/index.js +42 -0
  108. package/webapp/src/utils/colors.js +96 -0
  109. package/webapp/src/utils/formatters.js +49 -0
  110. package/webapp/src/views/Analytics.vue +377 -0
  111. package/webapp/src/views/ConsumerGroups.vue +433 -0
  112. package/webapp/src/views/Dashboard.vue +418 -0
  113. package/webapp/src/views/Messages.vue +361 -0
  114. package/webapp/src/views/QueueDetail.vue +582 -0
  115. package/webapp/src/views/Queues.vue +496 -0
  116. package/webapp/tailwind.config.js +25 -0
  117. package/webapp/vite.config.js +10 -0
  118. package/CACHE.md +0 -519
  119. package/DASHBOARD-V3.md +0 -478
  120. package/DASHBOARD.md +0 -382
  121. package/MOD_QUEUE.md +0 -453
  122. package/PARTITION_LOCKING_DESIGN.md +0 -989
  123. package/PLAN.md +0 -707
  124. package/QUERY_ANALSYS.md +0 -72
  125. package/QUEUE_BUS.md +0 -334
  126. package/V2-PLAN.md +0 -236
  127. package/dashboard/.vscode/extensions.json +0 -3
  128. package/dashboard/README.md +0 -5
  129. package/dashboard/index.html +0 -14
  130. package/dashboard/package-lock.json +0 -1458
  131. package/dashboard/package.json +0 -25
  132. package/dashboard/public/vite.svg +0 -1
  133. package/dashboard/src/App.vue +0 -29
  134. package/dashboard/src/assets/styles/main.css +0 -908
  135. package/dashboard/src/assets/vue.svg +0 -1
  136. package/dashboard/src/components/cards/MetricCard.vue +0 -298
  137. package/dashboard/src/components/charts/QueueDepthChart.vue +0 -276
  138. package/dashboard/src/components/charts/QueueLagChart.vue +0 -436
  139. package/dashboard/src/components/charts/ThroughputChart.vue +0 -302
  140. package/dashboard/src/components/common/ActivityFeed.vue +0 -251
  141. package/dashboard/src/components/layout/AppHeader.vue +0 -208
  142. package/dashboard/src/components/layout/AppLayout.vue +0 -88
  143. package/dashboard/src/components/layout/AppSidebar.vue +0 -261
  144. package/dashboard/src/main.js +0 -44
  145. package/dashboard/src/router.js +0 -54
  146. package/dashboard/src/services/api.js +0 -187
  147. package/dashboard/src/services/websocket.js +0 -167
  148. package/dashboard/src/utils/constants.js +0 -56
  149. package/dashboard/src/utils/helpers.js +0 -118
  150. package/dashboard/src/views/Analytics.vue +0 -912
  151. package/dashboard/src/views/Dashboard.vue +0 -906
  152. package/dashboard/src/views/Messages.vue +0 -437
  153. package/dashboard/src/views/QueueDetail.vue +0 -501
  154. package/dashboard/src/views/Queues.vue +0 -333
  155. package/dashboard/vite.config.js +0 -30
  156. package/debug-namespace.js +0 -110
  157. package/docs/long-polling.md +0 -159
  158. package/docs/multi-server-cache-solutions.md +0 -185
  159. package/docs/performance-tuning.md +0 -222
  160. package/src/routes/analytics.js +0 -812
package/src/server.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import uWS from 'uWebSockets.js';
2
- import pg from 'pg';
3
2
  import fs from 'fs';
4
3
  import path from 'path';
5
4
  import { fileURLToPath } from 'url';
6
5
  import config from './config.js';
7
- import { initDatabase } from './database/connection.js';
6
+ import { createPool, initDatabase } from './database/connection.js';
8
7
  import { createOptimizedQueueManager } from './managers/queueManagerOptimized.js';
9
8
  import { createResourceCache } from './managers/resourceCache.js';
10
9
  import { createEventManager } from './managers/eventManager.js';
@@ -15,14 +14,15 @@ import { createPushRoute } from './routes/push.js';
15
14
  import { createPopRoute } from './routes/pop.js';
16
15
  import { createAckRoute } from './routes/ack.js';
17
16
  import { createConfigureRoute } from './routes/configure.js';
18
- import { createAnalyticsRoutes } from './routes/analytics.js';
19
17
  import { createMessagesRoutes } from './routes/messages.js';
20
18
  import { createResourcesRoutes } from './routes/resources.js';
19
+ import { createStatusRoutes } from './routes/status.js';
21
20
  import { createWebSocketServer } from './websocket/wsServer.js';
22
21
  import { initEncryption } from './services/encryptionService.js';
23
22
  import { startRetentionJob } from './services/retentionService.js';
24
23
  import { startEvictionJob } from './services/evictionService.js';
25
24
  import { log } from './utils/logger.js';
25
+ import { streamJSON, sendJSON, sendError } from './utils/streaming.js';
26
26
 
27
27
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
28
 
@@ -38,26 +38,11 @@ let requestCount = 0;
38
38
  let messageCount = 0;
39
39
  const startTime = Date.now();
40
40
 
41
- // Create optimized connection pool
42
- const { Pool } = pg;
43
- const createOptimizedPool = () => {
44
- return new Pool({
45
- user: config.DATABASE.USER,
46
- host: config.DATABASE.HOST,
47
- database: config.DATABASE.DATABASE,
48
- password: config.DATABASE.PASSWORD,
49
- port: config.DATABASE.PORT,
50
- max: config.DATABASE.POOL_SIZE,
51
- idleTimeoutMillis: config.DATABASE.IDLE_TIMEOUT,
52
- connectionTimeoutMillis: config.DATABASE.CONNECTION_TIMEOUT,
53
- statement_timeout: config.DATABASE.STATEMENT_TIMEOUT,
54
- query_timeout: config.DATABASE.QUERY_TIMEOUT,
55
- application_name: config.SERVER.APPLICATION_NAME
56
- });
57
- };
41
+ // Shutdown flag to prevent background jobs from running during shutdown
42
+ let isShuttingDown = false;
58
43
 
59
44
  // Initialize components
60
- const pool = createOptimizedPool();
45
+ const pool = createPool();
61
46
  const resourceCache = createResourceCache();
62
47
  const eventManager = createEventManager();
63
48
 
@@ -69,9 +54,9 @@ resourceCache.registerEventHandlers(systemEventManager);
69
54
 
70
55
  // Always use optimized queue manager - it's better for all scenarios
71
56
  const queueManager = createOptimizedQueueManager(pool, resourceCache, eventManager, systemEventManager);
72
- const analyticsRoutes = createAnalyticsRoutes(queueManager);
73
57
  const messagesRoutes = createMessagesRoutes(pool, queueManager);
74
- const resourcesRoutes = createResourcesRoutes(pool);
58
+ const resourcesRoutes = createResourcesRoutes(pool, systemEventManager);
59
+ const statusRoutes = createStatusRoutes(pool);
75
60
 
76
61
  // Initialize database with migrations
77
62
  const initDatabaseWithMigrations = async () => {
@@ -136,14 +121,17 @@ const stopRetention = startRetentionJob(pool);
136
121
  const stopEviction = startEvictionJob(pool, eventManager);
137
122
 
138
123
  // Background job for lease reclamation
139
- setInterval(async () => {
124
+ const leaseReclaimInterval = setInterval(async () => {
125
+ if (isShuttingDown) return;
140
126
  try {
141
127
  const reclaimed = await queueManager.reclaimExpiredLeases();
142
128
  if (reclaimed > 0) {
143
129
  log(`Reclaimed ${reclaimed} expired leases`);
144
130
  }
145
131
  } catch (error) {
146
- log('Error reclaiming leases:', error);
132
+ if (!isShuttingDown) {
133
+ log('Error reclaiming leases:', error);
134
+ }
147
135
  }
148
136
  }, config.JOBS.LEASE_RECLAIM_INTERVAL);
149
137
 
@@ -154,13 +142,27 @@ const app = uWS.App();
154
142
  const wsServer = createWebSocketServer(app, eventManager);
155
143
 
156
144
  // Background job for queue depth updates
157
- setInterval(async () => {
158
- await wsServer.updateQueueDepths(queueManager);
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);
152
+ }
153
+ }
159
154
  }, config.JOBS.QUEUE_DEPTH_UPDATE_INTERVAL);
160
155
 
161
156
  // Background job for system stats
162
- setInterval(async () => {
163
- await wsServer.sendSystemStats(pool);
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);
164
+ }
165
+ }
164
166
  }, config.JOBS.SYSTEM_STATS_UPDATE_INTERVAL);
165
167
 
166
168
  // CORS headers helper
@@ -363,21 +365,26 @@ app.get('/api/v1/pop/queue/:queue/partition/:partition', (res, req) => {
363
365
  });
364
366
  }
365
367
 
366
- res.cork(() => {
367
- setCorsHeaders(res);
368
- if (result.messages.length > 0) {
369
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
370
- } else {
368
+ // Use streaming with backpressure for large responses
369
+ if (result.messages.length === 0) {
370
+ res.cork(() => {
371
+ setCorsHeaders(res);
371
372
  res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
372
- }
373
- });
373
+ });
374
+ } else {
375
+ res.cork(() => {
376
+ setCorsHeaders(res);
377
+ res.writeStatus(config.HTTP_STATUS.OK.toString());
378
+ });
379
+ streamJSON(res, result, { aborted });
380
+ }
374
381
  }).catch(error => {
375
382
  if (aborted) return;
376
383
  log('Pop error:', error);
377
384
  res.cork(() => {
378
385
  setCorsHeaders(res);
379
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
380
386
  });
387
+ sendError(res, error, config.HTTP_STATUS.INTERNAL_SERVER_ERROR);
381
388
  });
382
389
  });
383
390
 
@@ -415,21 +422,26 @@ app.get('/api/v1/pop/queue/:queue', (res, req) => {
415
422
  });
416
423
  }
417
424
 
418
- res.cork(() => {
419
- setCorsHeaders(res);
420
- if (result.messages.length > 0) {
421
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
422
- } else {
425
+ // Use streaming with backpressure for large responses
426
+ if (result.messages.length === 0) {
427
+ res.cork(() => {
428
+ setCorsHeaders(res);
423
429
  res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
424
- }
425
- });
430
+ });
431
+ } else {
432
+ res.cork(() => {
433
+ setCorsHeaders(res);
434
+ res.writeStatus(config.HTTP_STATUS.OK.toString());
435
+ });
436
+ streamJSON(res, result, { aborted });
437
+ }
426
438
  }).catch(error => {
427
439
  if (aborted) return;
428
440
  log('Pop error:', error);
429
441
  res.cork(() => {
430
442
  setCorsHeaders(res);
431
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
432
443
  });
444
+ sendError(res, error, config.HTTP_STATUS.INTERNAL_SERVER_ERROR);
433
445
  });
434
446
  });
435
447
 
@@ -470,21 +482,26 @@ app.get('/api/v1/pop', (res, req) => {
470
482
  });
471
483
  }
472
484
 
473
- res.cork(() => {
474
- setCorsHeaders(res);
475
- if (result.messages.length > 0) {
476
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
477
- } else {
485
+ // Use streaming with backpressure for large responses
486
+ if (result.messages.length === 0) {
487
+ res.cork(() => {
488
+ setCorsHeaders(res);
478
489
  res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
479
- }
480
- });
490
+ });
491
+ } else {
492
+ res.cork(() => {
493
+ setCorsHeaders(res);
494
+ res.writeStatus(config.HTTP_STATUS.OK.toString());
495
+ });
496
+ streamJSON(res, result, { aborted });
497
+ }
481
498
  }).catch(error => {
482
499
  if (aborted) return;
483
500
  log('Pop error:', error);
484
501
  res.cork(() => {
485
502
  setCorsHeaders(res);
486
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
487
503
  });
504
+ sendError(res, error, config.HTTP_STATUS.INTERNAL_SERVER_ERROR);
488
505
  });
489
506
  });
490
507
 
@@ -575,30 +592,34 @@ app.post('/api/v1/ack/batch', (res, req) => {
575
592
  }, abortedRef);
576
593
  });
577
594
 
578
- // Analytics routes
579
- app.get('/api/v1/analytics/queues', (res, req) => {
595
+ // Messages routes
596
+ app.get('/api/v1/messages', (res, req) => {
580
597
  const query = new URLSearchParams(req.getQuery());
581
- const fromDateTime = query.get('fromDateTime');
582
- const toDateTime = query.get('toDateTime');
583
- const queue = query.get('queue');
584
- const namespace = query.get('namespace');
585
- const task = query.get('task');
586
598
 
587
599
  let aborted = false;
588
600
  res.onAborted(() => {
589
601
  aborted = true;
590
- log('Analytics request aborted');
602
+ log('Messages list request aborted');
591
603
  });
592
604
 
593
- analyticsRoutes.getQueues({ fromDateTime, toDateTime, queue, namespace, task }).then(result => {
605
+ const filters = {
606
+ ns: query.get('ns'),
607
+ task: query.get('task'),
608
+ queue: query.get('queue'),
609
+ status: query.get('status'),
610
+ limit: parseInt(query.get('limit') || config.API.DEFAULT_LIMIT.toString()),
611
+ offset: parseInt(query.get('offset') || config.API.DEFAULT_OFFSET.toString())
612
+ };
613
+
614
+ messagesRoutes.listMessages(filters).then(result => {
594
615
  if (aborted) return;
595
616
  res.cork(() => {
596
617
  setCorsHeaders(res);
597
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
618
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ messages: result }));
598
619
  });
599
620
  }).catch(error => {
600
621
  if (aborted) return;
601
- log('Analytics error:', error.message || error);
622
+ log('List messages error:', error);
602
623
  res.cork(() => {
603
624
  setCorsHeaders(res);
604
625
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -606,19 +627,16 @@ app.get('/api/v1/analytics/queues', (res, req) => {
606
627
  });
607
628
  });
608
629
 
609
- app.get('/api/v1/analytics/queue/:queue', (res, req) => {
610
- const queue = req.getParameter(0);
611
- const query = new URLSearchParams(req.getQuery());
612
- const fromDateTime = query.get('fromDateTime');
613
- const toDateTime = query.get('toDateTime');
630
+ app.get('/api/v1/messages/:transactionId', (res, req) => {
631
+ const transactionId = req.getParameter(0);
614
632
 
615
633
  let aborted = false;
616
634
  res.onAborted(() => {
617
635
  aborted = true;
618
- log('Analytics request aborted');
636
+ log('Get message request aborted');
619
637
  });
620
638
 
621
- analyticsRoutes.getQueueStats(queue, { fromDateTime, toDateTime }).then(result => {
639
+ messagesRoutes.getMessage(transactionId).then(result => {
622
640
  if (aborted) return;
623
641
  res.cork(() => {
624
642
  setCorsHeaders(res);
@@ -626,37 +644,25 @@ app.get('/api/v1/analytics/queue/:queue', (res, req) => {
626
644
  });
627
645
  }).catch(error => {
628
646
  if (aborted) return;
629
- log('Analytics error:', error.message || error);
647
+ log('Get message error:', error);
630
648
  res.cork(() => {
631
649
  setCorsHeaders(res);
632
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
650
+ res.writeStatus(error.message === 'Message not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
651
+ .end(JSON.stringify({ error: error.message }));
633
652
  });
634
653
  });
635
654
  });
636
655
 
637
- // Analytics with filters
638
- app.get('/api/v1/analytics', (res, req) => {
639
- const query = new URLSearchParams(req.getQuery());
640
- const namespace = query.get('namespace');
641
- const task = query.get('task');
642
- const fromDateTime = query.get('fromDateTime');
643
- const toDateTime = query.get('toDateTime');
644
- const queue = query.get('queue');
656
+ app.del('/api/v1/messages/:transactionId', (res, req) => {
657
+ const transactionId = req.getParameter(0);
645
658
 
646
659
  let aborted = false;
647
660
  res.onAborted(() => {
648
661
  aborted = true;
649
- log('Analytics request aborted');
662
+ log('Delete message request aborted');
650
663
  });
651
664
 
652
- const filters = { fromDateTime, toDateTime };
653
- const getStats = namespace
654
- ? analyticsRoutes.getNamespaceStats(namespace, filters)
655
- : task
656
- ? analyticsRoutes.getTaskStats(task, filters)
657
- : analyticsRoutes.getQueues({ ...filters, queue, namespace, task });
658
-
659
- getStats.then(result => {
665
+ messagesRoutes.deleteMessage(transactionId).then(result => {
660
666
  if (aborted) return;
661
667
  res.cork(() => {
662
668
  setCorsHeaders(res);
@@ -664,29 +670,25 @@ app.get('/api/v1/analytics', (res, req) => {
664
670
  });
665
671
  }).catch(error => {
666
672
  if (aborted) return;
667
- log('Analytics error:', error.message || error);
673
+ log('Delete message error:', error);
668
674
  res.cork(() => {
669
675
  setCorsHeaders(res);
670
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
676
+ res.writeStatus(error.message === 'Message not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
677
+ .end(JSON.stringify({ error: error.message }));
671
678
  });
672
679
  });
673
680
  });
674
681
 
675
- app.get('/api/v1/analytics/queue-depths', (res, req) => {
676
- const query = new URLSearchParams(req.getQuery());
677
- const fromDateTime = query.get('fromDateTime');
678
- const toDateTime = query.get('toDateTime');
679
- const queue = query.get('queue');
680
- const namespace = query.get('namespace');
681
- const task = query.get('task');
682
+ app.post('/api/v1/messages/:transactionId/retry', (res, req) => {
683
+ const transactionId = req.getParameter(0);
682
684
 
683
685
  let aborted = false;
684
686
  res.onAborted(() => {
685
687
  aborted = true;
686
- log('Analytics request aborted');
688
+ log('Retry message request aborted');
687
689
  });
688
690
 
689
- analyticsRoutes.getQueueDepths({ fromDateTime, toDateTime, queue, namespace, task }).then(result => {
691
+ messagesRoutes.retryMessage(transactionId).then(result => {
690
692
  if (aborted) return;
691
693
  res.cork(() => {
692
694
  setCorsHeaders(res);
@@ -694,7 +696,7 @@ app.get('/api/v1/analytics/queue-depths', (res, req) => {
694
696
  });
695
697
  }).catch(error => {
696
698
  if (aborted) return;
697
- log('Analytics error:', error.message || error);
699
+ log('Retry message error:', error);
698
700
  res.cork(() => {
699
701
  setCorsHeaders(res);
700
702
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -702,21 +704,16 @@ app.get('/api/v1/analytics/queue-depths', (res, req) => {
702
704
  });
703
705
  });
704
706
 
705
- app.get('/api/v1/analytics/throughput', (res, req) => {
706
- const query = new URLSearchParams(req.getQuery());
707
- const fromDateTime = query.get('fromDateTime');
708
- const toDateTime = query.get('toDateTime');
709
- const queue = query.get('queue');
710
- const namespace = query.get('namespace');
711
- const task = query.get('task');
707
+ app.post('/api/v1/messages/:transactionId/dlq', (res, req) => {
708
+ const transactionId = req.getParameter(0);
712
709
 
713
710
  let aborted = false;
714
711
  res.onAborted(() => {
715
712
  aborted = true;
716
- log('Analytics request aborted');
713
+ log('Move to DLQ request aborted');
717
714
  });
718
715
 
719
- analyticsRoutes.getThroughput(pool, { fromDateTime, toDateTime, queue, namespace, task }).then(result => {
716
+ messagesRoutes.moveToDLQ(transactionId).then(result => {
720
717
  if (aborted) return;
721
718
  res.cork(() => {
722
719
  setCorsHeaders(res);
@@ -724,7 +721,7 @@ app.get('/api/v1/analytics/throughput', (res, req) => {
724
721
  });
725
722
  }).catch(error => {
726
723
  if (aborted) return;
727
- log('Analytics error:', error.message || error);
724
+ log('Move to DLQ error:', error);
728
725
  res.cork(() => {
729
726
  setCorsHeaders(res);
730
727
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -732,29 +729,24 @@ app.get('/api/v1/analytics/throughput', (res, req) => {
732
729
  });
733
730
  });
734
731
 
735
- app.get('/api/v1/analytics/queue-lag', (res, req) => {
736
- const query = new URLSearchParams(req.getQuery());
737
- const queue = query.get('queue');
738
- const namespace = query.get('namespace');
739
- const task = query.get('task');
740
- const fromDateTime = query.get('fromDateTime');
741
- const toDateTime = query.get('toDateTime');
732
+ app.get('/api/v1/messages/:transactionId/related', (res, req) => {
733
+ const transactionId = req.getParameter(0);
742
734
 
743
735
  let aborted = false;
744
736
  res.onAborted(() => {
745
737
  aborted = true;
746
- log('Queue lag request aborted');
738
+ log('Get related messages request aborted');
747
739
  });
748
740
 
749
- analyticsRoutes.getQueueLag({ queue, namespace, task, fromDateTime, toDateTime }).then(result => {
741
+ messagesRoutes.getRelatedMessages(transactionId).then(result => {
750
742
  if (aborted) return;
751
743
  res.cork(() => {
752
744
  setCorsHeaders(res);
753
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
745
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ messages: result }));
754
746
  });
755
747
  }).catch(error => {
756
748
  if (aborted) return;
757
- log('Queue lag error:', error);
749
+ log('Get related messages error:', error);
758
750
  res.cork(() => {
759
751
  setCorsHeaders(res);
760
752
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -762,22 +754,18 @@ app.get('/api/v1/analytics/queue-lag', (res, req) => {
762
754
  });
763
755
  });
764
756
 
765
- // Queue stats route
766
- app.get('/api/v1/analytics/queue-stats', (res, req) => {
757
+ app.del('/api/v1/queues/:queue/clear', (res, req) => {
758
+ const queue = req.getParameter(0);
767
759
  const query = new URLSearchParams(req.getQuery());
768
- const queue = query.get('queue');
769
- const namespace = query.get('namespace');
770
- const task = query.get('task');
771
- const fromDateTime = query.get('fromDateTime');
772
- const toDateTime = query.get('toDateTime');
760
+ const partition = query.get('partition');
773
761
 
774
762
  let aborted = false;
775
763
  res.onAborted(() => {
776
764
  aborted = true;
777
- log('Queue stats request aborted');
765
+ log('Clear queue request aborted');
778
766
  });
779
767
 
780
- queueManager.getQueueStats({ queue, namespace, task, fromDateTime, toDateTime }).then(result => {
768
+ messagesRoutes.clearQueue(queue, partition).then(result => {
781
769
  if (aborted) return;
782
770
  res.cork(() => {
783
771
  setCorsHeaders(res);
@@ -785,7 +773,7 @@ app.get('/api/v1/analytics/queue-stats', (res, req) => {
785
773
  });
786
774
  }).catch(error => {
787
775
  if (aborted) return;
788
- log('Queue stats error:', error);
776
+ log('Clear queue error:', error);
789
777
  res.cork(() => {
790
778
  setCorsHeaders(res);
791
779
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -793,34 +781,27 @@ app.get('/api/v1/analytics/queue-stats', (res, req) => {
793
781
  });
794
782
  });
795
783
 
796
- // Messages routes
797
- app.get('/api/v1/messages', (res, req) => {
784
+ // Resources routes
785
+ app.get('/api/v1/resources/queues', (res, req) => {
798
786
  const query = new URLSearchParams(req.getQuery());
787
+ const namespace = query.get('namespace');
788
+ const task = query.get('task');
799
789
 
800
790
  let aborted = false;
801
791
  res.onAborted(() => {
802
792
  aborted = true;
803
- log('Messages list request aborted');
793
+ log('Get queues request aborted');
804
794
  });
805
795
 
806
- const filters = {
807
- ns: query.get('ns'),
808
- task: query.get('task'),
809
- queue: query.get('queue'),
810
- status: query.get('status'),
811
- limit: parseInt(query.get('limit') || config.API.DEFAULT_LIMIT.toString()),
812
- offset: parseInt(query.get('offset') || config.API.DEFAULT_OFFSET.toString())
813
- };
814
-
815
- messagesRoutes.listMessages(filters).then(result => {
796
+ resourcesRoutes.getQueues({ namespace, task }).then(result => {
816
797
  if (aborted) return;
817
798
  res.cork(() => {
818
799
  setCorsHeaders(res);
819
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ messages: result }));
800
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ queues: result }));
820
801
  });
821
802
  }).catch(error => {
822
803
  if (aborted) return;
823
- log('List messages error:', error);
804
+ log('Get queues error:', error);
824
805
  res.cork(() => {
825
806
  setCorsHeaders(res);
826
807
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -828,16 +809,16 @@ app.get('/api/v1/messages', (res, req) => {
828
809
  });
829
810
  });
830
811
 
831
- app.get('/api/v1/messages/:transactionId', (res, req) => {
832
- const transactionId = req.getParameter(0);
812
+ app.get('/api/v1/resources/queues/:queue', (res, req) => {
813
+ const queue = req.getParameter(0);
833
814
 
834
815
  let aborted = false;
835
816
  res.onAborted(() => {
836
817
  aborted = true;
837
- log('Get message request aborted');
818
+ log('Get queue request aborted');
838
819
  });
839
820
 
840
- messagesRoutes.getMessage(transactionId).then(result => {
821
+ resourcesRoutes.getQueue(queue).then(result => {
841
822
  if (aborted) return;
842
823
  res.cork(() => {
843
824
  setCorsHeaders(res);
@@ -845,59 +826,58 @@ app.get('/api/v1/messages/:transactionId', (res, req) => {
845
826
  });
846
827
  }).catch(error => {
847
828
  if (aborted) return;
848
- log('Get message error:', error);
829
+ log('Get queue error:', error);
849
830
  res.cork(() => {
850
831
  setCorsHeaders(res);
851
- res.writeStatus(error.message === 'Message not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
832
+ res.writeStatus(error.message === 'Queue not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
852
833
  .end(JSON.stringify({ error: error.message }));
853
834
  });
854
835
  });
855
836
  });
856
837
 
857
- app.del('/api/v1/messages/:transactionId', (res, req) => {
858
- const transactionId = req.getParameter(0);
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');
859
842
 
860
843
  let aborted = false;
861
844
  res.onAborted(() => {
862
845
  aborted = true;
863
- log('Delete message request aborted');
846
+ log('Get partitions request aborted');
864
847
  });
865
848
 
866
- messagesRoutes.deleteMessage(transactionId).then(result => {
849
+ resourcesRoutes.getPartitions({ queue, minDepth: minDepth ? parseInt(minDepth) : null }).then(result => {
867
850
  if (aborted) return;
868
851
  res.cork(() => {
869
852
  setCorsHeaders(res);
870
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
853
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ partitions: result }));
871
854
  });
872
855
  }).catch(error => {
873
856
  if (aborted) return;
874
- log('Delete message error:', error);
857
+ log('Get partitions error:', error);
875
858
  res.cork(() => {
876
859
  setCorsHeaders(res);
877
- res.writeStatus(error.message === 'Message not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
878
- .end(JSON.stringify({ error: error.message }));
860
+ res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
879
861
  });
880
862
  });
881
863
  });
882
864
 
883
- app.post('/api/v1/messages/:transactionId/retry', (res, req) => {
884
- const transactionId = req.getParameter(0);
885
-
865
+ app.get('/api/v1/resources/namespaces', (res, req) => {
886
866
  let aborted = false;
887
867
  res.onAborted(() => {
888
868
  aborted = true;
889
- log('Retry message request aborted');
869
+ log('Get namespaces request aborted');
890
870
  });
891
871
 
892
- messagesRoutes.retryMessage(transactionId).then(result => {
872
+ resourcesRoutes.getNamespaces().then(result => {
893
873
  if (aborted) return;
894
874
  res.cork(() => {
895
875
  setCorsHeaders(res);
896
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
876
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ namespaces: result }));
897
877
  });
898
878
  }).catch(error => {
899
879
  if (aborted) return;
900
- log('Retry message error:', error);
880
+ log('Get namespaces error:', error);
901
881
  res.cork(() => {
902
882
  setCorsHeaders(res);
903
883
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -905,24 +885,22 @@ app.post('/api/v1/messages/:transactionId/retry', (res, req) => {
905
885
  });
906
886
  });
907
887
 
908
- app.post('/api/v1/messages/:transactionId/dlq', (res, req) => {
909
- const transactionId = req.getParameter(0);
910
-
888
+ app.get('/api/v1/resources/tasks', (res, req) => {
911
889
  let aborted = false;
912
890
  res.onAborted(() => {
913
891
  aborted = true;
914
- log('Move to DLQ request aborted');
892
+ log('Get tasks request aborted');
915
893
  });
916
894
 
917
- messagesRoutes.moveToDLQ(transactionId).then(result => {
895
+ resourcesRoutes.getTasks().then(result => {
918
896
  if (aborted) return;
919
897
  res.cork(() => {
920
898
  setCorsHeaders(res);
921
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
899
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ tasks: result }));
922
900
  });
923
901
  }).catch(error => {
924
902
  if (aborted) return;
925
- log('Move to DLQ error:', error);
903
+ log('Get tasks error:', error);
926
904
  res.cork(() => {
927
905
  setCorsHeaders(res);
928
906
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -930,24 +908,22 @@ app.post('/api/v1/messages/:transactionId/dlq', (res, req) => {
930
908
  });
931
909
  });
932
910
 
933
- app.get('/api/v1/messages/:transactionId/related', (res, req) => {
934
- const transactionId = req.getParameter(0);
935
-
911
+ app.get('/api/v1/resources/overview', (res, req) => {
936
912
  let aborted = false;
937
913
  res.onAborted(() => {
938
914
  aborted = true;
939
- log('Get related messages request aborted');
915
+ log('Get overview request aborted');
940
916
  });
941
917
 
942
- messagesRoutes.getRelatedMessages(transactionId).then(result => {
918
+ resourcesRoutes.getSystemOverview().then(result => {
943
919
  if (aborted) return;
944
920
  res.cork(() => {
945
921
  setCorsHeaders(res);
946
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ messages: result }));
922
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
947
923
  });
948
924
  }).catch(error => {
949
925
  if (aborted) return;
950
- log('Get related messages error:', error);
926
+ log('Get overview error:', error);
951
927
  res.cork(() => {
952
928
  setCorsHeaders(res);
953
929
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -955,18 +931,17 @@ app.get('/api/v1/messages/:transactionId/related', (res, req) => {
955
931
  });
956
932
  });
957
933
 
958
- app.del('/api/v1/queues/:queue/clear', (res, req) => {
934
+ // Delete queue
935
+ app.del('/api/v1/resources/queues/:queue', (res, req) => {
959
936
  const queue = req.getParameter(0);
960
- const query = new URLSearchParams(req.getQuery());
961
- const partition = query.get('partition');
962
937
 
963
938
  let aborted = false;
964
939
  res.onAborted(() => {
965
940
  aborted = true;
966
- log('Clear queue request aborted');
941
+ log('Delete queue request aborted');
967
942
  });
968
943
 
969
- messagesRoutes.clearQueue(queue, partition).then(result => {
944
+ resourcesRoutes.deleteQueue(queue).then(result => {
970
945
  if (aborted) return;
971
946
  res.cork(() => {
972
947
  setCorsHeaders(res);
@@ -974,7 +949,7 @@ app.del('/api/v1/queues/:queue/clear', (res, req) => {
974
949
  });
975
950
  }).catch(error => {
976
951
  if (aborted) return;
977
- log('Clear queue error:', error);
952
+ log('Delete queue error:', error);
978
953
  res.cork(() => {
979
954
  setCorsHeaders(res);
980
955
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -982,27 +957,37 @@ app.del('/api/v1/queues/:queue/clear', (res, req) => {
982
957
  });
983
958
  });
984
959
 
985
- // Resources routes
986
- app.get('/api/v1/resources/queues', (res, req) => {
960
+ // ============================================================================
961
+ // Status/Dashboard Routes (New unified API)
962
+ // ============================================================================
963
+
964
+ // 1. Dashboard Status - GET /api/v1/status
965
+ app.get('/api/v1/status', (res, req) => {
987
966
  const query = new URLSearchParams(req.getQuery());
988
- const namespace = query.get('namespace');
989
- const task = query.get('task');
990
967
 
991
968
  let aborted = false;
992
969
  res.onAborted(() => {
993
970
  aborted = true;
994
- log('Get queues request aborted');
971
+ log('Status request aborted');
995
972
  });
996
973
 
997
- resourcesRoutes.getQueues({ namespace, task }).then(result => {
974
+ const filters = {
975
+ from: query.get('from'),
976
+ to: query.get('to'),
977
+ queue: query.get('queue'),
978
+ namespace: query.get('namespace'),
979
+ task: query.get('task')
980
+ };
981
+
982
+ statusRoutes.getStatus(filters).then(result => {
998
983
  if (aborted) return;
999
984
  res.cork(() => {
1000
985
  setCorsHeaders(res);
1001
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ queues: result }));
986
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
1002
987
  });
1003
988
  }).catch(error => {
1004
989
  if (aborted) return;
1005
- log('Get queues error:', error);
990
+ log('Status error:', error);
1006
991
  res.cork(() => {
1007
992
  setCorsHeaders(res);
1008
993
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -1010,16 +995,26 @@ app.get('/api/v1/resources/queues', (res, req) => {
1010
995
  });
1011
996
  });
1012
997
 
1013
- app.get('/api/v1/resources/queues/:queue', (res, req) => {
1014
- const queue = req.getParameter(0);
998
+ // 2. Queues List - GET /api/v1/status/queues
999
+ app.get('/api/v1/status/queues', (res, req) => {
1000
+ const query = new URLSearchParams(req.getQuery());
1015
1001
 
1016
1002
  let aborted = false;
1017
1003
  res.onAborted(() => {
1018
1004
  aborted = true;
1019
- log('Get queue request aborted');
1005
+ log('Status queues request aborted');
1020
1006
  });
1021
1007
 
1022
- resourcesRoutes.getQueue(queue).then(result => {
1008
+ const filters = {
1009
+ from: query.get('from'),
1010
+ to: query.get('to'),
1011
+ namespace: query.get('namespace'),
1012
+ task: query.get('task'),
1013
+ limit: query.get('limit'),
1014
+ offset: query.get('offset')
1015
+ };
1016
+
1017
+ statusRoutes.getQueues(filters).then(result => {
1023
1018
  if (aborted) return;
1024
1019
  res.cork(() => {
1025
1020
  setCorsHeaders(res);
@@ -1027,81 +1022,78 @@ app.get('/api/v1/resources/queues/:queue', (res, req) => {
1027
1022
  });
1028
1023
  }).catch(error => {
1029
1024
  if (aborted) return;
1030
- log('Get queue error:', error);
1025
+ log('Status queues error:', error);
1031
1026
  res.cork(() => {
1032
1027
  setCorsHeaders(res);
1033
- res.writeStatus(error.message === 'Queue not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
1034
- .end(JSON.stringify({ error: error.message }));
1028
+ res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
1035
1029
  });
1036
1030
  });
1037
1031
  });
1038
1032
 
1039
- app.get('/api/v1/resources/partitions', (res, req) => {
1033
+ // 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);
1040
1036
  const query = new URLSearchParams(req.getQuery());
1041
- const queue = query.get('queue');
1042
- const minDepth = query.get('minDepth');
1043
1037
 
1044
1038
  let aborted = false;
1045
1039
  res.onAborted(() => {
1046
1040
  aborted = true;
1047
- log('Get partitions request aborted');
1041
+ log('Status queue detail request aborted');
1048
1042
  });
1049
1043
 
1050
- resourcesRoutes.getPartitions({ queue, minDepth: minDepth ? parseInt(minDepth) : null }).then(result => {
1051
- if (aborted) return;
1052
- res.cork(() => {
1053
- setCorsHeaders(res);
1054
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ partitions: result }));
1055
- });
1056
- }).catch(error => {
1057
- if (aborted) return;
1058
- log('Get partitions error:', error);
1059
- res.cork(() => {
1060
- setCorsHeaders(res);
1061
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
1062
- });
1063
- });
1064
- });
1065
-
1066
- app.get('/api/v1/resources/namespaces', (res, req) => {
1067
- let aborted = false;
1068
- res.onAborted(() => {
1069
- aborted = true;
1070
- log('Get namespaces request aborted');
1071
- });
1044
+ const filters = {
1045
+ from: query.get('from'),
1046
+ to: query.get('to')
1047
+ };
1072
1048
 
1073
- resourcesRoutes.getNamespaces().then(result => {
1049
+ statusRoutes.getQueueDetail(queueName, filters).then(result => {
1074
1050
  if (aborted) return;
1075
1051
  res.cork(() => {
1076
1052
  setCorsHeaders(res);
1077
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ namespaces: result }));
1053
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
1078
1054
  });
1079
1055
  }).catch(error => {
1080
1056
  if (aborted) return;
1081
- log('Get namespaces error:', error);
1057
+ log('Status queue detail error:', error);
1058
+ const statusCode = error.message === 'Queue not found'
1059
+ ? config.HTTP_STATUS.NOT_FOUND
1060
+ : config.HTTP_STATUS.INTERNAL_SERVER_ERROR;
1082
1061
  res.cork(() => {
1083
1062
  setCorsHeaders(res);
1084
- res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
1063
+ res.writeStatus(statusCode.toString()).end(JSON.stringify({ error: error.message }));
1085
1064
  });
1086
1065
  });
1087
1066
  });
1088
1067
 
1089
- app.get('/api/v1/resources/tasks', (res, req) => {
1068
+ // 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());
1072
+
1090
1073
  let aborted = false;
1091
1074
  res.onAborted(() => {
1092
1075
  aborted = true;
1093
- log('Get tasks request aborted');
1076
+ log('Status queue messages request aborted');
1094
1077
  });
1095
1078
 
1096
- resourcesRoutes.getTasks().then(result => {
1079
+ const filters = {
1080
+ status: query.get('status'),
1081
+ partition: query.get('partition'),
1082
+ from: query.get('from'),
1083
+ to: query.get('to'),
1084
+ limit: query.get('limit'),
1085
+ offset: query.get('offset')
1086
+ };
1087
+
1088
+ statusRoutes.getQueueMessages(queueName, filters).then(result => {
1097
1089
  if (aborted) return;
1098
1090
  res.cork(() => {
1099
1091
  setCorsHeaders(res);
1100
- res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ tasks: result }));
1092
+ res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
1101
1093
  });
1102
1094
  }).catch(error => {
1103
1095
  if (aborted) return;
1104
- log('Get tasks error:', error);
1096
+ log('Status queue messages error:', error);
1105
1097
  res.cork(() => {
1106
1098
  setCorsHeaders(res);
1107
1099
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -1109,14 +1101,26 @@ app.get('/api/v1/resources/tasks', (res, req) => {
1109
1101
  });
1110
1102
  });
1111
1103
 
1112
- app.get('/api/v1/resources/overview', (res, req) => {
1104
+ // 5. Analytics - GET /api/v1/status/analytics
1105
+ app.get('/api/v1/status/analytics', (res, req) => {
1106
+ const query = new URLSearchParams(req.getQuery());
1107
+
1113
1108
  let aborted = false;
1114
1109
  res.onAborted(() => {
1115
1110
  aborted = true;
1116
- log('Get overview request aborted');
1111
+ log('Status analytics request aborted');
1117
1112
  });
1118
1113
 
1119
- resourcesRoutes.getSystemOverview().then(result => {
1114
+ const filters = {
1115
+ from: query.get('from'),
1116
+ to: query.get('to'),
1117
+ queue: query.get('queue'),
1118
+ namespace: query.get('namespace'),
1119
+ task: query.get('task'),
1120
+ interval: query.get('interval') || 'hour'
1121
+ };
1122
+
1123
+ statusRoutes.getAnalytics(filters).then(result => {
1120
1124
  if (aborted) return;
1121
1125
  res.cork(() => {
1122
1126
  setCorsHeaders(res);
@@ -1124,7 +1128,7 @@ app.get('/api/v1/resources/overview', (res, req) => {
1124
1128
  });
1125
1129
  }).catch(error => {
1126
1130
  if (aborted) return;
1127
- log('Get overview error:', error);
1131
+ log('Status analytics error:', error);
1128
1132
  res.cork(() => {
1129
1133
  setCorsHeaders(res);
1130
1134
  res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
@@ -1217,38 +1221,41 @@ app.listen(HOST, PORT, async (token) => {
1217
1221
  log(`āœ… Queen Server running at http://${HOST}:${PORT}`);
1218
1222
  log(`šŸ“Š Features enabled:`);
1219
1223
  log(` - Encryption: ${encryptionEnabled ? 'āœ…' : 'āŒ (set QUEEN_ENCRYPTION_KEY)'}`);
1224
+ log(` - System Events: ${config.SYSTEM_EVENTS.ENABLED ? 'āœ…' : 'āŒ (disabled via config)'}`);
1220
1225
  log(` - Retention: āœ…`);
1221
1226
  log(` - Eviction: āœ…`);
1222
1227
  log(` - WebSocket Dashboard: ws://${HOST}:${PORT}/ws/dashboard`);
1223
1228
  log(` - Server Instance ID: ${SERVER_INSTANCE_ID}`);
1224
1229
 
1225
- // Start system event synchronization and consumption
1226
- try {
1227
- // Create temporary client for synchronization
1228
- const syncClient = createQueenClient({
1229
- baseUrl: `http://localhost:${PORT}`
1230
- });
1231
-
1232
- // Synchronize system events (catch up on missed events)
1233
- await syncSystemEvents(syncClient, systemEventManager, SERVER_INSTANCE_ID);
1234
-
1235
- // Start consuming system events
1236
- // Note: consume() returns the stop function directly
1237
- stopSystemEventConsumer = syncClient.consume({
1238
- queue: SYSTEM_QUEUE,
1239
- consumerGroup: SERVER_INSTANCE_ID,
1240
- handler: async (message) => {
1241
- await systemEventManager.processSystemEvent(message.payload);
1242
- },
1243
- options: {
1244
- batch: 10,
1245
- wait: true
1246
- }
1247
- });
1248
-
1249
- log(`āœ… System event consumer started`);
1250
- } catch (error) {
1251
- log(`āš ļø Failed to start system event consumer: ${error.message}`);
1230
+ // Start system event synchronization and consumption (only if enabled)
1231
+ if (config.SYSTEM_EVENTS.ENABLED) {
1232
+ try {
1233
+ // Create temporary client for synchronization
1234
+ const syncClient = createQueenClient({
1235
+ baseUrl: `http://localhost:${PORT}`
1236
+ });
1237
+
1238
+ // Synchronize system events (catch up on missed events)
1239
+ await syncSystemEvents(syncClient, systemEventManager, SERVER_INSTANCE_ID);
1240
+
1241
+ // Start consuming system events
1242
+ // Note: consume() returns the stop function directly
1243
+ stopSystemEventConsumer = syncClient.consume({
1244
+ queue: SYSTEM_QUEUE,
1245
+ consumerGroup: SERVER_INSTANCE_ID,
1246
+ handler: async (message) => {
1247
+ await systemEventManager.processSystemEvent(message.payload);
1248
+ },
1249
+ options: {
1250
+ batch: 10,
1251
+ wait: true
1252
+ }
1253
+ });
1254
+
1255
+ log(`āœ… System event consumer started`);
1256
+ } catch (error) {
1257
+ log(`āš ļø Failed to start system event consumer: ${error.message}`);
1258
+ }
1252
1259
  }
1253
1260
 
1254
1261
  log(`šŸŽÆ Ready to process messages with high performance!`);
@@ -1262,6 +1269,9 @@ app.listen(HOST, PORT, async (token) => {
1262
1269
  const shutdown = async (signal) => {
1263
1270
  log(`\nšŸ›‘ Received ${signal}, shutting down gracefully...`);
1264
1271
 
1272
+ // Set shutdown flag immediately to prevent new operations
1273
+ isShuttingDown = true;
1274
+
1265
1275
  // Stop system event consumer
1266
1276
  if (stopSystemEventConsumer) {
1267
1277
  stopSystemEventConsumer();
@@ -1269,8 +1279,31 @@ const shutdown = async (signal) => {
1269
1279
  }
1270
1280
 
1271
1281
  // Stop background jobs
1272
- if (stopRetention) stopRetention();
1273
- if (stopEviction) stopEviction();
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));
1274
1307
 
1275
1308
  const uptime = (Date.now() - startTime) / 1000;
1276
1309
  if (messageCount > 0) {
@@ -1278,7 +1311,10 @@ const shutdown = async (signal) => {
1278
1311
  log(` Average: ${(messageCount / uptime).toFixed(2)} messages/second`);
1279
1312
  }
1280
1313
 
1314
+ // Close database pool last
1281
1315
  await pool.end();
1316
+ log('āœ… Database pool closed');
1317
+
1282
1318
  process.exit(0);
1283
1319
  };
1284
1320