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,305 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Multi-Server Load Balancing Test
5
+ *
6
+ * This script tests the Queen client with multiple servers using different
7
+ * load balancing strategies (ROUND_ROBIN and SESSION).
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/multi-server-test.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
+ // Test configuration
26
+ const TEST_QUEUE = 'multi-server-test';
27
+ const TEST_NAMESPACE = 'load-balancing';
28
+
29
+ // Helper to log with timestamp
30
+ const log = (message, type = 'info') => {
31
+ const timestamp = new Date().toISOString();
32
+ const prefix = type === 'error' ? 'āŒ' : type === 'success' ? 'āœ…' : 'ā„¹ļø';
33
+ console.log(`[${timestamp}] ${prefix} ${message}`);
34
+ };
35
+
36
+ // Test Round Robin strategy
37
+ async function testRoundRobinStrategy() {
38
+ log('Testing ROUND_ROBIN load balancing strategy', 'info');
39
+
40
+ const client = createQueenClient({
41
+ baseUrls: servers,
42
+ loadBalancingStrategy: LoadBalancingStrategy.ROUND_ROBIN,
43
+ enableFailover: true
44
+ });
45
+
46
+ // Configure the queue
47
+ await client.configure({
48
+ queue: TEST_QUEUE,
49
+ namespace: TEST_NAMESPACE,
50
+ task: 'round-robin-test',
51
+ options: {
52
+ maxRetries: 3,
53
+ retryDelay: 1000
54
+ }
55
+ });
56
+
57
+ // Push multiple messages to see round-robin distribution
58
+ const messages = [];
59
+ for (let i = 1; i <= 10; i++) {
60
+ messages.push({
61
+ queue: TEST_QUEUE,
62
+ partition: 'Default',
63
+ payload: {
64
+ id: i,
65
+ strategy: 'round-robin',
66
+ timestamp: Date.now()
67
+ }
68
+ });
69
+ }
70
+
71
+ log(`Pushing ${messages.length} messages with ROUND_ROBIN strategy`);
72
+ const pushResult = await client.push({ items: messages });
73
+ log(`Pushed messages: ${JSON.stringify(pushResult)}`, 'success');
74
+
75
+ // Get load balancer stats
76
+ const stats = client.getLoadBalancerStats();
77
+ log(`Load balancer stats: ${JSON.stringify(stats, null, 2)}`);
78
+
79
+ // Pop messages to verify they were stored
80
+ log('Popping messages to verify...');
81
+ const popResult = await client.pop({
82
+ queue: TEST_QUEUE,
83
+ batch: 5,
84
+ wait: false
85
+ });
86
+
87
+ if (popResult.messages && popResult.messages.length > 0) {
88
+ log(`Retrieved ${popResult.messages.length} messages`, 'success');
89
+
90
+ // Acknowledge messages
91
+ for (const msg of popResult.messages) {
92
+ await client.ack(msg.transactionId, 'completed');
93
+ }
94
+ log('Messages acknowledged', 'success');
95
+ } else {
96
+ log('No messages retrieved', 'warning');
97
+ }
98
+
99
+ return client;
100
+ }
101
+
102
+ // Test Session affinity strategy
103
+ async function testSessionStrategy() {
104
+ log('\nTesting SESSION load balancing strategy', 'info');
105
+
106
+ const client = createQueenClient({
107
+ baseUrls: servers,
108
+ loadBalancingStrategy: LoadBalancingStrategy.SESSION,
109
+ enableFailover: true
110
+ });
111
+
112
+ // Configure the queue
113
+ await client.configure({
114
+ queue: TEST_QUEUE,
115
+ namespace: TEST_NAMESPACE,
116
+ task: 'session-test',
117
+ options: {
118
+ maxRetries: 3,
119
+ retryDelay: 1000
120
+ }
121
+ });
122
+
123
+ // Push multiple messages - should all go to the same server
124
+ const messages = [];
125
+ for (let i = 1; i <= 10; i++) {
126
+ messages.push({
127
+ queue: TEST_QUEUE,
128
+ partition: 'Session',
129
+ payload: {
130
+ id: i,
131
+ strategy: 'session',
132
+ timestamp: Date.now()
133
+ }
134
+ });
135
+ }
136
+
137
+ log(`Pushing ${messages.length} messages with SESSION strategy`);
138
+ const pushResult = await client.push({ items: messages });
139
+ log(`Pushed messages: ${JSON.stringify(pushResult)}`, 'success');
140
+
141
+ // Get load balancer stats
142
+ const stats = client.getLoadBalancerStats();
143
+ log(`Load balancer stats: ${JSON.stringify(stats, null, 2)}`);
144
+
145
+ // Pop messages to verify they were stored
146
+ log('Popping messages to verify...');
147
+ const popResult = await client.pop({
148
+ queue: TEST_QUEUE,
149
+ partition: 'Session',
150
+ batch: 5,
151
+ wait: false
152
+ });
153
+
154
+ if (popResult.messages && popResult.messages.length > 0) {
155
+ log(`Retrieved ${popResult.messages.length} messages from session-affinity server`, 'success');
156
+
157
+ // Acknowledge messages
158
+ for (const msg of popResult.messages) {
159
+ await client.ack(msg.transactionId, 'completed');
160
+ }
161
+ log('Messages acknowledged', 'success');
162
+ } else {
163
+ log('No messages retrieved', 'warning');
164
+ }
165
+
166
+ return client;
167
+ }
168
+
169
+ // Test failover behavior
170
+ async function testFailover() {
171
+ log('\nTesting failover behavior (requires one server to be down)', 'info');
172
+
173
+ const client = createQueenClient({
174
+ baseUrls: [
175
+ 'http://localhost:9999', // Non-existent server
176
+ 'http://localhost:6632' // Working server
177
+ ],
178
+ loadBalancingStrategy: LoadBalancingStrategy.ROUND_ROBIN,
179
+ enableFailover: true,
180
+ timeout: 5000 // Shorter timeout for testing
181
+ });
182
+
183
+ try {
184
+ // Try to push a message - should failover to working server
185
+ log('Attempting to push message with one server down...');
186
+ const result = await client.push({
187
+ items: [{
188
+ queue: TEST_QUEUE,
189
+ partition: 'Failover',
190
+ payload: { test: 'failover', timestamp: Date.now() }
191
+ }]
192
+ });
193
+
194
+ log('Message pushed successfully via failover!', 'success');
195
+ log(`Result: ${JSON.stringify(result)}`);
196
+
197
+ // Get stats to see which server was used
198
+ const stats = client.getLoadBalancerStats();
199
+ log(`Load balancer stats after failover: ${JSON.stringify(stats, null, 2)}`);
200
+
201
+ } catch (error) {
202
+ log(`Failover test failed: ${error.message}`, 'error');
203
+ }
204
+
205
+ return client;
206
+ }
207
+
208
+ // Test WebSocket connections with multiple servers
209
+ async function testWebSocketConnections() {
210
+ log('\nTesting WebSocket connections with multiple servers', 'info');
211
+
212
+ const client = createQueenClient({
213
+ baseUrls: servers,
214
+ loadBalancingStrategy: LoadBalancingStrategy.ROUND_ROBIN
215
+ });
216
+
217
+ // Create WebSocket connections to different servers
218
+ log('Creating WebSocket connections...');
219
+
220
+ const ws1 = client.createWebSocketConnection('/ws/dashboard');
221
+ log(`WebSocket 1 connected to: ${ws1.serverUrl}`);
222
+
223
+ const ws2 = client.createWebSocketConnection('/ws/dashboard');
224
+ log(`WebSocket 2 connected to: ${ws2.serverUrl}`);
225
+
226
+ // Set up message handlers
227
+ ws1.onMessage((data) => {
228
+ log(`WS1 received: ${JSON.stringify(data)}`);
229
+ });
230
+
231
+ ws2.onMessage((data) => {
232
+ log(`WS2 received: ${JSON.stringify(data)}`);
233
+ });
234
+
235
+ // Keep connections open for a bit
236
+ await new Promise(resolve => setTimeout(resolve, 2000));
237
+
238
+ // Close connections
239
+ ws1.close();
240
+ ws2.close();
241
+ log('WebSocket connections closed', 'success');
242
+
243
+ return client;
244
+ }
245
+
246
+ // Test single server backward compatibility
247
+ async function testBackwardCompatibility() {
248
+ log('\nTesting backward compatibility with single baseUrl', 'info');
249
+
250
+ // Old style - single baseUrl
251
+ const client = createQueenClient({
252
+ baseUrl: 'http://localhost:6632',
253
+ timeout: 30000
254
+ });
255
+
256
+ try {
257
+ const result = await client.push({
258
+ items: [{
259
+ queue: TEST_QUEUE,
260
+ partition: 'Compatibility',
261
+ payload: { test: 'backward-compatibility', timestamp: Date.now() }
262
+ }]
263
+ });
264
+
265
+ log('Backward compatibility test passed!', 'success');
266
+ log(`Result: ${JSON.stringify(result)}`);
267
+
268
+ // This should return null for single-server mode
269
+ const stats = client.getLoadBalancerStats();
270
+ log(`Load balancer stats (should be null): ${stats}`);
271
+
272
+ } catch (error) {
273
+ log(`Backward compatibility test failed: ${error.message}`, 'error');
274
+ }
275
+
276
+ return client;
277
+ }
278
+
279
+ // Main test runner
280
+ async function runTests() {
281
+ console.log('=' .repeat(80));
282
+ console.log('Queen Multi-Server Load Balancing Test Suite');
283
+ console.log('=' .repeat(80));
284
+
285
+ try {
286
+ // Test different strategies
287
+ await testRoundRobinStrategy();
288
+ await testSessionStrategy();
289
+ await testFailover();
290
+ await testWebSocketConnections();
291
+ await testBackwardCompatibility();
292
+
293
+ console.log('\n' + '=' .repeat(80));
294
+ log('All tests completed!', 'success');
295
+ console.log('=' .repeat(80));
296
+
297
+ } catch (error) {
298
+ log(`Test suite failed: ${error.message}`, 'error');
299
+ console.error(error);
300
+ process.exit(1);
301
+ }
302
+ }
303
+
304
+ // Run the tests
305
+ runTests().catch(console.error);
@@ -0,0 +1,64 @@
1
+ import { createQueenClient } from '../src/client/index.js';
2
+
3
+ const client = createQueenClient({
4
+ baseUrl: process.env.QUEEN_URL || 'http://localhost:6632',
5
+ timeout: 10000 // 10 second timeout for operations
6
+ });
7
+
8
+ // V2: Using queue and partition structure
9
+ const queue = 'smartchat-agent';
10
+ const partition = 'id-1'; // Can use partition to separate different agents
11
+
12
+ async function consumer () {
13
+ // Start consuming messages from the specific partition
14
+ const stop = client.consume({
15
+ queue: queue,
16
+ partition: partition, // Optional: remove to consume from any partition
17
+ handler: async (message) => {
18
+ console.log(`Consumed message from ${message.partition}: ${message.data.data}`);
19
+ },
20
+ options: {
21
+ wait: true,
22
+ timeout: 10000
23
+ }
24
+ });
25
+
26
+ // Return stop function for cleanup if needed
27
+ return stop;
28
+ }
29
+
30
+ let count = 0;
31
+ async function producer () {
32
+ const messages = [{
33
+ queue: queue,
34
+ partition: partition, // Optional: defaults to "Default"
35
+ payload: {
36
+ id: count,
37
+ data: `Hello, world! ${count++}`,
38
+ timestamp: new Date().toISOString()
39
+ }
40
+ }];
41
+
42
+ const result = await client.push({
43
+ items: messages
44
+ });
45
+ console.log(`Pushed ${result.messages.length} messages to ${queue}/${partition}`);
46
+ }
47
+
48
+ // Start consumer
49
+ consumer().then(stop => {
50
+ // Handle graceful shutdown
51
+ process.on('SIGINT', () => {
52
+ console.log('\nStopping consumer...');
53
+ stop();
54
+ process.exit(0);
55
+ });
56
+ });
57
+
58
+ // Start producer loop
59
+ (async () => {
60
+ while (true) {
61
+ await producer();
62
+ await new Promise(resolve => setTimeout(resolve, 10000));
63
+ }
64
+ })();
@@ -0,0 +1,42 @@
1
+ // Create client
2
+ import fs from 'fs';
3
+ import { createQueenClient } from '../src/client/index.js';
4
+
5
+ const client = createQueenClient({
6
+ baseUrl: 'http://localhost:6632'
7
+ });
8
+
9
+ // Configure a queue with delayed processing
10
+ await client.configure({
11
+ queue: 'htmls-delayed-02',
12
+ // partition parameter removed - all config is queue-level now
13
+ options: {
14
+ windowBuffer: 10,
15
+ //delayedProcessing: 10, // Don't process messages until 60 seconds old
16
+ leaseTime: 300,
17
+ retryLimit: 3
18
+ }
19
+ })
20
+
21
+ for (let i = 0; i < 10; i++) {
22
+ await client.push({
23
+ items: [{
24
+ queue: 'htmls-delayed-02',
25
+ partition: 'html-02', // Use 'Default' instead of 0
26
+ payload: { to: 'user-' + i + '@example.com' },
27
+ }]
28
+ })
29
+ }
30
+
31
+ const stop = await client.consume({
32
+ queue: 'htmls-delayed-02',
33
+ partition: 'html-02', // Use 'Default' instead of 0
34
+ handler: async (message) => {
35
+ console.log(message)
36
+ },
37
+ options: {
38
+ wait: true,
39
+ timeout: 30000,
40
+ batch: 5
41
+ }
42
+ })
@@ -0,0 +1,52 @@
1
+ // Create client
2
+ import fs from 'fs';
3
+ import { createQueenClient } from '../src/client/index.js';
4
+
5
+ const client = createQueenClient({
6
+ baseUrl: 'http://localhost:6632'
7
+ });
8
+
9
+ const numberOfConsumers = 10
10
+
11
+ async function producer () {
12
+ let count = 0;
13
+ while (true) {
14
+ const partition = count % 2;
15
+ console.log('Producing to partition:', partition);
16
+ const items = [];
17
+ for (let i = 0; i < 100; i++) {
18
+ items.push({ queue: 'htmls', partition: partition, payload: { to: 'user@example.com-' + count++ } });
19
+ }
20
+ await client.push({
21
+ items: items
22
+ })
23
+ await new Promise(resolve => setTimeout(resolve, 50));
24
+ }
25
+ }
26
+
27
+ async function consumer (i) {
28
+ const stop = client.consume({
29
+ queue: 'htmls',
30
+ consumerGroup: 'smartchat-' + i,
31
+ handler: async (message) => {
32
+ console.log(i, 'Processing:', message.data);
33
+ //fs.appendFileSync('consumed_items ' + message.partition + '.json', JSON.stringify(message.data) + '\n');
34
+ },
35
+ options: {
36
+ batch: 100, // Process up to 10 messages at a time
37
+ wait: true, // Wait for messages if queue is empty
38
+ timeout: 30000 // Timeout for long polling
39
+ }
40
+ });
41
+
42
+ return stop;
43
+ }
44
+
45
+
46
+ if (process.argv[2] === 'producer') {
47
+ producer();
48
+ } else {
49
+ for (let i = 0; i < numberOfConsumers; i++) {
50
+ consumer(i);
51
+ }
52
+ }
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Example: Cache Invalidation with System Events
4
+ *
5
+ * This example demonstrates how the system event propagation
6
+ * ensures cache consistency across multiple server instances.
7
+ */
8
+
9
+ import { createQueenClient } from '../src/client/queenClient.js';
10
+
11
+ // Simulate two different server instances
12
+ const server1 = createQueenClient({ baseUrl: 'http://localhost:6632' });
13
+ const server2 = createQueenClient({ baseUrl: 'http://localhost:6633' }); // Would be a different port in production
14
+
15
+ async function demonstrateCacheInvalidation() {
16
+ console.log('šŸ“Š Demonstrating Cache Invalidation with System Events\n');
17
+ console.log('=' .repeat(60));
18
+
19
+ const queueName = 'cache-test-queue';
20
+
21
+ try {
22
+ // Step 1: Server 1 creates a queue
23
+ console.log('\n1ļøāƒ£ Server 1 creates a queue with initial configuration:');
24
+ await server1.configure({
25
+ queue: queueName,
26
+ options: {
27
+ ttl: 300,
28
+ retryLimit: 3,
29
+ priority: 5
30
+ }
31
+ });
32
+ console.log(' āœ… Queue created with TTL=300, retryLimit=3, priority=5');
33
+
34
+ // Step 2: Both servers use the queue
35
+ console.log('\n2ļøāƒ£ Both servers push messages to the queue:');
36
+
37
+ await server1.push({
38
+ queue: queueName,
39
+ messages: [{ id: 'msg-from-server1', payload: { source: 'server1' } }]
40
+ });
41
+ console.log(' āœ… Server 1 pushed a message (uses cached config)');
42
+
43
+ // In a real multi-server setup, server2 would have its own cache
44
+ // For this demo, we're using the same server but the principle applies
45
+ await server2.push({
46
+ queue: queueName,
47
+ messages: [{ id: 'msg-from-server2', payload: { source: 'server2' } }]
48
+ });
49
+ console.log(' āœ… Server 2 pushed a message (fetches and caches config)');
50
+
51
+ // Step 3: Server 1 updates the queue configuration
52
+ console.log('\n3ļøāƒ£ Server 1 updates queue configuration:');
53
+ await server1.configure({
54
+ queue: queueName,
55
+ options: {
56
+ ttl: 600, // Changed from 300
57
+ retryLimit: 5, // Changed from 3
58
+ priority: 10 // Changed from 5
59
+ }
60
+ });
61
+ console.log(' āœ… Configuration updated: TTL=600, retryLimit=5, priority=10');
62
+ console.log(' šŸ“Ø System event emitted to propagate changes');
63
+
64
+ // Step 4: Wait a moment for event propagation
65
+ console.log('\n4ļøāƒ£ Waiting for system event propagation...');
66
+ await new Promise(resolve => setTimeout(resolve, 100));
67
+
68
+ // Step 5: Both servers use the queue again
69
+ console.log('\n5ļøāƒ£ Both servers push messages again:');
70
+
71
+ await server1.push({
72
+ queue: queueName,
73
+ messages: [{ id: 'msg2-from-server1', payload: { after: 'update' } }]
74
+ });
75
+ console.log(' āœ… Server 1 uses new config (immediate local update)');
76
+
77
+ await server2.push({
78
+ queue: queueName,
79
+ messages: [{ id: 'msg2-from-server2', payload: { after: 'update' } }]
80
+ });
81
+ console.log(' āœ… Server 2 uses new config (cache invalidated by event)');
82
+
83
+ // Step 6: Verify the queue stats
84
+ console.log('\n6ļøāƒ£ Verifying queue statistics:');
85
+ const stats = await server1.getQueueStats(queueName);
86
+ console.log(` šŸ“Š Total messages: ${stats.total}`);
87
+ console.log(` šŸ“Š Pending: ${stats.pending}`);
88
+
89
+ // Step 7: Check system events queue
90
+ console.log('\n7ļøāƒ£ Checking system events queue:');
91
+ try {
92
+ const systemStats = await server1.getQueueStats('__system_events__');
93
+ console.log(` šŸ“Š System events processed: ${systemStats.completed || 0}`);
94
+ console.log(` šŸ“Š System events pending: ${systemStats.pending || 0}`);
95
+ } catch (error) {
96
+ console.log(' ā„¹ļø System queue may not have stats if events were already consumed');
97
+ }
98
+
99
+ console.log('\n' + '=' .repeat(60));
100
+ console.log('✨ Cache invalidation demonstration completed!');
101
+ console.log('\nšŸ’” Key Points:');
102
+ console.log(' • Configuration changes trigger system events');
103
+ console.log(' • Events are propagated to all server instances');
104
+ console.log(' • Each server invalidates its cache upon receiving events');
105
+ console.log(' • This ensures consistency across the cluster');
106
+
107
+ } catch (error) {
108
+ console.error('āŒ Error:', error.message);
109
+ process.exit(1);
110
+ }
111
+ }
112
+
113
+ // Run the demonstration
114
+ demonstrateCacheInvalidation().then(() => {
115
+ process.exit(0);
116
+ }).catch(error => {
117
+ console.error('Fatal error:', error);
118
+ process.exit(1);
119
+ });