@autofleet/rabbit 3.2.0 → 3.2.2-2.beta-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/dist/index.js CHANGED
@@ -10,6 +10,7 @@ const moment_1 = __importDefault(require("moment"));
10
10
  const redis_lock_1 = __importDefault(require("redis-lock"));
11
11
  const amqp_connection_manager_1 = require("amqp-connection-manager");
12
12
  const zehut_1 = require("@autofleet/zehut");
13
+ const node_crypto_1 = require("node:crypto");
13
14
  const logger_1 = __importDefault(require("./logger"));
14
15
  const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
15
16
  const redis_1 = __importDefault(require("./lib/redis"));
@@ -17,10 +18,43 @@ const utils_1 = require("./lib/utils");
17
18
  const consts_1 = require("./lib/consts");
18
19
  const types_1 = require("./lib/types");
19
20
  // const debug = nodeDebug('af-rabbitmq')
20
- const debug = logger_1.default.info;
21
+ const debug = logger_1.default.debug.bind(logger_1.default);
21
22
  const PUBLISH_TIMEOUT = 1000 * 10;
22
23
  const HEARTBEAT = '60';
23
24
  class RabbitMq {
25
+ static parseMsg(msg) {
26
+ let { content } = msg;
27
+ content = content.toString();
28
+ try {
29
+ content = JSON.parse(content);
30
+ }
31
+ catch (e) { }
32
+ return {
33
+ ...msg,
34
+ content,
35
+ };
36
+ }
37
+ static validateName(type, name) {
38
+ if (!name || name === '') {
39
+ throw new rabbitError_1.default(`error while using ${type} with no name`);
40
+ }
41
+ }
42
+ static getPublishOptions(customHeaders = {}) {
43
+ const trace = (0, zehut_1.getCurrentPayload)();
44
+ const user = trace?.context?.get(consts_1.USER_OBJECT);
45
+ const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
46
+ const outbreakTrace = zehut_1.outbreak.getCurrentContext();
47
+ return {
48
+ timestamp: (0, moment_1.default)().unix(),
49
+ timeout: PUBLISH_TIMEOUT,
50
+ headers: {
51
+ creationTimestamp: (0, moment_1.default)().valueOf(),
52
+ ...customHeaders,
53
+ [consts_1.USER_TRACING_HEADER]: user?.id,
54
+ [consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
55
+ },
56
+ };
57
+ }
24
58
  constructor(options, redisConfig) {
25
59
  this.DISCONNECT_MSG = 'rabbit: connection disconnect';
26
60
  this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
@@ -39,6 +73,7 @@ class RabbitMq {
39
73
  };
40
74
  this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
41
75
  if (msg) {
76
+ debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
42
77
  await channel.ack(msg);
43
78
  const { properties: { headers } } = msg;
44
79
  const timestamp = headers?.creationTimestamp;
@@ -71,28 +106,30 @@ class RabbitMq {
71
106
  : 1,
72
107
  });
73
108
  }
109
+ debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
74
110
  await channel.ack(msg);
75
111
  }
76
112
  else {
77
113
  logger_1.default.error('no channel or msg', {
78
- channel: channel ? channel.name : '',
79
114
  msg,
80
115
  });
81
116
  }
82
117
  };
83
- this.userName = process.env.RABBITMQ_USERNAME || 'guest';
84
- this.password = process.env.RABBITMQ_PASSWORD || 'guest';
85
- this.host = options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
86
118
  this.em = new events_1.EventEmitter();
87
119
  this.channel = null;
88
- this.connection = null;
89
- this.creatingConnection = false;
120
+ this.publishChannelSetupPromise = null;
121
+ this.connectionsMap = {
122
+ [types_1.ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
123
+ [types_1.ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
124
+ };
90
125
  this.exchanges = {};
91
126
  this.queues = {};
127
+ this.queueSetupPromises = {};
128
+ this.consumers = [];
92
129
  this.options = options;
93
- this.redisClient = redisConfig && redis_1.default(redisConfig);
130
+ this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
94
131
  if (this.redisClient) {
95
- this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
132
+ this.redisLock = (0, util_1.promisify)((0, redis_lock_1.default)(this.redisClient));
96
133
  }
97
134
  this.consumersTags = [];
98
135
  logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
@@ -105,68 +142,52 @@ class RabbitMq {
105
142
  });
106
143
  }
107
144
  }
108
- static parseMsg(msg) {
109
- let { content } = msg;
110
- content = content.toString();
111
- try {
112
- content = JSON.parse(content);
113
- }
114
- catch (e) { }
115
- return {
116
- ...msg,
117
- content,
118
- };
119
- }
120
- static validateName(type, name) {
121
- if (!name || name === '') {
122
- throw new rabbitError_1.default(`error while using ${type} with no name`);
123
- }
124
- }
125
- static getPublishOptions(customHeaders = {}) {
126
- const trace = zehut_1.getCurrentPayload();
127
- const user = trace?.context?.get(consts_1.USER_OBJECT);
128
- const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
129
- const outbreakTrace = zehut_1.outbreak.getCurrentContext();
130
- return {
131
- timestamp: moment_1.default().unix(),
132
- timeout: PUBLISH_TIMEOUT,
133
- headers: {
134
- creationTimestamp: moment_1.default().valueOf(),
135
- ...customHeaders,
136
- [consts_1.USER_TRACING_HEADER]: user?.id,
137
- [consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
138
- },
139
- };
140
- }
141
- async getConnection() {
145
+ async getConnection(connectionPurpose) {
142
146
  return new Promise(async (resolve, reject) => {
147
+ let { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
143
148
  if (this.blockReconnect) {
144
149
  debug('rabbit: block reconnect');
145
150
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
146
151
  // @ts-ignore
147
152
  return resolve();
148
153
  }
149
- if (this.connection !== null) {
150
- if (this.options?.disableReconnect || this.connection?.isConnected()) {
154
+ if (connection !== null) {
155
+ if (this.options?.disableReconnect || connection?.isConnected()) {
151
156
  debug('rabbit: connection - is connected');
152
157
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
153
158
  // @ts-ignore
154
- return resolve(this.connection);
159
+ return resolve(connection);
155
160
  }
156
161
  debug('rabbit: connection - reconnecting');
157
162
  }
158
- if (this.creatingConnection) {
163
+ if (creatingConnection) {
159
164
  debug('rabbit: creating connection emi');
160
165
  this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
161
166
  this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
162
167
  return;
163
168
  }
164
- this.creatingConnection = true;
169
+ this.connectionsMap[connectionPurpose].creatingConnection = true;
165
170
  let isResolved = false;
166
- debug('rabbit: creating connection', { host: this.host, userName: this.userName, HEARTBEAT });
167
- const connection = await amqp_connection_manager_1.connect([`amqp://${this.userName}:${this.password}@${this.host}?heartbeat=${HEARTBEAT}}`]);
168
- this.connection = connection;
169
- this.connection.on('error', (err) => {
171
+ // It is import to use it as a function and not as a variable
172
+ // because of k8s changes the env variables
173
+ // and we want to use the new values
174
+ const findServers = () => {
175
+ const userName = process.env.RABBITMQ_USERNAME || 'guest';
176
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
177
+ const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
178
+ debug('rabbit: creating connection', { host, userName, HEARTBEAT });
179
+ return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
180
+ };
181
+ const defaultUrls = findServers();
182
+ const newConnection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
183
+ findServers,
184
+ });
185
+ if (!newConnection) {
186
+ logger_1.default.error('rabbit: couldnt create a connection');
187
+ return resolve(connection);
188
+ }
189
+ this.connectionsMap[connectionPurpose].connection = newConnection;
190
+ newConnection.on('error', (err) => {
170
191
  logger_1.default.error('rabbit: connection error', { err });
171
192
  if (!isResolved) {
172
193
  isResolved = true;
@@ -174,7 +195,8 @@ class RabbitMq {
174
195
  this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
175
196
  }
176
197
  });
177
- this.connection.on('connectFailed', (err) => {
198
+ newConnection.on('connectFailed', (err) => {
199
+ this.consumersTags = [];
178
200
  logger_1.default.error('rabbit: connection connectFailed', { err });
179
201
  if (!isResolved) {
180
202
  isResolved = true;
@@ -182,10 +204,9 @@ class RabbitMq {
182
204
  this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
183
205
  }
184
206
  });
185
- this.connection.on('disconnect', ({ err }) => {
207
+ newConnection.on('disconnect', ({ err }) => {
208
+ this.consumersTags = [];
186
209
  debug('rabbit: connection closed');
187
- this.exchanges = {};
188
- this.queues = {};
189
210
  if (this.options?.disableReconnect) {
190
211
  logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
191
212
  this.blockReconnect = true;
@@ -194,95 +215,99 @@ class RabbitMq {
194
215
  logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
195
216
  }
196
217
  });
197
- this.connection.once('connect', async () => {
218
+ newConnection.once('connect', async () => {
198
219
  debug('rabbit: connection established');
199
- await this.loadConsumers();
200
- this.creatingConnection = false;
201
- this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
220
+ this.connectionsMap[connectionPurpose].creatingConnection = false;
221
+ this.em.emit(consts_1.CONNECTION_CREATED_CONST, newConnection);
202
222
  isResolved = true;
203
- resolve(connection);
223
+ resolve(newConnection);
204
224
  });
205
225
  });
206
226
  }
207
- async getNewChannel({ name = utils_1.rand().toString(), onClose = null } = {}) {
208
- return new Promise(async (resolve, reject) => {
209
- const connection = await this.getConnection();
210
- const channel = connection.createChannel({});
211
- let isResolved = false;
212
- channel.on('error', (err) => {
213
- logger_1.default.error(`rabbit: channel ${name} error`, { err });
214
- if (!isResolved) {
215
- isResolved = true;
216
- reject(err);
227
+ async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connectionPurpose = types_1.ConnectionPurpose.Consume, }) {
228
+ let connection;
229
+ try {
230
+ connection = await this.getConnection(connectionPurpose);
231
+ }
232
+ catch (e) {
233
+ logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
234
+ throw e;
235
+ }
236
+ if (!connection) {
237
+ throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
238
+ }
239
+ const channel = connection.createChannel({ ...options });
240
+ (0, events_1.once)(channel, 'close').then((args) => {
241
+ logger_1.default.error(`rabbit: channel ${name} closed`);
242
+ onClose?.(args);
243
+ });
244
+ try {
245
+ await (0, events_1.once)(channel, 'connect');
246
+ debug(`rabbit: channel ${name} CONNECTED`);
247
+ return channel;
248
+ }
249
+ catch (err) {
250
+ logger_1.default.error(`rabbit: channel error ${name} error`, { err });
251
+ throw err;
252
+ }
253
+ }
254
+ async assertChannel({ force = false, connectionPurpose = types_1.ConnectionPurpose.Consume }) {
255
+ if (!this.publishChannelSetupPromise) {
256
+ this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
257
+ if (this.channel && !force) {
258
+ return resolve(this.channel);
217
259
  }
218
- });
219
- channel.on('close', (...args) => {
220
- logger_1.default.error(`rabbit: channel ${name} closed`, { args });
221
- if (onClose) {
222
- onClose(args);
260
+ try {
261
+ const channel = await this.getNewChannel({ connectionPurpose });
262
+ channel.on('error', (err) => {
263
+ logger_1.default.error('rabbit: channel error', { err });
264
+ });
265
+ this.channel = channel;
266
+ resolve(channel);
267
+ }
268
+ catch (e) {
269
+ reject(e);
223
270
  }
224
271
  });
225
- channel.once('connect', () => {
226
- debug(`rabbit: channel ${name} CONNECTED`);
227
- isResolved = true;
228
- resolve(channel);
229
- });
230
- });
231
- }
232
- async assertChannel({ force = false } = {}) {
233
- return new Promise(async (resolve, reject) => {
234
- if (this.channel && !force) {
235
- return resolve(this.channel);
236
- }
237
- try {
238
- const channel = await this.getNewChannel({});
239
- channel.on('error', (err) => {
240
- logger_1.default.error('rabbit: channel error', { err });
241
- });
242
- this.channel = channel;
243
- resolve(channel);
244
- }
245
- catch (e) {
246
- reject(e);
247
- }
248
- });
272
+ }
273
+ return this.publishChannelSetupPromise;
249
274
  }
250
275
  async assertExchange(exchangeName, options) {
251
- const channel = await this.assertChannel();
276
+ const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
252
277
  if (this.exchanges[exchangeName]) {
253
278
  return this.exchanges[exchangeName];
254
279
  }
255
- const exchange = await utils_1.assertExchangeFanout(channel, exchangeName);
280
+ const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
256
281
  this.exchanges[exchangeName] = exchange;
257
282
  return exchange;
258
283
  }
259
- async getQueueLength(queue) {
284
+ async getQueueLength(queue, connectionPurpose = types_1.ConnectionPurpose.Consume) {
260
285
  RabbitMq.validateName('queue', queue);
261
- const channel = await this.assertChannel();
262
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
263
- return channel.checkQueue(queue);
286
+ let { connection } = this.connectionsMap[connectionPurpose];
287
+ const { channel } = this;
288
+ if (!channel) {
289
+ throw new Error('channel is not defined');
290
+ }
291
+ debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
292
+ return channel?.checkQueue(queue);
264
293
  }
265
- async deleteQueue(queue) {
294
+ async deleteQueue(queue, connectionPurpose) {
266
295
  RabbitMq.validateName('queue', queue);
267
- const channel = await this.assertChannel();
296
+ const channel = await this.assertChannel({ connectionPurpose });
268
297
  logger_1.default.info('rabbit: deleting queue', { queue });
269
298
  const deleteQueueRes = await channel.deleteQueue(queue);
270
299
  debug('queue deleted', deleteQueueRes);
271
300
  return deleteQueueRes;
272
301
  }
273
- async bindQueue(queue, exchange) {
274
- const channel = await this.assertChannel();
302
+ async bindQueue(queue, exchange, connectionPurpose) {
303
+ const channel = await this.assertChannel({ connectionPurpose });
275
304
  await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
276
305
  return channel.bindQueue(queue, exchange, '');
277
306
  }
278
- async assertQueue(queueName, options) {
279
- let queue = null;
280
- RabbitMq.validateName('queue', queueName);
281
- if (this.queues[queueName]) {
282
- return this.queues[queueName];
283
- }
307
+ async setupQueue(queueName, connectionPurpose, options) {
308
+ let queue;
284
309
  try {
285
- const channel = await this.assertChannel();
310
+ const channel = await this.assertChannel({ connectionPurpose });
286
311
  debug('assertQueue->channel.addSetup', { queueName });
287
312
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
288
313
  debug('assertQueue->channel.assertQueue', { queueName });
@@ -292,11 +317,11 @@ class RabbitMq {
292
317
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
293
318
  if (!this.options?.dontRetryAssert) {
294
319
  debug('retrying assertQueue', { queueName });
295
- const channel = await this.assertChannel({ force: true });
296
- await this.deleteQueue(queueName);
297
- debug('1assertQueue->channel.addSetup', { queueName });
320
+ const channel = await this.assertChannel({ force: true, connectionPurpose });
321
+ await this.deleteQueue(queueName, connectionPurpose);
322
+ debug('retrying assertQueue->channel.addSetup', { queueName });
298
323
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
299
- debug('1assertQueue->channel.assertQueue', { queueName });
324
+ debug('retrying assertQueue->channel.assertQueue', { queueName });
300
325
  queue = await channel.assertQueue(queueName, options);
301
326
  }
302
327
  else {
@@ -306,25 +331,31 @@ class RabbitMq {
306
331
  this.queues[queueName] = queueName;
307
332
  return queue;
308
333
  }
309
- saveConsumer(queue, callback, options) {
310
- this.consumers.push({
311
- queue,
312
- callback,
313
- options,
314
- });
334
+ async assertQueue(queueName, connectionPurpose, options) {
335
+ RabbitMq.validateName('queue', queueName);
336
+ if (this.queues[queueName]) {
337
+ delete this.queueSetupPromises[queueName];
338
+ return this.queues[queueName];
339
+ }
340
+ if (this.queueSetupPromises[queueName]) {
341
+ return this.queueSetupPromises[queueName];
342
+ }
343
+ this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
344
+ return this.queueSetupPromises[queueName];
315
345
  }
316
- async loadConsumers() {
317
- debug('rabbit: loading consumers', { consumers: this.consumers.length });
318
- if (this.consumers.length > 0) {
319
- await Promise.all(this.consumers.map((consumer) => this
320
- .consumeFromRabbit(consumer.queue, consumer.callback, consumer.options)));
346
+ saveConsumer(queue, callback, options) {
347
+ const isConsumerExist = this.consumers.some((consumer) => consumer.queue === queue);
348
+ if (!isConsumerExist) {
349
+ logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
350
+ this.consumers.push({
351
+ queue,
352
+ callback,
353
+ options,
354
+ });
321
355
  }
322
356
  }
323
357
  async consume(queue, callback, options) {
324
- if (this.connection && !this.creatingConnection) {
325
- this.consumeFromRabbit(queue, callback, options);
326
- }
327
- return this.saveConsumer(queue, callback, options);
358
+ await this.consumeFromRabbit(queue, callback, options);
328
359
  }
329
360
  async lockRedisIfNeeded(msg, options) {
330
361
  const { properties: { headers } } = msg;
@@ -343,6 +374,8 @@ class RabbitMq {
343
374
  async consumeFromRabbit(queue, callback, options) {
344
375
  const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
345
376
  RabbitMq.validateName('queue', queue);
377
+ this.saveConsumer(queue, callback, options);
378
+ const uniqueId = (0, node_crypto_1.randomUUID)();
346
379
  const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
347
380
  if (useConsumeWithLock) {
348
381
  if (!this.redisLock) {
@@ -350,11 +383,11 @@ class RabbitMq {
350
383
  }
351
384
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
352
385
  }
353
- const channel = await this.getNewChannel({ name: `consume-queue-${queue}` });
354
- return channel.addSetup(async (c) => {
355
- await c.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
356
- await c.prefetch(limit, true);
357
- const { consumerTag } = await c.consume(queue, async (msg) => {
386
+ const channel = await this.getNewChannel({ connectionPurpose: types_1.ConnectionPurpose.Consume });
387
+ return channel.addSetup(async (confirmChannel) => {
388
+ await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
389
+ await confirmChannel.prefetch(limit, true);
390
+ const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
358
391
  if (!msg) {
359
392
  return null;
360
393
  }
@@ -362,7 +395,7 @@ class RabbitMq {
362
395
  const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
363
396
  const parsedMessage = RabbitMq.parseMsg(msg);
364
397
  const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
365
- const trace = zehut_1.newTrace(zehut_1.traceTypes.RABBIT);
398
+ const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
366
399
  // setting also outbreak trace as part of legacy code
367
400
  const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
368
401
  // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
@@ -370,13 +403,13 @@ class RabbitMq {
370
403
  if (userId && enableRabbitTrace) {
371
404
  try {
372
405
  await Promise.all([
373
- zehut_1.createOrSetRabbitTrace(trace, userId),
374
- zehut_1.createOrSetRabbitTrace(outbreakTrace, userId),
406
+ (0, zehut_1.createOrSetRabbitTrace)(trace, userId),
407
+ (0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
375
408
  ]);
376
409
  }
377
410
  catch (e) {
378
411
  logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
379
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
412
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
380
413
  }
381
414
  }
382
415
  if (traceId) {
@@ -389,13 +422,30 @@ class RabbitMq {
389
422
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
390
423
  if (!shouldConsume) {
391
424
  await this.unlockRedisIfNeeded(releaseLock);
392
- return this.ack(channel, msg)(msg);
425
+ return this.ack(confirmChannel, msg)(msg);
393
426
  }
427
+ let messageAcked = false;
428
+ // setting the localAck function to be used in the callback
429
+ const localAck = async () => {
430
+ if (messageAcked) {
431
+ return;
432
+ }
433
+ messageAcked = true;
434
+ return this.ack(confirmChannel, msg, true, releaseLock)(msg);
435
+ };
436
+ const localNack = async (_, nackOptions = {}) => {
437
+ if (messageAcked) {
438
+ return;
439
+ }
440
+ debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
441
+ messageAcked = true;
442
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
443
+ };
394
444
  try {
395
- await callback(parsedMessage, this.ack(channel, msg, true, releaseLock), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock));
445
+ await callback(parsedMessage, localAck, localNack);
396
446
  }
397
447
  catch (e) {
398
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
448
+ await localNack(msg);
399
449
  }
400
450
  }, types_1.CONSUMER_DEFAULT_OPTIONS);
401
451
  if (!consumerTag) {
@@ -403,7 +453,7 @@ class RabbitMq {
403
453
  }
404
454
  else {
405
455
  logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
406
- this.consumersTags.push([c, consumerTag]);
456
+ this.consumersTags.push([confirmChannel, consumerTag]);
407
457
  }
408
458
  });
409
459
  }
@@ -412,9 +462,10 @@ class RabbitMq {
412
462
  RabbitMq.validateName('exchange', exchange);
413
463
  RabbitMq.validateName('queue', queue);
414
464
  const { limit, deadMessageTtl } = optionsWithDefaults;
415
- const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
465
+ await this.saveConsumer(queue, callback, options);
466
+ const channel = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}`, connectionPurpose: types_1.ConnectionPurpose.Consume });
416
467
  return channel.addSetup(async (c) => {
417
- const assertExchange = await utils_1.assertExchangeFanout(c, exchange);
468
+ const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
418
469
  await c.assertQueue(queue);
419
470
  this.exchanges[exchange] = assertExchange;
420
471
  await c.prefetch(limit, true);
@@ -425,36 +476,64 @@ class RabbitMq {
425
476
  });
426
477
  }
427
478
  async publish(exchange, content, customHeaders) {
428
- return utils_1.wrapSetImmediate(async () => {
479
+ return (0, utils_1.wrapSetImmediate)(async () => {
429
480
  RabbitMq.validateName('exchange', exchange);
430
- const channel = await this.assertChannel();
431
- await this.assertExchange(exchange);
481
+ const channel = await this.assertChannel({ connectionPurpose: types_1.ConnectionPurpose.Publish });
482
+ await this.assertExchange(exchange, { connectionPurpose: types_1.ConnectionPurpose.Publish });
432
483
  await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
433
484
  });
434
485
  }
435
- async sendToQueue(queue, content, options, customHeaders, isBlocking) {
436
- const callback = async () => {
437
- try {
438
- RabbitMq.validateName('queue', queue);
439
- await this.assertChannel();
440
- await this.assertQueue(queue, options);
441
- const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
442
- debug(`rabbit: sending to queue ${queue}`, { res });
443
- return res;
444
- }
445
- catch (e) {
446
- logger_1.default.error(`rabbit: failed to send to queue ${queue}`, { e });
447
- throw e;
448
- }
449
- };
450
- if (isBlocking) {
451
- return callback();
486
+ async sendToQueue(queue, content, options, customHeaders) {
487
+ try {
488
+ await this.assertChannel({ connectionPurpose: types_1.ConnectionPurpose.Publish });
489
+ }
490
+ catch (e) {
491
+ logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
492
+ throw e;
493
+ }
494
+ try {
495
+ RabbitMq.validateName('queue', queue);
496
+ await this.assertQueue(queue, types_1.ConnectionPurpose.Publish, options);
497
+ }
498
+ catch (e) {
499
+ logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
500
+ throw e;
501
+ }
502
+ try {
503
+ const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
504
+ debug(`rabbit: sending to queue ${queue}`, { res });
505
+ return res;
506
+ }
507
+ catch (e) {
508
+ const isConnected = await this.isConnected(types_1.ConnectionPurpose.Publish);
509
+ logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
510
+ throw e;
452
511
  }
453
- return utils_1.wrapSetImmediate(callback);
454
512
  }
455
- async isConnected() {
456
- const connection = await this.getConnection();
457
- return connection.isConnected();
513
+ async isConnected(connectionPurpose) {
514
+ const connection = await this.getConnection(connectionPurpose);
515
+ if (!connection) {
516
+ logger_1.default.error('rabbit: isConnected - false');
517
+ return false;
518
+ }
519
+ const isConnected = connection.isConnected();
520
+ if (!isConnected) {
521
+ logger_1.default.error('rabbit: isConnected - false');
522
+ return false;
523
+ }
524
+ const channel = await this.assertChannel({ connectionPurpose });
525
+ try {
526
+ await Promise.all([
527
+ channel.waitForConnect(),
528
+ ...this.consumers.map((c) => channel.checkQueue(c.queue)),
529
+ ]);
530
+ }
531
+ catch (e) {
532
+ logger_1.default.error('rabbit: isConnected - false');
533
+ return false;
534
+ }
535
+ logger_1.default.info('rabbit: isConnected - true');
536
+ return true;
458
537
  }
459
538
  async gracefulShutdown(signal) {
460
539
  const tagsNumber = this.consumersTags.length;
@@ -1,4 +1,4 @@
1
- export declare type RedisConfig = {
1
+ export type RedisConfig = {
2
2
  host: string;
3
3
  port: number | undefined;
4
4
  prefix?: string;