@autofleet/rabbit 3.2.25-beta.103 → 3.2.26-beta.0

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