@autofleet/rabbit 3.2.23 → 3.2.24-beta.1

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,12 @@ 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
+ logger_1.default.info(`rabbit: created new connection ${connectionPurpose}`);
189
+ newConnection.on('error', (err) => {
186
190
  logger_1.default.error('rabbit: connection error', { err });
187
191
  if (!isResolved) {
188
192
  isResolved = true;
@@ -190,7 +194,7 @@ class RabbitMq {
190
194
  this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
191
195
  }
192
196
  });
193
- this.connection.on('connectFailed', (err) => {
197
+ newConnection.on('connectFailed', (err) => {
194
198
  this.consumersTags = [];
195
199
  logger_1.default.error('rabbit: connection connectFailed', { err });
196
200
  if (!isResolved) {
@@ -199,8 +203,7 @@ class RabbitMq {
199
203
  this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
200
204
  }
201
205
  });
202
- this.connection.on('disconnect', ({ err }) => {
203
- // this.channel = null;
206
+ newConnection.on('disconnect', ({ err }) => {
204
207
  this.consumersTags = [];
205
208
  debug('rabbit: connection closed');
206
209
  if (this.options?.disableReconnect) {
@@ -211,24 +214,26 @@ class RabbitMq {
211
214
  logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
212
215
  }
213
216
  });
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);
217
+ newConnection.once('connect', async () => {
218
+ this.connectionsMap[connectionPurpose].creatingConnection = false;
219
+ this.em.emit(consts_1.CONNECTION_CREATED_CONST, newConnection);
218
220
  isResolved = true;
219
- resolve(connection);
221
+ resolve(newConnection);
220
222
  });
221
223
  });
222
224
  }
223
- async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
225
+ async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connectionPurpose = consts_1.ConnectionPurpose.Consume, }) {
224
226
  let connection;
225
227
  try {
226
- connection = await this.getConnection();
228
+ connection = await this.getConnection(connectionPurpose);
227
229
  }
228
230
  catch (e) {
229
231
  logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
230
232
  throw e;
231
233
  }
234
+ if (!connection) {
235
+ throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
236
+ }
232
237
  const channel = connection.createChannel({ ...options });
233
238
  (0, events_1.once)(channel, 'close').then((args) => {
234
239
  logger_1.default.error(`rabbit: channel ${name} closed`);
@@ -244,14 +249,15 @@ class RabbitMq {
244
249
  throw err;
245
250
  }
246
251
  }
247
- async assertChannel({ force = false } = {}) {
252
+ async assertChannel({ force = false, connectionPurpose = consts_1.ConnectionPurpose.Consume }) {
248
253
  if (!this.publishChannelSetupPromise) {
249
254
  this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
250
255
  if (this.channel && !force) {
251
256
  return resolve(this.channel);
252
257
  }
253
258
  try {
254
- const channel = await this.getNewChannel({});
259
+ const channel = await this.getNewChannel({ connectionPurpose });
260
+ debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
255
261
  channel.on('error', (err) => {
256
262
  logger_1.default.error('rabbit: channel error', { err });
257
263
  });
@@ -265,8 +271,8 @@ class RabbitMq {
265
271
  }
266
272
  return this.publishChannelSetupPromise;
267
273
  }
268
- async assertExchange(exchangeName, options) {
269
- const channel = await this.assertChannel();
274
+ async assertExchange(exchangeName, options = { connectionPurpose: consts_1.ConnectionPurpose.Consume }) {
275
+ const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
270
276
  if (this.exchanges[exchangeName]) {
271
277
  delete this.assertExchangePromises[exchangeName];
272
278
  return this.exchanges[exchangeName];
@@ -278,29 +284,30 @@ class RabbitMq {
278
284
  this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
279
285
  return this.exchanges[exchangeName];
280
286
  }
281
- async getQueueLength(queue) {
287
+ async getQueueLength(queue, connectionPurpose = consts_1.ConnectionPurpose.Consume) {
282
288
  RabbitMq.validateName('queue', queue);
289
+ const { connection } = this.connectionsMap[connectionPurpose];
283
290
  const { channel } = this;
284
291
  if (!channel) {
285
292
  throw new Error('channel is not defined');
286
293
  }
287
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
294
+ debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
288
295
  return channel?.checkQueue(queue);
289
296
  }
290
- async deleteQueue(queue) {
297
+ async deleteQueue(queue, connectionPurpose) {
291
298
  RabbitMq.validateName('queue', queue);
292
- const channel = await this.assertChannel();
299
+ const channel = await this.assertChannel({ connectionPurpose });
293
300
  logger_1.default.info('rabbit: deleting queue', { queue });
294
301
  const deleteQueueRes = await channel.deleteQueue(queue);
295
302
  debug('queue deleted', deleteQueueRes);
296
303
  return deleteQueueRes;
297
304
  }
298
- async bindQueue(queue, exchange) {
299
- const channel = await this.assertChannel();
305
+ async bindQueue(queue, exchange, connectionPurpose) {
306
+ const channel = await this.assertChannel({ connectionPurpose });
300
307
  await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
301
308
  return channel.bindQueue(queue, exchange, '');
302
309
  }
303
- async setupQueue(queueName, options) {
310
+ async setupQueue(queueName, connectionPurpose, options) {
304
311
  let queue;
305
312
  const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
306
313
  const localeOptions = {
@@ -313,7 +320,7 @@ class RabbitMq {
313
320
  },
314
321
  };
315
322
  try {
316
- const channel = await this.assertChannel();
323
+ const channel = await this.assertChannel({ connectionPurpose });
317
324
  debug('assertQueue->channel.addSetup', { queueName });
318
325
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
319
326
  debug('assertQueue->channel.assertQueue', { queueName });
@@ -323,8 +330,8 @@ class RabbitMq {
323
330
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
324
331
  if (!this.options?.dontRetryAssert) {
325
332
  debug('retrying assertQueue', { queueName });
326
- const channel = await this.assertChannel({ force: true });
327
- await this.deleteQueue(queueName);
333
+ const channel = await this.assertChannel({ force: true, connectionPurpose });
334
+ await this.deleteQueue(queueName, connectionPurpose);
328
335
  debug('retrying assertQueue->channel.addSetup', { queueName });
329
336
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
330
337
  debug('retrying assertQueue->channel.assertQueue', { queueName });
@@ -348,7 +355,7 @@ class RabbitMq {
348
355
  }
349
356
  return false;
350
357
  }
351
- async assertQueue(queueName, options) {
358
+ async assertQueue(queueName, connectionPurpose, options) {
352
359
  RabbitMq.validateName('queue', queueName);
353
360
  if (this.queues[queueName]) {
354
361
  delete this.queueSetupPromises[queueName];
@@ -357,7 +364,7 @@ class RabbitMq {
357
364
  if (this.queueSetupPromises[queueName]) {
358
365
  return this.queueSetupPromises[queueName];
359
366
  }
360
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
367
+ this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
361
368
  return this.queueSetupPromises[queueName];
362
369
  }
363
370
  saveConsumer(queue, callback, options) {
@@ -400,9 +407,9 @@ class RabbitMq {
400
407
  }
401
408
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
402
409
  }
403
- const channel = await this.getNewChannel({});
410
+ const channel = await this.getNewChannel({ connectionPurpose: consts_1.ConnectionPurpose.Consume });
404
411
  return channel.addSetup(async (confirmChannel) => {
405
- const q = await this.assertQueue(queue, optionsWithDefaults);
412
+ const q = await this.assertQueue(queue, consts_1.ConnectionPurpose.Consume, optionsWithDefaults);
406
413
  await confirmChannel.prefetch(limit, false);
407
414
  const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
408
415
  if (!msg) {
@@ -484,7 +491,7 @@ class RabbitMq {
484
491
  RabbitMq.validateName('queue', queue);
485
492
  const { limit, deadMessageTtl } = optionsWithDefaults;
486
493
  await this.saveConsumer(queue, callback, options);
487
- const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
494
+ const channel = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
488
495
  return channel.addSetup(async (c) => {
489
496
  const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
490
497
  await c.assertQueue(queue);
@@ -499,14 +506,14 @@ class RabbitMq {
499
506
  async publish(exchange, content, customHeaders) {
500
507
  return (0, utils_1.wrapSetImmediate)(async () => {
501
508
  RabbitMq.validateName('exchange', exchange);
502
- const channel = await this.assertChannel();
503
- await this.assertExchange(exchange);
509
+ const channel = await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
510
+ await this.assertExchange(exchange, { connectionPurpose: consts_1.ConnectionPurpose.Publish });
504
511
  await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
505
512
  });
506
513
  }
507
514
  async sendToQueue(queue, content, options, customHeaders) {
508
515
  try {
509
- await this.assertChannel();
516
+ await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
510
517
  }
511
518
  catch (e) {
512
519
  logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
@@ -514,7 +521,7 @@ class RabbitMq {
514
521
  }
515
522
  try {
516
523
  RabbitMq.validateName('queue', queue);
517
- await this.assertQueue(queue, options);
524
+ await this.assertQueue(queue, consts_1.ConnectionPurpose.Publish, options);
518
525
  }
519
526
  catch (e) {
520
527
  logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
@@ -526,31 +533,36 @@ class RabbitMq {
526
533
  return res;
527
534
  }
528
535
  catch (e) {
529
- const isConnected = await this.isConnected();
530
- logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
536
+ logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
531
537
  throw e;
532
538
  }
533
539
  }
534
540
  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;
541
+ const isEachConnectionConnected = await Promise.all(Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
542
+ const { connection } = connectionData;
543
+ if (connectionPurpose === consts_1.ConnectionPurpose.Publish && !connection) {
544
+ return true; // The connection hasn't been initialized yet, as no messages have been sent through it
545
+ }
546
+ const isConnected = connection?.isConnected();
547
+ if (!isConnected) {
548
+ logger_1.default.error('rabbit: isConnected - false');
549
+ return false;
550
+ }
551
+ const channel = await this.assertChannel({ connectionPurpose: connectionPurpose });
552
+ try {
553
+ await Promise.all([
554
+ channel.waitForConnect(),
555
+ ...this.consumers.map((c) => channel.checkQueue(c.queue)),
556
+ ]);
557
+ }
558
+ catch (e) {
559
+ logger_1.default.error('rabbit: isConnected - false');
560
+ return false;
561
+ }
562
+ logger_1.default.info('rabbit: isConnected - true');
563
+ return true;
564
+ }));
565
+ return isEachConnectionConnected.every((isConnected) => isConnected === true);
554
566
  }
555
567
  async gracefulShutdown(signal) {
556
568
  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.1",
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,14 @@ 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
+ logger.info(`rabbit: created new connection ${connectionPurpose}`);
333
+
334
+ newConnection.on('error', (err) => {
326
335
  logger.error('rabbit: connection error', { err });
327
336
  if (!isResolved) {
328
337
  isResolved = true;
@@ -331,7 +340,7 @@ class RabbitMq implements IAfRabbitMq {
331
340
  }
332
341
  });
333
342
 
334
- this.connection.on('connectFailed', (err) => {
343
+ newConnection.on('connectFailed', (err) => {
335
344
  this.consumersTags = [];
336
345
  logger.error('rabbit: connection connectFailed', { err });
337
346
  if (!isResolved) {
@@ -341,8 +350,7 @@ class RabbitMq implements IAfRabbitMq {
341
350
  }
342
351
  });
343
352
 
344
- this.connection.on('disconnect', ({ err }) => {
345
- // this.channel = null;
353
+ newConnection.on('disconnect', ({ err }) => {
346
354
  this.consumersTags = [];
347
355
  debug('rabbit: connection closed');
348
356
  if (this.options?.disableReconnect) {
@@ -352,25 +360,28 @@ class RabbitMq implements IAfRabbitMq {
352
360
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
353
361
  }
354
362
  });
355
-
356
- this.connection.once('connect', async () => {
357
- debug('rabbit: connection established');
358
- this.creatingConnection = false;
359
- this.em.emit(CONNECTION_CREATED_CONST, connection);
363
+ newConnection.once('connect', async () => {
364
+ this.connectionsMap[connectionPurpose].creatingConnection = false;
365
+ this.em.emit(CONNECTION_CREATED_CONST, newConnection);
360
366
  isResolved = true;
361
- resolve(connection);
367
+ resolve(newConnection);
362
368
  });
363
369
  });
364
370
  }
365
371
 
366
- async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
367
- let connection!: AmqpConnectionManager;
372
+ async getNewChannel({
373
+ name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
374
+ }: newChannelOpts): Promise<ChannelWrapper> {
375
+ let connection!: AmqpConnectionManager | undefined | null;
368
376
  try {
369
- connection = await this.getConnection();
377
+ connection = await this.getConnection(connectionPurpose);
370
378
  } catch (e) {
371
379
  logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
372
380
  throw e;
373
381
  }
382
+ if (!connection) {
383
+ throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
384
+ }
374
385
  const channel = connection.createChannel({ ...options });
375
386
  once(channel, 'close').then((args) => {
376
387
  logger.error(`rabbit: channel ${name} closed`);
@@ -386,7 +397,7 @@ class RabbitMq implements IAfRabbitMq {
386
397
  }
387
398
  }
388
399
 
389
- async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
400
+ async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
390
401
  if (!this.publishChannelSetupPromise) {
391
402
  this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
392
403
  if (this.channel && !force) {
@@ -394,7 +405,8 @@ class RabbitMq implements IAfRabbitMq {
394
405
  }
395
406
 
396
407
  try {
397
- const channel = await this.getNewChannel({});
408
+ const channel = await this.getNewChannel({ connectionPurpose });
409
+ debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
398
410
  channel.on('error', (err) => {
399
411
  logger.error('rabbit: channel error', { err });
400
412
  });
@@ -408,9 +420,8 @@ class RabbitMq implements IAfRabbitMq {
408
420
  return this.publishChannelSetupPromise;
409
421
  }
410
422
 
411
- async assertExchange(exchangeName: string, options?: any) {
412
- const channel: ChannelWrapper = await this.assertChannel();
413
-
423
+ async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
424
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
414
425
  if (this.exchanges[exchangeName]) {
415
426
  delete this.assertExchangePromises[exchangeName];
416
427
  return this.exchanges[exchangeName];
@@ -425,32 +436,33 @@ class RabbitMq implements IAfRabbitMq {
425
436
  return this.exchanges[exchangeName];
426
437
  }
427
438
 
428
- async getQueueLength(queue: string) {
439
+ async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
429
440
  RabbitMq.validateName('queue', queue);
441
+ const { connection } = this.connectionsMap[connectionPurpose];
430
442
  const { channel } = this;
431
443
  if (!channel) {
432
444
  throw new Error('channel is not defined');
433
445
  }
434
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
446
+ debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
435
447
  return channel?.checkQueue(queue);
436
448
  }
437
449
 
438
- private async deleteQueue(queue: string) {
450
+ private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
439
451
  RabbitMq.validateName('queue', queue);
440
- const channel: ChannelWrapper = await this.assertChannel();
452
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
441
453
  logger.info('rabbit: deleting queue', { queue });
442
454
  const deleteQueueRes = await channel.deleteQueue(queue);
443
455
  debug('queue deleted', deleteQueueRes);
444
456
  return deleteQueueRes;
445
457
  }
446
458
 
447
- async bindQueue(queue: string, exchange: string) {
448
- const channel: ChannelWrapper = await this.assertChannel();
459
+ async bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose) {
460
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
449
461
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
450
462
  return channel.bindQueue(queue, exchange, '');
451
463
  }
452
464
 
453
- async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
465
+ async setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
454
466
  let queue: Replies.AssertQueue;
455
467
  const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
456
468
  const localeOptions = {
@@ -463,7 +475,7 @@ class RabbitMq implements IAfRabbitMq {
463
475
  },
464
476
  };
465
477
  try {
466
- const channel: ChannelWrapper = await this.assertChannel();
478
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
467
479
  debug('assertQueue->channel.addSetup', { queueName });
468
480
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
469
481
  debug('assertQueue->channel.assertQueue', { queueName });
@@ -472,8 +484,8 @@ class RabbitMq implements IAfRabbitMq {
472
484
  logger.error('rabbit: assertQueue error', { queueName, options, error: e });
473
485
  if (!this.options?.dontRetryAssert) {
474
486
  debug('retrying assertQueue', { queueName });
475
- const channel = await this.assertChannel({ force: true });
476
- await this.deleteQueue(queueName);
487
+ const channel = await this.assertChannel({ force: true, connectionPurpose });
488
+ await this.deleteQueue(queueName, connectionPurpose);
477
489
 
478
490
  debug('retrying assertQueue->channel.addSetup', { queueName });
479
491
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
@@ -503,7 +515,7 @@ class RabbitMq implements IAfRabbitMq {
503
515
  return false;
504
516
  }
505
517
 
506
- async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
518
+ async assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue) {
507
519
  RabbitMq.validateName('queue', queueName);
508
520
  if (this.queues[queueName]) {
509
521
  delete this.queueSetupPromises[queueName];
@@ -514,12 +526,12 @@ class RabbitMq implements IAfRabbitMq {
514
526
  return this.queueSetupPromises[queueName];
515
527
  }
516
528
 
517
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
529
+ this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
518
530
  return this.queueSetupPromises[queueName];
519
531
  }
520
532
 
521
533
  private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
522
- const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
534
+ const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
523
535
  if (!isConsumerExist) {
524
536
  logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
525
537
  this.consumers.push({
@@ -568,9 +580,9 @@ class RabbitMq implements IAfRabbitMq {
568
580
  }
569
581
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
570
582
  }
571
- const channel = await this.getNewChannel({});
583
+ const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
572
584
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
573
- const q = await this.assertQueue(queue, optionsWithDefaults);
585
+ const q = await this.assertQueue(queue, ConnectionPurpose.Consume, optionsWithDefaults);
574
586
  await confirmChannel.prefetch(limit, false);
575
587
  const { consumerTag } = await confirmChannel.consume(
576
588
  queue,
@@ -658,13 +670,13 @@ class RabbitMq implements IAfRabbitMq {
658
670
  });
659
671
  }
660
672
 
661
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
673
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
662
674
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
663
675
  RabbitMq.validateName('exchange', exchange);
664
676
  RabbitMq.validateName('queue', queue);
665
677
  const { limit, deadMessageTtl } = optionsWithDefaults;
666
678
  await this.saveConsumer(queue, callback, options);
667
- const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
679
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
668
680
 
669
681
  return channel.addSetup(async (c: ConfirmChannel) => {
670
682
  const assertExchange = await assertExchangeFanout(c, exchange);
@@ -682,11 +694,11 @@ class RabbitMq implements IAfRabbitMq {
682
694
  });
683
695
  }
684
696
 
685
- async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
697
+ async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
686
698
  return wrapSetImmediate(async () => {
687
699
  RabbitMq.validateName('exchange', exchange);
688
- const channel: ChannelWrapper = await this.assertChannel();
689
- await this.assertExchange(exchange);
700
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
701
+ await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
690
702
  await channel.publish(exchange, '',
691
703
  Buffer.from(JSON.stringify(content)),
692
704
  RabbitMq.getPublishOptions(customHeaders));
@@ -700,7 +712,7 @@ class RabbitMq implements IAfRabbitMq {
700
712
  customHeaders?: any,
701
713
  ): Promise<boolean | undefined> {
702
714
  try {
703
- await this.assertChannel();
715
+ await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
704
716
  } catch (e) {
705
717
  logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
706
718
  throw e;
@@ -708,7 +720,7 @@ class RabbitMq implements IAfRabbitMq {
708
720
 
709
721
  try {
710
722
  RabbitMq.validateName('queue', queue);
711
- await this.assertQueue(queue, options);
723
+ await this.assertQueue(queue, ConnectionPurpose.Publish, options);
712
724
  } catch (e) {
713
725
  logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
714
726
  throw e;
@@ -721,34 +733,41 @@ class RabbitMq implements IAfRabbitMq {
721
733
  debug(`rabbit: sending to queue ${queue}`, { res });
722
734
  return res;
723
735
  } catch (e) {
724
- const isConnected = await this.isConnected();
725
- logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
736
+ logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
726
737
  throw e;
727
738
  }
728
739
  }
729
740
 
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;
741
+ async isConnected(): Promise<boolean> {
742
+ const isEachConnectionConnected = await Promise.all(
743
+ Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
744
+ const { connection } = connectionData;
745
+ if (connectionPurpose === ConnectionPurpose.Publish && !connection) {
746
+ return true; // The connection hasn't been initialized yet, as no messages have been sent through it
747
+ }
748
+ const isConnected = connection?.isConnected();
749
+ if (!isConnected) {
750
+ logger.error('rabbit: isConnected - false');
751
+ return false;
752
+ }
753
+ const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
754
+ try {
755
+ await Promise.all([
756
+ channel.waitForConnect(),
757
+ ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
758
+ ]);
759
+ } catch (e) {
760
+ logger.error('rabbit: isConnected - false');
761
+ return false;
762
+ }
763
+ logger.info('rabbit: isConnected - true');
764
+ return true;
765
+ }),
766
+ );
767
+ return isEachConnectionConnected.every((isConnected) => isConnected === true);
749
768
  }
750
769
 
751
- async gracefulShutdown(signal: string) : Promise<void> {
770
+ async gracefulShutdown(signal: string): Promise<void> {
752
771
  const tagsNumber = this.consumersTags.length;
753
772
  logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
754
773
  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
+ };