queen-mq 0.2.0 → 0.2.11

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 (84) hide show
  1. package/README.md +165 -769
  2. package/{src → client-js}/benchmark/consumer.js +5 -3
  3. package/{src → client-js}/benchmark/producer.js +9 -4
  4. package/{src → client-js}/client/client.js +272 -6
  5. package/{src → client-js}/client/index.js +1 -4
  6. package/{src → client-js}/client/utils/http.js +3 -2
  7. package/{src → client-js}/client/utils/retry.js +7 -1
  8. package/{src → client-js}/test/core-tests.js +7 -2
  9. package/{src → client-js}/test/edge-case-tests.js +2 -1
  10. package/client-js/test/human.js +162 -0
  11. package/client-js/test/qos0-tests.js +334 -0
  12. package/{src → client-js}/test/test-new.js +42 -0
  13. package/package.json +8 -17
  14. package/init-db.js +0 -20
  15. package/src/cluster-server.js +0 -242
  16. package/src/config.js +0 -229
  17. package/src/database/connection.js +0 -129
  18. package/src/database/poolManager.js +0 -199
  19. package/src/database/schema-v2.sql +0 -278
  20. package/src/managers/eventManager.js +0 -59
  21. package/src/managers/queueManagerOptimized.js +0 -1634
  22. package/src/managers/resourceCache.js +0 -96
  23. package/src/managers/systemEventManager.js +0 -132
  24. package/src/routes/ack.js +0 -26
  25. package/src/routes/configure.js +0 -46
  26. package/src/routes/messages.js +0 -368
  27. package/src/routes/pop.js +0 -69
  28. package/src/routes/push.js +0 -28
  29. package/src/routes/resources.js +0 -330
  30. package/src/routes/status.js +0 -1037
  31. package/src/server.js +0 -1633
  32. package/src/test/MIGRATION_ISSUES.md +0 -174
  33. package/src/test/test.js +0 -4524
  34. package/src/utils/streaming.js +0 -231
  35. package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
  36. package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
  37. package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
  38. package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
  39. package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
  40. package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
  41. package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
  42. package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
  43. package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
  44. package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
  45. package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
  46. package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
  47. package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
  48. package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
  49. package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
  50. package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
  51. package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
  52. package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
  53. package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
  54. package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
  55. package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
  56. package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
  57. package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
  58. package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
  59. package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
  60. package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
  61. package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
  62. package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
  63. package/src/webapp-dist/assets/queen-logo.svg +0 -263
  64. package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
  65. package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
  66. package/src/webapp-dist/index.html +0 -15
  67. package/src/websocket/wsServer.js +0 -228
  68. /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
  69. /package/{src → client-js}/benchmark/producer_multi.js +0 -0
  70. /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
  71. /package/{src → client-js}/services/encryptionService.js +0 -0
  72. /package/{src → client-js}/services/evictionService.js +0 -0
  73. /package/{src → client-js}/services/retentionService.js +0 -0
  74. /package/{src → client-js}/services/startupSync.js +0 -0
  75. /package/{src → client-js}/test/README.md +0 -0
  76. /package/{src → client-js}/test/advanced-client-tests.js +0 -0
  77. /package/{src → client-js}/test/advanced-pattern-tests.js +0 -0
  78. /package/{src → client-js}/test/bus-mode-tests.js +0 -0
  79. /package/{src → client-js}/test/enterprise-tests.js +0 -0
  80. /package/{src → client-js}/test/partition-locking-tests.js +0 -0
  81. /package/{src → client-js}/test/utils.js +0 -0
  82. /package/{src → client-js}/test/window-buffer-test.js +0 -0
  83. /package/{src → client-js}/utils/logger.js +0 -0
  84. /package/{src → client-js}/utils/uuid.js +0 -0
package/src/routes/pop.js DELETED
@@ -1,69 +0,0 @@
1
- import config from '../config.js';
2
-
3
- export const createPopRoute = (queueManager, eventManager) => {
4
- return async (scope, options = {}) => {
5
- const {
6
- wait = false,
7
- timeout = config.QUEUE.DEFAULT_TIMEOUT,
8
- batch = config.QUEUE.DEFAULT_BATCH_SIZE,
9
- consumerGroup = scope.consumerGroup || null,
10
- subscriptionMode = scope.subscriptionMode || null,
11
- subscriptionFrom = scope.subscriptionFrom || null
12
- } = options;
13
- const maxTimeout = config.QUEUE.MAX_TIMEOUT;
14
- const effectiveTimeout = Math.min(timeout, maxTimeout);
15
-
16
- // ALWAYS USE UNIFIED POP - handles all 3 modes (direct, queue-level, filtered)
17
- let result = await queueManager.uniquePop(scope, {
18
- batch,
19
- wait: false,
20
- timeout: effectiveTimeout,
21
- subscriptionMode,
22
- subscriptionFrom
23
- });
24
-
25
- if (result.messages.length > 0 || !wait) {
26
- return result;
27
- }
28
-
29
- // Long polling with event-driven optimization and exponential backoff
30
- const queuePath = scope.partition
31
- ? `${scope.queue}/${scope.partition}`
32
- : scope.queue;
33
-
34
- const startTime = Date.now();
35
- const initialPollInterval = Math.min(config.QUEUE.POLL_INTERVAL, effectiveTimeout / 10);
36
- const maxPollInterval = config.QUEUE.MAX_POLL_INTERVAL || 2000; // Default 2 seconds
37
- const backoffThreshold = config.QUEUE.BACKOFF_THRESHOLD || 5; // Retries before backoff
38
- const backoffMultiplier = config.QUEUE.BACKOFF_MULTIPLIER || 2;
39
-
40
- let currentPollInterval = initialPollInterval;
41
- let consecutiveEmptyPolls = 0;
42
-
43
- while (Date.now() - startTime < effectiveTimeout) {
44
- // Wait for message notification or current poll interval
45
- const notification = await Promise.race([
46
- eventManager.waitForMessage(queuePath, currentPollInterval),
47
- new Promise(resolve => setTimeout(() => resolve(null), currentPollInterval))
48
- ]);
49
-
50
- // Try to get messages whether notified or not
51
- result = await queueManager.uniquePop(scope, { batch, wait: false, subscriptionMode, subscriptionFrom });
52
- if (result.messages.length > 0) {
53
- return result;
54
- }
55
-
56
- // Implement exponential backoff after threshold
57
- consecutiveEmptyPolls++;
58
- if (consecutiveEmptyPolls >= backoffThreshold) {
59
- currentPollInterval = Math.min(
60
- currentPollInterval * backoffMultiplier,
61
- maxPollInterval
62
- );
63
- }
64
- }
65
-
66
- // Timeout reached, no messages
67
- return { messages: [] };
68
- };
69
- };
@@ -1,28 +0,0 @@
1
- export const createPushRoute = (queueManager) => {
2
- return async (body) => {
3
- const { items = [], config = {} } = body;
4
-
5
- if (!items || items.length === 0) {
6
- throw new Error('No items to push');
7
- }
8
-
9
- // Validate items
10
- for (const item of items) {
11
- if (!item.queue) {
12
- throw new Error('Each item must have a queue');
13
- }
14
- // Allow missing, null or undefined payloads - they'll be stored as null in JSONB
15
- // If payload is not provided, set it to null
16
- if (!('payload' in item)) {
17
- item.payload = null;
18
- }
19
- // partition is optional, defaults to "Default"
20
- }
21
-
22
- const results = await queueManager.pushMessages(items);
23
-
24
- return {
25
- messages: results
26
- };
27
- };
28
- };
@@ -1,330 +0,0 @@
1
- import { EventTypes } from '../managers/systemEventManager.js';
2
-
3
- export const createResourcesRoutes = (pool, systemEventManager) => {
4
-
5
- // Get all queues with their partitions
6
- const getQueues = async (filters = {}) => {
7
- const { namespace, task } = filters;
8
-
9
- let query = `
10
- SELECT DISTINCT
11
- q.id as queue_id,
12
- q.name as queue_name,
13
- q.namespace,
14
- q.task,
15
- q.created_at as queue_created_at,
16
- COUNT(DISTINCT p.id) as partition_count,
17
- COUNT(DISTINCT m.id) as message_count,
18
- COALESCE(SUM(DISTINCT pc.pending_estimate), 0) as pending_count,
19
- COUNT(DISTINCT CASE WHEN pc.lease_expires_at IS NOT NULL AND pc.lease_expires_at > NOW() THEN p.id END) as processing_count
20
- FROM queen.queues q
21
- LEFT JOIN queen.partitions p ON p.queue_id = q.id
22
- LEFT JOIN queen.messages m ON m.partition_id = p.id
23
- LEFT JOIN queen.partition_consumers pc ON pc.partition_id = p.id AND pc.consumer_group = '__QUEUE_MODE__'
24
- WHERE 1=1
25
- `;
26
-
27
- const params = [];
28
- if (namespace) {
29
- params.push(namespace);
30
- query += ` AND q.namespace = $${params.length}`;
31
- }
32
- if (task) {
33
- params.push(task);
34
- query += ` AND q.task = $${params.length}`;
35
- }
36
-
37
- query += ` GROUP BY q.id, q.name, q.namespace, q.task, q.created_at
38
- ORDER BY q.created_at DESC`;
39
-
40
- const result = await pool.query(query, params);
41
-
42
- return result.rows.map(row => ({
43
- id: row.queue_id,
44
- name: row.queue_name,
45
- namespace: row.namespace,
46
- task: row.task,
47
- createdAt: row.queue_created_at,
48
- partitions: parseInt(row.partition_count),
49
- messages: {
50
- total: parseInt(row.message_count),
51
- pending: parseInt(row.pending_count),
52
- processing: parseInt(row.processing_count)
53
- }
54
- }));
55
- };
56
-
57
- // Get single queue with detailed partition information
58
- const getQueue = async (queueName) => {
59
- // Get queue info
60
- const queueResult = await pool.query(`
61
- SELECT
62
- q.id,
63
- q.name,
64
- q.namespace,
65
- q.task,
66
- q.created_at
67
- FROM queen.queues q
68
- WHERE q.name = $1
69
- `, [queueName]);
70
-
71
- if (queueResult.rows.length === 0) {
72
- throw new Error('Queue not found');
73
- }
74
-
75
- const queue = queueResult.rows[0];
76
-
77
- // Get partitions with stats using partition_consumers
78
- const partitionsResult = await pool.query(`
79
- SELECT
80
- p.id,
81
- p.name,
82
- p.created_at,
83
- COUNT(DISTINCT m.id) as message_count,
84
- COALESCE(pc.pending_estimate, 0) as pending,
85
- CASE WHEN pc.lease_expires_at IS NOT NULL AND pc.lease_expires_at > NOW() THEN 1 ELSE 0 END as processing,
86
- COALESCE(pc.total_messages_consumed, 0) as completed,
87
- 0 as failed,
88
- (SELECT COUNT(*) FROM queen.dead_letter_queue dlq
89
- WHERE dlq.partition_id = p.id
90
- AND (dlq.consumer_group IS NULL OR dlq.consumer_group = '__QUEUE_MODE__')) as dead_letter,
91
- MIN(m.created_at) as oldest_message,
92
- MAX(m.created_at) as newest_message
93
- FROM queen.partitions p
94
- LEFT JOIN queen.messages m ON m.partition_id = p.id
95
- LEFT JOIN queen.partition_consumers pc ON pc.partition_id = p.id AND pc.consumer_group = '__QUEUE_MODE__'
96
- WHERE p.queue_id = $1
97
- GROUP BY p.id, p.name, p.created_at, pc.pending_estimate, pc.lease_expires_at, pc.total_messages_consumed
98
- ORDER BY p.name
99
- `, [queue.id]);
100
-
101
- const partitions = partitionsResult.rows.map(row => ({
102
- id: row.id,
103
- name: row.name,
104
- createdAt: row.created_at,
105
- stats: {
106
- total: parseInt(row.message_count),
107
- pending: parseInt(row.pending),
108
- processing: parseInt(row.processing),
109
- completed: parseInt(row.completed),
110
- failed: parseInt(row.failed),
111
- deadLetter: parseInt(row.dead_letter)
112
- },
113
- oldestMessage: row.oldest_message,
114
- newestMessage: row.newest_message
115
- }));
116
-
117
- // Calculate totals
118
- const totals = partitions.reduce((acc, p) => ({
119
- total: acc.total + p.stats.total,
120
- pending: acc.pending + p.stats.pending,
121
- processing: acc.processing + p.stats.processing,
122
- completed: acc.completed + p.stats.completed,
123
- failed: acc.failed + p.stats.failed,
124
- deadLetter: acc.deadLetter + p.stats.deadLetter
125
- }), {
126
- total: 0,
127
- pending: 0,
128
- processing: 0,
129
- completed: 0,
130
- failed: 0,
131
- deadLetter: 0
132
- });
133
-
134
- return {
135
- id: queue.id,
136
- name: queue.name,
137
- namespace: queue.namespace,
138
- task: queue.task,
139
- createdAt: queue.created_at,
140
- partitions,
141
- totals
142
- };
143
- };
144
-
145
- // Get all partitions across all queues
146
- const getPartitions = async (filters = {}) => {
147
- const { queue, minDepth } = filters;
148
-
149
- let query = `
150
- SELECT
151
- p.id,
152
- p.name as partition_name,
153
- p.created_at,
154
- q.name as queue_name,
155
- q.namespace,
156
- q.task,
157
- q.priority,
158
- COALESCE(pc.pending_estimate, 0) as pending,
159
- CASE WHEN pc.lease_expires_at IS NOT NULL AND pc.lease_expires_at > NOW() THEN 1 ELSE 0 END as processing,
160
- COUNT(DISTINCT m.id) as total
161
- FROM queen.partitions p
162
- JOIN queen.queues q ON q.id = p.queue_id
163
- LEFT JOIN queen.messages m ON m.partition_id = p.id
164
- LEFT JOIN queen.partition_consumers pc ON pc.partition_id = p.id AND pc.consumer_group = '__QUEUE_MODE__'
165
- WHERE 1=1
166
- `;
167
-
168
- const params = [];
169
- if (queue) {
170
- params.push(queue);
171
- query += ` AND q.name = $${params.length}`;
172
- }
173
-
174
- query += ` GROUP BY p.id, p.name, p.created_at,
175
- q.name, q.namespace, q.task, q.priority, pc.pending_estimate, pc.lease_expires_at`;
176
-
177
- if (minDepth) {
178
- params.push(minDepth);
179
- query += ` HAVING COALESCE(pc.pending_estimate, 0) >= $${params.length}`;
180
- }
181
-
182
- query += ` ORDER BY q.name, q.priority DESC, p.name`;
183
-
184
- const result = await pool.query(query, params);
185
-
186
- return result.rows.map(row => ({
187
- id: row.id,
188
- name: row.partition_name,
189
- queue: row.queue_name,
190
- namespace: row.namespace,
191
- task: row.task,
192
- queuePriority: row.priority, // Priority is now at queue level
193
- createdAt: row.created_at,
194
- depth: parseInt(row.pending),
195
- processing: parseInt(row.processing),
196
- total: parseInt(row.total)
197
- }));
198
- };
199
-
200
- // Get namespace/task groupings
201
- const getNamespaces = async () => {
202
- const result = await pool.query(`
203
- SELECT DISTINCT
204
- q.namespace,
205
- COUNT(DISTINCT q.id) as queue_count,
206
- COUNT(DISTINCT p.id) as partition_count,
207
- COUNT(DISTINCT m.id) as message_count,
208
- COALESCE(SUM(pc.pending_estimate), 0) as pending_count
209
- FROM queen.queues q
210
- LEFT JOIN queen.partitions p ON p.queue_id = q.id
211
- LEFT JOIN queen.messages m ON m.partition_id = p.id
212
- LEFT JOIN queen.partition_consumers pc ON pc.partition_id = p.id AND pc.consumer_group = '__QUEUE_MODE__'
213
- WHERE q.namespace IS NOT NULL
214
- GROUP BY q.namespace
215
- ORDER BY q.namespace
216
- `);
217
-
218
- return result.rows.map(row => ({
219
- namespace: row.namespace,
220
- queues: parseInt(row.queue_count),
221
- partitions: parseInt(row.partition_count),
222
- messages: {
223
- total: parseInt(row.message_count),
224
- pending: parseInt(row.pending_count)
225
- }
226
- }));
227
- };
228
-
229
- const getTasks = async () => {
230
- const result = await pool.query(`
231
- SELECT DISTINCT
232
- q.task,
233
- COUNT(DISTINCT q.id) as queue_count,
234
- COUNT(DISTINCT p.id) as partition_count,
235
- COUNT(DISTINCT m.id) as message_count,
236
- COALESCE(SUM(pc.pending_estimate), 0) as pending_count
237
- FROM queen.queues q
238
- LEFT JOIN queen.partitions p ON p.queue_id = q.id
239
- LEFT JOIN queen.messages m ON m.partition_id = p.id
240
- LEFT JOIN queen.partition_consumers pc ON pc.partition_id = p.id AND pc.consumer_group = '__QUEUE_MODE__'
241
- WHERE q.task IS NOT NULL
242
- GROUP BY q.task
243
- ORDER BY q.task
244
- `);
245
-
246
- return result.rows.map(row => ({
247
- task: row.task,
248
- queues: parseInt(row.queue_count),
249
- partitions: parseInt(row.partition_count),
250
- messages: {
251
- total: parseInt(row.message_count),
252
- pending: parseInt(row.pending_count)
253
- }
254
- }));
255
- };
256
-
257
- // Get system overview
258
- const getSystemOverview = async () => {
259
- const overview = await pool.query(`
260
- SELECT
261
- (SELECT COUNT(*) FROM queen.queues) as total_queues,
262
- (SELECT COUNT(*) FROM queen.partitions) as total_partitions,
263
- (SELECT COUNT(*) FROM queen.messages) as total_messages,
264
- (SELECT COALESCE(SUM(pending_estimate), 0)::integer FROM queen.partition_consumers
265
- WHERE consumer_group = '__QUEUE_MODE__') as pending_messages,
266
- (SELECT COUNT(*) FROM queen.partition_consumers
267
- WHERE consumer_group = '__QUEUE_MODE__'
268
- AND lease_expires_at IS NOT NULL
269
- AND lease_expires_at > NOW()) as processing_messages,
270
- (SELECT COALESCE(SUM(total_messages_consumed), 0)::integer FROM queen.partition_consumers
271
- WHERE consumer_group = '__QUEUE_MODE__') as completed_messages,
272
- 0 as failed_messages,
273
- (SELECT COUNT(*) FROM queen.dead_letter_queue WHERE consumer_group IS NULL OR consumer_group = '__QUEUE_MODE__') as dead_letter_messages,
274
- (SELECT COUNT(DISTINCT namespace) FROM queen.queues WHERE namespace IS NOT NULL) as namespaces,
275
- (SELECT COUNT(DISTINCT task) FROM queen.queues WHERE task IS NOT NULL) as tasks
276
- `);
277
-
278
- const row = overview.rows[0];
279
-
280
- return {
281
- queues: parseInt(row.total_queues),
282
- partitions: parseInt(row.total_partitions),
283
- namespaces: parseInt(row.namespaces),
284
- tasks: parseInt(row.tasks),
285
- messages: {
286
- total: parseInt(row.total_messages),
287
- pending: parseInt(row.pending_messages),
288
- processing: parseInt(row.processing_messages),
289
- completed: parseInt(row.completed_messages),
290
- failed: parseInt(row.failed_messages),
291
- deadLetter: parseInt(row.dead_letter_messages)
292
- },
293
- timestamp: new Date().toISOString()
294
- };
295
- };
296
-
297
- // Delete a queue (cascades to partitions, messages, etc.)
298
- // Idempotent: returns success even if queue doesn't exist
299
- const deleteQueue = async (queueName) => {
300
- const result = await pool.query(
301
- `DELETE FROM queen.queues WHERE name = $1 RETURNING id, name`,
302
- [queueName]
303
- );
304
-
305
- // Emit system event for cache invalidation across nodes
306
- if (result.rows.length > 0 && systemEventManager) {
307
- await systemEventManager.emit(EventTypes.QUEUE_DELETED, {
308
- entityId: queueName,
309
- queueId: result.rows[0].id
310
- });
311
- }
312
-
313
- return {
314
- deleted: true,
315
- queue: queueName,
316
- existed: result.rows.length > 0,
317
- id: result.rows.length > 0 ? result.rows[0].id : null
318
- };
319
- };
320
-
321
- return {
322
- getQueues,
323
- getQueue,
324
- getPartitions,
325
- getNamespaces,
326
- getTasks,
327
- getSystemOverview,
328
- deleteQueue
329
- };
330
- };