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.
- package/API.md +1116 -0
- package/CACHE.md +519 -0
- package/DASHBOARD-V3.md +478 -0
- package/DASHBOARD.md +382 -0
- package/MOD_QUEUE.md +453 -0
- package/PARTITION_LOCKING_DESIGN.md +989 -0
- package/PLAN.md +707 -0
- package/QUERY_ANALSYS.md +72 -0
- package/QUEUE_BUS.md +334 -0
- package/README.md +1495 -0
- package/V2-PLAN.md +236 -0
- package/assets/dashboard.png +0 -0
- package/dashboard/.vscode/extensions.json +3 -0
- package/dashboard/README.md +5 -0
- package/dashboard/index.html +14 -0
- package/dashboard/package-lock.json +1458 -0
- package/dashboard/package.json +25 -0
- package/dashboard/public/vite.svg +1 -0
- package/dashboard/src/App.vue +29 -0
- package/dashboard/src/assets/styles/main.css +908 -0
- package/dashboard/src/assets/vue.svg +1 -0
- package/dashboard/src/components/cards/MetricCard.vue +298 -0
- package/dashboard/src/components/charts/QueueDepthChart.vue +276 -0
- package/dashboard/src/components/charts/QueueLagChart.vue +436 -0
- package/dashboard/src/components/charts/ThroughputChart.vue +302 -0
- package/dashboard/src/components/common/ActivityFeed.vue +251 -0
- package/dashboard/src/components/layout/AppHeader.vue +208 -0
- package/dashboard/src/components/layout/AppLayout.vue +88 -0
- package/dashboard/src/components/layout/AppSidebar.vue +261 -0
- package/dashboard/src/main.js +44 -0
- package/dashboard/src/router.js +54 -0
- package/dashboard/src/services/api.js +187 -0
- package/dashboard/src/services/websocket.js +167 -0
- package/dashboard/src/utils/constants.js +56 -0
- package/dashboard/src/utils/helpers.js +118 -0
- package/dashboard/src/views/Analytics.vue +912 -0
- package/dashboard/src/views/Dashboard.vue +906 -0
- package/dashboard/src/views/Messages.vue +437 -0
- package/dashboard/src/views/QueueDetail.vue +501 -0
- package/dashboard/src/views/Queues.vue +333 -0
- package/dashboard/vite.config.js +30 -0
- package/debug-namespace.js +110 -0
- package/docs/long-polling.md +159 -0
- package/docs/multi-server-cache-solutions.md +185 -0
- package/docs/performance-tuning.md +222 -0
- package/examples/bus-mode.js +239 -0
- package/examples/continuous-consumer-optimized.js +215 -0
- package/examples/continuous-consumer.js +159 -0
- package/examples/continuous-producer.js +343 -0
- package/examples/mixed-mode.js +277 -0
- package/examples/multi-server-test.js +305 -0
- package/examples/single.js +64 -0
- package/examples/smartchat-dealyed.js +42 -0
- package/examples/smartchat.js +52 -0
- package/examples/test-cache-invalidation.js +119 -0
- package/examples/test-cache-multi-server.js +245 -0
- package/examples/test-minimal-client.js +112 -0
- package/examples/test-queue-creation-policy.js +137 -0
- package/init-db.js +20 -0
- package/package.json +36 -0
- package/src/client/client.js +291 -0
- package/src/client/index.js +6 -0
- package/src/client/queenClient.js +513 -0
- package/src/client/utils/http.js +172 -0
- package/src/client/utils/loadBalancer.js +152 -0
- package/src/client/utils/retry.js +35 -0
- package/src/config.js +215 -0
- package/src/database/connection.js +103 -0
- package/src/database/poolManager.js +192 -0
- package/src/database/schema-v2.sql +214 -0
- package/src/managers/eventManager.js +59 -0
- package/src/managers/queueManagerOptimized.js +1512 -0
- package/src/managers/resourceCache.js +96 -0
- package/src/managers/systemEventManager.js +127 -0
- package/src/routes/ack.js +26 -0
- package/src/routes/analytics.js +812 -0
- package/src/routes/configure.js +46 -0
- package/src/routes/messages.js +298 -0
- package/src/routes/pop.js +85 -0
- package/src/routes/push.js +28 -0
- package/src/routes/resources.js +296 -0
- package/src/server.js +1286 -0
- package/src/services/encryptionService.js +82 -0
- package/src/services/evictionService.js +131 -0
- package/src/services/retentionService.js +129 -0
- package/src/services/startupSync.js +35 -0
- package/src/test/test.js +4521 -0
- package/src/utils/logger.js +44 -0
- package/src/utils/uuid.js +5 -0
- package/src/websocket/wsServer.js +221 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cache Testing with Multiple Servers
|
|
5
|
+
*
|
|
6
|
+
* This script helps test if cache is causing issues when using multiple servers.
|
|
7
|
+
* It sends requests alternately to different servers to check cache consistency.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* 1. Start two Queen servers on different ports:
|
|
11
|
+
* PORT=6632 node src/server.js
|
|
12
|
+
* PORT=6633 node src/server.js
|
|
13
|
+
*
|
|
14
|
+
* 2. Run this test:
|
|
15
|
+
* node examples/test-cache-multi-server.js
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createQueenClient, LoadBalancingStrategy } from '../src/client/index.js';
|
|
19
|
+
|
|
20
|
+
const servers = [
|
|
21
|
+
'http://localhost:6632',
|
|
22
|
+
'http://localhost:6633'
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const TEST_QUEUE = 'cache-test';
|
|
26
|
+
const TEST_NAMESPACE = 'cache-verification';
|
|
27
|
+
|
|
28
|
+
// Helper to log with server info
|
|
29
|
+
const log = (message, server = null) => {
|
|
30
|
+
const timestamp = new Date().toISOString().split('T')[1].slice(0, -1);
|
|
31
|
+
const serverInfo = server ? ` [${server}]` : '';
|
|
32
|
+
console.log(`${timestamp}${serverInfo} | ${message}`);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function testCacheConsistency() {
|
|
36
|
+
console.log('=' .repeat(80));
|
|
37
|
+
console.log('Testing Cache Consistency Across Multiple Servers');
|
|
38
|
+
console.log('=' .repeat(80));
|
|
39
|
+
|
|
40
|
+
// Create client with ROUND_ROBIN to alternate between servers
|
|
41
|
+
const roundRobinClient = createQueenClient({
|
|
42
|
+
baseUrls: servers,
|
|
43
|
+
loadBalancingStrategy: LoadBalancingStrategy.ROUND_ROBIN,
|
|
44
|
+
enableFailover: false // Disable failover to see actual server responses
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create client with SESSION to stick to one server
|
|
48
|
+
const sessionClient = createQueenClient({
|
|
49
|
+
baseUrls: servers,
|
|
50
|
+
loadBalancingStrategy: LoadBalancingStrategy.SESSION,
|
|
51
|
+
enableFailover: false
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Step 1: Configure queue on both servers via round-robin
|
|
56
|
+
log('Step 1: Configuring queue on both servers...');
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < servers.length; i++) {
|
|
59
|
+
await roundRobinClient.configure({
|
|
60
|
+
queue: TEST_QUEUE,
|
|
61
|
+
namespace: TEST_NAMESPACE,
|
|
62
|
+
task: 'cache-test',
|
|
63
|
+
options: {
|
|
64
|
+
maxRetries: 3,
|
|
65
|
+
retryDelay: 1000,
|
|
66
|
+
priority: 5
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const stats = roundRobinClient.getLoadBalancerStats();
|
|
70
|
+
log(`Configuration sent to server ${i + 1}`, servers[i]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Step 2: Push messages alternating between servers
|
|
74
|
+
log('\nStep 2: Pushing messages to alternate servers...');
|
|
75
|
+
|
|
76
|
+
for (let i = 1; i <= 6; i++) {
|
|
77
|
+
const result = await roundRobinClient.push({
|
|
78
|
+
items: [{
|
|
79
|
+
queue: TEST_QUEUE,
|
|
80
|
+
partition: 'Default',
|
|
81
|
+
payload: {
|
|
82
|
+
messageId: i,
|
|
83
|
+
server: `push-${i % 2 === 0 ? 2 : 1}`,
|
|
84
|
+
timestamp: Date.now()
|
|
85
|
+
}
|
|
86
|
+
}]
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const stats = roundRobinClient.getLoadBalancerStats();
|
|
90
|
+
log(`Message ${i} pushed`, servers[(i - 1) % servers.length]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Step 3: Check queue stats from both servers
|
|
94
|
+
log('\nStep 3: Checking queue stats from each server...');
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < servers.length; i++) {
|
|
97
|
+
try {
|
|
98
|
+
const analytics = await roundRobinClient.analytics.queue(TEST_QUEUE);
|
|
99
|
+
log(`Queue stats from server ${i + 1}: ${JSON.stringify(analytics)}`, servers[i]);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
log(`Failed to get stats from server ${i + 1}: ${error.message}`, servers[i]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Step 4: Pop messages from different servers
|
|
106
|
+
log('\nStep 4: Popping messages from different servers...');
|
|
107
|
+
|
|
108
|
+
// Pop from server 1 (using session client for consistency)
|
|
109
|
+
const sessionClient1 = createQueenClient({
|
|
110
|
+
baseUrl: servers[0],
|
|
111
|
+
enableFailover: false
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const pop1 = await sessionClient1.pop({
|
|
115
|
+
queue: TEST_QUEUE,
|
|
116
|
+
batch: 3,
|
|
117
|
+
wait: false
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
log(`Server 1 returned ${pop1.messages?.length || 0} messages`, servers[0]);
|
|
121
|
+
if (pop1.messages && pop1.messages.length > 0) {
|
|
122
|
+
for (const msg of pop1.messages) {
|
|
123
|
+
log(` - Message: ${JSON.stringify(msg.payload)}`, servers[0]);
|
|
124
|
+
await sessionClient1.ack(msg.transactionId, 'completed');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Pop from server 2
|
|
129
|
+
const sessionClient2 = createQueenClient({
|
|
130
|
+
baseUrl: servers[1],
|
|
131
|
+
enableFailover: false
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const pop2 = await sessionClient2.pop({
|
|
135
|
+
queue: TEST_QUEUE,
|
|
136
|
+
batch: 3,
|
|
137
|
+
wait: false
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
log(`Server 2 returned ${pop2.messages?.length || 0} messages`, servers[1]);
|
|
141
|
+
if (pop2.messages && pop2.messages.length > 0) {
|
|
142
|
+
for (const msg of pop2.messages) {
|
|
143
|
+
log(` - Message: ${JSON.stringify(msg.payload)}`, servers[1]);
|
|
144
|
+
await sessionClient2.ack(msg.transactionId, 'completed');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Step 5: Test resource cache consistency
|
|
149
|
+
log('\nStep 5: Testing resource cache consistency...');
|
|
150
|
+
|
|
151
|
+
// Update configuration on server 1
|
|
152
|
+
const updateClient1 = createQueenClient({
|
|
153
|
+
baseUrl: servers[0],
|
|
154
|
+
enableFailover: false
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
await updateClient1.configure({
|
|
158
|
+
queue: TEST_QUEUE,
|
|
159
|
+
namespace: TEST_NAMESPACE,
|
|
160
|
+
task: 'cache-test-updated',
|
|
161
|
+
options: {
|
|
162
|
+
maxRetries: 5, // Changed from 3
|
|
163
|
+
retryDelay: 2000, // Changed from 1000
|
|
164
|
+
priority: 10 // Changed from 5
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
log('Configuration updated on server 1', servers[0]);
|
|
169
|
+
|
|
170
|
+
// Wait a moment for potential cache propagation
|
|
171
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
172
|
+
|
|
173
|
+
// Check if server 2 sees the update
|
|
174
|
+
log('Checking if configuration changes are visible on both servers...');
|
|
175
|
+
|
|
176
|
+
// Push a message to server 2 and see if it uses new or old config
|
|
177
|
+
const testClient2 = createQueenClient({
|
|
178
|
+
baseUrl: servers[1],
|
|
179
|
+
enableFailover: false
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await testClient2.push({
|
|
183
|
+
items: [{
|
|
184
|
+
queue: TEST_QUEUE,
|
|
185
|
+
partition: 'CacheTest',
|
|
186
|
+
payload: {
|
|
187
|
+
test: 'cache-verification',
|
|
188
|
+
expectedConfig: 'Should use database config, not cached'
|
|
189
|
+
}
|
|
190
|
+
}]
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
log('Test message pushed to server 2', servers[1]);
|
|
194
|
+
|
|
195
|
+
// Pop and check the message properties
|
|
196
|
+
const verifyPop = await testClient2.pop({
|
|
197
|
+
queue: TEST_QUEUE,
|
|
198
|
+
partition: 'CacheTest',
|
|
199
|
+
wait: false
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (verifyPop.messages && verifyPop.messages.length > 0) {
|
|
203
|
+
const msg = verifyPop.messages[0];
|
|
204
|
+
log(`Message from server 2: priority=${msg.priority}, retryCount=${msg.retryCount}`, servers[1]);
|
|
205
|
+
|
|
206
|
+
// Check queue configuration directly to verify cache consistency
|
|
207
|
+
const queueStats = await testClient2.analytics.queue(TEST_QUEUE);
|
|
208
|
+
log(`Queue config from server 2: ${JSON.stringify(queueStats)}`, servers[1]);
|
|
209
|
+
|
|
210
|
+
// Priority is included in the message and should reflect the updated value
|
|
211
|
+
if (msg.priority === 10) {
|
|
212
|
+
log('✅ Cache consistency verified! Server 2 sees updated priority configuration', servers[1]);
|
|
213
|
+
} else {
|
|
214
|
+
log(`⚠️ Potential cache issue: Server 2 may be using cached configuration (priority=${msg.priority} instead of 10)`, servers[1]);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
await testClient2.ack(msg.transactionId, 'completed');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Step 6: Clean up
|
|
221
|
+
log('\nStep 6: Cleaning up test data...');
|
|
222
|
+
|
|
223
|
+
// Clear the queue on both servers
|
|
224
|
+
await roundRobinClient.queues.clear(TEST_QUEUE);
|
|
225
|
+
log('Test queue cleared');
|
|
226
|
+
|
|
227
|
+
console.log('\n' + '=' .repeat(80));
|
|
228
|
+
log('✅ Cache consistency test completed!');
|
|
229
|
+
console.log('=' .repeat(80));
|
|
230
|
+
|
|
231
|
+
// Print summary
|
|
232
|
+
console.log('\nSummary:');
|
|
233
|
+
console.log('- If both servers returned the same messages, they share the same database');
|
|
234
|
+
console.log('- If configuration changes were visible on both servers, cache is properly invalidated');
|
|
235
|
+
console.log('- If you see cache issues, check the resourceCache implementation in queueManagerOptimized.js');
|
|
236
|
+
|
|
237
|
+
} catch (error) {
|
|
238
|
+
log(`❌ Test failed: ${error.message}`);
|
|
239
|
+
console.error(error);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Run the test
|
|
245
|
+
testCacheConsistency().catch(console.error);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test the minimalist Queen client interface
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Queen } from '../src/client/client.js';
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
console.log('🧪 Testing Minimalist Queen Client\n');
|
|
11
|
+
|
|
12
|
+
// Create client instance
|
|
13
|
+
const client = new Queen({
|
|
14
|
+
baseUrls: ['http://localhost:6632'],
|
|
15
|
+
timeout: 30000,
|
|
16
|
+
retryAttempts: 3,
|
|
17
|
+
retryDelay: 1000
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const testQueue = 'test-minimal-' + Date.now();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// 1. Configure queue
|
|
24
|
+
console.log('1️⃣ Configuring queue...');
|
|
25
|
+
await client.queue(testQueue, {
|
|
26
|
+
leaseTime: 300,
|
|
27
|
+
retryLimit: 3,
|
|
28
|
+
retryDelay: 1000
|
|
29
|
+
});
|
|
30
|
+
console.log('✅ Queue configured:', testQueue);
|
|
31
|
+
|
|
32
|
+
// 2. Push single message
|
|
33
|
+
console.log('\n2️⃣ Pushing single message...');
|
|
34
|
+
await client.push(testQueue, {
|
|
35
|
+
id: 1,
|
|
36
|
+
message: 'Hello, Queen!',
|
|
37
|
+
timestamp: new Date().toISOString()
|
|
38
|
+
});
|
|
39
|
+
console.log('✅ Single message pushed');
|
|
40
|
+
|
|
41
|
+
// 3. Push batch of messages
|
|
42
|
+
console.log('\n3️⃣ Pushing batch of messages...');
|
|
43
|
+
const batch = [
|
|
44
|
+
{ id: 2, message: 'Message 2' },
|
|
45
|
+
{ id: 3, message: 'Message 3' },
|
|
46
|
+
{ id: 4, message: 'Message 4' }
|
|
47
|
+
];
|
|
48
|
+
await client.push(testQueue, batch);
|
|
49
|
+
console.log('✅ Batch of 3 messages pushed');
|
|
50
|
+
|
|
51
|
+
// 4. Push to specific partition
|
|
52
|
+
console.log('\n4️⃣ Pushing to urgent partition...');
|
|
53
|
+
await client.push(`${testQueue}/urgent`, {
|
|
54
|
+
id: 5,
|
|
55
|
+
message: 'Urgent message!',
|
|
56
|
+
priority: 'high'
|
|
57
|
+
});
|
|
58
|
+
console.log('✅ Message pushed to urgent partition');
|
|
59
|
+
|
|
60
|
+
// 5. Take messages with limit
|
|
61
|
+
console.log('\n5️⃣ Taking 3 messages...');
|
|
62
|
+
let count = 0;
|
|
63
|
+
for await (const message of client.take(testQueue, { limit: 3 })) {
|
|
64
|
+
count++;
|
|
65
|
+
console.log(` 📩 Message ${count}:`, message.data);
|
|
66
|
+
await client.ack(message);
|
|
67
|
+
}
|
|
68
|
+
console.log('✅ Took and acknowledged 3 messages');
|
|
69
|
+
|
|
70
|
+
// 6. Take from specific partition
|
|
71
|
+
console.log('\n6️⃣ Taking from urgent partition...');
|
|
72
|
+
for await (const message of client.take(`${testQueue}/urgent`, { limit: 1 })) {
|
|
73
|
+
console.log(' 🚨 Urgent message:', message.data);
|
|
74
|
+
await client.ack(message, true);
|
|
75
|
+
}
|
|
76
|
+
console.log('✅ Processed urgent message');
|
|
77
|
+
|
|
78
|
+
// 7. Test failure acknowledgment
|
|
79
|
+
console.log('\n7️⃣ Testing failure acknowledgment...');
|
|
80
|
+
await client.push(testQueue, { id: 6, message: 'Will fail' });
|
|
81
|
+
|
|
82
|
+
for await (const message of client.take(testQueue, { limit: 1 })) {
|
|
83
|
+
console.log(' ❌ Simulating failure for:', message.data);
|
|
84
|
+
await client.ack(message, false, { error: 'Simulated error' });
|
|
85
|
+
}
|
|
86
|
+
console.log('✅ Failed acknowledgment sent');
|
|
87
|
+
|
|
88
|
+
// 8. Test consumer groups (if needed)
|
|
89
|
+
console.log('\n8️⃣ Testing consumer groups...');
|
|
90
|
+
await client.push(testQueue, { id: 7, message: 'For consumer group' });
|
|
91
|
+
|
|
92
|
+
for await (const message of client.take(`${testQueue}@test-group`, { limit: 1 })) {
|
|
93
|
+
console.log(' 👥 Message via consumer group:', message.data);
|
|
94
|
+
await client.ack(message, true, { group: 'test-group' });
|
|
95
|
+
}
|
|
96
|
+
console.log('✅ Consumer group test complete');
|
|
97
|
+
|
|
98
|
+
console.log('\n🎉 All tests passed!');
|
|
99
|
+
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('\n❌ Test failed:', error.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
} finally {
|
|
104
|
+
await client.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Run tests
|
|
109
|
+
main().catch(error => {
|
|
110
|
+
console.error('Fatal error:', error);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test script to verify the new queue creation policy:
|
|
5
|
+
* - Queues must be created via configure endpoint
|
|
6
|
+
* - Push to non-existent queue should fail
|
|
7
|
+
* - Partitions can still be created on-demand
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createQueenClient } from '../src/client/index.js';
|
|
11
|
+
|
|
12
|
+
const TEST_QUEUE = 'test-creation-policy';
|
|
13
|
+
const NON_EXISTENT_QUEUE = 'queue-that-does-not-exist';
|
|
14
|
+
|
|
15
|
+
// Helper to log with timestamp
|
|
16
|
+
const log = (message, type = 'info') => {
|
|
17
|
+
const timestamp = new Date().toISOString().split('T')[1].slice(0, -1);
|
|
18
|
+
const prefix = type === 'error' ? '❌' : type === 'success' ? '✅' : 'ℹ️';
|
|
19
|
+
console.log(`${timestamp} | ${prefix} ${message}`);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
async function runTests() {
|
|
23
|
+
console.log('=' .repeat(80));
|
|
24
|
+
console.log('Testing Queue Creation Policy');
|
|
25
|
+
console.log('=' .repeat(80));
|
|
26
|
+
|
|
27
|
+
const client = createQueenClient({
|
|
28
|
+
baseUrl: 'http://localhost:6632'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Test 1: Push to non-existent queue should fail
|
|
33
|
+
log('\nTest 1: Pushing to non-existent queue (should fail)...');
|
|
34
|
+
try {
|
|
35
|
+
await client.push({
|
|
36
|
+
items: [{
|
|
37
|
+
queue: NON_EXISTENT_QUEUE,
|
|
38
|
+
partition: 'Default',
|
|
39
|
+
payload: { test: 'should fail' }
|
|
40
|
+
}]
|
|
41
|
+
});
|
|
42
|
+
log('Push succeeded unexpectedly!', 'error');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error.message.includes('does not exist')) {
|
|
46
|
+
log(`Push failed as expected: ${error.message}`, 'success');
|
|
47
|
+
} else {
|
|
48
|
+
log(`Push failed with unexpected error: ${error.message}`, 'error');
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Test 2: Create queue via configure
|
|
54
|
+
log('\nTest 2: Creating queue via configure endpoint...');
|
|
55
|
+
const configResult = await client.configure({
|
|
56
|
+
queue: TEST_QUEUE,
|
|
57
|
+
namespace: 'test',
|
|
58
|
+
task: 'creation-policy',
|
|
59
|
+
options: {
|
|
60
|
+
retryLimit: 3,
|
|
61
|
+
priority: 5
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
log(`Queue configured: ${JSON.stringify(configResult)}`, 'success');
|
|
65
|
+
|
|
66
|
+
// Test 3: Push to configured queue should succeed
|
|
67
|
+
log('\nTest 3: Pushing to configured queue (should succeed)...');
|
|
68
|
+
const pushResult1 = await client.push({
|
|
69
|
+
items: [{
|
|
70
|
+
queue: TEST_QUEUE,
|
|
71
|
+
partition: 'Default',
|
|
72
|
+
payload: { test: 'message 1' }
|
|
73
|
+
}]
|
|
74
|
+
});
|
|
75
|
+
log('Push to Default partition succeeded', 'success');
|
|
76
|
+
|
|
77
|
+
// Test 4: Push to new partition (should create partition on-demand)
|
|
78
|
+
log('\nTest 4: Pushing to new partition (should create on-demand)...');
|
|
79
|
+
const pushResult2 = await client.push({
|
|
80
|
+
items: [{
|
|
81
|
+
queue: TEST_QUEUE,
|
|
82
|
+
partition: 'NewPartition',
|
|
83
|
+
payload: { test: 'message 2' }
|
|
84
|
+
}]
|
|
85
|
+
});
|
|
86
|
+
log('Push to NewPartition succeeded (partition created on-demand)', 'success');
|
|
87
|
+
|
|
88
|
+
// Test 5: Verify messages can be popped
|
|
89
|
+
log('\nTest 5: Verifying messages can be popped...');
|
|
90
|
+
const popResult = await client.pop({
|
|
91
|
+
queue: TEST_QUEUE,
|
|
92
|
+
batch: 10,
|
|
93
|
+
wait: false
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (popResult.messages && popResult.messages.length === 2) {
|
|
97
|
+
log(`Successfully retrieved ${popResult.messages.length} messages`, 'success');
|
|
98
|
+
|
|
99
|
+
// Check partitions
|
|
100
|
+
const partitions = new Set(popResult.messages.map(m => m.partition));
|
|
101
|
+
if (partitions.has('Default') && partitions.has('NewPartition')) {
|
|
102
|
+
log('Both partitions (Default and NewPartition) confirmed', 'success');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Acknowledge messages
|
|
106
|
+
for (const msg of popResult.messages) {
|
|
107
|
+
await client.ack(msg.transactionId, 'completed');
|
|
108
|
+
}
|
|
109
|
+
log('Messages acknowledged', 'success');
|
|
110
|
+
} else {
|
|
111
|
+
log(`Expected 2 messages, got ${popResult.messages?.length || 0}`, 'error');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Test 6: Clean up
|
|
115
|
+
log('\nTest 6: Cleaning up...');
|
|
116
|
+
await client.queues.clear(TEST_QUEUE);
|
|
117
|
+
log('Test queue cleared', 'success');
|
|
118
|
+
|
|
119
|
+
console.log('\n' + '=' .repeat(80));
|
|
120
|
+
log('All tests passed! Queue creation policy working correctly', 'success');
|
|
121
|
+
console.log('=' .repeat(80));
|
|
122
|
+
|
|
123
|
+
console.log('\nSummary:');
|
|
124
|
+
console.log('✅ Queues must be created via configure endpoint');
|
|
125
|
+
console.log('✅ Push to non-existent queue fails with clear error');
|
|
126
|
+
console.log('✅ Partitions can still be created on-demand');
|
|
127
|
+
console.log('✅ No implicit queue creation during push operations');
|
|
128
|
+
|
|
129
|
+
} catch (error) {
|
|
130
|
+
log(`Test failed: ${error.message}`, 'error');
|
|
131
|
+
console.error(error);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Run the tests
|
|
137
|
+
runTests().catch(console.error);
|
package/init-db.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createPool, initDatabase } from './src/database/connection.js';
|
|
3
|
+
|
|
4
|
+
const init = async () => {
|
|
5
|
+
console.log('Initializing Queen database schema...');
|
|
6
|
+
|
|
7
|
+
const pool = createPool();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
await initDatabase(pool);
|
|
11
|
+
console.log('✅ Database schema initialized successfully');
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('❌ Failed to initialize database:', error.message);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
} finally {
|
|
16
|
+
await pool.end();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
init();
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "queen-mq",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "High-performance message queue backed by PostgreSQL",
|
|
6
|
+
"main": "src/client/index.js",
|
|
7
|
+
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "node src/server.js",
|
|
10
|
+
"test:produce": "node test.js produce",
|
|
11
|
+
"test:consume": "node test.js consume",
|
|
12
|
+
"test": "node src/test/test.js",
|
|
13
|
+
"test:core": "node src/test/core-features-test.js",
|
|
14
|
+
"test:comprehensive": "node src/test/comprehensive-test.js",
|
|
15
|
+
"test:edge": "node src/test/comprehensive-edge-cases-test.js",
|
|
16
|
+
"test:advanced": "node src/test/advanced-scenarios-test.js",
|
|
17
|
+
"test:all": "npm run test && npm run test:edge && npm run test:advanced",
|
|
18
|
+
"test:quick": "npm run test:core",
|
|
19
|
+
"test:lease": "node src/test/test-lease-expiration.js",
|
|
20
|
+
"test:lease:simple": "node test-lease-simple.js",
|
|
21
|
+
"test:client": "node test-client.js",
|
|
22
|
+
"test:simple": "node test-simple.js",
|
|
23
|
+
"init": "node init-db.js"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"pg": "^8.13.3",
|
|
27
|
+
"uuid": "^13.0.0",
|
|
28
|
+
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.52.0",
|
|
29
|
+
"ws": "^8.18.3"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22.0.0"
|
|
33
|
+
},
|
|
34
|
+
"author": "",
|
|
35
|
+
"license": "ISC"
|
|
36
|
+
}
|