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.
- package/API.md +862 -752
- package/AUTH.md +2044 -0
- package/LICENSE.md +202 -0
- package/README.md +1705 -1051
- package/WEBAPP.md +1889 -0
- package/assets/dashboard-01.png +0 -0
- package/assets/queen-logo-blue.svg +210 -0
- package/assets/queen-logo-cyan.svg +210 -0
- package/assets/queen-logo-indigo.svg +210 -0
- package/assets/queen-logo-orange.svg +210 -0
- package/assets/queen-logo-pink.svg +210 -0
- package/assets/queen-logo-purple.svg +210 -0
- package/assets/queen-logo-rose.svg +239 -0
- package/assets/queen-logo.svg +263 -0
- package/examples/batch-processing.js +58 -0
- package/examples/test-complete-client.js +260 -0
- package/examples/test-dashboard-api.js +200 -0
- package/examples/test-traceid.js +147 -0
- package/package.json +17 -4
- package/server.log +1 -0
- package/src/benchmark/consumer.js +207 -0
- package/src/benchmark/consumer_multi.js +216 -0
- package/src/benchmark/producer.js +75 -0
- package/src/benchmark/producer_multi.js +115 -0
- package/src/client/client.js +300 -31
- package/src/client/queenClient.js +5 -0
- package/src/cluster-server.js +242 -0
- package/src/config.js +19 -5
- package/src/database/connection.js +42 -16
- package/src/database/poolManager.js +7 -0
- package/src/database/schema-v2.sql +194 -130
- package/src/managers/queueManagerOptimized.js +823 -933
- package/src/managers/systemEventManager.js +8 -3
- package/src/routes/messages.js +127 -57
- package/src/routes/pop.js +27 -43
- package/src/routes/resources.js +61 -27
- package/src/routes/status.js +1037 -0
- package/src/server.js +308 -272
- package/src/services/evictionService.js +57 -28
- package/src/services/retentionService.js +44 -11
- package/src/test/MIGRATION_ISSUES.md +174 -0
- package/src/test/README.md +203 -0
- package/src/test/advanced-pattern-tests.js +1137 -0
- package/src/test/bus-mode-tests.js +361 -0
- package/src/test/core-tests.js +342 -0
- package/src/test/edge-case-tests.js +561 -0
- package/src/test/enterprise-tests.js +637 -0
- package/src/test/partition-locking-tests.js +545 -0
- package/src/test/test-new.js +278 -0
- package/src/test/test.js +6 -3
- package/src/test/utils.js +169 -0
- package/src/utils/streaming.js +231 -0
- package/src/utils/uuid.js +2 -2
- package/src/websocket/wsServer.js +10 -3
- package/test-keepalive-v2.sh +22 -0
- package/webapp/COLOR_GUIDE.md +118 -0
- package/webapp/README.md +143 -0
- package/webapp/index.html +14 -0
- package/webapp/package-lock.json +3184 -0
- package/webapp/package.json +25 -0
- package/webapp/postcss.config.js +7 -0
- package/webapp/public/assets/queen-logo-blue.svg +210 -0
- package/webapp/public/assets/queen-logo-cyan.svg +210 -0
- package/webapp/public/assets/queen-logo-indigo.svg +210 -0
- package/webapp/public/assets/queen-logo-orange.svg +210 -0
- package/webapp/public/assets/queen-logo-pink.svg +210 -0
- package/webapp/public/assets/queen-logo-purple.svg +210 -0
- package/webapp/public/assets/queen-logo-rose.svg +239 -0
- package/webapp/public/assets/queen-logo.svg +263 -0
- package/webapp/src/App.vue +19 -0
- package/webapp/src/api/analytics.js +10 -0
- package/webapp/src/api/client.js +29 -0
- package/webapp/src/api/consumers.js +52 -0
- package/webapp/src/api/health.js +7 -0
- package/webapp/src/api/messages.js +26 -0
- package/webapp/src/api/queues.js +14 -0
- package/webapp/src/api/resources.js +8 -0
- package/webapp/src/assets/styles/main.css +357 -0
- package/webapp/src/components/analytics/AnalyticsFilters.vue +87 -0
- package/webapp/src/components/analytics/AnalyticsMetrics.vue +57 -0
- package/webapp/src/components/analytics/MessageDistributionChart.vue +111 -0
- package/webapp/src/components/analytics/MessageFlowChart.vue +173 -0
- package/webapp/src/components/analytics/TimeRangeSelector.vue +27 -0
- package/webapp/src/components/analytics/TopQueuesChart.vue +132 -0
- package/webapp/src/components/common/ConfirmDialog.vue +56 -0
- package/webapp/src/components/common/LoadingSpinner.vue +6 -0
- package/webapp/src/components/common/MetricCard.vue +43 -0
- package/webapp/src/components/common/StatusBadge.vue +45 -0
- package/webapp/src/components/dashboard/MessageStatusCard.vue +50 -0
- package/webapp/src/components/dashboard/PerformanceCard.vue +38 -0
- package/webapp/src/components/dashboard/ThroughputChart.vue +182 -0
- package/webapp/src/components/dashboard/TopQueuesTable.vue +53 -0
- package/webapp/src/components/layout/AppLayout.vue +110 -0
- package/webapp/src/components/layout/AppSidebar.vue +304 -0
- package/webapp/src/components/messages/MessageDetailPanel.vue +242 -0
- package/webapp/src/components/messages/MessageFilters.vue +114 -0
- package/webapp/src/components/queue-detail/PartitionList.vue +79 -0
- package/webapp/src/components/queue-detail/PushMessageModal.vue +175 -0
- package/webapp/src/components/queue-detail/QueueConfig.vue +63 -0
- package/webapp/src/components/queue-detail/QueueDetailHeader.vue +53 -0
- package/webapp/src/components/queue-detail/RecentMessages.vue +76 -0
- package/webapp/src/components/queues/CreateQueueModal.vue +193 -0
- package/webapp/src/components/queues/QueueFilters.vue +90 -0
- package/webapp/src/composables/useApi.js +34 -0
- package/webapp/src/composables/useTheme.js +36 -0
- package/webapp/src/main.js +11 -0
- package/webapp/src/router/index.js +42 -0
- package/webapp/src/utils/colors.js +96 -0
- package/webapp/src/utils/formatters.js +49 -0
- package/webapp/src/views/Analytics.vue +377 -0
- package/webapp/src/views/ConsumerGroups.vue +433 -0
- package/webapp/src/views/Dashboard.vue +418 -0
- package/webapp/src/views/Messages.vue +361 -0
- package/webapp/src/views/QueueDetail.vue +582 -0
- package/webapp/src/views/Queues.vue +496 -0
- package/webapp/tailwind.config.js +25 -0
- package/webapp/vite.config.js +10 -0
- package/CACHE.md +0 -519
- package/DASHBOARD-V3.md +0 -478
- package/DASHBOARD.md +0 -382
- package/MOD_QUEUE.md +0 -453
- package/PARTITION_LOCKING_DESIGN.md +0 -989
- package/PLAN.md +0 -707
- package/QUERY_ANALSYS.md +0 -72
- package/QUEUE_BUS.md +0 -334
- package/V2-PLAN.md +0 -236
- package/dashboard/.vscode/extensions.json +0 -3
- package/dashboard/README.md +0 -5
- package/dashboard/index.html +0 -14
- package/dashboard/package-lock.json +0 -1458
- package/dashboard/package.json +0 -25
- package/dashboard/public/vite.svg +0 -1
- package/dashboard/src/App.vue +0 -29
- package/dashboard/src/assets/styles/main.css +0 -908
- package/dashboard/src/assets/vue.svg +0 -1
- package/dashboard/src/components/cards/MetricCard.vue +0 -298
- package/dashboard/src/components/charts/QueueDepthChart.vue +0 -276
- package/dashboard/src/components/charts/QueueLagChart.vue +0 -436
- package/dashboard/src/components/charts/ThroughputChart.vue +0 -302
- package/dashboard/src/components/common/ActivityFeed.vue +0 -251
- package/dashboard/src/components/layout/AppHeader.vue +0 -208
- package/dashboard/src/components/layout/AppLayout.vue +0 -88
- package/dashboard/src/components/layout/AppSidebar.vue +0 -261
- package/dashboard/src/main.js +0 -44
- package/dashboard/src/router.js +0 -54
- package/dashboard/src/services/api.js +0 -187
- package/dashboard/src/services/websocket.js +0 -167
- package/dashboard/src/utils/constants.js +0 -56
- package/dashboard/src/utils/helpers.js +0 -118
- package/dashboard/src/views/Analytics.vue +0 -912
- package/dashboard/src/views/Dashboard.vue +0 -906
- package/dashboard/src/views/Messages.vue +0 -437
- package/dashboard/src/views/QueueDetail.vue +0 -501
- package/dashboard/src/views/Queues.vue +0 -333
- package/dashboard/vite.config.js +0 -30
- package/debug-namespace.js +0 -110
- package/docs/long-polling.md +0 -159
- package/docs/multi-server-cache-solutions.md +0 -185
- package/docs/performance-tuning.md +0 -222
- package/src/routes/analytics.js +0 -812
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Complete test of the minimalist Queen client
|
|
5
|
+
* Shows all features including namespace/task and message properties
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Queen } from '../src/client/client.js';
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
console.log('🧪 Testing Complete Minimalist Queen Client Features\n');
|
|
12
|
+
|
|
13
|
+
const client = new Queen({
|
|
14
|
+
baseUrls: ['http://localhost:6632'],
|
|
15
|
+
timeout: 30000
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// ============================================
|
|
20
|
+
// 1. Queue Configuration with Namespace/Task
|
|
21
|
+
// ============================================
|
|
22
|
+
console.log('1️⃣ Configuring queues with namespace and task...');
|
|
23
|
+
|
|
24
|
+
// Configure queue with namespace and task
|
|
25
|
+
await client.queue('orders-processing', {
|
|
26
|
+
leaseTime: 300,
|
|
27
|
+
retryLimit: 3,
|
|
28
|
+
priority: 10
|
|
29
|
+
}, {
|
|
30
|
+
namespace: 'ecommerce',
|
|
31
|
+
task: 'process'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await client.queue('inventory-updates', {
|
|
35
|
+
leaseTime: 300,
|
|
36
|
+
priority: 5
|
|
37
|
+
}, {
|
|
38
|
+
namespace: 'ecommerce',
|
|
39
|
+
task: 'update'
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await client.queue('notifications', {
|
|
43
|
+
priority: 8
|
|
44
|
+
}, {
|
|
45
|
+
namespace: 'ecommerce',
|
|
46
|
+
task: 'notify'
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log('✅ Queues configured with namespace/task metadata\n');
|
|
50
|
+
|
|
51
|
+
// ============================================
|
|
52
|
+
// 2. Push with Message Properties
|
|
53
|
+
// ============================================
|
|
54
|
+
console.log('2️⃣ Pushing messages with properties...');
|
|
55
|
+
|
|
56
|
+
// Method 1: Properties in options
|
|
57
|
+
await client.push('orders-processing', {
|
|
58
|
+
orderId: 123,
|
|
59
|
+
amount: 99.99
|
|
60
|
+
}, {
|
|
61
|
+
transactionId: 'txn-123'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Method 2: Properties in the message object
|
|
65
|
+
await client.push('orders-processing', {
|
|
66
|
+
orderId: 124,
|
|
67
|
+
amount: 199.99,
|
|
68
|
+
transactionId: 'txn-124' // Will be extracted
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Method 3: Batch with mixed properties
|
|
72
|
+
await client.push('inventory-updates', [
|
|
73
|
+
{
|
|
74
|
+
sku: 'PROD-001',
|
|
75
|
+
quantity: 5,
|
|
76
|
+
transactionId: 'inv-001'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
sku: 'PROD-002',
|
|
80
|
+
quantity: 10,
|
|
81
|
+
transactionId: 'inv-002'
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
// Method 4: Using special properties format
|
|
86
|
+
await client.push('notifications', {
|
|
87
|
+
_payload: { userId: 789, message: 'Order shipped' },
|
|
88
|
+
_transactionId: 'notif-001'
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log('✅ Messages pushed with various property patterns\n');
|
|
92
|
+
|
|
93
|
+
// ============================================
|
|
94
|
+
// 3. Take by Queue/Partition
|
|
95
|
+
// ============================================
|
|
96
|
+
console.log('3️⃣ Taking messages by queue/partition...');
|
|
97
|
+
|
|
98
|
+
// Take from specific queue
|
|
99
|
+
let count = 0;
|
|
100
|
+
for await (const msg of client.take('orders-processing', { limit: 2 })) {
|
|
101
|
+
count++;
|
|
102
|
+
console.log(` 📩 Order ${count}:`, msg.data);
|
|
103
|
+
console.log(` Transaction ID: ${msg.transactionId}`);
|
|
104
|
+
await client.ack(msg);
|
|
105
|
+
}
|
|
106
|
+
console.log('✅ Processed orders\n');
|
|
107
|
+
|
|
108
|
+
// ============================================
|
|
109
|
+
// 4. Take by Namespace/Task
|
|
110
|
+
// ============================================
|
|
111
|
+
console.log('4️⃣ Taking messages by namespace/task...');
|
|
112
|
+
|
|
113
|
+
// Take all messages from ecommerce namespace
|
|
114
|
+
count = 0;
|
|
115
|
+
for await (const msg of client.take('namespace:ecommerce', { limit: 2 })) {
|
|
116
|
+
count++;
|
|
117
|
+
console.log(` 📦 Namespace message ${count} from ${msg.queue}:`, msg.data);
|
|
118
|
+
await client.ack(msg);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Take only 'update' tasks
|
|
122
|
+
for await (const msg of client.take('task:update', { limit: 2 })) {
|
|
123
|
+
console.log(` 🔄 Update task from ${msg.queue}:`, msg.data);
|
|
124
|
+
await client.ack(msg);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Combine namespace and task
|
|
128
|
+
for await (const msg of client.take('namespace:ecommerce/task:notify', { limit: 1 })) {
|
|
129
|
+
console.log(` 🔔 Notification:`, msg.data);
|
|
130
|
+
await client.ack(msg);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log('✅ Processed messages by namespace/task\n');
|
|
134
|
+
|
|
135
|
+
// ============================================
|
|
136
|
+
// 5. Consumer Groups with Subscription Modes
|
|
137
|
+
// ============================================
|
|
138
|
+
console.log('5️⃣ Testing consumer groups with subscription modes...');
|
|
139
|
+
|
|
140
|
+
// Configure events queue first
|
|
141
|
+
await client.queue('events', {
|
|
142
|
+
leaseTime: 300,
|
|
143
|
+
retryLimit: 3
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Push test events
|
|
147
|
+
await client.push('events', [
|
|
148
|
+
{ event: 'user.signup', userId: 1 },
|
|
149
|
+
{ event: 'user.login', userId: 2 },
|
|
150
|
+
{ event: 'user.purchase', userId: 3 }
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
// Consumer group starting from all messages
|
|
154
|
+
for await (const event of client.take('events@analytics', {
|
|
155
|
+
limit: 1,
|
|
156
|
+
subscriptionMode: 'all'
|
|
157
|
+
})) {
|
|
158
|
+
console.log(' 📊 Analytics (all):', event.data);
|
|
159
|
+
await client.ack(event, true, { group: 'analytics' });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Consumer group starting from new messages only
|
|
163
|
+
for await (const event of client.take('events@realtime', {
|
|
164
|
+
limit: 1,
|
|
165
|
+
subscriptionMode: 'new'
|
|
166
|
+
})) {
|
|
167
|
+
console.log(' ⚡ Realtime (new only):', event.data);
|
|
168
|
+
await client.ack(event, true, { group: 'realtime' });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log('✅ Consumer groups tested\n');
|
|
172
|
+
|
|
173
|
+
// ============================================
|
|
174
|
+
// 6. Advanced Patterns
|
|
175
|
+
// ============================================
|
|
176
|
+
console.log('6️⃣ Testing advanced patterns...');
|
|
177
|
+
|
|
178
|
+
// Configure tasks queue
|
|
179
|
+
await client.queue('tasks', {
|
|
180
|
+
leaseTime: 300,
|
|
181
|
+
priority: 8
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Pattern 1: Partitioned messages with properties
|
|
185
|
+
await client.push('tasks/urgent', {
|
|
186
|
+
taskId: 'TASK-001',
|
|
187
|
+
priority: 'critical'
|
|
188
|
+
}, {
|
|
189
|
+
transactionId: 'task-txn-001'
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Pattern 2: Consumer group on specific partition
|
|
193
|
+
for await (const task of client.take('tasks/urgent@workers', { limit: 1 })) {
|
|
194
|
+
console.log(' 🚨 Urgent task for worker group:', task.data);
|
|
195
|
+
await client.ack(task, true, { group: 'workers' });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Configure logs queue
|
|
199
|
+
await client.queue('logs', {
|
|
200
|
+
leaseTime: 300,
|
|
201
|
+
retentionSeconds: 86400 // Keep for 1 day
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Pattern 3: Batch push with partition
|
|
205
|
+
await client.push('logs/audit', [
|
|
206
|
+
{ action: 'login', user: 'alice', timestamp: Date.now() },
|
|
207
|
+
{ action: 'update', user: 'bob', timestamp: Date.now() },
|
|
208
|
+
{ action: 'delete', user: 'charlie', timestamp: Date.now() }
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
console.log('✅ Advanced patterns tested\n');
|
|
212
|
+
|
|
213
|
+
// ============================================
|
|
214
|
+
// 7. Error Handling
|
|
215
|
+
// ============================================
|
|
216
|
+
console.log('7️⃣ Testing error handling...');
|
|
217
|
+
|
|
218
|
+
// Configure error-test queue
|
|
219
|
+
await client.queue('error-test', {
|
|
220
|
+
leaseTime: 60,
|
|
221
|
+
retryLimit: 2
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Push a message that will fail processing
|
|
225
|
+
await client.push('error-test', {
|
|
226
|
+
willFail: true,
|
|
227
|
+
reason: 'Testing failure path'
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
for await (const msg of client.take('error-test', { limit: 1 })) {
|
|
231
|
+
try {
|
|
232
|
+
// Simulate processing error
|
|
233
|
+
throw new Error('Simulated processing error');
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.log(' ❌ Failed to process:', msg.data);
|
|
236
|
+
// Acknowledge with failure and error context
|
|
237
|
+
await client.ack(msg, false, {
|
|
238
|
+
error: error.message,
|
|
239
|
+
retryable: true
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log('✅ Error handling tested\n');
|
|
245
|
+
|
|
246
|
+
console.log('🎉 All features tested successfully!');
|
|
247
|
+
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('❌ Test failed:', error.message);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
} finally {
|
|
252
|
+
await client.close();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Run tests
|
|
257
|
+
main().catch(error => {
|
|
258
|
+
console.error('Fatal error:', error);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test script for the new Dashboard Status API endpoints
|
|
3
|
+
*
|
|
4
|
+
* This script demonstrates all 5 new endpoints:
|
|
5
|
+
* 1. GET /api/v1/status - Dashboard overview
|
|
6
|
+
* 2. GET /api/v1/status/queues - List queues with stats
|
|
7
|
+
* 3. GET /api/v1/status/queues/:queue - Queue detail
|
|
8
|
+
* 4. GET /api/v1/status/queues/:queue/messages - Queue messages
|
|
9
|
+
* 5. GET /api/v1/status/analytics - Advanced analytics
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createQueenClient } from '../src/client/queenClient.js';
|
|
13
|
+
|
|
14
|
+
const BASE_URL = process.env.QUEEN_URL || 'http://localhost:3000';
|
|
15
|
+
|
|
16
|
+
// Create client
|
|
17
|
+
const client = createQueenClient({ baseUrl: BASE_URL });
|
|
18
|
+
|
|
19
|
+
// Helper to make GET requests
|
|
20
|
+
async function get(path) {
|
|
21
|
+
const response = await fetch(`${BASE_URL}${path}`, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json'
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const error = await response.text();
|
|
30
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return await response.json();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Helper to format JSON output
|
|
37
|
+
function printJson(label, data) {
|
|
38
|
+
console.log(`\n${'='.repeat(80)}`);
|
|
39
|
+
console.log(`📊 ${label}`);
|
|
40
|
+
console.log('='.repeat(80));
|
|
41
|
+
console.log(JSON.stringify(data, null, 2));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function runTests() {
|
|
45
|
+
console.log('🚀 Testing Queen Dashboard Status API\n');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// First, let's create some test data
|
|
49
|
+
console.log('📝 Setting up test data...');
|
|
50
|
+
|
|
51
|
+
// Configure a test queue
|
|
52
|
+
await client.configure({
|
|
53
|
+
queue: 'dashboard-test-queue',
|
|
54
|
+
namespace: 'test-namespace',
|
|
55
|
+
task: 'test-task',
|
|
56
|
+
partitions: 3
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Push some test messages
|
|
60
|
+
const messages = [];
|
|
61
|
+
for (let i = 0; i < 20; i++) {
|
|
62
|
+
messages.push({
|
|
63
|
+
queue: 'dashboard-test-queue',
|
|
64
|
+
partition: `partition-${i % 3}`,
|
|
65
|
+
payload: {
|
|
66
|
+
messageNumber: i,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
data: `Test message ${i}`
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await client.pushBatch(messages);
|
|
74
|
+
console.log('✅ Created test queue with 20 messages\n');
|
|
75
|
+
|
|
76
|
+
// Wait a bit for messages to be processed
|
|
77
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
78
|
+
|
|
79
|
+
// Test 1: Dashboard Status
|
|
80
|
+
console.log('🧪 Test 1: Dashboard Status (GET /api/v1/status)');
|
|
81
|
+
const status = await get('/api/v1/status');
|
|
82
|
+
printJson('Dashboard Status', {
|
|
83
|
+
timeRange: status.timeRange,
|
|
84
|
+
queuesCount: status.queues.length,
|
|
85
|
+
messages: status.messages,
|
|
86
|
+
leases: status.leases,
|
|
87
|
+
throughputSamples: status.throughput.length,
|
|
88
|
+
dlqTotal: status.deadLetterQueue.totalMessages
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Test 2: Queues List
|
|
92
|
+
console.log('\n🧪 Test 2: List Queues (GET /api/v1/status/queues)');
|
|
93
|
+
const queues = await get('/api/v1/status/queues?limit=10');
|
|
94
|
+
printJson('Queues List', {
|
|
95
|
+
total: queues.pagination.total,
|
|
96
|
+
queues: queues.queues.map(q => ({
|
|
97
|
+
name: q.name,
|
|
98
|
+
namespace: q.namespace,
|
|
99
|
+
partitions: q.partitions,
|
|
100
|
+
pending: q.messages.pending,
|
|
101
|
+
completed: q.messages.completed
|
|
102
|
+
}))
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Test 3: Queue Detail
|
|
106
|
+
console.log('\n🧪 Test 3: Queue Detail (GET /api/v1/status/queues/:queue)');
|
|
107
|
+
const queueDetail = await get('/api/v1/status/queues/dashboard-test-queue');
|
|
108
|
+
printJson('Queue Detail', {
|
|
109
|
+
queue: queueDetail.queue,
|
|
110
|
+
totals: queueDetail.totals,
|
|
111
|
+
partitionCount: queueDetail.partitions.length,
|
|
112
|
+
partitions: queueDetail.partitions.map(p => ({
|
|
113
|
+
name: p.name,
|
|
114
|
+
messages: p.messages,
|
|
115
|
+
cursor: p.cursor
|
|
116
|
+
}))
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Test 4: Queue Messages
|
|
120
|
+
console.log('\n🧪 Test 4: Queue Messages (GET /api/v1/status/queues/:queue/messages)');
|
|
121
|
+
const messages_result = await get('/api/v1/status/queues/dashboard-test-queue/messages?limit=5');
|
|
122
|
+
printJson('Queue Messages', {
|
|
123
|
+
total: messages_result.pagination.total,
|
|
124
|
+
showing: messages_result.messages.length,
|
|
125
|
+
messages: messages_result.messages.map(m => ({
|
|
126
|
+
transactionId: m.transactionId,
|
|
127
|
+
partition: m.partition,
|
|
128
|
+
status: m.status,
|
|
129
|
+
createdAt: m.createdAt
|
|
130
|
+
}))
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Test 5: Filter by status
|
|
134
|
+
console.log('\n🧪 Test 5: Filter Messages by Status (GET /api/v1/status/queues/:queue/messages?status=pending)');
|
|
135
|
+
const pendingMessages = await get('/api/v1/status/queues/dashboard-test-queue/messages?status=pending&limit=5');
|
|
136
|
+
printJson('Pending Messages', {
|
|
137
|
+
total: pendingMessages.pagination.total,
|
|
138
|
+
messages: pendingMessages.messages.map(m => ({
|
|
139
|
+
transactionId: m.transactionId,
|
|
140
|
+
status: m.status,
|
|
141
|
+
age: m.age
|
|
142
|
+
}))
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Test 6: Analytics (last hour with minute interval)
|
|
146
|
+
console.log('\n🧪 Test 6: Analytics (GET /api/v1/status/analytics?interval=minute)');
|
|
147
|
+
const analytics = await get('/api/v1/status/analytics?interval=minute');
|
|
148
|
+
printJson('Analytics Overview', {
|
|
149
|
+
timeRange: analytics.timeRange,
|
|
150
|
+
throughput: {
|
|
151
|
+
totals: analytics.throughput.totals,
|
|
152
|
+
recentSamples: analytics.throughput.timeSeries.slice(0, 3)
|
|
153
|
+
},
|
|
154
|
+
latency: analytics.latency.overall,
|
|
155
|
+
errorRates: analytics.errorRates.overall,
|
|
156
|
+
currentDepth: analytics.queueDepths.current,
|
|
157
|
+
topQueues: analytics.topQueues.slice(0, 3),
|
|
158
|
+
dlqTotal: analytics.deadLetterQueue.total
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Test 7: Filter by namespace
|
|
162
|
+
console.log('\n🧪 Test 7: Dashboard Status filtered by namespace (GET /api/v1/status?namespace=test-namespace)');
|
|
163
|
+
const statusFiltered = await get('/api/v1/status?namespace=test-namespace');
|
|
164
|
+
printJson('Filtered Status', {
|
|
165
|
+
queuesCount: statusFiltered.queues.length,
|
|
166
|
+
queues: statusFiltered.queues.map(q => q.name),
|
|
167
|
+
messages: statusFiltered.messages
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Test 8: Analytics with time range
|
|
171
|
+
console.log('\n🧪 Test 8: Analytics with custom time range (last 30 minutes)');
|
|
172
|
+
const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
173
|
+
const now = new Date().toISOString();
|
|
174
|
+
const analyticsTimeRange = await get(`/api/v1/status/analytics?from=${thirtyMinutesAgo}&to=${now}&interval=minute`);
|
|
175
|
+
printJson('Analytics (Last 30 min)', {
|
|
176
|
+
timeRange: analyticsTimeRange.timeRange,
|
|
177
|
+
throughputSamples: analyticsTimeRange.throughput.timeSeries.length,
|
|
178
|
+
totals: analyticsTimeRange.throughput.totals
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
console.log('\n' + '='.repeat(80));
|
|
182
|
+
console.log('✅ All tests completed successfully!');
|
|
183
|
+
console.log('='.repeat(80));
|
|
184
|
+
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('\n❌ Test failed:', error.message);
|
|
187
|
+
console.error(error.stack);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Run tests
|
|
193
|
+
runTests().then(() => {
|
|
194
|
+
console.log('\n✨ Dashboard API test complete!');
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}).catch(error => {
|
|
197
|
+
console.error('Fatal error:', error);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
});
|
|
200
|
+
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test client-provided traceId functionality
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Queen } from '../src/client/client.js';
|
|
8
|
+
import crypto from 'crypto';
|
|
9
|
+
|
|
10
|
+
// Helper to generate a valid UUID v4
|
|
11
|
+
function generateUUID() {
|
|
12
|
+
return crypto.randomUUID();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
console.log('🧪 Testing Client-Provided TraceId\n');
|
|
17
|
+
|
|
18
|
+
const client = new Queen({
|
|
19
|
+
baseUrls: ['http://localhost:6632'],
|
|
20
|
+
timeout: 30000
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Configure test queue
|
|
25
|
+
await client.queue('trace-test', {
|
|
26
|
+
leaseTime: 300,
|
|
27
|
+
retryLimit: 3
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Test 1: Push with valid UUID traceId
|
|
31
|
+
console.log('1️⃣ Testing valid UUID traceId...');
|
|
32
|
+
const validTraceId = generateUUID();
|
|
33
|
+
console.log(` Generated UUID: ${validTraceId}`);
|
|
34
|
+
|
|
35
|
+
await client.push('trace-test', {
|
|
36
|
+
message: 'Test with valid traceId'
|
|
37
|
+
}, {
|
|
38
|
+
transactionId: 'txn-001',
|
|
39
|
+
traceId: validTraceId
|
|
40
|
+
});
|
|
41
|
+
console.log('✅ Message pushed with valid traceId\n');
|
|
42
|
+
|
|
43
|
+
// Test 2: Push with invalid traceId (should be ignored)
|
|
44
|
+
console.log('2️⃣ Testing invalid traceId (should be ignored)...');
|
|
45
|
+
const invalidTraceId = 'not-a-valid-uuid';
|
|
46
|
+
console.log(` Invalid traceId: ${invalidTraceId}`);
|
|
47
|
+
|
|
48
|
+
await client.push('trace-test', {
|
|
49
|
+
message: 'Test with invalid traceId'
|
|
50
|
+
}, {
|
|
51
|
+
transactionId: 'txn-002',
|
|
52
|
+
traceId: invalidTraceId // Will be ignored, server will generate one
|
|
53
|
+
});
|
|
54
|
+
console.log('✅ Message pushed (invalid traceId ignored)\n');
|
|
55
|
+
|
|
56
|
+
// Test 3: Push without traceId (server generates)
|
|
57
|
+
console.log('3️⃣ Testing without traceId (server generates)...');
|
|
58
|
+
await client.push('trace-test', {
|
|
59
|
+
message: 'Test without traceId'
|
|
60
|
+
}, {
|
|
61
|
+
transactionId: 'txn-003'
|
|
62
|
+
});
|
|
63
|
+
console.log('✅ Message pushed (server generated traceId)\n');
|
|
64
|
+
|
|
65
|
+
// Test 4: Using special properties format
|
|
66
|
+
console.log('4️⃣ Testing special properties format...');
|
|
67
|
+
const anotherValidTraceId = generateUUID();
|
|
68
|
+
|
|
69
|
+
await client.push('trace-test', {
|
|
70
|
+
_payload: { message: 'Special format test' },
|
|
71
|
+
_transactionId: 'txn-004',
|
|
72
|
+
_traceId: anotherValidTraceId
|
|
73
|
+
});
|
|
74
|
+
console.log('✅ Message pushed with special format\n');
|
|
75
|
+
|
|
76
|
+
// Test 5: Batch with mixed traceIds
|
|
77
|
+
console.log('5️⃣ Testing batch with mixed traceIds...');
|
|
78
|
+
const batchTraceId1 = generateUUID();
|
|
79
|
+
const batchTraceId2 = generateUUID();
|
|
80
|
+
|
|
81
|
+
await client.push('trace-test', [
|
|
82
|
+
{
|
|
83
|
+
message: 'Batch message 1',
|
|
84
|
+
transactionId: 'batch-001',
|
|
85
|
+
traceId: batchTraceId1 // Valid UUID
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
message: 'Batch message 2',
|
|
89
|
+
transactionId: 'batch-002',
|
|
90
|
+
traceId: 'invalid-trace' // Invalid, will be ignored
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
message: 'Batch message 3',
|
|
94
|
+
transactionId: 'batch-003'
|
|
95
|
+
// No traceId, server will generate
|
|
96
|
+
}
|
|
97
|
+
]);
|
|
98
|
+
console.log('✅ Batch pushed with mixed traceId scenarios\n');
|
|
99
|
+
|
|
100
|
+
// Retrieve and display messages
|
|
101
|
+
console.log('📥 Retrieving messages to verify traceIds...\n');
|
|
102
|
+
let count = 0;
|
|
103
|
+
for await (const msg of client.take('trace-test', { limit: 7 })) {
|
|
104
|
+
count++;
|
|
105
|
+
console.log(`Message ${count}:`);
|
|
106
|
+
console.log(` Transaction ID: ${msg.transactionId}`);
|
|
107
|
+
console.log(` Trace ID: ${msg.traceId}`);
|
|
108
|
+
console.log(` Data: ${JSON.stringify(msg.data)}`);
|
|
109
|
+
|
|
110
|
+
// Check if our valid traceIds were preserved
|
|
111
|
+
if (msg.transactionId === 'txn-001') {
|
|
112
|
+
if (msg.traceId === validTraceId) {
|
|
113
|
+
console.log(' ✅ Client-provided traceId was preserved!');
|
|
114
|
+
} else {
|
|
115
|
+
console.log(' ❌ Client-provided traceId was not preserved');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (msg.transactionId === 'txn-002') {
|
|
120
|
+
if (msg.traceId !== invalidTraceId) {
|
|
121
|
+
console.log(' ✅ Invalid traceId was correctly replaced');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await client.ack(msg);
|
|
126
|
+
console.log('');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('🎉 TraceId test completed successfully!');
|
|
130
|
+
console.log('\nSummary:');
|
|
131
|
+
console.log('- Valid UUID traceIds are accepted and preserved');
|
|
132
|
+
console.log('- Invalid traceIds are ignored (server generates new ones)');
|
|
133
|
+
console.log('- Missing traceIds are auto-generated by server');
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('❌ Test failed:', error.message);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
} finally {
|
|
139
|
+
await client.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Run test
|
|
144
|
+
main().catch(error => {
|
|
145
|
+
console.error('Fatal error:', error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
});
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "queen-mq",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "High-performance message queue backed by PostgreSQL",
|
|
6
6
|
"main": "src/client/index.js",
|
|
7
|
-
|
|
8
7
|
"scripts": {
|
|
9
8
|
"start": "node src/server.js",
|
|
9
|
+
"start:cluster": "node src/cluster-server.js",
|
|
10
10
|
"test:produce": "node test.js produce",
|
|
11
11
|
"test:consume": "node test.js consume",
|
|
12
12
|
"test": "node src/test/test.js",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"init": "node init-db.js"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"axios": "^1.12.2",
|
|
27
|
+
"chart.js": "^4.5.1",
|
|
26
28
|
"pg": "^8.13.3",
|
|
27
29
|
"uuid": "^13.0.0",
|
|
28
30
|
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.52.0",
|
|
@@ -31,6 +33,17 @@
|
|
|
31
33
|
"engines": {
|
|
32
34
|
"node": ">=22.0.0"
|
|
33
35
|
},
|
|
34
|
-
"author": "",
|
|
35
|
-
"license": "
|
|
36
|
+
"author": "Smartness",
|
|
37
|
+
"license": "Apache-2.0",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/smartpricing/queen"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"message-queue",
|
|
44
|
+
"queue",
|
|
45
|
+
"broker",
|
|
46
|
+
"message-queue-system",
|
|
47
|
+
"fifo"
|
|
48
|
+
]
|
|
36
49
|
}
|
package/server.log
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zsh: command not found: node
|