@autofleet/rabbit 3.2.23 → 3.2.24-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.d.ts CHANGED
@@ -3,7 +3,8 @@ import { EventEmitter } from 'events';
3
3
  import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from 'amqp-connection-manager';
4
4
  import { ConfirmChannel, ConsumeMessage, Options, Replies } from 'amqplib';
5
5
  import { RedisConfig } from './lib/redis';
6
- import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, AssertExchangePromisesDictionary } from './lib/types';
6
+ import { ConnectionPurpose } from './lib/consts';
7
+ import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, AssertExchangePromisesDictionary, ConnectionData } from './lib/types';
7
8
  export interface IAfRabbitMq {
8
9
  ack: any;
9
10
  nack: any;
@@ -37,10 +38,12 @@ type newChannelOpts = {
37
38
  name?: string;
38
39
  onClose?: null | ((args: any | null) => void);
39
40
  options?: CreateChannelOpts | undefined;
41
+ connectionPurpose?: ConnectionPurpose;
40
42
  };
41
43
  type assertChannelOpts = {
42
44
  channelName?: string;
43
45
  force?: boolean;
46
+ connectionPurpose?: ConnectionPurpose;
44
47
  };
45
48
  declare class RabbitMq implements IAfRabbitMq {
46
49
  static parseMsg(msg: any): any;
@@ -60,9 +63,11 @@ declare class RabbitMq implements IAfRabbitMq {
60
63
  channel: ChannelWrapper | null;
61
64
  publishChannelSetupPromise: Promise<ChannelWrapper> | null;
62
65
  blockReconnect: boolean | null | undefined;
63
- connection: AmqpConnectionManager | null | undefined;
66
+ connectionsMap: {
67
+ [ConnectionPurpose.Consume]: ConnectionData;
68
+ [ConnectionPurpose.Publish]: ConnectionData;
69
+ };
64
70
  em: EventEmitter;
65
- creatingConnection: boolean;
66
71
  exchanges: ExchangesCache;
67
72
  queues: QueuesCache;
68
73
  queueSetupPromises: QueueSetupPromisesDictionary;
@@ -77,16 +82,16 @@ declare class RabbitMq implements IAfRabbitMq {
77
82
  private shouldConsumeMessageByTimestamp;
78
83
  ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
79
84
  nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
80
- getConnection(): Promise<AmqpConnectionManager>;
81
- getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
82
- assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
85
+ getConnection(connectionPurpose: ConnectionPurpose): Promise<AmqpConnectionManager | null | undefined>;
86
+ getNewChannel({ name, onClose, options, connectionPurpose, }: newChannelOpts): Promise<ChannelWrapper>;
87
+ assertChannel({ force, connectionPurpose }: assertChannelOpts): Promise<ChannelWrapper>;
83
88
  assertExchange(exchangeName: string, options?: any): Promise<any>;
84
- getQueueLength(queue: string): Promise<Replies.AssertQueue>;
89
+ getQueueLength(queue: string, connectionPurpose?: ConnectionPurpose): Promise<Replies.AssertQueue>;
85
90
  private deleteQueue;
86
- bindQueue(queue: string, exchange: string): Promise<void>;
87
- setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
91
+ bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose): Promise<void>;
92
+ setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
88
93
  static shouldUseQuorum(queueName: string): boolean;
89
- assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
94
+ assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<any>;
90
95
  private saveConsumer;
91
96
  consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
92
97
  private lockRedisIfNeeded;
package/dist/index.js CHANGED
@@ -119,8 +119,10 @@ class RabbitMq {
119
119
  this.em = new events_1.EventEmitter();
120
120
  this.channel = null;
121
121
  this.publishChannelSetupPromise = null;
122
- this.connection = null;
123
- this.creatingConnection = false;
122
+ this.connectionsMap = {
123
+ [consts_1.ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
124
+ [consts_1.ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
125
+ };
124
126
  this.exchanges = {};
125
127
  this.queues = {};
126
128
  this.queueSetupPromises = {};
@@ -142,30 +144,31 @@ class RabbitMq {
142
144
  });
143
145
  }
144
146
  }
145
- async getConnection() {
147
+ async getConnection(connectionPurpose) {
146
148
  return new Promise(async (resolve, reject) => {
149
+ const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
147
150
  if (this.blockReconnect) {
148
151
  debug('rabbit: block reconnect');
149
152
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
150
153
  // @ts-ignore
151
154
  return resolve();
152
155
  }
153
- if (this.connection !== null) {
154
- if (this.options?.disableReconnect || this.connection?.isConnected()) {
156
+ if (connection !== null) {
157
+ if (this.options?.disableReconnect || connection?.isConnected()) {
155
158
  debug('rabbit: connection - is connected');
156
159
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
157
160
  // @ts-ignore
158
- return resolve(this.connection);
161
+ return resolve(connection);
159
162
  }
160
163
  debug('rabbit: connection - reconnecting');
161
164
  }
162
- if (this.creatingConnection) {
165
+ if (creatingConnection) {
163
166
  debug('rabbit: creating connection emi');
164
167
  this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
165
168
  this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
166
169
  return;
167
170
  }
168
- this.creatingConnection = true;
171
+ this.connectionsMap[connectionPurpose].creatingConnection = true;
169
172
  let isResolved = false;
170
173
  // It is import to use it as a function and not as a variable
171
174
  // because of k8s changes the env variables
@@ -178,11 +181,11 @@ class RabbitMq {
178
181
  return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
179
182
  };
180
183
  const defaultUrls = findServers();
181
- const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
184
+ const newConnection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
182
185
  findServers,
183
186
  });
184
- this.connection = connection;
185
- this.connection.on('error', (err) => {
187
+ this.connectionsMap[connectionPurpose].connection = newConnection;
188
+ newConnection.on('error', (err) => {
186
189
  logger_1.default.error('rabbit: connection error', { err });
187
190
  if (!isResolved) {
188
191
  isResolved = true;
@@ -190,7 +193,7 @@ class RabbitMq {
190
193
  this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
191
194
  }
192
195
  });
193
- this.connection.on('connectFailed', (err) => {
196
+ newConnection.on('connectFailed', (err) => {
194
197
  this.consumersTags = [];
195
198
  logger_1.default.error('rabbit: connection connectFailed', { err });
196
199
  if (!isResolved) {
@@ -199,8 +202,7 @@ class RabbitMq {
199
202
  this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
200
203
  }
201
204
  });
202
- this.connection.on('disconnect', ({ err }) => {
203
- // this.channel = null;
205
+ newConnection.on('disconnect', ({ err }) => {
204
206
  this.consumersTags = [];
205
207
  debug('rabbit: connection closed');
206
208
  if (this.options?.disableReconnect) {
@@ -211,24 +213,26 @@ class RabbitMq {
211
213
  logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
212
214
  }
213
215
  });
214
- this.connection.once('connect', async () => {
215
- debug('rabbit: connection established');
216
- this.creatingConnection = false;
217
- this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
216
+ newConnection.once('connect', async () => {
217
+ this.connectionsMap[connectionPurpose].creatingConnection = false;
218
+ this.em.emit(consts_1.CONNECTION_CREATED_CONST, newConnection);
218
219
  isResolved = true;
219
- resolve(connection);
220
+ resolve(newConnection);
220
221
  });
221
222
  });
222
223
  }
223
- async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
224
+ async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connectionPurpose = consts_1.ConnectionPurpose.Consume, }) {
224
225
  let connection;
225
226
  try {
226
- connection = await this.getConnection();
227
+ connection = await this.getConnection(connectionPurpose);
227
228
  }
228
229
  catch (e) {
229
230
  logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
230
231
  throw e;
231
232
  }
233
+ if (!connection) {
234
+ throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
235
+ }
232
236
  const channel = connection.createChannel({ ...options });
233
237
  (0, events_1.once)(channel, 'close').then((args) => {
234
238
  logger_1.default.error(`rabbit: channel ${name} closed`);
@@ -244,14 +248,15 @@ class RabbitMq {
244
248
  throw err;
245
249
  }
246
250
  }
247
- async assertChannel({ force = false } = {}) {
251
+ async assertChannel({ force = false, connectionPurpose = consts_1.ConnectionPurpose.Consume }) {
248
252
  if (!this.publishChannelSetupPromise) {
249
253
  this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
250
254
  if (this.channel && !force) {
251
255
  return resolve(this.channel);
252
256
  }
253
257
  try {
254
- const channel = await this.getNewChannel({});
258
+ const channel = await this.getNewChannel({ connectionPurpose });
259
+ debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
255
260
  channel.on('error', (err) => {
256
261
  logger_1.default.error('rabbit: channel error', { err });
257
262
  });
@@ -265,8 +270,8 @@ class RabbitMq {
265
270
  }
266
271
  return this.publishChannelSetupPromise;
267
272
  }
268
- async assertExchange(exchangeName, options) {
269
- const channel = await this.assertChannel();
273
+ async assertExchange(exchangeName, options = { connectionPurpose: consts_1.ConnectionPurpose.Consume }) {
274
+ const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
270
275
  if (this.exchanges[exchangeName]) {
271
276
  delete this.assertExchangePromises[exchangeName];
272
277
  return this.exchanges[exchangeName];
@@ -278,29 +283,30 @@ class RabbitMq {
278
283
  this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
279
284
  return this.exchanges[exchangeName];
280
285
  }
281
- async getQueueLength(queue) {
286
+ async getQueueLength(queue, connectionPurpose = consts_1.ConnectionPurpose.Consume) {
282
287
  RabbitMq.validateName('queue', queue);
288
+ const { connection } = this.connectionsMap[connectionPurpose];
283
289
  const { channel } = this;
284
290
  if (!channel) {
285
291
  throw new Error('channel is not defined');
286
292
  }
287
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
293
+ debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
288
294
  return channel?.checkQueue(queue);
289
295
  }
290
- async deleteQueue(queue) {
296
+ async deleteQueue(queue, connectionPurpose) {
291
297
  RabbitMq.validateName('queue', queue);
292
- const channel = await this.assertChannel();
298
+ const channel = await this.assertChannel({ connectionPurpose });
293
299
  logger_1.default.info('rabbit: deleting queue', { queue });
294
300
  const deleteQueueRes = await channel.deleteQueue(queue);
295
301
  debug('queue deleted', deleteQueueRes);
296
302
  return deleteQueueRes;
297
303
  }
298
- async bindQueue(queue, exchange) {
299
- const channel = await this.assertChannel();
304
+ async bindQueue(queue, exchange, connectionPurpose) {
305
+ const channel = await this.assertChannel({ connectionPurpose });
300
306
  await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
301
307
  return channel.bindQueue(queue, exchange, '');
302
308
  }
303
- async setupQueue(queueName, options) {
309
+ async setupQueue(queueName, connectionPurpose, options) {
304
310
  let queue;
305
311
  const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
306
312
  const localeOptions = {
@@ -313,7 +319,7 @@ class RabbitMq {
313
319
  },
314
320
  };
315
321
  try {
316
- const channel = await this.assertChannel();
322
+ const channel = await this.assertChannel({ connectionPurpose });
317
323
  debug('assertQueue->channel.addSetup', { queueName });
318
324
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
319
325
  debug('assertQueue->channel.assertQueue', { queueName });
@@ -323,8 +329,8 @@ class RabbitMq {
323
329
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
324
330
  if (!this.options?.dontRetryAssert) {
325
331
  debug('retrying assertQueue', { queueName });
326
- const channel = await this.assertChannel({ force: true });
327
- await this.deleteQueue(queueName);
332
+ const channel = await this.assertChannel({ force: true, connectionPurpose });
333
+ await this.deleteQueue(queueName, connectionPurpose);
328
334
  debug('retrying assertQueue->channel.addSetup', { queueName });
329
335
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
330
336
  debug('retrying assertQueue->channel.assertQueue', { queueName });
@@ -348,7 +354,7 @@ class RabbitMq {
348
354
  }
349
355
  return false;
350
356
  }
351
- async assertQueue(queueName, options) {
357
+ async assertQueue(queueName, connectionPurpose, options) {
352
358
  RabbitMq.validateName('queue', queueName);
353
359
  if (this.queues[queueName]) {
354
360
  delete this.queueSetupPromises[queueName];
@@ -357,7 +363,7 @@ class RabbitMq {
357
363
  if (this.queueSetupPromises[queueName]) {
358
364
  return this.queueSetupPromises[queueName];
359
365
  }
360
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
366
+ this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
361
367
  return this.queueSetupPromises[queueName];
362
368
  }
363
369
  saveConsumer(queue, callback, options) {
@@ -400,9 +406,9 @@ class RabbitMq {
400
406
  }
401
407
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
402
408
  }
403
- const channel = await this.getNewChannel({});
409
+ const channel = await this.getNewChannel({ connectionPurpose: consts_1.ConnectionPurpose.Consume });
404
410
  return channel.addSetup(async (confirmChannel) => {
405
- const q = await this.assertQueue(queue, optionsWithDefaults);
411
+ const q = await this.assertQueue(queue, consts_1.ConnectionPurpose.Consume, optionsWithDefaults);
406
412
  await confirmChannel.prefetch(limit, false);
407
413
  const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
408
414
  if (!msg) {
@@ -484,7 +490,7 @@ class RabbitMq {
484
490
  RabbitMq.validateName('queue', queue);
485
491
  const { limit, deadMessageTtl } = optionsWithDefaults;
486
492
  await this.saveConsumer(queue, callback, options);
487
- const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
493
+ const channel = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
488
494
  return channel.addSetup(async (c) => {
489
495
  const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
490
496
  await c.assertQueue(queue);
@@ -499,14 +505,14 @@ class RabbitMq {
499
505
  async publish(exchange, content, customHeaders) {
500
506
  return (0, utils_1.wrapSetImmediate)(async () => {
501
507
  RabbitMq.validateName('exchange', exchange);
502
- const channel = await this.assertChannel();
503
- await this.assertExchange(exchange);
508
+ const channel = await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
509
+ await this.assertExchange(exchange, { connectionPurpose: consts_1.ConnectionPurpose.Publish });
504
510
  await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
505
511
  });
506
512
  }
507
513
  async sendToQueue(queue, content, options, customHeaders) {
508
514
  try {
509
- await this.assertChannel();
515
+ await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
510
516
  }
511
517
  catch (e) {
512
518
  logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
@@ -514,7 +520,7 @@ class RabbitMq {
514
520
  }
515
521
  try {
516
522
  RabbitMq.validateName('queue', queue);
517
- await this.assertQueue(queue, options);
523
+ await this.assertQueue(queue, consts_1.ConnectionPurpose.Publish, options);
518
524
  }
519
525
  catch (e) {
520
526
  logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
@@ -526,31 +532,36 @@ class RabbitMq {
526
532
  return res;
527
533
  }
528
534
  catch (e) {
529
- const isConnected = await this.isConnected();
530
- logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
535
+ logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
531
536
  throw e;
532
537
  }
533
538
  }
534
539
  async isConnected() {
535
- const connection = await this.getConnection();
536
- const isConnected = connection.isConnected();
537
- if (!isConnected) {
538
- logger_1.default.error('rabbit: isConnected - false');
539
- return false;
540
- }
541
- const channel = await this.assertChannel();
542
- try {
543
- await Promise.all([
544
- channel.waitForConnect(),
545
- ...this.consumers.map((c) => channel.checkQueue(c.queue)),
546
- ]);
547
- }
548
- catch (e) {
549
- logger_1.default.error('rabbit: isConnected - false');
550
- return false;
551
- }
552
- logger_1.default.info('rabbit: isConnected - true');
553
- return true;
540
+ const isEachConnectionConnected = await Promise.all(Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
541
+ const { connection } = connectionData;
542
+ if (connectionPurpose === consts_1.ConnectionPurpose.Publish && !connection) {
543
+ return true; // The connection hasn't been initialized yet, as no messages have been sent through it
544
+ }
545
+ const isConnected = connection?.isConnected();
546
+ if (!isConnected) {
547
+ logger_1.default.error('rabbit: isConnected - false');
548
+ return false;
549
+ }
550
+ const channel = await this.assertChannel({ connectionPurpose: connectionPurpose });
551
+ try {
552
+ await Promise.all([
553
+ channel.waitForConnect(),
554
+ ...this.consumers.map((c) => channel.checkQueue(c.queue)),
555
+ ]);
556
+ }
557
+ catch (e) {
558
+ logger_1.default.error('rabbit: isConnected - false');
559
+ return false;
560
+ }
561
+ logger_1.default.info('rabbit: isConnected - true');
562
+ return true;
563
+ }));
564
+ return isEachConnectionConnected.every((isConnected) => isConnected === true);
554
565
  }
555
566
  async gracefulShutdown(signal) {
556
567
  const tagsNumber = this.consumersTags.length;
@@ -17,3 +17,7 @@ export declare const DEFAULT_OPTIONS: {
17
17
  auditContext: null;
18
18
  enableRabbitTrace: boolean;
19
19
  };
20
+ export declare enum ConnectionPurpose {
21
+ Consume = "consume",
22
+ Publish = "publish"
23
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.AUTOMATION_ID_HEADER = exports.USER_TRACING_HEADER = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
3
+ exports.ConnectionPurpose = exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.AUTOMATION_ID_HEADER = exports.USER_TRACING_HEADER = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
4
4
  exports.DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 12;
5
5
  exports.DEFAULT_LOCK_TIMEOUT = 1000 * 5;
6
6
  exports.RETRY_HEADER = 'x-retry-count';
@@ -20,3 +20,8 @@ exports.DEFAULT_OPTIONS = {
20
20
  auditContext: null,
21
21
  enableRabbitTrace: false,
22
22
  };
23
+ var ConnectionPurpose;
24
+ (function (ConnectionPurpose) {
25
+ ConnectionPurpose["Consume"] = "consume";
26
+ ConnectionPurpose["Publish"] = "publish";
27
+ })(ConnectionPurpose = exports.ConnectionPurpose || (exports.ConnectionPurpose = {}));
@@ -1,3 +1,4 @@
1
+ import { AmqpConnectionManager } from 'amqp-connection-manager';
1
2
  import { ConsumeMessage, Options, Replies } from 'amqplib';
2
3
  export interface ExchangesCache {
3
4
  [key: string]: any;
@@ -41,3 +42,7 @@ export type AfConsumer = {
41
42
  options: ConsumeOptions | undefined;
42
43
  };
43
44
  export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
45
+ export type ConnectionData = {
46
+ connection: AmqpConnectionManager | null;
47
+ creatingConnection: boolean;
48
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "3.2.23",
3
+ "version": "3.2.24-beta.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "engines": {
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  AUTOMATION_ID_HEADER,
23
23
  CONNECTION_CREATED_CONST,
24
24
  CONNECTION_FAILED_CONST,
25
+ ConnectionPurpose,
25
26
  DEFAULT_LOCK_TIMEOUT,
26
27
  DEFAULT_OPTIONS,
27
28
  RETRY_HEADER,
@@ -40,13 +41,13 @@ import {
40
41
  CONSUMER_DEFAULT_OPTIONS,
41
42
  QueueSetupPromisesDictionary,
42
43
  AssertExchangePromisesDictionary,
44
+ ConnectionData,
43
45
  } from './lib/types';
44
46
 
45
47
  // const debug = nodeDebug('af-rabbitmq')
46
48
  const debug = logger.debug.bind(logger);
47
49
 
48
50
  const PUBLISH_TIMEOUT = 1000 * 10;
49
-
50
51
  export interface IAfRabbitMq {
51
52
  ack: any;
52
53
  nack: any;
@@ -86,11 +87,13 @@ type newChannelOpts = {
86
87
  name?: string;
87
88
  onClose?: null | ((args: any | null) => void);
88
89
  options?: CreateChannelOpts | undefined;
90
+ connectionPurpose?: ConnectionPurpose,
89
91
  };
90
92
 
91
93
  type assertChannelOpts = {
92
94
  channelName?: string;
93
95
  force?: boolean;
96
+ connectionPurpose?: ConnectionPurpose;
94
97
  }
95
98
 
96
99
  type AfConsumer = {
@@ -102,7 +105,7 @@ type AfConsumer = {
102
105
  const HEARTBEAT = '60';
103
106
 
104
107
  class RabbitMq implements IAfRabbitMq {
105
- static parseMsg(msg: any) : any {
108
+ static parseMsg(msg: any): any {
106
109
  let { content } = msg;
107
110
  content = content.toString();
108
111
 
@@ -147,14 +150,15 @@ class RabbitMq implements IAfRabbitMq {
147
150
 
148
151
  publishChannelSetupPromise: Promise<ChannelWrapper> | null;
149
152
 
150
- blockReconnect: boolean | null | undefined
153
+ blockReconnect: boolean | null | undefined;
151
154
 
152
- connection: AmqpConnectionManager | null | undefined
155
+ connectionsMap: {
156
+ [ConnectionPurpose.Consume]: ConnectionData;
157
+ [ConnectionPurpose.Publish]: ConnectionData;
158
+ };
153
159
 
154
160
  em: EventEmitter;
155
161
 
156
- creatingConnection: boolean;
157
-
158
162
  exchanges: ExchangesCache;
159
163
 
160
164
  queues: QueuesCache;
@@ -178,8 +182,10 @@ class RabbitMq implements IAfRabbitMq {
178
182
  this.em = new EventEmitter();
179
183
  this.channel = null;
180
184
  this.publishChannelSetupPromise = null;
181
- this.connection = null;
182
- this.creatingConnection = false;
185
+ this.connectionsMap = {
186
+ [ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
187
+ [ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
188
+ };
183
189
  this.exchanges = {};
184
190
  this.queues = {};
185
191
  this.queueSetupPromises = {};
@@ -216,7 +222,7 @@ class RabbitMq implements IAfRabbitMq {
216
222
  return false;
217
223
  }
218
224
 
219
- public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
225
+ public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
220
226
  if (msg) {
221
227
  debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
222
228
  await channel.ack(msg);
@@ -242,8 +248,8 @@ class RabbitMq implements IAfRabbitMq {
242
248
  userMsg: ConsumeMessageOrNull,
243
249
  {
244
250
  skipRetry = false,
245
- }: NackOptions = { },
246
- ) : Promise<any> => {
251
+ }: NackOptions = {},
252
+ ): Promise<any> => {
247
253
  await this.unlockRedisIfNeeded(releaseLock);
248
254
  if (channel && msg) {
249
255
  if (
@@ -277,30 +283,31 @@ class RabbitMq implements IAfRabbitMq {
277
283
  }
278
284
  }
279
285
 
280
- async getConnection() {
281
- return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
286
+ async getConnection(connectionPurpose: ConnectionPurpose) {
287
+ return new Promise<AmqpConnectionManager | undefined | null>(async (resolve, reject) => {
288
+ const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
282
289
  if (this.blockReconnect) {
283
290
  debug('rabbit: block reconnect');
284
291
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
285
292
  // @ts-ignore
286
293
  return resolve();
287
294
  }
288
- if (this.connection !== null) {
289
- if (this.options?.disableReconnect || this.connection?.isConnected()) {
295
+ if (connection !== null) {
296
+ if (this.options?.disableReconnect || connection?.isConnected()) {
290
297
  debug('rabbit: connection - is connected');
291
298
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
292
299
  // @ts-ignore
293
- return resolve(this.connection);
300
+ return resolve(connection);
294
301
  }
295
302
  debug('rabbit: connection - reconnecting');
296
303
  }
297
- if (this.creatingConnection) {
304
+ if (creatingConnection) {
298
305
  debug('rabbit: creating connection emi');
299
306
  this.em.once(CONNECTION_CREATED_CONST, resolve);
300
307
  this.em.once(CONNECTION_FAILED_CONST, reject);
301
308
  return;
302
309
  }
303
- this.creatingConnection = true;
310
+ this.connectionsMap[connectionPurpose].creatingConnection = true;
304
311
  let isResolved = false;
305
312
 
306
313
  // It is import to use it as a function and not as a variable
@@ -317,12 +324,13 @@ class RabbitMq implements IAfRabbitMq {
317
324
  };
318
325
 
319
326
  const defaultUrls = findServers();
320
- const connection: AmqpConnectionManager = await connect(defaultUrls, {
327
+ const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
321
328
  findServers,
322
329
  });
323
330
 
324
- this.connection = connection;
325
- this.connection.on('error', (err) => {
331
+ this.connectionsMap[connectionPurpose].connection = newConnection;
332
+
333
+ newConnection.on('error', (err) => {
326
334
  logger.error('rabbit: connection error', { err });
327
335
  if (!isResolved) {
328
336
  isResolved = true;
@@ -331,7 +339,7 @@ class RabbitMq implements IAfRabbitMq {
331
339
  }
332
340
  });
333
341
 
334
- this.connection.on('connectFailed', (err) => {
342
+ newConnection.on('connectFailed', (err) => {
335
343
  this.consumersTags = [];
336
344
  logger.error('rabbit: connection connectFailed', { err });
337
345
  if (!isResolved) {
@@ -341,8 +349,7 @@ class RabbitMq implements IAfRabbitMq {
341
349
  }
342
350
  });
343
351
 
344
- this.connection.on('disconnect', ({ err }) => {
345
- // this.channel = null;
352
+ newConnection.on('disconnect', ({ err }) => {
346
353
  this.consumersTags = [];
347
354
  debug('rabbit: connection closed');
348
355
  if (this.options?.disableReconnect) {
@@ -352,25 +359,28 @@ class RabbitMq implements IAfRabbitMq {
352
359
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
353
360
  }
354
361
  });
355
-
356
- this.connection.once('connect', async () => {
357
- debug('rabbit: connection established');
358
- this.creatingConnection = false;
359
- this.em.emit(CONNECTION_CREATED_CONST, connection);
362
+ newConnection.once('connect', async () => {
363
+ this.connectionsMap[connectionPurpose].creatingConnection = false;
364
+ this.em.emit(CONNECTION_CREATED_CONST, newConnection);
360
365
  isResolved = true;
361
- resolve(connection);
366
+ resolve(newConnection);
362
367
  });
363
368
  });
364
369
  }
365
370
 
366
- async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
367
- let connection!: AmqpConnectionManager;
371
+ async getNewChannel({
372
+ name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
373
+ }: newChannelOpts): Promise<ChannelWrapper> {
374
+ let connection!: AmqpConnectionManager | undefined | null;
368
375
  try {
369
- connection = await this.getConnection();
376
+ connection = await this.getConnection(connectionPurpose);
370
377
  } catch (e) {
371
378
  logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
372
379
  throw e;
373
380
  }
381
+ if (!connection) {
382
+ throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
383
+ }
374
384
  const channel = connection.createChannel({ ...options });
375
385
  once(channel, 'close').then((args) => {
376
386
  logger.error(`rabbit: channel ${name} closed`);
@@ -386,7 +396,7 @@ class RabbitMq implements IAfRabbitMq {
386
396
  }
387
397
  }
388
398
 
389
- async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
399
+ async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
390
400
  if (!this.publishChannelSetupPromise) {
391
401
  this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
392
402
  if (this.channel && !force) {
@@ -394,7 +404,8 @@ class RabbitMq implements IAfRabbitMq {
394
404
  }
395
405
 
396
406
  try {
397
- const channel = await this.getNewChannel({});
407
+ const channel = await this.getNewChannel({ connectionPurpose });
408
+ debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
398
409
  channel.on('error', (err) => {
399
410
  logger.error('rabbit: channel error', { err });
400
411
  });
@@ -408,9 +419,8 @@ class RabbitMq implements IAfRabbitMq {
408
419
  return this.publishChannelSetupPromise;
409
420
  }
410
421
 
411
- async assertExchange(exchangeName: string, options?: any) {
412
- const channel: ChannelWrapper = await this.assertChannel();
413
-
422
+ async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
423
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
414
424
  if (this.exchanges[exchangeName]) {
415
425
  delete this.assertExchangePromises[exchangeName];
416
426
  return this.exchanges[exchangeName];
@@ -425,32 +435,33 @@ class RabbitMq implements IAfRabbitMq {
425
435
  return this.exchanges[exchangeName];
426
436
  }
427
437
 
428
- async getQueueLength(queue: string) {
438
+ async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
429
439
  RabbitMq.validateName('queue', queue);
440
+ const { connection } = this.connectionsMap[connectionPurpose];
430
441
  const { channel } = this;
431
442
  if (!channel) {
432
443
  throw new Error('channel is not defined');
433
444
  }
434
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
445
+ debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
435
446
  return channel?.checkQueue(queue);
436
447
  }
437
448
 
438
- private async deleteQueue(queue: string) {
449
+ private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
439
450
  RabbitMq.validateName('queue', queue);
440
- const channel: ChannelWrapper = await this.assertChannel();
451
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
441
452
  logger.info('rabbit: deleting queue', { queue });
442
453
  const deleteQueueRes = await channel.deleteQueue(queue);
443
454
  debug('queue deleted', deleteQueueRes);
444
455
  return deleteQueueRes;
445
456
  }
446
457
 
447
- async bindQueue(queue: string, exchange: string) {
448
- const channel: ChannelWrapper = await this.assertChannel();
458
+ async bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose) {
459
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
449
460
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
450
461
  return channel.bindQueue(queue, exchange, '');
451
462
  }
452
463
 
453
- async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
464
+ async setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
454
465
  let queue: Replies.AssertQueue;
455
466
  const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
456
467
  const localeOptions = {
@@ -463,7 +474,7 @@ class RabbitMq implements IAfRabbitMq {
463
474
  },
464
475
  };
465
476
  try {
466
- const channel: ChannelWrapper = await this.assertChannel();
477
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
467
478
  debug('assertQueue->channel.addSetup', { queueName });
468
479
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
469
480
  debug('assertQueue->channel.assertQueue', { queueName });
@@ -472,8 +483,8 @@ class RabbitMq implements IAfRabbitMq {
472
483
  logger.error('rabbit: assertQueue error', { queueName, options, error: e });
473
484
  if (!this.options?.dontRetryAssert) {
474
485
  debug('retrying assertQueue', { queueName });
475
- const channel = await this.assertChannel({ force: true });
476
- await this.deleteQueue(queueName);
486
+ const channel = await this.assertChannel({ force: true, connectionPurpose });
487
+ await this.deleteQueue(queueName, connectionPurpose);
477
488
 
478
489
  debug('retrying assertQueue->channel.addSetup', { queueName });
479
490
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
@@ -503,7 +514,7 @@ class RabbitMq implements IAfRabbitMq {
503
514
  return false;
504
515
  }
505
516
 
506
- async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
517
+ async assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue) {
507
518
  RabbitMq.validateName('queue', queueName);
508
519
  if (this.queues[queueName]) {
509
520
  delete this.queueSetupPromises[queueName];
@@ -514,12 +525,12 @@ class RabbitMq implements IAfRabbitMq {
514
525
  return this.queueSetupPromises[queueName];
515
526
  }
516
527
 
517
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
528
+ this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
518
529
  return this.queueSetupPromises[queueName];
519
530
  }
520
531
 
521
532
  private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
522
- const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
533
+ const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
523
534
  if (!isConsumerExist) {
524
535
  logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
525
536
  this.consumers.push({
@@ -568,9 +579,9 @@ class RabbitMq implements IAfRabbitMq {
568
579
  }
569
580
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
570
581
  }
571
- const channel = await this.getNewChannel({});
582
+ const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
572
583
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
573
- const q = await this.assertQueue(queue, optionsWithDefaults);
584
+ const q = await this.assertQueue(queue, ConnectionPurpose.Consume, optionsWithDefaults);
574
585
  await confirmChannel.prefetch(limit, false);
575
586
  const { consumerTag } = await confirmChannel.consume(
576
587
  queue,
@@ -658,13 +669,13 @@ class RabbitMq implements IAfRabbitMq {
658
669
  });
659
670
  }
660
671
 
661
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
672
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
662
673
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
663
674
  RabbitMq.validateName('exchange', exchange);
664
675
  RabbitMq.validateName('queue', queue);
665
676
  const { limit, deadMessageTtl } = optionsWithDefaults;
666
677
  await this.saveConsumer(queue, callback, options);
667
- const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
678
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
668
679
 
669
680
  return channel.addSetup(async (c: ConfirmChannel) => {
670
681
  const assertExchange = await assertExchangeFanout(c, exchange);
@@ -682,11 +693,11 @@ class RabbitMq implements IAfRabbitMq {
682
693
  });
683
694
  }
684
695
 
685
- async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
696
+ async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
686
697
  return wrapSetImmediate(async () => {
687
698
  RabbitMq.validateName('exchange', exchange);
688
- const channel: ChannelWrapper = await this.assertChannel();
689
- await this.assertExchange(exchange);
699
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
700
+ await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
690
701
  await channel.publish(exchange, '',
691
702
  Buffer.from(JSON.stringify(content)),
692
703
  RabbitMq.getPublishOptions(customHeaders));
@@ -700,7 +711,7 @@ class RabbitMq implements IAfRabbitMq {
700
711
  customHeaders?: any,
701
712
  ): Promise<boolean | undefined> {
702
713
  try {
703
- await this.assertChannel();
714
+ await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
704
715
  } catch (e) {
705
716
  logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
706
717
  throw e;
@@ -708,7 +719,7 @@ class RabbitMq implements IAfRabbitMq {
708
719
 
709
720
  try {
710
721
  RabbitMq.validateName('queue', queue);
711
- await this.assertQueue(queue, options);
722
+ await this.assertQueue(queue, ConnectionPurpose.Publish, options);
712
723
  } catch (e) {
713
724
  logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
714
725
  throw e;
@@ -721,34 +732,41 @@ class RabbitMq implements IAfRabbitMq {
721
732
  debug(`rabbit: sending to queue ${queue}`, { res });
722
733
  return res;
723
734
  } catch (e) {
724
- const isConnected = await this.isConnected();
725
- logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
735
+ logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
726
736
  throw e;
727
737
  }
728
738
  }
729
739
 
730
- async isConnected() : Promise<boolean> {
731
- const connection = await this.getConnection();
732
- const isConnected = connection.isConnected();
733
- if (!isConnected) {
734
- logger.error('rabbit: isConnected - false');
735
- return false;
736
- }
737
- const channel: any = await this.assertChannel();
738
- try {
739
- await Promise.all([
740
- channel.waitForConnect(),
741
- ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
742
- ]);
743
- } catch (e) {
744
- logger.error('rabbit: isConnected - false');
745
- return false;
746
- }
747
- logger.info('rabbit: isConnected - true');
748
- return true;
740
+ async isConnected(): Promise<boolean> {
741
+ const isEachConnectionConnected = await Promise.all(
742
+ Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
743
+ const { connection } = connectionData;
744
+ if (connectionPurpose === ConnectionPurpose.Publish && !connection) {
745
+ return true; // The connection hasn't been initialized yet, as no messages have been sent through it
746
+ }
747
+ const isConnected = connection?.isConnected();
748
+ if (!isConnected) {
749
+ logger.error('rabbit: isConnected - false');
750
+ return false;
751
+ }
752
+ const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
753
+ try {
754
+ await Promise.all([
755
+ channel.waitForConnect(),
756
+ ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
757
+ ]);
758
+ } catch (e) {
759
+ logger.error('rabbit: isConnected - false');
760
+ return false;
761
+ }
762
+ logger.info('rabbit: isConnected - true');
763
+ return true;
764
+ }),
765
+ );
766
+ return isEachConnectionConnected.every((isConnected) => isConnected === true);
749
767
  }
750
768
 
751
- async gracefulShutdown(signal: string) : Promise<void> {
769
+ async gracefulShutdown(signal: string): Promise<void> {
752
770
  const tagsNumber = this.consumersTags.length;
753
771
  logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
754
772
  const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
package/src/lib/consts.ts CHANGED
@@ -17,3 +17,8 @@ export const DEFAULT_OPTIONS = {
17
17
  auditContext: null,
18
18
  enableRabbitTrace: false,
19
19
  };
20
+
21
+ export enum ConnectionPurpose {
22
+ Consume = 'consume',
23
+ Publish = 'publish',
24
+ }
package/src/lib/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { AmqpConnectionManager } from 'amqp-connection-manager';
1
2
  import { ConsumeMessage, Options, Replies } from 'amqplib';
2
3
 
3
4
  export interface ExchangesCache {
@@ -59,3 +60,8 @@ export const CONSUMER_DEFAULT_OPTIONS: Options.Consume = {
59
60
  [HA_PROMOTE_ON_SHUTDOWN]: 'always',
60
61
  },
61
62
  };
63
+
64
+ export type ConnectionData = {
65
+ connection: AmqpConnectionManager | null;
66
+ creatingConnection: boolean;
67
+ };