@autofleet/rabbit 3.1.2 → 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,15 +18,44 @@ 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 USERNAME = process.env.RABBITMQ_USERNAME || 'guest';
22
- const PASSWORD = process.env.RABBITMQ_PASSWORD || 'guest';
23
- const HOST = process.env.RABBITMQ_SERVICE_HOST || 'localhost';
21
+ const debug = logger_1.default.debug.bind(logger_1.default);
22
+ const PUBLISH_TIMEOUT = 1000 * 10;
24
23
  const HEARTBEAT = '60';
25
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
+ }
26
58
  constructor(options, redisConfig) {
27
- // PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
28
- // PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
29
59
  this.DISCONNECT_MSG = 'rabbit: connection disconnect';
30
60
  this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
31
61
  this.consumers = [];
@@ -43,6 +73,7 @@ class RabbitMq {
43
73
  };
44
74
  this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
45
75
  if (msg) {
76
+ debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
46
77
  await channel.ack(msg);
47
78
  const { properties: { headers } } = msg;
48
79
  const timestamp = headers?.creationTimestamp;
@@ -75,25 +106,30 @@ class RabbitMq {
75
106
  : 1,
76
107
  });
77
108
  }
109
+ debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
78
110
  await channel.ack(msg);
79
111
  }
80
112
  else {
81
113
  logger_1.default.error('no channel or msg', {
82
- channel: channel ? channel.name : '',
83
114
  msg,
84
115
  });
85
116
  }
86
117
  };
87
118
  this.em = new events_1.EventEmitter();
88
119
  this.channel = null;
89
- this.connection = null;
90
- 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
+ };
91
125
  this.exchanges = {};
92
126
  this.queues = {};
127
+ this.queueSetupPromises = {};
128
+ this.consumers = [];
93
129
  this.options = options;
94
- this.redisClient = redisConfig && redis_1.default(redisConfig);
130
+ this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
95
131
  if (this.redisClient) {
96
- 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));
97
133
  }
98
134
  this.consumersTags = [];
99
135
  logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
@@ -106,67 +142,52 @@ class RabbitMq {
106
142
  });
107
143
  }
108
144
  }
109
- static parseMsg(msg) {
110
- let { content } = msg;
111
- content = content.toString();
112
- try {
113
- content = JSON.parse(content);
114
- }
115
- catch (e) { }
116
- return {
117
- ...msg,
118
- content,
119
- };
120
- }
121
- static validateName(type, name) {
122
- if (!name || name === '') {
123
- throw new rabbitError_1.default(`error while using ${type} with no name`);
124
- }
125
- }
126
- static getPublishOptions(customHeaders = {}) {
127
- const trace = zehut_1.getCurrentPayload();
128
- const user = trace?.context?.get(consts_1.USER_OBJECT);
129
- const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
130
- const outbreakTrace = zehut_1.outbreak.getCurrentContext();
131
- return {
132
- timestamp: moment_1.default().unix(),
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, USERNAME, HEARTBEAT });
167
- const connection = await amqp_connection_manager_1.connect([`amqp://${USERNAME}:${PASSWORD}@${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,12 +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
- this.connection = null;
190
- this.channel = null;
191
210
  if (this.options?.disableReconnect) {
192
211
  logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
193
212
  this.blockReconnect = true;
@@ -196,96 +215,99 @@ class RabbitMq {
196
215
  logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
197
216
  }
198
217
  });
199
- this.connection.once('connect', async () => {
218
+ newConnection.once('connect', async () => {
200
219
  debug('rabbit: connection established');
201
- await this.loadConsumers();
202
- this.creatingConnection = false;
203
- 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);
204
222
  isResolved = true;
205
- resolve(connection);
223
+ resolve(newConnection);
206
224
  });
207
225
  });
208
226
  }
209
- async getNewChannel({ name = utils_1.rand().toString(), onClose = null } = {}) {
210
- return new Promise(async (resolve, reject) => {
211
- const connection = await this.getConnection();
212
- const channel = connection.createChannel({});
213
- let isResolved = false;
214
- channel.on('error', (err) => {
215
- logger_1.default.error(`rabbit: channel ${name} error`, { err });
216
- if (!isResolved) {
217
- isResolved = true;
218
- 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);
219
259
  }
220
- });
221
- channel.on('close', (...args) => {
222
- logger_1.default.error(`rabbit: channel ${name} closed`, { args });
223
- if (onClose) {
224
- 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);
225
270
  }
226
271
  });
227
- channel.once('connect', () => {
228
- debug(`rabbit: channel ${name} CONNECTED`);
229
- isResolved = true;
230
- resolve(channel);
231
- });
232
- });
233
- }
234
- async assertChannel({ force = false } = {}) {
235
- return new Promise(async (resolve, reject) => {
236
- if (this.channel && !force) {
237
- return resolve(this.channel);
238
- }
239
- try {
240
- const channel = await this.getNewChannel({
241
- onClose: () => {
242
- this.channel = null;
243
- },
244
- });
245
- this.channel = channel;
246
- resolve(channel);
247
- }
248
- catch (e) {
249
- reject(e);
250
- }
251
- });
272
+ }
273
+ return this.publishChannelSetupPromise;
252
274
  }
253
275
  async assertExchange(exchangeName, options) {
254
- const channel = await this.assertChannel();
276
+ const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
255
277
  if (this.exchanges[exchangeName]) {
256
278
  return this.exchanges[exchangeName];
257
279
  }
258
- const exchange = await utils_1.assertExchangeFanout(channel, exchangeName);
280
+ const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
259
281
  this.exchanges[exchangeName] = exchange;
260
282
  return exchange;
261
283
  }
262
- async getQueueLength(queue) {
284
+ async getQueueLength(queue, connectionPurpose = types_1.ConnectionPurpose.Consume) {
263
285
  RabbitMq.validateName('queue', queue);
264
- const channel = await this.assertChannel();
265
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
266
- 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);
267
293
  }
268
- async deleteQueue(queue) {
294
+ async deleteQueue(queue, connectionPurpose) {
269
295
  RabbitMq.validateName('queue', queue);
270
- const channel = await this.assertChannel();
296
+ const channel = await this.assertChannel({ connectionPurpose });
271
297
  logger_1.default.info('rabbit: deleting queue', { queue });
272
298
  const deleteQueueRes = await channel.deleteQueue(queue);
273
299
  debug('queue deleted', deleteQueueRes);
274
300
  return deleteQueueRes;
275
301
  }
276
- async bindQueue(queue, exchange) {
277
- const channel = await this.assertChannel();
302
+ async bindQueue(queue, exchange, connectionPurpose) {
303
+ const channel = await this.assertChannel({ connectionPurpose });
278
304
  await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
279
305
  return channel.bindQueue(queue, exchange, '');
280
306
  }
281
- async assertQueue(queueName, options) {
282
- let queue = null;
283
- RabbitMq.validateName('queue', queueName);
284
- if (this.queues[queueName]) {
285
- return this.queues[queueName];
286
- }
307
+ async setupQueue(queueName, connectionPurpose, options) {
308
+ let queue;
287
309
  try {
288
- const channel = await this.assertChannel();
310
+ const channel = await this.assertChannel({ connectionPurpose });
289
311
  debug('assertQueue->channel.addSetup', { queueName });
290
312
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
291
313
  debug('assertQueue->channel.assertQueue', { queueName });
@@ -295,11 +317,11 @@ class RabbitMq {
295
317
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
296
318
  if (!this.options?.dontRetryAssert) {
297
319
  debug('retrying assertQueue', { queueName });
298
- const channel = await this.assertChannel({ force: true });
299
- await this.deleteQueue(queueName);
300
- 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 });
301
323
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
302
- debug('1assertQueue->channel.assertQueue', { queueName });
324
+ debug('retrying assertQueue->channel.assertQueue', { queueName });
303
325
  queue = await channel.assertQueue(queueName, options);
304
326
  }
305
327
  else {
@@ -309,25 +331,31 @@ class RabbitMq {
309
331
  this.queues[queueName] = queueName;
310
332
  return queue;
311
333
  }
312
- saveConsumer(queue, callback, options) {
313
- this.consumers.push({
314
- queue,
315
- callback,
316
- options,
317
- });
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];
318
345
  }
319
- async loadConsumers() {
320
- debug('rabbit: loading consumers', { consumers: this.consumers.length });
321
- if (this.consumers.length > 0) {
322
- await Promise.all(this.consumers.map((consumer) => this
323
- .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
+ });
324
355
  }
325
356
  }
326
357
  async consume(queue, callback, options) {
327
- if (this.connection && !this.creatingConnection) {
328
- this.consumeFromRabbit(queue, callback, options);
329
- }
330
- return this.saveConsumer(queue, callback, options);
358
+ await this.consumeFromRabbit(queue, callback, options);
331
359
  }
332
360
  async lockRedisIfNeeded(msg, options) {
333
361
  const { properties: { headers } } = msg;
@@ -346,6 +374,8 @@ class RabbitMq {
346
374
  async consumeFromRabbit(queue, callback, options) {
347
375
  const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
348
376
  RabbitMq.validateName('queue', queue);
377
+ this.saveConsumer(queue, callback, options);
378
+ const uniqueId = (0, node_crypto_1.randomUUID)();
349
379
  const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
350
380
  if (useConsumeWithLock) {
351
381
  if (!this.redisLock) {
@@ -353,11 +383,11 @@ class RabbitMq {
353
383
  }
354
384
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
355
385
  }
356
- const channel = await this.getNewChannel({ name: `consume-queue-${queue}` });
357
- return channel.addSetup(async (c) => {
358
- await c.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
359
- await c.prefetch(limit, true);
360
- 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) => {
361
391
  if (!msg) {
362
392
  return null;
363
393
  }
@@ -365,7 +395,7 @@ class RabbitMq {
365
395
  const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
366
396
  const parsedMessage = RabbitMq.parseMsg(msg);
367
397
  const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
368
- const trace = zehut_1.newTrace(zehut_1.traceTypes.RABBIT);
398
+ const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
369
399
  // setting also outbreak trace as part of legacy code
370
400
  const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
371
401
  // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
@@ -373,13 +403,13 @@ class RabbitMq {
373
403
  if (userId && enableRabbitTrace) {
374
404
  try {
375
405
  await Promise.all([
376
- zehut_1.createOrSetRabbitTrace(trace, userId),
377
- zehut_1.createOrSetRabbitTrace(outbreakTrace, userId),
406
+ (0, zehut_1.createOrSetRabbitTrace)(trace, userId),
407
+ (0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
378
408
  ]);
379
409
  }
380
410
  catch (e) {
381
411
  logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
382
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
412
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
383
413
  }
384
414
  }
385
415
  if (traceId) {
@@ -392,13 +422,30 @@ class RabbitMq {
392
422
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
393
423
  if (!shouldConsume) {
394
424
  await this.unlockRedisIfNeeded(releaseLock);
395
- return this.ack(channel, msg)(msg);
425
+ return this.ack(confirmChannel, msg)(msg);
396
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
+ };
397
444
  try {
398
- await callback(parsedMessage, this.ack(channel, msg, true, releaseLock), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock));
445
+ await callback(parsedMessage, localAck, localNack);
399
446
  }
400
447
  catch (e) {
401
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
448
+ await localNack(msg);
402
449
  }
403
450
  }, types_1.CONSUMER_DEFAULT_OPTIONS);
404
451
  if (!consumerTag) {
@@ -406,7 +453,7 @@ class RabbitMq {
406
453
  }
407
454
  else {
408
455
  logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
409
- this.consumersTags.push([c, consumerTag]);
456
+ this.consumersTags.push([confirmChannel, consumerTag]);
410
457
  }
411
458
  });
412
459
  }
@@ -415,9 +462,10 @@ class RabbitMq {
415
462
  RabbitMq.validateName('exchange', exchange);
416
463
  RabbitMq.validateName('queue', queue);
417
464
  const { limit, deadMessageTtl } = optionsWithDefaults;
418
- 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 });
419
467
  return channel.addSetup(async (c) => {
420
- const assertExchange = await utils_1.assertExchangeFanout(c, exchange);
468
+ const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
421
469
  await c.assertQueue(queue);
422
470
  this.exchanges[exchange] = assertExchange;
423
471
  await c.prefetch(limit, true);
@@ -428,30 +476,64 @@ class RabbitMq {
428
476
  });
429
477
  }
430
478
  async publish(exchange, content, customHeaders) {
431
- return utils_1.wrapSetImmediate(async () => {
479
+ return (0, utils_1.wrapSetImmediate)(async () => {
432
480
  RabbitMq.validateName('exchange', exchange);
433
- const channel = await this.assertChannel();
434
- 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 });
435
483
  await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
436
484
  });
437
485
  }
438
- async sendToQueue(queue, content, options, customHeaders, isBlocking) {
439
- const callback = async () => {
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 {
440
495
  RabbitMq.validateName('queue', queue);
441
- await this.assertChannel();
442
- await this.assertQueue(queue, options);
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 {
443
503
  const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
444
504
  debug(`rabbit: sending to queue ${queue}`, { res });
445
505
  return res;
446
- };
447
- if (isBlocking) {
448
- return callback();
449
506
  }
450
- return utils_1.wrapSetImmediate(callback);
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;
511
+ }
451
512
  }
452
- async isConnected() {
453
- const connection = await this.getConnection();
454
- 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;
455
537
  }
456
538
  async gracefulShutdown(signal) {
457
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;