@autofleet/rabbit 3.3.0-beta.0 → 3.3.0-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/src/index.ts CHANGED
@@ -19,8 +19,8 @@ import RabbitError from './lib/rabbitError';
19
19
  import getRedisInstance, { RedisConfig } from './lib/redis';
20
20
  import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
21
21
  import {
22
- CONNECTION_CREATED_CONST,
23
- CONNECTION_FAILED_CONST,
22
+ AUTOMATION_ID_HEADER,
23
+ ConnectionPurpose,
24
24
  DEFAULT_LOCK_TIMEOUT,
25
25
  DEFAULT_OPTIONS,
26
26
  RETRY_HEADER,
@@ -35,14 +35,17 @@ import {
35
35
  CustomMessageHeaders,
36
36
  QueuesCache,
37
37
  RedisLockType,
38
- ExchangesCache, CONSUMER_DEFAULT_OPTIONS, QueueSetupPromisesDictionary,
38
+ ExchangesCache,
39
+ CONSUMER_DEFAULT_OPTIONS,
40
+ QueueSetupPromisesDictionary,
41
+ AssertExchangePromisesDictionary,
42
+ ConnectionData,
39
43
  } from './lib/types';
40
44
 
41
45
  // const debug = nodeDebug('af-rabbitmq')
42
46
  const debug = logger.debug.bind(logger);
43
47
 
44
48
  const PUBLISH_TIMEOUT = 1000 * 10;
45
-
46
49
  export interface IAfRabbitMq {
47
50
  ack: any;
48
51
  nack: any;
@@ -76,21 +79,19 @@ export interface AfRabbitOptions {
76
79
  dontRetryAssert?: boolean;
77
80
 
78
81
  rabbitHost?: string;
79
-
80
- serviceName: string;
81
-
82
- podIp?: string;
83
82
  }
84
83
 
85
84
  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,17 +144,18 @@ class RabbitMq implements IAfRabbitMq {
143
144
 
144
145
  RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
145
146
 
146
- channel: ChannelWrapper | null;
147
-
148
- blockReconnect: boolean | null | undefined
147
+ publishChannel: ChannelWrapper | null;
149
148
 
150
- connection: AmqpConnectionManager | null | undefined
149
+ publishChannelSetupPromise: Promise<ChannelWrapper> | null;
151
150
 
152
- em: EventEmitter;
151
+ blockReconnect: boolean | null | undefined;
153
152
 
154
- podId: string;
153
+ connectionsMap: {
154
+ [ConnectionPurpose.Consume]: ConnectionData;
155
+ [ConnectionPurpose.Publish]: ConnectionData;
156
+ };
155
157
 
156
- creatingConnection: boolean;
158
+ em: EventEmitter;
157
159
 
158
160
  exchanges: ExchangesCache;
159
161
 
@@ -161,6 +163,8 @@ class RabbitMq implements IAfRabbitMq {
161
163
 
162
164
  queueSetupPromises: QueueSetupPromisesDictionary;
163
165
 
166
+ assertExchangePromises: AssertExchangePromisesDictionary;
167
+
164
168
  options: AfRabbitOptions | undefined;
165
169
 
166
170
  redisClient: any;
@@ -174,15 +178,28 @@ class RabbitMq implements IAfRabbitMq {
174
178
 
175
179
  constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
176
180
  this.em = new EventEmitter();
177
- this.channel = null;
178
- this.connection = null;
179
- this.creatingConnection = false;
181
+ this.publishChannel = null;
182
+ this.publishChannelSetupPromise = null;
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
+ };
180
197
  this.exchanges = {};
181
198
  this.queues = {};
182
199
  this.queueSetupPromises = {};
200
+ this.assertExchangePromises = {};
183
201
  this.consumers = [];
184
202
  this.options = options;
185
- this.podId = `${options?.serviceName}${options?.podIp ? `-${options.podIp}` : ''}`;
186
203
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
187
204
  if (this.redisClient) {
188
205
  this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
@@ -213,7 +230,7 @@ class RabbitMq implements IAfRabbitMq {
213
230
  return false;
214
231
  }
215
232
 
216
- 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> => {
217
234
  if (msg) {
218
235
  debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
219
236
  await channel.ack(msg);
@@ -239,8 +256,8 @@ class RabbitMq implements IAfRabbitMq {
239
256
  userMsg: ConsumeMessageOrNull,
240
257
  {
241
258
  skipRetry = false,
242
- }: NackOptions = { },
243
- ) : Promise<any> => {
259
+ }: NackOptions = {},
260
+ ): Promise<any> => {
244
261
  await this.unlockRedisIfNeeded(releaseLock);
245
262
  if (channel && msg) {
246
263
  if (
@@ -274,30 +291,36 @@ class RabbitMq implements IAfRabbitMq {
274
291
  }
275
292
  }
276
293
 
277
- async getConnection() {
278
- 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];
279
302
  if (this.blockReconnect) {
280
303
  debug('rabbit: block reconnect');
281
304
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
282
305
  // @ts-ignore
283
306
  return resolve();
284
307
  }
285
- if (this.connection !== null) {
286
- if (this.options?.disableReconnect || this.connection?.isConnected()) {
308
+ if (connection !== null) {
309
+ if (this.options?.disableReconnect || connection?.isConnected()) {
287
310
  debug('rabbit: connection - is connected');
288
311
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
289
312
  // @ts-ignore
290
- return resolve(this.connection);
313
+ return resolve(connection);
291
314
  }
292
315
  debug('rabbit: connection - reconnecting');
293
316
  }
294
- if (this.creatingConnection) {
317
+ if (creatingConnection) {
295
318
  debug('rabbit: creating connection emi');
296
- this.em.once(CONNECTION_CREATED_CONST, resolve);
297
- this.em.once(CONNECTION_FAILED_CONST, reject);
319
+ this.em.once(connectionCreatedEventName, resolve);
320
+ this.em.once(connectionFailedEventName, reject);
298
321
  return;
299
322
  }
300
- this.creatingConnection = true;
323
+ this.connectionsMap[connectionPurpose].creatingConnection = true;
301
324
  let isResolved = false;
302
325
 
303
326
  // It is import to use it as a function and not as a variable
@@ -314,31 +337,33 @@ class RabbitMq implements IAfRabbitMq {
314
337
  };
315
338
 
316
339
  const defaultUrls = findServers();
317
- const connection: AmqpConnectionManager = await connect(defaultUrls, {
340
+ const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
318
341
  findServers,
319
342
  });
320
343
 
321
- this.connection = connection;
322
- 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) => {
323
348
  logger.error('rabbit: connection error', { err });
324
349
  if (!isResolved) {
325
350
  isResolved = true;
326
351
  reject(err);
327
- this.em.emit(CONNECTION_FAILED_CONST, err);
352
+ this.em.emit(connectionFailedEventName, err);
328
353
  }
329
354
  });
330
355
 
331
- this.connection.on('connectFailed', (err) => {
356
+ newConnection.on('connectFailed', (err) => {
332
357
  this.consumersTags = [];
333
358
  logger.error('rabbit: connection connectFailed', { err });
334
359
  if (!isResolved) {
335
360
  isResolved = true;
336
361
  reject(err);
337
- this.em.emit(CONNECTION_FAILED_CONST, err);
362
+ this.em.emit(connectionFailedEventName, err);
338
363
  }
339
364
  });
340
365
 
341
- this.connection.on('disconnect', ({ err }) => {
366
+ newConnection.on('disconnect', ({ err }) => {
342
367
  this.consumersTags = [];
343
368
  debug('rabbit: connection closed');
344
369
  if (this.options?.disableReconnect) {
@@ -348,25 +373,29 @@ class RabbitMq implements IAfRabbitMq {
348
373
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
349
374
  }
350
375
  });
351
- this.connection.once('connect', async () => {
352
- debug('rabbit: connection established');
353
- this.creatingConnection = false;
354
- this.em.emit(CONNECTION_CREATED_CONST, connection);
376
+ newConnection.once('connect', async () => {
377
+ this.connectionsMap[connectionPurpose].creatingConnection = false;
378
+ this.em.emit(connectionCreatedEventName, newConnection);
355
379
  isResolved = true;
356
- resolve(connection);
380
+ resolve(newConnection);
357
381
  });
358
382
  });
359
383
  }
360
384
 
361
- async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
362
- 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;
363
389
  try {
364
- connection = await this.getConnection();
390
+ connection = await this.getConnection(connectionPurpose);
365
391
  } catch (e) {
366
392
  logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
367
393
  throw e;
368
394
  }
369
- const channel = connection.createChannel({ ...options, name });
395
+ if (!connection) {
396
+ throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
397
+ }
398
+ const channel = connection.createChannel({ ...options });
370
399
  once(channel, 'close').then((args) => {
371
400
  logger.error(`rabbit: channel ${name} closed`);
372
401
  onClose?.(args);
@@ -381,48 +410,62 @@ class RabbitMq implements IAfRabbitMq {
381
410
  }
382
411
  }
383
412
 
384
- async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
385
- return new Promise<ChannelWrapper>(async (resolve, reject) => {
386
- if (this.channel && !force) {
387
- return resolve(this.channel);
388
- }
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) {
416
+ this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
417
+ if (this.publishChannel && !force) {
418
+ return resolve(this.publishChannel);
419
+ }
389
420
 
390
- try {
391
- const channel = await this.getNewChannel({});
392
- channel.on('error', (err) => {
393
- logger.error('rabbit: channel error', { err });
394
- });
395
- this.channel = channel;
396
- resolve(channel);
397
- } catch (e) {
398
- reject(e);
399
- }
400
- });
421
+ try {
422
+ const channel = await this.getNewChannel({ connectionPurpose });
423
+ debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
424
+ channel.on('error', (err) => {
425
+ logger.error('rabbit: channel error', { err });
426
+ });
427
+ if (connectionPurpose === ConnectionPurpose.Publish) {
428
+ this.publishChannel = channel;
429
+ }
430
+ resolve(channel);
431
+ } catch (e) {
432
+ reject(e);
433
+ }
434
+ });
435
+ }
436
+ return this.publishChannelSetupPromise;
401
437
  }
402
438
 
403
- async assertExchange(exchangeName: string, options?: any) {
404
- const channel: ChannelWrapper = await this.assertChannel();
439
+ async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
440
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
405
441
  if (this.exchanges[exchangeName]) {
442
+ delete this.assertExchangePromises[exchangeName];
406
443
  return this.exchanges[exchangeName];
407
444
  }
408
- const exchange = await assertExchangeFanout(channel, exchangeName);
409
- this.exchanges[exchangeName] = exchange;
410
- return exchange;
445
+
446
+ if (this.assertExchangePromises[exchangeName]) {
447
+ return this.assertExchangePromises[exchangeName];
448
+ }
449
+
450
+ this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
451
+ this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
452
+ return this.exchanges[exchangeName];
411
453
  }
412
454
 
413
- async getQueueLength(queue: string) {
455
+ async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
414
456
  RabbitMq.validateName('queue', queue);
415
- const { channel } = this;
416
- if (!channel) {
457
+ const { connection } = this.connectionsMap[connectionPurpose];
458
+ const { publishChannel } = this;
459
+ if (!publishChannel) {
417
460
  throw new Error('channel is not defined');
418
461
  }
419
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
420
- return channel?.checkQueue(queue);
462
+ debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
463
+ return publishChannel?.checkQueue(queue);
421
464
  }
422
465
 
423
- private async deleteQueue(queue: string) {
466
+ private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
424
467
  RabbitMq.validateName('queue', queue);
425
- const channel: ChannelWrapper = await this.assertChannel();
468
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
426
469
  logger.info('rabbit: deleting queue', { queue });
427
470
  const deleteQueueRes = await channel.deleteQueue(queue);
428
471
  debug('queue deleted', deleteQueueRes);
@@ -430,40 +473,67 @@ class RabbitMq implements IAfRabbitMq {
430
473
  }
431
474
 
432
475
  async bindQueue(queue: string, exchange: string) {
433
- const channel: ChannelWrapper = await this.assertChannel();
476
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
434
477
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
435
478
  return channel.bindQueue(queue, exchange, '');
436
479
  }
437
480
 
438
481
  async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
439
482
  let queue: Replies.AssertQueue;
483
+ const connectionPurpose = ConnectionPurpose.Publish;
484
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
485
+ const localeOptions = {
486
+ ...options,
487
+ durable: true,
488
+ arguments: {
489
+ ...options?.arguments,
490
+ 'x-consumer-timeout': 1000 * 60 * 60 * 24,
491
+ 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
492
+ },
493
+ };
440
494
  try {
441
- const channel: ChannelWrapper = await this.assertChannel();
495
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
442
496
  debug('assertQueue->channel.addSetup', { queueName });
443
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
497
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
444
498
  debug('assertQueue->channel.assertQueue', { queueName });
445
- queue = await channel.assertQueue(queueName, options);
499
+ queue = await channel.assertQueue(queueName, localeOptions);
446
500
  } catch (e) {
447
501
  logger.error('rabbit: assertQueue error', { queueName, options, error: e });
448
502
  if (!this.options?.dontRetryAssert) {
449
503
  debug('retrying assertQueue', { queueName });
450
- const channel = await this.assertChannel({ force: true });
451
- await this.deleteQueue(queueName);
504
+ const channel = await this.assertChannel({ force: true, connectionPurpose });
505
+ await this.deleteQueue(queueName, connectionPurpose);
452
506
 
453
507
  debug('retrying assertQueue->channel.addSetup', { queueName });
454
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
508
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
455
509
  debug('retrying assertQueue->channel.assertQueue', { queueName });
456
- queue = await channel.assertQueue(queueName, options);
510
+ queue = await channel.assertQueue(queueName, localeOptions);
457
511
  } else {
458
512
  throw e;
459
513
  }
460
514
  }
461
515
 
462
- this.queues[queueName] = queueName;
516
+ this.queues[queueName] = queue;
463
517
  return queue;
464
518
  }
465
519
 
520
+ static shouldUseQuorum(queueName: string): boolean {
521
+ const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
522
+
523
+ if (envQuorumQueuesWhitelist === '*') {
524
+ return true;
525
+ }
526
+
527
+ if (envQuorumQueuesWhitelist) {
528
+ const whitelist = envQuorumQueuesWhitelist.split(',');
529
+ return whitelist.includes(queueName);
530
+ }
531
+
532
+ return false;
533
+ }
534
+
466
535
  async assertQueue(queueName: string, options?: Options.AssertQueue) {
536
+ debug('rabbit: start assert queue', { queueName });
467
537
  RabbitMq.validateName('queue', queueName);
468
538
  if (this.queues[queueName]) {
469
539
  delete this.queueSetupPromises[queueName];
@@ -475,11 +545,12 @@ class RabbitMq implements IAfRabbitMq {
475
545
  }
476
546
 
477
547
  this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
548
+ debug('rabbit: done assert queue', { queueName });
478
549
  return this.queueSetupPromises[queueName];
479
550
  }
480
551
 
481
552
  private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
482
- const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
553
+ const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
483
554
  if (!isConsumerExist) {
484
555
  logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
485
556
  this.consumers.push({
@@ -528,10 +599,10 @@ class RabbitMq implements IAfRabbitMq {
528
599
  }
529
600
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
530
601
  }
531
- const channel = await this.getNewChannel({ name: `${this.podId}_queue_${queue}` });
602
+ const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
532
603
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
533
- await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
534
- await confirmChannel.prefetch(limit, true);
604
+ const q = await this.assertQueue(queue, optionsWithDefaults);
605
+ await confirmChannel.prefetch(limit, false);
535
606
  const { consumerTag } = await confirmChannel.consume(
536
607
  queue,
537
608
  async (msg: ConsumeMessageOrNull) => {
@@ -541,6 +612,7 @@ class RabbitMq implements IAfRabbitMq {
541
612
 
542
613
  const traceId = msg.properties.headers[TRACING_HEADER];
543
614
  const userId = msg.properties.headers[USER_TRACING_HEADER];
615
+ const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
544
616
  const parsedMessage = RabbitMq.parseMsg(msg);
545
617
  const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
546
618
  const trace = newTrace(traceTypes.RABBIT);
@@ -566,7 +638,10 @@ class RabbitMq implements IAfRabbitMq {
566
638
  }
567
639
 
568
640
  if (auditContext) {
569
- await auditContext(queue);
641
+ await auditContext(queue, {
642
+ userId,
643
+ automationId,
644
+ });
570
645
  }
571
646
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
572
647
  if (!shouldConsume) {
@@ -614,19 +689,19 @@ class RabbitMq implements IAfRabbitMq {
614
689
  });
615
690
  }
616
691
 
617
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
692
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
618
693
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
619
694
  RabbitMq.validateName('exchange', exchange);
620
695
  RabbitMq.validateName('queue', queue);
621
696
  const { limit, deadMessageTtl } = optionsWithDefaults;
622
697
  await this.saveConsumer(queue, callback, options);
623
- const channel: ChannelWrapper = await this.getNewChannel({ name: `${this.podId}_exchange_${exchange}_queue_${queue}` });
698
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
624
699
 
625
700
  return channel.addSetup(async (c: ConfirmChannel) => {
626
701
  const assertExchange = await assertExchangeFanout(c, exchange);
627
702
  await c.assertQueue(queue);
628
703
  this.exchanges[exchange] = assertExchange;
629
- await c.prefetch(limit, true);
704
+ await c.prefetch(limit, false);
630
705
  return Promise.all([
631
706
  c.bindQueue(queue, exchange, ''),
632
707
  this.consume(
@@ -638,11 +713,12 @@ class RabbitMq implements IAfRabbitMq {
638
713
  });
639
714
  }
640
715
 
641
- async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
716
+ async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
717
+ debug('rabbit: start publish msg');
642
718
  return wrapSetImmediate(async () => {
643
719
  RabbitMq.validateName('exchange', exchange);
644
- const channel: ChannelWrapper = await this.assertChannel();
645
- await this.assertExchange(exchange);
720
+ const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
721
+ await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
646
722
  await channel.publish(exchange, '',
647
723
  Buffer.from(JSON.stringify(content)),
648
724
  RabbitMq.getPublishOptions(customHeaders));
@@ -654,51 +730,69 @@ class RabbitMq implements IAfRabbitMq {
654
730
  content: any,
655
731
  options?: any,
656
732
  customHeaders?: any,
657
- isBlocking?: boolean,
658
733
  ): Promise<boolean | undefined> {
659
- const callback = async (): Promise<boolean | undefined> => {
660
- try {
661
- RabbitMq.validateName('queue', queue);
662
- await this.assertChannel();
663
- await this.assertQueue(queue, options);
664
- const res = await this.channel?.sendToQueue(queue,
665
- Buffer.from(JSON.stringify(content)),
666
- RabbitMq.getPublishOptions(customHeaders));
667
- debug(`rabbit: sending to queue ${queue}`, { res });
668
- return res;
669
- } catch (e) {
670
- logger.error(`rabbit: failed to send to queue ${queue}`, { e });
671
- throw e;
672
- }
673
- };
674
- if (isBlocking) {
675
- return callback();
734
+ try {
735
+ await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
736
+ } catch (e) {
737
+ logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
738
+ throw e;
676
739
  }
677
- return wrapSetImmediate(callback);
678
- }
679
740
 
680
- async isConnected() : Promise<boolean> {
681
- const connection = await this.getConnection();
682
- const isConnected = connection.isConnected();
683
- if (!isConnected) {
684
- logger.error('rabbit: isConnected - false');
685
- return false;
741
+ try {
742
+ RabbitMq.validateName('queue', queue);
743
+ await this.assertQueue(queue, options);
744
+ } catch (e) {
745
+ logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
746
+ throw e;
686
747
  }
687
- const channel: any = await this.assertChannel();
748
+
688
749
  try {
689
- await Promise.all([
690
- channel.waitForConnect(),
691
- ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
692
- ]);
750
+ const res = await this.publishChannel?.sendToQueue(queue,
751
+ Buffer.from(JSON.stringify(content)),
752
+ RabbitMq.getPublishOptions(customHeaders));
753
+ debug(`rabbit: sending to queue ${queue}`, { res });
754
+ return res;
693
755
  } catch (e) {
694
- logger.error('rabbit: isConnected - false');
695
- return false;
756
+ logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
757
+ throw e;
696
758
  }
697
- logger.info('rabbit: isConnected - true');
698
- return true;
699
759
  }
700
760
 
701
- async gracefulShutdown(signal: string) : Promise<void> {
761
+ async isConnected(): Promise<boolean> {
762
+ debug('rabbit: start is connected');
763
+ const isEachConnectionConnected = await Promise.all(
764
+ Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
765
+ const { connection } = connectionData;
766
+ if (connection) {
767
+ const isConnected = connection.isConnected();
768
+ if (!isConnected) {
769
+ logger.error('rabbit: isConnected - false', { connectionPurpose });
770
+ return false;
771
+ }
772
+ logger.info('rabbit: isConnected - true', { connectionPurpose });
773
+
774
+ if (connectionPurpose === ConnectionPurpose.Publish) {
775
+ const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
776
+ try {
777
+ await Promise.all([
778
+ channel.waitForConnect(),
779
+ ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
780
+ ]);
781
+ } catch (e) {
782
+ logger.error('rabbit: isConnected - false');
783
+ return false;
784
+ }
785
+ }
786
+ } else {
787
+ logger.info('rabbit: connection hasnt initialized yet', { connectionPurpose });
788
+ }
789
+ return true;
790
+ }),
791
+ );
792
+ return isEachConnectionConnected.every((isConnected) => isConnected === true);
793
+ }
794
+
795
+ async gracefulShutdown(signal: string): Promise<void> {
702
796
  const tagsNumber = this.consumersTags.length;
703
797
  logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
704
798
  const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
@@ -715,3 +809,7 @@ class RabbitMq implements IAfRabbitMq {
715
809
  }
716
810
 
717
811
  export default RabbitMq;
812
+
813
+ export {
814
+ sendCeleryTaskViaHttp,
815
+ } from './lib/celery';