@onlineapps/mq-client-core 1.0.32 → 1.0.34
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/package.json +1 -1
- package/src/transports/rabbitmqClient.js +301 -97
package/package.json
CHANGED
|
@@ -35,6 +35,13 @@ class RabbitMQClient extends EventEmitter {
|
|
|
35
35
|
this._channel = null; // ConfirmChannel for publish operations
|
|
36
36
|
this._queueChannel = null; // Regular channel for queue operations (assertQueue, checkQueue)
|
|
37
37
|
this._consumerChannel = null; // Dedicated channel for consume operations
|
|
38
|
+
|
|
39
|
+
// Track active consumers for re-registration after channel recreation
|
|
40
|
+
this._activeConsumers = new Map(); // queue -> { handler, options, consumerTag }
|
|
41
|
+
|
|
42
|
+
// Auto-reconnect flags
|
|
43
|
+
this._reconnecting = false;
|
|
44
|
+
this._reconnectAttempts = 0;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
/**
|
|
@@ -94,33 +101,57 @@ class RabbitMQClient extends EventEmitter {
|
|
|
94
101
|
|
|
95
102
|
// Use ConfirmChannel to enable publisher confirms for publish operations
|
|
96
103
|
this._channel = await this._connection.createConfirmChannel();
|
|
97
|
-
|
|
98
|
-
this._channel.on('
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
// Enable publisher confirms explicitly
|
|
105
|
+
this._channel.on('error', async (err) => {
|
|
106
|
+
console.error('[RabbitMQClient] [mq-client-core] Publisher channel error:', err.message);
|
|
107
|
+
// Auto-recreate channel if connection is still alive
|
|
108
|
+
await this._ensurePublisherChannel();
|
|
109
|
+
this.emit('error', err);
|
|
110
|
+
});
|
|
111
|
+
this._channel.on('close', async () => {
|
|
112
|
+
console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed - will auto-recreate on next publish');
|
|
113
|
+
// Mark channel as null - will be recreated on next publish
|
|
114
|
+
this._channel = null;
|
|
115
|
+
// Try to recreate immediately if connection is alive
|
|
116
|
+
await this._ensurePublisherChannel();
|
|
117
|
+
});
|
|
118
|
+
// Set up publisher confirms callback
|
|
119
|
+
this._channel.on('drain', () => {
|
|
120
|
+
console.log('[RabbitMQClient] [mq-client-core] Publisher channel drained');
|
|
101
121
|
});
|
|
102
122
|
|
|
103
123
|
// Create a separate regular channel for queue operations (assertQueue, checkQueue)
|
|
104
124
|
// This avoids RPC reply queue issues with ConfirmChannel.assertQueue()
|
|
105
125
|
this._queueChannel = await this._connection.createChannel();
|
|
106
|
-
this._queueChannel.on('error', (err) => {
|
|
126
|
+
this._queueChannel.on('error', async (err) => {
|
|
107
127
|
// Log but don't emit - queue channel errors are less critical
|
|
108
128
|
console.warn('[RabbitMQClient] Queue channel error:', err.message);
|
|
129
|
+
// Auto-recreate channel if connection is still alive
|
|
130
|
+
await this._ensureQueueChannel();
|
|
109
131
|
});
|
|
110
|
-
this._queueChannel.on('close', () => {
|
|
111
|
-
console.warn('[RabbitMQClient] Queue channel closed');
|
|
132
|
+
this._queueChannel.on('close', async () => {
|
|
133
|
+
console.warn('[RabbitMQClient] Queue channel closed - will auto-recreate on next operation');
|
|
134
|
+
// Mark channel as null - will be recreated on next queue operation
|
|
135
|
+
this._queueChannel = null;
|
|
136
|
+
// Try to recreate immediately if connection is alive
|
|
137
|
+
await this._ensureQueueChannel();
|
|
112
138
|
});
|
|
113
139
|
|
|
114
140
|
// Create a dedicated channel for consume operations
|
|
115
141
|
// This prevents channel conflicts between publish (ConfirmChannel) and consume operations
|
|
116
142
|
this._consumerChannel = await this._connection.createChannel();
|
|
117
|
-
this._consumerChannel.on('error', (err) => {
|
|
143
|
+
this._consumerChannel.on('error', async (err) => {
|
|
118
144
|
console.warn('[RabbitMQClient] Consumer channel error:', err.message);
|
|
145
|
+
// Auto-recreate channel and re-register consumers
|
|
146
|
+
await this._ensureConsumerChannel();
|
|
119
147
|
this.emit('error', err);
|
|
120
148
|
});
|
|
121
|
-
this._consumerChannel.on('close', () => {
|
|
122
|
-
console.warn('[RabbitMQClient] Consumer channel closed');
|
|
123
|
-
|
|
149
|
+
this._consumerChannel.on('close', async () => {
|
|
150
|
+
console.warn('[RabbitMQClient] Consumer channel closed - will auto-recreate and re-register consumers');
|
|
151
|
+
// Mark channel as null - will be recreated and consumers re-registered
|
|
152
|
+
this._consumerChannel = null;
|
|
153
|
+
// Try to recreate and re-register consumers immediately if connection is alive
|
|
154
|
+
await this._ensureConsumerChannel();
|
|
124
155
|
});
|
|
125
156
|
} catch (err) {
|
|
126
157
|
// Cleanup partially created resources
|
|
@@ -136,11 +167,192 @@ class RabbitMQClient extends EventEmitter {
|
|
|
136
167
|
}
|
|
137
168
|
}
|
|
138
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Ensure publisher channel exists and is open
|
|
172
|
+
* Auto-recreates if closed
|
|
173
|
+
* @private
|
|
174
|
+
* @returns {Promise<void>}
|
|
175
|
+
*/
|
|
176
|
+
async _ensurePublisherChannel() {
|
|
177
|
+
if (this._channel && !this._channel.closed) {
|
|
178
|
+
return; // Channel is good
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!this._connection || this._connection.closed) {
|
|
182
|
+
throw new Error('Cannot recreate publisher channel: connection is closed');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
// Close old channel if exists
|
|
187
|
+
if (this._channel) {
|
|
188
|
+
try {
|
|
189
|
+
await this._channel.close();
|
|
190
|
+
} catch (_) {
|
|
191
|
+
// Ignore errors when closing already-closed channel
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Create new ConfirmChannel
|
|
196
|
+
this._channel = await this._connection.createConfirmChannel();
|
|
197
|
+
this._channel.on('error', async (err) => {
|
|
198
|
+
console.error('[RabbitMQClient] [mq-client-core] Publisher channel error:', err.message);
|
|
199
|
+
await this._ensurePublisherChannel();
|
|
200
|
+
this.emit('error', err);
|
|
201
|
+
});
|
|
202
|
+
this._channel.on('close', async () => {
|
|
203
|
+
console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed - will auto-recreate on next publish');
|
|
204
|
+
this._channel = null;
|
|
205
|
+
await this._ensurePublisherChannel();
|
|
206
|
+
});
|
|
207
|
+
this._channel.on('drain', () => {
|
|
208
|
+
console.log('[RabbitMQClient] [mq-client-core] Publisher channel drained');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
console.log('[RabbitMQClient] [mq-client-core] ✓ Publisher channel recreated');
|
|
212
|
+
} catch (err) {
|
|
213
|
+
this._channel = null;
|
|
214
|
+
throw new Error(`Failed to recreate publisher channel: ${err.message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Ensure queue channel exists and is open
|
|
220
|
+
* Auto-recreates if closed
|
|
221
|
+
* @private
|
|
222
|
+
* @returns {Promise<void>}
|
|
223
|
+
*/
|
|
224
|
+
async _ensureQueueChannel() {
|
|
225
|
+
if (this._queueChannel && !this._queueChannel.closed) {
|
|
226
|
+
return; // Channel is good
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!this._connection || this._connection.closed) {
|
|
230
|
+
throw new Error('Cannot recreate queue channel: connection is closed');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// Close old channel if exists
|
|
235
|
+
if (this._queueChannel) {
|
|
236
|
+
try {
|
|
237
|
+
await this._queueChannel.close();
|
|
238
|
+
} catch (_) {
|
|
239
|
+
// Ignore errors when closing already-closed channel
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Create new regular channel
|
|
244
|
+
this._queueChannel = await this._connection.createChannel();
|
|
245
|
+
this._queueChannel.on('error', async (err) => {
|
|
246
|
+
console.warn('[RabbitMQClient] Queue channel error:', err.message);
|
|
247
|
+
await this._ensureQueueChannel();
|
|
248
|
+
});
|
|
249
|
+
this._queueChannel.on('close', async () => {
|
|
250
|
+
console.warn('[RabbitMQClient] Queue channel closed - will auto-recreate on next operation');
|
|
251
|
+
this._queueChannel = null;
|
|
252
|
+
await this._ensureQueueChannel();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
console.log('[RabbitMQClient] [mq-client-core] ✓ Queue channel recreated');
|
|
256
|
+
} catch (err) {
|
|
257
|
+
this._queueChannel = null;
|
|
258
|
+
throw new Error(`Failed to recreate queue channel: ${err.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Ensure consumer channel exists and is open
|
|
264
|
+
* Auto-recreates and re-registers all consumers if closed
|
|
265
|
+
* @private
|
|
266
|
+
* @returns {Promise<void>}
|
|
267
|
+
*/
|
|
268
|
+
async _ensureConsumerChannel() {
|
|
269
|
+
if (this._consumerChannel && !this._consumerChannel.closed) {
|
|
270
|
+
return; // Channel is good
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!this._connection || this._connection.closed) {
|
|
274
|
+
throw new Error('Cannot recreate consumer channel: connection is closed');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// Close old channel if exists
|
|
279
|
+
if (this._consumerChannel) {
|
|
280
|
+
try {
|
|
281
|
+
await this._consumerChannel.close();
|
|
282
|
+
} catch (_) {
|
|
283
|
+
// Ignore errors when closing already-closed channel
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Create new regular channel
|
|
288
|
+
this._consumerChannel = await this._connection.createChannel();
|
|
289
|
+
this._consumerChannel.on('error', async (err) => {
|
|
290
|
+
console.warn('[RabbitMQClient] Consumer channel error:', err.message);
|
|
291
|
+
await this._ensureConsumerChannel();
|
|
292
|
+
this.emit('error', err);
|
|
293
|
+
});
|
|
294
|
+
this._consumerChannel.on('close', async () => {
|
|
295
|
+
console.warn('[RabbitMQClient] Consumer channel closed - will auto-recreate and re-register consumers');
|
|
296
|
+
this._consumerChannel = null;
|
|
297
|
+
await this._ensureConsumerChannel();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Re-register all active consumers
|
|
301
|
+
if (this._activeConsumers.size > 0) {
|
|
302
|
+
console.log(`[RabbitMQClient] [mq-client-core] Re-registering ${this._activeConsumers.size} consumers...`);
|
|
303
|
+
for (const [queue, consumerInfo] of this._activeConsumers.entries()) {
|
|
304
|
+
try {
|
|
305
|
+
// Re-assert queue with correct options before consuming (may have been deleted)
|
|
306
|
+
if (consumerInfo.options.queueOptions) {
|
|
307
|
+
try {
|
|
308
|
+
await this._ensureQueueChannel();
|
|
309
|
+
await this._queueChannel.assertQueue(queue, consumerInfo.options.queueOptions);
|
|
310
|
+
} catch (assertErr) {
|
|
311
|
+
if (assertErr.code === 404) {
|
|
312
|
+
console.warn(`[RabbitMQClient] [mq-client-core] Queue ${queue} does not exist, skipping consumer re-registration`);
|
|
313
|
+
this._activeConsumers.delete(queue);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
// 406 error means queue exists with different args - skip this consumer
|
|
317
|
+
if (assertErr.code === 406) {
|
|
318
|
+
console.warn(`[RabbitMQClient] [mq-client-core] Queue ${queue} exists with different args, skipping consumer re-registration`);
|
|
319
|
+
this._activeConsumers.delete(queue);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
throw assertErr;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const consumeResult = await this._consumerChannel.consume(
|
|
327
|
+
queue,
|
|
328
|
+
consumerInfo.handler,
|
|
329
|
+
{ noAck: consumerInfo.options.noAck }
|
|
330
|
+
);
|
|
331
|
+
consumerInfo.consumerTag = consumeResult.consumerTag;
|
|
332
|
+
console.log(`[RabbitMQClient] [mq-client-core] ✓ Re-registered consumer for queue: ${queue} (consumerTag: ${consumeResult.consumerTag})`);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
console.error(`[RabbitMQClient] [mq-client-core] Failed to re-register consumer for queue ${queue}:`, err.message);
|
|
335
|
+
// Remove failed consumer from tracking
|
|
336
|
+
this._activeConsumers.delete(queue);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log('[RabbitMQClient] [mq-client-core] ✓ Consumer channel recreated');
|
|
342
|
+
} catch (err) {
|
|
343
|
+
this._consumerChannel = null;
|
|
344
|
+
throw new Error(`Failed to recreate consumer channel: ${err.message}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
139
348
|
/**
|
|
140
349
|
* Disconnects: closes channel and connection.
|
|
141
350
|
* @returns {Promise<void>}
|
|
142
351
|
*/
|
|
143
352
|
async disconnect() {
|
|
353
|
+
// Clear active consumers tracking
|
|
354
|
+
this._activeConsumers.clear();
|
|
355
|
+
|
|
144
356
|
try {
|
|
145
357
|
if (this._consumerChannel) {
|
|
146
358
|
await this._consumerChannel.close();
|
|
@@ -185,10 +397,11 @@ class RabbitMQClient extends EventEmitter {
|
|
|
185
397
|
* @throws {Error} If publish fails or channel is not available.
|
|
186
398
|
*/
|
|
187
399
|
async publish(queue, buffer, options = {}) {
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
400
|
+
// Ensure publisher channel exists and is open (auto-recreates if closed)
|
|
401
|
+
await this._ensurePublisherChannel();
|
|
402
|
+
|
|
403
|
+
// Log publish attempt for debugging
|
|
404
|
+
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Attempting to publish to queue "${queue}"`);
|
|
192
405
|
|
|
193
406
|
const exchange = this._config.exchange || '';
|
|
194
407
|
const routingKey = options.routingKey || queue;
|
|
@@ -203,21 +416,8 @@ class RabbitMQClient extends EventEmitter {
|
|
|
203
416
|
// If queue doesn't exist (404), we should NOT auto-create it - infrastructure queues must be created explicitly
|
|
204
417
|
// This prevents creating queues with wrong arguments (no TTL) which causes 406 errors later
|
|
205
418
|
try {
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
// Recreate queueChannel if closed
|
|
209
|
-
if (this._connection && !this._connection.closed) {
|
|
210
|
-
this._queueChannel = await this._connection.createChannel();
|
|
211
|
-
this._queueChannel.on('error', (err) => {
|
|
212
|
-
console.warn('[RabbitMQClient] Queue channel error:', err.message);
|
|
213
|
-
});
|
|
214
|
-
this._queueChannel.on('close', () => {
|
|
215
|
-
console.warn('[RabbitMQClient] Queue channel closed');
|
|
216
|
-
});
|
|
217
|
-
} else {
|
|
218
|
-
throw new Error('Cannot publish: connection is closed');
|
|
219
|
-
}
|
|
220
|
-
}
|
|
419
|
+
// Ensure queue channel exists and is open (auto-recreates if closed)
|
|
420
|
+
await this._ensureQueueChannel();
|
|
221
421
|
await this._queueChannel.checkQueue(queue);
|
|
222
422
|
// Queue exists - proceed to publish
|
|
223
423
|
} catch (checkErr) {
|
|
@@ -252,30 +452,56 @@ class RabbitMQClient extends EventEmitter {
|
|
|
252
452
|
}
|
|
253
453
|
}
|
|
254
454
|
// Publish to queue using ConfirmChannel (for publisher confirms)
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
455
|
+
// Channel is guaranteed to be open (ensured above)
|
|
456
|
+
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Sending message to queue "${queue}" (size: ${buffer.length} bytes)`);
|
|
457
|
+
|
|
458
|
+
// Use callback-based confirmation - kanály jsou spolehlivé, takže callback vždy dorazí
|
|
459
|
+
const confirmPromise = new Promise((resolve, reject) => {
|
|
460
|
+
// Send message with callback
|
|
461
|
+
this._channel.sendToQueue(queue, buffer, { persistent, headers }, (err, ok) => {
|
|
462
|
+
if (err) {
|
|
463
|
+
console.error(`[RabbitMQClient] [mq-client-core] [PUBLISH] Send callback error for queue "${queue}":`, err.message);
|
|
464
|
+
reject(err);
|
|
465
|
+
} else {
|
|
466
|
+
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] ✓ Message confirmed for queue "${queue}"`);
|
|
467
|
+
resolve();
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
await confirmPromise;
|
|
260
473
|
} else {
|
|
261
474
|
// If exchange is specified, assert exchange and publish to it
|
|
262
|
-
|
|
263
|
-
throw new Error('Cannot publish: channel closed during operation');
|
|
264
|
-
}
|
|
475
|
+
// Channel is guaranteed to be open (ensured above)
|
|
265
476
|
await this._channel.assertExchange(exchange, 'direct', { durable: this._config.durable });
|
|
266
|
-
|
|
477
|
+
|
|
478
|
+
// Use callback-based confirmation - kanály jsou spolehlivé
|
|
479
|
+
const confirmPromise = new Promise((resolve, reject) => {
|
|
480
|
+
this._channel.publish(exchange, routingKey, buffer, { persistent, headers }, (err, ok) => {
|
|
481
|
+
if (err) {
|
|
482
|
+
console.error(`[RabbitMQClient] [mq-client-core] [PUBLISH] Exchange publish callback error:`, err.message);
|
|
483
|
+
reject(err);
|
|
484
|
+
} else {
|
|
485
|
+
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] ✓ Exchange publish confirmed`);
|
|
486
|
+
resolve();
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
await confirmPromise;
|
|
267
492
|
}
|
|
268
|
-
// Wait for confirmation (with timeout to prevent hanging)
|
|
269
|
-
const confirmPromise = this._channel.waitForConfirms();
|
|
270
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
271
|
-
setTimeout(() => reject(new Error('Publisher confirm timeout after 5 seconds')), 5000);
|
|
272
|
-
});
|
|
273
|
-
await Promise.race([confirmPromise, timeoutPromise]);
|
|
274
493
|
} catch (err) {
|
|
275
|
-
// If channel was closed,
|
|
494
|
+
// If channel was closed, try to recreate and retry once
|
|
276
495
|
if (err.message && (err.message.includes('Channel closed') || err.message.includes('channel is closed') || this._channel?.closed)) {
|
|
277
|
-
console.warn('[RabbitMQClient] [mq-client-core] [PUBLISH] Channel closed during publish,
|
|
278
|
-
|
|
496
|
+
console.warn('[RabbitMQClient] [mq-client-core] [PUBLISH] Channel closed during publish, recreating and retrying...');
|
|
497
|
+
try {
|
|
498
|
+
await this._ensurePublisherChannel();
|
|
499
|
+
// Retry publish once
|
|
500
|
+
return await this.publish(queue, buffer, options);
|
|
501
|
+
} catch (retryErr) {
|
|
502
|
+
console.error('[RabbitMQClient] [mq-client-core] [PUBLISH] Retry failed:', retryErr.message);
|
|
503
|
+
throw retryErr;
|
|
504
|
+
}
|
|
279
505
|
}
|
|
280
506
|
this.emit('error', err);
|
|
281
507
|
throw err;
|
|
@@ -291,41 +517,8 @@ class RabbitMQClient extends EventEmitter {
|
|
|
291
517
|
* @throws {Error} If consume setup fails or channel is not available.
|
|
292
518
|
*/
|
|
293
519
|
async consume(queue, onMessage, options = {}) {
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
if (!this._consumerChannel) {
|
|
297
|
-
// Recreate consumer channel if closed
|
|
298
|
-
if (this._connection && !this._connection.closed) {
|
|
299
|
-
this._consumerChannel = await this._connection.createChannel();
|
|
300
|
-
this._consumerChannel.on('error', (err) => {
|
|
301
|
-
console.warn('[RabbitMQClient] Consumer channel error:', err.message);
|
|
302
|
-
this.emit('error', err);
|
|
303
|
-
});
|
|
304
|
-
this._consumerChannel.on('close', () => {
|
|
305
|
-
console.warn('[RabbitMQClient] Consumer channel closed');
|
|
306
|
-
this.emit('error', new Error('RabbitMQ consumer channel closed unexpectedly'));
|
|
307
|
-
});
|
|
308
|
-
} else {
|
|
309
|
-
throw new Error('Cannot consume: consumer channel is not initialized and connection is closed');
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (this._consumerChannel.closed) {
|
|
314
|
-
// Recreate consumer channel if closed
|
|
315
|
-
if (this._connection && !this._connection.closed) {
|
|
316
|
-
this._consumerChannel = await this._connection.createChannel();
|
|
317
|
-
this._consumerChannel.on('error', (err) => {
|
|
318
|
-
console.warn('[RabbitMQClient] Consumer channel error:', err.message);
|
|
319
|
-
this.emit('error', err);
|
|
320
|
-
});
|
|
321
|
-
this._consumerChannel.on('close', () => {
|
|
322
|
-
console.warn('[RabbitMQClient] Consumer channel closed');
|
|
323
|
-
this.emit('error', new Error('RabbitMQ consumer channel closed unexpectedly'));
|
|
324
|
-
});
|
|
325
|
-
} else {
|
|
326
|
-
throw new Error('Cannot consume: consumer channel is closed and connection is closed');
|
|
327
|
-
}
|
|
328
|
-
}
|
|
520
|
+
// Ensure consumer channel exists and is open (auto-recreates if closed and re-registers consumers)
|
|
521
|
+
await this._ensureConsumerChannel();
|
|
329
522
|
|
|
330
523
|
const durable = options.durable !== undefined ? options.durable : this._config.durable;
|
|
331
524
|
const prefetch = options.prefetch !== undefined ? options.prefetch : this._config.prefetch;
|
|
@@ -435,25 +628,36 @@ class RabbitMQClient extends EventEmitter {
|
|
|
435
628
|
|
|
436
629
|
// Use dedicated consumer channel for consume operations
|
|
437
630
|
// This prevents conflicts with ConfirmChannel used for publish operations
|
|
631
|
+
const messageHandler = async (msg) => {
|
|
632
|
+
if (msg === null) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
await onMessage(msg);
|
|
637
|
+
if (!noAck) {
|
|
638
|
+
this._consumerChannel.ack(msg);
|
|
639
|
+
}
|
|
640
|
+
} catch (handlerErr) {
|
|
641
|
+
// Negative acknowledge and requeue by default
|
|
642
|
+
this._consumerChannel.nack(msg, false, true);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
438
646
|
const consumeResult = await this._consumerChannel.consume(
|
|
439
647
|
queue,
|
|
440
|
-
|
|
441
|
-
if (msg === null) {
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
try {
|
|
445
|
-
await onMessage(msg);
|
|
446
|
-
if (!noAck) {
|
|
447
|
-
this._consumerChannel.ack(msg);
|
|
448
|
-
}
|
|
449
|
-
} catch (handlerErr) {
|
|
450
|
-
// Negative acknowledge and requeue by default
|
|
451
|
-
this._consumerChannel.nack(msg, false, true);
|
|
452
|
-
}
|
|
453
|
-
},
|
|
648
|
+
messageHandler,
|
|
454
649
|
{ noAck }
|
|
455
650
|
);
|
|
456
651
|
|
|
652
|
+
// Track consumer for auto re-registration if channel closes
|
|
653
|
+
this._activeConsumers.set(queue, {
|
|
654
|
+
handler: messageHandler,
|
|
655
|
+
options: { noAck, prefetch, durable, queueOptions },
|
|
656
|
+
consumerTag: consumeResult.consumerTag
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] ✓ Consumer registered for queue "${queue}" (consumerTag: ${consumeResult.consumerTag})`);
|
|
660
|
+
|
|
457
661
|
// Return consumer tag for cancellation
|
|
458
662
|
return consumeResult.consumerTag;
|
|
459
663
|
} catch (err) {
|