@onlineapps/conn-infra-mq 1.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.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/package.json +96 -0
- package/src/BaseClient.js +219 -0
- package/src/ConnectorMQClient.js +446 -0
- package/src/config/configSchema.js +70 -0
- package/src/config/defaultConfig.js +48 -0
- package/src/index.js +65 -0
- package/src/layers/ForkJoinHandler.js +312 -0
- package/src/layers/QueueManager.js +263 -0
- package/src/layers/RPCHandler.js +324 -0
- package/src/layers/RetryHandler.js +370 -0
- package/src/layers/WorkflowRouter.js +136 -0
- package/src/transports/rabbitmqClient.js +216 -0
- package/src/transports/transportFactory.js +33 -0
- package/src/utils/errorHandler.js +120 -0
- package/src/utils/logger.js +38 -0
- package/src/utils/serializer.js +44 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ForkJoinHandler - Manages fork-join parallel processing patterns
|
|
5
|
+
* Handles accumulator queues and result collection from parallel branches
|
|
6
|
+
*/
|
|
7
|
+
class ForkJoinHandler {
|
|
8
|
+
constructor(mqClient, queueManager, config = {}) {
|
|
9
|
+
this.client = mqClient;
|
|
10
|
+
this.queueManager = queueManager;
|
|
11
|
+
this.config = {
|
|
12
|
+
defaultTimeout: config.defaultTimeout || 30000,
|
|
13
|
+
accumulatorPrefix: config.accumulatorPrefix || 'fork',
|
|
14
|
+
...config
|
|
15
|
+
};
|
|
16
|
+
this.activeAccumulators = new Map();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create accumulator queue for collecting fork-join results
|
|
21
|
+
* @param {string} workflowId - Workflow identifier
|
|
22
|
+
* @param {string} stepId - Step identifier
|
|
23
|
+
* @param {number} expectedCount - Number of expected results
|
|
24
|
+
*/
|
|
25
|
+
async createAccumulator(workflowId, stepId, expectedCount) {
|
|
26
|
+
const queueName = `${this.config.accumulatorPrefix}.${workflowId}.${stepId}.accumulator`;
|
|
27
|
+
|
|
28
|
+
// Create temporary queue for accumulation
|
|
29
|
+
await this.queueManager.ensureQueue(queueName, {
|
|
30
|
+
durable: false,
|
|
31
|
+
autoDelete: true,
|
|
32
|
+
expires: this.config.defaultTimeout * 2, // Double the timeout for safety
|
|
33
|
+
exclusive: false
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Track accumulator
|
|
37
|
+
this.activeAccumulators.set(queueName, {
|
|
38
|
+
workflowId,
|
|
39
|
+
stepId,
|
|
40
|
+
expectedCount,
|
|
41
|
+
collectedCount: 0,
|
|
42
|
+
results: [],
|
|
43
|
+
createdAt: Date.now()
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return queueName;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Fork - distribute work to multiple queues
|
|
51
|
+
* @param {Array} branches - Array of { queue, message } objects
|
|
52
|
+
* @param {Object} context - Common context for all branches
|
|
53
|
+
*/
|
|
54
|
+
async fork(branches, context = {}) {
|
|
55
|
+
const forkId = `fork-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
56
|
+
const promises = [];
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < branches.length; i++) {
|
|
59
|
+
const branch = branches[i];
|
|
60
|
+
const message = {
|
|
61
|
+
...branch.message,
|
|
62
|
+
_fork: {
|
|
63
|
+
id: forkId,
|
|
64
|
+
branch: i,
|
|
65
|
+
total: branches.length,
|
|
66
|
+
context
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
promises.push(
|
|
71
|
+
this.client.publish(message, {
|
|
72
|
+
queue: branch.queue,
|
|
73
|
+
...branch.options
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await Promise.all(promises);
|
|
79
|
+
return forkId;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Collect results from accumulator queue
|
|
84
|
+
* @param {string} queueName - Accumulator queue name
|
|
85
|
+
* @param {Function} onComplete - Callback when all results collected
|
|
86
|
+
* @param {Object} options - Collection options
|
|
87
|
+
*/
|
|
88
|
+
async collectResults(queueName, onComplete, options = {}) {
|
|
89
|
+
const accumulator = this.activeAccumulators.get(queueName);
|
|
90
|
+
if (!accumulator) {
|
|
91
|
+
throw new Error(`Accumulator ${queueName} not found`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const timeout = options.timeout || this.config.defaultTimeout;
|
|
95
|
+
const timeoutHandle = setTimeout(() => {
|
|
96
|
+
this.handleTimeout(queueName, onComplete);
|
|
97
|
+
}, timeout);
|
|
98
|
+
|
|
99
|
+
// Consume with prefetch 1 for atomic result collection
|
|
100
|
+
await this.client.consume(async (result, rawMsg) => {
|
|
101
|
+
accumulator.results.push(result);
|
|
102
|
+
accumulator.collectedCount++;
|
|
103
|
+
|
|
104
|
+
// Acknowledge immediately
|
|
105
|
+
await this.client.ack(rawMsg);
|
|
106
|
+
|
|
107
|
+
// Check if all results collected
|
|
108
|
+
if (accumulator.collectedCount >= accumulator.expectedCount) {
|
|
109
|
+
clearTimeout(timeoutHandle);
|
|
110
|
+
await this.handleComplete(queueName, onComplete);
|
|
111
|
+
}
|
|
112
|
+
}, {
|
|
113
|
+
queue: queueName,
|
|
114
|
+
prefetch: 1,
|
|
115
|
+
noAck: false
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
queueName,
|
|
120
|
+
expectedCount: accumulator.expectedCount
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Join - wait for all results and combine them
|
|
126
|
+
* @param {string} queueName - Accumulator queue name
|
|
127
|
+
* @param {Function} joinStrategy - Function to combine results
|
|
128
|
+
* @param {Object} options - Join options
|
|
129
|
+
*/
|
|
130
|
+
async join(queueName, joinStrategy, options = {}) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const timeout = options.timeout || this.config.defaultTimeout;
|
|
133
|
+
|
|
134
|
+
this.collectResults(queueName, async (error, results) => {
|
|
135
|
+
if (error) {
|
|
136
|
+
reject(error);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const joined = await joinStrategy(results);
|
|
142
|
+
resolve(joined);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
reject(err);
|
|
145
|
+
}
|
|
146
|
+
}, { timeout });
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Fork-join convenience method
|
|
152
|
+
* @param {Array} branches - Branches to fork to
|
|
153
|
+
* @param {Function} joinStrategy - How to combine results
|
|
154
|
+
* @param {Object} options - Fork-join options
|
|
155
|
+
*/
|
|
156
|
+
async forkJoin(branches, joinStrategy, options = {}) {
|
|
157
|
+
const workflowId = options.workflowId || `wf-${Date.now()}`;
|
|
158
|
+
const stepId = options.stepId || 'step-1';
|
|
159
|
+
|
|
160
|
+
// Create accumulator
|
|
161
|
+
const accumulatorQueue = await this.createAccumulator(
|
|
162
|
+
workflowId,
|
|
163
|
+
stepId,
|
|
164
|
+
branches.length
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Modify branches to send results to accumulator
|
|
168
|
+
const modifiedBranches = branches.map(branch => ({
|
|
169
|
+
...branch,
|
|
170
|
+
message: {
|
|
171
|
+
...branch.message,
|
|
172
|
+
_replyTo: accumulatorQueue
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
// Fork work
|
|
177
|
+
const forkId = await this.fork(modifiedBranches);
|
|
178
|
+
|
|
179
|
+
// Join results
|
|
180
|
+
const result = await this.join(accumulatorQueue, joinStrategy, options);
|
|
181
|
+
|
|
182
|
+
// Cleanup
|
|
183
|
+
await this.cleanupAccumulator(accumulatorQueue);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
forkId,
|
|
187
|
+
result,
|
|
188
|
+
branchCount: branches.length
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handle timeout for accumulator
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
handleTimeout(queueName, callback) {
|
|
197
|
+
const accumulator = this.activeAccumulators.get(queueName);
|
|
198
|
+
if (accumulator) {
|
|
199
|
+
const error = new Error(
|
|
200
|
+
`Timeout waiting for fork-join results. Received ${accumulator.collectedCount}/${accumulator.expectedCount}`
|
|
201
|
+
);
|
|
202
|
+
error.partialResults = accumulator.results;
|
|
203
|
+
callback(error, null);
|
|
204
|
+
this.cleanupAccumulator(queueName).catch(() => {});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Handle completion of result collection
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
async handleComplete(queueName, callback) {
|
|
213
|
+
const accumulator = this.activeAccumulators.get(queueName);
|
|
214
|
+
if (accumulator) {
|
|
215
|
+
callback(null, accumulator.results);
|
|
216
|
+
await this.cleanupAccumulator(queueName);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Cleanup accumulator queue and tracking
|
|
222
|
+
* @param {string} queueName - Accumulator queue name
|
|
223
|
+
*/
|
|
224
|
+
async cleanupAccumulator(queueName) {
|
|
225
|
+
this.activeAccumulators.delete(queueName);
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await this.queueManager.deleteQueue(queueName, { ifEmpty: true });
|
|
229
|
+
} catch (error) {
|
|
230
|
+
// Queue might already be deleted or have messages
|
|
231
|
+
console.warn(`Failed to delete accumulator queue ${queueName}:`, error.message);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Built-in join strategies
|
|
237
|
+
*/
|
|
238
|
+
static JoinStrategies = {
|
|
239
|
+
/**
|
|
240
|
+
* Merge all results into single object
|
|
241
|
+
*/
|
|
242
|
+
merge: (results) => {
|
|
243
|
+
return results.reduce((acc, result) => ({ ...acc, ...result }), {});
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Concatenate array results
|
|
248
|
+
*/
|
|
249
|
+
concat: (results) => {
|
|
250
|
+
return results.reduce((acc, result) => {
|
|
251
|
+
if (Array.isArray(result)) {
|
|
252
|
+
return acc.concat(result);
|
|
253
|
+
}
|
|
254
|
+
acc.push(result);
|
|
255
|
+
return acc;
|
|
256
|
+
}, []);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Return first result
|
|
261
|
+
*/
|
|
262
|
+
first: (results) => results[0],
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Return last result
|
|
266
|
+
*/
|
|
267
|
+
last: (results) => results[results.length - 1],
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Return all results as array
|
|
271
|
+
*/
|
|
272
|
+
all: (results) => results,
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Sum numeric results
|
|
276
|
+
*/
|
|
277
|
+
sum: (results) => {
|
|
278
|
+
return results.reduce((acc, result) => {
|
|
279
|
+
if (typeof result === 'number') {
|
|
280
|
+
return acc + result;
|
|
281
|
+
}
|
|
282
|
+
if (result && typeof result.value === 'number') {
|
|
283
|
+
return acc + result.value;
|
|
284
|
+
}
|
|
285
|
+
return acc;
|
|
286
|
+
}, 0);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get active accumulators
|
|
292
|
+
*/
|
|
293
|
+
getActiveAccumulators() {
|
|
294
|
+
return Array.from(this.activeAccumulators.entries()).map(([queue, data]) => ({
|
|
295
|
+
queue,
|
|
296
|
+
...data
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Cleanup all active accumulators
|
|
302
|
+
*/
|
|
303
|
+
async cleanupAll() {
|
|
304
|
+
const promises = [];
|
|
305
|
+
for (const queueName of this.activeAccumulators.keys()) {
|
|
306
|
+
promises.push(this.cleanupAccumulator(queueName));
|
|
307
|
+
}
|
|
308
|
+
await Promise.all(promises);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = ForkJoinHandler;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* QueueManager - Manages queue creation, configuration, and lifecycle
|
|
5
|
+
* Handles TTL, DLQ, auto-delete, and other queue properties
|
|
6
|
+
*/
|
|
7
|
+
class QueueManager {
|
|
8
|
+
constructor(mqClient, config = {}) {
|
|
9
|
+
this.client = mqClient;
|
|
10
|
+
this.config = {
|
|
11
|
+
defaultTTL: config.defaultTTL || 30000,
|
|
12
|
+
dlxExchange: config.dlxExchange || 'dlx',
|
|
13
|
+
autoDeleteTimeout: config.autoDeleteTimeout || 300000, // 5 minutes
|
|
14
|
+
maxRetries: config.maxRetries || 3,
|
|
15
|
+
...config
|
|
16
|
+
};
|
|
17
|
+
this.managedQueues = new Set();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create or ensure queue exists with specific configuration
|
|
22
|
+
* @param {string} queueName - Name of the queue
|
|
23
|
+
* @param {Object} options - Queue configuration options
|
|
24
|
+
*/
|
|
25
|
+
async ensureQueue(queueName, options = {}) {
|
|
26
|
+
// Get channel from client's transport
|
|
27
|
+
const transport = this.client._transport;
|
|
28
|
+
if (!transport || !transport.channel) {
|
|
29
|
+
throw new Error('MQ client not connected');
|
|
30
|
+
}
|
|
31
|
+
const channel = transport.channel;
|
|
32
|
+
|
|
33
|
+
const queueOptions = {
|
|
34
|
+
durable: options.durable !== false,
|
|
35
|
+
arguments: {
|
|
36
|
+
...options.arguments
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Add TTL if specified
|
|
41
|
+
if (options.ttl !== null && options.ttl !== undefined) {
|
|
42
|
+
queueOptions.arguments['x-message-ttl'] = options.ttl || this.config.defaultTTL;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Add DLQ configuration
|
|
46
|
+
if (options.dlq !== false) {
|
|
47
|
+
queueOptions.arguments['x-dead-letter-exchange'] = options.dlxExchange || this.config.dlxExchange;
|
|
48
|
+
queueOptions.arguments['x-dead-letter-routing-key'] = options.dlqKey || `${queueName}.dlq`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Add auto-delete expiry
|
|
52
|
+
if (options.autoDelete === true) {
|
|
53
|
+
queueOptions.arguments['x-expires'] = options.expires || this.config.autoDeleteTimeout;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add max length if specified
|
|
57
|
+
if (options.maxLength) {
|
|
58
|
+
queueOptions.arguments['x-max-length'] = options.maxLength;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add priority support
|
|
62
|
+
if (options.maxPriority) {
|
|
63
|
+
queueOptions.arguments['x-max-priority'] = options.maxPriority;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Track managed queue
|
|
67
|
+
this.managedQueues.add(queueName);
|
|
68
|
+
|
|
69
|
+
// Assert the queue
|
|
70
|
+
return channel.assertQueue(queueName, queueOptions);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Setup service queues (main + DLQ + optional workflow queue)
|
|
75
|
+
* @param {string} serviceName - Name of the service
|
|
76
|
+
* @param {Object} options - Configuration options
|
|
77
|
+
*/
|
|
78
|
+
async setupServiceQueues(serviceName, options = {}) {
|
|
79
|
+
const transport = this.client._transport;
|
|
80
|
+
if (!transport || !transport.channel) {
|
|
81
|
+
throw new Error('MQ client not connected');
|
|
82
|
+
}
|
|
83
|
+
const channel = transport.channel;
|
|
84
|
+
|
|
85
|
+
const queues = {};
|
|
86
|
+
|
|
87
|
+
// Create main processing queue
|
|
88
|
+
await this.ensureQueue(`${serviceName}.queue`, {
|
|
89
|
+
ttl: options.ttl || this.config.defaultTTL,
|
|
90
|
+
dlq: true,
|
|
91
|
+
durable: true,
|
|
92
|
+
maxRetries: this.config.maxRetries
|
|
93
|
+
});
|
|
94
|
+
queues.main = `${serviceName}.queue`;
|
|
95
|
+
|
|
96
|
+
// Create dead letter queue
|
|
97
|
+
await this.ensureQueue(`${serviceName}.dlq`, {
|
|
98
|
+
ttl: null, // No TTL for DLQ
|
|
99
|
+
dlq: false,
|
|
100
|
+
durable: true,
|
|
101
|
+
autoDelete: false
|
|
102
|
+
});
|
|
103
|
+
queues.dlq = `${serviceName}.dlq`;
|
|
104
|
+
|
|
105
|
+
// Create workflow queue if requested
|
|
106
|
+
if (options.includeWorkflow !== false) {
|
|
107
|
+
await this.ensureQueue(`${serviceName}.workflow`, {
|
|
108
|
+
ttl: options.workflowTTL || this.config.defaultTTL,
|
|
109
|
+
dlq: true,
|
|
110
|
+
durable: true
|
|
111
|
+
});
|
|
112
|
+
queues.workflow = `${serviceName}.workflow`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Setup DLQ exchange and bindings
|
|
116
|
+
await channel.assertExchange(this.config.dlxExchange, 'direct', {
|
|
117
|
+
durable: true
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Bind DLQ
|
|
121
|
+
await channel.bindQueue(
|
|
122
|
+
queues.dlq,
|
|
123
|
+
this.config.dlxExchange,
|
|
124
|
+
`${serviceName}.queue.dlq`
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (queues.workflow) {
|
|
128
|
+
await channel.bindQueue(
|
|
129
|
+
queues.dlq,
|
|
130
|
+
this.config.dlxExchange,
|
|
131
|
+
`${serviceName}.workflow.dlq`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return queues;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create temporary queue for short-lived operations
|
|
140
|
+
* @param {string} prefix - Queue name prefix
|
|
141
|
+
* @param {Object} options - Queue options
|
|
142
|
+
*/
|
|
143
|
+
async createTemporaryQueue(prefix = 'temp', options = {}) {
|
|
144
|
+
const queueName = `${prefix}.${Date.now()}.${Math.random().toString(36).substr(2, 9)}`;
|
|
145
|
+
|
|
146
|
+
await this.ensureQueue(queueName, {
|
|
147
|
+
durable: false,
|
|
148
|
+
autoDelete: true,
|
|
149
|
+
expires: options.expires || 60000, // 1 minute default
|
|
150
|
+
exclusive: options.exclusive || false,
|
|
151
|
+
...options
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return queueName;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Delete a queue
|
|
159
|
+
* @param {string} queueName - Name of the queue to delete
|
|
160
|
+
* @param {Object} options - Delete options
|
|
161
|
+
*/
|
|
162
|
+
async deleteQueue(queueName, options = {}) {
|
|
163
|
+
const transport = this.client._transport;
|
|
164
|
+
if (!transport || !transport.channel) {
|
|
165
|
+
throw new Error('MQ client not connected');
|
|
166
|
+
}
|
|
167
|
+
const channel = transport.channel;
|
|
168
|
+
|
|
169
|
+
this.managedQueues.delete(queueName);
|
|
170
|
+
return channel.deleteQueue(queueName, options);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Purge all messages from a queue
|
|
175
|
+
* @param {string} queueName - Name of the queue to purge
|
|
176
|
+
*/
|
|
177
|
+
async purgeQueue(queueName) {
|
|
178
|
+
const transport = this.client._transport;
|
|
179
|
+
if (!transport || !transport.channel) {
|
|
180
|
+
throw new Error('MQ client not connected');
|
|
181
|
+
}
|
|
182
|
+
const channel = transport.channel;
|
|
183
|
+
|
|
184
|
+
return channel.purgeQueue(queueName);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get queue statistics
|
|
189
|
+
* @param {string} queueName - Name of the queue
|
|
190
|
+
*/
|
|
191
|
+
async getQueueStats(queueName) {
|
|
192
|
+
const transport = this.client._transport;
|
|
193
|
+
if (!transport || !transport.channel) {
|
|
194
|
+
throw new Error('MQ client not connected');
|
|
195
|
+
}
|
|
196
|
+
const channel = transport.channel;
|
|
197
|
+
|
|
198
|
+
const result = await channel.checkQueue(queueName);
|
|
199
|
+
return {
|
|
200
|
+
name: result.queue,
|
|
201
|
+
messageCount: result.messageCount,
|
|
202
|
+
consumerCount: result.consumerCount
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Setup exchange
|
|
208
|
+
* @param {string} exchangeName - Name of the exchange
|
|
209
|
+
* @param {string} type - Exchange type (direct, topic, fanout, headers)
|
|
210
|
+
* @param {Object} options - Exchange options
|
|
211
|
+
*/
|
|
212
|
+
async setupExchange(exchangeName, type = 'direct', options = {}) {
|
|
213
|
+
const transport = this.client._transport;
|
|
214
|
+
if (!transport || !transport.channel) {
|
|
215
|
+
throw new Error('MQ client not connected');
|
|
216
|
+
}
|
|
217
|
+
const channel = transport.channel;
|
|
218
|
+
|
|
219
|
+
return channel.assertExchange(exchangeName, type, {
|
|
220
|
+
durable: options.durable !== false,
|
|
221
|
+
...options
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Bind queue to exchange
|
|
227
|
+
* @param {string} queue - Queue name
|
|
228
|
+
* @param {string} exchange - Exchange name
|
|
229
|
+
* @param {string} routingKey - Routing key for binding
|
|
230
|
+
*/
|
|
231
|
+
async bindQueue(queue, exchange, routingKey = '') {
|
|
232
|
+
const transport = this.client._transport;
|
|
233
|
+
if (!transport || !transport.channel) {
|
|
234
|
+
throw new Error('MQ client not connected');
|
|
235
|
+
}
|
|
236
|
+
const channel = transport.channel;
|
|
237
|
+
|
|
238
|
+
return channel.bindQueue(queue, exchange, routingKey);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Cleanup all managed queues
|
|
243
|
+
*/
|
|
244
|
+
async cleanup() {
|
|
245
|
+
const promises = [];
|
|
246
|
+
for (const queueName of this.managedQueues) {
|
|
247
|
+
if (queueName.startsWith('temp.')) {
|
|
248
|
+
promises.push(this.deleteQueue(queueName).catch(() => {}));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
await Promise.all(promises);
|
|
252
|
+
this.managedQueues.clear();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get list of managed queues
|
|
257
|
+
*/
|
|
258
|
+
getManagedQueues() {
|
|
259
|
+
return Array.from(this.managedQueues);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = QueueManager;
|