queen-mq 0.6.3 → 0.7.4

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 (48) hide show
  1. package/README.md +500 -327
  2. package/{client-js/client-v2 → client-v2}/Queen.js +5 -2
  3. package/{client-js/client-v2 → client-v2}/README.md +53 -2
  4. package/{client-js/client-v2 → client-v2}/builders/QueueBuilder.js +24 -1
  5. package/{client-js/client-v2 → client-v2}/consumer/ConsumerManager.js +25 -4
  6. package/{client-js/client-v2 → client-v2}/http/HttpClient.js +24 -12
  7. package/client-v2/http/LoadBalancer.js +271 -0
  8. package/{client-js/client-v2 → client-v2}/utils/defaults.js +4 -2
  9. package/package.json +6 -8
  10. package/{client-js/test-v2 → test-v2}/maintenance.js +1 -1
  11. package/LICENSE.md +0 -202
  12. package/client-js/benchmark/consumer.js +0 -209
  13. package/client-js/benchmark/consumer_multi.js +0 -216
  14. package/client-js/benchmark/producer.js +0 -80
  15. package/client-js/benchmark/producer_multi.js +0 -115
  16. package/client-js/client-v2/http/LoadBalancer.js +0 -50
  17. /package/{client-js/client-v2 → client-v2}/LOGGING.md +0 -0
  18. /package/{client-js/client-v2 → client-v2}/buffer/BufferManager.js +0 -0
  19. /package/{client-js/client-v2 → client-v2}/buffer/MessageBuffer.js +0 -0
  20. /package/{client-js/client-v2 → client-v2}/builders/TransactionBuilder.js +0 -0
  21. /package/{client-js/client-v2 → client-v2}/index.js +0 -0
  22. /package/{client-js/client-v2 → client-v2}/stream/StreamBuilder.js +0 -0
  23. /package/{client-js/client-v2 → client-v2}/stream/StreamConsumer.js +0 -0
  24. /package/{client-js/client-v2 → client-v2}/stream/Window.js +0 -0
  25. /package/{client-js/client-v2 → client-v2}/utils/logger.js +0 -0
  26. /package/{client-js/client-v2 → client-v2}/utils/validation.js +0 -0
  27. /package/{client-js/test-v2 → test-v2}/AI_TEST_SUMMARY.md +0 -0
  28. /package/{client-js/test-v2 → test-v2}/GETTING_STARTED.md +0 -0
  29. /package/{client-js/test-v2 → test-v2}/MAINTENANCE_TEST.md +0 -0
  30. /package/{client-js/test-v2 → test-v2}/README_SUBSCRIPTION_TESTS.md +0 -0
  31. /package/{client-js/test-v2 → test-v2}/ai_buffering.js +0 -0
  32. /package/{client-js/test-v2 → test-v2}/ai_error_handling.js +0 -0
  33. /package/{client-js/test-v2 → test-v2}/ai_lease_renewal.js +0 -0
  34. /package/{client-js/test-v2 → test-v2}/ai_mixed_scenarios.js +0 -0
  35. /package/{client-js/test-v2 → test-v2}/ai_priority.js +0 -0
  36. /package/{client-js/test-v2 → test-v2}/ai_resources.js +0 -0
  37. /package/{client-js/test-v2 → test-v2}/ai_ttl_retention.js +0 -0
  38. /package/{client-js/test-v2 → test-v2}/complete.js +0 -0
  39. /package/{client-js/test-v2 → test-v2}/consume.js +0 -0
  40. /package/{client-js/test-v2 → test-v2}/dlq.js +0 -0
  41. /package/{client-js/test-v2 → test-v2}/load.js +0 -0
  42. /package/{client-js/test-v2 → test-v2}/pop.js +0 -0
  43. /package/{client-js/test-v2 → test-v2}/push.js +0 -0
  44. /package/{client-js/test-v2 → test-v2}/queue.js +0 -0
  45. /package/{client-js/test-v2 → test-v2}/retention.js +0 -0
  46. /package/{client-js/test-v2 → test-v2}/run.js +0 -0
  47. /package/{client-js/test-v2 → test-v2}/subscription.js +0 -0
  48. /package/{client-js/test-v2 → test-v2}/transaction.js +0 -0
package/README.md CHANGED
@@ -1,459 +1,632 @@
1
- # Queen MQ - PostgreSQL-backed C++ Message Queue
1
+ # Queen MQ - JavaScript Client
2
2
 
3
3
  <div align="center">
4
4
 
5
- **A modern, performant message queue system built on PostgreSQL**
5
+ **Modern, high-performance message queue client for Node.js**
6
6
 
7
+ [![npm](https://img.shields.io/npm/v/queen-mq.svg)](https://www.npmjs.com/package/queen-mq)
7
8
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.md)
8
9
  [![Node](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)](https://nodejs.org/)
9
- [![C++](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://en.cppreference.com/w/cpp/17)
10
10
 
11
- [Quick Start](#one-single-example) • [Complete V2 Guide](client-js/client-v2/README.md) • [Webapp](#webapp) • [Server Setup](#install-the-server-and-configure-it) • [HTTP API](#raw-http-api)
12
-
13
- <p align="center">
14
- <img src="assets/queen-logo.svg" alt="Queen Logo" width="120" />
15
- </p>
11
+ [Quick Start](#quick-start) • [Complete Guide](client-v2/README.md) • [Examples](#examples) • [API Reference](#api-reference)
16
12
 
17
13
  </div>
18
14
 
19
15
  ---
20
16
 
21
- Why "Queen"? Because years ago, when I first read the word "queue" in my mind, I read it as "queen".
17
+ ## What is Queen MQ?
18
+
19
+ Queen MQ is a PostgreSQL-backed message queue system with a powerful feature set:
20
+
21
+ - **FIFO Partitions** - Unlimited ordered partitions within queues
22
+ - **Consumer Groups** - Kafka-style consumer groups for scalability
23
+ - **Flexible Semantics** - Exactly-once, at-least-once, and at-most-once delivery
24
+ - **Transactions** - Atomic operations across push and ack
25
+ - **High Performance** - 200K+ messages/sec with proper batching
26
+ - **Subscription Modes** - Process from beginning, new messages only, or from timestamp
27
+ - **Dead Letter Queue** - Automatic failure handling and monitoring
28
+ - **Message Tracing** - Debug distributed workflows with trace timelines
29
+ - **Client-Side Buffering** - 10x-100x throughput boost for high-volume pushes
30
+ - **Real-time Streaming** - Windowed aggregation and processing
31
+
32
+ This client provides a fluent, promise-based API for Node.js applications.
22
33
 
23
34
  ---
24
35
 
25
- [QUICKSTART](docs/QUICKSTART.md)
36
+ ## Installation
26
37
 
27
- Latest server production version is **0.6.3**.
38
+ ```bash
39
+ npm install queen-mq
40
+ ```
41
+
42
+ **Requirements:** Node.js 22+
28
43
 
29
44
  ---
30
45
 
31
- ## Introduction
32
-
33
- QueenMQ is a queue system written in C++ and backed by PostgreSQL, born from the need to manage many FIFO partitions for Smartchat with solid guarantees around delivery and failure handling. During the initial development, I realized that with a few simple additions to the original design, I could build a very powerful and flexible queue system. This project is almost entirely written by AI, with my supervision—only the test files (or a good part of them) are manually written.
34
-
35
- Here are the main features:
36
- - Unlimited FIFO partitions within queues
37
- - Queue semantics like RabbitMQ
38
- - Consumer groups over queues like Kafka
39
- - Allows for all the patterns you need, from simple queues to complex workflows and request/response patterns
40
- - QoS levels: Exactly-once delivery (with transactionId), at-least-once delivery, and at-most-once delivery
41
- - Subscription modes for replay (new messages only or from a specific timestamp) and message history control
42
- - Transactions between operations (push and ack mainly) for atomicity
43
- - Dead letter queue for failure handling
44
- - Lease renewal for long-running tasks
45
- - Message tracing for debugging workflows
46
- - Encryption of messages at DB level
47
- - Automatic message retention and cleanup - Configurable per-queue retention policies
48
- - Streaming capabilities for real-time aggregation and processing of messages
49
- - A nice webapp for monitoring and managing the system
50
- - Maintenace mode that allows to continue pushing messages even when the database is down, and to drain the messages to the database when the maintenance mode is disabled
51
-
52
- The system consists of a PostgreSQL database, a replicated server that can be scaled horizontally (though it won't be the bottleneck), and a client library for interacting with the server. All client-server communication happens over HTTP, and the client library are available for JavaScript and C++ (Other languages like Python are planned). There's also a modern Vue 3 web app for monitoring and managing the system.
53
-
54
- With proper batching, the system can handle +200k **messages** per second (not req/s) on modest hardware.
55
-
56
- Main documentation:
57
- - [Client Guide JS](client-js/client-v2/README.md)
58
- - [Client Guide C++](client-cpp/README.md)
59
- - [Server Guide](server/README.md)
60
- - [Streaming Guide](docs/STREAMING_USAGE.md)
61
- - [API Reference](server/API.md)
62
- - [Message Retention & Cleanup](docs/RETENTION.md)
63
- - [Webapp](webapp/README.md)
64
- - [Expose the Webapp behind a proxy](proxy/README.md)
65
-
66
- ## Concepts
67
-
68
- Although the system is designed to be simple to use (not really), there are some concepts that are important to understand to use the system effectively.
46
+ ## Quick Start
47
+
48
+ ```javascript
49
+ import { Queen } from 'queen-mq'
50
+
51
+ // Connect to Queen server
52
+ const queen = new Queen('http://localhost:6632')
53
+
54
+ // Create a queue
55
+ await queen.queue('tasks').create()
56
+
57
+ // Push messages
58
+ await queen.queue('tasks').push([
59
+ { data: { task: 'send-email', to: 'alice@example.com' } }
60
+ ])
61
+
62
+ // Consume messages
63
+ await queen.queue('tasks').consume(async (message) => {
64
+ console.log('Processing:', message.data)
65
+ // Auto-ack on success, auto-retry on error
66
+ })
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Core Concepts
69
72
 
70
73
  ### Queues
71
74
 
72
- Queues are a way to organize your messages into logical groups. Each queue is like a container for messages. You can have as many queues as you want, and you can configure each queue with different settings, like the lease time, retry limit, encryption, retention, priority, delayed processing, window buffer and retention.
75
+ Logical containers for messages with configurable settings:
76
+ - **Lease time** - How long a consumer has to process a message
77
+ - **Retry limit** - Number of retry attempts before DLQ
78
+ - **Priority** - Queue priority for multi-queue consumers
79
+ - **Encryption** - Message payload encryption at rest
80
+ - **Retention** - Automatic cleanup policies
81
+
82
+ ```javascript
83
+ await queen.queue('orders')
84
+ .config({
85
+ leaseTime: 300, // 5 minutes
86
+ retryLimit: 3,
87
+ priority: 5,
88
+ encryptionEnabled: false
89
+ })
90
+ .create()
91
+ ```
73
92
 
74
93
  ### Partitions
75
94
 
76
- Partitions are a way to organize your messages into logical ordered groups. Each partition is like a separate queue inside a queue, and messages in the same partition are guaranteed to be processed in order. Only one consumer/client can acquire the lock for a partition at a time. You can have as many partitions as you want, and you can have as many consumers/clients as you want. If the lease in not acknowledged in time, the message is released and can be acquired by another consumer/client. Use renewLease() to renew the lease during long-running tasks. Each different consumer group can acquire the lock for a partition independently, so you can have as many consumer groups as you want.
95
+ Ordered lanes within a queue. Messages in the same partition are processed sequentially:
77
96
 
78
- ### Default partition and consumer group
97
+ ```javascript
98
+ // All messages for user-123 are processed in order
99
+ await queen.queue('user-events')
100
+ .partition('user-123')
101
+ .push([
102
+ { data: { event: 'login' } },
103
+ { data: { event: 'view-page' } },
104
+ { data: { event: 'logout' } }
105
+ ])
106
+ ```
79
107
 
80
- The default partition (if not specified) is `Default`. If you don't specify a consumer group, the messages are processed in queue mode, that is creating a default consumer group named `__QUEUE_MODE__`. Each queue can be used at the same time by multiple consumer groups, and each consumer group can process messages from multiple partitions.
108
+ **Use cases:**
109
+ - Per-user ordering
110
+ - Per-tenant isolation
111
+ - Sharding for parallelism
81
112
 
82
- ### Consumer groups
113
+ ### Consumer Groups
83
114
 
84
- Consumer groups are a way to process messages for different purposes. Each consumer group is like a separate queue, and messages in the same consumer group partition are guaranteed to be processed in order. Each consumer group tracks its own position in the queue. You can start a consumer group from the beginning of the queue, from a specific timestamp, or only new messages.
115
+ Multiple consumers sharing work, with independent progress tracking:
85
116
 
86
- ### Subscription modes
117
+ ```javascript
118
+ // Worker 1 & 2 share the load
119
+ await queen.queue('emails')
120
+ .group('processors')
121
+ .consume(async (message) => {
122
+ await sendEmail(message.data)
123
+ })
124
+
125
+ // Separate group processes same messages independently
126
+ await queen.queue('emails')
127
+ .group('analytics')
128
+ .consume(async (message) => {
129
+ await logMetrics(message.data)
130
+ })
131
+ ```
87
132
 
88
- Subscription modes are a way to control the message history that is processed. You can choose to process all messages (including historical ones), only new messages, or messages from a specific timestamp. Subscription modes are only available when using consumer groups.
133
+ ### Subscription Modes
89
134
 
90
- **Server Default:** By default, new consumer groups process all historical messages. You can change this server-wide:
135
+ Control whether consumer groups process historical messages:
91
136
 
92
- ```bash
93
- export DEFAULT_SUBSCRIPTION_MODE="new" # Skip historical messages by default
94
- ./bin/queen-server
137
+ ```javascript
138
+ // Default: Process ALL messages (including backlog)
139
+ await queen.queue('events')
140
+ .group('batch-analytics')
141
+ .consume(async (message) => { /* all messages */ })
142
+
143
+ // Skip history, only new messages
144
+ await queen.queue('events')
145
+ .group('realtime-monitor')
146
+ .subscriptionMode('new')
147
+ .consume(async (message) => { /* new only */ })
148
+
149
+ // Start from specific timestamp
150
+ await queen.queue('events')
151
+ .group('replay')
152
+ .subscriptionFrom('2025-10-28T10:00:00.000Z')
153
+ .consume(async (message) => { /* from timestamp */ })
95
154
  ```
96
155
 
97
- This is useful for real-time systems where only new messages matter, or to prevent accidental processing of large backlogs. Clients can still override with `.subscriptionMode()` if needed.
156
+ ---
98
157
 
99
- ### Long polling (waiting for messages)
158
+ ## Connection Options
100
159
 
101
- The client works with the pull model for pop operations, meaning that you need to explicitly request messages from the queue. Pop and consume mehtods can "wait" server side for messages to be available. When the method is called with wait=true, the method will block until messages are available or the timeout is reached. When the timeout is reached, the method returns an empty array. Long polling is a very efficient way to wait for messages, and it is the recommended way to consume messages.
160
+ ### Single Server
102
161
 
103
- ### Lease renewal
162
+ ```javascript
163
+ const queen = new Queen('http://localhost:6632')
164
+ ```
104
165
 
105
- Lease renewal is a way to keep the lock for a partition or consumer group alive. You can use lease renewal to prevent the lock from expiring and being acquired by another consumer/client.
166
+ ### Multiple Servers (High Availability)
106
167
 
107
- ### Ack and Nack
168
+ ```javascript
169
+ const queen = new Queen([
170
+ 'http://server1:6632',
171
+ 'http://server2:6632'
172
+ ])
173
+ ```
108
174
 
109
- Ack and Nack are the way to acknowledge or not a message. Ack means that the message has been processed successfully, and Nack means that the message has not been processed successfully. Ack/Nack requires the partitionId, the transactionId and the leaseId to be specified. If the ack is coming from a consumer group, the consumer group name is also required. This logic is handled automatically by the client library, you don't need to worry about it. If the message has already been acknowledged, the ack will be ignored. Based on the configuration, the message can be automatically acknowledged or manually acknowledged.
175
+ ### Full Configuration
110
176
 
111
- ### Transactions
177
+ ```javascript
178
+ const queen = new Queen({
179
+ urls: ['http://server1:6632', 'http://server2:6632'],
180
+ timeoutMillis: 30000,
181
+ retryAttempts: 3,
182
+ loadBalancingStrategy: 'round-robin', // or 'session'
183
+ enableFailover: true
184
+ })
185
+ ```
112
186
 
113
- Transactions are a way to ensure that a group of operations are atomic. You can use transactions to ensure that a group of operations are executed together, and if one of the operations fails, the entire transaction is rolled back. Transactions are useful to achieve the exactly-once guarantee. You can process a message on a queue, and forward to the next queue only if the consumer lease is still valid, concatenating ack and push into a single transaction.
187
+ ---
114
188
 
115
- ### Dead letter queue
189
+ ## Basic Usage Patterns
116
190
 
117
- The dead letter queue is a way to handle messages that are not processed successfully. When a message is not processed successfully, it is moved to the dead letter queue. Messages goes in the DLQ when they are NACK after the retry limit is reached. You configure the retry limit for each queue at queue config.
191
+ ### Push Messages
118
192
 
119
- ## Comparison with RabbitMQ, Kafka, and NATS
193
+ ```javascript
194
+ // Simple push
195
+ await queen.queue('tasks').push([
196
+ { data: { job: 'resize-image', imageId: 123 } }
197
+ ])
120
198
 
121
- For users familiar with existing message queue systems, here's how Queen's semantics and usage patterns compare:
199
+ // With partition
200
+ await queen.queue('tasks')
201
+ .partition('tenant-456')
202
+ .push([{ data: { action: 'process' } }])
203
+
204
+ // With custom transaction ID (for exactly-once)
205
+ await queen.queue('tasks').push([
206
+ {
207
+ transactionId: 'unique-id-123',
208
+ data: { value: 42 }
209
+ }
210
+ ])
211
+ ```
122
212
 
123
- ### Conceptual Mapping
213
+ ### Consume Messages (Long-Running Workers)
124
214
 
125
- | Queen Concept | RabbitMQ Equivalent | Kafka Equivalent | NATS Equivalent |
126
- |---------------|---------------------|------------------|-----------------|
127
- | Queue | Queue | Topic | Stream (JetStream) |
128
- | Partition | N/A (queues are single-consumer by default) | Partition | N/A |
129
- | Consumer Group | Competing Consumers pattern | Consumer Group | Queue Group |
130
- | Queue Mode (no group) | Exclusive consumer | N/A (always uses groups) | Single subscriber |
131
- | Lease | Message TTL / Visibility timeout | N/A (commit-based) | Ack wait / nak delay |
132
- | Ack/Nack | Ack/Nack | Commit offset | Ack/Nak |
133
- | Transaction | Publisher confirms + consumer acks | Transactional producer/consumer | N/A |
134
- | Dead Letter Queue | Dead Letter Exchange | N/A (manual) | N/A (manual) |
215
+ ```javascript
216
+ // Runs forever, processes messages as they arrive
217
+ await queen.queue('tasks')
218
+ .concurrency(10) // 10 parallel workers
219
+ .batch(20) // Fetch 20 at a time
220
+ .consume(async (message) => {
221
+ await processTask(message.data)
222
+ // Auto-ack on success, auto-retry on error
223
+ })
224
+
225
+ // Process with limit and stop
226
+ await queen.queue('tasks')
227
+ .limit(100)
228
+ .consume(async (message) => {
229
+ await processTask(message.data)
230
+ })
231
+ ```
135
232
 
233
+ ### Pop Messages (On-Demand Processing)
136
234
 
137
- ## One single example
235
+ ```javascript
236
+ // Grab messages manually
237
+ const messages = await queen.queue('tasks')
238
+ .batch(10)
239
+ .wait(true) // Long polling
240
+ .pop()
241
+
242
+ // Manual acknowledgment
243
+ for (const message of messages) {
244
+ try {
245
+ await processMessage(message.data)
246
+ await queen.ack(message, true) // Success
247
+ } catch (error) {
248
+ await queen.ack(message, false) // Retry
249
+ }
250
+ }
251
+ ```
138
252
 
139
- > 📖 **[Complete Guide: client-js/client-v2/README.md](client-js/client-v2/README.md)** - Full tutorial with all features!
253
+ ### Transactions (Atomic Operations)
140
254
 
141
255
  ```javascript
142
- import { Queen } from 'queen-mq'
256
+ // Pop from queue A
257
+ const messages = await queen.queue('input').pop()
143
258
 
144
- // Connect to Queen
145
- const queen = new Queen('http://localhost:6632')
259
+ // Atomically: ack input AND push output
260
+ await queen.transaction()
261
+ .ack(messages[0])
262
+ .queue('output')
263
+ .push([{ data: processedResult }])
264
+ .commit()
146
265
 
147
- // Create a queue
148
- await queen
149
- .queue('critical-task')
150
- .config({
151
- leaseTime: 10, // 10 seconds to process the messages (seconds)
152
- })
153
- .create()
266
+ // If commit fails, nothing happens - message stays in input queue
267
+ ```
154
268
 
155
- // Push messages
156
- await queen
157
- .queue('critical-task')
158
- .partition('tenant-123')
159
- .push([
160
- {
161
- transactionId: 'my-id', // This is autogenerated, but you can set your own. Unique per queue partition.
162
- data: { id: 123, description: 'Critical task' } // The message payload
163
- },
164
- ])
165
- .onSuccess(async (messages) => { // Not mandatory
166
- console.log('Messages pushed successfully:', messages)
167
- })
168
- .onDuplicate(async (messages) => { // Not mandatory, triggered when a message with the same transactionId is pushed
169
- console.warn('Duplicate transaction IDs detected')
170
- })
171
- .onError(async (messages, error) => { // Without callbacks, push throws an error if some messages are not pushed
172
- console.error('Error pushing messages:', error)
173
- })
269
+ ### Client-Side Buffering (High Throughput)
174
270
 
175
- // Consume messages
176
- await queen
177
- .queue('critical-task')
178
- // The consumer group name, without this, the messages are processed in queue mode
179
- .group('processor-consumer-group')
180
- // 10 parallel workers
181
- .concurrency(10)
182
- // I want to manually ack/nack messages
183
- .autoAck(false)
184
- // 10 messages per batch, prefetch them
185
- .batch(10)
186
- // Auto-renew the lease for the messages every 2 seconds
187
- .renewLease(true, 2000)
188
- // Process each message individually
189
- .each()
190
- // Do your work here
191
- .consume(async (message) => {
192
- console.log('Processing:', message.data)
193
- })
194
- // Ack the messages if you processed them successfully
195
- .onSuccess(async (message) => {
196
- await queen
197
- .transaction()
198
- .queue('critical-task-next')
199
- .partition('XXX')
200
- .push([{ data: { message: 'Final', count: 3 } }])
201
- .ack(message)
202
- .commit()
203
- })
204
- // Nack the messages if you failed to process them
205
- .onError(async (message, error) => {
206
- console.error('Error processing messages:', error)
207
- await queen.ack(message, false)
208
- })
271
+ ```javascript
272
+ // Buffer messages locally, batch to server
273
+ for (let i = 0; i < 10000; i++) {
274
+ await queen.queue('events')
275
+ .buffer({ messageCount: 500, timeMillis: 1000 })
276
+ .push([{ data: { id: i } }])
277
+ }
278
+
279
+ // Flush remaining buffered messages
280
+ await queen.flushAllBuffers()
281
+
282
+ // Result: 10x-100x faster than individual pushes
209
283
  ```
210
284
 
211
- ## Webapp
285
+ ### Dead Letter Queue
212
286
 
213
- A modern Vue 3 web interface for managing and monitoring Queen MQ.
287
+ ```javascript
288
+ // Enable DLQ on queue
289
+ await queen.queue('risky')
290
+ .config({ retryLimit: 3, dlqAfterMaxRetries: true })
291
+ .create()
292
+
293
+ // Query failed messages
294
+ const dlq = await queen.queue('risky')
295
+ .dlq()
296
+ .limit(10)
297
+ .get()
298
+
299
+ console.log(`Found ${dlq.total} failed messages`)
300
+ for (const msg of dlq.messages) {
301
+ console.log('Error:', msg.errorMessage)
302
+ }
303
+ ```
214
304
 
215
- ![Queen MQ Dashboard](./assets/dashboard.png)
305
+ ### Message Tracing
216
306
 
217
- ![Queen MQ Queues](./assets/queues.png)
307
+ ```javascript
308
+ await queen.queue('orders').consume(async (msg) => {
309
+ const orderId = msg.data.orderId
310
+
311
+ // Record trace with name for cross-service correlation
312
+ await msg.trace({
313
+ traceName: `order-${orderId}`,
314
+ eventType: 'info',
315
+ data: { text: 'Order processing started' }
316
+ })
317
+
318
+ await processOrder(msg.data)
319
+
320
+ await msg.trace({
321
+ traceName: `order-${orderId}`,
322
+ eventType: 'processing',
323
+ data: {
324
+ text: 'Order completed',
325
+ total: msg.data.total
326
+ }
327
+ })
328
+ })
218
329
 
219
- ![Queen MQ Messages](./assets/messages.png)
330
+ // View traces in webapp: Traces → Search "order-12345"
331
+ ```
220
332
 
221
- ![Queen MQ Traces](./assets/traces.png)
333
+ ---
222
334
 
223
- ![Queen MQ Analytics](./assets/analytics.png)
335
+ ## Examples
224
336
 
225
- ![Queen MQ System Metrics](./assets/systemmetrics.png)
337
+ ### Complete Pipeline with Consumer Groups
226
338
 
227
- **Features:**
228
- - 📊 Real-time dashboard with system metrics
229
- - 📈 Message throughput visualization
230
- - 🔍 Queue management and monitoring
231
- - 👥 Consumer group tracking
232
- - 💬 Message browser with trace timeline
233
- - 🔎 **Trace explorer for debugging distributed workflows**
234
- - 📉 Analytics and insights
235
- - 🌓 Dark/light theme support
339
+ ```javascript
340
+ import { Queen } from 'queen-mq'
236
341
 
237
- **Quick Start:**
238
- ```bash
239
- cd webapp
240
- npm install
241
- npm run dev
342
+ const queen = new Queen('http://localhost:6632')
343
+
344
+ // Stage 1: Ingest with buffering
345
+ async function ingestEvents() {
346
+ for (let i = 0; i < 10000; i++) {
347
+ await queen.queue('raw-events')
348
+ .partition(`user-${i % 100}`)
349
+ .buffer({ messageCount: 500, timeMillis: 1000 })
350
+ .push([{ data: { userId: i % 100, event: 'page_view' } }])
351
+ }
352
+ await queen.flushAllBuffers()
353
+ }
354
+
355
+ // Stage 2: Process with transactions
356
+ async function processEvents() {
357
+ await queen.queue('raw-events')
358
+ .group('processors')
359
+ .concurrency(5)
360
+ .batch(10)
361
+ .autoAck(false)
362
+ .consume(async (messages) => {
363
+ const results = messages.map(m => process(m.data))
364
+
365
+ // Atomic: ack all inputs, push all outputs
366
+ const txn = queen.transaction()
367
+ for (const msg of messages) txn.ack(msg)
368
+ txn.queue('processed-events').push(results.map(r => ({ data: r })))
369
+ await txn.commit()
370
+ })
371
+ }
372
+
373
+ // Stage 3: Separate analytics consumer (fan-out)
374
+ async function analytics() {
375
+ await queen.queue('raw-events')
376
+ .group('analytics')
377
+ .subscriptionMode('new') // Skip backlog
378
+ .consume(async (message) => {
379
+ await logMetrics(message.data)
380
+ })
381
+ }
382
+
383
+ await ingestEvents()
384
+ await Promise.all([processEvents(), analytics()])
242
385
  ```
243
386
 
244
- The dashboard will be available at `http://localhost:4000` or at `http://localhost:6632` directly from the server.
387
+ ### Long-Running Tasks with Lease Renewal
245
388
 
246
- See [webapp/README.md](webapp/README.md) for more details.
389
+ ```javascript
390
+ await queen.queue('video-processing')
391
+ .renewLease(true, 60000) // Renew every 60 seconds
392
+ .consume(async (message) => {
393
+ // Can take hours - lease keeps renewing automatically
394
+ await processVideo(message.data)
395
+ })
396
+ ```
247
397
 
248
- ## Architecture
398
+ ### Error Handling with Callbacks
249
399
 
250
- Queen uses a high-performance **acceptor/worker pattern** with uWebSockets, featuring a **fully asynchronous, non-blocking PostgreSQL architecture** for maximum throughput and minimal latency.
400
+ ```javascript
401
+ await queen.queue('tasks')
402
+ .autoAck(false)
403
+ .consume(async (message) => {
404
+ return await riskyOperation(message.data)
405
+ })
406
+ .onSuccess(async (message, result) => {
407
+ console.log('Success:', result)
408
+ await queen.ack(message, true)
409
+ })
410
+ .onError(async (message, error) => {
411
+ console.error('Failed:', error.message)
412
+
413
+ // Custom retry logic
414
+ if (error.message.includes('temporary')) {
415
+ await queen.ack(message, false) // Retry
416
+ } else {
417
+ await queen.ack(message, 'failed', { error: error.message })
418
+ }
419
+ })
420
+ ```
251
421
 
252
- ### Core Components
422
+ ---
253
423
 
254
- **Network Layer:**
255
- - **UWS Acceptor**: Single thread listening on port 6632, distributes connections round-robin to workers
256
- - **UWS Workers**: Configurable event loop threads (default: 10) handling HTTP routes and WebSocket connections
424
+ ## API Reference
257
425
 
258
- **Database Layer:**
259
- - **AsyncDbPool**: Non-blocking PostgreSQL connection pool (142 connections) using libpq async API
260
- - Socket-based I/O with `select()` for non-blocking operations
261
- - RAII-based resource management with automatic connection cleanup
262
- - Connection health monitoring and automatic reset
263
- - Thread-safe with mutex/condition variable synchronization
264
- - **AsyncQueueManager**: Event-loop-based queue operations
265
- - Direct execution in worker threads for PUSH, POP, ACK, and TRANSACTION operations
266
- - Batch processing with dynamic sizing
267
- - Encryption support with status checks
268
- - Automatic failover to file buffer when database unavailable
426
+ ### Queue Operations
269
427
 
270
- **Background Services:**
271
- - **Poll Workers**: 4 dedicated threads for long-polling operations
272
- - Non-blocking I/O with exponential backoff (100ms→2000ms)
273
- - Intention registry for efficient request grouping
274
- - Rate-limited queries to prevent database overload
275
- - **Background Pool**: 8 connections for metrics, retention, eviction, and stream management
428
+ ```javascript
429
+ // Create
430
+ await queen.queue('my-queue').create()
431
+ await queen.queue('my-queue').config({ priority: 5 }).create()
276
432
 
277
- ### Request Flow
433
+ // Delete
434
+ await queen.queue('my-queue').delete()
278
435
 
279
- **Standard Operations (PUSH/POP/ACK/TRANSACTION):**
436
+ // Get info
437
+ const info = await queen.getQueueInfo('my-queue')
280
438
  ```
281
- Client Request
282
- ↓
283
- Acceptor (port 6632)
284
- ↓
285
- Worker (event loop) → AsyncQueueManager → AsyncDbPool → PostgreSQL
286
- ↓ (non-blocking) (socket I/O)
287
- Response sent immediately
439
+
440
+ ### Push
441
+
442
+ ```javascript
443
+ await queen.queue('q').push([{ data: { value: 1 } }])
444
+ await queen.queue('q').partition('p1').push([{ data: { value: 1 } }])
445
+ await queen.queue('q').buffer({ messageCount: 100, timeMillis: 1000 }).push([...])
288
446
  ```
289
447
 
290
- **Long-Polling Operations (wait=true):**
448
+ ### Pop
449
+
450
+ ```javascript
451
+ const msgs = await queen.queue('q').pop()
452
+ const msgs = await queen.queue('q').batch(10).pop()
453
+ const msgs = await queen.queue('q').batch(10).wait(true).pop()
291
454
  ```
292
- Client Request
293
- ↓
294
- Worker registers intention in Registry
295
- ↓
296
- Poll Worker (50ms interval)
297
- ↓
298
- Non-blocking query via AsyncDbPool
299
- ↓
300
- Messages distributed to waiting clients
455
+
456
+ ### Consume
457
+
458
+ ```javascript
459
+ await queen.queue('q').consume(async (msg) => { /* process */ })
460
+ await queen.queue('q').limit(10).consume(async (msg) => { /* process */ })
461
+ await queen.queue('q').concurrency(5).consume(async (msg) => { /* 5 workers */ })
462
+ await queen.queue('q').group('my-group').consume(async (msg) => { /* consumer group */ })
301
463
  ```
302
464
 
303
- ### Performance Characteristics
465
+ ### Acknowledgment
304
466
 
305
- **Latency:**
306
- - **POP (immediate)**: 10-50ms
307
- - **ACK**: 10-50ms
308
- - **TRANSACTION**: 50-200ms
309
- - **Long-polling**: Configurable (50ms-2000ms backoff)
467
+ ```javascript
468
+ await queen.ack(message, true) // Success
469
+ await queen.ack(message, false) // Retry
470
+ await queen.ack(message, false, { error: 'reason' })
471
+ await queen.ack([msg1, msg2], true) // Batch ack
472
+ ```
310
473
 
311
- **Throughput:**
312
- - **Peak**: 148,000+ msg/s
313
- - **Sustained**: 130,000+ msg/s
314
- - **Batch push**: 5,000-8,000 msg/s (with batches of 100)
474
+ ### Transactions
315
475
 
316
- **Resource Usage:**
317
- - **Database connections**: 150 total (142 async + 8 background)
318
- - **Threads**: 14 total (10 workers + 4 poll workers)
319
- - **Memory**: ~80MB for thread stacks + connection overhead
476
+ ```javascript
477
+ await queen.transaction()
478
+ .ack(message)
479
+ .queue('output')
480
+ .push([{ data: { result: 'processed' } }])
481
+ .commit()
482
+ ```
320
483
 
321
- ### Scalability
484
+ ### Lease Renewal
322
485
 
323
- The event-driven architecture enables:
324
- - ✅ Unlimited concurrent requests (limited only by connection pool)
325
- - ✅ Horizontal scaling (multiple server instances)
326
- - ✅ Efficient resource utilization (non-blocking I/O)
327
- - ✅ Low latency under high load
328
- - ✅ Automatic load distribution across workers
486
+ ```javascript
487
+ await queen.renew(message)
488
+ await queen.renew([msg1, msg2, msg3])
489
+ await queen.queue('q').renewLease(true, 60000).consume(async (msg) => { /* auto-renew */ })
490
+ ```
329
491
 
330
- **📚 Technical Documentation:**
331
- - [Server Architecture Guide](server/README.md) - Complete server setup and configuration
332
- - [Architecture Diagrams](assets/architecture.svg) - Visual architecture overview
492
+ ### Buffering
333
493
 
334
- ### PostgreSQL Failover
494
+ ```javascript
495
+ await queen.flushAllBuffers()
496
+ await queen.queue('q').flushBuffer()
497
+ const stats = queen.getBufferStats()
498
+ ```
335
499
 
336
- Queen automatically buffers messages to disk when PostgreSQL is unavailable - **zero message loss**:
500
+ ### Dead Letter Queue
337
501
 
338
- - Normal pushes go directly to PostgreSQL (FIFO preserved)
339
- - If PostgreSQL is down, messages buffered to file (macOS: `/tmp/queen`, Linux: `/var/lib/queen/buffers`)
340
- - Automatic replay when PostgreSQL recovers
341
- - Survives server crashes and restarts
342
- - Directory auto-created on first run
502
+ ```javascript
503
+ const dlq = await queen.queue('q').dlq().limit(10).get()
504
+ const dlq = await queen.queue('q').dlq('consumer-group').limit(10).get()
505
+ const dlq = await queen.queue('q').dlq().from('2025-01-01').to('2025-01-31').get()
506
+ ```
343
507
 
344
- **No configuration needed** - failover is automatic!
508
+ ### Shutdown
345
509
 
346
- **Custom directory:**
347
- ```bash
348
- FILE_BUFFER_DIR=/custom/path ./bin/queen-server
510
+ ```javascript
511
+ await queen.close() // Flush buffers and close connections
349
512
  ```
350
513
 
351
- ## Performance Benchmarks
514
+ ---
515
+
516
+ ## Configuration Defaults
352
517
 
353
- **Preliminary results from C++ client benchmark** (detailed benchmarks on dedicated hardware coming soon)
518
+ ### Client Defaults
354
519
 
355
- ### Test Environment
356
- - **Hardware:** Apple M4 Air (all components on same machine, 10 processors available)
357
- - **Server:** 1 server, 4 workers, 95 total DB connections/threads
358
- - **Database:** PostgreSQL in Docker
359
- - **Client:** C++ benchmark tool (`benchmark/bin/benchmark`)
520
+ ```javascript
521
+ {
522
+ timeoutMillis: 30000,
523
+ retryAttempts: 3,
524
+ retryDelayMillis: 1000,
525
+ loadBalancingStrategy: 'round-robin',
526
+ enableFailover: true
527
+ }
528
+ ```
360
529
 
361
- ### Results
530
+ ### Queue Defaults
362
531
 
363
- All tests run with: `--threads 10 --partitions 10 --mode single-queue`
532
+ ```javascript
533
+ {
534
+ leaseTime: 300, // 5 minutes
535
+ retryLimit: 3,
536
+ priority: 0,
537
+ delayedProcessing: 0,
538
+ windowBuffer: 0,
539
+ maxSize: 0, // Unlimited
540
+ retentionSeconds: 0, // Keep forever
541
+ encryptionEnabled: false
542
+ }
543
+ ```
364
544
 
365
- | Test | Mode | Messages | Batch Size | Throughput | Bandwidth |
366
- |------|------|----------|------------|------------|-----------|
367
- | **T1** | Producer | 10,000 | 1 | 785 msg/sec | 0.22 MB/sec |
368
- | **T1** | Consumer | 10,000 | 1 | 456 msg/sec | 0.23 MB/sec |
369
- | **T2** | Producer | 10,000 | 10 | 7,677 msg/sec | 2.20 MB/sec |
370
- | **T2** | Consumer | 10,000 | 10 | 4,989 msg/sec | 2.53 MB/sec |
371
- | **T3** | Producer | 10,000 | 100 | 39,065 msg/sec | 11.18 MB/sec |
372
- | **T3** | Consumer | 10,000 | 100 | 30,079 msg/sec | 15.26 MB/sec |
373
- | **T4** | Producer | 10,000 | 1,000 | 85,862 msg/sec | 24.57 MB/sec |
374
- | **T4** | Consumer | 10,000 | 1,000 | **488,650 msg/sec** | **247.87 MB/sec** |
375
- | **T5** | Producer | 100,000 | 1,000 | **90,601 msg/sec** | **25.92 MB/sec** |
376
- | **T5** | Consumer | 100,000 | 1,000 | 84,530 msg/sec | 42.96 MB/sec |
545
+ ### Consume Defaults
377
546
 
378
- ### Key Observations
547
+ ```javascript
548
+ {
549
+ concurrency: 1,
550
+ batch: 1,
551
+ autoAck: true,
552
+ wait: true, // Long polling
553
+ timeoutMillis: 30000,
554
+ limit: null, // Run forever
555
+ renewLease: false
556
+ }
557
+ ```
379
558
 
380
- - ✅ **Batch size matters:** Larger batches (1,000) dramatically improve throughput
381
- - ✅ **Consumer performance:** Peaks at 488K msg/sec with batch size 1,000 (247 MB/sec bandwidth)
382
- - ✅ **Producer peak:** 90K msg/sec with batch size 1,000 on 100K messages
383
- - ⚠️ **Small batches:** Performance drops significantly with batch=1 (lock contention)
384
- - 📈 **Scalability:** Performance improves with larger message volumes (T4 vs T5)
559
+ ---
385
560
 
386
- **Note:** All timing metrics are based on processing time (excludes idle time) and measure actual message processing (first message → last message).
561
+ ## Logging
387
562
 
388
- ### Run Your Own Benchmarks
563
+ Enable detailed logging for debugging:
389
564
 
390
565
  ```bash
391
- cd benchmark
392
- make
393
-
394
- # Producer
395
- ./bin/benchmark producer --threads 10 --count 1000000 --batch 1000 --partitions 100 --mode single-queue
566
+ export QUEEN_CLIENT_LOG=true
567
+ node your-app.js
568
+ ```
396
569
 
397
- # Consumer
398
- ./bin/benchmark consumer --threads 10 --batch 1000 --partitions 100 --mode single-queue
570
+ Example output:
571
+ ```
572
+ [2025-10-28T10:30:45.123Z] [INFO] [Queen.constructor] {"status":"initialized","urls":1}
573
+ [2025-10-28T10:30:45.234Z] [INFO] [QueueBuilder.push] {"queue":"tasks","partition":"Default","count":5}
399
574
  ```
400
575
 
401
- See [benchmark/README.md](benchmark/README.md) for detailed usage.
576
+ ---
402
577
 
403
- ## Install the server and configure it
578
+ ## Best Practices
404
579
 
405
- ### Quick Start
580
+ 1. ✅ **Use `consume()` for workers** - Simpler API, handles retries automatically
581
+ 2. ✅ **Use `pop()` for control** - When you need precise control over acking
582
+ 3. ✅ **Buffer for speed** - Always use buffering when pushing many messages
583
+ 4. ✅ **Partitions for order** - Use partitions when message order matters
584
+ 5. ✅ **Consumer groups for scale** - Run multiple workers in the same group
585
+ 6. ✅ **Transactions for consistency** - Use transactions for atomic operations
586
+ 7. ✅ **Enable DLQ** - Always enable DLQ in production
587
+ 8. ✅ **Renew long leases** - Use auto-renewal for long-running tasks
588
+ 9. ✅ **Graceful shutdown** - Always call `queen.close()` before exiting
589
+ 10. ✅ **Monitor DLQ** - Regularly check for failed messages
406
590
 
407
- ```sh
408
- cd server
409
- make clean
410
- make deps
411
- make build-only
412
- DB_POOL_SIZE=50 ./bin/queen-server
413
- ```
591
+ ---
414
592
 
415
- **📖 Complete Build & Tuning Guide:** [server/README.md](server/README.md)
593
+ ## TypeScript Support
416
594
 
417
- Includes:
418
- - Build instructions and optimization
419
- - Performance tuning (worker threads, database pool)
420
- - Production deployment (systemd, Docker, load balancing)
421
- - Troubleshooting common issues
422
- - Benchmarking guides
595
+ Full TypeScript definitions included:
423
596
 
424
- ### Environment Variables
597
+ ```typescript
598
+ import { Queen, Message, QueueConfig } from 'queen-mq'
425
599
 
426
- [The full list of environment variables is here](server/ENV_VARIABLES.md)
600
+ const queen: Queen = new Queen('http://localhost:6632')
427
601
 
428
- ### With Docker
429
- ```sh
430
- ./build.sh
602
+ interface OrderData {
603
+ orderId: number
604
+ amount: number
605
+ }
606
+
607
+ const messages: Message<OrderData>[] = await queen.queue('orders').pop()
431
608
  ```
432
609
 
433
- ### Running on k8s
610
+ ---
434
611
 
435
- [Running in k8s](server/k8s-example.yaml)
612
+ ## Documentation
436
613
 
437
- ## 🔌 Raw HTTP API
614
+ - **[Complete V2 Guide](client-v2/README.md)** - Full tutorial with all features (94 test examples)
615
+ - **[HTTP API Reference](https://github.com/smartpricing/queen/blob/master/server/API.md)** - Raw HTTP endpoints
616
+ - **[Server Guide](https://github.com/smartpricing/queen/blob/master/server/README.md)** - Server setup and configuration
617
+ - **[Architecture Guide](https://github.com/smartpricing/queen/blob/master/documentation/ARCHITECTURE.md)** - Deep dive into internals
438
618
 
439
- You can use Queen directly from HTTP without the JS client.
619
+ ---
440
620
 
441
- [Here the complete list of API endpoints](server/API.md)
621
+ ## Support
442
622
 
443
- ## ⚠️ Known Issues & Roadmap
623
+ - **GitHub:** [smartpricing/queen](https://github.com/smartpricing/queen)
624
+ - **Issues:** [GitHub Issues](https://github.com/smartpricing/queen/issues)
625
+ - **LinkedIn:** [Smartness](https://www.linkedin.com/company/smartness-com/)
444
626
 
445
- ### Server Startup Timing (Critical)
446
- **Issue:** Worker initialization timeout (30s → 3600s) now matches file buffer recovery timeout. This is a temporary fix.
627
+ ---
447
628
 
448
- **Better Solution Needed:**
449
- - Make recovery non-blocking while preserving FIFO ordering guarantees
450
- - Implement progressive readiness with memory-buffered queue during recovery
451
- - Add configurable recovery timeout with graceful degradation
452
- - See: `server/src/services/file_buffer.cpp:212` (MAX_STARTUP_RECOVERY_SECONDS)
453
- - See: `server/src/acceptor_server.cpp:1876` (worker initialization timeout)
629
+ ## License
454
630
 
631
+ Apache 2.0 - See [LICENSE.md](../LICENSE.md)
455
632
 
456
- ### Other TODO Items
457
- - Proper concurrency on clients
458
- - Check client failover
459
- - Py client