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