queen-mq 0.2.0 → 0.2.22
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.
- package/README.md +186 -763
- package/{src → client-js}/benchmark/consumer.js +6 -4
- package/{src → client-js}/benchmark/producer.js +9 -4
- package/{src → client-js}/client/client.js +277 -7
- package/{src → client-js}/client/index.js +1 -4
- package/{src → client-js}/client/utils/http.js +3 -2
- package/{src → client-js}/client/utils/retry.js +7 -1
- package/{src → client-js}/test/README.md +30 -9
- package/{src → client-js}/test/core-tests.js +7 -2
- package/{src → client-js}/test/edge-case-tests.js +2 -1
- package/client-js/test/human.js +162 -0
- package/client-js/test/partition-transaction-tests.js +482 -0
- package/client-js/test/qos0-tests.js +334 -0
- package/{src → client-js}/test/test-new.js +55 -0
- package/package.json +8 -17
- package/init-db.js +0 -20
- package/src/cluster-server.js +0 -242
- package/src/config.js +0 -229
- package/src/database/connection.js +0 -129
- package/src/database/poolManager.js +0 -199
- package/src/database/schema-v2.sql +0 -278
- package/src/managers/eventManager.js +0 -59
- package/src/managers/queueManagerOptimized.js +0 -1634
- package/src/managers/resourceCache.js +0 -96
- package/src/managers/systemEventManager.js +0 -132
- package/src/routes/ack.js +0 -26
- package/src/routes/configure.js +0 -46
- package/src/routes/messages.js +0 -368
- package/src/routes/pop.js +0 -69
- package/src/routes/push.js +0 -28
- package/src/routes/resources.js +0 -330
- package/src/routes/status.js +0 -1037
- package/src/server.js +0 -1633
- package/src/test/MIGRATION_ISSUES.md +0 -174
- package/src/test/test.js +0 -4524
- package/src/utils/streaming.js +0 -231
- package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
- package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
- package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
- package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
- package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
- package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
- package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
- package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
- package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
- package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
- package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
- package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
- package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
- package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
- package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
- package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
- package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
- package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
- package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
- package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
- package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
- package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
- package/src/webapp-dist/assets/queen-logo.svg +0 -263
- package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
- package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
- package/src/webapp-dist/index.html +0 -15
- package/src/websocket/wsServer.js +0 -228
- /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
- /package/{src → client-js}/benchmark/producer_multi.js +0 -0
- /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
- /package/{src → client-js}/services/encryptionService.js +0 -0
- /package/{src → client-js}/services/evictionService.js +0 -0
- /package/{src → client-js}/services/retentionService.js +0 -0
- /package/{src → client-js}/services/startupSync.js +0 -0
- /package/{src → client-js}/test/advanced-client-tests.js +0 -0
- /package/{src → client-js}/test/advanced-pattern-tests.js +0 -0
- /package/{src → client-js}/test/bus-mode-tests.js +0 -0
- /package/{src → client-js}/test/enterprise-tests.js +0 -0
- /package/{src → client-js}/test/partition-locking-tests.js +0 -0
- /package/{src → client-js}/test/utils.js +0 -0
- /package/{src → client-js}/test/window-buffer-test.js +0 -0
- /package/{src → client-js}/utils/logger.js +0 -0
- /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
|
-
};
|
package/src/routes/push.js
DELETED
|
@@ -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
|
-
};
|
package/src/routes/resources.js
DELETED
|
@@ -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
|
-
};
|