queen-mq 0.4.0 → 0.6.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 (47) hide show
  1. package/README.md +520 -273
  2. package/{client-js/client-v2 → client-v2}/Queen.js +38 -0
  3. package/{client-js/client-v2 → client-v2}/README.md +61 -8
  4. package/package.json +5 -8
  5. package/{client-js/test-v2 → test-v2}/GETTING_STARTED.md +27 -0
  6. package/test-v2/README_SUBSCRIPTION_TESTS.md +201 -0
  7. package/{client-js/test-v2 → test-v2}/consume.js +10 -0
  8. package/{client-js/test-v2 → test-v2}/dlq.js +1 -1
  9. package/{client-js/test-v2 → test-v2}/load.js +2 -0
  10. package/{client-js/test-v2 → test-v2}/subscription.js +198 -18
  11. package/{client-js/test-v2 → test-v2}/transaction.js +66 -4
  12. package/LICENSE.md +0 -202
  13. package/client-js/benchmark/consumer.js +0 -209
  14. package/client-js/benchmark/consumer_multi.js +0 -216
  15. package/client-js/benchmark/producer.js +0 -80
  16. package/client-js/benchmark/producer_multi.js +0 -115
  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/QueueBuilder.js +0 -0
  21. /package/{client-js/client-v2 → client-v2}/builders/TransactionBuilder.js +0 -0
  22. /package/{client-js/client-v2 → client-v2}/consumer/ConsumerManager.js +0 -0
  23. /package/{client-js/client-v2 → client-v2}/http/HttpClient.js +0 -0
  24. /package/{client-js/client-v2 → client-v2}/http/LoadBalancer.js +0 -0
  25. /package/{client-js/client-v2 → client-v2}/index.js +0 -0
  26. /package/{client-js/client-v2 → client-v2}/stream/StreamBuilder.js +0 -0
  27. /package/{client-js/client-v2 → client-v2}/stream/StreamConsumer.js +0 -0
  28. /package/{client-js/client-v2 → client-v2}/stream/Window.js +0 -0
  29. /package/{client-js/client-v2 → client-v2}/utils/defaults.js +0 -0
  30. /package/{client-js/client-v2 → client-v2}/utils/logger.js +0 -0
  31. /package/{client-js/client-v2 → client-v2}/utils/validation.js +0 -0
  32. /package/{client-js/test-v2 → test-v2}/AI_TEST_SUMMARY.md +0 -0
  33. /package/{client-js/test-v2 → test-v2}/MAINTENANCE_TEST.md +0 -0
  34. /package/{client-js/test-v2 → test-v2}/ai_buffering.js +0 -0
  35. /package/{client-js/test-v2 → test-v2}/ai_error_handling.js +0 -0
  36. /package/{client-js/test-v2 → test-v2}/ai_lease_renewal.js +0 -0
  37. /package/{client-js/test-v2 → test-v2}/ai_mixed_scenarios.js +0 -0
  38. /package/{client-js/test-v2 → test-v2}/ai_priority.js +0 -0
  39. /package/{client-js/test-v2 → test-v2}/ai_resources.js +0 -0
  40. /package/{client-js/test-v2 → test-v2}/ai_ttl_retention.js +0 -0
  41. /package/{client-js/test-v2 → test-v2}/complete.js +0 -0
  42. /package/{client-js/test-v2 → test-v2}/maintenance.js +0 -0
  43. /package/{client-js/test-v2 → test-v2}/pop.js +0 -0
  44. /package/{client-js/test-v2 → test-v2}/push.js +0 -0
  45. /package/{client-js/test-v2 → test-v2}/queue.js +0 -0
  46. /package/{client-js/test-v2 → test-v2}/retention.js +0 -0
  47. /package/{client-js/test-v2 → test-v2}/run.js +0 -0
package/README.md CHANGED
@@ -1,385 +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
- ## Introduction
26
-
27
- 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.
28
-
29
- Here are the main features:
30
- - Unlimited FIFO partitions within queues
31
- - Queue semantics like RabbitMQ
32
- - Consumer groups over queues like Kafka
33
- - Allows for all the patterns you need, from simple queues to complex workflows and request/response patterns
34
- - QoS levels: Exactly-once delivery (with transactionId), at-least-once delivery, and at-most-once delivery
35
- - Subscription modes for replay (new messages only or from a specific timestamp) and message history control
36
- - Transactions between operations (push and ack mainly) for atomicity
37
- - Dead letter queue for failure handling
38
- - Lease renewal for long-running tasks
39
- - Message tracing for debugging workflows
40
- - Encryption of messages at DB level
41
- - Automatic message retention and cleanup - Configurable per-queue retention policies
42
- - Streaming capabilities for real-time aggregation and processing of messages
43
- - A nice webapp for monitoring and managing the system
44
- - 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
45
-
46
- 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.
47
-
48
- With proper batching, the system can handle +200k **messages** per second (not req/s) on modest hardware.
49
-
50
- Main documentation:
51
- - [Client Guide JS](client-js/client-v2/README.md)
52
- - [Client Guide C++](client-cpp/README.md)
53
- - [Server Guide](server/README.md)
54
- - [Streaming Guide](docs/STREAMING_USAGE.md)
55
- - [API Reference](server/API.md)
56
- - [Message Retention & Cleanup](docs/RETENTION.md)
57
- - [Webapp](webapp/README.md)
58
- - [Expose the Webapp behind a proxy](proxy/README.md)
59
-
60
- ## Concepts
61
-
62
- 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.
36
+ ## Installation
63
37
 
64
- ### Queues
38
+ ```bash
39
+ npm install queen-mq
40
+ ```
65
41
 
66
- 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.
42
+ **Requirements:** Node.js 22+
67
43
 
68
- ### Partitions
44
+ ---
69
45
 
70
- 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.
46
+ ## Quick Start
71
47
 
72
- ### Default partition and consumer group
48
+ ```javascript
49
+ import { Queen } from 'queen-mq'
73
50
 
74
- 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.
51
+ // Connect to Queen server
52
+ const queen = new Queen('http://localhost:6632')
75
53
 
76
- ### Consumer groups
54
+ // Create a queue
55
+ await queen.queue('tasks').create()
77
56
 
78
- 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.
57
+ // Push messages
58
+ await queen.queue('tasks').push([
59
+ { data: { task: 'send-email', to: 'alice@example.com' } }
60
+ ])
79
61
 
80
- ### Subscription modes
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
+ ```
81
68
 
82
- 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.
69
+ ---
83
70
 
84
- ### Long polling (waiting for messages)
71
+ ## Core Concepts
85
72
 
86
- 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.
73
+ ### Queues
87
74
 
88
- ### Lease renewal
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
89
81
 
90
- 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.
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
+ ```
91
92
 
92
- ### Ack and Nack
93
+ ### Partitions
93
94
 
94
- 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.
95
+ Ordered lanes within a queue. Messages in the same partition are processed sequentially:
95
96
 
96
- ### Transactions
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
+ ```
97
107
 
98
- 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.
108
+ **Use cases:**
109
+ - Per-user ordering
110
+ - Per-tenant isolation
111
+ - Sharding for parallelism
99
112
 
100
- ### Dead letter queue
113
+ ### Consumer Groups
101
114
 
102
- 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.
115
+ Multiple consumers sharing work, with independent progress tracking:
103
116
 
104
- ## Comparison with RabbitMQ, Kafka, and NATS
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
+ ```
105
132
 
106
- For users familiar with existing message queue systems, here's how Queen's semantics and usage patterns compare:
133
+ ### Subscription Modes
107
134
 
108
- ### Conceptual Mapping
135
+ Control whether consumer groups process historical messages:
109
136
 
110
- | Queen Concept | RabbitMQ Equivalent | Kafka Equivalent | NATS Equivalent |
111
- |---------------|---------------------|------------------|-----------------|
112
- | Queue | Queue | Topic | Stream (JetStream) |
113
- | Partition | N/A (queues are single-consumer by default) | Partition | N/A |
114
- | Consumer Group | Competing Consumers pattern | Consumer Group | Queue Group |
115
- | Queue Mode (no group) | Exclusive consumer | N/A (always uses groups) | Single subscriber |
116
- | Lease | Message TTL / Visibility timeout | N/A (commit-based) | Ack wait / nak delay |
117
- | Ack/Nack | Ack/Nack | Commit offset | Ack/Nak |
118
- | Transaction | Publisher confirms + consumer acks | Transactional producer/consumer | N/A |
119
- | Dead Letter Queue | Dead Letter Exchange | N/A (manual) | N/A (manual) |
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 */ })
154
+ ```
120
155
 
156
+ ---
121
157
 
122
- ## One single example
158
+ ## Connection Options
123
159
 
124
- > 📖 **[Complete Guide: client-js/client-v2/README.md](client-js/client-v2/README.md)** - Full tutorial with all features!
160
+ ### Single Server
125
161
 
126
162
  ```javascript
127
- import { Queen } from 'queen-mq'
128
-
129
- // Connect to Queen
130
163
  const queen = new Queen('http://localhost:6632')
164
+ ```
131
165
 
132
- // Create a queue
133
- await queen
134
- .queue('critical-task')
135
- .config({
136
- leaseTime: 10, // 10 seconds to process the messages (seconds)
137
- })
138
- .create()
166
+ ### Multiple Servers (High Availability)
139
167
 
140
- // Push messages
141
- await queen
142
- .queue('critical-task')
143
- .partition('tenant-123')
144
- .push([
145
- {
146
- transactionId: 'my-id', // This is autogenerated, but you can set your own. Unique per queue partition.
147
- data: { id: 123, description: 'Critical task' } // The message payload
148
- },
168
+ ```javascript
169
+ const queen = new Queen([
170
+ 'http://server1:6632',
171
+ 'http://server2:6632'
149
172
  ])
150
- .onSuccess(async (messages) => { // Not mandatory
151
- console.log('Messages pushed successfully:', messages)
152
- })
153
- .onDuplicate(async (messages) => { // Not mandatory, triggered when a message with the same transactionId is pushed
154
- console.warn('Duplicate transaction IDs detected')
155
- })
156
- .onError(async (messages, error) => { // Without callbacks, push throws an error if some messages are not pushed
157
- console.error('Error pushing messages:', error)
158
- })
173
+ ```
159
174
 
160
- // Consume messages
161
- await queen
162
- .queue('critical-task')
163
- // The consumer group name, without this, the messages are processed in queue mode
164
- .group('processor-consumer-group')
165
- // 10 parallel workers
166
- .concurrency(10)
167
- // I want to manually ack/nack messages
168
- .autoAck(false)
169
- // 10 messages per batch, prefetch them
170
- .batch(10)
171
- // Auto-renew the lease for the messages every 2 seconds
172
- .renewLease(true, 2000)
173
- // Process each message individually
174
- .each()
175
- // Do your work here
176
- .consume(async (message) => {
177
- console.log('Processing:', message.data)
178
- })
179
- // Ack the messages if you processed them successfully
180
- .onSuccess(async (message) => {
181
- await queen
182
- .transaction()
183
- .queue('critical-task-next')
184
- .partition('XXX')
185
- .push([{ data: { message: 'Final', count: 3 } }])
186
- .ack(message)
187
- .commit()
175
+ ### Full Configuration
176
+
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
188
184
  })
189
- // Nack the messages if you failed to process them
190
- .onError(async (message, error) => {
191
- console.error('Error processing messages:', error)
192
- await queen.ack(message, false)
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Basic Usage Patterns
190
+
191
+ ### Push Messages
192
+
193
+ ```javascript
194
+ // Simple push
195
+ await queen.queue('tasks').push([
196
+ { data: { job: 'resize-image', imageId: 123 } }
197
+ ])
198
+
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
+ ```
212
+
213
+ ### Consume Messages (Long-Running Workers)
214
+
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
+ ```
232
+
233
+ ### Pop Messages (On-Demand Processing)
234
+
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
+ ```
252
+
253
+ ### Transactions (Atomic Operations)
254
+
255
+ ```javascript
256
+ // Pop from queue A
257
+ const messages = await queen.queue('input').pop()
258
+
259
+ // Atomically: ack input AND push output
260
+ await queen.transaction()
261
+ .ack(messages[0])
262
+ .queue('output')
263
+ .push([{ data: processedResult }])
264
+ .commit()
265
+
266
+ // If commit fails, nothing happens - message stays in input queue
267
+ ```
268
+
269
+ ### Client-Side Buffering (High Throughput)
270
+
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
283
+ ```
284
+
285
+ ### Dead Letter Queue
286
+
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
+ ```
304
+
305
+ ### Message Tracing
306
+
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
+ })
193
328
  })
329
+
330
+ // View traces in webapp: Traces → Search "order-12345"
194
331
  ```
195
332
 
196
- ## Webapp
333
+ ---
197
334
 
198
- A modern Vue 3 web interface for managing and monitoring Queen MQ.
335
+ ## Examples
199
336
 
200
- ![Queen MQ Dashboard](./assets/dashboard.png)
337
+ ### Complete Pipeline with Consumer Groups
201
338
 
202
- ![Queen MQ Queues](./assets/queues.png)
339
+ ```javascript
340
+ import { Queen } from 'queen-mq'
203
341
 
204
- ![Queen MQ Messages](./assets/messages.png)
342
+ const queen = new Queen('http://localhost:6632')
205
343
 
206
- ![Queen MQ Traces](./assets/traces.png)
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()])
385
+ ```
207
386
 
208
- ![Queen MQ Analytics](./assets/analytics.png)
387
+ ### Long-Running Tasks with Lease Renewal
209
388
 
210
- ![Queen MQ System Metrics](./assets/systemmetrics.png)
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
+ ```
211
397
 
212
- **Features:**
213
- - 📊 Real-time dashboard with system metrics
214
- - 📈 Message throughput visualization
215
- - 🔍 Queue management and monitoring
216
- - 👥 Consumer group tracking
217
- - 💬 Message browser with trace timeline
218
- - 🔎 **Trace explorer for debugging distributed workflows**
219
- - 📉 Analytics and insights
220
- - 🌓 Dark/light theme support
398
+ ### Error Handling with Callbacks
221
399
 
222
- **Quick Start:**
223
- ```bash
224
- cd webapp
225
- npm install
226
- npm run dev
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
+ })
227
420
  ```
228
421
 
229
- The dashboard will be available at `http://localhost:4000` or at `http://localhost:6632` directly from the server.
422
+ ---
230
423
 
231
- See [webapp/README.md](webapp/README.md) for more details.
424
+ ## API Reference
232
425
 
233
- ## Architecture
426
+ ### Queue Operations
234
427
 
235
- Queen uses a high-performance **acceptor/worker pattern** with uWebSockets, combining non-blocking I/O for HTTP/WebSocket with a dedicated thread pool for database operations.
428
+ ```javascript
429
+ // Create
430
+ await queen.queue('my-queue').create()
431
+ await queen.queue('my-queue').config({ priority: 5 }).create()
236
432
 
237
- **View the interactive architecture diagram:** [architecture.svg](./assets/architecture.svg)
433
+ // Delete
434
+ await queen.queue('my-queue').delete()
238
435
 
239
- **Key Components:**
240
- - **UWS Acceptor**: Single thread listening on port 6632, round-robin distributes to workers
241
- - **UWS Workers**: N event loop threads (default: 10) handling HTTP routes and WebSocket
242
- - **Response Timers**: Per-worker timers (25ms tick) drain response queue back to clients
243
- - **DB ThreadPool**: Separate pool for blocking PostgreSQL operations
244
- - **Poll Workers**: 2 reserved threads for long-polling with adaptive backoff (100ms→2000ms)
245
- - **Poll Intention Registry**: Thread-safe store for long-poll requests
246
- - **Database Pool**: 150 shared PostgreSQL connections (libpq) with mutex/condition variable
247
- - **Response Queue**: Thread-safe queue decoupling DB results from event loop responses
436
+ // Get info
437
+ const info = await queen.getQueueInfo('my-queue')
438
+ ```
248
439
 
249
- **Request Flow:**
250
- 1. Client → Acceptor → Worker (event loop)
251
- 2. Worker registers response, submits job to DB ThreadPool
252
- 3. DB thread executes query, pushes result to Response Queue
253
- 4. Worker's response timer drains queue, sends HTTP response
440
+ ### Push
254
441
 
255
- **Long-Polling Flow:**
256
- 1. No immediate messages? Register intention in Registry
257
- 2. Poll Workers wake every 50ms, group intentions by queue/partition/consumer
258
- 3. Rate-limited DB queries (100ms initial, exponential backoff to 2s)
259
- 4. Messages distributed to waiting clients via Response Queue
260
- 5. Timeouts detected by Poll Workers, send 204 No Content
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([...])
446
+ ```
261
447
 
262
- This architecture provides high concurrency, efficient connection pooling, and minimal latency for both immediate and long-polling requests.
448
+ ### Pop
263
449
 
264
- ### PostgreSQL Failover
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()
454
+ ```
265
455
 
266
- Queen automatically buffers messages to disk when PostgreSQL is unavailable - **zero message loss**:
456
+ ### Consume
267
457
 
268
- - Normal pushes go directly to PostgreSQL (FIFO preserved)
269
- - If PostgreSQL is down, messages buffered to file (macOS: `/tmp/queen`, Linux: `/var/lib/queen/buffers`)
270
- - Automatic replay when PostgreSQL recovers
271
- - Survives server crashes and restarts
272
- - Directory auto-created on first run
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 */ })
463
+ ```
273
464
 
274
- **No configuration needed** - failover is automatic!
465
+ ### Acknowledgment
275
466
 
276
- **Custom directory:**
277
- ```bash
278
- FILE_BUFFER_DIR=/custom/path ./bin/queen-server
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
279
472
  ```
280
473
 
281
- ## Performance Benchmarks
474
+ ### Transactions
282
475
 
283
- **Preliminary results from C++ client benchmark** (detailed benchmarks on dedicated hardware coming soon)
476
+ ```javascript
477
+ await queen.transaction()
478
+ .ack(message)
479
+ .queue('output')
480
+ .push([{ data: { result: 'processed' } }])
481
+ .commit()
482
+ ```
284
483
 
285
- ### Test Environment
286
- - **Hardware:** Apple M4 Air (all components on same machine, 10 processors available)
287
- - **Server:** 1 server, 4 workers, 95 total DB connections/threads
288
- - **Database:** PostgreSQL in Docker
289
- - **Client:** C++ benchmark tool (`benchmark/bin/benchmark`)
484
+ ### Lease Renewal
290
485
 
291
- ### Results
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
+ ```
292
491
 
293
- | Mode | Threads | Messages | Batch Size | Partitions | Queue Mode | Throughput | Bandwidth |
294
- |------|---------|----------|------------|------------|------------|------------|-----------|
295
- | **Producer** | 100 | 10K | 1 | 100 | single-queue | **1,431 msg/sec** | 0.41 MB/sec |
296
- | **Producer** | 100 | 10K | 1 | 100 | multi-queue | 527 msg/sec | 0.15 MB/sec |
297
- | **Producer** | 10 | 1M | 1,000 | 100 | single-queue | **62,927 msg/sec** | 18.00 MB/sec |
298
- | **Producer** | 10 | 1M | 1,000 | 100 | multi-queue | 49,663 msg/sec | 14.21 MB/sec |
299
- | **Producer** | 10 | 1M | 10,000 | 10 | single-queue | 30,351 msg/sec | 8.68 MB/sec |
300
- | **Consumer** | 10 | 1M | 1,000 | 100 | single-queue | **31,922 msg/sec** | 16.25 MB/sec |
301
- | **Consumer** | 10 | 1M | 10,000 | 10 | single-queue | **82,974 msg/sec** | 42.25 MB/sec |
492
+ ### Buffering
302
493
 
303
- ### Key Observations
494
+ ```javascript
495
+ await queen.flushAllBuffers()
496
+ await queen.queue('q').flushBuffer()
497
+ const stats = queen.getBufferStats()
498
+ ```
304
499
 
305
- - ✅ **Batch size matters:** Larger batches (1,000-10,000) dramatically improve throughput
306
- - ✅ **Single-queue mode faster:** Better partition-level parallelism than multi-queue
307
- - ✅ **Consumer performance:** Scales well with batch size (82K msg/sec with 10K batches)
308
- - ✅ **Producer peak:** 62K msg/sec with optimal batch size (1,000)
309
- - ⚠️ **Small batches:** Performance drops significantly with batch=1 (lock contention)
500
+ ### Dead Letter Queue
310
501
 
311
- **Note:** All timing metrics exclude idle timeouts and measure actual message processing time (first message → last message).
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
+ ```
312
507
 
313
- ### Run Your Own Benchmarks
508
+ ### Shutdown
314
509
 
315
- ```bash
316
- cd benchmark
317
- make
510
+ ```javascript
511
+ await queen.close() // Flush buffers and close connections
512
+ ```
513
+
514
+ ---
515
+
516
+ ## Configuration Defaults
517
+
518
+ ### Client Defaults
519
+
520
+ ```javascript
521
+ {
522
+ timeoutMillis: 30000,
523
+ retryAttempts: 3,
524
+ retryDelayMillis: 1000,
525
+ loadBalancingStrategy: 'round-robin',
526
+ enableFailover: true
527
+ }
528
+ ```
529
+
530
+ ### Queue Defaults
531
+
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
+ ```
318
544
 
319
- # Producer
320
- ./bin/benchmark producer --threads 10 --count 1000000 --batch 1000 --partitions 100 --mode single-queue
545
+ ### Consume Defaults
321
546
 
322
- # Consumer
323
- ./bin/benchmark consumer --threads 10 --batch 1000 --partitions 100 --mode single-queue
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
+ }
324
557
  ```
325
558
 
326
- See [benchmark/README.md](benchmark/README.md) for detailed usage.
559
+ ---
327
560
 
328
- ## Install the server and configure it
561
+ ## Logging
329
562
 
330
- ### Quick Start
563
+ Enable detailed logging for debugging:
331
564
 
332
- ```sh
333
- cd server
334
- make clean
335
- make deps
336
- make build-only
337
- DB_POOL_SIZE=50 ./bin/queen-server
565
+ ```bash
566
+ export QUEEN_CLIENT_LOG=true
567
+ node your-app.js
338
568
  ```
339
569
 
340
- **📖 Complete Build & Tuning Guide:** [server/README.md](server/README.md)
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}
574
+ ```
575
+
576
+ ---
577
+
578
+ ## Best Practices
579
+
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
590
+
591
+ ---
592
+
593
+ ## TypeScript Support
341
594
 
342
- Includes:
343
- - Build instructions and optimization
344
- - Performance tuning (worker threads, database pool)
345
- - Production deployment (systemd, Docker, load balancing)
346
- - Troubleshooting common issues
347
- - Benchmarking guides
595
+ Full TypeScript definitions included:
348
596
 
349
- ### Environment Variables
597
+ ```typescript
598
+ import { Queen, Message, QueueConfig } from 'queen-mq'
350
599
 
351
- [The full list of environment variables is here](server/ENV_VARIABLES.md)
600
+ const queen: Queen = new Queen('http://localhost:6632')
352
601
 
353
- ### With Docker
354
- ```sh
355
- ./build.sh
602
+ interface OrderData {
603
+ orderId: number
604
+ amount: number
605
+ }
606
+
607
+ const messages: Message<OrderData>[] = await queen.queue('orders').pop()
356
608
  ```
357
609
 
358
- ### Running on k8s
610
+ ---
359
611
 
360
- [Running in k8s](server/k8s-example.yaml)
612
+ ## Documentation
361
613
 
362
- ## 🔌 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/docs/ARCHITECTURE.md)** - Deep dive into internals
363
618
 
364
- You can use Queen directly from HTTP without the JS client.
619
+ ---
365
620
 
366
- [Here the complete list of API endpoints](server/API.md)
621
+ ## Support
367
622
 
368
- ## ⚠️ 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/)
369
626
 
370
- ### Server Startup Timing (Critical)
371
- **Issue:** Worker initialization timeout (30s → 3600s) now matches file buffer recovery timeout. This is a temporary fix.
627
+ ---
372
628
 
373
- **Better Solution Needed:**
374
- - Make recovery non-blocking while preserving FIFO ordering guarantees
375
- - Implement progressive readiness with memory-buffered queue during recovery
376
- - Add configurable recovery timeout with graceful degradation
377
- - See: `server/src/services/file_buffer.cpp:212` (MAX_STARTUP_RECOVERY_SECONDS)
378
- - See: `server/src/acceptor_server.cpp:1876` (worker initialization timeout)
629
+ ## License
379
630
 
631
+ Apache 2.0 - See [LICENSE.md](../LICENSE.md)
380
632
 
381
- ### Other TODO Items
382
- - Mini streaming engine
383
- - Proper concurrency on clients
384
- - Check client failover
385
- - Py client