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,343 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Continuous Producer Example for Queen V2
5
+ *
6
+ * This example demonstrates a continuous message producer that pushes
7
+ * messages to a Queen queue at regular intervals. It's designed to work
8
+ * with the continuous-consumer.js example to demonstrate the full
9
+ * message flow with long polling.
10
+ */
11
+
12
+ import { createQueenClient } from '../src/client/index.js';
13
+
14
+ async function main() {
15
+ // Create client with configuration
16
+ const client = createQueenClient({
17
+ baseUrl: process.env.QUEEN_URL || 'http://localhost:6632',
18
+ timeout: 10000 // 10 second timeout for push operations
19
+ });
20
+
21
+ // V2: Using queue and optional partition
22
+ const queue = process.env.QUEUE || 'email-translations';
23
+ const partition = process.env.PARTITION; // Optional: if not set, uses "Default"
24
+ const useMultiplePartitions = process.env.MULTI_PARTITION === 'true';
25
+
26
+ // Production rate configuration
27
+ const messagesPerBatch = parseInt(process.env.BATCH_SIZE) || 100;
28
+ const intervalMilliseconds = parseInt(process.env.INTERVAL) || 10000;
29
+ const burstMode = process.env.BURST === 'true';
30
+ const highPerformanceMode = process.env.HIGH_PERF === 'true';
31
+
32
+ console.log('šŸš€ Starting continuous producer (V2)');
33
+ console.log(`šŸ“¦ Target Queue: ${queue}${partition ? '/' + partition : ''}`);
34
+ console.log(`šŸ“Š Configuration:`);
35
+ console.log(` - Messages per batch: ${messagesPerBatch}`);
36
+ console.log(` - Interval: ${intervalMilliseconds} milliseconds`);
37
+ console.log(` - Burst mode: ${burstMode ? 'enabled' : 'disabled'}`);
38
+ console.log(` - High performance: ${highPerformanceMode ? 'enabled' : 'disabled'}`);
39
+ console.log(` - Multiple partitions: ${useMultiplePartitions ? 'enabled' : 'disabled'}`);
40
+
41
+ if (highPerformanceMode) {
42
+ console.log('\n⚔ HIGH PERFORMANCE MODE ENABLED');
43
+ console.log(' Target: 10,000+ messages/second');
44
+ console.log(' Make sure server is running with: DB_POOL_SIZE=100 node src/server.js');
45
+ }
46
+ console.log('');
47
+
48
+ // Configure the queue
49
+ try {
50
+ await client.configure({
51
+ queue: queue,
52
+ // partition parameter removed - all config is queue-level now
53
+ options: {
54
+ retryLimit: 3,
55
+ retryDelay: 1000,
56
+ dlqAfterMaxRetries: true,
57
+ leaseTime: 300
58
+ }
59
+ });
60
+ console.log('āœ… Queue configured successfully\n');
61
+ } catch (error) {
62
+ console.log('āš ļø Queue configuration failed (may already exist):', error.message, '\n');
63
+ }
64
+
65
+ let stats = {
66
+ batches: 0,
67
+ messages: 0,
68
+ errors: 0,
69
+ startTime: Date.now(),
70
+ lastBatchTime: null,
71
+ partitionCounts: {}
72
+ };
73
+
74
+ // Partition selection for multi-partition mode
75
+ const partitions = useMultiplePartitions
76
+ ? ['Default', 'urgent', 'bulk', 'priority', 'standard']
77
+ : [partition || 'Default'];
78
+
79
+ // Message generator function
80
+ function generateMessages(count) {
81
+ const messages = [];
82
+ const templates = [
83
+ { type: 'welcome', subject: 'Welcome to our service!' },
84
+ { type: 'notification', subject: 'New feature available' },
85
+ { type: 'reminder', subject: 'Don\'t forget to check out...' },
86
+ { type: 'newsletter', subject: 'Weekly updates' },
87
+ { type: 'alert', subject: 'Important information' }
88
+ ];
89
+
90
+ for (let i = 0; i < count; i++) {
91
+ const template = templates[Math.floor(Math.random() * templates.length)];
92
+ const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
93
+
94
+ // Select partition based on message type or random
95
+ let selectedPartition;
96
+ if (useMultiplePartitions) {
97
+ // Route alerts to urgent, newsletters to bulk, etc.
98
+ if (template.type === 'alert') {
99
+ selectedPartition = 'urgent';
100
+ } else if (template.type === 'newsletter') {
101
+ selectedPartition = 'bulk';
102
+ } else if (Math.random() > 0.7) {
103
+ selectedPartition = 'priority';
104
+ } else {
105
+ selectedPartition = partitions[Math.floor(Math.random() * partitions.length)];
106
+ }
107
+ } else {
108
+ selectedPartition = partition || 'Default';
109
+ }
110
+
111
+ messages.push({
112
+ queue: queue,
113
+ partition: selectedPartition,
114
+ payload: {
115
+ id: messageId,
116
+ type: template.type,
117
+ email: {
118
+ to: `user${Math.floor(Math.random() * 1000)}@example.com`,
119
+ subject: template.subject,
120
+ template: template.type,
121
+ variables: {
122
+ userName: `User${Math.floor(Math.random() * 1000)}`,
123
+ timestamp: new Date().toISOString(),
124
+ locale: ['en', 'es', 'fr', 'de'][Math.floor(Math.random() * 4)]
125
+ }
126
+ },
127
+ priority: Math.random() > 0.8 ? 'high' : 'normal',
128
+ createdAt: new Date().toISOString()
129
+ }
130
+ });
131
+
132
+ // Track partition usage
133
+ stats.partitionCounts[selectedPartition] = (stats.partitionCounts[selectedPartition] || 0) + 1;
134
+ }
135
+
136
+ return messages;
137
+ }
138
+
139
+ // Producer function
140
+ async function produceMessages() {
141
+ const batchSize = burstMode
142
+ ? messagesPerBatch * (1 + Math.floor(Math.random() * 3)) // 1x to 3x in burst mode
143
+ : messagesPerBatch;
144
+
145
+ const messages = generateMessages(batchSize);
146
+
147
+ try {
148
+ console.log(`šŸ“¤ [${new Date().toISOString()}] Pushing ${messages.length} messages...`);
149
+
150
+ const result = await client.push({
151
+ items: messages
152
+ });
153
+
154
+ stats.batches++;
155
+ stats.messages += result.messages.length;
156
+ stats.lastBatchTime = Date.now();
157
+
158
+ console.log(`āœ… Successfully pushed ${result.messages.length} messages`);
159
+
160
+ if (useMultiplePartitions) {
161
+ // Show partition distribution for this batch
162
+ const batchPartitions = {};
163
+ messages.forEach(m => {
164
+ batchPartitions[m.partition] = (batchPartitions[m.partition] || 0) + 1;
165
+ });
166
+ console.log(` Partition distribution:`, batchPartitions);
167
+ }
168
+
169
+ console.log(` Sample IDs: ${result.messages.slice(0, 3).map(m => m.transactionId).join(', ')}${result.messages.length > 3 ? '...' : ''}`);
170
+
171
+ // Show sample message details occasionally
172
+ if (stats.batches % 5 === 0) {
173
+ console.log(` Sample payload:`, JSON.stringify(messages[0].payload, null, 2));
174
+ }
175
+
176
+ } catch (error) {
177
+ stats.errors++;
178
+ console.error(`āŒ Failed to push messages:`, error.message);
179
+ }
180
+ }
181
+
182
+ // Start producing messages
183
+ console.log('šŸ Starting production cycle...\n');
184
+
185
+ if (highPerformanceMode) {
186
+ // High performance mode: Multiple concurrent producers
187
+ const concurrentProducers = 10;
188
+ const highPerfBatchSize = 1000;
189
+ const highPerfInterval = 100; // 10 producers * 1000 msgs * 10/sec = 100,000 msgs/sec theoretical max
190
+
191
+ console.log(`⚔ Running ${concurrentProducers} concurrent producers`);
192
+ console.log(`⚔ Each producing ${highPerfBatchSize} messages every ${highPerfInterval}ms`);
193
+ console.log(`⚔ Theoretical max: ${(concurrentProducers * highPerfBatchSize * (1000/highPerfInterval)).toFixed(0)} messages/second\n`);
194
+
195
+ // Start multiple concurrent producers
196
+ const producers = [];
197
+ for (let i = 0; i < concurrentProducers; i++) {
198
+ const producerId = i;
199
+
200
+ // Stagger start times to avoid thundering herd
201
+ setTimeout(() => {
202
+ const interval = setInterval(async () => {
203
+ const messages = generateMessages(highPerfBatchSize);
204
+
205
+ try {
206
+ const start = Date.now();
207
+ const result = await client.push({ items: messages });
208
+ const duration = Date.now() - start;
209
+
210
+ stats.batches++;
211
+ stats.messages += result.messages.length;
212
+ stats.lastBatchTime = Date.now();
213
+
214
+ if (stats.batches % 10 === 0) {
215
+ console.log(`⚔ [Producer ${producerId}] Pushed ${result.messages.length} messages in ${duration}ms`);
216
+ }
217
+ } catch (error) {
218
+ stats.errors++;
219
+ console.error(`āŒ [Producer ${producerId}] Failed:`, error.message);
220
+ }
221
+ }, highPerfInterval);
222
+
223
+ producers.push(interval);
224
+ }, i * 10); // Stagger by 10ms
225
+ }
226
+
227
+ // Store intervals for cleanup
228
+ globalThis.highPerfProducers = producers;
229
+ } else {
230
+ // Normal mode: Single producer
231
+ // Initial batch
232
+ await produceMessages();
233
+
234
+ // Set up interval for continuous production
235
+ const productionInterval = setInterval(async () => {
236
+ await produceMessages();
237
+ }, intervalMilliseconds);
238
+
239
+ globalThis.productionInterval = productionInterval;
240
+ }
241
+
242
+ // Statistics reporting
243
+ const statsInterval = setInterval(() => {
244
+ const uptime = Math.floor((Date.now() - stats.startTime) / 1000);
245
+ const rate = stats.messages / (uptime / 60);
246
+
247
+ console.log('\nšŸ“Š Production Statistics:');
248
+ console.log(` Uptime: ${formatUptime(uptime)}`);
249
+ console.log(` Batches sent: ${stats.batches}`);
250
+ console.log(` Messages sent: ${stats.messages}`);
251
+ console.log(` Production rate: ${rate.toFixed(2)} msg/min`);
252
+ console.log(` Errors: ${stats.errors}`);
253
+
254
+ if (useMultiplePartitions && Object.keys(stats.partitionCounts).length > 0) {
255
+ console.log(' Total partition distribution:');
256
+ for (const [part, count] of Object.entries(stats.partitionCounts)) {
257
+ const percentage = ((count / stats.messages) * 100).toFixed(1);
258
+ console.log(` ${part}: ${count} messages (${percentage}%)`);
259
+ }
260
+ }
261
+
262
+ if (stats.lastBatchTime) {
263
+ const timeSinceLastBatch = Math.floor((Date.now() - stats.lastBatchTime) / 1000);
264
+ console.log(` Time since last batch: ${timeSinceLastBatch}s`);
265
+ }
266
+ console.log('');
267
+ }, 60000);
268
+
269
+ // Handle shutdown
270
+ process.on('SIGINT', () => {
271
+ console.log('\n\nšŸ›‘ Shutting down producer...');
272
+
273
+ // Clear intervals based on mode
274
+ if (globalThis.highPerfProducers) {
275
+ globalThis.highPerfProducers.forEach(interval => clearInterval(interval));
276
+ }
277
+ if (globalThis.productionInterval) {
278
+ clearInterval(globalThis.productionInterval);
279
+ }
280
+ clearInterval(statsInterval);
281
+
282
+ const uptime = Math.floor((Date.now() - stats.startTime) / 1000);
283
+ console.log('\nšŸ“ˆ Final Production Statistics:');
284
+ console.log(` Total runtime: ${formatUptime(uptime)}`);
285
+ console.log(` Total batches: ${stats.batches}`);
286
+ console.log(` Total messages: ${stats.messages}`);
287
+ console.log(` Average batch size: ${(stats.messages / stats.batches).toFixed(1)}`);
288
+ console.log(` Overall rate: ${(stats.messages / (uptime / 60)).toFixed(2)} msg/min`);
289
+ console.log(` Errors encountered: ${stats.errors}`);
290
+ console.log(` Success rate: ${((1 - stats.errors / stats.batches) * 100).toFixed(1)}%`);
291
+
292
+ if (useMultiplePartitions && Object.keys(stats.partitionCounts).length > 0) {
293
+ console.log('\n Final partition distribution:');
294
+ for (const [part, count] of Object.entries(stats.partitionCounts)) {
295
+ const percentage = ((count / stats.messages) * 100).toFixed(1);
296
+ console.log(` ${part}: ${count} messages (${percentage}%)`);
297
+ }
298
+ }
299
+
300
+ process.exit(0);
301
+ });
302
+
303
+ // Simulate varying load (optional - only in normal mode)
304
+ if (process.env.VARY_LOAD === 'true' && !highPerformanceMode) {
305
+ console.log('šŸ“ˆ Variable load mode enabled - production rate will vary\n');
306
+
307
+ setInterval(() => {
308
+ // Randomly adjust the production rate
309
+ const factor = 0.5 + Math.random() * 2; // 0.5x to 2.5x
310
+ const newInterval = Math.floor(intervalMilliseconds * factor);
311
+
312
+ if (globalThis.productionInterval) {
313
+ clearInterval(globalThis.productionInterval);
314
+ globalThis.productionInterval = setInterval(async () => {
315
+ await produceMessages();
316
+ }, newInterval);
317
+
318
+ console.log(`šŸ”„ Production rate adjusted: new interval ${newInterval}ms`);
319
+ }
320
+ }, 120000); // Adjust every 2 minutes
321
+ }
322
+
323
+ console.log('āœ… Producer started. Press Ctrl+C to stop.\n');
324
+ }
325
+
326
+ function formatUptime(seconds) {
327
+ const hours = Math.floor(seconds / 3600);
328
+ const minutes = Math.floor((seconds % 3600) / 60);
329
+ const secs = seconds % 60;
330
+
331
+ const parts = [];
332
+ if (hours > 0) parts.push(`${hours}h`);
333
+ if (minutes > 0) parts.push(`${minutes}m`);
334
+ parts.push(`${secs}s`);
335
+
336
+ return parts.join(' ');
337
+ }
338
+
339
+ // Run the producer
340
+ main().catch(error => {
341
+ console.error('āŒ Fatal error:', error);
342
+ process.exit(1);
343
+ });
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Mixed Mode Example for Queen V3
5
+ *
6
+ * This example demonstrates how the SAME queue can serve both:
7
+ * 1. Competing consumers (Queue Mode) - workers compete for messages
8
+ * 2. Consumer groups (Bus Mode) - each group gets all messages
9
+ *
10
+ * This is a unique feature that makes Queen more flexible than traditional
11
+ * message queue systems.
12
+ */
13
+
14
+ import { createQueenClient } from '../src/client/index.js';
15
+
16
+ async function main() {
17
+ const client = createQueenClient({
18
+ baseUrl: process.env.QUEEN_URL || 'http://localhost:6632',
19
+ timeout: 35000
20
+ });
21
+
22
+ const queue = 'orders';
23
+
24
+ console.log('šŸš€ Starting Mixed Mode Example');
25
+ console.log('šŸ“¦ Queue:', queue);
26
+ console.log('');
27
+ console.log('This example demonstrates the SAME queue serving:');
28
+ console.log(' • 3 Workers in Queue Mode (competing for messages)');
29
+ console.log(' • 2 Consumer Groups in Bus Mode (each gets all messages)');
30
+ console.log('');
31
+
32
+ // Track message processing
33
+ const processed = {
34
+ workers: {
35
+ worker1: [],
36
+ worker2: [],
37
+ worker3: []
38
+ },
39
+ groups: {
40
+ analytics: [],
41
+ audit: []
42
+ }
43
+ };
44
+
45
+ // Start 3 competing workers (Queue Mode - no consumer group)
46
+ console.log('šŸ‘· Starting 3 competing workers (Queue Mode):');
47
+
48
+ const worker1 = client.consume({
49
+ queue,
50
+ // No consumerGroup = Queue Mode (competing consumers)
51
+ handler: async (message) => {
52
+ console.log(` [Worker 1] Processing order ${message.data.orderId}`);
53
+ processed.workers.worker1.push(message.data.orderId);
54
+ await new Promise(resolve => setTimeout(resolve, 200));
55
+ },
56
+ options: { batch: 1, wait: false }
57
+ });
58
+
59
+ const worker2 = client.consume({
60
+ queue,
61
+ // No consumerGroup = Queue Mode
62
+ handler: async (message) => {
63
+ console.log(` [Worker 2] Processing order ${message.data.orderId}`);
64
+ processed.workers.worker2.push(message.data.orderId);
65
+ await new Promise(resolve => setTimeout(resolve, 150));
66
+ },
67
+ options: { batch: 1, wait: false }
68
+ });
69
+
70
+ const worker3 = client.consume({
71
+ queue,
72
+ // No consumerGroup = Queue Mode
73
+ handler: async (message) => {
74
+ console.log(` [Worker 3] Processing order ${message.data.orderId}`);
75
+ processed.workers.worker3.push(message.data.orderId);
76
+ await new Promise(resolve => setTimeout(resolve, 100));
77
+ },
78
+ options: { batch: 1, wait: false }
79
+ });
80
+
81
+ console.log('āœ… Workers started\n');
82
+
83
+ // Start 2 consumer groups (Bus Mode - each gets all messages)
84
+ console.log('šŸ“Š Starting 2 consumer groups (Bus Mode):');
85
+
86
+ const analyticsConsumer = client.consume({
87
+ queue,
88
+ consumerGroup: 'analytics', // With consumerGroup = Bus Mode
89
+ handler: async (message) => {
90
+ console.log(` [Analytics Group] Analyzing order ${message.data.orderId}`);
91
+ processed.groups.analytics.push(message.data.orderId);
92
+ await new Promise(resolve => setTimeout(resolve, 50));
93
+ },
94
+ options: { batch: 2, wait: false }
95
+ });
96
+
97
+ const auditConsumer = client.consume({
98
+ queue,
99
+ consumerGroup: 'audit-log', // With consumerGroup = Bus Mode
100
+ handler: async (message) => {
101
+ console.log(` [Audit Group] Logging order ${message.data.orderId}`);
102
+ processed.groups.audit.push(message.data.orderId);
103
+ await new Promise(resolve => setTimeout(resolve, 30));
104
+ },
105
+ options: { batch: 3, wait: false }
106
+ });
107
+
108
+ console.log('āœ… Consumer groups started\n');
109
+
110
+ // Push test orders
111
+ console.log('šŸ“¤ Pushing 10 test orders...\n');
112
+ const orders = [];
113
+ for (let i = 1; i <= 10; i++) {
114
+ orders.push({
115
+ queue,
116
+ payload: {
117
+ orderId: i,
118
+ amount: Math.floor(Math.random() * 1000) + 100,
119
+ customer: `customer-${i}`,
120
+ timestamp: new Date().toISOString()
121
+ }
122
+ });
123
+ }
124
+
125
+ await client.push({ items: orders });
126
+
127
+ // Wait for processing
128
+ console.log('ā³ Processing messages...\n');
129
+ await new Promise(resolve => setTimeout(resolve, 4000));
130
+
131
+ // Stop all consumers
132
+ worker1();
133
+ worker2();
134
+ worker3();
135
+ analyticsConsumer();
136
+ auditConsumer();
137
+
138
+ // Display results
139
+ console.log('\n\nšŸ“Š RESULTS');
140
+ console.log('═══════════════════════════════════════════════════════');
141
+
142
+ console.log('\nšŸ”§ QUEUE MODE (Competing Workers):');
143
+ console.log('───────────────────────────────────');
144
+ console.log(`Worker 1 processed: [${processed.workers.worker1.sort((a,b) => a-b).join(', ')}]`);
145
+ console.log(`Worker 2 processed: [${processed.workers.worker2.sort((a,b) => a-b).join(', ')}]`);
146
+ console.log(`Worker 3 processed: [${processed.workers.worker3.sort((a,b) => a-b).join(', ')}]`);
147
+
148
+ const totalWorkerMessages =
149
+ processed.workers.worker1.length +
150
+ processed.workers.worker2.length +
151
+ processed.workers.worker3.length;
152
+
153
+ console.log(`\nTotal messages processed by workers: ${totalWorkerMessages}`);
154
+ console.log('āœ… Each message was processed by ONLY ONE worker (competing)');
155
+
156
+ console.log('\nšŸ“” BUS MODE (Consumer Groups):');
157
+ console.log('───────────────────────────────────');
158
+ console.log(`Analytics group received: [${processed.groups.analytics.sort((a,b) => a-b).join(', ')}]`);
159
+ console.log(`Audit group received: [${processed.groups.audit.sort((a,b) => a-b).join(', ')}]`);
160
+
161
+ console.log(`\nTotal messages received by Analytics: ${processed.groups.analytics.length}`);
162
+ console.log(`Total messages received by Audit: ${processed.groups.audit.length}`);
163
+ console.log('āœ… Each consumer group received ALL messages (broadcast)');
164
+
165
+ // Verify the behavior
166
+ console.log('\n\nšŸŽÆ KEY INSIGHTS:');
167
+ console.log('════════════════════════════════════════════════');
168
+
169
+ // Check if workers competed correctly
170
+ const workerOrderIds = new Set([
171
+ ...processed.workers.worker1,
172
+ ...processed.workers.worker2,
173
+ ...processed.workers.worker3
174
+ ]);
175
+
176
+ if (workerOrderIds.size === totalWorkerMessages && totalWorkerMessages === 10) {
177
+ console.log('āœ… Workers competed correctly: Each order processed ONCE across all workers');
178
+ }
179
+
180
+ // Check if consumer groups got all messages
181
+ if (processed.groups.analytics.length === 10 && processed.groups.audit.length === 10) {
182
+ console.log('āœ… Consumer groups worked correctly: Each group got ALL orders');
183
+ }
184
+
185
+ console.log('\nšŸ“ Summary:');
186
+ console.log(' • The SAME queue served both patterns simultaneously');
187
+ console.log(' • Workers (no consumer group) → competed for messages');
188
+ console.log(' • Consumer groups → each received all messages');
189
+ console.log(' • This flexibility is unique to Queen!');
190
+ }
191
+
192
+ // Demonstrate a real-world scenario
193
+ async function realWorldScenario() {
194
+ console.log('\n\nšŸ¢ REAL-WORLD SCENARIO');
195
+ console.log('════════════════════════════════════════════════');
196
+ console.log('E-commerce Order Processing System:');
197
+ console.log(' • Order Processing Workers (Queue Mode) - handle the actual order');
198
+ console.log(' • Analytics Service (Bus Mode) - analyzes all orders');
199
+ console.log(' • Notification Service (Bus Mode) - sends notifications for all orders');
200
+ console.log(' • Audit Service (Bus Mode) - logs all orders for compliance');
201
+ console.log('');
202
+
203
+ const client = createQueenClient({
204
+ baseUrl: process.env.QUEEN_URL || 'http://localhost:6632'
205
+ });
206
+
207
+ const queue = 'ecommerce-orders';
208
+
209
+ // Simulate order processing workers
210
+ console.log('Starting order processing workers...');
211
+ for (let i = 1; i <= 2; i++) {
212
+ client.consume({
213
+ queue,
214
+ // No consumer group - workers compete
215
+ handler: async (message) => {
216
+ console.log(` [Order Worker ${i}] Processing order #${message.data.orderId} ($${message.data.amount})`);
217
+ // Simulate order processing: inventory, payment, shipping
218
+ await new Promise(resolve => setTimeout(resolve, 500));
219
+ console.log(` [Order Worker ${i}] āœ… Order #${message.data.orderId} completed`);
220
+ },
221
+ options: { batch: 1, wait: true }
222
+ });
223
+ }
224
+
225
+ // Analytics service (needs all orders)
226
+ console.log('Starting analytics service...');
227
+ client.consume({
228
+ queue,
229
+ consumerGroup: 'analytics-service',
230
+ handler: async (message) => {
231
+ console.log(` [Analytics] Recording metrics for order #${message.data.orderId}`);
232
+ // Update dashboards, calculate revenue, track trends
233
+ },
234
+ options: { batch: 5, wait: true }
235
+ });
236
+
237
+ // Notification service (needs all orders)
238
+ console.log('Starting notification service...');
239
+ client.consume({
240
+ queue,
241
+ consumerGroup: 'notification-service',
242
+ handler: async (message) => {
243
+ console.log(` [Notifications] Sending email for order #${message.data.orderId} to ${message.data.email}`);
244
+ // Send order confirmation emails, SMS, push notifications
245
+ },
246
+ options: { batch: 1, wait: true }
247
+ });
248
+
249
+ // Audit service (needs all orders for compliance)
250
+ console.log('Starting audit service...');
251
+ client.consume({
252
+ queue,
253
+ consumerGroup: 'audit-service',
254
+ handler: async (message) => {
255
+ console.log(` [Audit] Logging order #${message.data.orderId} for compliance`);
256
+ // Store in audit log, ensure regulatory compliance
257
+ },
258
+ options: { batch: 10, wait: true }
259
+ });
260
+
261
+ console.log('\nāœ… All services started!');
262
+ console.log('\nNow push some orders to see the system in action:');
263
+ console.log(' await client.push({ items: [{ queue: "ecommerce-orders", payload: {...} }] })');
264
+ }
265
+
266
+ // Run the example
267
+ main()
268
+ .then(() => realWorldScenario())
269
+ .then(() => {
270
+ console.log('\n\n✨ Mixed mode example completed!');
271
+ console.log('Press Ctrl+C to exit.');
272
+ // Keep the real-world scenario running
273
+ })
274
+ .catch(error => {
275
+ console.error('āŒ Error:', error);
276
+ process.exit(1);
277
+ });