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,185 @@
1
+ # Multi-Server Cache Consistency Solutions
2
+
3
+ ## Problem
4
+ When running multiple Queen servers, each server maintains its own in-memory resource cache with a 1-minute TTL. Configuration changes on one server are not reflected on other servers until the cache expires.
5
+
6
+ ## Solutions
7
+
8
+ ### 1. **Disable Cache in Multi-Server Mode** (Quick Fix)
9
+ Set cache TTL to 0 or very short duration when running multiple servers.
10
+
11
+ ```javascript
12
+ // In src/managers/resourceCache.js
13
+ const TTL = process.env.QUEEN_CACHE_TTL ? parseInt(process.env.QUEEN_CACHE_TTL) : 60000;
14
+ ```
15
+
16
+ Run servers with:
17
+ ```bash
18
+ QUEEN_CACHE_TTL=0 PORT=6632 node src/server.js
19
+ QUEEN_CACHE_TTL=0 PORT=6633 node src/server.js
20
+ ```
21
+
22
+ ### 2. **Cache Invalidation via Database Triggers** (Recommended)
23
+ Add a `cache_invalidation` table and use database triggers to notify servers of changes.
24
+
25
+ ```sql
26
+ -- Add to schema
27
+ CREATE TABLE queen.cache_invalidations (
28
+ id SERIAL PRIMARY KEY,
29
+ resource_type VARCHAR(50) NOT NULL,
30
+ resource_key VARCHAR(255) NOT NULL,
31
+ invalidated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
32
+ );
33
+
34
+ CREATE INDEX idx_cache_invalidations_time ON queen.cache_invalidations(invalidated_at);
35
+
36
+ -- Trigger on queue updates
37
+ CREATE OR REPLACE FUNCTION queen.notify_cache_invalidation()
38
+ RETURNS TRIGGER AS $$
39
+ BEGIN
40
+ INSERT INTO queen.cache_invalidations (resource_type, resource_key)
41
+ VALUES ('queue', NEW.name);
42
+
43
+ PERFORM pg_notify('cache_invalidation', json_build_object(
44
+ 'type', 'queue',
45
+ 'key', NEW.name
46
+ )::text);
47
+
48
+ RETURN NEW;
49
+ END;
50
+ $$ LANGUAGE plpgsql;
51
+
52
+ CREATE TRIGGER queue_cache_invalidation
53
+ AFTER UPDATE ON queen.queues
54
+ FOR EACH ROW
55
+ EXECUTE FUNCTION queen.notify_cache_invalidation();
56
+ ```
57
+
58
+ ### 3. **Redis-Based Distributed Cache** (Best for Scale)
59
+ Replace in-memory cache with Redis for shared caching across servers.
60
+
61
+ ```javascript
62
+ // src/managers/redisResourceCache.js
63
+ import Redis from 'ioredis';
64
+
65
+ export const createRedisResourceCache = (redisConfig) => {
66
+ const redis = new Redis(redisConfig);
67
+ const TTL = 60; // seconds
68
+
69
+ const getCacheKey = (queue, partition) =>
70
+ `queen:resource:${queue}:${partition || 'Default'}`;
71
+
72
+ const checkResource = async (queue, partition) => {
73
+ const key = getCacheKey(queue, partition);
74
+ const cached = await redis.get(key);
75
+ return cached ? JSON.parse(cached) : null;
76
+ };
77
+
78
+ const cacheResource = async (queue, partition, data) => {
79
+ const key = getCacheKey(queue, partition);
80
+ await redis.setex(key, TTL, JSON.stringify(data));
81
+ };
82
+
83
+ const invalidate = async (queue, partition) => {
84
+ if (queue && partition) {
85
+ await redis.del(getCacheKey(queue, partition));
86
+ } else if (queue) {
87
+ const keys = await redis.keys(`queen:resource:${queue}:*`);
88
+ if (keys.length > 0) {
89
+ await redis.del(...keys);
90
+ }
91
+ } else {
92
+ const keys = await redis.keys('queen:resource:*');
93
+ if (keys.length > 0) {
94
+ await redis.del(...keys);
95
+ }
96
+ }
97
+ };
98
+
99
+ return {
100
+ checkResource,
101
+ cacheResource,
102
+ invalidate,
103
+ invalidateQueue: (queue) => invalidate(queue)
104
+ };
105
+ };
106
+ ```
107
+
108
+ ### 4. **Polling-Based Cache Invalidation** (Simple)
109
+ Each server periodically checks for configuration changes.
110
+
111
+ ```javascript
112
+ // Add to resourceCache.js
113
+ const pollForChanges = async (pool) => {
114
+ const result = await pool.query(`
115
+ SELECT name, updated_at
116
+ FROM queen.queues
117
+ WHERE updated_at > $1
118
+ `, [lastCheckTime]);
119
+
120
+ for (const row of result.rows) {
121
+ invalidateQueue(row.name);
122
+ }
123
+
124
+ lastCheckTime = new Date();
125
+ };
126
+
127
+ // Poll every 5 seconds
128
+ setInterval(() => pollForChanges(pool), 5000);
129
+ ```
130
+
131
+ ### 5. **Event-Based Invalidation via Message Queue**
132
+ Use the Queen queue itself to propagate cache invalidation events.
133
+
134
+ ```javascript
135
+ // When configuration changes on one server
136
+ await client.push({
137
+ items: [{
138
+ queue: '_system_cache_invalidation',
139
+ partition: 'Default',
140
+ payload: {
141
+ action: 'invalidate',
142
+ resourceType: 'queue',
143
+ resourceKey: queueName,
144
+ timestamp: Date.now()
145
+ }
146
+ }]
147
+ });
148
+
149
+ // All servers consume from this queue
150
+ client.consume({
151
+ queue: '_system_cache_invalidation',
152
+ handler: async (message) => {
153
+ const { resourceType, resourceKey } = message.payload;
154
+ if (resourceType === 'queue') {
155
+ resourceCache.invalidateQueue(resourceKey);
156
+ }
157
+ }
158
+ });
159
+ ```
160
+
161
+ ## Recommended Approach
162
+
163
+ For your immediate testing needs, use **Solution 1** (disable cache):
164
+ ```bash
165
+ # Start servers without cache
166
+ QUEEN_CACHE_TTL=0 PORT=6632 node src/server.js
167
+ QUEEN_CACHE_TTL=0 PORT=6633 node src/server.js
168
+ ```
169
+
170
+ For production multi-server deployment:
171
+ - **Small scale (2-3 servers)**: Solution 2 (Database triggers with LISTEN/NOTIFY)
172
+ - **Medium scale (4-10 servers)**: Solution 3 (Redis cache)
173
+ - **Large scale (10+ servers)**: Solution 3 (Redis) + Solution 5 (Event-based)
174
+
175
+ ## Testing Cache Behavior
176
+
177
+ After implementing a solution, run:
178
+ ```bash
179
+ node examples/test-cache-multi-server.js
180
+ ```
181
+
182
+ The test should show:
183
+ - Both servers returning the same messages āœ…
184
+ - Configuration changes immediately visible on both servers āœ…
185
+ - No cache inconsistency warnings āœ…
@@ -0,0 +1,222 @@
1
+ # Performance Tuning Guide for Queen
2
+
3
+ ## Database Connection Pool Optimization
4
+
5
+ When running Queen under high load, you may encounter database connection pool exhaustion errors:
6
+ ```
7
+ Error: timeout exceeded when trying to connect
8
+ ```
9
+
10
+ This happens when the rate of incoming requests exceeds the available database connections.
11
+
12
+ ## Solution 1: Increase Connection Pool Size
13
+
14
+ Set these environment variables before starting the server:
15
+
16
+ ```bash
17
+ # Increase the connection pool size (default is 20)
18
+ export DB_POOL_SIZE=50
19
+
20
+ # Increase connection timeout (default is 2000ms)
21
+ export DB_CONNECTION_TIMEOUT=5000
22
+
23
+ # Start the server
24
+ npm start
25
+ ```
26
+
27
+ ## Solution 2: Optimize PostgreSQL Settings
28
+
29
+ Edit your `postgresql.conf`:
30
+
31
+ ```conf
32
+ # Increase maximum connections
33
+ max_connections = 200
34
+
35
+ # Connection pooling
36
+ shared_preload_libraries = 'pg_stat_statements'
37
+
38
+ # Memory settings for better performance
39
+ shared_buffers = 256MB
40
+ effective_cache_size = 1GB
41
+ work_mem = 4MB
42
+ maintenance_work_mem = 64MB
43
+ ```
44
+
45
+ ## Solution 3: Use Batch Inserts (Database Optimization)
46
+
47
+ The current implementation processes messages individually within a transaction. For better performance under high load, consider using batch inserts.
48
+
49
+ ### Current Implementation (Sequential)
50
+ ```javascript
51
+ // Process each item individually
52
+ for (const item of items) {
53
+ // Individual INSERT for each message
54
+ }
55
+ ```
56
+
57
+ ### Optimized Implementation (Batch)
58
+ ```sql
59
+ -- Use single INSERT with multiple VALUES
60
+ INSERT INTO queen.messages (transaction_id, queue_id, payload, status)
61
+ VALUES
62
+ ($1, $2, $3, 'pending'),
63
+ ($4, $5, $6, 'pending'),
64
+ ...
65
+ RETURNING id, transaction_id;
66
+ ```
67
+
68
+ ## Solution 4: Implement Connection Pooling with PgBouncer
69
+
70
+ For production environments with very high load, use PgBouncer as a connection pooler:
71
+
72
+ 1. Install PgBouncer:
73
+ ```bash
74
+ # macOS
75
+ brew install pgbouncer
76
+
77
+ # Linux
78
+ apt-get install pgbouncer
79
+ ```
80
+
81
+ 2. Configure PgBouncer (`/etc/pgbouncer/pgbouncer.ini`):
82
+ ```ini
83
+ [databases]
84
+ queen = host=localhost port=5432 dbname=postgres
85
+
86
+ [pgbouncer]
87
+ listen_port = 6432
88
+ listen_addr = *
89
+ auth_type = md5
90
+ auth_file = /etc/pgbouncer/userlist.txt
91
+ pool_mode = transaction
92
+ max_client_conn = 1000
93
+ default_pool_size = 50
94
+ ```
95
+
96
+ 3. Update Queen to connect through PgBouncer:
97
+ ```bash
98
+ export PG_PORT=6432 # PgBouncer port
99
+ npm start
100
+ ```
101
+
102
+ ## Recommended Production Settings
103
+
104
+ For a production environment handling high message throughput:
105
+
106
+ ### Environment Variables
107
+ ```bash
108
+ # Database
109
+ DB_POOL_SIZE=50
110
+ DB_CONNECTION_TIMEOUT=5000
111
+ DB_IDLE_TIMEOUT=30000
112
+
113
+ # Application
114
+ NODE_ENV=production
115
+ NODE_OPTIONS="--max-old-space-size=4096"
116
+
117
+ # Monitoring
118
+ LOG_LEVEL=warn
119
+ ENABLE_METRICS=true
120
+ ```
121
+
122
+ ### Example High-Load Configuration
123
+
124
+ For sustained load of 1000+ messages/second:
125
+
126
+ ```bash
127
+ # Start server with optimized settings
128
+ DB_POOL_SIZE=100 \
129
+ DB_CONNECTION_TIMEOUT=10000 \
130
+ NODE_OPTIONS="--max-old-space-size=8192" \
131
+ npm start
132
+ ```
133
+
134
+ ## Testing Under Load
135
+
136
+ ### Reasonable Load Test
137
+ ```bash
138
+ # 10 messages per batch, every 100ms = ~100 msg/sec
139
+ BATCH_SIZE=10 INTERVAL=100 node examples/continuous-producer.js
140
+ ```
141
+
142
+ ### Medium Load Test
143
+ ```bash
144
+ # 50 messages per batch, every 500ms = ~100 msg/sec
145
+ BATCH_SIZE=50 INTERVAL=500 node examples/continuous-producer.js
146
+ ```
147
+
148
+ ### High Load Test (requires tuning)
149
+ ```bash
150
+ # First, increase DB pool
151
+ export DB_POOL_SIZE=100
152
+
153
+ # Then run high load producer
154
+ # 100 messages per batch, every 1000ms = ~100 msg/sec
155
+ BATCH_SIZE=100 INTERVAL=1000 node examples/continuous-producer.js
156
+ ```
157
+
158
+ ## Monitoring Performance
159
+
160
+ Monitor these metrics to identify bottlenecks:
161
+
162
+ 1. **Database Connections**
163
+ ```sql
164
+ -- Check active connections
165
+ SELECT count(*) FROM pg_stat_activity;
166
+
167
+ -- Check connections by state
168
+ SELECT state, count(*)
169
+ FROM pg_stat_activity
170
+ GROUP BY state;
171
+ ```
172
+
173
+ 2. **Connection Pool Status**
174
+ ```javascript
175
+ // Add monitoring endpoint to server
176
+ app.get('/metrics/pool', (req, res) => {
177
+ res.json({
178
+ totalCount: pool.totalCount,
179
+ idleCount: pool.idleCount,
180
+ waitingCount: pool.waitingCount
181
+ });
182
+ });
183
+ ```
184
+
185
+ 3. **Message Throughput**
186
+ ```bash
187
+ # Monitor message insertion rate
188
+ watch -n 1 "psql -c 'SELECT count(*) FROM queen.messages WHERE created_at > NOW() - INTERVAL '\''1 minute'\'';'"
189
+ ```
190
+
191
+ ## Best Practices
192
+
193
+ 1. **Start Conservative**: Begin with lower message rates and gradually increase
194
+ 2. **Monitor Resources**: Watch CPU, memory, and database connections
195
+ 3. **Use Batching**: Process messages in reasonable batches (10-100 per batch)
196
+ 4. **Implement Backpressure**: Slow down producers when consumers can't keep up
197
+ 5. **Scale Horizontally**: Run multiple Queen instances behind a load balancer
198
+ 6. **Use Read Replicas**: For analytics and read-heavy operations
199
+
200
+ ## Troubleshooting
201
+
202
+ ### "timeout exceeded when trying to connect"
203
+ - Increase `DB_POOL_SIZE`
204
+ - Increase `DB_CONNECTION_TIMEOUT`
205
+ - Check PostgreSQL `max_connections`
206
+ - Consider using PgBouncer
207
+
208
+ ### "Push request aborted"
209
+ - Client timeout is too short
210
+ - Increase client timeout in producer
211
+ - Reduce batch size
212
+
213
+ ### High Memory Usage
214
+ - Reduce batch sizes
215
+ - Increase Node.js heap size with `--max-old-space-size`
216
+ - Implement message pagination
217
+
218
+ ### Slow Message Processing
219
+ - Add database indexes
220
+ - Use batch operations
221
+ - Implement caching for frequently accessed data
222
+ - Consider partitioning large tables
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Bus Mode Example for Queen V3
5
+ *
6
+ * This example demonstrates the new bus/pub-sub functionality where
7
+ * multiple consumer groups can receive ALL messages independently,
8
+ * similar to Kafka's consumer groups.
9
+ *
10
+ * Each consumer group maintains its own progress through the message stream.
11
+ */
12
+
13
+ import { createQueenClient } from '../src/client/index.js';
14
+
15
+ async function main() {
16
+ const client = createQueenClient({
17
+ baseUrl: process.env.QUEEN_URL || 'http://localhost:6632',
18
+ timeout: 35000
19
+ });
20
+
21
+ const queue = 'events';
22
+
23
+ console.log('šŸš€ Starting Bus Mode Example');
24
+ console.log('šŸ“¦ Queue:', queue);
25
+ console.log('');
26
+ console.log('This example will:');
27
+ console.log('1. Push some test messages to the queue');
28
+ console.log('2. Start 3 different consumer groups');
29
+ console.log('3. Show how each group receives ALL messages independently');
30
+ console.log('');
31
+
32
+ // First, push some test messages
33
+ console.log('šŸ“¤ Pushing test messages...');
34
+ const testMessages = [];
35
+ for (let i = 1; i <= 5; i++) {
36
+ testMessages.push({
37
+ queue,
38
+ payload: {
39
+ id: i,
40
+ type: 'test-event',
41
+ timestamp: new Date().toISOString(),
42
+ data: `Message ${i}`
43
+ }
44
+ });
45
+ }
46
+
47
+ await client.push({ items: testMessages });
48
+ console.log(`āœ… Pushed ${testMessages.length} messages\n`);
49
+
50
+ // Track messages received by each consumer group
51
+ const received = {
52
+ analytics: [],
53
+ notifications: [],
54
+ audit: []
55
+ };
56
+
57
+ // Consumer Group 1: Analytics Service
58
+ console.log('šŸ”µ Starting Analytics Consumer Group...');
59
+ const analyticsConsumer = client.consume({
60
+ queue,
61
+ consumerGroup: 'analytics-service',
62
+ handler: async (message) => {
63
+ console.log(` [Analytics] Received message ${message.data.id}: ${message.data.data}`);
64
+ received.analytics.push(message.data.id);
65
+
66
+ // Simulate processing
67
+ await new Promise(resolve => setTimeout(resolve, 100));
68
+ },
69
+ options: {
70
+ batch: 2,
71
+ wait: false // Don't wait for new messages in this example
72
+ }
73
+ });
74
+
75
+ // Consumer Group 2: Notification Service
76
+ console.log('🟢 Starting Notifications Consumer Group...');
77
+ const notificationConsumer = client.consume({
78
+ queue,
79
+ consumerGroup: 'notification-service',
80
+ handler: async (message) => {
81
+ console.log(` [Notifications] Received message ${message.data.id}: ${message.data.data}`);
82
+ received.notifications.push(message.data.id);
83
+
84
+ // Simulate processing
85
+ await new Promise(resolve => setTimeout(resolve, 150));
86
+ },
87
+ options: {
88
+ batch: 1,
89
+ wait: false
90
+ }
91
+ });
92
+
93
+ // Consumer Group 3: Audit Log Service
94
+ console.log('🟔 Starting Audit Log Consumer Group...');
95
+ const auditConsumer = client.consume({
96
+ queue,
97
+ consumerGroup: 'audit-log',
98
+ handler: async (message) => {
99
+ console.log(` [Audit] Received message ${message.data.id}: ${message.data.data}`);
100
+ received.audit.push(message.data.id);
101
+
102
+ // Simulate processing
103
+ await new Promise(resolve => setTimeout(resolve, 50));
104
+ },
105
+ options: {
106
+ batch: 3,
107
+ wait: false
108
+ }
109
+ });
110
+
111
+ // Wait for all messages to be processed
112
+ console.log('\nā³ Processing messages...\n');
113
+ await new Promise(resolve => setTimeout(resolve, 3000));
114
+
115
+ // Stop all consumers
116
+ analyticsConsumer();
117
+ notificationConsumer();
118
+ auditConsumer();
119
+
120
+ // Display results
121
+ console.log('\nšŸ“Š Results:');
122
+ console.log('═══════════════════════════════════════════');
123
+ console.log(`Analytics Service received: [${received.analytics.sort().join(', ')}]`);
124
+ console.log(`Notification Service received: [${received.notifications.sort().join(', ')}]`);
125
+ console.log(`Audit Log Service received: [${received.audit.sort().join(', ')}]`);
126
+ console.log('');
127
+
128
+ // Verify all groups received all messages
129
+ const allReceived =
130
+ received.analytics.length === testMessages.length &&
131
+ received.notifications.length === testMessages.length &&
132
+ received.audit.length === testMessages.length;
133
+
134
+ if (allReceived) {
135
+ console.log('āœ… SUCCESS: All consumer groups received ALL messages!');
136
+ console.log(' This demonstrates bus/pub-sub mode where each consumer group');
137
+ console.log(' gets its own copy of every message, independently.');
138
+ } else {
139
+ console.log('āš ļø Some messages were not received by all groups.');
140
+ console.log(' This might be due to timing. Try running again.');
141
+ }
142
+
143
+ console.log('\nšŸŽÆ Key Differences from Queue Mode:');
144
+ console.log(' • In Queue Mode: Messages are consumed by ONE worker');
145
+ console.log(' • In Bus Mode: Messages are consumed by ALL consumer groups');
146
+ console.log(' • Each group maintains its own progress/offset');
147
+ console.log(' • Perfect for: analytics, notifications, audit logs, etc.');
148
+ }
149
+
150
+ // Demonstrate subscription modes
151
+ async function demonstrateSubscriptionModes() {
152
+ const client = createQueenClient({
153
+ baseUrl: process.env.QUEEN_URL || 'http://localhost:6632'
154
+ });
155
+
156
+ const queue = 'subscription-test';
157
+
158
+ console.log('\n\nšŸ“… Demonstrating Subscription Modes');
159
+ console.log('════════════════════════════════════════════');
160
+
161
+ // Push some historical messages
162
+ console.log('šŸ“¤ Pushing historical messages...');
163
+ for (let i = 1; i <= 3; i++) {
164
+ await client.push({
165
+ items: [{
166
+ queue,
167
+ payload: { id: i, type: 'historical', data: `Historical message ${i}` }
168
+ }]
169
+ });
170
+ }
171
+
172
+ console.log('āœ… Historical messages pushed\n');
173
+
174
+ // Consumer 1: Consume all messages (default)
175
+ console.log('šŸ”µ Consumer Group "all-messages" (default mode):');
176
+ const allMessages = [];
177
+ const consumer1 = client.consume({
178
+ queue,
179
+ consumerGroup: 'all-messages',
180
+ handler: async (msg) => {
181
+ console.log(` Received: ${msg.data.type} - ${msg.data.data}`);
182
+ allMessages.push(msg.data.id);
183
+ },
184
+ options: { wait: false }
185
+ });
186
+
187
+ await new Promise(resolve => setTimeout(resolve, 1000));
188
+ consumer1();
189
+
190
+ // Consumer 2: Only new messages from subscription time
191
+ console.log('\n🟢 Consumer Group "new-only" (subscriptionMode: "new"):');
192
+ const newOnly = [];
193
+ const consumer2 = client.consume({
194
+ queue,
195
+ consumerGroup: 'new-only',
196
+ handler: async (msg) => {
197
+ console.log(` Received: ${msg.data.type} - ${msg.data.data}`);
198
+ newOnly.push(msg.data.id);
199
+ },
200
+ options: {
201
+ wait: false,
202
+ subscriptionMode: 'new'
203
+ }
204
+ });
205
+
206
+ // Push new messages after subscription
207
+ console.log('\nšŸ“¤ Pushing new messages...');
208
+ for (let i = 4; i <= 6; i++) {
209
+ await client.push({
210
+ items: [{
211
+ queue,
212
+ payload: { id: i, type: 'new', data: `New message ${i}` }
213
+ }]
214
+ });
215
+ }
216
+
217
+ await new Promise(resolve => setTimeout(resolve, 1000));
218
+ consumer2();
219
+
220
+ // Display results
221
+ console.log('\nšŸ“Š Subscription Mode Results:');
222
+ console.log('════════════════════════════════════');
223
+ console.log(`"all-messages" group received: [${allMessages.sort().join(', ')}] (all 6 messages)`);
224
+ console.log(`"new-only" group received: [${newOnly.sort().join(', ')}] (only new messages)`);
225
+ console.log('');
226
+ console.log('āœ… This demonstrates how consumer groups can choose when to start consuming!');
227
+ }
228
+
229
+ // Run the examples
230
+ main()
231
+ .then(() => demonstrateSubscriptionModes())
232
+ .then(() => {
233
+ console.log('\n✨ Bus mode examples completed!');
234
+ process.exit(0);
235
+ })
236
+ .catch(error => {
237
+ console.error('āŒ Error:', error);
238
+ process.exit(1);
239
+ });