@autofleet/rabbit 3.3.0-beta.1 → 3.3.0-beta.10

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
@@ -20,7 +20,8 @@ import getRedisInstance, { RedisConfig } from './lib/redis';
20
20
  import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
21
21
  import {
22
22
  AUTOMATION_ID_HEADER,
23
- ConnectionPurpose,
23
+ CONNECTION_CREATED_CONST,
24
+ CONNECTION_FAILED_CONST,
24
25
  DEFAULT_LOCK_TIMEOUT,
25
26
  DEFAULT_OPTIONS,
26
27
  RETRY_HEADER,
@@ -39,13 +40,13 @@ import {
39
40
  CONSUMER_DEFAULT_OPTIONS,
40
41
  QueueSetupPromisesDictionary,
41
42
  AssertExchangePromisesDictionary,
42
- ConnectionData,
43
43
  } from './lib/types';
44
44
 
45
45
  // const debug = nodeDebug('af-rabbitmq')
46
46
  const debug = logger.debug.bind(logger);
47
47
 
48
48
  const PUBLISH_TIMEOUT = 1000 * 10;
49
+
49
50
  export interface IAfRabbitMq {
50
51
  ack: any;
51
52
  nack: any;
@@ -85,13 +86,11 @@ type newChannelOpts = {
85
86
  name?: string;
86
87
  onClose?: null | ((args: any | null) => void);
87
88
  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;
95
94
  }
96
95
 
97
96
  type AfConsumer = {
@@ -103,7 +102,7 @@ type AfConsumer = {
103
102
  const HEARTBEAT = '60';
104
103
 
105
104
  class RabbitMq implements IAfRabbitMq {
106
- static parseMsg(msg: any): any {
105
+ static parseMsg(msg: any) : any {
107
106
  let { content } = msg;
108
107
  content = content.toString();
109
108
 
@@ -144,19 +143,18 @@ class RabbitMq implements IAfRabbitMq {
144
143
 
145
144
  RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
146
145
 
147
- publishChannel: ChannelWrapper | null;
146
+ channel: ChannelWrapper | null;
148
147
 
149
148
  publishChannelSetupPromise: Promise<ChannelWrapper> | null;
150
149
 
151
- blockReconnect: boolean | null | undefined;
150
+ blockReconnect: boolean | null | undefined
152
151
 
153
- connectionsMap: {
154
- [ConnectionPurpose.Consume]: ConnectionData;
155
- [ConnectionPurpose.Publish]: ConnectionData;
156
- };
152
+ connection: AmqpConnectionManager | null | undefined
157
153
 
158
154
  em: EventEmitter;
159
155
 
156
+ creatingConnection: boolean;
157
+
160
158
  exchanges: ExchangesCache;
161
159
 
162
160
  queues: QueuesCache;
@@ -176,30 +174,48 @@ class RabbitMq implements IAfRabbitMq {
176
174
 
177
175
  private consumers: Array<AfConsumer> = [];
178
176
 
179
- constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
177
+ private doesVHostExist = false;
178
+
179
+ private vhost = 'quorum-vhost';
180
+
181
+ // TODO:[QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
182
+ oldChannel: ChannelWrapper | null;
183
+
184
+ oldPublishChannelSetupPromise: Promise<ChannelWrapper> | null;
185
+
186
+ oldBlockReconnect: boolean | null | undefined
187
+
188
+ oldConnection: AmqpConnectionManager | null | undefined
189
+
190
+ oldEm: EventEmitter;
191
+
192
+ oldCreatingConnection: boolean;
193
+
194
+ oldExchanges: ExchangesCache;
195
+
196
+ oldQueues: QueuesCache;
197
+
198
+ oldQueueSetupPromises: QueueSetupPromisesDictionary;
199
+
200
+ oldAssertExchangePromises: AssertExchangePromisesDictionary;
201
+
202
+ oldConsumersTags: Array<[ConfirmChannel, string]>;
203
+
204
+ private oldConsumers: Array<AfConsumer> = [];
205
+
206
+ constructor(options: AfRabbitOptions = {}, redisConfig?: RedisConfig) {
180
207
  this.em = new EventEmitter();
181
- this.publishChannel = null;
208
+ this.channel = null;
182
209
  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
- };
210
+ this.connection = null;
211
+ this.creatingConnection = false;
197
212
  this.exchanges = {};
198
213
  this.queues = {};
199
214
  this.queueSetupPromises = {};
200
215
  this.assertExchangePromises = {};
201
216
  this.consumers = [];
202
217
  this.options = options;
218
+
203
219
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
204
220
  if (this.redisClient) {
205
221
  this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
@@ -214,8 +230,74 @@ class RabbitMq implements IAfRabbitMq {
214
230
  await this.gracefulShutdown('SIGINT');
215
231
  });
216
232
  }
233
+
234
+ // TODO: [QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
235
+ this.oldEm = new EventEmitter();
236
+ this.oldChannel = null;
237
+ this.oldPublishChannelSetupPromise = null;
238
+ this.oldConnection = null;
239
+ this.oldCreatingConnection = false;
240
+ this.oldExchanges = {};
241
+ this.oldQueues = {};
242
+ this.oldQueueSetupPromises = {};
243
+ this.oldAssertExchangePromises = {};
244
+ this.oldConsumers = [];
245
+ this.oldConsumersTags = [];
217
246
  }
218
247
 
248
+ private assertVHost = async () => {
249
+ if (this.doesVHostExist) {
250
+ return;
251
+ }
252
+
253
+ const username = process.env.RABBITMQ_USERNAME || 'guest';
254
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
255
+ const credentials = Buffer.from(`${username}:${password}`).toString('base64');
256
+ const headers = {
257
+ Authorization: `Basic ${credentials}`,
258
+ 'Content-Type': 'application/json',
259
+ };
260
+
261
+ const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
262
+
263
+ const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
264
+
265
+ try {
266
+ const response = await fetch(url, {
267
+ method: 'GET',
268
+ headers,
269
+ });
270
+
271
+ if (response.status === 200) {
272
+ this.doesVHostExist = true;
273
+ logger.info('Vhost exists', { vhost: this.vhost });
274
+ return;
275
+ }
276
+
277
+ if (response.status !== 404) {
278
+ logger.error('Failed to check vhost', { response });
279
+ throw new RabbitError('Failed to check vhost');
280
+ }
281
+
282
+ const createResponse = await fetch(url, {
283
+ method: 'PUT',
284
+ headers,
285
+ body: JSON.stringify({ default_queue_type: 'quorum' }),
286
+ });
287
+
288
+ if (!createResponse.ok) {
289
+ logger.error('Failed to create vhost', { response: createResponse });
290
+ throw new RabbitError('Failed to create vhost');
291
+ }
292
+
293
+ this.doesVHostExist = true;
294
+ logger.info('Vhost created', { vhost: this.vhost });
295
+ } catch (error) {
296
+ logger.error('Failed to check or create vhost', { error });
297
+ throw error;
298
+ }
299
+ };
300
+
219
301
  private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
220
302
  if (msg) {
221
303
  const { properties: { headers } } = msg;
@@ -230,7 +312,7 @@ class RabbitMq implements IAfRabbitMq {
230
312
  return false;
231
313
  }
232
314
 
233
- public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
315
+ public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
234
316
  if (msg) {
235
317
  debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
236
318
  await channel.ack(msg);
@@ -256,20 +338,20 @@ class RabbitMq implements IAfRabbitMq {
256
338
  userMsg: ConsumeMessageOrNull,
257
339
  {
258
340
  skipRetry = false,
259
- }: NackOptions = {},
260
- ): Promise<any> => {
341
+ }: NackOptions = { },
342
+ ) : Promise<any> => {
261
343
  await this.unlockRedisIfNeeded(releaseLock);
262
344
  if (channel && msg) {
263
345
  if (
264
346
  !skipRetry
265
347
  && (
266
- !msg.properties.headers[RETRY_HEADER]
267
- || parseInt(msg.properties.headers[RETRY_HEADER], 10) < options.retries
348
+ !msg.properties.headers?.[RETRY_HEADER]
349
+ || parseInt(msg.properties.headers?.[RETRY_HEADER], 10) < options.retries
268
350
  )
269
351
  ) {
270
352
  await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
271
353
  ...msg.properties.headers,
272
- [RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
354
+ [RETRY_HEADER]: msg.properties.headers?.[RETRY_HEADER]
273
355
  ? msg.properties.headers[RETRY_HEADER] + 1
274
356
  : 1,
275
357
  });
@@ -277,7 +359,7 @@ class RabbitMq implements IAfRabbitMq {
277
359
  const deadQueue = `${queue}-dead`;
278
360
  await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
279
361
  ...msg.properties.headers,
280
- [RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
362
+ [RETRY_HEADER]: msg.properties.headers?.[RETRY_HEADER]
281
363
  ? msg.properties.headers[RETRY_HEADER] + 1
282
364
  : 1,
283
365
  });
@@ -291,36 +373,30 @@ class RabbitMq implements IAfRabbitMq {
291
373
  }
292
374
  }
293
375
 
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];
376
+ async getConnection() {
377
+ return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
302
378
  if (this.blockReconnect) {
303
379
  debug('rabbit: block reconnect');
304
380
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
305
381
  // @ts-ignore
306
382
  return resolve();
307
383
  }
308
- if (connection !== null) {
309
- if (this.options?.disableReconnect || connection?.isConnected()) {
384
+ if (this.connection !== null) {
385
+ if (this.options?.disableReconnect || this.connection?.isConnected()) {
310
386
  debug('rabbit: connection - is connected');
311
387
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
312
388
  // @ts-ignore
313
- return resolve(connection);
389
+ return resolve(this.connection);
314
390
  }
315
391
  debug('rabbit: connection - reconnecting');
316
392
  }
317
- if (creatingConnection) {
393
+ if (this.creatingConnection) {
318
394
  debug('rabbit: creating connection emi');
319
- this.em.once(connectionCreatedEventName, resolve);
320
- this.em.once(connectionFailedEventName, reject);
395
+ this.em.once(CONNECTION_CREATED_CONST, resolve);
396
+ this.em.once(CONNECTION_FAILED_CONST, reject);
321
397
  return;
322
398
  }
323
- this.connectionsMap[connectionPurpose].creatingConnection = true;
399
+ this.creatingConnection = true;
324
400
  let isResolved = false;
325
401
 
326
402
  // It is import to use it as a function and not as a variable
@@ -332,38 +408,36 @@ class RabbitMq implements IAfRabbitMq {
332
408
  const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
333
409
 
334
410
  debug('rabbit: creating connection', { host, userName, HEARTBEAT });
335
-
336
- return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
411
+ return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
337
412
  };
338
413
 
339
414
  const defaultUrls = findServers();
340
- const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
415
+ const connection: AmqpConnectionManager = await connect(defaultUrls, {
341
416
  findServers,
342
417
  });
343
418
 
344
- this.connectionsMap[connectionPurpose].connection = newConnection;
345
- logger.info(`rabbit: created new connection ${connectionPurpose}`);
346
-
347
- newConnection.on('error', (err) => {
419
+ this.connection = connection;
420
+ this.connection.on('error', (err) => {
348
421
  logger.error('rabbit: connection error', { err });
349
422
  if (!isResolved) {
350
423
  isResolved = true;
351
424
  reject(err);
352
- this.em.emit(connectionFailedEventName, err);
425
+ this.em.emit(CONNECTION_FAILED_CONST, err);
353
426
  }
354
427
  });
355
428
 
356
- newConnection.on('connectFailed', (err) => {
429
+ this.connection.on('connectFailed', (err) => {
357
430
  this.consumersTags = [];
358
- logger.error('rabbit: connection connectFailed', { err });
431
+ logger.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
359
432
  if (!isResolved) {
360
433
  isResolved = true;
361
434
  reject(err);
362
- this.em.emit(connectionFailedEventName, err);
435
+ this.em.emit(CONNECTION_FAILED_CONST, err);
363
436
  }
364
437
  });
365
438
 
366
- newConnection.on('disconnect', ({ err }) => {
439
+ this.connection.on('disconnect', ({ err }) => {
440
+ // this.channel = null;
367
441
  this.consumersTags = [];
368
442
  debug('rabbit: connection closed');
369
443
  if (this.options?.disableReconnect) {
@@ -373,28 +447,25 @@ class RabbitMq implements IAfRabbitMq {
373
447
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
374
448
  }
375
449
  });
376
- newConnection.once('connect', async () => {
377
- this.connectionsMap[connectionPurpose].creatingConnection = false;
378
- this.em.emit(connectionCreatedEventName, newConnection);
450
+
451
+ this.connection.once('connect', async () => {
452
+ debug('rabbit: connection established');
453
+ this.creatingConnection = false;
454
+ this.em.emit(CONNECTION_CREATED_CONST, connection);
379
455
  isResolved = true;
380
- resolve(newConnection);
456
+ resolve(connection);
381
457
  });
382
458
  });
383
459
  }
384
460
 
385
- async getNewChannel({
386
- name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
387
- }: newChannelOpts): Promise<ChannelWrapper> {
388
- let connection!: AmqpConnectionManager | undefined | null;
461
+ async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
462
+ let connection!: AmqpConnectionManager;
389
463
  try {
390
- connection = await this.getConnection(connectionPurpose);
464
+ connection = await this.getConnection();
391
465
  } catch (e) {
392
466
  logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
393
467
  throw e;
394
468
  }
395
- if (!connection) {
396
- throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
397
- }
398
469
  const channel = connection.createChannel({ ...options });
399
470
  once(channel, 'close').then((args) => {
400
471
  logger.error(`rabbit: channel ${name} closed`);
@@ -410,23 +481,19 @@ class RabbitMq implements IAfRabbitMq {
410
481
  }
411
482
  }
412
483
 
413
- async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
414
- debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
484
+ async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
415
485
  if (!this.publishChannelSetupPromise) {
416
486
  this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
417
- if (this.publishChannel && !force) {
418
- return resolve(this.publishChannel);
487
+ if (this.channel && !force) {
488
+ return resolve(this.channel);
419
489
  }
420
490
 
421
491
  try {
422
- const channel = await this.getNewChannel({ connectionPurpose });
423
- debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
492
+ const channel = await this.getNewChannel({});
424
493
  channel.on('error', (err) => {
425
494
  logger.error('rabbit: channel error', { err });
426
495
  });
427
- if (connectionPurpose === ConnectionPurpose.Publish) {
428
- this.publishChannel = channel;
429
- }
496
+ this.channel = channel;
430
497
  resolve(channel);
431
498
  } catch (e) {
432
499
  reject(e);
@@ -436,8 +503,9 @@ class RabbitMq implements IAfRabbitMq {
436
503
  return this.publishChannelSetupPromise;
437
504
  }
438
505
 
439
- async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
440
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
506
+ async assertExchange(exchangeName: string, options?: any) {
507
+ const channel: ChannelWrapper = await this.assertChannel();
508
+
441
509
  if (this.exchanges[exchangeName]) {
442
510
  delete this.assertExchangePromises[exchangeName];
443
511
  return this.exchanges[exchangeName];
@@ -452,57 +520,58 @@ class RabbitMq implements IAfRabbitMq {
452
520
  return this.exchanges[exchangeName];
453
521
  }
454
522
 
455
- async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
523
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
524
+ async getQueueLength(queue: string) {
456
525
  RabbitMq.validateName('queue', queue);
457
- const { connection } = this.connectionsMap[connectionPurpose];
458
- const { publishChannel } = this;
459
- if (!publishChannel) {
460
- throw new Error('channel is not defined');
526
+ const { oldChannel: channel } = this;
527
+ if (!channel) {
528
+ throw new RabbitError('channel is not defined');
461
529
  }
462
- debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
463
- return publishChannel?.checkQueue(queue);
530
+ debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
531
+ return channel?.checkQueue(queue);
464
532
  }
465
533
 
466
- private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
534
+ private async deleteQueue(queue: string) {
467
535
  RabbitMq.validateName('queue', queue);
468
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
536
+ const channel: ChannelWrapper = await this.assertChannel();
469
537
  logger.info('rabbit: deleting queue', { queue });
470
538
  const deleteQueueRes = await channel.deleteQueue(queue);
471
539
  debug('queue deleted', deleteQueueRes);
472
540
  return deleteQueueRes;
473
541
  }
474
542
 
543
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
475
544
  async bindQueue(queue: string, exchange: string) {
476
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
545
+ const channel: ChannelWrapper = await this.assertChannelOld();
477
546
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
478
547
  return channel.bindQueue(queue, exchange, '');
479
548
  }
480
549
 
481
550
  async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
482
551
  let queue: Replies.AssertQueue;
483
- const connectionPurpose = ConnectionPurpose.Publish;
484
- const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
485
552
  const localeOptions = {
486
553
  ...options,
487
554
  durable: true,
488
555
  arguments: {
489
556
  ...options?.arguments,
490
557
  'x-consumer-timeout': 1000 * 60 * 60 * 24,
491
- 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
558
+ 'x-queue-type': 'quorum',
492
559
  },
493
560
  };
494
561
  try {
495
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
562
+ const channel: ChannelWrapper = await this.assertChannel();
496
563
  debug('assertQueue->channel.addSetup', { queueName });
497
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
564
+ await channel.addSetup(async (setupChannel: ConfirmChannel) => {
565
+ await setupChannel.assertQueue(queueName, localeOptions);
566
+ });
498
567
  debug('assertQueue->channel.assertQueue', { queueName });
499
568
  queue = await channel.assertQueue(queueName, localeOptions);
500
569
  } catch (e) {
501
570
  logger.error('rabbit: assertQueue error', { queueName, options, error: e });
502
571
  if (!this.options?.dontRetryAssert) {
503
572
  debug('retrying assertQueue', { queueName });
504
- const channel = await this.assertChannel({ force: true, connectionPurpose });
505
- await this.deleteQueue(queueName, connectionPurpose);
573
+ const channel = await this.assertChannel({ force: true });
574
+ await this.deleteQueue(queueName);
506
575
 
507
576
  debug('retrying assertQueue->channel.addSetup', { queueName });
508
577
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
@@ -512,11 +581,11 @@ class RabbitMq implements IAfRabbitMq {
512
581
  throw e;
513
582
  }
514
583
  }
515
-
516
584
  this.queues[queueName] = queue;
517
585
  return queue;
518
586
  }
519
587
 
588
+ // TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
520
589
  static shouldUseQuorum(queueName: string): boolean {
521
590
  const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
522
591
 
@@ -532,8 +601,7 @@ class RabbitMq implements IAfRabbitMq {
532
601
  return false;
533
602
  }
534
603
 
535
- async assertQueue(queueName: string, options?: Options.AssertQueue) {
536
- debug('rabbit: start assert queue', { queueName });
604
+ async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
537
605
  RabbitMq.validateName('queue', queueName);
538
606
  if (this.queues[queueName]) {
539
607
  delete this.queueSetupPromises[queueName];
@@ -545,12 +613,11 @@ class RabbitMq implements IAfRabbitMq {
545
613
  }
546
614
 
547
615
  this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
548
- debug('rabbit: done assert queue', { queueName });
549
616
  return this.queueSetupPromises[queueName];
550
617
  }
551
618
 
552
619
  private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
553
- const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
620
+ const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
554
621
  if (!isConsumerExist) {
555
622
  logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
556
623
  this.consumers.push({
@@ -561,10 +628,27 @@ class RabbitMq implements IAfRabbitMq {
561
628
  }
562
629
  }
563
630
 
631
+ // Used by the microservices to consume messages from the queue
564
632
  async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
633
+ // TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
634
+ if (options?.isQuorumQueue !== false) {
635
+ await this.assertVHost();
636
+ await this.consumeNew(queue, callback, options);
637
+ }
638
+
639
+ await this.consumeOld(queue, callback, options);
640
+ }
641
+
642
+ // TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
643
+ async consumeNew(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
565
644
  await this.consumeFromRabbit(queue, callback, options);
566
645
  }
567
646
 
647
+ // TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
648
+ async consumeOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
649
+ await this.consumeFromRabbitOld(queue, callback, options);
650
+ }
651
+
568
652
  private async lockRedisIfNeeded(msg: any, options: any) {
569
653
  const { properties: { headers } } = msg;
570
654
  const timestamp = headers?.creationTimestamp;
@@ -595,12 +679,13 @@ class RabbitMq implements IAfRabbitMq {
595
679
  } = optionsWithDefaults;
596
680
  if (useConsumeWithLock) {
597
681
  if (!this.redisLock) {
598
- throw new Error('Usage of consumeWithLock requires RedisInstance');
682
+ throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
599
683
  }
600
684
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
601
685
  }
602
- const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
686
+ const channel = await this.getNewChannel({});
603
687
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
688
+ logger.info(`rabbit: channel.addSetup before ${queue} assertQueueOld`);
604
689
  const q = await this.assertQueue(queue, optionsWithDefaults);
605
690
  await confirmChannel.prefetch(limit, false);
606
691
  const { consumerTag } = await confirmChannel.consume(
@@ -610,9 +695,9 @@ class RabbitMq implements IAfRabbitMq {
610
695
  return null;
611
696
  }
612
697
 
613
- const traceId = msg.properties.headers[TRACING_HEADER];
614
- const userId = msg.properties.headers[USER_TRACING_HEADER];
615
- const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
698
+ const traceId = msg.properties.headers?.[TRACING_HEADER];
699
+ const userId = msg.properties.headers?.[USER_TRACING_HEADER];
700
+ const automationId = msg.properties.headers?.[AUTOMATION_ID_HEADER];
616
701
  const parsedMessage = RabbitMq.parseMsg(msg);
617
702
  const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
618
703
  const trace = newTrace(traceTypes.RABBIT);
@@ -689,22 +774,45 @@ class RabbitMq implements IAfRabbitMq {
689
774
  });
690
775
  }
691
776
 
692
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
777
+ // Used by the microservices to consume messages from the exchange
778
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
693
779
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
694
780
  RabbitMq.validateName('exchange', exchange);
695
781
  RabbitMq.validateName('queue', queue);
696
782
  const { limit, deadMessageTtl } = optionsWithDefaults;
697
- await this.saveConsumer(queue, callback, options);
698
- const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
783
+ // TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
784
+ if (options?.isQuorumQueue !== false) {
785
+ await this.assertVHost();
786
+ await this.saveConsumer(queue, callback, options);
787
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
788
+
789
+ await channel.addSetup(async (c: ConfirmChannel) => {
790
+ const assertExchange = await assertExchangeFanout(c, exchange);
791
+ await c.assertQueue(queue);
792
+ this.exchanges[exchange] = assertExchange;
793
+ await c.prefetch(limit, false);
794
+ return Promise.all([
795
+ c.bindQueue(queue, exchange, ''),
796
+ this.consumeNew(
797
+ queue,
798
+ callback,
799
+ options,
800
+ ),
801
+ ]);
802
+ });
803
+ }
699
804
 
700
- return channel.addSetup(async (c: ConfirmChannel) => {
805
+ // TODO: [QUORUM-PHASE-3] Delete the old implementation
806
+ await this.saveConsumerOld(queue, callback, options);
807
+ const channelOld: ChannelWrapper = await this.getNewChannelOld({ name: `consume-exchange-${exchange}-queue-${queue}-old` });
808
+ await channelOld.addSetup(async (c: ConfirmChannel) => {
701
809
  const assertExchange = await assertExchangeFanout(c, exchange);
702
- await c.assertQueue(queue);
703
- this.exchanges[exchange] = assertExchange;
810
+ // await c.assertQueue(queue);
811
+ this.oldExchanges[exchange] = assertExchange;
704
812
  await c.prefetch(limit, false);
705
813
  return Promise.all([
706
- c.bindQueue(queue, exchange, ''),
707
- this.consume(
814
+ // c.bindQueue(queue, exchange, ''),
815
+ this.consumeOld(
708
816
  queue,
709
817
  callback,
710
818
  options,
@@ -713,18 +821,21 @@ class RabbitMq implements IAfRabbitMq {
713
821
  });
714
822
  }
715
823
 
716
- async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
717
- debug('rabbit: start publish msg');
824
+ // Used by the microservices to publish messages to the exchange
825
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
826
+ async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
718
827
  return wrapSetImmediate(async () => {
719
828
  RabbitMq.validateName('exchange', exchange);
720
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
721
- await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
829
+ const channel: ChannelWrapper = await this.assertChannelOld();
830
+ await this.assertExchangeOld(exchange);
722
831
  await channel.publish(exchange, '',
723
832
  Buffer.from(JSON.stringify(content)),
724
833
  RabbitMq.getPublishOptions(customHeaders));
725
834
  });
726
835
  }
727
836
 
837
+ // Used by the microservices to send messages to the queue
838
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
728
839
  async sendToQueue(
729
840
  queue: string,
730
841
  content: any,
@@ -732,7 +843,7 @@ class RabbitMq implements IAfRabbitMq {
732
843
  customHeaders?: any,
733
844
  ): Promise<boolean | undefined> {
734
845
  try {
735
- await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
846
+ await this.assertChannelOld();
736
847
  } catch (e) {
737
848
  logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
738
849
  throw e;
@@ -740,71 +851,395 @@ class RabbitMq implements IAfRabbitMq {
740
851
 
741
852
  try {
742
853
  RabbitMq.validateName('queue', queue);
743
- await this.assertQueue(queue, options);
854
+ await this.assertQueueOld(queue, options);
744
855
  } catch (e) {
745
856
  logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
746
857
  throw e;
747
858
  }
748
859
 
749
860
  try {
750
- const res = await this.publishChannel?.sendToQueue(queue,
861
+ const res = await this.oldChannel?.sendToQueue(queue,
751
862
  Buffer.from(JSON.stringify(content)),
752
863
  RabbitMq.getPublishOptions(customHeaders));
753
864
  debug(`rabbit: sending to queue ${queue}`, { res });
754
865
  return res;
755
866
  } catch (e) {
756
- logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
867
+ const isConnected = await this.isConnected();
868
+ logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
757
869
  throw e;
758
870
  }
759
871
  }
760
872
 
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;
873
+ // TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
874
+ async isConnected() : Promise<boolean> {
875
+ const connection = await this.getConnectionOld();
876
+ const isConnected = connection.isConnected();
877
+ if (!isConnected) {
878
+ logger.error('rabbit: isConnected - false');
879
+ return false;
880
+ }
881
+ const channel: any = await this.assertChannelOld();
882
+ try {
883
+ await Promise.all([
884
+ channel.waitForConnect(),
885
+ ...this.oldConsumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
886
+ ]);
887
+ } catch (e) {
888
+ logger.error('rabbit: isConnected - false');
889
+ return false;
890
+ }
891
+ logger.info('rabbit: isConnected - true');
892
+ return true;
893
+ }
894
+
895
+ async gracefulShutdown(signal: string) : Promise<void> {
896
+ // TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
897
+ const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
898
+ logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
899
+ const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
900
+ const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
901
+ // Clean the array to avoid race
902
+ this.consumersTags = [];
903
+ this.oldConsumersTags = [];
904
+ const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
905
+ const rejected = results.filter((p) => p.status === 'rejected');
906
+ if (rejected.length > 0) {
907
+ logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
908
+ } else {
909
+ logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
910
+ }
911
+ }
912
+
913
+ private async consumeFromRabbitOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
914
+ const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
915
+ RabbitMq.validateName('queue', queue);
916
+ this.saveConsumerOld(queue, callback, options);
917
+ const uniqueId = randomUUID();
918
+ const {
919
+ limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
920
+ } = optionsWithDefaults;
921
+ if (useConsumeWithLock) {
922
+ if (!this.redisLock) {
923
+ throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
924
+ }
925
+ logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
926
+ }
927
+ const channel = await this.getNewChannelOld({});
928
+ return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
929
+ const q = await this.assertQueueOld(queue, optionsWithDefaults);
930
+ await confirmChannel.prefetch(limit, false);
931
+ const { consumerTag } = await confirmChannel.consume(
932
+ queue,
933
+ async (msg: ConsumeMessageOrNull) => {
934
+ if (!msg) {
935
+ return null;
771
936
  }
772
- logger.info('rabbit: isConnected - true', { connectionPurpose });
773
937
 
774
- if (connectionPurpose === ConnectionPurpose.Publish) {
775
- const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
938
+ const traceId = msg.properties.headers?.[TRACING_HEADER];
939
+ const userId = msg.properties.headers?.[USER_TRACING_HEADER];
940
+ const automationId = msg.properties.headers?.[AUTOMATION_ID_HEADER];
941
+ const parsedMessage = RabbitMq.parseMsg(msg);
942
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
943
+ const trace = newTrace(traceTypes.RABBIT);
944
+ // setting also outbreak trace as part of legacy code
945
+ const outbreakTrace = outbreak.newTrace(traceTypes.RABBIT);
946
+ // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
947
+ // and we don't want to fail the flow because of it
948
+ if (userId && enableRabbitTrace) {
776
949
  try {
777
950
  await Promise.all([
778
- channel.waitForConnect(),
779
- ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
951
+ createOrSetRabbitTrace(trace, userId),
952
+ createOrSetRabbitTrace(outbreakTrace, userId),
780
953
  ]);
781
954
  } catch (e) {
782
- logger.error('rabbit: isConnected - false');
783
- return false;
955
+ logger.error('rabbit: failed to setRabbitTrace', { userId, e });
956
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
784
957
  }
785
958
  }
959
+
960
+ if (traceId) {
961
+ (trace as any)?.context?.set(TRACING_HEADER, traceId);
962
+ (outbreakTrace as any)?.context.set(TRACING_HEADER, traceId);
963
+ }
964
+
965
+ if (auditContext) {
966
+ await auditContext(queue, {
967
+ userId,
968
+ automationId,
969
+ });
970
+ }
971
+ const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
972
+ if (!shouldConsume) {
973
+ await this.unlockRedisIfNeeded(releaseLock);
974
+ return this.ack(confirmChannel, msg)(msg);
975
+ }
976
+
977
+ let messageAcked = false;
978
+ // setting the localAck function to be used in the callback
979
+
980
+ const localAck = async () => {
981
+ if (messageAcked) {
982
+ return;
983
+ }
984
+ messageAcked = true;
985
+ return this.ack(confirmChannel, msg, true, releaseLock)(msg);
986
+ };
987
+
988
+ const localNack = async (_: ConsumeMessageOrNull, nackOptions: NackOptions = {}) => {
989
+ if (messageAcked) {
990
+ return;
991
+ }
992
+ debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
993
+ messageAcked = true;
994
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
995
+ };
996
+
997
+ try {
998
+ await callback(
999
+ parsedMessage,
1000
+ localAck,
1001
+ localNack,
1002
+ );
1003
+ } catch (e) {
1004
+ await localNack(msg);
1005
+ }
1006
+ }, CONSUMER_DEFAULT_OPTIONS,
1007
+ );
1008
+ if (!consumerTag) {
1009
+ logger.error(`rabbit: failed to consume from queue ${queue}`);
1010
+ } else {
1011
+ logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
1012
+ this.oldConsumersTags.push([confirmChannel, consumerTag]);
1013
+ }
1014
+ });
1015
+ }
1016
+
1017
+ // TODO: [QUORUM-PHASE-3] Delete all the function under this line (getNewChannelOld, getConnectionOld, assertQueueOld, setupQueueOld, saveConsumerOld)
1018
+ async getNewChannelOld({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
1019
+ let connection!: AmqpConnectionManager;
1020
+ try {
1021
+ connection = await this.getConnectionOld();
1022
+ } catch (e) {
1023
+ logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
1024
+ throw e;
1025
+ }
1026
+ const channel = connection.createChannel({ ...options });
1027
+ once(channel, 'close').then((args) => {
1028
+ logger.error(`rabbit: channel ${name} closed`);
1029
+ onClose?.(args);
1030
+ });
1031
+ try {
1032
+ await once(channel, 'connect');
1033
+ debug(`rabbit: channel ${name} CONNECTED`);
1034
+ return channel;
1035
+ } catch (err) {
1036
+ logger.error(`rabbit: channel error ${name} error`, { err });
1037
+ throw err;
1038
+ }
1039
+ }
1040
+
1041
+ async getConnectionOld() {
1042
+ return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
1043
+ if (this.oldBlockReconnect) {
1044
+ debug('rabbit: block reconnect');
1045
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1046
+ // @ts-ignore
1047
+ return resolve();
1048
+ }
1049
+ if (this.oldConnection !== null) {
1050
+ if (this.options?.disableReconnect || this.oldConnection?.isConnected()) {
1051
+ debug('rabbit: connection - is connected');
1052
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1053
+ // @ts-ignore
1054
+ return resolve(this.oldConnection);
1055
+ }
1056
+ debug('rabbit: connection - reconnecting');
1057
+ }
1058
+ if (this.oldCreatingConnection) {
1059
+ debug('rabbit: creating connection emi');
1060
+ this.oldEm.once(CONNECTION_CREATED_CONST, resolve);
1061
+ this.oldEm.once(CONNECTION_FAILED_CONST, reject);
1062
+ return;
1063
+ }
1064
+ this.oldCreatingConnection = true;
1065
+ let isResolved = false;
1066
+
1067
+ // It is import to use it as a function and not as a variable
1068
+ // because of k8s changes the env variables
1069
+ // and we want to use the new values
1070
+ const findServers = () => {
1071
+ const userName = process.env.RABBITMQ_USERNAME || 'guest';
1072
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
1073
+ const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
1074
+
1075
+ debug('rabbit: creating connection', { host, userName, HEARTBEAT });
1076
+
1077
+ return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
1078
+ };
1079
+
1080
+ const defaultUrls = findServers();
1081
+ const connection: AmqpConnectionManager = await connect(defaultUrls, {
1082
+ findServers,
1083
+ });
1084
+
1085
+ this.oldConnection = connection;
1086
+ this.oldConnection.on('error', (err) => {
1087
+ logger.error('rabbit: connection error', { err });
1088
+ if (!isResolved) {
1089
+ isResolved = true;
1090
+ reject(err);
1091
+ this.oldEm.emit(CONNECTION_FAILED_CONST, err);
1092
+ }
1093
+ });
1094
+
1095
+ this.oldConnection.on('connectFailed', (err) => {
1096
+ this.oldConsumersTags = [];
1097
+ logger.error('rabbit: connection connectFailed', { err });
1098
+ if (!isResolved) {
1099
+ isResolved = true;
1100
+ reject(err);
1101
+ this.oldEm.emit(CONNECTION_FAILED_CONST, err);
1102
+ }
1103
+ });
1104
+
1105
+ this.oldConnection.on('disconnect', ({ err }) => {
1106
+ this.oldConsumersTags = [];
1107
+ debug('rabbit: connection closed');
1108
+ if (this.options?.disableReconnect) {
1109
+ logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
1110
+ this.oldBlockReconnect = true;
786
1111
  } else {
787
- logger.info('rabbit: connection hasnt initialized yet', { connectionPurpose });
1112
+ logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
788
1113
  }
789
- return true;
790
- }),
791
- );
792
- return isEachConnectionConnected.every((isConnected) => isConnected === true);
1114
+ });
1115
+
1116
+ this.oldConnection.once('connect', async () => {
1117
+ debug('rabbit: connection established');
1118
+ this.oldCreatingConnection = false;
1119
+ this.oldEm.emit(CONNECTION_CREATED_CONST, connection);
1120
+ isResolved = true;
1121
+ resolve(connection);
1122
+ });
1123
+ });
793
1124
  }
794
1125
 
795
- async gracefulShutdown(signal: string): Promise<void> {
796
- const tagsNumber = this.consumersTags.length;
797
- logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
798
- const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
799
- // Clean the array to avoid race
800
- this.consumersTags = [];
801
- const results = await Promise.allSettled(cancelTagPromises);
802
- const rejected = results.filter((p) => p.status === 'rejected');
803
- if (rejected.length > 0) {
804
- logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
805
- } else {
806
- logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
1126
+ private saveConsumerOld(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
1127
+ const isConsumerExist :boolean = this.oldConsumers.some((consumer) => consumer.queue === queue);
1128
+ if (!isConsumerExist) {
1129
+ logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
1130
+ this.oldConsumers.push({
1131
+ queue,
1132
+ callback,
1133
+ options,
1134
+ });
1135
+ }
1136
+ }
1137
+
1138
+ async assertQueueOld(queueName: string, options?: Options.AssertQueue): Promise<any> {
1139
+ RabbitMq.validateName('queue', queueName);
1140
+ if (this.oldQueues[queueName]) {
1141
+ delete this.oldQueueSetupPromises[queueName];
1142
+ return this.oldQueues[queueName];
1143
+ }
1144
+
1145
+ if (this.oldQueueSetupPromises[queueName]) {
1146
+ return this.oldQueueSetupPromises[queueName];
807
1147
  }
1148
+
1149
+ this.oldQueueSetupPromises[queueName] = this.setupQueueOld(queueName, options);
1150
+ return this.oldQueueSetupPromises[queueName];
1151
+ }
1152
+
1153
+ async setupQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
1154
+ // let queue: Replies.AssertQueue;
1155
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
1156
+ const localeOptions = {
1157
+ ...options,
1158
+ durable: true,
1159
+ arguments: {
1160
+ ...options?.arguments,
1161
+ 'x-consumer-timeout': 1000 * 60 * 60 * 24,
1162
+ 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
1163
+ },
1164
+ };
1165
+ try {
1166
+ const channel: ChannelWrapper = await this.assertChannelOld();
1167
+ debug('assertQueue->channel.addSetup', { queueName });
1168
+ // await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
1169
+ debug('assertQueue->channel.assertQueue', { queueName });
1170
+ // queue = await channel.assertQueue(queueName, localeOptions);
1171
+ } catch (e) {
1172
+ logger.error('rabbit: assertQueue error', { queueName, options, error: e });
1173
+ if (!this.options?.dontRetryAssert) {
1174
+ debug('retrying assertQueue', { queueName });
1175
+ const channel = await this.assertChannelOld({ force: true });
1176
+ await this.deleteQueueOld(queueName);
1177
+
1178
+ debug('retrying assertQueue->channel.addSetup', { queueName });
1179
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
1180
+ debug('retrying assertQueue->channel.assertQueue', { queueName });
1181
+ // queue = await channel.assertQueue(queueName, localeOptions);
1182
+ } else {
1183
+ throw e;
1184
+ }
1185
+ }
1186
+
1187
+ const queue = {
1188
+ queue: 'bla bla queue',
1189
+ messageCount: 0,
1190
+ consumerCount: 2,
1191
+ };
1192
+
1193
+ this.oldQueues[queueName] = queue;
1194
+ return queue;
1195
+ }
1196
+
1197
+ async assertChannelOld({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
1198
+ if (!this.oldPublishChannelSetupPromise) {
1199
+ this.oldPublishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
1200
+ if (this.oldChannel && !force) {
1201
+ return resolve(this.oldChannel);
1202
+ }
1203
+
1204
+ try {
1205
+ const channel = await this.getNewChannelOld({});
1206
+ channel.on('error', (err) => {
1207
+ logger.error('rabbit: channel error', { err });
1208
+ });
1209
+ this.oldChannel = channel;
1210
+ resolve(channel);
1211
+ } catch (e) {
1212
+ reject(e);
1213
+ }
1214
+ });
1215
+ }
1216
+ return this.oldPublishChannelSetupPromise;
1217
+ }
1218
+
1219
+ private async deleteQueueOld(queue: string) {
1220
+ RabbitMq.validateName('queue', queue);
1221
+ const channel: ChannelWrapper = await this.assertChannelOld();
1222
+ logger.info('rabbit: deleting queue', { queue });
1223
+ const deleteQueueRes = await channel.deleteQueue(queue);
1224
+ debug('queue deleted', deleteQueueRes);
1225
+ return deleteQueueRes;
1226
+ }
1227
+
1228
+ async assertExchangeOld(exchangeName: string, options?: any) {
1229
+ const channel: ChannelWrapper = await this.assertChannelOld();
1230
+
1231
+ if (this.oldExchanges[exchangeName]) {
1232
+ delete this.oldAssertExchangePromises[exchangeName];
1233
+ return this.oldExchanges[exchangeName];
1234
+ }
1235
+
1236
+ if (this.oldAssertExchangePromises[exchangeName]) {
1237
+ return this.oldAssertExchangePromises[exchangeName];
1238
+ }
1239
+
1240
+ this.oldAssertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
1241
+ this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
1242
+ return this.oldExchanges[exchangeName];
808
1243
  }
809
1244
  }
810
1245