@autofleet/rabbit 3.3.0-beta.11 → 3.3.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -56,57 +56,10 @@ class RabbitMq {
56
56
  },
57
57
  };
58
58
  }
59
- constructor(options = {}, redisConfig) {
59
+ constructor(options, redisConfig) {
60
60
  this.DISCONNECT_MSG = 'rabbit: connection disconnect';
61
61
  this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
62
62
  this.consumers = [];
63
- this.doesVHostExist = false;
64
- this.vhost = 'quorum-vhost';
65
- this.oldConsumers = [];
66
- this.assertVHost = async () => {
67
- if (this.doesVHostExist) {
68
- return;
69
- }
70
- const username = process.env.RABBITMQ_USERNAME || 'guest';
71
- const password = process.env.RABBITMQ_PASSWORD || 'guest';
72
- const credentials = Buffer.from(`${username}:${password}`).toString('base64');
73
- const headers = {
74
- Authorization: `Basic ${credentials}`,
75
- 'Content-Type': 'application/json',
76
- };
77
- const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
78
- const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
79
- try {
80
- const response = await fetch(url, {
81
- method: 'GET',
82
- headers,
83
- });
84
- if (response.status === 200) {
85
- this.doesVHostExist = true;
86
- logger_1.default.info('Vhost exists', { vhost: this.vhost });
87
- return;
88
- }
89
- if (response.status !== 404) {
90
- logger_1.default.error('Failed to check vhost', { response });
91
- throw new rabbitError_1.default('Failed to check vhost');
92
- }
93
- const createResponse = await fetch(url, {
94
- method: 'PUT',
95
- headers,
96
- body: JSON.stringify({ default_queue_type: 'quorum' }),
97
- });
98
- if (!createResponse.ok) {
99
- logger_1.default.error('Failed to create vhost', { response: createResponse });
100
- throw new rabbitError_1.default('Failed to create vhost');
101
- }
102
- this.doesVHostExist = true;
103
- logger_1.default.info('Vhost created', { vhost: this.vhost });
104
- }
105
- catch (error) {
106
- logger_1.default.error('Failed to check or create vhost', { error });
107
- throw error;
108
- }
109
- };
110
63
  this.shouldConsumeMessageByTimestamp = async (msg) => {
111
64
  if (msg) {
112
65
  const { properties: { headers } } = msg;
@@ -164,10 +117,22 @@ class RabbitMq {
164
117
  }
165
118
  };
166
119
  this.em = new events_1.EventEmitter();
167
- this.channel = null;
120
+ this.publishChannel = null;
168
121
  this.publishChannelSetupPromise = null;
169
- this.connection = null;
170
- this.creatingConnection = false;
122
+ this.connectionsMap = {
123
+ [consts_1.ConnectionPurpose.Consume]: {
124
+ connection: null,
125
+ creatingConnection: false,
126
+ connectionCreatedEventName: 'consumeConnectionCreated',
127
+ connectionFailedEventName: 'consumeConnectionFailed',
128
+ },
129
+ [consts_1.ConnectionPurpose.Publish]: {
130
+ connection: null,
131
+ creatingConnection: false,
132
+ connectionCreatedEventName: 'publishConnectionCreated',
133
+ connectionFailedEventName: 'publishConnectionFailed',
134
+ },
135
+ };
171
136
  this.exchanges = {};
172
137
  this.queues = {};
173
138
  this.queueSetupPromises = {};
@@ -188,43 +153,32 @@ class RabbitMq {
188
153
  await this.gracefulShutdown('SIGINT');
189
154
  });
190
155
  }
191
- // TODO: [QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
192
- this.oldEm = new events_1.EventEmitter();
193
- this.oldChannel = null;
194
- this.oldPublishChannelSetupPromise = null;
195
- this.oldConnection = null;
196
- this.oldCreatingConnection = false;
197
- this.oldExchanges = {};
198
- this.oldQueues = {};
199
- this.oldQueueSetupPromises = {};
200
- this.oldAssertExchangePromises = {};
201
- this.oldConsumers = [];
202
- this.oldConsumersTags = [];
203
156
  }
204
- async getConnection() {
157
+ async getConnection(connectionPurpose) {
205
158
  return new Promise(async (resolve, reject) => {
159
+ const { connection, creatingConnection, connectionCreatedEventName, connectionFailedEventName, } = this.connectionsMap[connectionPurpose];
206
160
  if (this.blockReconnect) {
207
161
  debug('rabbit: block reconnect');
208
162
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
209
163
  // @ts-ignore
210
164
  return resolve();
211
165
  }
212
- if (this.connection !== null) {
213
- if (this.options?.disableReconnect || this.connection?.isConnected()) {
166
+ if (connection !== null) {
167
+ if (this.options?.disableReconnect || connection?.isConnected()) {
214
168
  debug('rabbit: connection - is connected');
215
169
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
216
170
  // @ts-ignore
217
- return resolve(this.connection);
171
+ return resolve(connection);
218
172
  }
219
173
  debug('rabbit: connection - reconnecting');
220
174
  }
221
- if (this.creatingConnection) {
175
+ if (creatingConnection) {
222
176
  debug('rabbit: creating connection emi');
223
- this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
224
- this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
177
+ this.em.once(connectionCreatedEventName, resolve);
178
+ this.em.once(connectionFailedEventName, reject);
225
179
  return;
226
180
  }
227
- this.creatingConnection = true;
181
+ this.connectionsMap[connectionPurpose].creatingConnection = true;
228
182
  let isResolved = false;
229
183
  // It is import to use it as a function and not as a variable
230
184
  // because of k8s changes the env variables
@@ -234,32 +188,32 @@ class RabbitMq {
234
188
  const password = process.env.RABBITMQ_PASSWORD || 'guest';
235
189
  const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
236
190
  debug('rabbit: creating connection', { host, userName, HEARTBEAT });
237
- return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
191
+ return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
238
192
  };
239
193
  const defaultUrls = findServers();
240
- const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
194
+ const newConnection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
241
195
  findServers,
242
196
  });
243
- this.connection = connection;
244
- this.connection.on('error', (err) => {
197
+ this.connectionsMap[connectionPurpose].connection = newConnection;
198
+ logger_1.default.info(`rabbit: created new connection ${connectionPurpose}`);
199
+ newConnection.on('error', (err) => {
245
200
  logger_1.default.error('rabbit: connection error', { err });
246
201
  if (!isResolved) {
247
202
  isResolved = true;
248
203
  reject(err);
249
- this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
204
+ this.em.emit(connectionFailedEventName, err);
250
205
  }
251
206
  });
252
- this.connection.on('connectFailed', (err) => {
207
+ newConnection.on('connectFailed', (err) => {
253
208
  this.consumersTags = [];
254
- logger_1.default.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
209
+ logger_1.default.error('rabbit: connection connectFailed', { err });
255
210
  if (!isResolved) {
256
211
  isResolved = true;
257
212
  reject(err);
258
- this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
213
+ this.em.emit(connectionFailedEventName, err);
259
214
  }
260
215
  });
261
- this.connection.on('disconnect', ({ err }) => {
262
- // this.channel = null;
216
+ newConnection.on('disconnect', ({ err }) => {
263
217
  this.consumersTags = [];
264
218
  debug('rabbit: connection closed');
265
219
  if (this.options?.disableReconnect) {
@@ -270,24 +224,26 @@ class RabbitMq {
270
224
  logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
271
225
  }
272
226
  });
273
- this.connection.once('connect', async () => {
274
- debug('rabbit: connection established');
275
- this.creatingConnection = false;
276
- this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
227
+ newConnection.once('connect', async () => {
228
+ this.connectionsMap[connectionPurpose].creatingConnection = false;
229
+ this.em.emit(connectionCreatedEventName, newConnection);
277
230
  isResolved = true;
278
- resolve(connection);
231
+ resolve(newConnection);
279
232
  });
280
233
  });
281
234
  }
282
- async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
235
+ async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connectionPurpose = consts_1.ConnectionPurpose.Consume, }) {
283
236
  let connection;
284
237
  try {
285
- connection = await this.getConnection();
238
+ connection = await this.getConnection(connectionPurpose);
286
239
  }
287
240
  catch (e) {
288
241
  logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
289
242
  throw e;
290
243
  }
244
+ if (!connection) {
245
+ throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
246
+ }
291
247
  const channel = connection.createChannel({ ...options });
292
248
  (0, events_1.once)(channel, 'close').then((args) => {
293
249
  logger_1.default.error(`rabbit: channel ${name} closed`);
@@ -303,18 +259,22 @@ class RabbitMq {
303
259
  throw err;
304
260
  }
305
261
  }
306
- async assertChannel({ force = false } = {}) {
262
+ async assertChannel({ force = false, connectionPurpose = consts_1.ConnectionPurpose.Consume }) {
263
+ debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
307
264
  if (!this.publishChannelSetupPromise) {
308
265
  this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
309
- if (this.channel && !force) {
310
- return resolve(this.channel);
266
+ if (this.publishChannel && !force) {
267
+ return resolve(this.publishChannel);
311
268
  }
312
269
  try {
313
- const channel = await this.getNewChannel({});
270
+ const channel = await this.getNewChannel({ connectionPurpose });
271
+ debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
314
272
  channel.on('error', (err) => {
315
273
  logger_1.default.error('rabbit: channel error', { err });
316
274
  });
317
- this.channel = channel;
275
+ if (connectionPurpose === consts_1.ConnectionPurpose.Publish) {
276
+ this.publishChannel = channel;
277
+ }
318
278
  resolve(channel);
319
279
  }
320
280
  catch (e) {
@@ -324,8 +284,8 @@ class RabbitMq {
324
284
  }
325
285
  return this.publishChannelSetupPromise;
326
286
  }
327
- async assertExchange(exchangeName, options) {
328
- const channel = await this.assertChannel();
287
+ async assertExchange(exchangeName, options = { connectionPurpose: consts_1.ConnectionPurpose.Consume }) {
288
+ const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
329
289
  if (this.exchanges[exchangeName]) {
330
290
  delete this.assertExchangePromises[exchangeName];
331
291
  return this.exchanges[exchangeName];
@@ -337,47 +297,46 @@ class RabbitMq {
337
297
  this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
338
298
  return this.exchanges[exchangeName];
339
299
  }
340
- // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
341
- async getQueueLength(queue) {
300
+ async getQueueLength(queue, connectionPurpose = consts_1.ConnectionPurpose.Consume) {
342
301
  RabbitMq.validateName('queue', queue);
343
- const { oldChannel: channel } = this;
344
- if (!channel) {
345
- throw new rabbitError_1.default('channel is not defined');
302
+ const { connection } = this.connectionsMap[connectionPurpose];
303
+ const { publishChannel } = this;
304
+ if (!publishChannel) {
305
+ throw new Error('channel is not defined');
346
306
  }
347
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
348
- return channel?.checkQueue(queue);
307
+ debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
308
+ return publishChannel?.checkQueue(queue);
349
309
  }
350
- async deleteQueue(queue) {
310
+ async deleteQueue(queue, connectionPurpose) {
351
311
  RabbitMq.validateName('queue', queue);
352
- const channel = await this.assertChannel();
312
+ const channel = await this.assertChannel({ connectionPurpose });
353
313
  logger_1.default.info('rabbit: deleting queue', { queue });
354
314
  const deleteQueueRes = await channel.deleteQueue(queue);
355
315
  debug('queue deleted', deleteQueueRes);
356
316
  return deleteQueueRes;
357
317
  }
358
- // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
359
318
  async bindQueue(queue, exchange) {
360
- const channel = await this.assertChannelOld();
319
+ const channel = await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
361
320
  await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
362
321
  return channel.bindQueue(queue, exchange, '');
363
322
  }
364
323
  async setupQueue(queueName, options) {
365
324
  let queue;
325
+ const connectionPurpose = consts_1.ConnectionPurpose.Publish;
326
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
366
327
  const localeOptions = {
367
328
  ...options,
368
329
  durable: true,
369
330
  arguments: {
370
331
  ...options?.arguments,
371
332
  'x-consumer-timeout': 1000 * 60 * 60 * 24,
372
- 'x-queue-type': 'quorum',
333
+ 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
373
334
  },
374
335
  };
375
336
  try {
376
- const channel = await this.assertChannel();
337
+ const channel = await this.assertChannel({ connectionPurpose });
377
338
  debug('assertQueue->channel.addSetup', { queueName });
378
- await channel.addSetup(async (setupChannel) => {
379
- await setupChannel.assertQueue(queueName, localeOptions);
380
- });
339
+ await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
381
340
  debug('assertQueue->channel.assertQueue', { queueName });
382
341
  queue = await channel.assertQueue(queueName, localeOptions);
383
342
  }
@@ -385,8 +344,8 @@ class RabbitMq {
385
344
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
386
345
  if (!this.options?.dontRetryAssert) {
387
346
  debug('retrying assertQueue', { queueName });
388
- const channel = await this.assertChannel({ force: true });
389
- await this.deleteQueue(queueName);
347
+ const channel = await this.assertChannel({ force: true, connectionPurpose });
348
+ await this.deleteQueue(queueName, connectionPurpose);
390
349
  debug('retrying assertQueue->channel.addSetup', { queueName });
391
350
  await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
392
351
  debug('retrying assertQueue->channel.assertQueue', { queueName });
@@ -399,7 +358,6 @@ class RabbitMq {
399
358
  this.queues[queueName] = queue;
400
359
  return queue;
401
360
  }
402
- // TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
403
361
  static shouldUseQuorum(queueName) {
404
362
  const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
405
363
  if (envQuorumQueuesWhitelist === '*') {
@@ -412,6 +370,7 @@ class RabbitMq {
412
370
  return false;
413
371
  }
414
372
  async assertQueue(queueName, options) {
373
+ debug('rabbit: start assert queue', { queueName });
415
374
  RabbitMq.validateName('queue', queueName);
416
375
  if (this.queues[queueName]) {
417
376
  delete this.queueSetupPromises[queueName];
@@ -421,6 +380,7 @@ class RabbitMq {
421
380
  return this.queueSetupPromises[queueName];
422
381
  }
423
382
  this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
383
+ debug('rabbit: done assert queue', { queueName });
424
384
  return this.queueSetupPromises[queueName];
425
385
  }
426
386
  saveConsumer(queue, callback, options) {
@@ -434,23 +394,9 @@ class RabbitMq {
434
394
  });
435
395
  }
436
396
  }
437
- // Used by the microservices to consume messages from the queue
438
397
  async consume(queue, callback, options) {
439
- // TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
440
- if (options?.isQuorumQueue !== false) {
441
- await this.assertVHost();
442
- await this.consumeNew(queue, callback, options);
443
- }
444
- await this.consumeOld(queue, callback, options);
445
- }
446
- // TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
447
- async consumeNew(queue, callback, options) {
448
398
  await this.consumeFromRabbit(queue, callback, options);
449
399
  }
450
- // TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
451
- async consumeOld(queue, callback, options) {
452
- await this.consumeFromRabbitOld(queue, callback, options);
453
- }
454
400
  async lockRedisIfNeeded(msg, options) {
455
401
  const { properties: { headers } } = msg;
456
402
  const timestamp = headers?.creationTimestamp;
@@ -473,13 +419,12 @@ class RabbitMq {
473
419
  const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
474
420
  if (useConsumeWithLock) {
475
421
  if (!this.redisLock) {
476
- throw new rabbitError_1.default('Usage of consumeWithLock requires RedisInstance');
422
+ throw new Error('Usage of consumeWithLock requires RedisInstance');
477
423
  }
478
424
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
479
425
  }
480
- const channel = await this.getNewChannel({});
426
+ const channel = await this.getNewChannel({ connectionPurpose: consts_1.ConnectionPurpose.Consume });
481
427
  return channel.addSetup(async (confirmChannel) => {
482
- throw new Error('Dummy assertQueue error');
483
428
  const q = await this.assertQueue(queue, optionsWithDefaults);
484
429
  await confirmChannel.prefetch(limit, false);
485
430
  const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
@@ -556,57 +501,36 @@ class RabbitMq {
556
501
  }
557
502
  });
558
503
  }
559
- // Used by the microservices to consume messages from the exchange
560
504
  async consumeFromExchange(queue, exchange, callback, options) {
561
505
  const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
562
506
  RabbitMq.validateName('exchange', exchange);
563
507
  RabbitMq.validateName('queue', queue);
564
508
  const { limit, deadMessageTtl } = optionsWithDefaults;
565
- // TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
566
- if (options?.isQuorumQueue !== false) {
567
- await this.assertVHost();
568
- await this.saveConsumer(queue, callback, options);
569
- const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
570
- await channel.addSetup(async (c) => {
571
- const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
572
- await c.assertQueue(queue);
573
- this.exchanges[exchange] = assertExchange;
574
- await c.prefetch(limit, false);
575
- return Promise.all([
576
- c.bindQueue(queue, exchange, ''),
577
- this.consumeNew(queue, callback, options),
578
- ]);
579
- });
580
- }
581
- // TODO: [QUORUM-PHASE-3] Delete the old implementation
582
- await this.saveConsumerOld(queue, callback, options);
583
- const channelOld = await this.getNewChannelOld({ name: `consume-exchange-${exchange}-queue-${queue}-old` });
584
- await channelOld.addSetup(async (c) => {
509
+ await this.saveConsumer(queue, callback, options);
510
+ const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
511
+ return channel.addSetup(async (c) => {
585
512
  const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
586
513
  await c.assertQueue(queue);
587
- this.oldExchanges[exchange] = assertExchange;
514
+ this.exchanges[exchange] = assertExchange;
588
515
  await c.prefetch(limit, false);
589
516
  return Promise.all([
590
517
  c.bindQueue(queue, exchange, ''),
591
- this.consumeOld(queue, callback, options),
518
+ this.consume(queue, callback, options),
592
519
  ]);
593
520
  });
594
521
  }
595
- // Used by the microservices to publish messages to the exchange
596
- // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
597
522
  async publish(exchange, content, customHeaders) {
523
+ debug('rabbit: start publish msg');
598
524
  return (0, utils_1.wrapSetImmediate)(async () => {
599
525
  RabbitMq.validateName('exchange', exchange);
600
- const channel = await this.assertChannelOld();
601
- await this.assertExchangeOld(exchange);
526
+ const channel = await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
527
+ await this.assertExchange(exchange, { connectionPurpose: consts_1.ConnectionPurpose.Publish });
602
528
  await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
603
529
  });
604
530
  }
605
- // Used by the microservices to send messages to the queue
606
- // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
607
531
  async sendToQueue(queue, content, options, customHeaders) {
608
532
  try {
609
- await this.assertChannelOld();
533
+ await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
610
534
  }
611
535
  catch (e) {
612
536
  logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
@@ -614,55 +538,56 @@ class RabbitMq {
614
538
  }
615
539
  try {
616
540
  RabbitMq.validateName('queue', queue);
617
- await this.assertQueueOld(queue, options);
541
+ await this.assertQueue(queue, options);
618
542
  }
619
543
  catch (e) {
620
544
  logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
621
545
  throw e;
622
546
  }
623
547
  try {
624
- const res = await this.oldChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
548
+ const res = await this.publishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
625
549
  debug(`rabbit: sending to queue ${queue}`, { res });
626
550
  return res;
627
551
  }
628
552
  catch (e) {
629
- const isConnected = await this.isConnected();
630
- logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
553
+ logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
631
554
  throw e;
632
555
  }
633
556
  }
634
- // TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
635
557
  async isConnected() {
636
- const connection = await this.getConnectionOld();
637
- const isConnected = connection.isConnected();
638
- if (!isConnected) {
639
- logger_1.default.error('rabbit: isConnected - false');
640
- return false;
641
- }
642
- const channel = await this.assertChannelOld();
643
- try {
644
- await Promise.all([
645
- channel.waitForConnect(),
646
- ...this.oldConsumers.map((c) => channel.checkQueue(c.queue)),
647
- ]);
648
- }
649
- catch (e) {
650
- logger_1.default.error('rabbit: isConnected - false');
651
- return false;
652
- }
653
- logger_1.default.info('rabbit: isConnected - true');
654
- return true;
558
+ debug('rabbit: start is connected');
559
+ const isEachConnectionConnected = await Promise.all(Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
560
+ const { connection } = connectionData;
561
+ const isConnected = connection?.isConnected();
562
+ if (!isConnected) {
563
+ logger_1.default.error('rabbit: isConnected - false', { connectionPurpose });
564
+ return false;
565
+ }
566
+ logger_1.default.info('rabbit: isConnected - true', { connectionPurpose });
567
+ if (connectionPurpose === consts_1.ConnectionPurpose.Publish) {
568
+ const channel = await this.assertChannel({ connectionPurpose: connectionPurpose });
569
+ try {
570
+ await Promise.all([
571
+ channel.waitForConnect(),
572
+ ...this.consumers.map((c) => channel.checkQueue(c.queue)),
573
+ ]);
574
+ }
575
+ catch (e) {
576
+ logger_1.default.error('rabbit: isConnected - false');
577
+ return false;
578
+ }
579
+ }
580
+ return true;
581
+ }));
582
+ return isEachConnectionConnected.every((isConnected) => isConnected === true);
655
583
  }
656
584
  async gracefulShutdown(signal) {
657
- // TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
658
- const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
585
+ const tagsNumber = this.consumersTags.length;
659
586
  logger_1.default.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
660
587
  const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
661
- const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
662
588
  // Clean the array to avoid race
663
589
  this.consumersTags = [];
664
- this.oldConsumersTags = [];
665
- const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
590
+ const results = await Promise.allSettled(cancelTagPromises);
666
591
  const rejected = results.filter((p) => p.status === 'rejected');
667
592
  if (rejected.length > 0) {
668
593
  logger_1.default.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
@@ -671,300 +596,6 @@ class RabbitMq {
671
596
  logger_1.default.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
672
597
  }
673
598
  }
674
- async consumeFromRabbitOld(queue, callback, options) {
675
- const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
676
- RabbitMq.validateName('queue', queue);
677
- this.saveConsumerOld(queue, callback, options);
678
- const uniqueId = (0, node_crypto_1.randomUUID)();
679
- const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
680
- if (useConsumeWithLock) {
681
- if (!this.redisLock) {
682
- throw new rabbitError_1.default('Usage of consumeWithLock requires RedisInstance');
683
- }
684
- logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
685
- }
686
- const channel = await this.getNewChannelOld({});
687
- return channel.addSetup(async (confirmChannel) => {
688
- const q = await this.assertQueueOld(queue, optionsWithDefaults);
689
- await confirmChannel.prefetch(limit, false);
690
- const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
691
- if (!msg) {
692
- return null;
693
- }
694
- const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
695
- const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
696
- const automationId = msg.properties.headers[consts_1.AUTOMATION_ID_HEADER];
697
- const parsedMessage = RabbitMq.parseMsg(msg);
698
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
699
- const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
700
- // setting also outbreak trace as part of legacy code
701
- const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
702
- // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
703
- // and we don't want to fail the flow because of it
704
- if (userId && enableRabbitTrace) {
705
- try {
706
- await Promise.all([
707
- (0, zehut_1.createOrSetRabbitTrace)(trace, userId),
708
- (0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
709
- ]);
710
- }
711
- catch (e) {
712
- logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
713
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
714
- }
715
- }
716
- if (traceId) {
717
- trace?.context?.set(consts_1.TRACING_HEADER, traceId);
718
- outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
719
- }
720
- if (auditContext) {
721
- await auditContext(queue, {
722
- userId,
723
- automationId,
724
- });
725
- }
726
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
727
- if (!shouldConsume) {
728
- await this.unlockRedisIfNeeded(releaseLock);
729
- return this.ack(confirmChannel, msg)(msg);
730
- }
731
- let messageAcked = false;
732
- // setting the localAck function to be used in the callback
733
- const localAck = async () => {
734
- if (messageAcked) {
735
- return;
736
- }
737
- messageAcked = true;
738
- return this.ack(confirmChannel, msg, true, releaseLock)(msg);
739
- };
740
- const localNack = async (_, nackOptions = {}) => {
741
- if (messageAcked) {
742
- return;
743
- }
744
- debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
745
- messageAcked = true;
746
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
747
- };
748
- try {
749
- await callback(parsedMessage, localAck, localNack);
750
- }
751
- catch (e) {
752
- await localNack(msg);
753
- }
754
- }, types_1.CONSUMER_DEFAULT_OPTIONS);
755
- if (!consumerTag) {
756
- logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
757
- }
758
- else {
759
- logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
760
- this.oldConsumersTags.push([confirmChannel, consumerTag]);
761
- }
762
- });
763
- }
764
- // TODO: [QUORUM-PHASE-3] Delete all the function under this line (getNewChannelOld, getConnectionOld, assertQueueOld, setupQueueOld, saveConsumerOld)
765
- async getNewChannelOld({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
766
- let connection;
767
- try {
768
- connection = await this.getConnectionOld();
769
- }
770
- catch (e) {
771
- logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
772
- throw e;
773
- }
774
- const channel = connection.createChannel({ ...options });
775
- (0, events_1.once)(channel, 'close').then((args) => {
776
- logger_1.default.error(`rabbit: channel ${name} closed`);
777
- onClose?.(args);
778
- });
779
- try {
780
- await (0, events_1.once)(channel, 'connect');
781
- debug(`rabbit: channel ${name} CONNECTED`);
782
- return channel;
783
- }
784
- catch (err) {
785
- logger_1.default.error(`rabbit: channel error ${name} error`, { err });
786
- throw err;
787
- }
788
- }
789
- async getConnectionOld() {
790
- return new Promise(async (resolve, reject) => {
791
- if (this.oldBlockReconnect) {
792
- debug('rabbit: block reconnect');
793
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
794
- // @ts-ignore
795
- return resolve();
796
- }
797
- if (this.oldConnection !== null) {
798
- if (this.options?.disableReconnect || this.oldConnection?.isConnected()) {
799
- debug('rabbit: connection - is connected');
800
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
801
- // @ts-ignore
802
- return resolve(this.oldConnection);
803
- }
804
- debug('rabbit: connection - reconnecting');
805
- }
806
- if (this.oldCreatingConnection) {
807
- debug('rabbit: creating connection emi');
808
- this.oldEm.once(consts_1.CONNECTION_CREATED_CONST, resolve);
809
- this.oldEm.once(consts_1.CONNECTION_FAILED_CONST, reject);
810
- return;
811
- }
812
- this.oldCreatingConnection = true;
813
- let isResolved = false;
814
- // It is import to use it as a function and not as a variable
815
- // because of k8s changes the env variables
816
- // and we want to use the new values
817
- const findServers = () => {
818
- const userName = process.env.RABBITMQ_USERNAME || 'guest';
819
- const password = process.env.RABBITMQ_PASSWORD || 'guest';
820
- const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
821
- debug('rabbit: creating connection', { host, userName, HEARTBEAT });
822
- return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
823
- };
824
- const defaultUrls = findServers();
825
- const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
826
- findServers,
827
- });
828
- this.oldConnection = connection;
829
- this.oldConnection.on('error', (err) => {
830
- logger_1.default.error('rabbit: connection error', { err });
831
- if (!isResolved) {
832
- isResolved = true;
833
- reject(err);
834
- this.oldEm.emit(consts_1.CONNECTION_FAILED_CONST, err);
835
- }
836
- });
837
- this.oldConnection.on('connectFailed', (err) => {
838
- this.oldConsumersTags = [];
839
- logger_1.default.error('rabbit: connection connectFailed', { err });
840
- if (!isResolved) {
841
- isResolved = true;
842
- reject(err);
843
- this.oldEm.emit(consts_1.CONNECTION_FAILED_CONST, err);
844
- }
845
- });
846
- this.oldConnection.on('disconnect', ({ err }) => {
847
- this.oldConsumersTags = [];
848
- debug('rabbit: connection closed');
849
- if (this.options?.disableReconnect) {
850
- logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
851
- this.oldBlockReconnect = true;
852
- }
853
- else {
854
- logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
855
- }
856
- });
857
- this.oldConnection.once('connect', async () => {
858
- debug('rabbit: connection established');
859
- this.oldCreatingConnection = false;
860
- this.oldEm.emit(consts_1.CONNECTION_CREATED_CONST, connection);
861
- isResolved = true;
862
- resolve(connection);
863
- });
864
- });
865
- }
866
- saveConsumerOld(queue, callback, options) {
867
- const isConsumerExist = this.oldConsumers.some((consumer) => consumer.queue === queue);
868
- if (!isConsumerExist) {
869
- logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
870
- this.oldConsumers.push({
871
- queue,
872
- callback,
873
- options,
874
- });
875
- }
876
- }
877
- async assertQueueOld(queueName, options) {
878
- RabbitMq.validateName('queue', queueName);
879
- if (this.oldQueues[queueName]) {
880
- delete this.oldQueueSetupPromises[queueName];
881
- return this.oldQueues[queueName];
882
- }
883
- if (this.oldQueueSetupPromises[queueName]) {
884
- return this.oldQueueSetupPromises[queueName];
885
- }
886
- this.oldQueueSetupPromises[queueName] = this.setupQueueOld(queueName, options);
887
- return this.oldQueueSetupPromises[queueName];
888
- }
889
- async setupQueueOld(queueName, options) {
890
- let queue;
891
- const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
892
- const localeOptions = {
893
- ...options,
894
- durable: true,
895
- arguments: {
896
- ...options?.arguments,
897
- 'x-consumer-timeout': 1000 * 60 * 60 * 24,
898
- 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
899
- },
900
- };
901
- try {
902
- const channel = await this.assertChannelOld();
903
- debug('assertQueue->channel.addSetup', { queueName });
904
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
905
- debug('assertQueue->channel.assertQueue', { queueName });
906
- queue = await channel.assertQueue(queueName, localeOptions);
907
- }
908
- catch (e) {
909
- logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
910
- if (!this.options?.dontRetryAssert) {
911
- debug('retrying assertQueue', { queueName });
912
- const channel = await this.assertChannelOld({ force: true });
913
- await this.deleteQueueOld(queueName);
914
- debug('retrying assertQueue->channel.addSetup', { queueName });
915
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
916
- debug('retrying assertQueue->channel.assertQueue', { queueName });
917
- queue = await channel.assertQueue(queueName, localeOptions);
918
- }
919
- else {
920
- throw e;
921
- }
922
- }
923
- this.oldQueues[queueName] = queue;
924
- return queue;
925
- }
926
- async assertChannelOld({ force = false } = {}) {
927
- if (!this.oldPublishChannelSetupPromise) {
928
- this.oldPublishChannelSetupPromise = new Promise(async (resolve, reject) => {
929
- if (this.oldChannel && !force) {
930
- return resolve(this.oldChannel);
931
- }
932
- try {
933
- const channel = await this.getNewChannelOld({});
934
- channel.on('error', (err) => {
935
- logger_1.default.error('rabbit: channel error', { err });
936
- });
937
- this.oldChannel = channel;
938
- resolve(channel);
939
- }
940
- catch (e) {
941
- reject(e);
942
- }
943
- });
944
- }
945
- return this.oldPublishChannelSetupPromise;
946
- }
947
- async deleteQueueOld(queue) {
948
- RabbitMq.validateName('queue', queue);
949
- const channel = await this.assertChannelOld();
950
- logger_1.default.info('rabbit: deleting queue', { queue });
951
- const deleteQueueRes = await channel.deleteQueue(queue);
952
- debug('queue deleted', deleteQueueRes);
953
- return deleteQueueRes;
954
- }
955
- async assertExchangeOld(exchangeName, options) {
956
- const channel = await this.assertChannelOld();
957
- if (this.oldExchanges[exchangeName]) {
958
- delete this.oldAssertExchangePromises[exchangeName];
959
- return this.oldExchanges[exchangeName];
960
- }
961
- if (this.oldAssertExchangePromises[exchangeName]) {
962
- return this.oldAssertExchangePromises[exchangeName];
963
- }
964
- this.oldAssertExchangePromises[exchangeName] = (0, utils_1.assertExchangeFanout)(channel, exchangeName);
965
- this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
966
- return this.oldExchanges[exchangeName];
967
- }
968
599
  }
969
600
  exports.default = RabbitMq;
970
601
  var celery_1 = require("./lib/celery");