queen-mq 0.1.0

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 (90) hide show
  1. package/API.md +1116 -0
  2. package/CACHE.md +519 -0
  3. package/DASHBOARD-V3.md +478 -0
  4. package/DASHBOARD.md +382 -0
  5. package/MOD_QUEUE.md +453 -0
  6. package/PARTITION_LOCKING_DESIGN.md +989 -0
  7. package/PLAN.md +707 -0
  8. package/QUERY_ANALSYS.md +72 -0
  9. package/QUEUE_BUS.md +334 -0
  10. package/README.md +1495 -0
  11. package/V2-PLAN.md +236 -0
  12. package/assets/dashboard.png +0 -0
  13. package/dashboard/.vscode/extensions.json +3 -0
  14. package/dashboard/README.md +5 -0
  15. package/dashboard/index.html +14 -0
  16. package/dashboard/package-lock.json +1458 -0
  17. package/dashboard/package.json +25 -0
  18. package/dashboard/public/vite.svg +1 -0
  19. package/dashboard/src/App.vue +29 -0
  20. package/dashboard/src/assets/styles/main.css +908 -0
  21. package/dashboard/src/assets/vue.svg +1 -0
  22. package/dashboard/src/components/cards/MetricCard.vue +298 -0
  23. package/dashboard/src/components/charts/QueueDepthChart.vue +276 -0
  24. package/dashboard/src/components/charts/QueueLagChart.vue +436 -0
  25. package/dashboard/src/components/charts/ThroughputChart.vue +302 -0
  26. package/dashboard/src/components/common/ActivityFeed.vue +251 -0
  27. package/dashboard/src/components/layout/AppHeader.vue +208 -0
  28. package/dashboard/src/components/layout/AppLayout.vue +88 -0
  29. package/dashboard/src/components/layout/AppSidebar.vue +261 -0
  30. package/dashboard/src/main.js +44 -0
  31. package/dashboard/src/router.js +54 -0
  32. package/dashboard/src/services/api.js +187 -0
  33. package/dashboard/src/services/websocket.js +167 -0
  34. package/dashboard/src/utils/constants.js +56 -0
  35. package/dashboard/src/utils/helpers.js +118 -0
  36. package/dashboard/src/views/Analytics.vue +912 -0
  37. package/dashboard/src/views/Dashboard.vue +906 -0
  38. package/dashboard/src/views/Messages.vue +437 -0
  39. package/dashboard/src/views/QueueDetail.vue +501 -0
  40. package/dashboard/src/views/Queues.vue +333 -0
  41. package/dashboard/vite.config.js +30 -0
  42. package/debug-namespace.js +110 -0
  43. package/docs/long-polling.md +159 -0
  44. package/docs/multi-server-cache-solutions.md +185 -0
  45. package/docs/performance-tuning.md +222 -0
  46. package/examples/bus-mode.js +239 -0
  47. package/examples/continuous-consumer-optimized.js +215 -0
  48. package/examples/continuous-consumer.js +159 -0
  49. package/examples/continuous-producer.js +343 -0
  50. package/examples/mixed-mode.js +277 -0
  51. package/examples/multi-server-test.js +305 -0
  52. package/examples/single.js +64 -0
  53. package/examples/smartchat-dealyed.js +42 -0
  54. package/examples/smartchat.js +52 -0
  55. package/examples/test-cache-invalidation.js +119 -0
  56. package/examples/test-cache-multi-server.js +245 -0
  57. package/examples/test-minimal-client.js +112 -0
  58. package/examples/test-queue-creation-policy.js +137 -0
  59. package/init-db.js +20 -0
  60. package/package.json +36 -0
  61. package/src/client/client.js +291 -0
  62. package/src/client/index.js +6 -0
  63. package/src/client/queenClient.js +513 -0
  64. package/src/client/utils/http.js +172 -0
  65. package/src/client/utils/loadBalancer.js +152 -0
  66. package/src/client/utils/retry.js +35 -0
  67. package/src/config.js +215 -0
  68. package/src/database/connection.js +103 -0
  69. package/src/database/poolManager.js +192 -0
  70. package/src/database/schema-v2.sql +214 -0
  71. package/src/managers/eventManager.js +59 -0
  72. package/src/managers/queueManagerOptimized.js +1512 -0
  73. package/src/managers/resourceCache.js +96 -0
  74. package/src/managers/systemEventManager.js +127 -0
  75. package/src/routes/ack.js +26 -0
  76. package/src/routes/analytics.js +812 -0
  77. package/src/routes/configure.js +46 -0
  78. package/src/routes/messages.js +298 -0
  79. package/src/routes/pop.js +85 -0
  80. package/src/routes/push.js +28 -0
  81. package/src/routes/resources.js +296 -0
  82. package/src/server.js +1286 -0
  83. package/src/services/encryptionService.js +82 -0
  84. package/src/services/evictionService.js +131 -0
  85. package/src/services/retentionService.js +129 -0
  86. package/src/services/startupSync.js +35 -0
  87. package/src/test/test.js +4521 -0
  88. package/src/utils/logger.js +44 -0
  89. package/src/utils/uuid.js +5 -0
  90. package/src/websocket/wsServer.js +221 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Centralized logging utility for Queen message queue
3
+ * Logs all state-changing operations with consistent formatting
4
+ */
5
+
6
+ /**
7
+ * Log a message with timestamp and process ID
8
+ * @param {...any} args - Arguments to log (objects will be JSON stringified)
9
+ */
10
+ export function log(...args) {
11
+ args = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg);
12
+ const msg = args.join(' ');
13
+ console.log(new Date(), `| ${process.pid} | #> ${msg}`);
14
+ }
15
+
16
+ /**
17
+ * Log operation types for consistency
18
+ */
19
+ export const LogTypes = {
20
+ // Message operations
21
+ PUSH: 'PUSH',
22
+ POP: 'POP',
23
+ POP_FILTERED: 'POP_FILTERED',
24
+ ACK: 'ACK',
25
+ ACK_BATCH: 'ACK_BATCH',
26
+
27
+ // Message state changes
28
+ DELETE: 'DELETE',
29
+ RETRY: 'RETRY',
30
+ RETRY_SCHEDULED: 'RETRY_SCHEDULED',
31
+ MOVED_TO_DLQ: 'MOVED_TO_DLQ',
32
+ MANUAL_DLQ: 'MANUAL_DLQ',
33
+
34
+ // Queue operations
35
+ CLEAR_QUEUE: 'CLEAR_QUEUE',
36
+ CONFIGURE_QUEUE: 'CONFIGURE_QUEUE',
37
+
38
+ // System operations
39
+ EVICTION: 'EVICTION',
40
+ RETENTION: 'RETENTION',
41
+ LEASE_EXPIRED: 'LEASE_EXPIRED'
42
+ };
43
+
44
+ export default log;
@@ -0,0 +1,5 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ export const generateUUID = () => {
4
+ return uuidv4();
5
+ };
@@ -0,0 +1,221 @@
1
+ import { generateUUID } from '../utils/uuid.js';
2
+ import config from '../config.js';
3
+
4
+ export const createWebSocketServer = (app, eventManager) => {
5
+ const connections = new Map();
6
+
7
+ // Broadcast event to all connected clients
8
+ const broadcast = (event, data) => {
9
+ const message = JSON.stringify({ event, data, timestamp: new Date().toISOString() });
10
+
11
+ for (const [id, ws] of connections) {
12
+ try {
13
+ ws.send(message);
14
+ } catch (error) {
15
+ console.error(`Failed to send to client ${id}:`, error);
16
+ connections.delete(id);
17
+ }
18
+ }
19
+ };
20
+
21
+ // Setup WebSocket route
22
+ app.ws('/ws/dashboard', {
23
+ compression: config.WEBSOCKET.COMPRESSION,
24
+ maxPayloadLength: config.WEBSOCKET.MAX_PAYLOAD_LENGTH,
25
+ idleTimeout: config.WEBSOCKET.IDLE_TIMEOUT,
26
+
27
+ open: (ws) => {
28
+ const connectionId = generateUUID();
29
+ connections.set(connectionId, ws);
30
+ ws.connectionId = connectionId;
31
+
32
+ console.log(`WebSocket client connected: ${connectionId}`);
33
+
34
+ // Send welcome message
35
+ ws.send(JSON.stringify({
36
+ event: 'connected',
37
+ data: {
38
+ connectionId,
39
+ version: 'v2'
40
+ },
41
+ timestamp: new Date().toISOString()
42
+ }));
43
+
44
+ broadcast('client.connected', { clientId: connectionId });
45
+ },
46
+
47
+ message: (ws, message, isBinary) => {
48
+ // Handle ping/pong
49
+ const msg = Buffer.from(message).toString();
50
+ if (msg === 'ping') {
51
+ ws.send('pong');
52
+ } else {
53
+ // Handle subscription requests
54
+ try {
55
+ const data = JSON.parse(msg);
56
+ if (data.type === 'subscribe') {
57
+ // Store subscription preferences (could be used for filtering)
58
+ ws.subscriptions = data.queues || [];
59
+ ws.send(JSON.stringify({
60
+ event: 'subscribed',
61
+ data: { queues: ws.subscriptions },
62
+ timestamp: new Date().toISOString()
63
+ }));
64
+ }
65
+ } catch (error) {
66
+ // Invalid JSON, ignore
67
+ }
68
+ }
69
+ },
70
+
71
+ close: (ws, code, message) => {
72
+ const connectionId = ws.connectionId;
73
+ connections.delete(connectionId);
74
+ console.log(`WebSocket client disconnected: ${connectionId}`);
75
+ broadcast('client.disconnected', { clientId: connectionId });
76
+ }
77
+ });
78
+
79
+ // Subscribe to queue events - Updated for V2 structure
80
+ eventManager.on('message.pushed', (data) => {
81
+ broadcast('message.pushed', {
82
+ queue: data.queue,
83
+ partition: data.partition,
84
+ transactionId: data.transactionId
85
+ });
86
+ });
87
+
88
+ eventManager.on('message.processing', (data) => {
89
+ broadcast('message.processing', {
90
+ queue: data.queue,
91
+ partition: data.partition,
92
+ transactionId: data.transactionId,
93
+ workerId: data.workerId
94
+ });
95
+ });
96
+
97
+ eventManager.on('message.completed', (data) => {
98
+ broadcast('message.completed', {
99
+ transactionId: data.transactionId
100
+ });
101
+ });
102
+
103
+ eventManager.on('message.failed', (data) => {
104
+ broadcast('message.failed', {
105
+ transactionId: data.transactionId,
106
+ error: data.error
107
+ });
108
+ });
109
+
110
+ eventManager.on('queue.created', (data) => {
111
+ broadcast('queue.created', {
112
+ queue: data.queue,
113
+ partition: data.partition ?? 'Default'
114
+ });
115
+ });
116
+
117
+ // Periodic queue depth updates - Updated for V2
118
+ const updateQueueDepths = async (queueManager) => {
119
+ try {
120
+ const stats = await queueManager.getQueueStats();
121
+
122
+ // Group by queue for aggregated depths
123
+ const queueDepths = {};
124
+
125
+ for (const stat of stats) {
126
+ if (!queueDepths[stat.queue]) {
127
+ queueDepths[stat.queue] = {
128
+ queue: stat.queue,
129
+ namespace: stat.namespace,
130
+ task: stat.task,
131
+ totalDepth: 0,
132
+ totalProcessing: 0,
133
+ partitions: {}
134
+ };
135
+ }
136
+
137
+ // Skip consumer group specific stats for queue depth calculation
138
+ // Only count NULL consumer_group (queue mode) stats
139
+ if (stat.consumer_group) continue;
140
+
141
+ // Initialize partition if not exists
142
+ if (!queueDepths[stat.queue].partitions[stat.partition]) {
143
+ queueDepths[stat.queue].partitions[stat.partition] = {
144
+ depth: 0,
145
+ processing: 0,
146
+ completed: 0,
147
+ failed: 0
148
+ };
149
+ }
150
+
151
+ // Add partition stats (using direct field names from new schema)
152
+ const partitionStats = queueDepths[stat.queue].partitions[stat.partition];
153
+ partitionStats.depth += parseInt(stat.pending || 0);
154
+ partitionStats.processing += parseInt(stat.processing || 0);
155
+ partitionStats.completed += parseInt(stat.completed || 0);
156
+ partitionStats.failed += parseInt(stat.failed || 0);
157
+
158
+ // Update totals
159
+ queueDepths[stat.queue].totalDepth += parseInt(stat.pending || 0);
160
+ queueDepths[stat.queue].totalProcessing += parseInt(stat.processing || 0);
161
+ }
162
+
163
+ // Broadcast aggregated queue depths
164
+ for (const queueData of Object.values(queueDepths)) {
165
+ broadcast('queue.depth', queueData);
166
+ }
167
+
168
+ // Also broadcast individual partition depths for detailed monitoring
169
+ for (const stat of stats) {
170
+ // Skip consumer group specific stats
171
+ if (stat.consumer_group) continue;
172
+
173
+ broadcast('partition.depth', {
174
+ queue: stat.queue,
175
+ partition: stat.partition,
176
+ depth: parseInt(stat.pending || 0),
177
+ processing: parseInt(stat.processing || 0),
178
+ completed: parseInt(stat.completed || 0),
179
+ failed: parseInt(stat.failed || 0),
180
+ total: parseInt(stat.total_messages || 0)
181
+ });
182
+ }
183
+
184
+ } catch (error) {
185
+ console.error('Error updating queue depths:', error);
186
+ }
187
+ };
188
+
189
+ // Send system stats periodically
190
+ const sendSystemStats = async (pool) => {
191
+ try {
192
+ const result = await pool.query(`
193
+ SELECT
194
+ (SELECT COUNT(*) FROM queen.messages_status WHERE status = 'pending' AND consumer_group IS NULL) as pending,
195
+ (SELECT COUNT(*) FROM queen.messages_status WHERE status = 'processing' AND consumer_group IS NULL) as processing,
196
+ (SELECT COUNT(*) FROM queen.messages WHERE created_at > NOW() - INTERVAL '1 minute') as recent_created,
197
+ (SELECT COUNT(*) FROM queen.messages_status WHERE completed_at > NOW() - INTERVAL '1 minute' AND consumer_group IS NULL) as recent_completed
198
+ `);
199
+
200
+ const stats = result.rows[0];
201
+
202
+ broadcast('system.stats', {
203
+ pending: parseInt(stats.pending || 0),
204
+ processing: parseInt(stats.processing || 0),
205
+ recentCreated: parseInt(stats.recent_created || 0),
206
+ recentCompleted: parseInt(stats.recent_completed || 0),
207
+ connections: connections.size
208
+ });
209
+ } catch (error) {
210
+ console.error('Error sending system stats:', error);
211
+ }
212
+ };
213
+
214
+ return {
215
+ broadcast,
216
+ updateQueueDepths,
217
+ sendSystemStats,
218
+ getConnectionCount: () => connections.size,
219
+ getConnections: () => Array.from(connections.keys())
220
+ };
221
+ };