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,296 @@
1
+ export const createResourcesRoutes = (pool) => {
2
+
3
+ // Get all queues with their partitions
4
+ const getQueues = async (filters = {}) => {
5
+ const { namespace, task } = filters;
6
+
7
+ let query = `
8
+ SELECT DISTINCT
9
+ q.id as queue_id,
10
+ q.name as queue_name,
11
+ q.namespace,
12
+ q.task,
13
+ q.created_at as queue_created_at,
14
+ COUNT(DISTINCT p.id) as partition_count,
15
+ COUNT(DISTINCT m.id) as message_count,
16
+ COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending_count,
17
+ COUNT(DISTINCT CASE WHEN ms.status = 'processing' THEN m.id END) as processing_count
18
+ FROM queen.queues q
19
+ LEFT JOIN queen.partitions p ON p.queue_id = q.id
20
+ LEFT JOIN queen.messages m ON m.partition_id = p.id
21
+ LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
22
+ WHERE 1=1
23
+ `;
24
+
25
+ const params = [];
26
+ if (namespace) {
27
+ params.push(namespace);
28
+ query += ` AND q.namespace = $${params.length}`;
29
+ }
30
+ if (task) {
31
+ params.push(task);
32
+ query += ` AND q.task = $${params.length}`;
33
+ }
34
+
35
+ query += ` GROUP BY q.id, q.name, q.namespace, q.task, q.created_at
36
+ ORDER BY q.created_at DESC`;
37
+
38
+ const result = await pool.query(query, params);
39
+
40
+ return result.rows.map(row => ({
41
+ id: row.queue_id,
42
+ name: row.queue_name,
43
+ namespace: row.namespace,
44
+ task: row.task,
45
+ createdAt: row.queue_created_at,
46
+ partitions: parseInt(row.partition_count),
47
+ messages: {
48
+ total: parseInt(row.message_count),
49
+ pending: parseInt(row.pending_count),
50
+ processing: parseInt(row.processing_count)
51
+ }
52
+ }));
53
+ };
54
+
55
+ // Get single queue with detailed partition information
56
+ const getQueue = async (queueName) => {
57
+ // Get queue info
58
+ const queueResult = await pool.query(`
59
+ SELECT
60
+ q.id,
61
+ q.name,
62
+ q.namespace,
63
+ q.task,
64
+ q.created_at
65
+ FROM queen.queues q
66
+ WHERE q.name = $1
67
+ `, [queueName]);
68
+
69
+ if (queueResult.rows.length === 0) {
70
+ throw new Error('Queue not found');
71
+ }
72
+
73
+ const queue = queueResult.rows[0];
74
+
75
+ // Get partitions with stats (partitions no longer have options or priority)
76
+ const partitionsResult = await pool.query(`
77
+ SELECT
78
+ p.id,
79
+ p.name,
80
+ p.created_at,
81
+ COUNT(DISTINCT m.id) as message_count,
82
+ COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending,
83
+ COUNT(DISTINCT CASE WHEN ms.status = 'processing' THEN m.id END) as processing,
84
+ COUNT(DISTINCT CASE WHEN ms.status = 'completed' THEN m.id END) as completed,
85
+ COUNT(DISTINCT CASE WHEN ms.status = 'failed' THEN m.id END) as failed,
86
+ COUNT(DISTINCT CASE WHEN ms.status = 'dead_letter' THEN m.id END) as dead_letter,
87
+ MIN(m.created_at) as oldest_message,
88
+ MAX(m.created_at) as newest_message
89
+ FROM queen.partitions p
90
+ LEFT JOIN queen.messages m ON m.partition_id = p.id
91
+ LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
92
+ WHERE p.queue_id = $1
93
+ GROUP BY p.id, p.name, p.created_at
94
+ ORDER BY p.name
95
+ `, [queue.id]);
96
+
97
+ const partitions = partitionsResult.rows.map(row => ({
98
+ id: row.id,
99
+ name: row.name,
100
+ createdAt: row.created_at,
101
+ stats: {
102
+ total: parseInt(row.message_count),
103
+ pending: parseInt(row.pending),
104
+ processing: parseInt(row.processing),
105
+ completed: parseInt(row.completed),
106
+ failed: parseInt(row.failed),
107
+ deadLetter: parseInt(row.dead_letter)
108
+ },
109
+ oldestMessage: row.oldest_message,
110
+ newestMessage: row.newest_message
111
+ }));
112
+
113
+ // Calculate totals
114
+ const totals = partitions.reduce((acc, p) => ({
115
+ total: acc.total + p.stats.total,
116
+ pending: acc.pending + p.stats.pending,
117
+ processing: acc.processing + p.stats.processing,
118
+ completed: acc.completed + p.stats.completed,
119
+ failed: acc.failed + p.stats.failed,
120
+ deadLetter: acc.deadLetter + p.stats.deadLetter
121
+ }), {
122
+ total: 0,
123
+ pending: 0,
124
+ processing: 0,
125
+ completed: 0,
126
+ failed: 0,
127
+ deadLetter: 0
128
+ });
129
+
130
+ return {
131
+ id: queue.id,
132
+ name: queue.name,
133
+ namespace: queue.namespace,
134
+ task: queue.task,
135
+ createdAt: queue.created_at,
136
+ partitions,
137
+ totals
138
+ };
139
+ };
140
+
141
+ // Get all partitions across all queues
142
+ const getPartitions = async (filters = {}) => {
143
+ const { queue, minDepth } = filters;
144
+
145
+ let query = `
146
+ SELECT
147
+ p.id,
148
+ p.name as partition_name,
149
+ p.created_at,
150
+ q.name as queue_name,
151
+ q.namespace,
152
+ q.task,
153
+ q.priority,
154
+ COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending,
155
+ COUNT(DISTINCT CASE WHEN ms.status = 'processing' THEN m.id END) as processing,
156
+ COUNT(DISTINCT m.id) as total
157
+ FROM queen.partitions p
158
+ JOIN queen.queues q ON q.id = p.queue_id
159
+ LEFT JOIN queen.messages m ON m.partition_id = p.id
160
+ LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
161
+ WHERE 1=1
162
+ `;
163
+
164
+ const params = [];
165
+ if (queue) {
166
+ params.push(queue);
167
+ query += ` AND q.name = $${params.length}`;
168
+ }
169
+
170
+ query += ` GROUP BY p.id, p.name, p.created_at,
171
+ q.name, q.namespace, q.task, q.priority`;
172
+
173
+ if (minDepth) {
174
+ params.push(minDepth);
175
+ query += ` HAVING COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) >= $${params.length}`;
176
+ }
177
+
178
+ query += ` ORDER BY q.name, q.priority DESC, p.name`;
179
+
180
+ const result = await pool.query(query, params);
181
+
182
+ return result.rows.map(row => ({
183
+ id: row.id,
184
+ name: row.partition_name,
185
+ queue: row.queue_name,
186
+ namespace: row.namespace,
187
+ task: row.task,
188
+ queuePriority: row.priority, // Priority is now at queue level
189
+ createdAt: row.created_at,
190
+ depth: parseInt(row.pending),
191
+ processing: parseInt(row.processing),
192
+ total: parseInt(row.total)
193
+ }));
194
+ };
195
+
196
+ // Get namespace/task groupings
197
+ const getNamespaces = async () => {
198
+ const result = await pool.query(`
199
+ SELECT DISTINCT
200
+ q.namespace,
201
+ COUNT(DISTINCT q.id) as queue_count,
202
+ COUNT(DISTINCT p.id) as partition_count,
203
+ COUNT(DISTINCT m.id) as message_count,
204
+ COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending_count
205
+ FROM queen.queues q
206
+ LEFT JOIN queen.partitions p ON p.queue_id = q.id
207
+ LEFT JOIN queen.messages m ON m.partition_id = p.id
208
+ LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
209
+ WHERE q.namespace IS NOT NULL
210
+ GROUP BY q.namespace
211
+ ORDER BY q.namespace
212
+ `);
213
+
214
+ return result.rows.map(row => ({
215
+ namespace: row.namespace,
216
+ queues: parseInt(row.queue_count),
217
+ partitions: parseInt(row.partition_count),
218
+ messages: {
219
+ total: parseInt(row.message_count),
220
+ pending: parseInt(row.pending_count)
221
+ }
222
+ }));
223
+ };
224
+
225
+ const getTasks = async () => {
226
+ const result = await pool.query(`
227
+ SELECT DISTINCT
228
+ q.task,
229
+ COUNT(DISTINCT q.id) as queue_count,
230
+ COUNT(DISTINCT p.id) as partition_count,
231
+ COUNT(DISTINCT m.id) as message_count,
232
+ COUNT(DISTINCT CASE WHEN ms.status = 'pending' THEN m.id END) as pending_count
233
+ FROM queen.queues q
234
+ LEFT JOIN queen.partitions p ON p.queue_id = q.id
235
+ LEFT JOIN queen.messages m ON m.partition_id = p.id
236
+ LEFT JOIN queen.messages_status ms ON ms.message_id = m.id AND ms.consumer_group IS NULL
237
+ WHERE q.task IS NOT NULL
238
+ GROUP BY q.task
239
+ ORDER BY q.task
240
+ `);
241
+
242
+ return result.rows.map(row => ({
243
+ task: row.task,
244
+ queues: parseInt(row.queue_count),
245
+ partitions: parseInt(row.partition_count),
246
+ messages: {
247
+ total: parseInt(row.message_count),
248
+ pending: parseInt(row.pending_count)
249
+ }
250
+ }));
251
+ };
252
+
253
+ // Get system overview
254
+ const getSystemOverview = async () => {
255
+ const overview = await pool.query(`
256
+ SELECT
257
+ (SELECT COUNT(*) FROM queen.queues) as total_queues,
258
+ (SELECT COUNT(*) FROM queen.partitions) as total_partitions,
259
+ (SELECT COUNT(*) FROM queen.messages) as total_messages,
260
+ (SELECT COUNT(*) FROM queen.messages_status WHERE status = 'pending' AND consumer_group IS NULL) as pending_messages,
261
+ (SELECT COUNT(*) FROM queen.messages_status WHERE status = 'processing' AND consumer_group IS NULL) as processing_messages,
262
+ (SELECT COUNT(*) FROM queen.messages_status WHERE status = 'completed' AND consumer_group IS NULL) as completed_messages,
263
+ (SELECT COUNT(*) FROM queen.messages_status WHERE status = 'failed' AND consumer_group IS NULL) as failed_messages,
264
+ (SELECT COUNT(*) FROM queen.messages_status WHERE status = 'dead_letter' AND consumer_group IS NULL) as dead_letter_messages,
265
+ (SELECT COUNT(DISTINCT namespace) FROM queen.queues WHERE namespace IS NOT NULL) as namespaces,
266
+ (SELECT COUNT(DISTINCT task) FROM queen.queues WHERE task IS NOT NULL) as tasks
267
+ `);
268
+
269
+ const row = overview.rows[0];
270
+
271
+ return {
272
+ queues: parseInt(row.total_queues),
273
+ partitions: parseInt(row.total_partitions),
274
+ namespaces: parseInt(row.namespaces),
275
+ tasks: parseInt(row.tasks),
276
+ messages: {
277
+ total: parseInt(row.total_messages),
278
+ pending: parseInt(row.pending_messages),
279
+ processing: parseInt(row.processing_messages),
280
+ completed: parseInt(row.completed_messages),
281
+ failed: parseInt(row.failed_messages),
282
+ deadLetter: parseInt(row.dead_letter_messages)
283
+ },
284
+ timestamp: new Date().toISOString()
285
+ };
286
+ };
287
+
288
+ return {
289
+ getQueues,
290
+ getQueue,
291
+ getPartitions,
292
+ getNamespaces,
293
+ getTasks,
294
+ getSystemOverview
295
+ };
296
+ };