queen-mq 0.3.0 โ†’ 0.4.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 (39) hide show
  1. package/README.md +212 -144
  2. package/client-js/client-v2/Queen.js +28 -0
  3. package/client-js/client-v2/README.md +6 -2
  4. package/client-js/client-v2/builders/QueueBuilder.js +7 -1
  5. package/client-js/client-v2/builders/TransactionBuilder.js +20 -4
  6. package/client-js/client-v2/stream/StreamBuilder.js +72 -0
  7. package/client-js/client-v2/stream/StreamConsumer.js +156 -0
  8. package/client-js/client-v2/stream/Window.js +140 -0
  9. package/client-js/test-v2/MAINTENANCE_TEST.md +148 -0
  10. package/client-js/test-v2/consume.js +1 -0
  11. package/client-js/test-v2/maintenance.js +261 -0
  12. package/client-js/test-v2/retention.js +69 -0
  13. package/client-js/test-v2/run.js +7 -1
  14. package/package.json +2 -2
  15. package/client-js/client/client.js +0 -1536
  16. package/client-js/client/index.js +0 -4
  17. package/client-js/client/utils/http.js +0 -173
  18. package/client-js/client/utils/loadBalancer.js +0 -152
  19. package/client-js/client/utils/retry.js +0 -41
  20. package/client-js/services/encryptionService.js +0 -82
  21. package/client-js/services/evictionService.js +0 -160
  22. package/client-js/services/retentionService.js +0 -162
  23. package/client-js/services/startupSync.js +0 -35
  24. package/client-js/test/README.md +0 -224
  25. package/client-js/test/advanced-client-tests.js +0 -761
  26. package/client-js/test/advanced-pattern-tests.js +0 -1137
  27. package/client-js/test/bus-mode-tests.js +0 -361
  28. package/client-js/test/core-tests.js +0 -457
  29. package/client-js/test/edge-case-tests.js +0 -562
  30. package/client-js/test/enterprise-tests.js +0 -637
  31. package/client-js/test/human.js +0 -162
  32. package/client-js/test/partition-locking-tests.js +0 -545
  33. package/client-js/test/partition-transaction-tests.js +0 -482
  34. package/client-js/test/qos0-tests.js +0 -334
  35. package/client-js/test/test-new.js +0 -370
  36. package/client-js/test/utils.js +0 -169
  37. package/client-js/test/window-buffer-test.js +0 -114
  38. package/client-js/utils/logger.js +0 -44
  39. package/client-js/utils/uuid.js +0 -5
@@ -0,0 +1,261 @@
1
+ import { Queen } from '../client-v2/index.js';
2
+ import axios from 'axios';
3
+
4
+ export async function test_maintenance_mode(client = null) {
5
+ console.log('๐Ÿงช Testing Maintenance Mode with File Buffer\n');
6
+
7
+ const queen = client
8
+
9
+ // Use port 6632 (default Queen port) for API calls
10
+ const API_URL = 'http://localhost:6632';
11
+ const QUEUE_NAME = 'test-maintenance-queue';
12
+ const MESSAGES_PER_SECOND = 10;
13
+ const MAINTENANCE_DURATION_SECONDS = 10;
14
+
15
+ let producedCount = 0;
16
+ let receivedCount = 0;
17
+ let consumerActive = false;
18
+
19
+ try {
20
+ // 0. Ensure maintenance mode is OFF before starting
21
+ console.log('๐Ÿงน Step 0: Ensuring clean state...');
22
+ await axios.post(`${API_URL}/api/v1/system/maintenance`, { enabled: false });
23
+
24
+ let initialStatus = await axios.get(`${API_URL}/api/v1/system/maintenance`);
25
+ console.log(` Initial status:`, initialStatus.data);
26
+
27
+ if (initialStatus.data.bufferedMessages > 0) {
28
+ console.log(` โš ๏ธ Found ${initialStatus.data.bufferedMessages} buffered messages from previous runs`);
29
+ console.log(' Waiting for buffer to drain (checking every 2 seconds)...');
30
+
31
+ // Wait for buffer to drain completely
32
+ let waitTime = 0;
33
+ const maxWait = 60; // Max 60 seconds
34
+ while (waitTime < maxWait) {
35
+ await sleep(2000);
36
+ waitTime += 2;
37
+ const status = await axios.get(`${API_URL}/api/v1/system/maintenance`);
38
+ console.log(` ${waitTime}s - Buffered: ${status.data.bufferedMessages}`);
39
+
40
+ if (status.data.bufferedMessages === 0) {
41
+ console.log(' โœ… Buffer drained completely');
42
+ break;
43
+ }
44
+ }
45
+
46
+ if (waitTime >= maxWait) {
47
+ console.log(` โš ๏ธ Timeout waiting for buffer to drain. Continuing anyway...`);
48
+ }
49
+ }
50
+ console.log('');
51
+
52
+ // 1. Configure queue
53
+ console.log(`๐Ÿ“‹ Step 1: Configuring queue "${QUEUE_NAME}"...`);
54
+ await queen.queue(QUEUE_NAME).config({
55
+ leaseTime: 60,
56
+ }).create();
57
+ console.log('โœ… Queue configured\n');
58
+
59
+ // 2. Start consumer
60
+ console.log('๐Ÿ“ฅ Step 2: Starting consumer...');
61
+ consumerActive = true;
62
+
63
+ const consumePromise = (async () => {
64
+ while (consumerActive) {
65
+ try {
66
+ const messages = await queen.queue(QUEUE_NAME).batch(10).wait(true).limit(1).each().pop();
67
+
68
+ if (messages && messages.length > 0) {
69
+ receivedCount += messages.length;
70
+ console.log(` ๐Ÿ“จ Received ${messages.length} messages (total: ${receivedCount})`);
71
+
72
+ // Acknowledge messages
73
+ await queen.ack(messages);
74
+ }
75
+ } catch (err) {
76
+ if (consumerActive) {
77
+ console.error(' โŒ Consumer error:', err.message);
78
+ }
79
+ }
80
+ }
81
+ })();
82
+
83
+ console.log('โœ… Consumer started\n');
84
+
85
+ // 3. Start producer (10 messages/second)
86
+ console.log(`๐Ÿ“ค Step 3: Starting producer (${MESSAGES_PER_SECOND} msgs/sec)...`);
87
+ let producerRunning = true;
88
+
89
+ const producerLoop = async () => {
90
+ while (producerRunning) {
91
+ try {
92
+ // Push batch of 10 messages
93
+ const batch = [];
94
+ for (let i = 0; i < MESSAGES_PER_SECOND; i++) {
95
+ batch.push({
96
+ data: {
97
+ messageNumber: producedCount + i,
98
+ timestamp: Date.now()
99
+ }
100
+ });
101
+ }
102
+
103
+ // Push and wait for confirmation
104
+ await queen.queue(QUEUE_NAME).push(batch);
105
+ producedCount += batch.length; // Only increment after successful push
106
+
107
+ // Wait 1 second before next batch
108
+ await sleep(1000);
109
+ } catch (err) {
110
+ console.error(' โŒ Producer error:', err.message);
111
+ await sleep(1000); // Wait before retry
112
+ }
113
+ }
114
+ };
115
+
116
+ const producerPromise = producerLoop();
117
+
118
+ console.log('โœ… Producer started\n');
119
+
120
+ // Wait 3 seconds for messages to flow normally
121
+ console.log('โฑ๏ธ Step 4: Waiting 3 seconds for normal message flow...');
122
+ await sleep(3000);
123
+ console.log(` Produced: ${producedCount}, Received: ${receivedCount}\n`);
124
+
125
+ const receivedBeforeMaintenance = receivedCount;
126
+
127
+ // 4. Enable maintenance mode
128
+ console.log('๐Ÿ”ง Step 5: Enabling MAINTENANCE MODE...');
129
+ const maintenanceResponse = await axios.post(`${API_URL}/api/v1/system/maintenance`, {
130
+ enabled: true
131
+ });
132
+ console.log(` Response:`, maintenanceResponse.data);
133
+ console.log('โœ… Maintenance mode enabled\n');
134
+
135
+ // Wait 2 seconds and check that consumer stops receiving
136
+ console.log('โฑ๏ธ Step 6: Waiting 5 seconds - consumer should stop receiving...');
137
+ await sleep(5000);
138
+ const receivedDuringWait = receivedCount - receivedBeforeMaintenance;
139
+ console.log(` Messages received during maintenance: ${receivedDuringWait}`);
140
+ console.log(` Total: Produced=${producedCount}, Received=${receivedCount}\n`);
141
+
142
+ // 5. Keep producing during maintenance
143
+ console.log(`โฑ๏ธ Step 7: Producing during maintenance for ${MAINTENANCE_DURATION_SECONDS} seconds...`);
144
+ console.log(' (Messages should go to file buffer)\n');
145
+ await sleep(MAINTENANCE_DURATION_SECONDS * 1000);
146
+
147
+ const producedDuringMaintenance = producedCount;
148
+ const receivedAfterMaintenance = receivedCount;
149
+ const messagesBuffered = producedDuringMaintenance - receivedAfterMaintenance;
150
+
151
+ console.log(` Maintenance period complete:`);
152
+ console.log(` - Total produced: ${producedCount}`);
153
+ console.log(` - Total received: ${receivedCount}`);
154
+ console.log(` - Buffered (should be ~${MESSAGES_PER_SECOND * MAINTENANCE_DURATION_SECONDS}): ${messagesBuffered}\n`);
155
+
156
+ // 6. Check maintenance status
157
+ console.log('๐Ÿ“Š Step 8: Checking maintenance status...');
158
+ const statusResponse = await axios.get(`${API_URL}/api/v1/system/maintenance`);
159
+ console.log(` Status:`, statusResponse.data);
160
+ console.log('');
161
+
162
+ // 7. Disable maintenance mode
163
+ console.log('โœ… Step 9: Disabling MAINTENANCE MODE...');
164
+ const disableResponse = await axios.post(`${API_URL}/api/v1/system/maintenance`, {
165
+ enabled: false
166
+ });
167
+ console.log(` Response:`, disableResponse.data);
168
+ console.log(' (File buffer should start draining to database)\n');
169
+
170
+ // 8. Wait for messages to resume and stop producer
171
+ console.log('โฑ๏ธ Step 10: Waiting for messages to resume...');
172
+ await sleep(2000);
173
+
174
+ const receivedAfterResume = receivedCount;
175
+ const messagesResumed = receivedAfterResume - receivedAfterMaintenance;
176
+ console.log(` Messages received after resuming: ${messagesResumed}`);
177
+ console.log('โœ… Messages are flowing again!\n');
178
+
179
+ // Stop producer
180
+ console.log('๐Ÿ›‘ Step 11: Stopping producer...');
181
+ producerRunning = false;
182
+ await producerPromise; // Wait for producer to actually stop
183
+
184
+ const finalProducedCount = producedCount;
185
+ console.log(` Final produced count: ${finalProducedCount}\n`);
186
+
187
+ // 9. Wait for drain to complete
188
+ console.log('โฑ๏ธ Step 12: Waiting 10 seconds for file buffer to drain...');
189
+ for (let i = 0; i < 10; i++) {
190
+ await sleep(1000);
191
+ console.log(` ${i + 1}s - Received: ${receivedCount}/${finalProducedCount} (${((receivedCount/finalProducedCount)*100).toFixed(1)}%)`);
192
+
193
+ // Check if we've received all messages
194
+ if (receivedCount >= finalProducedCount) {
195
+ console.log(' โœ… All messages received!\n');
196
+ break;
197
+ }
198
+ }
199
+
200
+ // 10. Stop consumer
201
+ console.log('๐Ÿ›‘ Step 13: Stopping consumer...');
202
+ consumerActive = false;
203
+ await consumePromise;
204
+ console.log('โœ… Consumer stopped\n');
205
+
206
+ // 11. Final verification
207
+ console.log('๐Ÿ“Š Final Verification:\n');
208
+ console.log(` Total Produced: ${finalProducedCount}`);
209
+ console.log(` Total Received: ${receivedCount}`);
210
+ console.log(` Difference: ${finalProducedCount - receivedCount}`);
211
+
212
+ if (receivedCount === finalProducedCount) {
213
+ console.log('\nโœ… SUCCESS: All messages accounted for!');
214
+ console.log(' Maintenance mode works correctly with file buffer.\n');
215
+
216
+ return {
217
+ success: true,
218
+ message: `All ${finalProducedCount} messages accounted for during maintenance mode test`
219
+ };
220
+ } else if (receivedCount < finalProducedCount) {
221
+ const missing = finalProducedCount - receivedCount;
222
+ console.log(`\nโš ๏ธ WARNING: Missing ${missing} messages`);
223
+ console.log(' They may still be in the file buffer or queue.');
224
+ console.log(' Check /var/lib/queen/buffers/ for remaining files.\n');
225
+
226
+ // Check buffer status
227
+ const finalStatus = await axios.get(`${API_URL}/api/v1/system/maintenance`);
228
+ console.log(' Buffer status:', finalStatus.data);
229
+
230
+ return {
231
+ success: false,
232
+ message: `Missing ${missing} messages (produced: ${finalProducedCount}, received: ${receivedCount})`
233
+ };
234
+ } else {
235
+ console.log(`\nโŒ ERROR: Received more messages than produced! (${receivedCount} > ${finalProducedCount})`);
236
+ console.log(' This should not happen - possible duplicate issue.\n');
237
+
238
+ return {
239
+ success: false,
240
+ message: `Received more than produced (${receivedCount} > ${finalProducedCount})`
241
+ };
242
+ }
243
+
244
+ } catch (err) {
245
+ console.error('\nโŒ Test failed:', err.message);
246
+ console.error(err.stack);
247
+
248
+ // Clean up
249
+ consumerActive = false;
250
+
251
+ return { success: false, message: `Test threw error: ${err.message}` };
252
+ } finally {
253
+ // Clean up
254
+ consumerActive = false;
255
+ }
256
+ }
257
+
258
+ function sleep(ms) {
259
+ return new Promise(resolve => setTimeout(resolve, ms));
260
+ }
261
+
@@ -0,0 +1,69 @@
1
+
2
+ // To pass this test the server needs to
3
+ // be started with RETENTION_INTERVAL=2000
4
+ export async function retentionTest(client) {
5
+ const queueName = 'test-queue-retention-01';
6
+ const queue = await client
7
+ .queue(queueName)
8
+ .config({
9
+ retentionEnabled: true,
10
+ retentionSeconds: 10,
11
+ completedRetentionSeconds: 5
12
+ })
13
+ .create()
14
+ if (!queue.configured) {
15
+ return { success: false, message: 'Queue not created' }
16
+ }
17
+ for (let i = 0; i < 100; i++) {
18
+ const res = await client
19
+ .queue(queueName)
20
+ .push([{ data: { message: i } }])
21
+ }
22
+
23
+ await new Promise(resolve => setTimeout(resolve, 15000))
24
+
25
+ const messages = await client
26
+ .queue(queueName)
27
+ .batch(100)
28
+ .wait(false)
29
+ .pop()
30
+
31
+ if (messages.length === 0) {
32
+ return { success: true, message: 'Messages cleaned up' }
33
+ }
34
+
35
+ return { success: false }
36
+ }
37
+
38
+
39
+ // To pass this test the server needs to
40
+ // be started with RETENTION_INTERVAL=2000
41
+ export async function retentionTestMaxTime(client) {
42
+ const queueName = 'test-queue-retention-02';
43
+ const queue = await client
44
+ .queue(queueName)
45
+ .config({ retentionEnabled: true, maxWaitTimeSeconds: 10 })
46
+ .create()
47
+ if (!queue.configured) {
48
+ return { success: false, message: 'Queue not created with retention enabled' }
49
+ }
50
+ for (let i = 0; i < 100; i++) {
51
+ const res = await client
52
+ .queue(queueName)
53
+ .push([{ data: { message: i } }])
54
+ }
55
+
56
+ await new Promise(resolve => setTimeout(resolve, 15000))
57
+
58
+ const messages = await client
59
+ .queue(queueName)
60
+ .batch(100)
61
+ .wait(false)
62
+ .pop()
63
+
64
+ if (messages.length === 0) {
65
+ return { success: true, message: 'Messages cleaned up' }
66
+ }
67
+
68
+ return { success: false }
69
+ }
@@ -9,6 +9,8 @@ import * as dlqTests from './dlq.js'
9
9
  import * as completeTests from './complete.js'
10
10
  import * as transactionTests from './transaction.js'
11
11
  import * as subscriptionTests from './subscription.js'
12
+ import * as maintenanceTests from './maintenance.js'
13
+ import * as retentionTests from './retention.js'
12
14
  import * as errorHandlingTests from './ai_error_handling.js'
13
15
  import * as leaseRenewalTests from './ai_lease_renewal.js'
14
16
  import * as resourcesTests from './ai_resources.js'
@@ -17,6 +19,7 @@ import * as priorityTests from './ai_priority.js'
17
19
  import * as ttlRetentionTests from './ai_ttl_retention.js'
18
20
  import * as mixedScenariosTests from './ai_mixed_scenarios.js'
19
21
 
22
+
20
23
  // Test configuration
21
24
  export const TEST_CONFIG = {
22
25
  baseUrls: ['http://localhost:6632'],
@@ -92,7 +95,9 @@ async function main() {
92
95
  dlqTests,
93
96
  completeTests,
94
97
  transactionTests,
95
- subscriptionTests
98
+ subscriptionTests,
99
+ retentionTests,
100
+ maintenanceTests
96
101
  ]
97
102
 
98
103
  const aiTests = [
@@ -155,6 +160,7 @@ async function main() {
155
160
 
156
161
  for (const test of testsToRun) {
157
162
  try {
163
+ console.log('Running test:', test.name)
158
164
  const result = await test(client)
159
165
  const message = result.message || 'Test completed successfully'
160
166
  addRestResult(result.success, test.name, message)
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "queen-mq",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "High-performance C++ message queue backed by PostgreSQL",
6
- "main": "client-js/client/index.js",
6
+ "main": "client-js/client-v2/index.js",
7
7
  "scripts": {
8
8
  "start": "./server/bin/queen-server",
9
9
  "build:webapp": "cd webapp && npm install && npm run build",