queen-mq 0.1.6 → 0.2.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/README.md CHANGED
@@ -24,6 +24,7 @@
24
24
  ### Why Queen?
25
25
 
26
26
  **🚀 Developer-First API**
27
+ - **Pipeline & Transaction APIs**: High-level fluent interfaces for complex workflows
27
28
  - **4 core methods, infinite patterns**: `queue()`, `push()`, `take()`, `ack()`
28
29
  - **Async iteration**: Process messages with familiar `for await` syntax
29
30
  - **Batch processing**: Use `takeBatch()` for 250k+ msg/sec throughput on millions of messages
@@ -35,18 +36,22 @@
35
36
  - **Long polling** for event-driven, real-time message delivery
36
37
  - **Partition locking** prevents duplicate processing across consumers
37
38
  - **Connection pooling** and optimized batch operations
39
+ - **Parallel processing** across partitions with `withConcurrency()`
38
40
 
39
41
  **🏗️ Flexible Architecture**
40
42
  - **Queue Mode**: Competitive consumption (traditional work queue)
41
43
  - **Bus Mode**: Pub/sub with consumer groups (event streaming)
42
44
  - **Mixed Mode**: Combine both patterns in the same system
43
45
  - **Partitions**: FIFO ordering with parallel processing
46
+ - **Exactly-Once Processing**: Lease-based locking with automatic validation
44
47
 
45
48
  **🔒 Enterprise Features**
46
49
  - **AES-256-GCM Encryption**: Protect sensitive data at rest
47
50
  - **Message Retention**: Automatic cleanup policies
48
51
  - **Message Eviction**: SLA enforcement for time-sensitive tasks
49
52
  - **Dead Letter Queue**: Handle failed messages gracefully
53
+ - **Automatic Lease Renewal**: For long-running tasks
54
+ - **Atomic Transactions**: Multi-operation consistency
50
55
 
51
56
  **📊 Built-in Observability**
52
57
  - **Real-time Dashboard**: WebSocket-powered monitoring
@@ -67,12 +72,12 @@
67
72
 
68
73
  ## 📋 Table of Contents
69
74
 
70
- - [First Queue](#-first-queue)
75
+ - [First Queue](#first-queue)
71
76
  - [Quick Start](#-quick-start)
72
- - [Client Examples](#-client-examples)
77
+ - [Advanced Client APIs](#advanced-client-apis)
78
+ - [Standard Client Examples](#-client-examples)
73
79
  - [Server Setup](#-server-setup)
74
80
  - [Core Concepts](#-core-concepts)
75
- - [Cursor-Based Consumption Strategy](#-cursor-based-consumption-strategy)
76
81
  - [HTTP API Reference](#-http-api-reference)
77
82
  - [Dashboard](#-dashboard)
78
83
  - [Configuration](#-configuration)
@@ -88,13 +93,17 @@
88
93
  import { Queen } from 'queen-mq';
89
94
 
90
95
  const client = new Queen({
91
- baseUrls: ['http://localhost:6632']
96
+ baseUrl: 'http://localhost:6632' // Single server
97
+ // OR for multiple servers with load balancing:
98
+ // baseUrls: ['http://server1:6632', 'http://server2:6632'],
99
+ // loadBalancingStrategy: 'round-robin', // or 'random', 'least-connections'
100
+ // enableFailover: true
92
101
  });
93
102
 
94
103
  // Configure queue
95
104
  await client.queue('tasks', {
96
- leaseTime: 300,
97
- retryLimit: 3
105
+ leaseTime: 300, // 5 minutes to process each message
106
+ retryLimit: 3 // Retry up to 3 times
98
107
  });
99
108
 
100
109
  // Push a message
@@ -134,33 +143,6 @@ npm install
134
143
  node init-db.js
135
144
  ```
136
145
 
137
- ### Running the Server
138
-
139
- **Option 1: Standalone Server (Traditional)**
140
-
141
- ```bash
142
- npm start
143
- ```
144
-
145
- **Option 2: Programmatic Server (New in v0.1.2)**
146
-
147
- ```javascript
148
- import { QueenServer } from 'queen-mq';
149
-
150
- // Start server with default config
151
- const server = await QueenServer();
152
-
153
- // Or with custom options
154
- const server = await QueenServer({
155
- port: 3000,
156
- host: '127.0.0.1'
157
- });
158
-
159
- console.log(`Server running at http://${server.host}:${server.port}`);
160
- ```
161
-
162
- This allows you to embed Queen MQ directly in your application, run multiple instances, or easily start/stop servers in tests. See [PROGRAMMATIC_SERVER.md](./PROGRAMMATIC_SERVER.md) for more details.
163
-
164
146
  ### Set Environment (Optional)
165
147
 
166
148
  ```bash
@@ -184,6 +166,193 @@ npm start
184
166
 
185
167
  ---
186
168
 
169
+ ## Advanced Client APIs
170
+
171
+ ### Pipeline API - Fluent Message Processing
172
+
173
+ The Pipeline API provides a chainable interface for complex message processing workflows:
174
+
175
+ ```javascript
176
+ // Simple message processing (one at a time)
177
+ await client.pipeline('my-queue')
178
+ .take(100) // Take up to 100 messages
179
+ .process(async (message) => { // Process each message individually
180
+ console.log('Processing:', message.data);
181
+ return { processed: true };
182
+ })
183
+ .execute();
184
+
185
+ // Batch processing
186
+ await client.pipeline('my-queue')
187
+ .take(100)
188
+ .processBatch(async (messages) => { // Process messages as a batch
189
+ console.log(`Processing ${messages.length} messages`);
190
+ return messages.map(m => ({ processed: m.id }));
191
+ })
192
+ .execute();
193
+
194
+ // With automatic lease renewal for long-running tasks
195
+ await client.pipeline('my-queue')
196
+ .take(50)
197
+ .withAutoRenewal({ interval: 5000 }) // Renew lease every 5 seconds
198
+ .process(async (message) => {
199
+ // Long-running task - lease automatically renewed
200
+ // Without this, if task takes > leaseTime, message may be redelivered
201
+ await heavyComputation(message);
202
+ })
203
+ .execute();
204
+
205
+ // Parallel processing across partitions
206
+ await client.pipeline('my-queue')
207
+ .take(100)
208
+ .withConcurrency(4) // 4 parallel workers
209
+ .process(async (message) => {
210
+ await processMessage(message);
211
+ })
212
+ .repeat({ continuous: true }) // Keep running continuously (default)
213
+ .execute();
214
+
215
+ // With error handling
216
+ await client.pipeline('my-queue')
217
+ .take(100)
218
+ .process(async (message) => {
219
+ if (message.data.invalid) {
220
+ throw new Error('Invalid message format');
221
+ }
222
+ return await riskyOperation(message);
223
+ })
224
+ .onError(async (error, messages) => {
225
+ console.error('Processing failed:', error.message);
226
+
227
+ // Move failed messages to error queue
228
+ await client.push('error-queue', {
229
+ error: error.message,
230
+ messages: messages.map(m => m.data),
231
+ timestamp: Date.now()
232
+ });
233
+
234
+ // ACK as failed (will retry based on retryLimit)
235
+ for (const msg of messages) {
236
+ await client.ack(msg, false, { error: error.message });
237
+ }
238
+ })
239
+ .execute();
240
+ ```
241
+
242
+ #### Automatic Lease Renewal
243
+
244
+ By default, messages have a lease time (e.g., 5 minutes). If processing takes longer, the message may be redelivered to another consumer. Use `.withAutoRenewal()` to prevent this:
245
+
246
+ ```javascript
247
+ // WITHOUT auto-renewal (default) - Risk of redelivery
248
+ await client.pipeline('video-processing')
249
+ .take(10)
250
+ .process(async (message) => {
251
+ // If this takes > leaseTime, message may be processed twice!
252
+ await longRunningTask(message);
253
+ })
254
+ .execute();
255
+
256
+ // WITH auto-renewal - Safe for long tasks
257
+ await client.pipeline('video-processing')
258
+ .take(10)
259
+ .withAutoRenewal({
260
+ interval: 30000 // Renew every 30 seconds (default)
261
+ })
262
+ .process(async (message) => {
263
+ // Lease automatically renewed while processing
264
+ await longRunningTask(message); // Safe even if takes hours
265
+ })
266
+ .execute();
267
+ ```
268
+
269
+ **When to use:**
270
+ - Video/audio processing
271
+ - Large file operations
272
+ - Machine learning inference
273
+ - Any task that might exceed the lease time
274
+
275
+ **Important:**
276
+ - Auto-renewal stops when message is ACKed or process crashes
277
+ - Set interval < lease time (e.g., renew at 1/3 of lease time)
278
+ - Only works within pipeline API
279
+
280
+ #### Error Handling in Pipeline
281
+
282
+ The `onError` handler provides robust error recovery:
283
+
284
+ **Key Behaviors:**
285
+ - Catches errors from `process()` or `processBatch()`
286
+ - Receives the error and affected messages
287
+ - Pipeline continues after error handler (doesn't stop)
288
+ - Without `onError`, errors stop the pipeline
289
+
290
+ **Error Recovery Strategies:**
291
+ ```javascript
292
+ // Strategy 1: Retry with backoff
293
+ .onError(async (error, messages) => {
294
+ // ACK as failed - will retry based on retryLimit
295
+ await client.ack(messages, false);
296
+ })
297
+
298
+ // Strategy 2: Move to Dead Letter Queue
299
+ .onError(async (error, messages) => {
300
+ for (const msg of messages) {
301
+ if (msg.retryCount >= 3) {
302
+ await client.push('dlq', { original: msg, error: error.message });
303
+ await client.ack(msg, true); // Remove from queue
304
+ } else {
305
+ await client.ack(msg, false); // Retry
306
+ }
307
+ }
308
+ })
309
+
310
+ // Strategy 3: Skip and continue
311
+ .onError(async (error, messages) => {
312
+ console.error(`Skipping ${messages.length} messages: ${error.message}`);
313
+ await client.ack(messages, true); // ACK as success to remove
314
+ })
315
+ ```
316
+
317
+ **Important Notes:**
318
+ - With `atomically()`: atomic operations only run on success
319
+ - In `repeat()` mode: errors don't stop continuous processing
320
+ - Batch processing: error affects all messages in the batch
321
+ - Parallel workers: each worker has independent error handling
322
+
323
+ ### Transaction API - Atomic Operations
324
+
325
+ Execute multiple operations atomically with lease validation:
326
+
327
+ ```javascript
328
+ // Atomic ACK + PUSH
329
+ const messages = await client.takeSingleBatch('input-queue');
330
+
331
+ await client.transaction()
332
+ .ack(messages) // ACK input messages
333
+ .push('output-queue', processedResults) // Push to output
334
+ .extend(leaseId) // Extend another lease
335
+ .commit(); // Execute atomically
336
+
337
+ // Pipeline with custom atomic operations
338
+ await client.pipeline('my-queue')
339
+ .take(50)
340
+ .process(async (message) => {
341
+ return await transform(message);
342
+ })
343
+ .atomically((tx, originalMessages, processedMessages) => {
344
+ tx.ack(originalMessages)
345
+ .push('output-queue', processedMessages)
346
+ .push('audit-queue', {
347
+ timestamp: Date.now(),
348
+ count: processedMessages.length
349
+ });
350
+ })
351
+ .execute();
352
+ ```
353
+
354
+ ---
355
+
187
356
  ## 💻 Client Examples
188
357
 
189
358
  The Queen client provides a minimalist API with just 4 methods that compose into any messaging pattern you need.
@@ -202,17 +371,21 @@ npm install queen-mq
202
371
  import { Queen } from 'queen-mq';
203
372
 
204
373
  const client = new Queen({
205
- baseUrls: ['http://localhost:6632'],
374
+ baseUrl: 'http://localhost:6632',
206
375
  timeout: 30000,
207
376
  retryAttempts: 3
208
377
  });
209
378
 
210
379
  // Configure with options
211
380
  await client.queue('orders', {
212
- priority: 10, // Higher priority queues processed first
213
- leaseTime: 600, // 10 minutes to process each message
214
- retryLimit: 3, // Retry up to 3 times
215
- delayedProcessing: 0, // No delay (immediate processing)
381
+ priority: 10, // Higher priority queues processed first
382
+ leaseTime: 300, // 5 minutes to process each message
383
+ retryLimit: 3, // Retry up to 3 times
384
+ windowBuffer: 0, // No delay (immediate processing)
385
+ retentionSeconds: 86400, // Keep messages for 24 hours
386
+ completedRetentionSeconds: 3600, // Keep completed messages for 1 hour
387
+ partitions: 10, // Create 10 partitions for parallel processing
388
+ maxWaitTimeSeconds: 600 // Evict messages waiting > 10 minutes
216
389
  });
217
390
 
218
391
  // Configure with namespace and task for grouping
@@ -246,15 +419,6 @@ await client.push('orders', [
246
419
  { orderId: 12348, amount: 79.99 },
247
420
  { orderId: 12349, amount: 29.99 }
248
421
  ]);
249
-
250
- // With message properties
251
- await client.push('orders', {
252
- orderId: 12350,
253
- amount: 199.99
254
- }, {
255
- transactionId: 'txn-12350', // For idempotency
256
- traceId: '550e8400-e29b-41d4-a716-446655440000' // Valid UUID for tracing
257
- });
258
422
  ```
259
423
 
260
424
  #### 3. Take Messages (Async Iterator)
@@ -305,6 +469,9 @@ for await (const messages of client.takeBatch('orders', {
305
469
  await client.ack(messages, false, { error: error.message });
306
470
  }
307
471
  }
472
+
473
+ // Take single batch (convenience method)
474
+ const messages = await client.takeSingleBatch('orders', { batch: 100 });
308
475
  ```
309
476
 
310
477
  #### 4. Acknowledge Messages
@@ -323,11 +490,44 @@ await client.ack(message, false, {
323
490
  error: 'Payment gateway timeout'
324
491
  });
325
492
 
326
- // Acknowledge using transaction ID
327
- await client.ack('4dfb0478-655b-4c91-bcd9-b7acacf0400f', true);
493
+ // Batch acknowledgment (with lease validation)
494
+ await client.ack(messages); // Pass array for batch ack
495
+ ```
496
+
497
+ #### 5. Renew Leases (for long-running tasks)
498
+
499
+ ```javascript
500
+ // Renew lease for a single message
501
+ const result = await client.renewLease(message);
502
+ console.log(`Lease renewed until: ${result.newExpiresAt}`);
503
+
504
+ // Renew by lease ID directly
505
+ await client.renewLease('lease-uuid-123');
328
506
 
329
- // Request explicit retry
330
- await client.ack(message, 'retry');
507
+ // Renew multiple messages at once
508
+ const results = await client.renewLease(messages);
509
+ results.forEach(r => {
510
+ if (r.success) {
511
+ console.log(`Renewed ${r.leaseId} until ${r.newExpiresAt}`);
512
+ } else {
513
+ console.error(`Failed to renew ${r.leaseId}: ${r.error}`);
514
+ }
515
+ });
516
+
517
+ // Example: Manual renewal during long processing
518
+ for await (const message of client.take('my-queue')) {
519
+ // Set up periodic renewal
520
+ const renewalInterval = setInterval(async () => {
521
+ await client.renewLease(message);
522
+ }, 30000); // Renew every 30 seconds
523
+
524
+ try {
525
+ await longRunningTask(message);
526
+ await client.ack(message);
527
+ } finally {
528
+ clearInterval(renewalInterval);
529
+ }
530
+ }
331
531
  ```
332
532
 
333
533
  ### Address Notation
@@ -350,72 +550,6 @@ Queen uses a simple addressing scheme that encodes queue, partition, and consume
350
550
 
351
551
  ### Consumer Patterns
352
552
 
353
- #### Continuous Processing (Long Polling)
354
-
355
- ```javascript
356
- // Efficient real-time processing
357
- for await (const message of client.take('tasks', {
358
- wait: true, // Long polling - waits for messages
359
- timeout: 30000 // Server timeout
360
- })) {
361
- await processTask(message.data);
362
- await client.ack(message);
363
- }
364
- ```
365
-
366
- #### Batch Processing
367
-
368
- ```javascript
369
- // Method 1: Manual batching with take()
370
- const batch = [];
371
- for await (const message of client.take('analytics', { batch: 100 })) {
372
- batch.push(message);
373
-
374
- if (batch.length >= 100) {
375
- await processBatch(batch.map(m => m.data));
376
-
377
- // Acknowledge all
378
- for (const msg of batch) {
379
- await client.ack(msg);
380
- }
381
- batch.length = 0;
382
- }
383
- }
384
-
385
- // Method 2: Direct batch processing with takeBatch() (RECOMMENDED)
386
- for await (const messages of client.takeBatch('analytics', { batch: 1000 })) {
387
- // messages is already an array!
388
- await processBatch(messages.map(m => m.data));
389
-
390
- // Single batch acknowledgment (much faster!)
391
- await client.ack(messages);
392
- }
393
- ```
394
-
395
- #### Parallel Processing with Partitions
396
-
397
- ```javascript
398
- // Create workers for parallel processing
399
- const partitions = ['worker-1', 'worker-2', 'worker-3', 'worker-4'];
400
-
401
- // Distribute messages across partitions
402
- for (let i = 0; i < messages.length; i++) {
403
- const partition = partitions[i % partitions.length];
404
- await client.push(`tasks/${partition}`, messages[i]);
405
- }
406
-
407
- // Each worker processes its own partition (in parallel)
408
- async function worker(partition) {
409
- for await (const msg of client.take(`tasks/${partition}`)) {
410
- await processTask(msg.data);
411
- await client.ack(msg);
412
- }
413
- }
414
-
415
- // Start all workers
416
- await Promise.all(partitions.map(p => worker(p)));
417
- ```
418
-
419
553
  #### Consumer Groups (Bus Mode)
420
554
 
421
555
  ```javascript
@@ -440,60 +574,6 @@ for await (const event of client.take('events@audit')) {
440
574
  }
441
575
  ```
442
576
 
443
- #### Subscription Modes
444
-
445
- ```javascript
446
- // Start from all existing messages (replay)
447
- for await (const event of client.take('events@replay-service', {
448
- subscriptionMode: 'all'
449
- })) {
450
- await replayEvent(event.data);
451
- await client.ack(event);
452
- }
453
-
454
- // Start from new messages only (real-time)
455
- for await (const event of client.take('events@realtime', {
456
- subscriptionMode: 'new'
457
- })) {
458
- await processEvent(event.data);
459
- await client.ack(event);
460
- }
461
-
462
- // Start from specific timestamp
463
- for await (const event of client.take('events@historical', {
464
- subscriptionMode: 'from',
465
- subscriptionFrom: '2024-01-01T00:00:00Z'
466
- })) {
467
- await processHistoricalEvent(event.data);
468
- await client.ack(event);
469
- }
470
- ```
471
-
472
- #### Error Handling
473
-
474
- ```javascript
475
- // Robust error handling with retries
476
- for await (const message of client.take('critical-tasks')) {
477
- let retries = 3;
478
-
479
- while (retries > 0) {
480
- try {
481
- await processTask(message.data);
482
- await client.ack(message);
483
- break;
484
- } catch (error) {
485
- retries--;
486
- if (retries === 0) {
487
- console.error('Task failed after retries:', error);
488
- await client.ack(message, false, { error: error.message });
489
- } else {
490
- await new Promise(r => setTimeout(r, 1000 * (4 - retries)));
491
- }
492
- }
493
- }
494
- }
495
- ```
496
-
497
577
  #### Graceful Shutdown
498
578
 
499
579
  ```javascript
@@ -511,7 +591,6 @@ for await (const message of client.take('orders')) {
511
591
  await client.ack(message);
512
592
  }
513
593
 
514
- await client.close();
515
594
  console.log('Shutdown complete');
516
595
  ```
517
596
 
@@ -519,1658 +598,271 @@ console.log('Shutdown complete');
519
598
 
520
599
  ## 🖥️ Server Setup
521
600
 
522
- ### Single Server
523
-
524
- ```bash
525
- # Start the server
526
- npm start
527
-
528
- # Or with custom configuration
529
- PORT=6632 \
530
- DB_POOL_SIZE=20 \
531
- QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32) \
532
- npm start
533
- ```
534
-
535
- ### Multi-Server (Load Balanced)
536
-
537
- Queen supports running multiple servers for high availability and load distribution:
601
+ ### Environment Variables
538
602
 
539
603
  ```bash
540
- # Server 1
541
- PORT=6632 WORKER_ID=server-1 npm start
542
-
543
- # Server 2
544
- PORT=6633 WORKER_ID=server-2 npm start
545
-
546
- # Server 3
547
- PORT=6634 WORKER_ID=server-3 npm start
548
- ```
604
+ # Server configuration
605
+ PORT=6632 # Server port
606
+ HOST=0.0.0.0 # Server host
607
+ WORKER_ID=srv-1 # Unique server instance ID
608
+ APP_NAME=queen-mq # Application name
549
609
 
550
- Client configuration:
610
+ # Database connection
611
+ PG_USER=postgres # PostgreSQL user
612
+ PG_HOST=localhost # PostgreSQL host
613
+ PG_DB=postgres # PostgreSQL database
614
+ PG_PASSWORD=postgres # PostgreSQL password
615
+ PG_PORT=5432 # PostgreSQL port
616
+ PG_USE_SSL=false # Enable SSL for PostgreSQL
617
+ DB_POOL_SIZE=150 # Connection pool size
618
+ DB_IDLE_TIMEOUT=30000 # Idle connection timeout (ms)
619
+ DB_CONNECTION_TIMEOUT=2000 # Connection timeout (ms)
620
+ DB_STATEMENT_TIMEOUT=30000 # Statement timeout (ms)
621
+
622
+ # Queue configuration
623
+ DEFAULT_TIMEOUT=30000 # Default pop timeout (ms)
624
+ MAX_TIMEOUT=60000 # Maximum pop timeout (ms)
625
+ DEFAULT_BATCH_SIZE=1 # Default batch size
626
+ BATCH_INSERT_SIZE=1000 # Batch insert size for push operations
627
+ DEFAULT_LEASE_TIME=300 # Default lease time (seconds)
628
+ DEFAULT_RETRY_LIMIT=3 # Default retry limit
629
+ DEFAULT_RETRY_DELAY=1000 # Default retry delay (ms)
630
+ DEFAULT_PRIORITY=0 # Default message priority
631
+ DEFAULT_WINDOW_BUFFER=0 # Default window buffer (seconds)
632
+
633
+ # Long polling configuration
634
+ QUEUE_POLL_INTERVAL=100 # Initial poll interval (ms)
635
+ QUEUE_MAX_POLL_INTERVAL=2000 # Max poll interval after backoff (ms)
636
+ QUEUE_BACKOFF_THRESHOLD=5 # Empty polls before backoff
637
+ QUEUE_BACKOFF_MULTIPLIER=2 # Exponential backoff multiplier
638
+
639
+ # Encryption
640
+ QUEEN_ENCRYPTION_KEY= # 32-byte hex key for AES-256-GCM encryption
641
+
642
+ # Retention & Eviction
643
+ DEFAULT_RETENTION_SECONDS=0 # Message retention (0 = disabled)
644
+ DEFAULT_COMPLETED_RETENTION_SECONDS=0 # Completed message retention
645
+ RETENTION_INTERVAL=300000 # Retention service interval (ms)
646
+ RETENTION_BATCH_SIZE=1000 # Retention batch size
647
+ EVICTION_INTERVAL=60000 # Eviction service interval (ms)
648
+ EVICTION_BATCH_SIZE=1000 # Eviction batch size
649
+ METRICS_RETENTION_DAYS=90 # Metrics retention period
650
+
651
+ # System Events
652
+ QUEEN_SYSTEM_EVENTS_ENABLED=false # Enable system event propagation
653
+ QUEEN_SYSTEM_EVENTS_BATCH_MS=10 # Event batching window (ms)
654
+ QUEEN_SYSTEM_EVENTS_SYNC_TIMEOUT=30000 # Startup sync timeout (ms)
655
+
656
+ # WebSocket configuration
657
+ WS_COMPRESSION=0 # WebSocket compression level
658
+ WS_MAX_PAYLOAD_LENGTH=16384 # Max WebSocket payload (bytes)
659
+ WS_IDLE_TIMEOUT=60 # WebSocket idle timeout (seconds)
660
+ WS_MAX_CONNECTIONS=1000 # Max WebSocket connections
661
+ WS_HEARTBEAT_INTERVAL=30000 # WebSocket heartbeat interval (ms)
662
+
663
+ # API configuration
664
+ MAX_BODY_SIZE=104857600 # Max request body size (100MB)
665
+ API_DEFAULT_LIMIT=100 # Default pagination limit
666
+ API_MAX_LIMIT=1000 # Max pagination limit
667
+ CORS_MAX_AGE=86400 # CORS max age (seconds)
668
+ CORS_ALLOWED_ORIGINS=* # CORS allowed origins
669
+ CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
670
+ CORS_ALLOWED_HEADERS=Content-Type,Authorization
551
671
 
552
- ```javascript
553
- const client = new Queen({
554
- baseUrls: [
555
- 'http://localhost:6632',
556
- 'http://localhost:6633',
557
- 'http://localhost:6634'
558
- ],
559
- loadBalancingStrategy: 'ROUND_ROBIN', // or 'RANDOM', 'LEAST_CONNECTIONS'
560
- enableFailover: true
672
+ # Monitoring & Logging
673
+ ENABLE_REQUEST_COUNTING=true # Enable request metrics
674
+ ENABLE_MESSAGE_COUNTING=true # Enable message metrics
675
+ METRICS_ENDPOINT_ENABLED=true # Enable metrics endpoint
676
+ HEALTH_CHECK_ENABLED=true # Enable health check endpoint
677
+ ENABLE_LOGGING=true # Enable logging
678
+ LOG_LEVEL=info # Log level (debug|info|warn|error)
679
+ LOG_FORMAT=json # Log format (json|text)
680
+ ```
681
+
682
+ ### Programmatic Server
683
+
684
+ ```javascript
685
+ import { createQueenServer } from 'queen-mq/server';
686
+
687
+ const server = await createQueenServer({
688
+ port: 6632,
689
+ database: {
690
+ connectionString: 'postgresql://...',
691
+ poolSize: 20
692
+ },
693
+ encryption: {
694
+ key: 'your-32-byte-hex-key'
695
+ },
696
+ features: {
697
+ retention: true,
698
+ eviction: true
699
+ }
561
700
  });
562
- ```
563
-
564
- ### Docker Deployment
565
-
566
- ```dockerfile
567
- FROM node:22-alpine
568
-
569
- WORKDIR /app
570
- COPY package*.json ./
571
- RUN npm ci --production
572
-
573
- COPY . .
574
-
575
- EXPOSE 6632
576
- CMD ["node", "src/server.js"]
577
- ```
578
-
579
- ```yaml
580
- # docker-compose.yml
581
- version: '3.8'
582
-
583
- services:
584
- postgres:
585
- image: postgres:16
586
- environment:
587
- POSTGRES_DB: queen
588
- POSTGRES_USER: queen
589
- POSTGRES_PASSWORD: queen
590
- volumes:
591
- - postgres_data:/var/lib/postgresql/data
592
- ports:
593
- - "5432:5432"
594
-
595
- queen:
596
- build: .
597
- ports:
598
- - "6632:6632"
599
- environment:
600
- PG_HOST: postgres
601
- PG_DB: queen
602
- PG_USER: queen
603
- PG_PASSWORD: queen
604
- DB_POOL_SIZE: 20
605
- QUEEN_ENCRYPTION_KEY: ${QUEEN_ENCRYPTION_KEY}
606
- depends_on:
607
- - postgres
608
-
609
- volumes:
610
- postgres_data:
611
- ```
612
701
 
613
- ### Environment Variables
614
-
615
- See the [Configuration](#-configuration) section for a complete list of environment variables.
616
-
617
- ### Database Schema
618
-
619
- The database schema is automatically created when you run:
620
-
621
- ```bash
622
- node init-db.js
702
+ await server.start();
623
703
  ```
624
704
 
625
- This creates:
626
- - `queen.queues` - Top-level message containers
627
- - `queen.partitions` - Subdivisions within queues (FIFO ordering)
628
- - `queen.messages` - Individual messages with processing state
629
-
630
705
  ---
631
706
 
632
- ## 💡 Core Concepts
633
-
634
- ### Architecture
635
-
636
- Queen uses a two-tier architecture:
637
-
638
- ```
639
- Queues (optional namespace/task grouping)
640
- └── Partitions (FIFO ordering, parallel processing)
641
- └── Messages (lease-based processing)
642
- ```
643
-
644
- **Key Principles:**
645
- - **Configuration at queue level**: All settings (priority, lease time, retries) apply to the entire queue
646
- - **FIFO within partitions**: Messages in the same partition are always processed in order
647
- - **Partition locking**: Prevents duplicate processing across consumers
648
- - **Lease-based processing**: Messages automatically return to pending if not acknowledged
649
-
650
- ### Queues and Partitions
651
-
652
- **Queues** are top-level organizational units. Each queue automatically gets a "Default" partition, and you can create additional partitions for logical separation or parallel processing.
653
-
654
- ```javascript
655
- // Messages go to "Default" partition
656
- await client.push('orders', { orderId: 123 });
657
-
658
- // Push to specific partition
659
- await client.push('orders/high-priority', { orderId: 456 });
660
-
661
- // Take from specific partition
662
- for await (const order of client.take('orders/high-priority')) {
663
- await processUrgentOrder(order.data);
664
- await client.ack(order);
665
- }
666
- ```
667
-
668
- **Partitions enable:**
669
- - **Parallel processing**: Different consumers can process different partitions simultaneously
670
- - **Ordered processing**: FIFO guarantees within each partition
671
- - **Logical separation**: Different priorities, teams, or workflow stages
672
- - **Resource isolation**: Lock contention is per-partition
673
-
674
- ### Message Lifecycle
675
-
676
- ```
677
- pending → processing → completed/failed → (retry) → dead_letter
678
- ```
679
-
680
- 1. **Pending**: Message queued, waiting to be processed
681
- 2. **Processing**: Leased to a worker (with timeout)
682
- 3. **Completed**: Successfully processed
683
- 4. **Failed**: Processing failed (may retry based on `retryLimit`)
684
- 5. **Dead Letter**: Exceeded retry limits
685
-
686
- ### Partition Locking
687
-
688
- **Partition locking ensures message processing isolation** - when a consumer retrieves messages from a partition, that partition is locked to prevent other consumers from accessing it until:
689
-
690
- - The consumer acknowledges all messages (releases lock)
691
- - The lease expires (automatic release)
692
- - The consumer explicitly releases the partition
693
-
694
- **Lock Scope:**
695
- - **Queue Mode**: Each consumer session is unique - locks prevent any other consumer from accessing the partition
696
- - **Bus Mode**: Locks are per consumer group - different groups can process the same partition independently
697
-
698
- ```javascript
699
- // Example: Partition locking in action
700
-
701
- // Consumer 1 takes from partition A (locks it)
702
- for await (const msg of client.take('orders', { limit: 5 })) {
703
- // Processing partition A - no other consumer can access it
704
- await client.ack(msg);
705
- // Partition A unlocked after all 5 messages acknowledged
706
- }
707
-
708
- // Consumer 2 gets messages from partition B (A was locked)
709
- for await (const msg of client.take('orders', { limit: 5 })) {
710
- // Processing partition B instead
711
- await client.ack(msg);
712
- }
713
- ```
714
-
715
- ### FIFO Ordering
716
-
717
- Queen provides **strong FIFO guarantees within each partition**:
718
-
719
- ```javascript
720
- // These messages will be processed in order 1, 2, 3
721
- await client.push('tasks/user-123', [
722
- { step: 1, action: 'create' },
723
- { step: 2, action: 'update' },
724
- { step: 3, action: 'complete' }
725
- ]);
726
-
727
- // Consumer will always receive them in order
728
- for await (const task of client.take('tasks/user-123')) {
729
- console.log(task.data.step); // Prints: 1, then 2, then 3
730
- await client.ack(task);
731
- }
732
- ```
733
-
734
- **Use cases:**
735
- - **Per-user operations**: Use user ID as partition for ordered processing
736
- - **Per-resource operations**: Use resource ID to maintain operation order
737
- - **Workflow stages**: Use partition to represent different stages
738
-
739
- ### Consumer Groups (Bus Mode)
740
-
741
- Consumer groups enable **pub-sub messaging** where multiple independent consumers process the same messages:
742
-
743
- ```javascript
744
- // Push once
745
- await client.push('events', { type: 'order.created', orderId: 123 });
746
-
747
- // Multiple services consume independently
748
- // Service 1: Analytics
749
- for await (const event of client.take('events@analytics')) {
750
- await updateAnalytics(event.data);
751
- await client.ack(event);
752
- }
753
-
754
- // Service 2: Notification (gets same message)
755
- for await (const event of client.take('events@notification')) {
756
- await sendNotification(event.data);
757
- await client.ack(event);
758
- }
759
-
760
- // Service 3: Audit (also gets same message)
761
- for await (const event of client.take('events@audit')) {
762
- await logEvent(event.data);
763
- await client.ack(event);
764
- }
765
- ```
766
-
767
- **Each consumer group maintains:**
768
- - Independent message status tracking
769
- - Separate partition leases
770
- - Individual retry counters
771
- - Isolated processing state
707
+ ## 🔑 Core Concepts
772
708
 
773
- ### Queue Mode vs Bus Mode
709
+ ### Message Flow
774
710
 
775
- **Queue Mode** (default - competitive consumption):
776
- ```javascript
777
- // Without consumer group - messages distributed
778
- await client.push('tasks', { id: 1 });
779
- await client.push('tasks', { id: 2 });
780
-
781
- // Worker 1 gets message 1
782
- for await (const msg of client.take('tasks')) { }
783
-
784
- // Worker 2 gets message 2 (different message)
785
- for await (const msg of client.take('tasks')) { }
786
- ```
711
+ 1. **Push**: Messages are encrypted and stored in PostgreSQL with metadata
712
+ 2. **Pop/Take**: Messages are leased to consumers with automatic lock management
713
+ 3. **Process**: Consumers process messages with automatic lease renewal
714
+ 4. **ACK**: Messages are marked complete and leases are released
715
+ 5. **Retry**: Failed messages are retried with exponential backoff
787
716
 
788
- **Bus Mode** (pub-sub with consumer groups):
789
- ```javascript
790
- // With consumer groups - all groups see all messages
791
- await client.push('events', { id: 1 });
717
+ ### Partition & Lease Management
792
718
 
793
- // Group 1 gets message 1
794
- for await (const msg of client.take('events@group1')) { }
719
+ - Each queue can have multiple partitions for parallel processing
720
+ - Consumers acquire exclusive leases on partitions
721
+ - Leases include unique IDs for validation and fencing
722
+ - Automatic lease renewal for long-running tasks
723
+ - Dead letter queue for exhausted retries
795
724
 
796
- // Group 2 also gets message 1 (same message)
797
- for await (const msg of client.take('events@group2')) { }
798
- ```
725
+ ### Scalability
799
726
 
800
- **Mixed Mode** (combine both):
801
- ```javascript
802
- // Competitive workers process jobs
803
- for await (const job of client.take('jobs')) {
804
- await processJob(job.data);
805
- await client.ack(job);
806
- }
727
+ - Horizontal scaling with multiple server instances
728
+ - Partition-based parallelism
729
+ - Connection pooling and query optimization
730
+ - WebSocket support for real-time updates
731
+ - Efficient batch operations
807
732
 
808
- // Monitoring sees all jobs (bus mode)
809
- for await (const job of client.take('jobs@monitoring')) {
810
- await monitorJob(job.data);
811
- await client.ack(job);
812
- }
813
- ```
733
+ ---
814
734
 
815
- ### Priority Processing
735
+ ## 📚 HTTP API Reference
816
736
 
817
- Configure priority at the **queue level**:
737
+ ### Queue Management
818
738
 
819
- ```javascript
820
- await client.queue('urgent-orders', { priority: 100 });
821
- await client.queue('normal-orders', { priority: 50 });
822
- await client.queue('batch-jobs', { priority: 10 });
739
+ ```bash
740
+ # Create/configure queue
741
+ curl -X POST http://localhost:6632/api/v1/configure \
742
+ -H "Content-Type: application/json" \
743
+ -d '{
744
+ "queue": "my-queue",
745
+ "options": {
746
+ "maxRetries": 3,
747
+ "visibilityTimeout": 30000
748
+ }
749
+ }'
823
750
 
824
- // Urgent orders processed first, then normal, then batch
751
+ # Delete queue
752
+ curl -X DELETE http://localhost:6632/api/v1/configure/my-queue
825
753
  ```
826
754
 
827
- ### Lease-Based Processing
828
-
829
- Messages are "leased" to workers for a specific duration. If not acknowledged within the lease time, they automatically return to pending status:
830
-
831
- ```javascript
832
- // Configure lease time
833
- await client.queue('long-tasks', {
834
- leaseTime: 600 // 10 minutes to process
835
- });
755
+ ### Message Operations
836
756
 
837
- // If worker crashes or takes too long:
838
- // - After 10 minutes, lease expires
839
- // - Message returns to pending
840
- // - Another worker can pick it up
757
+ ```bash
758
+ # Push messages
759
+ curl -X POST http://localhost:6632/api/v1/push \
760
+ -H "Content-Type: application/json" \
761
+ -d '{
762
+ "queue": "my-queue",
763
+ "messages": [
764
+ { "data": "message 1" },
765
+ { "data": "message 2", "priority": 100 }
766
+ ]
767
+ }'
768
+
769
+ # Pop messages (take)
770
+ curl -X POST http://localhost:6632/api/v1/pop \
771
+ -H "Content-Type: application/json" \
772
+ -d '{
773
+ "queue": "my-queue",
774
+ "batch": 10,
775
+ "visibilityTimeout": 30000
776
+ }'
777
+
778
+ # Acknowledge messages
779
+ curl -X POST http://localhost:6632/api/v1/ack \
780
+ -H "Content-Type: application/json" \
781
+ -d '{
782
+ "queue": "my-queue",
783
+ "transactionId": "msg-transaction-id",
784
+ "status": "completed",
785
+ "leaseId": "lease-uuid"
786
+ }'
841
787
  ```
842
788
 
843
- ### Delayed Processing
789
+ ### Advanced Operations
844
790
 
845
- Schedule messages for future processing:
791
+ ```bash
792
+ # Atomic transaction
793
+ curl -X POST http://localhost:6632/api/v1/transaction \
794
+ -H "Content-Type: application/json" \
795
+ -d '{
796
+ "operations": [
797
+ {
798
+ "type": "ack",
799
+ "queue": "input-queue",
800
+ "transactionId": "msg-id",
801
+ "status": "completed"
802
+ },
803
+ {
804
+ "type": "push",
805
+ "queue": "output-queue",
806
+ "messages": [{"data": "processed"}]
807
+ }
808
+ ],
809
+ "requiredLeases": ["lease-uuid-1", "lease-uuid-2"]
810
+ }'
846
811
 
847
- ```javascript
848
- await client.queue('scheduled-jobs', {
849
- delayedProcessing: 3600 // 1 hour delay
850
- });
812
+ # Extend lease
813
+ curl -X POST http://localhost:6632/api/v1/lease/lease-uuid/extend \
814
+ -H "Content-Type: application/json" \
815
+ -d '{}'
851
816
 
852
- await client.push('scheduled-jobs', {
853
- reportType: 'daily-sales'
854
- });
855
- // Message won't be available for processing until 1 hour later
817
+ # Get queue status
818
+ curl http://localhost:6632/api/v1/status/my-queue
856
819
  ```
857
820
 
858
- ### Window Buffering
859
-
860
- Batch messages within a time window:
821
+ ---
861
822
 
862
- ```javascript
863
- await client.queue('analytics', {
864
- windowBuffer: 60 // Wait 60 seconds to accumulate messages
865
- });
823
+ ## 📊 Dashboard
866
824
 
867
- // Messages held for 60 seconds to allow efficient batching
868
- ```
825
+ Access the real-time dashboard at `http://localhost:6632/`
869
826
 
870
- ### Retry and Dead Letter Queue
827
+ ### Features
828
+ - Real-time queue metrics
829
+ - Message browser with search
830
+ - Consumer group monitoring
831
+ - System health indicators
832
+ - Performance graphs
871
833
 
834
+ ### WebSocket Events
872
835
  ```javascript
873
- await client.queue('payments', {
874
- retryLimit: 3, // Retry up to 3 times
875
- dlqAfterMaxRetries: true // Move to DLQ after max retries
836
+ const ws = new WebSocket('ws://localhost:6632/ws/dashboard');
837
+ ws.on('message', (data) => {
838
+ const event = JSON.parse(data);
839
+ console.log('Queue event:', event);
876
840
  });
877
-
878
- // Failed messages automatically retry
879
- await client.ack(message, false); // Will retry if retries < 3
880
-
881
- // After 3 failures, message moves to dead_letter status
882
841
  ```
883
842
 
884
- ### Enterprise Features
843
+ ---
885
844
 
886
- #### 1. Encryption (AES-256-GCM)
845
+ ## 🧪 Testing
887
846
 
888
847
  ```bash
889
- # Generate encryption key
890
- export QUEEN_ENCRYPTION_KEY=$(openssl rand -hex 32)
891
- ```
892
-
893
- ```javascript
894
- await client.queue('sensitive-data', {
895
- encryptionEnabled: true
896
- });
897
-
898
- // Messages encrypted at rest in database
899
- await client.push('sensitive-data', { ssn: '123-45-6789' });
900
- ```
901
-
902
- #### 2. Message Retention
903
-
904
- ```javascript
905
- await client.queue('temp-queue', {
906
- retentionSeconds: 3600, // Delete pending after 1 hour
907
- completedRetentionSeconds: 300, // Delete completed after 5 minutes
908
- retentionEnabled: true
909
- });
910
- ```
848
+ # Run test suite
849
+ npm test
911
850
 
912
- #### 3. Message Eviction (SLA Enforcement)
851
+ # Run specific test
852
+ npm test -- --grep "Pipeline"
913
853
 
914
- ```javascript
915
- await client.queue('time-sensitive', {
916
- maxWaitTimeSeconds: 60 // Evict messages older than 1 minute
917
- });
854
+ # Benchmark
855
+ npm run benchmark
918
856
  ```
919
857
 
920
- ### Best Practices
921
-
922
- **1. Partition Strategy**
923
- - Use user IDs for per-user ordering
924
- - Use resource IDs for per-resource ordering
925
- - Use round-robin for load distribution
926
- - Keep partition counts manageable (10-100s, not 1000s)
927
-
928
- **2. Lease Management**
929
- - Set lease time slightly longer than expected processing time
930
- - Handle timeouts gracefully
931
- - Acknowledge messages as soon as processing completes
932
-
933
- **3. Consumer Group Design**
934
- - One clear purpose per consumer group
935
- - Design groups to be independent
936
- - Ensure operations are idempotent
937
-
938
- **4. Error Handling**
939
- - Always wrap processing in try-catch
940
- - Provide meaningful error messages in ack
941
- - Use retry limits appropriately
942
- - Monitor dead letter queue
943
-
944
858
  ---
945
859
 
946
- ## 🚀 Cursor-Based Consumption Strategy
947
-
948
- Queen uses a **cursor-based consumption model** for optimal performance at scale, providing O(batch_size) constant-time operations regardless of queue depth.
949
-
950
- ### How It Works
951
-
952
- Traditional message queues scan through all messages to find pending ones, leading to performance degradation as messages accumulate. Queen's cursor-based approach maintains a position marker (cursor) for each consumer, allowing direct access to the next batch of messages.
953
-
954
- **Partition Cursors:**
860
+ ## 🤝 Contributing
955
861
 
956
- Each partition maintains a cursor position per consumer group:
957
- - `last_consumed_created_at`: Timestamp of last consumed message
958
- - `last_consumed_id`: UUID of last consumed message (tie-breaker for same timestamp)
959
- - `total_messages_consumed`: Running count of consumed messages
862
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
960
863
 
961
- The cursor always moves **forward** in time, ensuring strict FIFO ordering.
864
+ ---
962
865
 
963
- ### The takeBatch Method
866
+ ## 📄 License
964
867
 
965
- Queen provides two consumption methods:
966
-
967
- **1. `take()` - Individual message iterator:**
968
- ```javascript
969
- // Processes messages one at a time
970
- for await (const message of client.take('orders', { batch: 1000 })) {
971
- await processOrder(message.data);
972
- await client.ack(message);
973
- }
974
- ```
975
-
976
- **2. `takeBatch()` - Array iterator (HIGH PERFORMANCE):**
977
- ```javascript
978
- // Yields arrays of messages - achieves 100k+ msg/s throughput
979
- for await (const messages of client.takeBatch('orders', { batch: 1000 })) {
980
- // messages is an array of up to 1000 message objects
981
- await processBatch(messages.map(m => m.data));
982
-
983
- // Batch acknowledge - single DB transaction for all messages
984
- await client.ack(messages);
985
- }
986
- ```
987
-
988
- **Under the hood**, both methods use cursor-based batch retrieval:
989
-
990
- ```sql
991
- -- Cursor-based query (simplified)
992
- SELECT * FROM messages
993
- WHERE partition_id = $1
994
- AND id > $2::uuid -- Start after last cursor position
995
- ORDER BY created_at ASC, id ASC
996
- LIMIT $3 -- Batch size
997
- FOR UPDATE SKIP LOCKED
998
- ```
999
-
1000
- **Key characteristics:**
1001
- 1. **Constant-time**: Performance stays consistent whether you've consumed 0% or 99% of messages
1002
- 2. **FIFO guarantee**: Messages always returned in creation order
1003
- 3. **Lock-free scanning**: `SKIP LOCKED` prevents contention between consumers
1004
- 4. **Efficient**: No table scans - direct cursor-based access using UUIDv7 (time-ordered)
1005
-
1006
- **Performance tip:** Use `takeBatch()` with large batch sizes (1,000-10,000) for maximum throughput. The server fetches messages in batches regardless, but `takeBatch()` gives you the array directly, allowing bulk processing and batch acknowledgment in a single operation.
1007
-
1008
- ### Batch Acknowledgment Semantics
1009
-
1010
- Queen handles batch acknowledgments intelligently:
1011
-
1012
- **Partial Success** (some messages succeed, some fail):
1013
- ```javascript
1014
- // Batch: 10,000 messages
1015
- // Success: 9,999 messages
1016
- // Failed: 1 message
1017
-
1018
- // Behavior:
1019
- // ✅ Cursor advances past all 10,000 messages
1020
- // ✅ Failed message moved to Dead Letter Queue
1021
- // ✅ Next take() starts from message 10,001
1022
- // ✅ FIFO maintained, no redelivery of successful messages
1023
- ```
1024
-
1025
- **Total Batch Failure** (all messages fail):
1026
- ```javascript
1027
- // Batch: 10,000 messages
1028
- // Success: 0 messages
1029
- // Failed: 10,000 messages
1030
-
1031
- // Behavior:
1032
- // ❌ Cursor DOES NOT advance
1033
- // ❌ Messages NOT moved to DLQ
1034
- // ✅ Lease released
1035
- // ✅ Next take() gets SAME batch (retry)
1036
- // ✅ Allows recovery from transient failures
1037
- ```
1038
-
1039
- This design handles transient failures (network issues, service outages) gracefully while preventing poison messages from blocking the queue.
1040
-
1041
- ### Performance Comparison
1042
-
1043
- | Operation | Traditional Approach | Cursor Approach | Improvement |
1044
- |-----------|---------------------|-----------------|-------------|
1045
- | Pop @ 0% consumed | O(partition_size) | O(batch_size) | Same |
1046
- | Pop @ 50% consumed | O(partition_size) | O(batch_size) | **10-100x faster** |
1047
- | Pop @ 99% consumed | O(partition_size) | O(batch_size) | **100-1000x faster** |
1048
-
1049
- **Real-world benchmark** (1M messages):
1050
- ```
1051
- Traditional:
1052
- Early batches: 300ms per pop
1053
- Late batches: 3500ms per pop (10x degradation)
1054
-
1055
- Cursor-based:
1056
- Early batches: 150ms per pop
1057
- Late batches: 200ms per pop (constant!)
1058
- ```
1059
-
1060
- ### Dead Letter Queue
1061
-
1062
- Individual message failures are moved to the Dead Letter Queue for inspection and manual intervention:
1063
-
1064
- ```javascript
1065
- // Monitor DLQ
1066
- const response = await fetch('http://localhost:6632/api/v1/analytics/dlq');
1067
- const dlqMessages = await response.json();
1068
-
1069
- // Inspect failed messages
1070
- for (const msg of dlqMessages) {
1071
- console.log(`Failed: ${msg.error_message}`);
1072
-
1073
- // After fixing issue, can re-push if needed
1074
- await client.push(msg.queue, fixedPayload);
1075
- }
1076
- ```
1077
-
1078
- **DLQ Query:**
1079
- ```sql
1080
- SELECT * FROM queen.dead_letter_queue
1081
- WHERE consumer_group = 'my-group'
1082
- ORDER BY failed_at DESC
1083
- LIMIT 100;
1084
- ```
1085
-
1086
- ### Batch Size Guidelines
1087
-
1088
- Choose batch sizes based on your workload:
1089
-
1090
- **Smaller batches (100-1,000):**
1091
- - ✅ Faster individual batch processing
1092
- - ✅ Less impact if entire batch fails
1093
- - ✅ Lower memory footprint
1094
- - ❌ More network round-trips
1095
-
1096
- **Larger batches (5,000-10,000):**
1097
- - ✅ Higher throughput (100,000+ msg/sec achievable)
1098
- - ✅ Fewer network round-trips
1099
- - ✅ Better database efficiency
1100
- - ❌ More messages retry if entire batch fails
1101
- - ❌ Higher memory usage
1102
-
1103
- **Recommendation:** Start with 1,000-2,000 for balanced performance. Increase to 5,000-10,000 for maximum throughput with reliable processing.
1104
-
1105
- ### Monitoring Cursor Progress
1106
-
1107
- Track consumption progress via SQL:
1108
-
1109
- ```sql
1110
- -- View cursor positions
1111
- SELECT
1112
- p.name as partition,
1113
- pc.consumer_group,
1114
- pc.total_messages_consumed,
1115
- pc.total_batches_consumed,
1116
- pc.last_consumed_at,
1117
- EXTRACT(EPOCH FROM (NOW() - pc.last_consumed_at)) as seconds_since_last_consume
1118
- FROM queen.partition_cursors pc
1119
- JOIN queen.partitions p ON p.id = pc.partition_id
1120
- ORDER BY pc.last_consumed_at DESC;
1121
-
1122
- -- Monitor DLQ
1123
- SELECT COUNT(*) as failed_count, consumer_group
1124
- FROM queen.dead_letter_queue
1125
- GROUP BY consumer_group;
1126
- ```
1127
-
1128
- ---
1129
-
1130
- ## 🔌 HTTP API Reference
1131
-
1132
- Base URL: `http://localhost:6632/api/v1`
1133
-
1134
- ### Push Messages
1135
-
1136
- **Endpoint:** `POST /api/v1/push`
1137
-
1138
- **Request:**
1139
- ```json
1140
- {
1141
- "items": [
1142
- {
1143
- "queue": "orders",
1144
- "partition": "urgent",
1145
- "payload": { "orderId": 123, "amount": 99.99 },
1146
- "transactionId": "optional-idempotency-key",
1147
- "traceId": "550e8400-e29b-41d4-a716-446655440000"
1148
- }
1149
- ]
1150
- }
1151
- ```
1152
-
1153
- **Response:**
1154
- ```json
1155
- {
1156
- "messages": [
1157
- {
1158
- "id": "018e63b7-6165-453f-88ae-56effa177605",
1159
- "transactionId": "4dfb0478-655b-4c91-bcd9-b7acacf0400f",
1160
- "status": "queued"
1161
- }
1162
- ]
1163
- }
1164
- ```
1165
-
1166
- ### Pop Messages
1167
-
1168
- **From specific partition:**
1169
- ```
1170
- GET /api/v1/pop/queue/{queue}/partition/{partition}?wait=true&timeout=30000&batch=10
1171
- ```
1172
-
1173
- **From any partition in queue:**
1174
- ```
1175
- GET /api/v1/pop/queue/{queue}?wait=true&timeout=30000&batch=10
1176
- ```
1177
-
1178
- **With namespace/task filter:**
1179
- ```
1180
- GET /api/v1/pop?namespace=ecommerce&task=checkout&wait=true&timeout=30000&batch=10
1181
- ```
1182
-
1183
- **With consumer group (bus mode):**
1184
- ```
1185
- GET /api/v1/pop/queue/{queue}?consumerGroup=analytics&subscriptionMode=all&wait=true&timeout=30000
1186
- ```
1187
-
1188
- **Response:**
1189
- ```json
1190
- {
1191
- "messages": [
1192
- {
1193
- "id": "018e63b7-6165-453f-88ae-56effa177605",
1194
- "transactionId": "4dfb0478-655b-4c91-bcd9-b7acacf0400f",
1195
- "queue": "orders",
1196
- "partition": "urgent",
1197
- "data": { "orderId": 123, "amount": 99.99 },
1198
- "retryCount": 0,
1199
- "priority": 10,
1200
- "createdAt": "2024-10-08T12:00:00.000Z",
1201
- "options": { "leaseTime": 300, "retryLimit": 3 }
1202
- }
1203
- ]
1204
- }
1205
- ```
1206
-
1207
- ### Acknowledge Messages
1208
-
1209
- **Single:**
1210
- ```json
1211
- POST /api/v1/ack
1212
- {
1213
- "transactionId": "4dfb0478-655b-4c91-bcd9-b7acacf0400f",
1214
- "status": "completed",
1215
- "consumerGroup": "analytics",
1216
- "error": null
1217
- }
1218
- ```
1219
-
1220
- **Batch:**
1221
- ```json
1222
- POST /api/v1/ack/batch
1223
- {
1224
- "acknowledgments": [
1225
- { "transactionId": "uuid-1", "status": "completed" },
1226
- { "transactionId": "uuid-2", "status": "failed", "error": "Processing error" }
1227
- ]
1228
- }
1229
- ```
1230
-
1231
- ### Configure Queue
1232
-
1233
- ```json
1234
- POST /api/v1/configure
1235
- {
1236
- "queue": "orders",
1237
- "namespace": "ecommerce",
1238
- "task": "checkout",
1239
- "options": {
1240
- "leaseTime": 600,
1241
- "retryLimit": 5,
1242
- "priority": 10,
1243
- "maxSize": 10000,
1244
- "ttl": 3600,
1245
- "dlqAfterMaxRetries": true,
1246
- "delayedProcessing": 0,
1247
- "windowBuffer": 0,
1248
- "retentionSeconds": 0,
1249
- "completedRetentionSeconds": 0,
1250
- "retentionEnabled": false,
1251
- "encryptionEnabled": false,
1252
- "maxWaitTimeSeconds": 0
1253
- }
1254
- }
1255
- ```
1256
-
1257
- ### Analytics
1258
-
1259
- **Queue statistics:**
1260
- ```
1261
- GET /api/v1/analytics/queue/{queue}
1262
- ```
1263
-
1264
- **All queues overview:**
1265
- ```
1266
- GET /api/v1/analytics/queues
1267
- ```
1268
-
1269
- **Queue depths:**
1270
- ```
1271
- GET /api/v1/analytics/queue-depths
1272
- ```
1273
-
1274
- **Throughput metrics:**
1275
- ```
1276
- GET /api/v1/analytics/throughput
1277
- ```
1278
-
1279
- **Queue lag analysis:**
1280
- ```
1281
- GET /api/v1/analytics/queue-lag?queue=orders
1282
- ```
1283
-
1284
- ### Message Management
1285
-
1286
- **List messages:**
1287
- ```
1288
- GET /api/v1/messages?queue=orders&status=pending&limit=100
1289
- ```
1290
-
1291
- **Get single message:**
1292
- ```
1293
- GET /api/v1/messages/{transactionId}
1294
- ```
1295
-
1296
- **Delete message:**
1297
- ```
1298
- DELETE /api/v1/messages/{transactionId}
1299
- ```
1300
-
1301
- **Retry failed message:**
1302
- ```
1303
- POST /api/v1/messages/{transactionId}/retry
1304
- ```
1305
-
1306
- **Move to dead letter queue:**
1307
- ```
1308
- POST /api/v1/messages/{transactionId}/dlq
1309
- ```
1310
-
1311
- **Clear queue:**
1312
- ```
1313
- DELETE /api/v1/queues/{queue}/clear
1314
- ```
1315
-
1316
- **Delete queue:**
1317
- ```
1318
- DELETE /api/v1/resources/queues/{queue}
1319
- ```
1320
- _Note: Deletes the queue and all its partitions, messages, and related data._
1321
-
1322
- ### System Health
1323
-
1324
- **Health check:**
1325
- ```
1326
- GET /health
1327
- ```
1328
-
1329
- **Detailed metrics:**
1330
- ```
1331
- GET /metrics
1332
- ```
1333
-
1334
- ### WebSocket (Real-time Updates)
1335
-
1336
- **Connect:**
1337
- ```javascript
1338
- const ws = new WebSocket('ws://localhost:6632/ws/dashboard');
1339
-
1340
- ws.onmessage = (event) => {
1341
- const { event: eventType, data } = JSON.parse(event.data);
1342
- // Handle events: message.pushed, message.completed, queue.depth, etc.
1343
- };
1344
- ```
1345
-
1346
- **Events:**
1347
- - `message.pushed` - New message added
1348
- - `message.processing` - Message being processed
1349
- - `message.completed` - Message completed
1350
- - `message.failed` - Message failed
1351
- - `queue.created` - New queue created
1352
- - `queue.depth` - Queue depth update (every 5s)
1353
- - `system.stats` - System statistics (every 10s)
1354
-
1355
- See [API.md](API.md) for complete API documentation.
1356
-
1357
- ---
1358
-
1359
- ## 📊 Dashboard
1360
-
1361
- Queen includes a comprehensive web dashboard for monitoring and management.
1362
-
1363
- [Dashboard](/assets/dashboard-01.png)
1364
-
1365
- ### Access
1366
-
1367
- 1. Start the server: `npm start`
1368
- 2. Open browser: `http://localhost:4000`
1369
- 3. WebSocket connection provides real-time updates
1370
-
1371
- ### Features
1372
-
1373
- **System Overview**
1374
- - Real-time metrics: total messages, processing rate, system health
1375
- - Queue summary with pending/processing/completed counts
1376
- - Performance indicators: throughput, latency, error rates
1377
-
1378
- **Queue Management**
1379
- - Queue list with status and message counts
1380
- - Partition view with priority indicators
1381
- - Message browser with search and filter
1382
- - Retry and DLQ management
1383
-
1384
- **Real-time Monitoring**
1385
- - Live updates via WebSocket
1386
- - Throughput charts (messages per second over time)
1387
- - Queue depth graphs with trend analysis
1388
- - Lag monitoring (processing time and backlog)
1389
-
1390
- **Analytics Dashboard**
1391
- - Performance metrics per queue
1392
- - Historical trends and patterns
1393
- - System health monitoring
1394
- - Database connections and memory usage
1395
-
1396
- **Message Browser**
1397
- - Search by queue, partition, status, time range
1398
- - View full payload and metadata
1399
- - Manually retry failed messages
1400
- - Dead letter queue management
1401
-
1402
- ### Dashboard Development
1403
-
1404
- The dashboard is built with Vue.js and located in the `dashboard/` directory:
1405
-
1406
- ```bash
1407
- cd dashboard
1408
- npm install
1409
- npm run dev # Development mode
1410
- npm run build # Production build
1411
- ```
1412
-
1413
- ---
1414
-
1415
- ## ⚙️ Configuration
1416
-
1417
- All configuration uses environment variables with sensible defaults. Configuration is centralized in `src/config.js`.
1418
-
1419
- ### Server Configuration
1420
-
1421
- ```bash
1422
- PORT=6632 # Server port (default: 6632)
1423
- HOST=0.0.0.0 # Server host (default: 0.0.0.0)
1424
- WORKER_ID=worker-1 # Worker identifier
1425
- APP_NAME=queen-mq # Application name
1426
-
1427
- # CORS
1428
- CORS_MAX_AGE=86400
1429
- CORS_ALLOWED_ORIGINS=*
1430
- CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
1431
- CORS_ALLOWED_HEADERS=Content-Type,Authorization
1432
- ```
1433
-
1434
- ### Database Configuration
1435
-
1436
- ```bash
1437
- # Connection
1438
- PG_USER=postgres
1439
- PG_HOST=localhost
1440
- PG_DB=postgres
1441
- PG_PASSWORD=postgres
1442
- PG_PORT=5432
1443
-
1444
- # Connection pool
1445
- DB_POOL_SIZE=20 # Max connections
1446
- DB_IDLE_TIMEOUT=30000 # Idle timeout (ms)
1447
- DB_CONNECTION_TIMEOUT=2000 # Connection timeout (ms)
1448
- DB_STATEMENT_TIMEOUT=30000 # Statement timeout (ms)
1449
- DB_QUERY_TIMEOUT=30000 # Query timeout (ms)
1450
- DB_MAX_RETRIES=3 # Max retry attempts
1451
- ```
1452
-
1453
- ### Queue Processing
1454
-
1455
- ```bash
1456
- # Pop defaults
1457
- DEFAULT_TIMEOUT=30000 # Default pop timeout (ms)
1458
- MAX_TIMEOUT=60000 # Maximum pop timeout (ms)
1459
- DEFAULT_BATCH_SIZE=1 # Default batch size
1460
- BATCH_INSERT_SIZE=1000 # Batch size for bulk inserts
1461
-
1462
- # Long polling
1463
- QUEUE_POLL_INTERVAL=100 # Poll interval (ms)
1464
- QUEUE_POLL_INTERVAL_FILTERED=1000 # Poll interval for filtered pops (ms)
1465
-
1466
- # Queue defaults
1467
- DEFAULT_LEASE_TIME=300 # Lease time (seconds)
1468
- DEFAULT_RETRY_LIMIT=3 # Retry limit
1469
- DEFAULT_RETRY_DELAY=1000 # Retry delay (ms)
1470
- DEFAULT_MAX_SIZE=10000 # Max queue size
1471
- DEFAULT_TTL=3600 # TTL (seconds)
1472
- DEFAULT_PRIORITY=0 # Priority
1473
- DEFAULT_DELAYED_PROCESSING=0 # Delayed processing (seconds)
1474
- DEFAULT_WINDOW_BUFFER=0 # Window buffer (seconds)
1475
- ```
1476
-
1477
- ### Background Jobs
1478
-
1479
- ```bash
1480
- LEASE_RECLAIM_INTERVAL=5000 # Lease reclamation (ms)
1481
- RETENTION_INTERVAL=300000 # Retention checks (ms)
1482
- RETENTION_BATCH_SIZE=1000 # Retention batch size
1483
- PARTITION_CLEANUP_DAYS=7 # Days before cleaning empty partitions
1484
- EVICTION_INTERVAL=60000 # Eviction checks (ms)
1485
- EVICTION_BATCH_SIZE=1000 # Eviction batch size
1486
- ```
1487
-
1488
- ### WebSocket
1489
-
1490
- ```bash
1491
- WS_COMPRESSION=0 # Compression level
1492
- WS_MAX_PAYLOAD_LENGTH=16384 # Max payload (bytes)
1493
- WS_IDLE_TIMEOUT=60 # Idle timeout (seconds)
1494
- WS_MAX_CONNECTIONS=1000 # Max connections
1495
- WS_HEARTBEAT_INTERVAL=30000 # Heartbeat (ms)
1496
- ```
1497
-
1498
- ### Encryption
1499
-
1500
- ```bash
1501
- # Generate key: openssl rand -hex 32
1502
- QUEEN_ENCRYPTION_KEY=<64-hex-chars> # AES-256-GCM encryption key
1503
- ```
1504
-
1505
- ### Client SDK
1506
-
1507
- ```bash
1508
- QUEEN_BASE_URL=http://localhost:6632
1509
- CLIENT_RETRY_ATTEMPTS=3
1510
- CLIENT_RETRY_DELAY=1000
1511
- CLIENT_RETRY_BACKOFF=2
1512
- CLIENT_POOL_SIZE=10
1513
- CLIENT_REQUEST_TIMEOUT=30000
1514
- ```
1515
-
1516
- ### Queue Options
1517
-
1518
- ```javascript
1519
- {
1520
- // Processing
1521
- leaseTime: 300, // Seconds before lease expires
1522
- retryLimit: 3, // Max retry attempts
1523
- priority: 0, // Queue priority (higher = first)
1524
- delayedProcessing: 0, // Delay in seconds
1525
- windowBuffer: 0, // Buffer time for batching
1526
- dlqAfterMaxRetries: true, // Move to DLQ after max retries
1527
-
1528
- // Encryption (Queue-level)
1529
- encryptionEnabled: false, // Enable AES-256-GCM encryption
1530
-
1531
- // Retention (Partition-level)
1532
- retentionSeconds: 0, // Delete pending messages after X seconds
1533
- completedRetentionSeconds: 0, // Delete completed/failed after X seconds
1534
- partitionRetentionSeconds: 0, // Delete empty partitions after X seconds
1535
- retentionEnabled: false, // Enable retention
1536
-
1537
- // Eviction (Queue-level)
1538
- maxWaitTimeSeconds: 0 // Evict messages older than X seconds
1539
- }
1540
- ```
1541
-
1542
- ---
1543
-
1544
- ## 📚 Full Examples
1545
-
1546
- ### Example 1: Email Queue with Priority
1547
-
1548
- ```javascript
1549
- import { Queen } from 'queen-mq';
1550
-
1551
- const client = new Queen({
1552
- baseUrls: ['http://localhost:6632']
1553
- });
1554
-
1555
- // Configure queues with different priorities
1556
- await client.queue('emails-urgent', {
1557
- priority: 10,
1558
- leaseTime: 300,
1559
- retryLimit: 5
1560
- });
1561
-
1562
- await client.queue('emails-normal', {
1563
- priority: 5,
1564
- leaseTime: 300,
1565
- retryLimit: 3
1566
- });
1567
-
1568
- // Producer: Send emails
1569
- async function sendEmails() {
1570
- // Urgent email
1571
- await client.push('emails-urgent', {
1572
- to: 'admin@company.com',
1573
- subject: 'Critical Alert',
1574
- body: 'System issue detected',
1575
- timestamp: Date.now()
1576
- });
1577
-
1578
- // Normal email
1579
- await client.push('emails-normal', {
1580
- to: 'user@example.com',
1581
- subject: 'Welcome',
1582
- body: 'Thanks for signing up',
1583
- timestamp: Date.now()
1584
- });
1585
- }
1586
-
1587
- // Consumer: Process emails
1588
- async function processEmails() {
1589
- // Urgent emails processed first (higher priority)
1590
- for await (const email of client.take('emails-urgent', {
1591
- wait: true,
1592
- timeout: 30000
1593
- })) {
1594
- try {
1595
- console.log('Sending urgent email:', email.data.to);
1596
- await sendEmail(email.data);
1597
- await client.ack(email);
1598
- } catch (error) {
1599
- console.error('Failed to send email:', error);
1600
- await client.ack(email, false, { error: error.message });
1601
- }
1602
- }
1603
- }
1604
-
1605
- // Send batch of emails
1606
- await sendEmails();
1607
-
1608
- // Start processing
1609
- processEmails().catch(console.error);
1610
- ```
1611
-
1612
- ### Example 2: Task Pipeline
1613
-
1614
- ```javascript
1615
- import { Queen } from 'queen-mq';
1616
-
1617
- const client = new Queen({
1618
- baseUrls: ['http://localhost:6632']
1619
- });
1620
-
1621
- // Configure pipeline stages
1622
- await client.queue('stage-1-validate', { priority: 10 });
1623
- await client.queue('stage-2-process', { priority: 9 });
1624
- await client.queue('stage-3-finalize', { priority: 8 });
1625
-
1626
- // Stage 1: Validate
1627
- async function validateStage() {
1628
- for await (const msg of client.take('stage-1-validate', { wait: true })) {
1629
- try {
1630
- const validated = await validate(msg.data);
1631
- await client.ack(msg);
1632
-
1633
- // Pass to next stage
1634
- await client.push('stage-2-process', validated);
1635
- } catch (error) {
1636
- await client.ack(msg, false, { error: error.message });
1637
- }
1638
- }
1639
- }
1640
-
1641
- // Stage 2: Process
1642
- async function processStage() {
1643
- for await (const msg of client.take('stage-2-process', { wait: true })) {
1644
- try {
1645
- const processed = await process(msg.data);
1646
- await client.ack(msg);
1647
-
1648
- // Pass to next stage
1649
- await client.push('stage-3-finalize', processed);
1650
- } catch (error) {
1651
- await client.ack(msg, false, { error: error.message });
1652
- }
1653
- }
1654
- }
1655
-
1656
- // Stage 3: Finalize
1657
- async function finalizeStage() {
1658
- for await (const msg of client.take('stage-3-finalize', { wait: true })) {
1659
- try {
1660
- await finalize(msg.data);
1661
- await client.ack(msg);
1662
- console.log('Pipeline complete:', msg.data.id);
1663
- } catch (error) {
1664
- await client.ack(msg, false, { error: error.message });
1665
- }
1666
- }
1667
- }
1668
-
1669
- // Start pipeline
1670
- Promise.all([
1671
- validateStage(),
1672
- processStage(),
1673
- finalizeStage()
1674
- ]);
1675
-
1676
- // Add work to pipeline
1677
- await client.push('stage-1-validate', { id: 1, data: 'raw data' });
1678
- ```
1679
-
1680
- ### Example 3: Event Streaming (Bus Mode)
1681
-
1682
- ```javascript
1683
- import { Queen } from 'queen-mq';
1684
-
1685
- const client = new Queen({
1686
- baseUrls: ['http://localhost:6632']
1687
- });
1688
-
1689
- // Configure event queue
1690
- await client.queue('events', {
1691
- priority: 10,
1692
- leaseTime: 60
1693
- });
1694
-
1695
- // Producer: Emit events
1696
- async function emitEvents() {
1697
- await client.push('events', {
1698
- type: 'order.created',
1699
- orderId: 12345,
1700
- userId: 789,
1701
- amount: 99.99,
1702
- timestamp: Date.now()
1703
- });
1704
- }
1705
-
1706
- // Consumer 1: Analytics Service
1707
- async function analyticsService() {
1708
- for await (const event of client.take('events@analytics', {
1709
- subscriptionMode: 'all', // Replay all messages
1710
- wait: true
1711
- })) {
1712
- console.log('[Analytics] Processing event:', event.data.type);
1713
- await updateAnalytics(event.data);
1714
- await client.ack(event, true, { group: 'analytics' });
1715
- }
1716
- }
1717
-
1718
- // Consumer 2: Notification Service
1719
- async function notificationService() {
1720
- for await (const event of client.take('events@notifications', {
1721
- subscriptionMode: 'new', // Only new messages
1722
- wait: true
1723
- })) {
1724
- console.log('[Notifications] Processing event:', event.data.type);
1725
- await sendNotification(event.data);
1726
- await client.ack(event, true, { group: 'notifications' });
1727
- }
1728
- }
1729
-
1730
- // Consumer 3: Audit Service
1731
- async function auditService() {
1732
- for await (const event of client.take('events@audit', {
1733
- subscriptionMode: 'all', // Log everything
1734
- wait: true
1735
- })) {
1736
- console.log('[Audit] Logging event:', event.data.type);
1737
- await logToAudit(event.data);
1738
- await client.ack(event, true, { group: 'audit' });
1739
- }
1740
- }
1741
-
1742
- // Start all services (they all see the same events)
1743
- Promise.all([
1744
- analyticsService(),
1745
- notificationService(),
1746
- auditService()
1747
- ]);
1748
-
1749
- // Emit events
1750
- await emitEvents();
1751
- ```
1752
-
1753
- ### Example 4: Batch Processing (High Throughput)
1754
-
1755
- ```javascript
1756
- import { Queen } from 'queen-mq';
1757
-
1758
- const client = new Queen({
1759
- baseUrls: ['http://localhost:6632']
1760
- });
1761
-
1762
- // Configure for batch processing
1763
- await client.queue('data-processing', {
1764
- priority: 5,
1765
- leaseTime: 600, // 10 minutes for batch
1766
- windowBuffer: 30 // Buffer for 30 seconds
1767
- });
1768
-
1769
- // Producer: Send data
1770
- async function sendData() {
1771
- const records = [];
1772
- for (let i = 0; i < 100000; i++) {
1773
- records.push({ id: i, value: Math.random() });
1774
- }
1775
-
1776
- // Push in batches
1777
- await client.push('data-processing/analytics', records);
1778
- }
1779
-
1780
- // Consumer: HIGH PERFORMANCE batch processor using takeBatch()
1781
- async function batchProcessor() {
1782
- const BATCH_SIZE = 5000; // Large batches for 100k+ msg/s throughput
1783
-
1784
- // takeBatch() yields arrays directly - no manual batching needed!
1785
- for await (const messages of client.takeBatch('data-processing/analytics', {
1786
- batch: BATCH_SIZE,
1787
- wait: true,
1788
- timeout: 30000
1789
- })) {
1790
- try {
1791
- console.log(`Processing batch of ${messages.length} records`);
1792
-
1793
- // Extract data
1794
- const records = messages.map(m => m.data);
1795
-
1796
- // Bulk process (single DB operation)
1797
- await bulkInsertToDatabase(records);
1798
-
1799
- // Batch acknowledge (single DB transaction!)
1800
- await client.ack(messages);
1801
-
1802
- console.log(`✓ Batch complete in single transaction`);
1803
- } catch (error) {
1804
- console.error('Batch processing failed:', error);
1805
-
1806
- // Mark entire batch as failed (single transaction)
1807
- await client.ack(messages, false, { error: error.message });
1808
- }
1809
- }
1810
- }
1811
-
1812
- // Run
1813
- await sendData();
1814
- await batchProcessor();
1815
-
1816
- // Performance characteristics:
1817
- // - Batch size 5000: ~100,000 messages/second
1818
- // - Single DB transaction per batch (fetch + ack)
1819
- // - Constant memory usage
1820
- // - No performance degradation as queue grows
1821
- ```
1822
-
1823
- ### Example 5: Scheduled Jobs
1824
-
1825
- ```javascript
1826
- import { Queen } from 'queen-mq';
1827
-
1828
- const client = new Queen({
1829
- baseUrls: ['http://localhost:6632']
1830
- });
1831
-
1832
- // Configure with delayed processing
1833
- await client.queue('scheduled-jobs', {
1834
- delayedProcessing: 3600, // 1 hour delay
1835
- priority: 5
1836
- });
1837
-
1838
- // Schedule a job
1839
- async function scheduleReport() {
1840
- await client.push('scheduled-jobs/daily-reports', {
1841
- reportType: 'daily-sales',
1842
- date: new Date().toISOString().split('T')[0],
1843
- recipients: ['manager@company.com'],
1844
- scheduledAt: Date.now()
1845
- });
1846
-
1847
- console.log('Report scheduled for processing in 1 hour');
1848
- }
1849
-
1850
- // Process scheduled jobs
1851
- async function processScheduledJobs() {
1852
- for await (const job of client.take('scheduled-jobs/daily-reports', {
1853
- wait: true
1854
- })) {
1855
- try {
1856
- console.log('Generating report:', job.data.reportType);
1857
- await generateReport(job.data);
1858
- await client.ack(job);
1859
- } catch (error) {
1860
- await client.ack(job, false, { error: error.message });
1861
- }
1862
- }
1863
- }
1864
-
1865
- await scheduleReport();
1866
- processScheduledJobs().catch(console.error);
1867
- ```
1868
-
1869
- ### Example 6: Rate Limiting
1870
-
1871
- ```javascript
1872
- import { Queen } from 'queen-mq';
1873
-
1874
- const client = new Queen({
1875
- baseUrls: ['http://localhost:6632']
1876
- });
1877
-
1878
- await client.queue('api-calls', {
1879
- priority: 5,
1880
- leaseTime: 60
1881
- });
1882
-
1883
- // Producer: Queue API calls
1884
- async function queueApiCalls(calls) {
1885
- await client.push('api-calls', calls);
1886
- }
1887
-
1888
- // Consumer: Rate-limited processor (10 calls per second max)
1889
- async function rateLimitedProcessor() {
1890
- const RATE_LIMIT = 10; // calls per second
1891
- const INTERVAL = 1000; // 1 second
1892
-
1893
- let callsThisInterval = 0;
1894
- let intervalStart = Date.now();
1895
-
1896
- for await (const call of client.take('api-calls', { wait: true })) {
1897
- // Check if we need to wait
1898
- if (callsThisInterval >= RATE_LIMIT) {
1899
- const elapsed = Date.now() - intervalStart;
1900
- if (elapsed < INTERVAL) {
1901
- await new Promise(r => setTimeout(r, INTERVAL - elapsed));
1902
- }
1903
- callsThisInterval = 0;
1904
- intervalStart = Date.now();
1905
- }
1906
-
1907
- try {
1908
- await makeApiCall(call.data);
1909
- await client.ack(call);
1910
- callsThisInterval++;
1911
- } catch (error) {
1912
- await client.ack(call, false, { error: error.message });
1913
- }
1914
- }
1915
- }
1916
-
1917
- // Generate calls
1918
- const calls = Array.from({ length: 100 }, (_, i) => ({
1919
- id: i,
1920
- endpoint: '/api/data',
1921
- method: 'GET'
1922
- }));
1923
-
1924
- await queueApiCalls(calls);
1925
- rateLimitedProcessor().catch(console.error);
1926
- ```
1927
-
1928
- ### Example 7: Enterprise Features
1929
-
1930
- ```javascript
1931
- import { Queen } from 'queen-mq';
1932
-
1933
- const client = new Queen({
1934
- baseUrls: ['http://localhost:6632']
1935
- });
1936
-
1937
- // Configure with all enterprise features
1938
- await client.queue('production-queue', {
1939
- // Encryption
1940
- encryptionEnabled: true,
1941
-
1942
- // Retention
1943
- retentionSeconds: 86400, // Delete pending after 24 hours
1944
- completedRetentionSeconds: 3600, // Delete completed after 1 hour
1945
- retentionEnabled: true,
1946
-
1947
- // Eviction (SLA enforcement)
1948
- maxWaitTimeSeconds: 600, // Evict messages older than 10 minutes
1949
-
1950
- // Standard options
1951
- priority: 10,
1952
- leaseTime: 300,
1953
- retryLimit: 3,
1954
- dlqAfterMaxRetries: true
1955
- });
1956
-
1957
- // Push sensitive data (will be encrypted)
1958
- await client.push('production-queue', {
1959
- userId: 123,
1960
- creditCard: '4111-1111-1111-1111',
1961
- amount: 99.99,
1962
- timestamp: Date.now()
1963
- });
1964
-
1965
- // Process (data decrypted automatically)
1966
- for await (const message of client.take('production-queue', { wait: true })) {
1967
- console.log('Processing encrypted data:', message.data.userId);
1968
- await processPayment(message.data);
1969
- await client.ack(message);
1970
- }
1971
- ```
1972
-
1973
- ---
1974
-
1975
- ## 🧪 Testing
1976
-
1977
- Queen includes a comprehensive test suite covering all features.
1978
-
1979
- ### Run Tests
1980
-
1981
- ```bash
1982
- # Start the server first
1983
- npm start
1984
-
1985
- # Run all tests
1986
- node src/test/test-new.js
1987
-
1988
- # Run specific test categories
1989
- node src/test/test-new.js core # Core features
1990
- node src/test/test-new.js partition # Partition locking
1991
- node src/test/test-new.js enterprise # Enterprise features
1992
- node src/test/test-new.js bus # Bus mode
1993
- node src/test/test-new.js edge # Edge cases
1994
- node src/test/test-new.js advanced # Advanced patterns
1995
-
1996
- # Show help
1997
- node src/test/test-new.js help
1998
- ```
1999
-
2000
- ### Test Coverage
2001
-
2002
- The test suite verifies:
2003
-
2004
- **Core Features:**
2005
- - Queue creation and configuration
2006
- - Single and batch message push
2007
- - Message take and acknowledgment
2008
- - Delayed processing
2009
- - Partition FIFO ordering
2010
-
2011
- **Partition Locking:**
2012
- - Lock acquisition and release
2013
- - Bus mode partition locking
2014
- - Specific partition locking
2015
- - Namespace/task filtering with locking
2016
-
2017
- **Enterprise Features:**
2018
- - AES-256-GCM encryption
2019
- - Message retention policies
2020
- - Message eviction
2021
- - Combined enterprise features
2022
-
2023
- **Bus Mode:**
2024
- - Consumer groups
2025
- - Subscription modes (all, new, from)
2026
- - Consumer group isolation
2027
- - Mixed mode (queue + bus)
2028
-
2029
- **Edge Cases:**
2030
- - Empty and null payloads
2031
- - Very large payloads
2032
- - Concurrent operations
2033
- - Lease expiration
2034
- - SQL injection prevention
2035
- - XSS prevention
2036
-
2037
- **Advanced Patterns:**
2038
- - Multi-stage pipelines
2039
- - Fan-out/fan-in
2040
- - Priority scenarios
2041
- - Dead letter queue
2042
- - Circuit breaker
2043
- - Message deduplication
2044
- - Event sourcing
2045
-
2046
- ### Test Results
2047
-
2048
- Example output:
2049
- ```
2050
- 🚀 Starting Queen Message Queue Test Suite
2051
- Using the new minimalist Queen client interface
2052
- ================================================================================
2053
-
2054
- 📦 CORE FEATURES
2055
- ----------------------------------------
2056
- ✅ Queue Creation Policy
2057
- ✅ Single Message Push
2058
- ✅ Batch Message Push
2059
- ✅ Queue Configuration
2060
- ✅ Take and Acknowledgment
2061
- ✅ Delayed Processing
2062
- ✅ Partition FIFO Ordering
2063
-
2064
- 🔒 PARTITION LOCKING
2065
- ----------------------------------------
2066
- ✅ Partition Locking
2067
- ✅ Bus Partition Locking
2068
- ✅ Specific Partition Locking
2069
- ✅ Namespace Task Filtering
2070
- ✅ Namespace Task Bus Mode
2071
-
2072
- 📈 Test Summary
2073
- ================================================================================
2074
- Total: 42 | Passed: 42 | Failed: 0 | Duration: 45.2s
2075
- ```
2076
-
2077
- ---
2078
-
2079
- ## 🤝 Contributing
2080
-
2081
- We welcome contributions! Here's how to get started:
2082
-
2083
- 1. **Fork the repository**
2084
- 2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
2085
- 3. **Make your changes**
2086
- 4. **Run the test suite**: `node src/test/test-new.js`
2087
- 5. **Commit your changes**: `git commit -m 'Add amazing feature'`
2088
- 6. **Push to the branch**: `git push origin feature/amazing-feature`
2089
- 7. **Open a Pull Request**
2090
-
2091
- ### Development Setup
2092
-
2093
- ```bash
2094
- # Clone your fork
2095
- git clone https://github.com/your-username/queen
2096
- cd queen
2097
-
2098
- # Install dependencies
2099
- nvm use 22
2100
- npm install
2101
-
2102
- # Initialize database
2103
- node init-db.js
2104
-
2105
- # Start server
2106
- npm start
2107
-
2108
- # Run tests
2109
- node src/test/test-new.js
2110
- ```
2111
-
2112
- ### Code Style
2113
-
2114
- - Use ES6+ features
2115
- - Follow existing code style
2116
- - Add comments for complex logic
2117
- - Write tests for new features
2118
-
2119
- ---
2120
-
2121
- ## 📄 License
2122
-
2123
- Apache License 2.0 - see [LICENSE.md](LICENSE.md) for details.
2124
-
2125
- ---
2126
-
2127
- ## 🔗 Links
2128
-
2129
- - **Repository**: [github.com/smartpricing/queen](https://github.com/smartpricing/queen)
2130
- - **Documentation**: See `docs/` directory
2131
- - **Issues**: [GitHub Issues](https://github.com/smartpricing/queen/issues)
2132
- - **API Reference**: [API.md](API.md)
2133
-
2134
- ---
2135
-
2136
- ## 📈 Performance
2137
-
2138
- **Benchmarks** (PostgreSQL 16, Node.js 22, cursor-based consumption):
2139
- - **Throughput**: 100,000+ messages/second with batch operations
2140
- - **Latency**: < 10ms for immediate pop operations
2141
- - **Constant-time consumption**: O(batch_size) regardless of queue depth
2142
- - **Concurrent Connections**: 1,000+ long polling connections
2143
- - **Database**: Optimized with proper indexing and connection pooling
2144
-
2145
- **Cursor-Based Architecture Benefits:**
2146
- - **No performance degradation**: Consistent speed whether queue has 1K or 1B messages
2147
- - **Predictable latency**: 150-200ms per batch throughout entire queue lifecycle
2148
- - **Efficient batch processing**: Direct cursor access eliminates table scans
2149
- - **Scalable to billions**: UUIDv7-based cursor positioning
2150
-
2151
- **Additional Optimization Features:**
2152
- - Connection pooling with configurable size
2153
- - Resource caching for queue/partition lookups
2154
- - Batch operations for bulk inserts/updates (up to 10,000 messages per batch)
2155
- - Optimized SQL queries with proper indexes
2156
- - Event-driven architecture for minimal polling overhead
2157
- - Long polling for real-time message delivery
2158
- - SKIP LOCKED for lock-free concurrent consumption
2159
-
2160
- ---
2161
-
2162
- ## 🎯 Roadmap
2163
-
2164
- - [ ] **Message Scheduling**: Cron-like scheduling for recurring jobs
2165
- - [ ] **Client Libraries**: Python, Go, Java clients
2166
- - [ ] **Kubernetes Operator**: Native K8s support
2167
-
2168
- ---
2169
-
2170
- <div align="center">
2171
-
2172
- **Queen Message Queue System** - Built for performance, reliability, and developer happiness 🚀
2173
-
2174
- Made with ❤️ by [Smartness](https://github.com/smartpricing)
2175
-
2176
- </div>
868
+ Apache 2.0 - see [LICENSE.md](LICENSE.md)