queen-mq 0.2.23 → 0.3.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 (35) hide show
  1. package/README.md +162 -136
  2. package/client-js/client-v2/LOGGING.md +240 -0
  3. package/client-js/client-v2/Queen.js +389 -0
  4. package/client-js/client-v2/README.md +1883 -0
  5. package/client-js/client-v2/buffer/BufferManager.js +215 -0
  6. package/client-js/client-v2/buffer/MessageBuffer.js +132 -0
  7. package/client-js/client-v2/builders/QueueBuilder.js +724 -0
  8. package/client-js/client-v2/builders/TransactionBuilder.js +110 -0
  9. package/client-js/client-v2/consumer/ConsumerManager.js +390 -0
  10. package/client-js/client-v2/http/HttpClient.js +215 -0
  11. package/client-js/client-v2/http/LoadBalancer.js +50 -0
  12. package/client-js/client-v2/index.js +7 -0
  13. package/client-js/client-v2/utils/defaults.js +54 -0
  14. package/client-js/client-v2/utils/logger.js +54 -0
  15. package/client-js/client-v2/utils/validation.js +31 -0
  16. package/client-js/test-v2/AI_TEST_SUMMARY.md +226 -0
  17. package/client-js/test-v2/GETTING_STARTED.md +154 -0
  18. package/client-js/test-v2/ai_buffering.js +194 -0
  19. package/client-js/test-v2/ai_error_handling.js +223 -0
  20. package/client-js/test-v2/ai_lease_renewal.js +206 -0
  21. package/client-js/test-v2/ai_mixed_scenarios.js +278 -0
  22. package/client-js/test-v2/ai_priority.js +169 -0
  23. package/client-js/test-v2/ai_resources.js +217 -0
  24. package/client-js/test-v2/ai_ttl_retention.js +170 -0
  25. package/client-js/test-v2/complete.js +59 -0
  26. package/client-js/test-v2/consume.js +655 -0
  27. package/client-js/test-v2/dlq.js +82 -0
  28. package/client-js/test-v2/load.js +177 -0
  29. package/client-js/test-v2/pop.js +114 -0
  30. package/client-js/test-v2/push.js +333 -0
  31. package/client-js/test-v2/queue.js +39 -0
  32. package/client-js/test-v2/run.js +187 -0
  33. package/client-js/test-v2/subscription.js +354 -0
  34. package/client-js/test-v2/transaction.js +443 -0
  35. package/package.json +1 -1
@@ -0,0 +1,1883 @@
1
+ # 👑 Queen Client
2
+
3
+ Welcome to Queen client! This is your friendly guide to mastering message queues without losing your sanity. We'll start simple and gradually unlock the superpowers. 🚀
4
+
5
+ ## Table of Contents
6
+
7
+ - [Getting Started](#getting-started)
8
+ - [Part 1: Hello Queue!](#part-1-hello-queue)
9
+ - [Part 2: Push & Consume Basics](#part-2-push--consume-basics)
10
+ - [Part 3: Pop vs Consume (Choose Your Adventure)](#part-3-pop-vs-consume-choose-your-adventure)
11
+ - [Part 4: Partitions - Organize Your World](#part-4-partitions---organize-your-world)
12
+ - [Part 5: Consumer Groups - Share the Load](#part-5-consumer-groups---share-the-load)
13
+ - [Part 5.5: Subscription Modes - Control Message History](#part-55-subscription-modes---control-message-history)
14
+ - [Part 6: Namespaces & Tasks - The Wildcard Way](#part-6-namespaces--tasks---the-wildcard-way)
15
+ - [Part 7: Transactions - All or Nothing](#part-7-transactions---all-or-nothing)
16
+ - [Part 8: Client-Side Buffering - Speed Demon Mode](#part-8-client-side-buffering---speed-demon-mode)
17
+ - [Part 9: Dead Letter Queue - When Things Go Wrong](#part-9-dead-letter-queue---when-things-go-wrong)
18
+ - [Part 10: Lease Renewal - Keep It Locked](#part-10-lease-renewal---keep-it-locked)
19
+ - [Part 11: Queue Configuration - Fine Tuning](#part-11-queue-configuration---fine-tuning)
20
+ - [Part 12: Message Tracing - Debug Your Workflows](#part-12-message-tracing---debug-your-workflows)
21
+ - [Part 13: Callbacks & Error Handling](#part-13-callbacks--error-handling)
22
+ - [Part 14: Graceful Shutdown](#part-14-graceful-shutdown)
23
+ - [Cheat Sheet](#cheat-sheet)
24
+
25
+ ---
26
+
27
+ ## Getting Started
28
+
29
+ First, install and import:
30
+
31
+ ```javascript
32
+ import { Queen } from './client-js/client-v2/index.js'
33
+
34
+ // Connect to your Queen server
35
+ const queen = new Queen('http://localhost:6632')
36
+
37
+ // Or with multiple servers for high availability
38
+ const queen = new Queen(['http://server1:6632', 'http://server2:6632'])
39
+
40
+ // Or with full configuration
41
+ const queen = new Queen({
42
+ urls: ['http://server1:6632', 'http://server2:6632'],
43
+ timeoutMillis: 30000,
44
+ retryAttempts: 3,
45
+ loadBalancingStrategy: 'round-robin', // or 'session'
46
+ enableFailover: true
47
+ })
48
+ ```
49
+
50
+ That's it! You're connected. Now let's do something fun. 🎉
51
+
52
+ ---
53
+
54
+ ## Part 1: Hello Queue!
55
+
56
+ Every journey starts with a queue. Let's create one:
57
+
58
+ ```javascript
59
+ // Create a simple queue
60
+ await queen.queue('my-tasks').create()
61
+
62
+ // That's it! The queue exists now with sensible defaults
63
+ ```
64
+
65
+ Need to customize it? We'll get to that later. For now, let's keep it simple.
66
+
67
+ Want to delete a queue? (Be careful! ⚠️)
68
+
69
+ ```javascript
70
+ await queen.queue('my-tasks').delete()
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Part 2: Push & Consume Basics
76
+
77
+ ### Pushing Messages (aka "Adding Work to Do")
78
+
79
+ ```javascript
80
+ // Push a single message
81
+ await queen.queue('my-tasks').push([
82
+ { data: { job: 'send-email', to: 'alice@example.com' } }
83
+ ])
84
+
85
+ // Push multiple messages at once
86
+ await queen.queue('my-tasks').push([
87
+ { data: { job: 'send-email', to: 'alice@example.com' } },
88
+ { data: { job: 'send-email', to: 'bob@example.com' } },
89
+ { data: { job: 'resize-image', id: 123 } }
90
+ ])
91
+ ```
92
+
93
+ **Pro tip:** Notice the array? Always wrap your messages in an array, even for a single message.
94
+
95
+ ### Consuming Messages (aka "Getting Work Done")
96
+
97
+ The easiest way to process messages:
98
+
99
+ ```javascript
100
+ await queen.queue('my-tasks').consume(async (message) => {
101
+ console.log('Processing:', message.data)
102
+
103
+ // Do your work here
104
+ await sendEmail(message.data.to)
105
+
106
+ // That's it! If your function succeeds, the message is automatically acknowledged
107
+ // If it throws an error, the message is automatically rejected and will retry
108
+ })
109
+ ```
110
+
111
+ **What just happened?**
112
+ 1. The consumer pulls messages from the queue
113
+ 2. Your function processes each message
114
+ 3. If successful → message is marked as complete ✅
115
+ 4. If error → message goes back to the queue for retry 🔄
116
+
117
+ This runs **forever** by default. Perfect for background workers!
118
+
119
+ Want to process just a few messages and stop?
120
+
121
+ ```javascript
122
+ // Process exactly 10 messages then stop
123
+ await queen
124
+ .queue('my-tasks')
125
+ .limit(10)
126
+ .consume(async (message) => {
127
+ console.log('Processing:', message.data)
128
+ })
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Part 3: Pop vs Consume (Choose Your Adventure)
134
+
135
+ ### The Consume Way (Recommended for Workers)
136
+
137
+ **Use when:** You want a long-running worker that continuously processes messages.
138
+
139
+ ```javascript
140
+ // Runs forever, processing messages as they arrive
141
+ await queen.queue('my-tasks').consume(async (message) => {
142
+ // Your processing logic
143
+ })
144
+ ```
145
+
146
+ ### The Pop Way (Good for On-Demand Processing)
147
+
148
+ **Use when:** You want to grab messages manually and control everything yourself.
149
+
150
+ ```javascript
151
+ // Grab one message right now
152
+ const messages = await queen.queue('my-tasks').pop()
153
+
154
+ if (messages.length > 0) {
155
+ const message = messages[0]
156
+
157
+ try {
158
+ // Do your work
159
+ await processMessage(message.data)
160
+
161
+ // Tell Queen it succeeded
162
+ await queen.ack(message, true)
163
+ } catch (error) {
164
+ // Tell Queen it failed
165
+ await queen.ack(message, false, { error: error.message })
166
+ }
167
+ }
168
+ ```
169
+
170
+ **Key differences:**
171
+ - `consume()` = Long-running, auto-ack, loops automatically
172
+ - `pop()` = One-shot, manual-ack, you control the loop
173
+
174
+ Want to pop multiple messages?
175
+
176
+ ```javascript
177
+ // Grab up to 10 messages at once
178
+ const messages = await queen
179
+ .queue('my-tasks')
180
+ .batch(10)
181
+ .pop()
182
+
183
+ console.log(`Got ${messages.length} messages`)
184
+ ```
185
+
186
+ Want to wait if no messages are available?
187
+
188
+ ```javascript
189
+ // Wait up to 30 seconds for messages to arrive
190
+ const messages = await queen
191
+ .queue('my-tasks')
192
+ .batch(10)
193
+ .wait(true) // Enable long polling
194
+ .pop()
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Part 4: Partitions - Organize Your World
200
+
201
+ Think of partitions like lanes on a highway. Each lane processes independently.
202
+
203
+ **Why use partitions?**
204
+ - Process different types of work in parallel
205
+ - Ensure order within a partition
206
+ - Isolate failures
207
+
208
+ ### Creating Partitioned Messages
209
+
210
+ ```javascript
211
+ // Send messages to specific partitions
212
+ await queen
213
+ .queue('user-events')
214
+ .partition('user-123')
215
+ .push([
216
+ { data: { event: 'login', timestamp: Date.now() } }
217
+ ])
218
+
219
+ await queen
220
+ .queue('user-events')
221
+ .partition('user-456')
222
+ .push([
223
+ { data: { event: 'logout', timestamp: Date.now() } }
224
+ ])
225
+ ```
226
+
227
+ **Important:** Messages in the same partition are **ordered**. Messages in different partitions are **independent**.
228
+
229
+ ### Consuming from a Specific Partition
230
+
231
+ ```javascript
232
+ // Process only messages from user-123's partition
233
+ await queen
234
+ .queue('user-events')
235
+ .partition('user-123')
236
+ .consume(async (message) => {
237
+ console.log('User 123 did:', message.data.event)
238
+ })
239
+ ```
240
+
241
+ ### Real-World Example: Per-User Processing
242
+
243
+ ```javascript
244
+ // Each user gets their own partition for ordered processing
245
+ const userId = 'alice-007'
246
+
247
+ // Push user-specific events
248
+ await queen
249
+ .queue('user-commands')
250
+ .partition(userId)
251
+ .push([
252
+ { data: { action: 'create-post', title: 'Hello World' } },
253
+ { data: { action: 'like-post', postId: 123 } },
254
+ { data: { action: 'comment', postId: 123, text: 'Nice!' } }
255
+ ])
256
+
257
+ // Process user's commands in order
258
+ await queen
259
+ .queue('user-commands')
260
+ .partition(userId)
261
+ .consume(async (message) => {
262
+ // These will be processed in exact order
263
+ console.log(`${userId} doing:`, message.data.action)
264
+ })
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Part 5: Consumer Groups - Share the Load
270
+
271
+ Consumer groups let multiple workers share the same queue while ensuring each message is processed exactly once.
272
+
273
+ **Use cases:**
274
+ - Scale horizontally (run multiple workers)
275
+ - A/B testing (send copies to different systems)
276
+ - Fan-out patterns (process each message multiple ways)
277
+
278
+ ### Basic Consumer Groups
279
+
280
+ ```javascript
281
+ // Worker 1 in group "processors"
282
+ await queen
283
+ .queue('emails')
284
+ .group('processors')
285
+ .consume(async (message) => {
286
+ console.log('Worker 1 processing:', message.data)
287
+ })
288
+
289
+ // Worker 2 in the SAME group (shares the load)
290
+ await queen
291
+ .queue('emails')
292
+ .group('processors')
293
+ .consume(async (message) => {
294
+ console.log('Worker 2 processing:', message.data)
295
+ })
296
+ ```
297
+
298
+ **Result:** Messages are distributed between Worker 1 and Worker 2. Each message goes to only ONE worker.
299
+
300
+ ### Multiple Consumer Groups (Fan-Out)
301
+
302
+ ```javascript
303
+ // Group 1: Send emails
304
+ await queen
305
+ .queue('notifications')
306
+ .group('email-sender')
307
+ .consume(async (message) => {
308
+ await sendEmail(message.data)
309
+ })
310
+
311
+ // Group 2: Log to analytics (processes THE SAME messages)
312
+ await queen
313
+ .queue('notifications')
314
+ .group('analytics')
315
+ .consume(async (message) => {
316
+ await trackEvent(message.data)
317
+ })
318
+ ```
319
+
320
+ **Result:** Every message is processed by BOTH groups independently! 🎉
321
+
322
+ ### Real-World Example: Order Processing
323
+
324
+ ```javascript
325
+ // Main order processor (high priority)
326
+ await queen
327
+ .queue('orders')
328
+ .group('order-fulfillment')
329
+ .concurrency(5) // Run 5 workers in parallel
330
+ .consume(async (message) => {
331
+ await processOrder(message.data)
332
+ })
333
+
334
+ // Analytics processor (separate group, same messages)
335
+ await queen
336
+ .queue('orders')
337
+ .group('analytics')
338
+ .consume(async (message) => {
339
+ await logOrderMetrics(message.data)
340
+ })
341
+ ```
342
+
343
+ ---
344
+
345
+ ## Part 5.5: Subscription Modes - Control Message History
346
+
347
+ When a consumer group first subscribes to a queue, should it process **all historical messages** or only **new messages** that arrive after subscription? Subscription modes give you control!
348
+
349
+ **Use cases:**
350
+ - Start fresh without processing old backlog
351
+ - Subscribe to real-time events only
352
+ - Join a stream at a specific point in time
353
+ - Skip historical data for new analytics consumers
354
+
355
+ ### Default Behavior (All Messages)
356
+
357
+ By default, consumer groups start from the **beginning** and process all messages:
358
+
359
+ ```javascript
360
+ // This consumer group gets ALL messages, including historical ones
361
+ await queen
362
+ .queue('events')
363
+ .group('new-analytics')
364
+ .consume(async (message) => {
365
+ console.log('Processing:', message.data)
366
+ })
367
+ ```
368
+
369
+ ### Subscription Mode: 'new'
370
+
371
+ Skip all historical messages and only process messages that arrive **after** subscription:
372
+
373
+ ```javascript
374
+ // Only process NEW messages, skip historical backlog
375
+ await queen
376
+ .queue('events')
377
+ .group('realtime-monitor')
378
+ .subscriptionMode('new') // 👈 Skip history
379
+ .consume(async (message) => {
380
+ console.log('New event:', message.data)
381
+ })
382
+ ```
383
+
384
+ **What happens:**
385
+ 1. Consumer subscribes at `T0`
386
+ 2. All messages before `T0` are skipped
387
+ 3. Only messages arriving after `T0` are processed
388
+
389
+ ### Subscription Mode: 'new-only'
390
+
391
+ Alias for `'new'` - same behavior:
392
+
393
+ ```javascript
394
+ await queen
395
+ .queue('events')
396
+ .group('fresh-start')
397
+ .subscriptionMode('new-only')
398
+ .consume(async (message) => {
399
+ // Only new messages
400
+ })
401
+ ```
402
+
403
+ ### Subscription From: 'now'
404
+
405
+ Alternative syntax using `subscriptionFrom('now')`:
406
+
407
+ ```javascript
408
+ await queen
409
+ .queue('events')
410
+ .group('from-now')
411
+ .subscriptionFrom('now') // 👈 Start from now
412
+ .consume(async (message) => {
413
+ console.log('New event:', message.data)
414
+ })
415
+ ```
416
+
417
+ ### Subscription From: Timestamp
418
+
419
+ Start consuming from a **specific timestamp**:
420
+
421
+ ```javascript
422
+ // Start from a specific point in time
423
+ const startTime = '2025-10-28T10:00:00.000Z'
424
+
425
+ await queen
426
+ .queue('events')
427
+ .group('replay-from-10am')
428
+ .subscriptionFrom(startTime) // 👈 ISO 8601 timestamp
429
+ .consume(async (message) => {
430
+ // Process messages from 10am onwards
431
+ })
432
+ ```
433
+
434
+ **Dynamic timestamp example:**
435
+
436
+ ```javascript
437
+ // Start from 1 hour ago
438
+ const oneHourAgo = new Date(Date.now() - 3600000).toISOString()
439
+
440
+ await queen
441
+ .queue('events')
442
+ .group('last-hour')
443
+ .subscriptionFrom(oneHourAgo)
444
+ .consume(async (message) => {
445
+ console.log('Processing recent event:', message.data)
446
+ })
447
+ ```
448
+
449
+ ### Real-World Example: Multi-Consumer Setup
450
+
451
+ ```javascript
452
+ // Group 1: Process ALL messages (including backlog)
453
+ await queen
454
+ .queue('user-actions')
455
+ .group('batch-analytics')
456
+ .consume(async (message) => {
457
+ await generateFullReport(message.data)
458
+ })
459
+
460
+ // Group 2: Only NEW messages (real-time monitoring)
461
+ await queen
462
+ .queue('user-actions')
463
+ .group('realtime-alerts')
464
+ .subscriptionMode('new')
465
+ .consume(async (message) => {
466
+ await sendRealtimeAlert(message.data)
467
+ })
468
+
469
+ // Group 3: Replay from specific time (debugging)
470
+ await queen
471
+ .queue('user-actions')
472
+ .group('debug-replay')
473
+ .subscriptionFrom('2025-10-28T15:30:00.000Z')
474
+ .consume(async (message) => {
475
+ await debugSpecificTimeframe(message.data)
476
+ })
477
+ ```
478
+
479
+ **Result:**
480
+ - `batch-analytics`: Processes all 10,000 historical messages + new ones
481
+ - `realtime-alerts`: Skips 10,000 historical messages, only processes new ones
482
+ - `debug-replay`: Starts from 3:30 PM, processes everything after that
483
+
484
+ ### Important Notes
485
+
486
+ ⚠️ **Subscription modes only work with consumer groups:**
487
+ - Requires `.group('name')`
488
+ - Does NOT work with default queue mode (no group)
489
+ - Each consumer group maintains its own subscription position
490
+
491
+ 🎯 **First subscription matters:**
492
+ - Subscription mode is set when the consumer group **first subscribes**
493
+ - Subsequent consumers in the same group inherit the same position
494
+ - To change subscription mode, use a different group name
495
+
496
+ 💡 **Best Practices:**
497
+ - Use `'new'` for real-time monitoring and alerting
498
+ - Use default (all) for batch processing and analytics
499
+ - Use timestamps for replay/debugging scenarios
500
+ - Name groups descriptively based on their subscription mode
501
+
502
+ ---
503
+
504
+ ## Part 6: Namespaces & Tasks - The Wildcard Way
505
+
506
+ Sometimes you don't care about specific queues. You want to process messages based on **what they do** or **where they belong**.
507
+
508
+ ### Namespaces (Logical Grouping)
509
+
510
+ Think of namespaces as folders for your queues.
511
+
512
+ ```javascript
513
+ // Create queues with namespaces
514
+ await queen.queue('billing-invoices').namespace('accounting').create()
515
+ await queen.queue('billing-receipts').namespace('accounting').create()
516
+ await queen.queue('user-emails').namespace('notifications').create()
517
+
518
+ // Push to specific queues
519
+ await queen.queue('billing-invoices').push([
520
+ { data: { invoice: 'INV-001' } }
521
+ ])
522
+
523
+ // Consume from ALL queues in the 'accounting' namespace
524
+ await queen
525
+ .queue()
526
+ .namespace('accounting')
527
+ .consume(async (message) => {
528
+ // This will receive messages from BOTH billing-invoices AND billing-receipts
529
+ console.log('Accounting message:', message.data)
530
+ })
531
+ ```
532
+
533
+ **Why this is cool:** Add new queues to the namespace later, and existing consumers automatically process them! 🎯
534
+
535
+ ### Tasks (Processing Types)
536
+
537
+ Tasks are like tags that describe what needs to be done.
538
+
539
+ ```javascript
540
+ // Create queues with tasks
541
+ await queen.queue('video-uploads').task('video-processing').create()
542
+ await queen.queue('image-uploads').task('image-processing').create()
543
+
544
+ // Consume by task type
545
+ await queen
546
+ .queue()
547
+ .task('video-processing')
548
+ .consume(async (message) => {
549
+ // Only video processing messages
550
+ await processVideo(message.data)
551
+ })
552
+ ```
553
+
554
+ ### Combining Namespace + Task
555
+
556
+ ```javascript
557
+ // Super specific filtering!
558
+ await queen
559
+ .queue()
560
+ .namespace('media')
561
+ .task('urgent-processing')
562
+ .consume(async (message) => {
563
+ // Only urgent media processing from the media namespace
564
+ })
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Part 7: Transactions - All or Nothing
570
+
571
+ Transactions are atomic operations. Either **everything** succeeds or **nothing** does.
572
+
573
+ **Use cases:**
574
+ - Ack one message and push to another queue (pipeline pattern)
575
+ - Process multiple messages atomically
576
+ - Ensure consistency across operations
577
+
578
+ ### Basic Transaction: Ack + Push
579
+
580
+ ```javascript
581
+ // Pop a message
582
+ const messages = await queen.queue('raw-data').batch(1).pop()
583
+
584
+ if (messages.length > 0) {
585
+ const message = messages[0]
586
+
587
+ // Process it
588
+ const processed = await transformData(message.data)
589
+
590
+ // Atomically: ack the input AND push the output
591
+ await queen
592
+ .transaction()
593
+ .ack(message) // Complete the input message
594
+ .queue('processed-data')
595
+ .push([{ data: processed }]) // Add to next queue
596
+ .commit()
597
+ }
598
+
599
+ // If commit fails, NOTHING happens. Message stays in raw-data queue!
600
+ ```
601
+
602
+ ### Multi-Queue Pipeline
603
+
604
+ ```javascript
605
+ // Pop from queue A
606
+ const messages = await queen.queue('queue-a').batch(1).pop()
607
+
608
+ // Transaction: ack from A, push to B and C
609
+ await queen
610
+ .transaction()
611
+ .ack(messages[0])
612
+ .queue('queue-b')
613
+ .push([{ data: { step: 2, value: messages[0].data.value * 2 } }])
614
+ .queue('queue-c')
615
+ .push([{ data: { step: 2, value: messages[0].data.value * 2 } }])
616
+ .commit()
617
+
618
+ // Atomic! Either all three operations succeed, or none do
619
+ ```
620
+
621
+ ### Batch Processing Transaction
622
+
623
+ ```javascript
624
+ // Pop multiple messages
625
+ const messages = await queen.queue('inputs').batch(10).pop()
626
+
627
+ // Process them
628
+ const results = messages.map(m => process(m.data))
629
+
630
+ // Atomically ack all inputs and push all outputs
631
+ const txn = queen.transaction()
632
+
633
+ // Ack all inputs
634
+ for (const message of messages) {
635
+ txn.ack(message)
636
+ }
637
+
638
+ // Push all outputs
639
+ txn.queue('outputs').push(results.map(r => ({ data: r })))
640
+
641
+ await txn.commit()
642
+ ```
643
+
644
+ ### Transaction with Consumer
645
+
646
+ Want to consume with transactions? Easy:
647
+
648
+ ```javascript
649
+ await queen
650
+ .queue('source')
651
+ .autoAck(false) // Must disable auto-ack for manual transaction
652
+ .consume(async (message) => {
653
+ // Do work
654
+ const result = await processMessage(message.data)
655
+
656
+ // Transactionally ack and push result
657
+ await queen
658
+ .transaction()
659
+ .ack(message)
660
+ .queue('destination')
661
+ .push([{ data: result }])
662
+ .commit()
663
+ })
664
+ ```
665
+
666
+ ---
667
+
668
+ ## Part 8: Client-Side Buffering - Speed Demon Mode
669
+
670
+ Pushing messages one-at-a-time is slow. Buffering batches them up for massive speed boosts! 🚄
671
+
672
+ ### How Buffering Works
673
+
674
+ Instead of sending messages immediately:
675
+ 1. Messages collect in a local buffer
676
+ 2. Buffer flushes when it reaches a **count** or **time** threshold
677
+ 3. All buffered messages are sent in one HTTP request
678
+
679
+ **Result:** 10x-100x faster throughput!
680
+
681
+ ### Basic Buffering
682
+
683
+ ```javascript
684
+ // Buffer up to 100 messages OR 1 second (whichever comes first)
685
+ await queen
686
+ .queue('logs')
687
+ .buffer({ messageCount: 100, timeMillis: 1000 })
688
+ .push([
689
+ { data: { level: 'info', message: 'User logged in' } }
690
+ ])
691
+
692
+ // Message is now buffered, not sent yet
693
+ // Will send when 100 messages accumulate OR 1 second passes
694
+ ```
695
+
696
+ ### High-Throughput Example
697
+
698
+ ```javascript
699
+ // Send 10,000 messages super fast
700
+ for (let i = 0; i < 10000; i++) {
701
+ await queen
702
+ .queue('events')
703
+ .buffer({ messageCount: 500, timeMillis: 100 })
704
+ .push([
705
+ { data: { id: i, timestamp: Date.now() } }
706
+ ])
707
+ }
708
+
709
+ // Flush any remaining buffered messages
710
+ await queen.flushAllBuffers()
711
+ ```
712
+
713
+ **Performance:** This might take seconds instead of minutes! ⚡
714
+
715
+ ### Manual Flush
716
+
717
+ ```javascript
718
+ // Flush all buffers for all queues
719
+ await queen.flushAllBuffers()
720
+
721
+ // Flush a specific queue's buffer
722
+ await queen.queue('my-queue').flushBuffer()
723
+
724
+ // Get buffer statistics
725
+ const stats = queen.getBufferStats()
726
+ console.log('Buffers:', stats)
727
+ // Example output: { 'my-queue/Default': { count: 45, size: 1234 } }
728
+ ```
729
+
730
+ ### Real-World Example: Log Aggregation
731
+
732
+ ```javascript
733
+ // High-frequency logging with buffering
734
+ class Logger {
735
+ constructor(queen) {
736
+ this.queen = queen
737
+ }
738
+
739
+ async log(level, message) {
740
+ await this.queen
741
+ .queue('application-logs')
742
+ .buffer({ messageCount: 1000, timeMillis: 5000 })
743
+ .push([
744
+ { data: { level, message, timestamp: Date.now() } }
745
+ ])
746
+ }
747
+
748
+ async flush() {
749
+ await this.queen.flushAllBuffers()
750
+ }
751
+ }
752
+
753
+ const logger = new Logger(queen)
754
+ await logger.log('info', 'Server started')
755
+ await logger.log('debug', 'Processing request...')
756
+ // Logs are buffered and sent in batches!
757
+ ```
758
+
759
+ ---
760
+
761
+ ## Part 9: Dead Letter Queue - When Things Go Wrong
762
+
763
+ Not all messages can be processed. Some are just... problematic. The DLQ is where failed messages go to be examined.
764
+
765
+ ### How DLQ Works
766
+
767
+ 1. Message fails (your handler throws an error)
768
+ 2. Message retries (up to `retryLimit`)
769
+ 3. After max retries → moves to Dead Letter Queue
770
+ 4. You can query DLQ to see what went wrong
771
+
772
+ ### Enable DLQ
773
+
774
+ ```javascript
775
+ // Create queue with DLQ enabled
776
+ await queen
777
+ .queue('risky-business')
778
+ .config({
779
+ retryLimit: 3, // Try 3 times
780
+ dlqAfterMaxRetries: true // Send to DLQ after 3 failures
781
+ })
782
+ .create()
783
+ ```
784
+
785
+ ### Process Messages (Some Will Fail)
786
+
787
+ ```javascript
788
+ await queen
789
+ .queue('risky-business')
790
+ .consume(async (message) => {
791
+ if (message.data.value < 0) {
792
+ throw new Error('Negative values not allowed!')
793
+ }
794
+ // Process normally
795
+ })
796
+ ```
797
+
798
+ ### Query the DLQ
799
+
800
+ ```javascript
801
+ // Get failed messages
802
+ const dlq = await queen
803
+ .queue('risky-business')
804
+ .dlq()
805
+ .limit(10)
806
+ .get()
807
+
808
+ console.log(`Found ${dlq.total} failed messages`)
809
+
810
+ for (const message of dlq.messages) {
811
+ console.log('Failed message:', message.data)
812
+ console.log('Error was:', message.errorMessage)
813
+ console.log('Failed at:', message.dlqTimestamp)
814
+ }
815
+ ```
816
+
817
+ ### DLQ with Consumer Groups
818
+
819
+ ```javascript
820
+ // Check DLQ for a specific consumer group
821
+ const dlq = await queen
822
+ .queue('risky-business')
823
+ .dlq('my-consumer-group')
824
+ .limit(100)
825
+ .get()
826
+ ```
827
+
828
+ ### Advanced DLQ Queries
829
+
830
+ ```javascript
831
+ // Query with time range
832
+ const dlq = await queen
833
+ .queue('risky-business')
834
+ .dlq()
835
+ .from('2025-01-01')
836
+ .to('2025-01-31')
837
+ .limit(100)
838
+ .offset(0) // Pagination
839
+ .get()
840
+ ```
841
+
842
+ ---
843
+
844
+ ## Part 10: Lease Renewal - Keep It Locked
845
+
846
+ When you pop a message, you get a "lease" (a lock). The lease expires after `leaseTime` seconds. If your processing takes longer, you need to **renew** the lease.
847
+
848
+ ### Why Lease Renewal?
849
+
850
+ Imagine processing a video that takes 10 minutes, but your lease is 5 minutes. After 5 minutes, Queen thinks you died and gives the message to someone else. Oops! 😱
851
+
852
+ ### Automatic Lease Renewal (Easy Mode)
853
+
854
+ ```javascript
855
+ await queen
856
+ .queue('long-tasks')
857
+ .renewLease(true, 60000) // Renew every 60 seconds
858
+ .consume(async (message) => {
859
+ // Even if this takes 30 minutes, the lease keeps renewing automatically!
860
+ await processVeryLongTask(message.data)
861
+ })
862
+ ```
863
+
864
+ **What happens:** Every 60 seconds, Queen automatically extends your lease. Your function can take as long as needed!
865
+
866
+ ### Manual Lease Renewal
867
+
868
+ ```javascript
869
+ // Pop a message
870
+ const messages = await queen.queue('long-tasks').pop()
871
+ const message = messages[0]
872
+
873
+ // Start long processing
874
+ const timer = setInterval(async () => {
875
+ await queen.renew(message) // Extend lease
876
+ console.log('Lease renewed!')
877
+ }, 30000) // Every 30 seconds
878
+
879
+ try {
880
+ await processVeryLongTask(message.data)
881
+ await queen.ack(message, true)
882
+ } finally {
883
+ clearInterval(timer)
884
+ }
885
+ ```
886
+
887
+ ### Batch Lease Renewal
888
+
889
+ ```javascript
890
+ // Renew multiple messages at once
891
+ const messages = await queen.queue('tasks').batch(10).pop()
892
+
893
+ // Renew all of them
894
+ await queen.renew(messages)
895
+ ```
896
+
897
+ ### Using Just the Lease ID
898
+
899
+ ```javascript
900
+ const message = messages[0]
901
+
902
+ // Renew by lease ID
903
+ await queen.renew(message.leaseId)
904
+ ```
905
+
906
+ ---
907
+
908
+ ## Part 11: Queue Configuration - Fine Tuning
909
+
910
+ Queues have lots of knobs to turn. Let's explore them all!
911
+
912
+ ### Complete Configuration Example
913
+
914
+ ```javascript
915
+ await queen
916
+ .queue('super-queue')
917
+ .config({
918
+ // Lease & Retry
919
+ leaseTime: 300, // 5 minutes to process (seconds)
920
+ retryLimit: 3, // Retry 3 times before giving up
921
+ retryDelay: 5000, // Wait 5 seconds between retries (milliseconds)
922
+
923
+ // Dead Letter Queue
924
+ dlqAfterMaxRetries: true, // Move to DLQ after max retries
925
+
926
+ // Priority
927
+ priority: 5, // Higher number = higher priority (0-10)
928
+
929
+ // Delays & Buffers
930
+ delayedProcessing: 60, // Messages become available after 60 seconds
931
+ windowBuffer: 30, // Hold messages for 30 seconds to batch them
932
+
933
+ // Capacity
934
+ maxSize: 10000, // Max 10,000 messages in queue
935
+
936
+ // Retention
937
+ retentionSeconds: 86400, // Keep pending messages for 24 hours
938
+ completedRetentionSeconds: 3600, // Keep completed messages for 1 hour
939
+ ttl: 86400, // Message expires after 24 hours (seconds)
940
+
941
+ // Security
942
+ encryptionEnabled: true // Encrypt message payloads at rest
943
+ })
944
+ .create()
945
+ ```
946
+
947
+ ### Priority Queues
948
+
949
+ Higher priority queues are processed first!
950
+
951
+ ```javascript
952
+ // High priority queue
953
+ await queen
954
+ .queue('urgent-alerts')
955
+ .config({ priority: 10 })
956
+ .create()
957
+
958
+ // Normal priority
959
+ await queen
960
+ .queue('regular-tasks')
961
+ .config({ priority: 5 })
962
+ .create()
963
+
964
+ // Low priority
965
+ await queen
966
+ .queue('background-jobs')
967
+ .config({ priority: 1 })
968
+ .create()
969
+
970
+ // Consumer processes urgent-alerts first, then regular-tasks, then background-jobs
971
+ await queen.queue().namespace('all').consume(async (message) => {
972
+ console.log('Processing:', message)
973
+ })
974
+ ```
975
+
976
+ ### Delayed Processing
977
+
978
+ Messages don't become available until the delay passes.
979
+
980
+ ```javascript
981
+ // Messages are invisible for 60 seconds
982
+ await queen
983
+ .queue('scheduled-tasks')
984
+ .config({ delayedProcessing: 60 })
985
+ .create()
986
+
987
+ // Push a message
988
+ await queen.queue('scheduled-tasks').push([
989
+ { data: { task: 'send-reminder' } }
990
+ ])
991
+
992
+ // Pop immediately: gets nothing!
993
+ const now = await queen.queue('scheduled-tasks').pop()
994
+ console.log(now) // []
995
+
996
+ // Wait 60 seconds...
997
+ await new Promise(r => setTimeout(r, 61000))
998
+
999
+ // Pop again: now we get the message!
1000
+ const later = await queen.queue('scheduled-tasks').pop()
1001
+ console.log(later) // [{ data: { task: 'send-reminder' } }]
1002
+ ```
1003
+
1004
+ ### Window Buffering (Server-Side Batching)
1005
+
1006
+ Holds messages server-side to create natural batches.
1007
+
1008
+ ```javascript
1009
+ // Hold messages for 5 seconds to batch them
1010
+ await queen
1011
+ .queue('events')
1012
+ .config({ windowBuffer: 5 })
1013
+ .create()
1014
+
1015
+ // Push 10 messages quickly
1016
+ for (let i = 0; i < 10; i++) {
1017
+ await queen.queue('events').push([{ data: { id: i } }])
1018
+ }
1019
+
1020
+ // Consumer gets them all at once!
1021
+ await queen
1022
+ .queue('events')
1023
+ .batch(100)
1024
+ .consume(async (messages) => {
1025
+ console.log(`Got ${messages.length} messages in one batch!`)
1026
+ // Likely: "Got 10 messages in one batch!"
1027
+ })
1028
+ ```
1029
+
1030
+ ### Message TTL (Time To Live)
1031
+
1032
+ Messages expire and are deleted automatically.
1033
+
1034
+ ```javascript
1035
+ // Messages live for 1 hour max
1036
+ await queen
1037
+ .queue('temporary-data')
1038
+ .config({ ttl: 3600 })
1039
+ .create()
1040
+
1041
+ // Messages older than 1 hour are automatically deleted
1042
+ ```
1043
+
1044
+ ### Encryption
1045
+
1046
+ Sensitive data? Enable encryption!
1047
+
1048
+ ```javascript
1049
+ await queen
1050
+ .queue('customer-pii')
1051
+ .config({ encryptionEnabled: true })
1052
+ .create()
1053
+
1054
+ // Messages are encrypted at rest
1055
+ // Decrypted automatically when consumed
1056
+ ```
1057
+
1058
+ ---
1059
+
1060
+ ## Part 12: Message Tracing - Debug Your Workflows
1061
+
1062
+ Ever wondered what's happening to your messages as they flow through your system? Message tracing lets you record breadcrumbs as messages are processed, perfect for debugging distributed workflows!
1063
+
1064
+ ### Basic Tracing
1065
+
1066
+ ```javascript
1067
+ await queen.queue('orders').consume(async (msg) => {
1068
+ // Record a trace event
1069
+ await msg.trace({
1070
+ data: { text: 'Order processing started' }
1071
+ })
1072
+
1073
+ // Do some work
1074
+ const order = await processOrder(msg.data)
1075
+
1076
+ // Record another trace
1077
+ await msg.trace({
1078
+ data: {
1079
+ text: 'Order processed successfully',
1080
+ orderId: order.id,
1081
+ total: order.total
1082
+ }
1083
+ })
1084
+ }, { autoAck: true })
1085
+ ```
1086
+
1087
+ **What you get:**
1088
+ - Timeline of processing events
1089
+ - View traces in the frontend (Messages → Click message → Processing Timeline)
1090
+ - Never crashes your consumer (safe error handling built-in)
1091
+
1092
+ ### Trace Names - Connect the Dots
1093
+
1094
+ The real power comes from **trace names** - they let you correlate traces across multiple messages!
1095
+
1096
+ ```javascript
1097
+ // Service 1: Order Service
1098
+ await queen.queue('orders').consume(async (msg) => {
1099
+ const orderId = msg.data.orderId
1100
+
1101
+ await msg.trace({
1102
+ traceName: `order-${orderId}`, // 👈 Link traces with this name
1103
+ data: { text: 'Order created', service: 'orders' }
1104
+ })
1105
+
1106
+ // Create inventory check
1107
+ await queen.queue('inventory').push([{
1108
+ data: { orderId, items: msg.data.items }
1109
+ }])
1110
+ })
1111
+
1112
+ // Service 2: Inventory Service
1113
+ await queen.queue('inventory').consume(async (msg) => {
1114
+ const orderId = msg.data.orderId
1115
+
1116
+ await msg.trace({
1117
+ traceName: `order-${orderId}`, // 👈 Same name = connected!
1118
+ data: { text: 'Stock checked', service: 'inventory' }
1119
+ })
1120
+
1121
+ // Create payment
1122
+ await queen.queue('payments').push([{
1123
+ data: { orderId }
1124
+ }])
1125
+ })
1126
+
1127
+ // Service 3: Payment Service
1128
+ await queen.queue('payments').consume(async (msg) => {
1129
+ const orderId = msg.data.orderId
1130
+
1131
+ await msg.trace({
1132
+ traceName: `order-${orderId}`, // 👈 All connected!
1133
+ data: { text: 'Payment processed', service: 'payments' }
1134
+ })
1135
+ })
1136
+ ```
1137
+
1138
+ **Now in the frontend:**
1139
+ - Go to **Traces** page
1140
+ - Search for `order-12345`
1141
+ - See the ENTIRE workflow across all 3 services! 🎉
1142
+
1143
+ ### Multi-Category Tracing
1144
+
1145
+ You can add multiple trace names to organize by different dimensions:
1146
+
1147
+ ```javascript
1148
+ await queen.queue('chat-messages').consume(async (msg) => {
1149
+ const { tenantId, roomId, userId } = msg.data
1150
+
1151
+ await msg.trace({
1152
+ traceName: [
1153
+ `tenant-${tenantId}`, // Track by tenant
1154
+ `room-${roomId}`, // Track by room
1155
+ `user-${userId}` // Track by user
1156
+ ],
1157
+ data: { text: 'Message sent' }
1158
+ })
1159
+ })
1160
+ ```
1161
+
1162
+ **Query any dimension:**
1163
+ - Search `tenant-acme` → See all tenant activity
1164
+ - Search `room-123` → See all room activity
1165
+ - Search `user-456` → See all user activity
1166
+
1167
+ ### Event Types
1168
+
1169
+ Organize traces by event type for better visualization:
1170
+
1171
+ ```javascript
1172
+ await msg.trace({
1173
+ eventType: 'info', // Blue in UI
1174
+ data: { text: 'Started processing' }
1175
+ })
1176
+
1177
+ await msg.trace({
1178
+ eventType: 'step', // Purple in UI
1179
+ data: { text: 'Validated data' }
1180
+ })
1181
+
1182
+ await msg.trace({
1183
+ eventType: 'error', // Red in UI
1184
+ data: { text: 'Validation failed', reason: 'Invalid email' }
1185
+ })
1186
+
1187
+ await msg.trace({
1188
+ eventType: 'processing', // Green in UI
1189
+ data: { text: 'Sending email' }
1190
+ })
1191
+ ```
1192
+
1193
+ ### Error Tracking
1194
+
1195
+ Traces are perfect for tracking errors without breaking your flow:
1196
+
1197
+ ```javascript
1198
+ await queen.queue('analytics').consume(async (msg) => {
1199
+ try {
1200
+ await msg.trace({ data: { text: 'Job started' } })
1201
+
1202
+ const result = await computeAnalytics(msg.data)
1203
+
1204
+ await msg.trace({
1205
+ data: {
1206
+ text: 'Job completed',
1207
+ recordsProcessed: result.count
1208
+ }
1209
+ })
1210
+ } catch (error) {
1211
+ // Record the error (this won't crash!)
1212
+ await msg.trace({
1213
+ eventType: 'error',
1214
+ data: {
1215
+ text: 'Job failed',
1216
+ error: error.message,
1217
+ stack: error.stack
1218
+ }
1219
+ })
1220
+
1221
+ throw error // Still fail the message for retry
1222
+ }
1223
+ }, { autoAck: true })
1224
+ ```
1225
+
1226
+ ### Performance Tracking
1227
+
1228
+ Track timing and metrics:
1229
+
1230
+ ```javascript
1231
+ await queen.queue('reports').consume(async (msg) => {
1232
+ const start = Date.now()
1233
+
1234
+ await msg.trace({ data: { text: 'Report generation started' } })
1235
+
1236
+ const data = await fetchData(msg.data)
1237
+ const fetchTime = Date.now() - start
1238
+
1239
+ await msg.trace({
1240
+ data: {
1241
+ text: 'Data fetched',
1242
+ durationMs: fetchTime,
1243
+ rowCount: data.length
1244
+ }
1245
+ })
1246
+
1247
+ const report = await generatePDF(data)
1248
+
1249
+ await msg.trace({
1250
+ data: {
1251
+ text: 'Report generated',
1252
+ totalDurationMs: Date.now() - start,
1253
+ sizeKB: Math.round(report.size / 1024)
1254
+ }
1255
+ })
1256
+ })
1257
+ ```
1258
+
1259
+ ### Viewing Traces in the UI
1260
+
1261
+ **Method 1: From Message Details**
1262
+ 1. Go to **Messages** page
1263
+ 2. Click on any message
1264
+ 3. See "Processing Timeline" with all traces
1265
+
1266
+ **Method 2: Search by Trace Name**
1267
+ 1. Go to **Traces** page
1268
+ 2. Enter a trace name (e.g., `order-12345`)
1269
+ 3. See timeline across ALL messages with that name
1270
+
1271
+ **Method 3: Browse Available Traces**
1272
+ 1. Go to **Traces** page
1273
+ 2. See list of all trace names with statistics
1274
+ 3. Click any trace name to view
1275
+
1276
+ ### Important Notes
1277
+
1278
+ ⚡ **Safe & Non-Blocking**
1279
+ - Traces are awaited but NEVER crash your consumer
1280
+ - Failures are logged but don't throw errors
1281
+ - Your message processing continues normally
1282
+
1283
+ 🎯 **Best Practices**
1284
+ - Use descriptive trace names for easy searching
1285
+ - Include relevant data (IDs, counts, durations)
1286
+ - Use event types for visual organization
1287
+ - Trace both successes and failures
1288
+
1289
+ 🔍 **Use Cases**
1290
+ - Debug distributed workflows
1291
+ - Track multi-tenant operations
1292
+ - Monitor user journeys
1293
+ - Performance analysis
1294
+ - Error investigation
1295
+ - Audit trails
1296
+
1297
+ ---
1298
+
1299
+ ## Part 13: Callbacks & Error Handling
1300
+
1301
+ Sometimes you need more control over what happens when messages succeed or fail.
1302
+
1303
+ ### Success Callback
1304
+
1305
+ ```javascript
1306
+ await queen
1307
+ .queue('tasks')
1308
+ .consume(async (message) => {
1309
+ return await processMessage(message.data)
1310
+ })
1311
+ .onSuccess(async (message, result) => {
1312
+ console.log('Success! Result:', result)
1313
+ // Custom ack logic could go here
1314
+ })
1315
+ ```
1316
+
1317
+ ### Error Callback
1318
+
1319
+ ```javascript
1320
+ await queen
1321
+ .queue('tasks')
1322
+ .consume(async (message) => {
1323
+ throw new Error('Something went wrong!')
1324
+ })
1325
+ .onError(async (message, error) => {
1326
+ console.error('Failed:', error.message)
1327
+ // Log to external service, send alert, etc.
1328
+ })
1329
+ ```
1330
+
1331
+ ### Both Callbacks (Full Control)
1332
+
1333
+ ```javascript
1334
+ await queen
1335
+ .queue('tasks')
1336
+ .autoAck(false) // Disable auto-ack to manually control it
1337
+ .consume(async (message) => {
1338
+ return await riskyOperation(message.data)
1339
+ })
1340
+ .onSuccess(async (message, result) => {
1341
+ console.log('Success!')
1342
+ await queen.ack(message, true)
1343
+ })
1344
+ .onError(async (message, error) => {
1345
+ console.error('Error:', error.message)
1346
+
1347
+ // Custom logic: retry or DLQ?
1348
+ if (error.message.includes('temporary')) {
1349
+ // Retry
1350
+ await queen.ack(message, false)
1351
+ } else {
1352
+ // Send to DLQ immediately
1353
+ await queen.ack(message, 'failed', { error: error.message })
1354
+ }
1355
+ })
1356
+ ```
1357
+
1358
+ ### Push Callbacks
1359
+
1360
+ ```javascript
1361
+ await queen
1362
+ .queue('tasks')
1363
+ .push([
1364
+ { data: { id: 1 } },
1365
+ { data: { id: 2 } }
1366
+ ])
1367
+ .onSuccess(async (messages) => {
1368
+ console.log('Pushed successfully!')
1369
+ })
1370
+ .onError(async (messages, error) => {
1371
+ console.error('Push failed:', error)
1372
+ })
1373
+ .onDuplicate(async (messages, error) => {
1374
+ console.warn('Duplicate transaction IDs detected')
1375
+ })
1376
+ ```
1377
+
1378
+ ### Batch Ack with Mixed Results
1379
+
1380
+ ```javascript
1381
+ const messages = await queen.queue('tasks').batch(10).pop()
1382
+
1383
+ // Process and mark each message individually
1384
+ for (const message of messages) {
1385
+ try {
1386
+ await processMessage(message.data)
1387
+ message._status = true // Mark as success
1388
+ } catch (error) {
1389
+ message._status = false // Mark as failure
1390
+ message._error = error.message
1391
+ }
1392
+ }
1393
+
1394
+ // Batch ack with individual statuses
1395
+ await queen.ack(messages)
1396
+ // Queen will ack some and nack others based on _status
1397
+ ```
1398
+
1399
+ ---
1400
+
1401
+ ## Part 14: Graceful Shutdown
1402
+
1403
+ Always clean up properly when shutting down!
1404
+
1405
+ ### Why Graceful Shutdown?
1406
+
1407
+ When you kill a process:
1408
+ 1. Buffered messages need to be flushed
1409
+ 2. In-progress messages need to finish
1410
+ 3. Connections need to close properly
1411
+
1412
+ ### Automatic Shutdown (Built-In)
1413
+
1414
+ Queen automatically handles `SIGINT` and `SIGTERM`:
1415
+
1416
+ ```javascript
1417
+ const queen = new Queen('http://localhost:6632')
1418
+
1419
+ // Your app runs...
1420
+
1421
+ // User presses Ctrl+C or Docker sends SIGTERM:
1422
+ // Queen automatically flushes buffers and closes cleanly!
1423
+ ```
1424
+
1425
+ ### Manual Shutdown
1426
+
1427
+ ```javascript
1428
+ const queen = new Queen('http://localhost:6632')
1429
+
1430
+ // Do work...
1431
+
1432
+ // Shutdown manually
1433
+ await queen.close()
1434
+ console.log('Queen shut down cleanly')
1435
+ ```
1436
+
1437
+ ### Shutdown with AbortController
1438
+
1439
+ For consumers, use signals to stop them gracefully:
1440
+
1441
+ ```javascript
1442
+ const controller = new AbortController()
1443
+
1444
+ // Start consumer with abort signal
1445
+ const consumerPromise = queen
1446
+ .queue('tasks')
1447
+ .consume(async (message) => {
1448
+ await processMessage(message.data)
1449
+ }, { signal: controller.signal })
1450
+
1451
+ // Later... stop the consumer
1452
+ controller.abort()
1453
+
1454
+ // Wait for consumer to finish current message and stop
1455
+ await consumerPromise
1456
+
1457
+ // Close Queen
1458
+ await queen.close()
1459
+ ```
1460
+
1461
+ ---
1462
+
1463
+ ## Cheat Sheet
1464
+
1465
+ ### Connection
1466
+
1467
+ ```javascript
1468
+ const queen = new Queen('http://localhost:6632')
1469
+ const queen = new Queen(['http://server1:6632', 'http://server2:6632'])
1470
+ ```
1471
+
1472
+ ### Queue Operations
1473
+
1474
+ ```javascript
1475
+ // Create
1476
+ await queen.queue('my-queue').create()
1477
+ await queen.queue('my-queue').config({ priority: 5 }).create()
1478
+
1479
+ // Delete
1480
+ await queen.queue('my-queue').delete()
1481
+ ```
1482
+
1483
+ ### Push
1484
+
1485
+ ```javascript
1486
+ // Simple
1487
+ await queen.queue('q').push([{ data: { value: 1 } }])
1488
+
1489
+ // With partition
1490
+ await queen.queue('q').partition('p1').push([{ data: { value: 1 } }])
1491
+
1492
+ // With buffering
1493
+ await queen.queue('q').buffer({ messageCount: 100, timeMillis: 1000 }).push([{ data: { value: 1 } }])
1494
+
1495
+ // With custom transaction ID
1496
+ await queen.queue('q').push([{ transactionId: 'my-id', data: { value: 1 } }])
1497
+ ```
1498
+
1499
+ ### Pop
1500
+
1501
+ ```javascript
1502
+ // Simple pop
1503
+ const msgs = await queen.queue('q').pop()
1504
+
1505
+ // Pop multiple
1506
+ const msgs = await queen.queue('q').batch(10).pop()
1507
+
1508
+ // Pop with long polling
1509
+ const msgs = await queen.queue('q').batch(10).wait(true).pop()
1510
+
1511
+ // Pop from partition
1512
+ const msgs = await queen.queue('q').partition('p1').pop()
1513
+ ```
1514
+
1515
+ ### Consume
1516
+
1517
+ ```javascript
1518
+ // Simple consume (runs forever)
1519
+ await queen.queue('q').consume(async (msg) => { /* process */ })
1520
+
1521
+ // Consume with limit
1522
+ await queen.queue('q').limit(10).consume(async (msg) => { /* process */ })
1523
+
1524
+ // Consume batches
1525
+ await queen.queue('q').batch(10).consume(async (msgs) => { /* process array */ })
1526
+
1527
+ // Consume with concurrency
1528
+ await queen.queue('q').concurrency(5).consume(async (msg) => { /* 5 parallel workers */ })
1529
+
1530
+ // Consume from partition
1531
+ await queen.queue('q').partition('p1').consume(async (msg) => { /* process */ })
1532
+
1533
+ // Consume with consumer group
1534
+ await queen.queue('q').group('my-group').consume(async (msg) => { /* process */ })
1535
+
1536
+ // Consume by namespace
1537
+ await queen.queue().namespace('my-ns').consume(async (msg) => { /* process */ })
1538
+
1539
+ // Consume by task
1540
+ await queen.queue().task('my-task').consume(async (msg) => { /* process */ })
1541
+ ```
1542
+
1543
+ ### Subscription Modes
1544
+
1545
+ ```javascript
1546
+ // Default (all messages, including historical)
1547
+ await queen.queue('q').group('my-group').consume(async (msg) => { /* all messages */ })
1548
+
1549
+ // Skip historical messages, only new ones
1550
+ await queen.queue('q').group('my-group').subscriptionMode('new').consume(async (msg) => { /* new only */ })
1551
+
1552
+ // Alternative: subscriptionMode('new-only')
1553
+ await queen.queue('q').group('my-group').subscriptionMode('new-only').consume(async (msg) => { /* new only */ })
1554
+
1555
+ // Subscribe from 'now'
1556
+ await queen.queue('q').group('my-group').subscriptionFrom('now').consume(async (msg) => { /* from now */ })
1557
+
1558
+ // Subscribe from timestamp
1559
+ const timestamp = '2025-10-28T10:00:00.000Z'
1560
+ await queen.queue('q').group('my-group').subscriptionFrom(timestamp).consume(async (msg) => { /* from timestamp */ })
1561
+ ```
1562
+
1563
+ ### Acknowledgment
1564
+
1565
+ ```javascript
1566
+ // Ack success
1567
+ await queen.ack(message, true)
1568
+
1569
+ // Ack failure (will retry)
1570
+ await queen.ack(message, false)
1571
+
1572
+ // Ack with error details
1573
+ await queen.ack(message, false, { error: 'Something went wrong' })
1574
+
1575
+ // Batch ack
1576
+ await queen.ack([msg1, msg2, msg3], true)
1577
+ ```
1578
+
1579
+ ### Transactions
1580
+
1581
+ ```javascript
1582
+ await queen
1583
+ .transaction()
1584
+ .ack(message)
1585
+ .queue('output-queue')
1586
+ .push([{ data: { result: 'processed' } }])
1587
+ .commit()
1588
+ ```
1589
+
1590
+ ### Lease Renewal
1591
+
1592
+ ```javascript
1593
+ // Manual renewal
1594
+ await queen.renew(message)
1595
+ await queen.renew([msg1, msg2, msg3])
1596
+ await queen.renew(message.leaseId)
1597
+
1598
+ // Auto renewal
1599
+ await queen.queue('q').renewLease(true, 60000).consume(async (msg) => { /* process */ })
1600
+ ```
1601
+
1602
+ ### Buffering
1603
+
1604
+ ```javascript
1605
+ // Flush all buffers
1606
+ await queen.flushAllBuffers()
1607
+
1608
+ // Flush specific queue
1609
+ await queen.queue('q').flushBuffer()
1610
+
1611
+ // Get buffer stats
1612
+ const stats = queen.getBufferStats()
1613
+ ```
1614
+
1615
+ ### Message Tracing
1616
+
1617
+ ```javascript
1618
+ // Basic trace
1619
+ await msg.trace({ data: { text: 'Processing started' } })
1620
+
1621
+ // Trace with name (for cross-message correlation)
1622
+ await msg.trace({
1623
+ traceName: 'order-12345',
1624
+ data: { text: 'Order created' }
1625
+ })
1626
+
1627
+ // Multiple trace names (multi-dimensional tracking)
1628
+ await msg.trace({
1629
+ traceName: ['tenant-acme', 'room-123', 'user-456'],
1630
+ data: { text: 'Message sent' }
1631
+ })
1632
+
1633
+ // With event type
1634
+ await msg.trace({
1635
+ eventType: 'error', // info, error, step, processing, warning
1636
+ data: { text: 'Processing failed', reason: 'timeout' }
1637
+ })
1638
+
1639
+ // Rich data
1640
+ await msg.trace({
1641
+ traceName: 'report-gen-789',
1642
+ data: {
1643
+ text: 'Report generated',
1644
+ durationMs: 1500,
1645
+ sizeKB: 250
1646
+ }
1647
+ })
1648
+ ```
1649
+
1650
+ ### DLQ
1651
+
1652
+ ```javascript
1653
+ // Query DLQ
1654
+ const dlq = await queen.queue('q').dlq().limit(10).get()
1655
+ const dlq = await queen.queue('q').dlq('consumer-group').limit(10).get()
1656
+ const dlq = await queen.queue('q').dlq().from('2025-01-01').to('2025-01-31').get()
1657
+ ```
1658
+
1659
+ ### Shutdown
1660
+
1661
+ ```javascript
1662
+ await queen.close()
1663
+ ```
1664
+
1665
+ ---
1666
+
1667
+ ## Configuration Defaults
1668
+
1669
+ ### Client Defaults
1670
+ ```javascript
1671
+ {
1672
+ timeoutMillis: 30000, // 30 seconds
1673
+ retryAttempts: 3,
1674
+ retryDelayMillis: 1000,
1675
+ loadBalancingStrategy: 'round-robin',
1676
+ enableFailover: true
1677
+ }
1678
+ ```
1679
+
1680
+ ### Queue Defaults
1681
+ ```javascript
1682
+ {
1683
+ leaseTime: 300, // 5 minutes
1684
+ retryLimit: 3,
1685
+ priority: 0,
1686
+ delayedProcessing: 0,
1687
+ windowBuffer: 0,
1688
+ maxSize: 0, // Unlimited
1689
+ retentionSeconds: 0, // Keep forever
1690
+ completedRetentionSeconds: 0,
1691
+ encryptionEnabled: false
1692
+ }
1693
+ ```
1694
+
1695
+ ### Consume Defaults
1696
+ ```javascript
1697
+ {
1698
+ concurrency: 1,
1699
+ batch: 1,
1700
+ autoAck: true,
1701
+ wait: true, // Long polling
1702
+ timeoutMillis: 30000,
1703
+ limit: null, // Run forever
1704
+ idleMillis: null, // No idle timeout
1705
+ renewLease: false
1706
+ }
1707
+ ```
1708
+
1709
+ ### Pop Defaults
1710
+ ```javascript
1711
+ {
1712
+ batch: 1,
1713
+ wait: false, // No long polling
1714
+ autoAck: false // Manual ack required
1715
+ }
1716
+ ```
1717
+
1718
+ ---
1719
+
1720
+ ## Logging
1721
+
1722
+ Enable detailed logging for debugging:
1723
+
1724
+ ```bash
1725
+ export QUEEN_CLIENT_LOG=true
1726
+ node your-app.js
1727
+ ```
1728
+
1729
+ Example log output:
1730
+ ```
1731
+ [2025-10-28T10:30:45.123Z] [INFO] [Queen.constructor] {"status":"initialized","urls":1}
1732
+ [2025-10-28T10:30:45.234Z] [INFO] [QueueBuilder.push] {"queue":"tasks","partition":"Default","count":5}
1733
+ [2025-10-28T10:30:46.789Z] [INFO] [HttpClient.request] {"method":"POST","url":"http://localhost:6632/api/v1/push"}
1734
+ ```
1735
+
1736
+ ---
1737
+
1738
+ ## Tips & Best Practices
1739
+
1740
+ 1. **Use `consume()` for workers** - It's simpler and handles retries automatically
1741
+ 2. **Use `pop()` for control** - When you need precise control over acking and timing
1742
+ 3. **Buffer for speed** - Always use buffering when pushing many messages
1743
+ 4. **Partitions for order** - Use partitions when message order matters
1744
+ 5. **Consumer groups for scale** - Run multiple workers in the same group
1745
+ 6. **Transactions for consistency** - Use transactions when operations must be atomic
1746
+ 7. **Enable DLQ** - Always enable DLQ in production to catch failures
1747
+ 8. **Renew long leases** - Use auto-renewal for long-running tasks
1748
+ 9. **Graceful shutdown** - Always call `queen.close()` before exiting
1749
+ 10. **Monitor DLQ** - Regularly check your DLQ for failed messages
1750
+
1751
+ ---
1752
+
1753
+ ## Real-World Example: Complete Pipeline
1754
+
1755
+ Here's a complete example showing many features together:
1756
+
1757
+ ```javascript
1758
+ import { Queen } from './client-js/client-v2/index.js'
1759
+
1760
+ const queen = new Queen('http://localhost:6632')
1761
+
1762
+ // Setup queues
1763
+ await queen.queue('raw-events').config({ priority: 5 }).create()
1764
+ await queen.queue('processed-events').config({ priority: 10 }).create()
1765
+ await queen.queue('notifications').config({ delayedProcessing: 60 }).create()
1766
+
1767
+ // Stage 1: Ingest raw events with buffering
1768
+ async function ingestEvents() {
1769
+ for (let i = 0; i < 10000; i++) {
1770
+ await queen
1771
+ .queue('raw-events')
1772
+ .partition(`user-${i % 100}`) // Partition by user
1773
+ .buffer({ messageCount: 500, timeMillis: 1000 })
1774
+ .push([{
1775
+ data: {
1776
+ userId: i % 100,
1777
+ event: 'page_view',
1778
+ timestamp: Date.now()
1779
+ }
1780
+ }])
1781
+ }
1782
+
1783
+ await queen.flushAllBuffers()
1784
+ console.log('Ingestion complete!')
1785
+ }
1786
+
1787
+ // Stage 2: Process events with transaction
1788
+ async function processEvents() {
1789
+ await queen
1790
+ .queue('raw-events')
1791
+ .group('processors')
1792
+ .concurrency(5)
1793
+ .batch(10)
1794
+ .autoAck(false) // Manual ack for transactions
1795
+ .consume(async (messages) => {
1796
+ // Process batch
1797
+ const processed = messages.map(m => ({
1798
+ userId: m.data.userId,
1799
+ processed: true,
1800
+ timestamp: Date.now()
1801
+ }))
1802
+
1803
+ // Atomic: ack inputs and push outputs
1804
+ const txn = queen.transaction()
1805
+
1806
+ for (const msg of messages) {
1807
+ txn.ack(msg)
1808
+ }
1809
+
1810
+ txn.queue('processed-events').push(
1811
+ processed.map(p => ({ data: p }))
1812
+ )
1813
+
1814
+ await txn.commit()
1815
+ })
1816
+ .onError(async (messages, error) => {
1817
+ console.error('Processing failed:', error)
1818
+ await queen.ack(messages, false)
1819
+ })
1820
+ }
1821
+
1822
+ // Stage 3: Send notifications (delayed)
1823
+ async function sendNotifications() {
1824
+ await queen
1825
+ .queue('processed-events')
1826
+ .group('notifiers')
1827
+ .renewLease(true, 30000) // Auto-renew every 30s
1828
+ .consume(async (message) => {
1829
+ // Queue delayed notification
1830
+ await queen
1831
+ .queue('notifications')
1832
+ .push([{
1833
+ data: {
1834
+ userId: message.data.userId,
1835
+ message: 'Your data has been processed!'
1836
+ }
1837
+ }])
1838
+
1839
+ console.log(`Notification queued for user ${message.data.userId}`)
1840
+ })
1841
+ }
1842
+
1843
+ // Stage 4: Check DLQ periodically
1844
+ setInterval(async () => {
1845
+ const dlq = await queen
1846
+ .queue('raw-events')
1847
+ .dlq()
1848
+ .limit(10)
1849
+ .get()
1850
+
1851
+ if (dlq.total > 0) {
1852
+ console.warn(`⚠️ ${dlq.total} failed messages in DLQ!`)
1853
+ }
1854
+ }, 60000) // Check every minute
1855
+
1856
+ // Run the pipeline
1857
+ await ingestEvents()
1858
+ await Promise.all([
1859
+ processEvents(),
1860
+ sendNotifications()
1861
+ ])
1862
+
1863
+ // Graceful shutdown
1864
+ process.on('SIGINT', async () => {
1865
+ await queen.close()
1866
+ process.exit(0)
1867
+ })
1868
+ ```
1869
+
1870
+ ---
1871
+
1872
+ ## What's Next?
1873
+
1874
+ You now know everything about Queen v2! 🎉
1875
+
1876
+ **Additional resources:**
1877
+ - [API Documentation](../../API.md) - Complete API reference
1878
+ - [Test Examples](../test-v2/) - 94 working test cases
1879
+ - [Architecture Guide](../../docs/) - Deep dive into Queen's internals
1880
+
1881
+ **Need help?** Check out the test files in `test-v2/` - they're full of working examples!
1882
+
1883
+ Happy queuing! 👑✨