@autofleet/rabbit 3.2.22-beta.5 → 3.2.23-beta.0.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/src/index.ts CHANGED
@@ -18,7 +18,9 @@ import logger from './logger';
18
18
  import RabbitError from './lib/rabbitError';
19
19
  import getRedisInstance, { RedisConfig } from './lib/redis';
20
20
  import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
21
+ import { sendCeleryTaskViaHttp } from './lib/celery';
21
22
  import {
23
+ AUTOMATION_ID_HEADER,
22
24
  CONNECTION_CREATED_CONST,
23
25
  CONNECTION_FAILED_CONST,
24
26
  DEFAULT_LOCK_TIMEOUT,
@@ -35,15 +37,17 @@ import {
35
37
  CustomMessageHeaders,
36
38
  QueuesCache,
37
39
  RedisLockType,
38
- ExchangesCache, CONSUMER_DEFAULT_OPTIONS, QueueSetupPromisesDictionary,
39
- ConnectionPurpose,
40
- ConnectionData,
40
+ ExchangesCache,
41
+ CONSUMER_DEFAULT_OPTIONS,
42
+ QueueSetupPromisesDictionary,
43
+ AssertExchangePromisesDictionary,
41
44
  } from './lib/types';
42
45
 
43
46
  // const debug = nodeDebug('af-rabbitmq')
44
47
  const debug = logger.debug.bind(logger);
45
48
 
46
49
  const PUBLISH_TIMEOUT = 1000 * 10;
50
+
47
51
  export interface IAfRabbitMq {
48
52
  ack: any;
49
53
  nack: any;
@@ -83,13 +87,11 @@ type newChannelOpts = {
83
87
  name?: string;
84
88
  onClose?: null | ((args: any | null) => void);
85
89
  options?: CreateChannelOpts | undefined;
86
- connectionPurpose?: ConnectionPurpose,
87
90
  };
88
91
 
89
92
  type assertChannelOpts = {
90
93
  channelName?: string;
91
94
  force?: boolean;
92
- connectionPurpose?: ConnectionPurpose;
93
95
  }
94
96
 
95
97
  type AfConsumer = {
@@ -101,7 +103,7 @@ type AfConsumer = {
101
103
  const HEARTBEAT = '60';
102
104
 
103
105
  class RabbitMq implements IAfRabbitMq {
104
- static parseMsg(msg: any): any {
106
+ static parseMsg(msg: any) : any {
105
107
  let { content } = msg;
106
108
  content = content.toString();
107
109
 
@@ -146,21 +148,22 @@ class RabbitMq implements IAfRabbitMq {
146
148
 
147
149
  publishChannelSetupPromise: Promise<ChannelWrapper> | null;
148
150
 
149
- blockReconnect: boolean | null | undefined;
151
+ blockReconnect: boolean | null | undefined
150
152
 
151
- connectionsMap: {
152
- [ConnectionPurpose.Consume]: ConnectionData;
153
- [ConnectionPurpose.Publish]: ConnectionData;
154
- };
153
+ connection: AmqpConnectionManager | null | undefined
155
154
 
156
155
  em: EventEmitter;
157
156
 
157
+ creatingConnection: boolean;
158
+
158
159
  exchanges: ExchangesCache;
159
160
 
160
161
  queues: QueuesCache;
161
162
 
162
163
  queueSetupPromises: QueueSetupPromisesDictionary;
163
164
 
165
+ assertExchangePromises: AssertExchangePromisesDictionary;
166
+
164
167
  options: AfRabbitOptions | undefined;
165
168
 
166
169
  redisClient: any;
@@ -176,13 +179,12 @@ class RabbitMq implements IAfRabbitMq {
176
179
  this.em = new EventEmitter();
177
180
  this.channel = null;
178
181
  this.publishChannelSetupPromise = null;
179
- this.connectionsMap = {
180
- [ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
181
- [ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
182
- };
182
+ this.connection = null;
183
+ this.creatingConnection = false;
183
184
  this.exchanges = {};
184
185
  this.queues = {};
185
186
  this.queueSetupPromises = {};
187
+ this.assertExchangePromises = {};
186
188
  this.consumers = [];
187
189
  this.options = options;
188
190
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
@@ -215,7 +217,7 @@ class RabbitMq implements IAfRabbitMq {
215
217
  return false;
216
218
  }
217
219
 
218
- public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
220
+ public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
219
221
  if (msg) {
220
222
  debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
221
223
  await channel.ack(msg);
@@ -241,8 +243,8 @@ class RabbitMq implements IAfRabbitMq {
241
243
  userMsg: ConsumeMessageOrNull,
242
244
  {
243
245
  skipRetry = false,
244
- }: NackOptions = {},
245
- ): Promise<any> => {
246
+ }: NackOptions = { },
247
+ ) : Promise<any> => {
246
248
  await this.unlockRedisIfNeeded(releaseLock);
247
249
  if (channel && msg) {
248
250
  if (
@@ -276,32 +278,30 @@ class RabbitMq implements IAfRabbitMq {
276
278
  }
277
279
  }
278
280
 
279
- async getConnection(connectionPurpose: ConnectionPurpose) {
280
- return new Promise<AmqpConnectionManager | undefined | null>(async (resolve, reject) => {
281
- const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
282
- debug('rabbit: start getting new connection', { connection, creatingConnection, connectionPurpose });
281
+ async getConnection() {
282
+ return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
283
283
  if (this.blockReconnect) {
284
284
  debug('rabbit: block reconnect');
285
285
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
286
286
  // @ts-ignore
287
287
  return resolve();
288
288
  }
289
- if (connection !== null) {
290
- if (this.options?.disableReconnect || connection?.isConnected()) {
289
+ if (this.connection !== null) {
290
+ if (this.options?.disableReconnect || this.connection?.isConnected()) {
291
291
  debug('rabbit: connection - is connected');
292
292
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
293
293
  // @ts-ignore
294
- return resolve(connection);
294
+ return resolve(this.connection);
295
295
  }
296
296
  debug('rabbit: connection - reconnecting');
297
297
  }
298
- if (creatingConnection) {
298
+ if (this.creatingConnection) {
299
299
  debug('rabbit: creating connection emi');
300
300
  this.em.once(CONNECTION_CREATED_CONST, resolve);
301
301
  this.em.once(CONNECTION_FAILED_CONST, reject);
302
302
  return;
303
303
  }
304
- this.connectionsMap[connectionPurpose].creatingConnection = true;
304
+ this.creatingConnection = true;
305
305
  let isResolved = false;
306
306
 
307
307
  // It is import to use it as a function and not as a variable
@@ -318,13 +318,12 @@ class RabbitMq implements IAfRabbitMq {
318
318
  };
319
319
 
320
320
  const defaultUrls = findServers();
321
- const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
321
+ const connection: AmqpConnectionManager = await connect(defaultUrls, {
322
322
  findServers,
323
323
  });
324
324
 
325
- this.connectionsMap[connectionPurpose].connection = newConnection;
326
-
327
- newConnection.on('error', (err) => {
325
+ this.connection = connection;
326
+ this.connection.on('error', (err) => {
328
327
  logger.error('rabbit: connection error', { err });
329
328
  if (!isResolved) {
330
329
  isResolved = true;
@@ -333,7 +332,7 @@ class RabbitMq implements IAfRabbitMq {
333
332
  }
334
333
  });
335
334
 
336
- newConnection.on('connectFailed', (err) => {
335
+ this.connection.on('connectFailed', (err) => {
337
336
  this.consumersTags = [];
338
337
  logger.error('rabbit: connection connectFailed', { err });
339
338
  if (!isResolved) {
@@ -343,7 +342,8 @@ class RabbitMq implements IAfRabbitMq {
343
342
  }
344
343
  });
345
344
 
346
- newConnection.on('disconnect', ({ err }) => {
345
+ this.connection.on('disconnect', ({ err }) => {
346
+ // this.channel = null;
347
347
  this.consumersTags = [];
348
348
  debug('rabbit: connection closed');
349
349
  if (this.options?.disableReconnect) {
@@ -353,29 +353,25 @@ class RabbitMq implements IAfRabbitMq {
353
353
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
354
354
  }
355
355
  });
356
- newConnection.once('connect', async () => {
357
- debug('rabbit: connection established', { connectionPurpose, connection: this.connectionsMap[connectionPurpose] });
358
- this.connectionsMap[connectionPurpose].creatingConnection = false;
359
- this.em.emit(CONNECTION_CREATED_CONST, newConnection);
356
+
357
+ this.connection.once('connect', async () => {
358
+ debug('rabbit: connection established');
359
+ this.creatingConnection = false;
360
+ this.em.emit(CONNECTION_CREATED_CONST, connection);
360
361
  isResolved = true;
361
- resolve(newConnection);
362
+ resolve(connection);
362
363
  });
363
364
  });
364
365
  }
365
366
 
366
- async getNewChannel({
367
- name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
368
- }: newChannelOpts): Promise<ChannelWrapper> {
369
- let connection!: AmqpConnectionManager | undefined | null;
367
+ async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
368
+ let connection!: AmqpConnectionManager;
370
369
  try {
371
- connection = await this.getConnection(connectionPurpose);
370
+ connection = await this.getConnection();
372
371
  } catch (e) {
373
372
  logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
374
373
  throw e;
375
374
  }
376
- if (!connection) {
377
- throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
378
- }
379
375
  const channel = connection.createChannel({ ...options });
380
376
  once(channel, 'close').then((args) => {
381
377
  logger.error(`rabbit: channel ${name} closed`);
@@ -391,8 +387,7 @@ class RabbitMq implements IAfRabbitMq {
391
387
  }
392
388
  }
393
389
 
394
- async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
395
- debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.channel });
390
+ async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
396
391
  if (!this.publishChannelSetupPromise) {
397
392
  this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
398
393
  if (this.channel && !force) {
@@ -400,8 +395,7 @@ class RabbitMq implements IAfRabbitMq {
400
395
  }
401
396
 
402
397
  try {
403
- const channel = await this.getNewChannel({ connectionPurpose });
404
- debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
398
+ const channel = await this.getNewChannel({});
405
399
  channel.on('error', (err) => {
406
400
  logger.error('rabbit: channel error', { err });
407
401
  });
@@ -415,73 +409,102 @@ class RabbitMq implements IAfRabbitMq {
415
409
  return this.publishChannelSetupPromise;
416
410
  }
417
411
 
418
- async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
419
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
412
+ async assertExchange(exchangeName: string, options?: any) {
413
+ const channel: ChannelWrapper = await this.assertChannel();
414
+
420
415
  if (this.exchanges[exchangeName]) {
416
+ delete this.assertExchangePromises[exchangeName];
421
417
  return this.exchanges[exchangeName];
422
418
  }
423
- const exchange = await assertExchangeFanout(channel, exchangeName);
424
- this.exchanges[exchangeName] = exchange;
425
- return exchange;
419
+
420
+ if (this.assertExchangePromises[exchangeName]) {
421
+ return this.assertExchangePromises[exchangeName];
422
+ }
423
+
424
+ this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
425
+ this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
426
+ return this.exchanges[exchangeName];
426
427
  }
427
428
 
428
- async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
429
+ async getQueueLength(queue: string) {
429
430
  RabbitMq.validateName('queue', queue);
430
- const { connection } = this.connectionsMap[connectionPurpose];
431
431
  const { channel } = this;
432
432
  if (!channel) {
433
433
  throw new Error('channel is not defined');
434
434
  }
435
- debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
435
+ debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
436
436
  return channel?.checkQueue(queue);
437
437
  }
438
438
 
439
- private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
439
+ private async deleteQueue(queue: string) {
440
440
  RabbitMq.validateName('queue', queue);
441
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
441
+ const channel: ChannelWrapper = await this.assertChannel();
442
442
  logger.info('rabbit: deleting queue', { queue });
443
443
  const deleteQueueRes = await channel.deleteQueue(queue);
444
444
  debug('queue deleted', deleteQueueRes);
445
445
  return deleteQueueRes;
446
446
  }
447
447
 
448
- async bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose) {
449
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
448
+ async bindQueue(queue: string, exchange: string) {
449
+ const channel: ChannelWrapper = await this.assertChannel();
450
450
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
451
451
  return channel.bindQueue(queue, exchange, '');
452
452
  }
453
453
 
454
- async setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
454
+ async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
455
455
  let queue: Replies.AssertQueue;
456
- debug('rabbit: start setup queue', { queueName, connectionPurpose });
456
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
457
+ const localeOptions = {
458
+ ...options,
459
+ durable: true,
460
+ arguments: {
461
+ ...options?.arguments,
462
+ 'x-consumer-timeout': 1000 * 60 * 60 * 24,
463
+ 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
464
+ },
465
+ };
457
466
  try {
458
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
467
+ const channel: ChannelWrapper = await this.assertChannel();
459
468
  debug('assertQueue->channel.addSetup', { queueName });
460
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
469
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
461
470
  debug('assertQueue->channel.assertQueue', { queueName });
462
- queue = await channel.assertQueue(queueName, options);
471
+ queue = await channel.assertQueue(queueName, localeOptions);
463
472
  } catch (e) {
464
473
  logger.error('rabbit: assertQueue error', { queueName, options, error: e });
465
474
  if (!this.options?.dontRetryAssert) {
466
475
  debug('retrying assertQueue', { queueName });
467
- const channel = await this.assertChannel({ force: true, connectionPurpose });
468
- await this.deleteQueue(queueName, connectionPurpose);
476
+ const channel = await this.assertChannel({ force: true });
477
+ await this.deleteQueue(queueName);
469
478
 
470
479
  debug('retrying assertQueue->channel.addSetup', { queueName });
471
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
480
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
472
481
  debug('retrying assertQueue->channel.assertQueue', { queueName });
473
- queue = await channel.assertQueue(queueName, options);
482
+ queue = await channel.assertQueue(queueName, localeOptions);
474
483
  } else {
475
484
  throw e;
476
485
  }
477
486
  }
478
- debug('rabbit: done setup queue', { queueName, connectionPurpose });
479
- this.queues[queueName] = queueName;
487
+
488
+ this.queues[queueName] = queue;
480
489
  return queue;
481
490
  }
482
491
 
483
- async assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue) {
484
- debug('rabbit: start assert queue', { connectionPurpose, queueName });
492
+ static shouldUseQuorum(queueName: string): boolean {
493
+ const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
494
+
495
+ if (envQuorumQueuesWhitelist === '*') {
496
+ return true;
497
+ }
498
+
499
+ if (envQuorumQueuesWhitelist) {
500
+ const whitelist = envQuorumQueuesWhitelist.split(',');
501
+ return whitelist.includes(queueName);
502
+ }
503
+
504
+ return false;
505
+ }
506
+
507
+ async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
485
508
  RabbitMq.validateName('queue', queueName);
486
509
  if (this.queues[queueName]) {
487
510
  delete this.queueSetupPromises[queueName];
@@ -492,13 +515,12 @@ class RabbitMq implements IAfRabbitMq {
492
515
  return this.queueSetupPromises[queueName];
493
516
  }
494
517
 
495
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
496
- debug('rabbit: done assert queue', { connectionPurpose, queueName });
518
+ this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
497
519
  return this.queueSetupPromises[queueName];
498
520
  }
499
521
 
500
522
  private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
501
- const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
523
+ const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
502
524
  if (!isConsumerExist) {
503
525
  logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
504
526
  this.consumers.push({
@@ -547,10 +569,10 @@ class RabbitMq implements IAfRabbitMq {
547
569
  }
548
570
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
549
571
  }
550
- const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
572
+ const channel = await this.getNewChannel({});
551
573
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
552
- await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
553
- await confirmChannel.prefetch(limit, true);
574
+ const q = await this.assertQueue(queue, optionsWithDefaults);
575
+ await confirmChannel.prefetch(limit, false);
554
576
  const { consumerTag } = await confirmChannel.consume(
555
577
  queue,
556
578
  async (msg: ConsumeMessageOrNull) => {
@@ -560,6 +582,7 @@ class RabbitMq implements IAfRabbitMq {
560
582
 
561
583
  const traceId = msg.properties.headers[TRACING_HEADER];
562
584
  const userId = msg.properties.headers[USER_TRACING_HEADER];
585
+ const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
563
586
  const parsedMessage = RabbitMq.parseMsg(msg);
564
587
  const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
565
588
  const trace = newTrace(traceTypes.RABBIT);
@@ -585,7 +608,10 @@ class RabbitMq implements IAfRabbitMq {
585
608
  }
586
609
 
587
610
  if (auditContext) {
588
- await auditContext(queue);
611
+ await auditContext(queue, {
612
+ userId,
613
+ automationId,
614
+ });
589
615
  }
590
616
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
591
617
  if (!shouldConsume) {
@@ -633,13 +659,13 @@ class RabbitMq implements IAfRabbitMq {
633
659
  });
634
660
  }
635
661
 
636
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
662
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
637
663
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
638
664
  RabbitMq.validateName('exchange', exchange);
639
665
  RabbitMq.validateName('queue', queue);
640
666
  const { limit, deadMessageTtl } = optionsWithDefaults;
641
667
  await this.saveConsumer(queue, callback, options);
642
- const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
668
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
643
669
 
644
670
  return channel.addSetup(async (c: ConfirmChannel) => {
645
671
  const assertExchange = await assertExchangeFanout(c, exchange);
@@ -657,11 +683,11 @@ class RabbitMq implements IAfRabbitMq {
657
683
  });
658
684
  }
659
685
 
660
- async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
686
+ async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
661
687
  return wrapSetImmediate(async () => {
662
688
  RabbitMq.validateName('exchange', exchange);
663
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
664
- await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
689
+ const channel: ChannelWrapper = await this.assertChannel();
690
+ await this.assertExchange(exchange);
665
691
  await channel.publish(exchange, '',
666
692
  Buffer.from(JSON.stringify(content)),
667
693
  RabbitMq.getPublishOptions(customHeaders));
@@ -675,7 +701,7 @@ class RabbitMq implements IAfRabbitMq {
675
701
  customHeaders?: any,
676
702
  ): Promise<boolean | undefined> {
677
703
  try {
678
- await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
704
+ await this.assertChannel();
679
705
  } catch (e) {
680
706
  logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
681
707
  throw e;
@@ -683,7 +709,7 @@ class RabbitMq implements IAfRabbitMq {
683
709
 
684
710
  try {
685
711
  RabbitMq.validateName('queue', queue);
686
- await this.assertQueue(queue, ConnectionPurpose.Publish, options);
712
+ await this.assertQueue(queue, options);
687
713
  } catch (e) {
688
714
  logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
689
715
  throw e;
@@ -696,41 +722,34 @@ class RabbitMq implements IAfRabbitMq {
696
722
  debug(`rabbit: sending to queue ${queue}`, { res });
697
723
  return res;
698
724
  } catch (e) {
699
- logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
725
+ const isConnected = await this.isConnected();
726
+ logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
700
727
  throw e;
701
728
  }
702
729
  }
703
730
 
704
- async isConnected(): Promise<boolean> {
705
- const isEachConnectionConnected = await Promise.all(
706
- Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
707
- const { connection } = connectionData;
708
- if (connectionPurpose === ConnectionPurpose.Publish && !connection) {
709
- return true; // The connection hasn't been initialized yet, as no messages have been sent through it
710
- }
711
- const isConnected = connection?.isConnected();
712
- if (!isConnected) {
713
- logger.error('rabbit: isConnected - false');
714
- return false;
715
- }
716
- const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
717
- try {
718
- await Promise.all([
719
- channel.waitForConnect(),
720
- ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
721
- ]);
722
- } catch (e) {
723
- logger.error('rabbit: isConnected - false');
724
- return false;
725
- }
726
- logger.info('rabbit: isConnected - true');
727
- return true;
728
- }),
729
- );
730
- return isEachConnectionConnected.every((isConnected) => isConnected === true);
731
+ async isConnected() : Promise<boolean> {
732
+ const connection = await this.getConnection();
733
+ const isConnected = connection.isConnected();
734
+ if (!isConnected) {
735
+ logger.error('rabbit: isConnected - false');
736
+ return false;
737
+ }
738
+ const channel: any = await this.assertChannel();
739
+ try {
740
+ await Promise.all([
741
+ channel.waitForConnect(),
742
+ ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
743
+ ]);
744
+ } catch (e) {
745
+ logger.error('rabbit: isConnected - false');
746
+ return false;
747
+ }
748
+ logger.info('rabbit: isConnected - true');
749
+ return true;
731
750
  }
732
751
 
733
- async gracefulShutdown(signal: string): Promise<void> {
752
+ async gracefulShutdown(signal: string) : Promise<void> {
734
753
  const tagsNumber = this.consumersTags.length;
735
754
  logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
736
755
  const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
@@ -747,3 +766,7 @@ class RabbitMq implements IAfRabbitMq {
747
766
  }
748
767
 
749
768
  export default RabbitMq;
769
+
770
+ export {
771
+ sendCeleryTaskViaHttp,
772
+ };
@@ -0,0 +1,89 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import logger from '../logger';
3
+ // Environment configuration
4
+ const config = {
5
+ host: process.env.RABBITMQ_SERVICE_HOST || 'localhost',
6
+ username: process.env.RABBITMQ_USERNAME || 'guest',
7
+ password: process.env.RABBITMQ_PASSWORD || 'guest',
8
+ } as const;
9
+
10
+ // Type definitions
11
+ interface TaskData {
12
+ [key: string]: any;
13
+ }
14
+
15
+ interface TaskMessage {
16
+ task: string;
17
+ id: string;
18
+ args: TaskData[];
19
+ }
20
+
21
+ interface PublishPayload {
22
+ properties: {
23
+ // eslint-disable-next-line camelcase
24
+ delivery_mode: number;
25
+ // eslint-disable-next-line camelcase
26
+ content_type: string;
27
+ };
28
+ // eslint-disable-next-line camelcase
29
+ routing_key: string;
30
+ payload: string;
31
+ // eslint-disable-next-line camelcase
32
+ payload_encoding: string;
33
+ }
34
+
35
+ interface PublishResponse {
36
+ routed: boolean;
37
+ }
38
+
39
+ interface SendTaskOptions {
40
+ taskName: string;
41
+ queueName: string;
42
+ }
43
+
44
+ async function sendCeleryTaskViaHttp(
45
+ data: TaskData,
46
+ { taskName, queueName }: SendTaskOptions,
47
+ ): Promise<void> {
48
+ const apiUrl = `http://${config.host}:15672/api/exchanges/%2f/amq.default/publish`;
49
+
50
+ const message: TaskMessage = {
51
+ task: taskName,
52
+ id: randomUUID(),
53
+ args: [data],
54
+ };
55
+
56
+ const payload: PublishPayload = {
57
+ properties: {
58
+ delivery_mode: 2,
59
+ content_type: 'application/json',
60
+ },
61
+ routing_key: queueName,
62
+ payload: JSON.stringify(message),
63
+ payload_encoding: 'string',
64
+ };
65
+
66
+ try {
67
+ const response = await fetch(apiUrl, {
68
+ method: 'POST',
69
+ headers: {
70
+ 'Content-Type': 'application/json',
71
+ Authorization: `Basic ${Buffer.from(`${config.username}:${config.password}`).toString('base64')}`,
72
+ },
73
+ body: JSON.stringify(payload),
74
+ });
75
+
76
+ if (response.ok) {
77
+ const result: PublishResponse = await response.json();
78
+ logger.info('Successfully published message:', result);
79
+ } else {
80
+ logger.error(`Failed to publish message. Status code: ${response.status}`);
81
+ logger.error(`Response: ${await response.text()}`);
82
+ }
83
+ } catch (error) {
84
+ logger.error('Error sending request:', error instanceof Error ? error.message : String(error));
85
+ throw error;
86
+ }
87
+ }
88
+
89
+ export { sendCeleryTaskViaHttp, TaskData, SendTaskOptions };
package/src/lib/consts.ts CHANGED
@@ -3,6 +3,7 @@ export const DEFAULT_LOCK_TIMEOUT = 1000 * 5;
3
3
  export const RETRY_HEADER = 'x-retry-count';
4
4
  export const TRACING_HEADER = 'x-trace-id';
5
5
  export const USER_TRACING_HEADER = 'x-af-user-id';
6
+ export const AUTOMATION_ID_HEADER = 'x-af-automation-id';
6
7
  export const USER_OBJECT = 'userObject';
7
8
  export const DEFAULT_USE_CONSUME_WITH_LOCK = false;
8
9
  export const CONNECTION_CREATED_CONST = 'connectionCreated';
package/src/lib/types.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { AmqpConnectionManager } from 'amqp-connection-manager';
2
1
  import { ConsumeMessage, Options, Replies } from 'amqplib';
3
2
 
4
3
  export interface ExchangesCache {
@@ -13,6 +12,10 @@ export interface QueueSetupPromisesDictionary {
13
12
  [key: string]: Promise<Replies.AssertQueue> | undefined
14
13
  }
15
14
 
15
+ export interface AssertExchangePromisesDictionary {
16
+ [key: string]: Promise<Replies.AssertExchange> | undefined
17
+ }
18
+
16
19
  export type CustomMessageHeaders = {
17
20
  redisTimestampValidationKey?: string;
18
21
  }
@@ -56,13 +59,3 @@ export const CONSUMER_DEFAULT_OPTIONS: Options.Consume = {
56
59
  [HA_PROMOTE_ON_SHUTDOWN]: 'always',
57
60
  },
58
61
  };
59
-
60
- export type ConnectionData = {
61
- connection: AmqpConnectionManager | null;
62
- creatingConnection: boolean;
63
- };
64
-
65
- export enum ConnectionPurpose {
66
- Consume = 'consume',
67
- Publish = 'publish',
68
- }