queen-mq 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/API.md +1116 -0
  2. package/CACHE.md +519 -0
  3. package/DASHBOARD-V3.md +478 -0
  4. package/DASHBOARD.md +382 -0
  5. package/MOD_QUEUE.md +453 -0
  6. package/PARTITION_LOCKING_DESIGN.md +989 -0
  7. package/PLAN.md +707 -0
  8. package/QUERY_ANALSYS.md +72 -0
  9. package/QUEUE_BUS.md +334 -0
  10. package/README.md +1495 -0
  11. package/V2-PLAN.md +236 -0
  12. package/assets/dashboard.png +0 -0
  13. package/dashboard/.vscode/extensions.json +3 -0
  14. package/dashboard/README.md +5 -0
  15. package/dashboard/index.html +14 -0
  16. package/dashboard/package-lock.json +1458 -0
  17. package/dashboard/package.json +25 -0
  18. package/dashboard/public/vite.svg +1 -0
  19. package/dashboard/src/App.vue +29 -0
  20. package/dashboard/src/assets/styles/main.css +908 -0
  21. package/dashboard/src/assets/vue.svg +1 -0
  22. package/dashboard/src/components/cards/MetricCard.vue +298 -0
  23. package/dashboard/src/components/charts/QueueDepthChart.vue +276 -0
  24. package/dashboard/src/components/charts/QueueLagChart.vue +436 -0
  25. package/dashboard/src/components/charts/ThroughputChart.vue +302 -0
  26. package/dashboard/src/components/common/ActivityFeed.vue +251 -0
  27. package/dashboard/src/components/layout/AppHeader.vue +208 -0
  28. package/dashboard/src/components/layout/AppLayout.vue +88 -0
  29. package/dashboard/src/components/layout/AppSidebar.vue +261 -0
  30. package/dashboard/src/main.js +44 -0
  31. package/dashboard/src/router.js +54 -0
  32. package/dashboard/src/services/api.js +187 -0
  33. package/dashboard/src/services/websocket.js +167 -0
  34. package/dashboard/src/utils/constants.js +56 -0
  35. package/dashboard/src/utils/helpers.js +118 -0
  36. package/dashboard/src/views/Analytics.vue +912 -0
  37. package/dashboard/src/views/Dashboard.vue +906 -0
  38. package/dashboard/src/views/Messages.vue +437 -0
  39. package/dashboard/src/views/QueueDetail.vue +501 -0
  40. package/dashboard/src/views/Queues.vue +333 -0
  41. package/dashboard/vite.config.js +30 -0
  42. package/debug-namespace.js +110 -0
  43. package/docs/long-polling.md +159 -0
  44. package/docs/multi-server-cache-solutions.md +185 -0
  45. package/docs/performance-tuning.md +222 -0
  46. package/examples/bus-mode.js +239 -0
  47. package/examples/continuous-consumer-optimized.js +215 -0
  48. package/examples/continuous-consumer.js +159 -0
  49. package/examples/continuous-producer.js +343 -0
  50. package/examples/mixed-mode.js +277 -0
  51. package/examples/multi-server-test.js +305 -0
  52. package/examples/single.js +64 -0
  53. package/examples/smartchat-dealyed.js +42 -0
  54. package/examples/smartchat.js +52 -0
  55. package/examples/test-cache-invalidation.js +119 -0
  56. package/examples/test-cache-multi-server.js +245 -0
  57. package/examples/test-minimal-client.js +112 -0
  58. package/examples/test-queue-creation-policy.js +137 -0
  59. package/init-db.js +20 -0
  60. package/package.json +36 -0
  61. package/src/client/client.js +291 -0
  62. package/src/client/index.js +6 -0
  63. package/src/client/queenClient.js +513 -0
  64. package/src/client/utils/http.js +172 -0
  65. package/src/client/utils/loadBalancer.js +152 -0
  66. package/src/client/utils/retry.js +35 -0
  67. package/src/config.js +215 -0
  68. package/src/database/connection.js +103 -0
  69. package/src/database/poolManager.js +192 -0
  70. package/src/database/schema-v2.sql +214 -0
  71. package/src/managers/eventManager.js +59 -0
  72. package/src/managers/queueManagerOptimized.js +1512 -0
  73. package/src/managers/resourceCache.js +96 -0
  74. package/src/managers/systemEventManager.js +127 -0
  75. package/src/routes/ack.js +26 -0
  76. package/src/routes/analytics.js +812 -0
  77. package/src/routes/configure.js +46 -0
  78. package/src/routes/messages.js +298 -0
  79. package/src/routes/pop.js +85 -0
  80. package/src/routes/push.js +28 -0
  81. package/src/routes/resources.js +296 -0
  82. package/src/server.js +1286 -0
  83. package/src/services/encryptionService.js +82 -0
  84. package/src/services/evictionService.js +131 -0
  85. package/src/services/retentionService.js +129 -0
  86. package/src/services/startupSync.js +35 -0
  87. package/src/test/test.js +4521 -0
  88. package/src/utils/logger.js +44 -0
  89. package/src/utils/uuid.js +5 -0
  90. package/src/websocket/wsServer.js +221 -0
@@ -0,0 +1,333 @@
1
+ <template>
2
+ <div class="queues-view">
3
+ <Card class="queues-card">
4
+ <template #content>
5
+ <DataTable
6
+ :value="queues"
7
+ :loading="loading"
8
+ :paginator="true"
9
+ :rows="10"
10
+ :rowsPerPageOptions="[10, 20, 50]"
11
+ responsiveLayout="scroll"
12
+ filterDisplay="row"
13
+ :globalFilterFields="['name', 'namespace', 'task']"
14
+ class="dark-table-v3"
15
+ >
16
+ <template #header>
17
+ <div class="table-header">
18
+ <h2>Queue Management</h2>
19
+ <div class="table-actions">
20
+ <span class="p-input-icon-left">
21
+ <i class="pi pi-search" />
22
+ <InputText v-model="filters.global" placeholder="Search queues..." class="search-input" />
23
+ </span>
24
+ <Button icon="pi pi-refresh" @click="fetchQueues" :loading="loading" class="p-button-text" />
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <Column field="name" header="Queue Name" :sortable="true">
30
+ <template #body="{ data }">
31
+ <div class="queue-name-cell">
32
+ <div class="queue-icon">Q</div>
33
+ <router-link :to="`/queues/${data.name}`" class="queue-link">
34
+ {{ data.name }}
35
+ </router-link>
36
+ </div>
37
+ </template>
38
+ </Column>
39
+
40
+ <Column field="namespace" header="Namespace" :sortable="true">
41
+ <template #body="{ data }">
42
+ <Tag v-if="data.namespace" :value="data.namespace" class="namespace-tag" />
43
+ <span v-else class="text-muted">-</span>
44
+ </template>
45
+ </Column>
46
+
47
+ <Column field="task" header="Task" :sortable="true">
48
+ <template #body="{ data }">
49
+ <Tag v-if="data.task" :value="data.task" class="task-tag" />
50
+ <span v-else class="text-muted">-</span>
51
+ </template>
52
+ </Column>
53
+
54
+ <Column header="Status">
55
+ <template #body="{ data }">
56
+ <div class="status-badges">
57
+ <span class="status-pending">{{ data.stats?.pending || 0 }} pending</span>
58
+ <span class="status-processing">{{ data.stats?.processing || 0 }} processing</span>
59
+ </div>
60
+ </template>
61
+ </Column>
62
+
63
+ <Column field="stats.completed" header="Completed" :sortable="true">
64
+ <template #body="{ data }">
65
+ <span class="status-completed">{{ data.stats?.completed || 0 }}</span>
66
+ </template>
67
+ </Column>
68
+
69
+ <Column field="stats.failed" header="Failed" :sortable="true">
70
+ <template #body="{ data }">
71
+ <span class="status-failed">{{ data.stats?.failed || 0 }}</span>
72
+ </template>
73
+ </Column>
74
+
75
+ <Column header="Actions" :exportable="false" style="min-width: 8rem">
76
+ <template #body="{ data }">
77
+ <Button
78
+ icon="pi pi-cog"
79
+ class="p-button-text p-button-sm action-btn"
80
+ @click="configureQueue(data)"
81
+ v-tooltip="'Configure'"
82
+ />
83
+ <Button
84
+ icon="pi pi-trash"
85
+ class="p-button-text p-button-sm p-button-danger action-btn"
86
+ @click="deleteQueue(data)"
87
+ v-tooltip="'Delete'"
88
+ />
89
+ </template>
90
+ </Column>
91
+ </DataTable>
92
+ </template>
93
+ </Card>
94
+ </div>
95
+ </template>
96
+
97
+ <script setup>
98
+ import { ref, onMounted } from 'vue'
99
+ import { useRouter } from 'vue-router'
100
+ import { useToast } from 'primevue/usetoast'
101
+ import { useConfirm } from 'primevue/useconfirm'
102
+ import DataTable from 'primevue/datatable'
103
+ import Column from 'primevue/column'
104
+ import InputText from 'primevue/inputtext'
105
+ import Button from 'primevue/button'
106
+ import Tag from 'primevue/tag'
107
+ import Card from 'primevue/card'
108
+ import api from '../services/api.js'
109
+
110
+ const router = useRouter()
111
+ const toast = useToast()
112
+ const confirm = useConfirm()
113
+
114
+ const loading = ref(false)
115
+ const queues = ref([])
116
+ const filters = ref({
117
+ global: ''
118
+ })
119
+
120
+ const fetchQueues = async () => {
121
+ try {
122
+ loading.value = true
123
+ const data = await api.getQueues()
124
+
125
+ if (data?.queues && Array.isArray(data.queues)) {
126
+ // Process the actual API format: {queues: [{name, namespace, task, messages: {total, pending, processing}}]}
127
+ queues.value = data.queues.map(queue => ({
128
+ id: queue.id,
129
+ name: queue.name,
130
+ namespace: queue.namespace,
131
+ task: queue.task,
132
+ createdAt: queue.createdAt,
133
+ partitions: queue.partitions,
134
+ stats: {
135
+ pending: queue.messages?.pending || 0,
136
+ processing: queue.messages?.processing || 0,
137
+ completed: (queue.messages?.total || 0) - (queue.messages?.pending || 0) - (queue.messages?.processing || 0),
138
+ failed: queue.messages?.failed || 0,
139
+ total: queue.messages?.total || 0
140
+ }
141
+ }))
142
+ } else {
143
+ // Fallback to empty array
144
+ queues.value = []
145
+ }
146
+ } catch (error) {
147
+ console.error('Failed to fetch queues:', error)
148
+ toast.add({
149
+ severity: 'error',
150
+ summary: 'Error',
151
+ detail: 'Failed to load queues',
152
+ life: 3000
153
+ })
154
+ queues.value = []
155
+ } finally {
156
+ loading.value = false
157
+ }
158
+ }
159
+
160
+ const configureQueue = (queue) => {
161
+ router.push(`/queues/${queue.name}/configure`)
162
+ }
163
+
164
+ const deleteQueue = (queue) => {
165
+ confirm.require({
166
+ message: `Are you sure you want to delete queue "${queue.name}"?`,
167
+ header: 'Delete Queue',
168
+ icon: 'pi pi-exclamation-triangle',
169
+ acceptClass: 'p-button-danger',
170
+ accept: async () => {
171
+ try {
172
+ await api.deleteQueue(queue.name)
173
+ toast.add({
174
+ severity: 'success',
175
+ summary: 'Success',
176
+ detail: `Queue "${queue.name}" deleted`,
177
+ life: 3000
178
+ })
179
+ fetchQueues()
180
+ } catch (error) {
181
+ toast.add({
182
+ severity: 'error',
183
+ summary: 'Error',
184
+ detail: 'Failed to delete queue',
185
+ life: 3000
186
+ })
187
+ }
188
+ }
189
+ })
190
+ }
191
+
192
+ onMounted(() => {
193
+ fetchQueues()
194
+ })
195
+ </script>
196
+
197
+ <style scoped>
198
+ .queues-view {
199
+ padding: 0;
200
+ }
201
+
202
+ .queues-card {
203
+ background: transparent !important;
204
+ border: 1px solid rgba(255, 255, 255, 0.1);
205
+ border-radius: 16px;
206
+ box-shadow: none !important;
207
+ }
208
+
209
+ :deep(.p-card-content) {
210
+ padding: 0;
211
+ }
212
+
213
+ .table-header {
214
+ display: flex;
215
+ justify-content: space-between;
216
+ align-items: center;
217
+ padding: 1.5rem;
218
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
219
+ }
220
+
221
+ .table-header h2 {
222
+ font-size: 1.25rem;
223
+ font-weight: 600;
224
+ color: var(--surface-700);
225
+ margin: 0;
226
+ }
227
+
228
+ .table-actions {
229
+ display: flex;
230
+ gap: 1rem;
231
+ align-items: center;
232
+ }
233
+
234
+ .search-input {
235
+ background: rgba(255, 255, 255, 0.05);
236
+ border: 1px solid rgba(255, 255, 255, 0.1);
237
+ color: var(--surface-600);
238
+ }
239
+
240
+ .queue-name-cell {
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 0.75rem;
244
+ }
245
+
246
+ .queue-icon {
247
+ width: 32px;
248
+ height: 32px;
249
+ border-radius: 8px;
250
+ background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ color: white;
255
+ font-weight: 600;
256
+ font-size: 0.875rem;
257
+ }
258
+
259
+ .queue-link {
260
+ color: var(--primary-500);
261
+ text-decoration: none;
262
+ font-weight: 500;
263
+ }
264
+
265
+ .queue-link:hover {
266
+ text-decoration: underline;
267
+ }
268
+
269
+ .namespace-tag,
270
+ .task-tag {
271
+ background: rgba(236, 72, 153, 0.15);
272
+ color: var(--primary-500);
273
+ border: 1px solid rgba(236, 72, 153, 0.3);
274
+ }
275
+
276
+ .status-badges {
277
+ display: flex;
278
+ gap: 0.5rem;
279
+ }
280
+
281
+ .text-muted {
282
+ color: var(--surface-400);
283
+ }
284
+
285
+ .action-btn {
286
+ color: var(--surface-500) !important;
287
+ }
288
+
289
+ .action-btn:hover {
290
+ background: rgba(236, 72, 153, 0.1) !important;
291
+ color: var(--primary-500) !important;
292
+ }
293
+
294
+ .action-btn.p-button-danger:hover {
295
+ background: rgba(239, 68, 68, 0.1) !important;
296
+ color: var(--danger-color) !important;
297
+ }
298
+
299
+ /* DataTable dark theme overrides */
300
+ :deep(.dark-table-v3) {
301
+ background: transparent !important;
302
+ border: none !important;
303
+ }
304
+
305
+ :deep(.dark-table-v3 .p-datatable-header) {
306
+ background: transparent !important;
307
+ border: none !important;
308
+ }
309
+
310
+ :deep(.dark-table-v3 .p-datatable-thead > tr > th) {
311
+ background: transparent !important;
312
+ color: var(--surface-400) !important;
313
+ border-color: rgba(255, 255, 255, 0.1) !important;
314
+ }
315
+
316
+ :deep(.dark-table-v3 .p-datatable-tbody > tr) {
317
+ background: transparent !important;
318
+ }
319
+
320
+ :deep(.dark-table-v3 .p-datatable-tbody > tr:hover) {
321
+ background: rgba(236, 72, 153, 0.05) !important;
322
+ }
323
+
324
+ :deep(.dark-table-v3 .p-datatable-tbody > tr > td) {
325
+ color: var(--surface-600) !important;
326
+ border-color: rgba(255, 255, 255, 0.05) !important;
327
+ }
328
+
329
+ :deep(.p-paginator) {
330
+ background: transparent !important;
331
+ border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
332
+ }
333
+ </style>
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ server: {
8
+ port: 4000,
9
+ proxy: {
10
+ '/api': {
11
+ target: 'http://localhost:6632',
12
+ changeOrigin: true
13
+ },
14
+ '/health': {
15
+ target: 'http://localhost:6632',
16
+ changeOrigin: true
17
+ },
18
+ '/metrics': {
19
+ target: 'http://localhost:6632',
20
+ changeOrigin: true
21
+ }
22
+ }
23
+ },
24
+ base: '/dashboard/',
25
+ build: {
26
+ outDir: 'dist',
27
+ assetsDir: 'assets',
28
+ sourcemap: false
29
+ }
30
+ })
@@ -0,0 +1,110 @@
1
+ import pg from 'pg';
2
+ import config from './src/config.js';
3
+
4
+ const pool = new pg.Pool({
5
+ host: config.DATABASE.HOST,
6
+ port: config.DATABASE.PORT,
7
+ database: config.DATABASE.NAME,
8
+ user: config.DATABASE.USER,
9
+ password: config.DATABASE.PASSWORD
10
+ });
11
+
12
+ async function debug() {
13
+ try {
14
+ // Check if test queues have namespace/task set
15
+ console.log('=== Checking queues with namespace ===');
16
+ const queues = await pool.query(`
17
+ SELECT name, namespace, task
18
+ FROM queen.queues
19
+ WHERE namespace IS NOT NULL OR task IS NOT NULL
20
+ `);
21
+ console.log('Queues with namespace/task:', queues.rows);
22
+
23
+ // Check messages in those queues
24
+ console.log('\n=== Checking messages in namespaced queues ===');
25
+ const messages = await pool.query(`
26
+ SELECT m.id, m.transaction_id, q.name as queue_name, q.namespace, q.task, p.name as partition_name
27
+ FROM queen.messages m
28
+ JOIN queen.partitions p ON m.partition_id = p.id
29
+ JOIN queen.queues q ON p.queue_id = q.id
30
+ WHERE q.namespace IS NOT NULL
31
+ LIMIT 10
32
+ `);
33
+ console.log('Messages in namespaced queues:', messages.rows);
34
+
35
+ // Test the actual query used in popMessagesWithFilters
36
+ console.log('\n=== Testing popMessagesWithFilters query ===');
37
+ const actualConsumerGroup = '__QUEUE_MODE__';
38
+ const namespace = 'test-namespace';
39
+ const batch = 3;
40
+
41
+ const testQuery = `
42
+ WITH available_messages AS (
43
+ SELECT m.id, m.transaction_id, m.trace_id, m.payload, m.is_encrypted, m.created_at,
44
+ p.id as partition_id, p.name as partition_name, q.name as queue_name,
45
+ q.priority, q.lease_time, q.retry_limit, q.delayed_processing,
46
+ q.window_buffer, q.max_wait_time_seconds, q.namespace, q.task
47
+ FROM queen.messages m
48
+ JOIN queen.partitions p ON m.partition_id = p.id
49
+ JOIN queen.queues q ON p.queue_id = q.id
50
+ LEFT JOIN queen.messages_status ms ON m.id = ms.message_id AND ms.consumer_group = $1
51
+ WHERE (ms.id IS NULL OR ms.status = 'pending')
52
+ AND m.created_at <= NOW() - INTERVAL '1 second' * q.delayed_processing
53
+ AND (q.max_wait_time_seconds = 0 OR
54
+ m.created_at > NOW() - INTERVAL '1 second' * q.max_wait_time_seconds)
55
+ AND (q.window_buffer = 0 OR NOT EXISTS (
56
+ SELECT 1 FROM queen.messages m2
57
+ WHERE m2.partition_id = p.id
58
+ AND m2.created_at > NOW() - INTERVAL '1 second' * q.window_buffer
59
+ ))
60
+ AND NOT EXISTS (
61
+ -- Check for active partition leases
62
+ SELECT 1 FROM queen.partition_leases pl
63
+ WHERE pl.partition_id = p.id
64
+ AND pl.consumer_group = $1
65
+ AND pl.released_at IS NULL
66
+ AND pl.lease_expires_at > NOW()
67
+ )
68
+ AND q.namespace = $2
69
+ ORDER BY q.priority DESC, m.created_at ASC LIMIT $3 FOR UPDATE OF m SKIP LOCKED
70
+ )
71
+ SELECT * FROM available_messages
72
+ `;
73
+
74
+ const result = await pool.query(testQuery, [actualConsumerGroup, namespace, batch]);
75
+ console.log('Query result count:', result.rows.length);
76
+ if (result.rows.length > 0) {
77
+ console.log('Sample message:', result.rows[0]);
78
+ }
79
+
80
+ // Check for any blocking issues
81
+ console.log('\n=== Checking for blocking issues ===');
82
+ const statusCheck = await pool.query(`
83
+ SELECT ms.*, m.transaction_id
84
+ FROM queen.messages_status ms
85
+ JOIN queen.messages m ON ms.message_id = m.id
86
+ WHERE ms.consumer_group = $1
87
+ AND ms.status != 'completed'
88
+ LIMIT 10
89
+ `, [actualConsumerGroup]);
90
+ console.log('Pending/processing messages:', statusCheck.rows);
91
+
92
+ const leaseCheck = await pool.query(`
93
+ SELECT pl.*, p.name as partition_name, q.name as queue_name
94
+ FROM queen.partition_leases pl
95
+ JOIN queen.partitions p ON pl.partition_id = p.id
96
+ JOIN queen.queues q ON p.queue_id = q.id
97
+ WHERE pl.consumer_group = $1
98
+ AND pl.released_at IS NULL
99
+ AND pl.lease_expires_at > NOW()
100
+ `, [actualConsumerGroup]);
101
+ console.log('Active partition leases:', leaseCheck.rows);
102
+
103
+ } catch (error) {
104
+ console.error('Error:', error);
105
+ } finally {
106
+ await pool.end();
107
+ }
108
+ }
109
+
110
+ debug();
@@ -0,0 +1,159 @@
1
+ # Long Polling in Queen Client
2
+
3
+ ## Overview
4
+
5
+ The Queen client implements efficient long polling for real-time message consumption. When configured with `wait: true`, the client will maintain a persistent connection to the server, waiting for messages to arrive.
6
+
7
+ ## Continuous Polling Behavior
8
+
9
+ The client has been optimized for continuous long polling without unnecessary delays:
10
+
11
+ ### Timeout Handling
12
+
13
+ When a long polling request times out (no messages received within the timeout period), the client **immediately** starts a new request without any delay. This ensures:
14
+
15
+ - **Zero message loss**: No gap between polling requests where messages could be missed
16
+ - **Minimal latency**: Messages are received as soon as they're available
17
+ - **Efficient resource usage**: No unnecessary waiting or reconnection overhead
18
+
19
+ ### Implementation Details
20
+
21
+ ```javascript
22
+ // The consume function handles timeouts gracefully
23
+ const stop = client.consume({
24
+ ns: 'myapp',
25
+ task: 'processing',
26
+ queue: 'orders',
27
+ handler: async (message) => {
28
+ // Process message
29
+ },
30
+ options: {
31
+ wait: true, // Enable long polling
32
+ timeout: 30000, // 30 second timeout
33
+ batch: 10, // Get up to 10 messages at once
34
+ stopOnError: false // Continue on errors
35
+ }
36
+ });
37
+ ```
38
+
39
+ ### How It Works
40
+
41
+ 1. **Normal Operation**: When messages are available, they are immediately returned and processed
42
+ 2. **Timeout Scenario**: If no messages arrive within the timeout period:
43
+ - Server returns an empty response (not an error)
44
+ - Client immediately starts a new long polling request
45
+ - No delay or backoff is applied
46
+ 3. **Error Handling**: Only actual errors (network issues, server errors) trigger retry delays
47
+
48
+ ### Client-Server Timeout Coordination
49
+
50
+ The client automatically adds a buffer to its timeout when long polling:
51
+ - Server timeout: 30 seconds (configured value)
52
+ - Client timeout: 35 seconds (server timeout + 5 second buffer)
53
+
54
+ This prevents premature client-side timeouts and ensures the server has time to respond.
55
+
56
+ ## Example Usage
57
+
58
+ ### Basic Consumer
59
+
60
+ ```javascript
61
+ import { createQueenClient } from '@queen/client';
62
+
63
+ const client = createQueenClient({
64
+ baseUrl: 'http://localhost:6632'
65
+ });
66
+
67
+ // Start continuous consumer
68
+ const stop = client.consume({
69
+ ns: 'production',
70
+ task: 'orders',
71
+ queue: 'processing',
72
+ handler: async (message) => {
73
+ console.log('Received:', message);
74
+ // Process the message
75
+ },
76
+ options: {
77
+ wait: true, // Enable long polling
78
+ timeout: 30000, // 30 second timeout
79
+ batch: 5 // Process up to 5 messages at once
80
+ }
81
+ });
82
+
83
+ // Stop when needed
84
+ // stop();
85
+ ```
86
+
87
+ ### Advanced Consumer with Statistics
88
+
89
+ ```javascript
90
+ const stats = { processed: 0, errors: 0 };
91
+
92
+ const stop = client.consume({
93
+ ns: 'production',
94
+ task: 'orders',
95
+ queue: 'processing',
96
+ handler: async (message) => {
97
+ try {
98
+ await processOrder(message.data);
99
+ stats.processed++;
100
+ } catch (error) {
101
+ stats.errors++;
102
+ throw error; // Re-throw to trigger failed acknowledgment
103
+ }
104
+ },
105
+ options: {
106
+ wait: true,
107
+ timeout: 30000,
108
+ batch: 10,
109
+ stopOnError: false // Continue even if individual messages fail
110
+ }
111
+ });
112
+
113
+ // Monitor statistics
114
+ setInterval(() => {
115
+ console.log(`Processed: ${stats.processed}, Errors: ${stats.errors}`);
116
+ }, 60000);
117
+ ```
118
+
119
+ ## Benefits
120
+
121
+ 1. **Real-time Processing**: Messages are processed immediately upon arrival
122
+ 2. **Efficient**: No wasted time between polling cycles
123
+ 3. **Reliable**: Automatic reconnection on network issues
124
+ 4. **Scalable**: Supports multiple concurrent consumers
125
+ 5. **Simple**: No complex configuration required
126
+
127
+ ## Configuration Options
128
+
129
+ | Option | Default | Description |
130
+ |--------|---------|-------------|
131
+ | `wait` | `false` | Enable long polling |
132
+ | `timeout` | `30000` | Polling timeout in milliseconds |
133
+ | `batch` | `1` | Number of messages to retrieve per request |
134
+ | `stopOnError` | `false` | Stop consumer on handler errors |
135
+
136
+ ## Best Practices
137
+
138
+ 1. **Set Appropriate Timeouts**: Use 30-60 second timeouts for long polling
139
+ 2. **Handle Errors Gracefully**: Implement proper error handling in your message handler
140
+ 3. **Use Batching**: Process multiple messages per request for better throughput
141
+ 4. **Monitor Performance**: Track message processing rates and errors
142
+ 5. **Graceful Shutdown**: Always call the stop function when shutting down
143
+
144
+ ## Comparison with Traditional Polling
145
+
146
+ ### Traditional Polling (with delays)
147
+ ```
148
+ Request -> Response -> Wait 5s -> Request -> Response -> Wait 5s -> ...
149
+ ```
150
+ - Messages may wait up to 5 seconds before being processed
151
+ - Unnecessary requests when no messages are available
152
+
153
+ ### Long Polling (Queen implementation)
154
+ ```
155
+ Request -> Wait for message/timeout -> Response -> Immediately Request -> ...
156
+ ```
157
+ - Messages processed immediately upon arrival
158
+ - No delays between requests
159
+ - Efficient use of resources