@autofleet/rabbit 3.2.22-beta.5 → 3.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { EventEmitter } from 'events';
3
3
  import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from 'amqp-connection-manager';
4
4
  import { ConfirmChannel, ConsumeMessage, Options, Replies } from 'amqplib';
5
5
  import { RedisConfig } from './lib/redis';
6
- import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, ConnectionPurpose, ConnectionData } from './lib/types';
6
+ import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, AssertExchangePromisesDictionary } from './lib/types';
7
7
  export interface IAfRabbitMq {
8
8
  ack: any;
9
9
  nack: any;
@@ -37,12 +37,10 @@ type newChannelOpts = {
37
37
  name?: string;
38
38
  onClose?: null | ((args: any | null) => void);
39
39
  options?: CreateChannelOpts | undefined;
40
- connectionPurpose?: ConnectionPurpose;
41
40
  };
42
41
  type assertChannelOpts = {
43
42
  channelName?: string;
44
43
  force?: boolean;
45
- connectionPurpose?: ConnectionPurpose;
46
44
  };
47
45
  declare class RabbitMq implements IAfRabbitMq {
48
46
  static parseMsg(msg: any): any;
@@ -62,14 +60,13 @@ declare class RabbitMq implements IAfRabbitMq {
62
60
  channel: ChannelWrapper | null;
63
61
  publishChannelSetupPromise: Promise<ChannelWrapper> | null;
64
62
  blockReconnect: boolean | null | undefined;
65
- connectionsMap: {
66
- [ConnectionPurpose.Consume]: ConnectionData;
67
- [ConnectionPurpose.Publish]: ConnectionData;
68
- };
63
+ connection: AmqpConnectionManager | null | undefined;
69
64
  em: EventEmitter;
65
+ creatingConnection: boolean;
70
66
  exchanges: ExchangesCache;
71
67
  queues: QueuesCache;
72
68
  queueSetupPromises: QueueSetupPromisesDictionary;
69
+ assertExchangePromises: AssertExchangePromisesDictionary;
73
70
  options: AfRabbitOptions | undefined;
74
71
  redisClient: any;
75
72
  redisLock?: RedisLockType;
@@ -80,15 +77,16 @@ declare class RabbitMq implements IAfRabbitMq {
80
77
  private shouldConsumeMessageByTimestamp;
81
78
  ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
82
79
  nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
83
- getConnection(connectionPurpose: ConnectionPurpose): Promise<AmqpConnectionManager | null | undefined>;
84
- getNewChannel({ name, onClose, options, connectionPurpose, }: newChannelOpts): Promise<ChannelWrapper>;
85
- assertChannel({ force, connectionPurpose }: assertChannelOpts): Promise<ChannelWrapper>;
80
+ getConnection(): Promise<AmqpConnectionManager>;
81
+ getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
82
+ assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
86
83
  assertExchange(exchangeName: string, options?: any): Promise<any>;
87
- getQueueLength(queue: string, connectionPurpose?: ConnectionPurpose): Promise<Replies.AssertQueue>;
84
+ getQueueLength(queue: string): Promise<Replies.AssertQueue>;
88
85
  private deleteQueue;
89
- bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose): Promise<void>;
90
- setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
91
- assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<any>;
86
+ bindQueue(queue: string, exchange: string): Promise<void>;
87
+ setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
88
+ static shouldUseQuorum(queueName: string): boolean;
89
+ assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
92
90
  private saveConsumer;
93
91
  consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
94
92
  private lockRedisIfNeeded;
package/dist/index.js CHANGED
@@ -118,13 +118,12 @@ class RabbitMq {
118
118
  this.em = new events_1.EventEmitter();
119
119
  this.channel = null;
120
120
  this.publishChannelSetupPromise = null;
121
- this.connectionsMap = {
122
- [types_1.ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
123
- [types_1.ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
124
- };
121
+ this.connection = null;
122
+ this.creatingConnection = false;
125
123
  this.exchanges = {};
126
124
  this.queues = {};
127
125
  this.queueSetupPromises = {};
126
+ this.assertExchangePromises = {};
128
127
  this.consumers = [];
129
128
  this.options = options;
130
129
  this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
@@ -142,32 +141,30 @@ class RabbitMq {
142
141
  });
143
142
  }
144
143
  }
145
- async getConnection(connectionPurpose) {
144
+ async getConnection() {
146
145
  return new Promise(async (resolve, reject) => {
147
- const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
148
- debug('rabbit: start getting new connection', { connection, creatingConnection, connectionPurpose });
149
146
  if (this.blockReconnect) {
150
147
  debug('rabbit: block reconnect');
151
148
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
152
149
  // @ts-ignore
153
150
  return resolve();
154
151
  }
155
- if (connection !== null) {
156
- if (this.options?.disableReconnect || connection?.isConnected()) {
152
+ if (this.connection !== null) {
153
+ if (this.options?.disableReconnect || this.connection?.isConnected()) {
157
154
  debug('rabbit: connection - is connected');
158
155
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
159
156
  // @ts-ignore
160
- return resolve(connection);
157
+ return resolve(this.connection);
161
158
  }
162
159
  debug('rabbit: connection - reconnecting');
163
160
  }
164
- if (creatingConnection) {
161
+ if (this.creatingConnection) {
165
162
  debug('rabbit: creating connection emi');
166
163
  this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
167
164
  this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
168
165
  return;
169
166
  }
170
- this.connectionsMap[connectionPurpose].creatingConnection = true;
167
+ this.creatingConnection = true;
171
168
  let isResolved = false;
172
169
  // It is import to use it as a function and not as a variable
173
170
  // because of k8s changes the env variables
@@ -180,11 +177,11 @@ class RabbitMq {
180
177
  return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
181
178
  };
182
179
  const defaultUrls = findServers();
183
- const newConnection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
180
+ const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
184
181
  findServers,
185
182
  });
186
- this.connectionsMap[connectionPurpose].connection = newConnection;
187
- newConnection.on('error', (err) => {
183
+ this.connection = connection;
184
+ this.connection.on('error', (err) => {
188
185
  logger_1.default.error('rabbit: connection error', { err });
189
186
  if (!isResolved) {
190
187
  isResolved = true;
@@ -192,7 +189,7 @@ class RabbitMq {
192
189
  this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
193
190
  }
194
191
  });
195
- newConnection.on('connectFailed', (err) => {
192
+ this.connection.on('connectFailed', (err) => {
196
193
  this.consumersTags = [];
197
194
  logger_1.default.error('rabbit: connection connectFailed', { err });
198
195
  if (!isResolved) {
@@ -201,7 +198,8 @@ class RabbitMq {
201
198
  this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
202
199
  }
203
200
  });
204
- newConnection.on('disconnect', ({ err }) => {
201
+ this.connection.on('disconnect', ({ err }) => {
202
+ // this.channel = null;
205
203
  this.consumersTags = [];
206
204
  debug('rabbit: connection closed');
207
205
  if (this.options?.disableReconnect) {
@@ -212,27 +210,24 @@ class RabbitMq {
212
210
  logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
213
211
  }
214
212
  });
215
- newConnection.once('connect', async () => {
216
- debug('rabbit: connection established', { connectionPurpose, connection: this.connectionsMap[connectionPurpose] });
217
- this.connectionsMap[connectionPurpose].creatingConnection = false;
218
- this.em.emit(consts_1.CONNECTION_CREATED_CONST, newConnection);
213
+ this.connection.once('connect', async () => {
214
+ debug('rabbit: connection established');
215
+ this.creatingConnection = false;
216
+ this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
219
217
  isResolved = true;
220
- resolve(newConnection);
218
+ resolve(connection);
221
219
  });
222
220
  });
223
221
  }
224
- async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connectionPurpose = types_1.ConnectionPurpose.Consume, }) {
222
+ async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
225
223
  let connection;
226
224
  try {
227
- connection = await this.getConnection(connectionPurpose);
225
+ connection = await this.getConnection();
228
226
  }
229
227
  catch (e) {
230
228
  logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
231
229
  throw e;
232
230
  }
233
- if (!connection) {
234
- throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
235
- }
236
231
  const channel = connection.createChannel({ ...options });
237
232
  (0, events_1.once)(channel, 'close').then((args) => {
238
233
  logger_1.default.error(`rabbit: channel ${name} closed`);
@@ -248,16 +243,14 @@ class RabbitMq {
248
243
  throw err;
249
244
  }
250
245
  }
251
- async assertChannel({ force = false, connectionPurpose = types_1.ConnectionPurpose.Consume }) {
252
- debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.channel });
246
+ async assertChannel({ force = false } = {}) {
253
247
  if (!this.publishChannelSetupPromise) {
254
248
  this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
255
249
  if (this.channel && !force) {
256
250
  return resolve(this.channel);
257
251
  }
258
252
  try {
259
- const channel = await this.getNewChannel({ connectionPurpose });
260
- debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
253
+ const channel = await this.getNewChannel({});
261
254
  channel.on('error', (err) => {
262
255
  logger_1.default.error('rabbit: channel error', { err });
263
256
  });
@@ -271,69 +264,90 @@ class RabbitMq {
271
264
  }
272
265
  return this.publishChannelSetupPromise;
273
266
  }
274
- async assertExchange(exchangeName, options = { connectionPurpose: types_1.ConnectionPurpose.Consume }) {
275
- const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
267
+ async assertExchange(exchangeName, options) {
268
+ const channel = await this.assertChannel();
276
269
  if (this.exchanges[exchangeName]) {
270
+ delete this.assertExchangePromises[exchangeName];
277
271
  return this.exchanges[exchangeName];
278
272
  }
279
- const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
280
- this.exchanges[exchangeName] = exchange;
281
- return exchange;
273
+ if (this.assertExchangePromises[exchangeName]) {
274
+ return this.assertExchangePromises[exchangeName];
275
+ }
276
+ this.assertExchangePromises[exchangeName] = (0, utils_1.assertExchangeFanout)(channel, exchangeName);
277
+ this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
278
+ return this.exchanges[exchangeName];
282
279
  }
283
- async getQueueLength(queue, connectionPurpose = types_1.ConnectionPurpose.Consume) {
280
+ async getQueueLength(queue) {
284
281
  RabbitMq.validateName('queue', queue);
285
- const { connection } = this.connectionsMap[connectionPurpose];
286
282
  const { channel } = this;
287
283
  if (!channel) {
288
284
  throw new Error('channel is not defined');
289
285
  }
290
- debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
286
+ debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
291
287
  return channel?.checkQueue(queue);
292
288
  }
293
- async deleteQueue(queue, connectionPurpose) {
289
+ async deleteQueue(queue) {
294
290
  RabbitMq.validateName('queue', queue);
295
- const channel = await this.assertChannel({ connectionPurpose });
291
+ const channel = await this.assertChannel();
296
292
  logger_1.default.info('rabbit: deleting queue', { queue });
297
293
  const deleteQueueRes = await channel.deleteQueue(queue);
298
294
  debug('queue deleted', deleteQueueRes);
299
295
  return deleteQueueRes;
300
296
  }
301
- async bindQueue(queue, exchange, connectionPurpose) {
302
- const channel = await this.assertChannel({ connectionPurpose });
297
+ async bindQueue(queue, exchange) {
298
+ const channel = await this.assertChannel();
303
299
  await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
304
300
  return channel.bindQueue(queue, exchange, '');
305
301
  }
306
- async setupQueue(queueName, connectionPurpose, options) {
302
+ async setupQueue(queueName, options) {
307
303
  let queue;
308
- debug('rabbit: start setup queue', { queueName, connectionPurpose });
304
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
305
+ const localeOptions = {
306
+ ...options,
307
+ durable: true,
308
+ arguments: {
309
+ ...options?.arguments,
310
+ 'x-consumer-timeout': 1000 * 60 * 60 * 24,
311
+ 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
312
+ },
313
+ };
309
314
  try {
310
- const channel = await this.assertChannel({ connectionPurpose });
315
+ const channel = await this.assertChannel();
311
316
  debug('assertQueue->channel.addSetup', { queueName });
312
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
317
+ await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
313
318
  debug('assertQueue->channel.assertQueue', { queueName });
314
- queue = await channel.assertQueue(queueName, options);
319
+ queue = await channel.assertQueue(queueName, localeOptions);
315
320
  }
316
321
  catch (e) {
317
322
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
318
323
  if (!this.options?.dontRetryAssert) {
319
324
  debug('retrying assertQueue', { queueName });
320
- const channel = await this.assertChannel({ force: true, connectionPurpose });
321
- await this.deleteQueue(queueName, connectionPurpose);
325
+ const channel = await this.assertChannel({ force: true });
326
+ await this.deleteQueue(queueName);
322
327
  debug('retrying assertQueue->channel.addSetup', { queueName });
323
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
328
+ await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
324
329
  debug('retrying assertQueue->channel.assertQueue', { queueName });
325
- queue = await channel.assertQueue(queueName, options);
330
+ queue = await channel.assertQueue(queueName, localeOptions);
326
331
  }
327
332
  else {
328
333
  throw e;
329
334
  }
330
335
  }
331
- debug('rabbit: done setup queue', { queueName, connectionPurpose });
332
- this.queues[queueName] = queueName;
336
+ this.queues[queueName] = queue;
333
337
  return queue;
334
338
  }
335
- async assertQueue(queueName, connectionPurpose, options) {
336
- debug('rabbit: start assert queue', { connectionPurpose, queueName });
339
+ static shouldUseQuorum(queueName) {
340
+ const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
341
+ if (envQuorumQueuesWhitelist === '*') {
342
+ return true;
343
+ }
344
+ if (envQuorumQueuesWhitelist) {
345
+ const whitelist = envQuorumQueuesWhitelist.split(',');
346
+ return whitelist.includes(queueName);
347
+ }
348
+ return false;
349
+ }
350
+ async assertQueue(queueName, options) {
337
351
  RabbitMq.validateName('queue', queueName);
338
352
  if (this.queues[queueName]) {
339
353
  delete this.queueSetupPromises[queueName];
@@ -342,8 +356,7 @@ class RabbitMq {
342
356
  if (this.queueSetupPromises[queueName]) {
343
357
  return this.queueSetupPromises[queueName];
344
358
  }
345
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
346
- debug('rabbit: done assert queue', { connectionPurpose, queueName });
359
+ this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
347
360
  return this.queueSetupPromises[queueName];
348
361
  }
349
362
  saveConsumer(queue, callback, options) {
@@ -386,16 +399,17 @@ class RabbitMq {
386
399
  }
387
400
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
388
401
  }
389
- const channel = await this.getNewChannel({ connectionPurpose: types_1.ConnectionPurpose.Consume });
402
+ const channel = await this.getNewChannel({});
390
403
  return channel.addSetup(async (confirmChannel) => {
391
- await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
392
- await confirmChannel.prefetch(limit, true);
404
+ const q = await this.assertQueue(queue, optionsWithDefaults);
405
+ await confirmChannel.prefetch(limit, false);
393
406
  const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
394
407
  if (!msg) {
395
408
  return null;
396
409
  }
397
410
  const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
398
411
  const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
412
+ const automationId = msg.properties.headers[consts_1.AUTOMATION_ID_HEADER];
399
413
  const parsedMessage = RabbitMq.parseMsg(msg);
400
414
  const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
401
415
  const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
@@ -420,7 +434,10 @@ class RabbitMq {
420
434
  outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
421
435
  }
422
436
  if (auditContext) {
423
- await auditContext(queue);
437
+ await auditContext(queue, {
438
+ userId,
439
+ automationId,
440
+ });
424
441
  }
425
442
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
426
443
  if (!shouldConsume) {
@@ -466,7 +483,7 @@ class RabbitMq {
466
483
  RabbitMq.validateName('queue', queue);
467
484
  const { limit, deadMessageTtl } = optionsWithDefaults;
468
485
  await this.saveConsumer(queue, callback, options);
469
- const channel = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
486
+ const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
470
487
  return channel.addSetup(async (c) => {
471
488
  const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
472
489
  await c.assertQueue(queue);
@@ -481,14 +498,14 @@ class RabbitMq {
481
498
  async publish(exchange, content, customHeaders) {
482
499
  return (0, utils_1.wrapSetImmediate)(async () => {
483
500
  RabbitMq.validateName('exchange', exchange);
484
- const channel = await this.assertChannel({ connectionPurpose: types_1.ConnectionPurpose.Publish });
485
- await this.assertExchange(exchange, { connectionPurpose: types_1.ConnectionPurpose.Publish });
501
+ const channel = await this.assertChannel();
502
+ await this.assertExchange(exchange);
486
503
  await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
487
504
  });
488
505
  }
489
506
  async sendToQueue(queue, content, options, customHeaders) {
490
507
  try {
491
- await this.assertChannel({ connectionPurpose: types_1.ConnectionPurpose.Publish });
508
+ await this.assertChannel();
492
509
  }
493
510
  catch (e) {
494
511
  logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
@@ -496,7 +513,7 @@ class RabbitMq {
496
513
  }
497
514
  try {
498
515
  RabbitMq.validateName('queue', queue);
499
- await this.assertQueue(queue, types_1.ConnectionPurpose.Publish, options);
516
+ await this.assertQueue(queue, options);
500
517
  }
501
518
  catch (e) {
502
519
  logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
@@ -508,36 +525,31 @@ class RabbitMq {
508
525
  return res;
509
526
  }
510
527
  catch (e) {
511
- logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
528
+ const isConnected = await this.isConnected();
529
+ logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
512
530
  throw e;
513
531
  }
514
532
  }
515
533
  async isConnected() {
516
- const isEachConnectionConnected = await Promise.all(Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
517
- const { connection } = connectionData;
518
- if (connectionPurpose === types_1.ConnectionPurpose.Publish && !connection) {
519
- return true; // The connection hasn't been initialized yet, as no messages have been sent through it
520
- }
521
- const isConnected = connection?.isConnected();
522
- if (!isConnected) {
523
- logger_1.default.error('rabbit: isConnected - false');
524
- return false;
525
- }
526
- const channel = await this.assertChannel({ connectionPurpose: connectionPurpose });
527
- try {
528
- await Promise.all([
529
- channel.waitForConnect(),
530
- ...this.consumers.map((c) => channel.checkQueue(c.queue)),
531
- ]);
532
- }
533
- catch (e) {
534
- logger_1.default.error('rabbit: isConnected - false');
535
- return false;
536
- }
537
- logger_1.default.info('rabbit: isConnected - true');
538
- return true;
539
- }));
540
- return isEachConnectionConnected.every((isConnected) => isConnected === true);
534
+ const connection = await this.getConnection();
535
+ const isConnected = connection.isConnected();
536
+ if (!isConnected) {
537
+ logger_1.default.error('rabbit: isConnected - false');
538
+ return false;
539
+ }
540
+ const channel = await this.assertChannel();
541
+ try {
542
+ await Promise.all([
543
+ channel.waitForConnect(),
544
+ ...this.consumers.map((c) => channel.checkQueue(c.queue)),
545
+ ]);
546
+ }
547
+ catch (e) {
548
+ logger_1.default.error('rabbit: isConnected - false');
549
+ return false;
550
+ }
551
+ logger_1.default.info('rabbit: isConnected - true');
552
+ return true;
541
553
  }
542
554
  async gracefulShutdown(signal) {
543
555
  const tagsNumber = this.consumersTags.length;
@@ -3,6 +3,7 @@ export declare const DEFAULT_LOCK_TIMEOUT: number;
3
3
  export declare const RETRY_HEADER = "x-retry-count";
4
4
  export declare const TRACING_HEADER = "x-trace-id";
5
5
  export declare const USER_TRACING_HEADER = "x-af-user-id";
6
+ export declare const AUTOMATION_ID_HEADER = "x-af-automation-id";
6
7
  export declare const USER_OBJECT = "userObject";
7
8
  export declare const DEFAULT_USE_CONSUME_WITH_LOCK = false;
8
9
  export declare const CONNECTION_CREATED_CONST = "connectionCreated";
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.USER_TRACING_HEADER = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
3
+ exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.AUTOMATION_ID_HEADER = exports.USER_TRACING_HEADER = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
4
4
  exports.DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 12;
5
5
  exports.DEFAULT_LOCK_TIMEOUT = 1000 * 5;
6
6
  exports.RETRY_HEADER = 'x-retry-count';
7
7
  exports.TRACING_HEADER = 'x-trace-id';
8
8
  exports.USER_TRACING_HEADER = 'x-af-user-id';
9
+ exports.AUTOMATION_ID_HEADER = 'x-af-automation-id';
9
10
  exports.USER_OBJECT = 'userObject';
10
11
  exports.DEFAULT_USE_CONSUME_WITH_LOCK = false;
11
12
  exports.CONNECTION_CREATED_CONST = 'connectionCreated';
@@ -1,4 +1,3 @@
1
- import { AmqpConnectionManager } from 'amqp-connection-manager';
2
1
  import { ConsumeMessage, Options, Replies } from 'amqplib';
3
2
  export interface ExchangesCache {
4
3
  [key: string]: any;
@@ -9,6 +8,9 @@ export interface QueuesCache {
9
8
  export interface QueueSetupPromisesDictionary {
10
9
  [key: string]: Promise<Replies.AssertQueue> | undefined;
11
10
  }
11
+ export interface AssertExchangePromisesDictionary {
12
+ [key: string]: Promise<Replies.AssertExchange> | undefined;
13
+ }
12
14
  export type CustomMessageHeaders = {
13
15
  redisTimestampValidationKey?: string;
14
16
  };
@@ -39,11 +41,3 @@ export type AfConsumer = {
39
41
  options: ConsumeOptions | undefined;
40
42
  };
41
43
  export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
42
- export type ConnectionData = {
43
- connection: AmqpConnectionManager | null;
44
- creatingConnection: boolean;
45
- };
46
- export declare enum ConnectionPurpose {
47
- Consume = "consume",
48
- Publish = "publish"
49
- }
package/dist/lib/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ConnectionPurpose = exports.CONSUMER_DEFAULT_OPTIONS = void 0;
3
+ exports.CONSUMER_DEFAULT_OPTIONS = void 0;
4
4
  const HA_PROMOTE_ON_FAILURE = 'ha-promote-on-failure';
5
5
  const HA_PROMOTE_ON_SHUTDOWN = 'ha-promote-on-shutdown';
6
6
  exports.CONSUMER_DEFAULT_OPTIONS = {
@@ -9,8 +9,3 @@ exports.CONSUMER_DEFAULT_OPTIONS = {
9
9
  [HA_PROMOTE_ON_SHUTDOWN]: 'always',
10
10
  },
11
11
  };
12
- var ConnectionPurpose;
13
- (function (ConnectionPurpose) {
14
- ConnectionPurpose["Consume"] = "consume";
15
- ConnectionPurpose["Publish"] = "publish";
16
- })(ConnectionPurpose = exports.ConnectionPurpose || (exports.ConnectionPurpose = {}));
@@ -1,5 +1,5 @@
1
1
  import { ChannelWrapper } from 'amqp-connection-manager';
2
- import { ConfirmChannel } from 'amqplib';
3
- export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<import("amqplib").Replies.AssertExchange>;
2
+ import { ConfirmChannel, Replies } from 'amqplib';
3
+ export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<Replies.AssertExchange>;
4
4
  export declare const wrapSetImmediate: (callback: () => any) => Promise<any>;
5
5
  export declare const rand: () => number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "3.2.22-beta.5",
3
+ "version": "3.2.22",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ import RabbitError from './lib/rabbitError';
19
19
  import getRedisInstance, { RedisConfig } from './lib/redis';
20
20
  import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
21
21
  import {
22
+ AUTOMATION_ID_HEADER,
22
23
  CONNECTION_CREATED_CONST,
23
24
  CONNECTION_FAILED_CONST,
24
25
  DEFAULT_LOCK_TIMEOUT,
@@ -35,15 +36,17 @@ import {
35
36
  CustomMessageHeaders,
36
37
  QueuesCache,
37
38
  RedisLockType,
38
- ExchangesCache, CONSUMER_DEFAULT_OPTIONS, QueueSetupPromisesDictionary,
39
- ConnectionPurpose,
40
- ConnectionData,
39
+ ExchangesCache,
40
+ CONSUMER_DEFAULT_OPTIONS,
41
+ QueueSetupPromisesDictionary,
42
+ AssertExchangePromisesDictionary,
41
43
  } from './lib/types';
42
44
 
43
45
  // const debug = nodeDebug('af-rabbitmq')
44
46
  const debug = logger.debug.bind(logger);
45
47
 
46
48
  const PUBLISH_TIMEOUT = 1000 * 10;
49
+
47
50
  export interface IAfRabbitMq {
48
51
  ack: any;
49
52
  nack: any;
@@ -83,13 +86,11 @@ type newChannelOpts = {
83
86
  name?: string;
84
87
  onClose?: null | ((args: any | null) => void);
85
88
  options?: CreateChannelOpts | undefined;
86
- connectionPurpose?: ConnectionPurpose,
87
89
  };
88
90
 
89
91
  type assertChannelOpts = {
90
92
  channelName?: string;
91
93
  force?: boolean;
92
- connectionPurpose?: ConnectionPurpose;
93
94
  }
94
95
 
95
96
  type AfConsumer = {
@@ -101,7 +102,7 @@ type AfConsumer = {
101
102
  const HEARTBEAT = '60';
102
103
 
103
104
  class RabbitMq implements IAfRabbitMq {
104
- static parseMsg(msg: any): any {
105
+ static parseMsg(msg: any) : any {
105
106
  let { content } = msg;
106
107
  content = content.toString();
107
108
 
@@ -146,21 +147,22 @@ class RabbitMq implements IAfRabbitMq {
146
147
 
147
148
  publishChannelSetupPromise: Promise<ChannelWrapper> | null;
148
149
 
149
- blockReconnect: boolean | null | undefined;
150
+ blockReconnect: boolean | null | undefined
150
151
 
151
- connectionsMap: {
152
- [ConnectionPurpose.Consume]: ConnectionData;
153
- [ConnectionPurpose.Publish]: ConnectionData;
154
- };
152
+ connection: AmqpConnectionManager | null | undefined
155
153
 
156
154
  em: EventEmitter;
157
155
 
156
+ creatingConnection: boolean;
157
+
158
158
  exchanges: ExchangesCache;
159
159
 
160
160
  queues: QueuesCache;
161
161
 
162
162
  queueSetupPromises: QueueSetupPromisesDictionary;
163
163
 
164
+ assertExchangePromises: AssertExchangePromisesDictionary;
165
+
164
166
  options: AfRabbitOptions | undefined;
165
167
 
166
168
  redisClient: any;
@@ -176,13 +178,12 @@ class RabbitMq implements IAfRabbitMq {
176
178
  this.em = new EventEmitter();
177
179
  this.channel = null;
178
180
  this.publishChannelSetupPromise = null;
179
- this.connectionsMap = {
180
- [ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
181
- [ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
182
- };
181
+ this.connection = null;
182
+ this.creatingConnection = false;
183
183
  this.exchanges = {};
184
184
  this.queues = {};
185
185
  this.queueSetupPromises = {};
186
+ this.assertExchangePromises = {};
186
187
  this.consumers = [];
187
188
  this.options = options;
188
189
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
@@ -215,7 +216,7 @@ class RabbitMq implements IAfRabbitMq {
215
216
  return false;
216
217
  }
217
218
 
218
- public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
219
+ public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
219
220
  if (msg) {
220
221
  debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
221
222
  await channel.ack(msg);
@@ -241,8 +242,8 @@ class RabbitMq implements IAfRabbitMq {
241
242
  userMsg: ConsumeMessageOrNull,
242
243
  {
243
244
  skipRetry = false,
244
- }: NackOptions = {},
245
- ): Promise<any> => {
245
+ }: NackOptions = { },
246
+ ) : Promise<any> => {
246
247
  await this.unlockRedisIfNeeded(releaseLock);
247
248
  if (channel && msg) {
248
249
  if (
@@ -276,32 +277,30 @@ class RabbitMq implements IAfRabbitMq {
276
277
  }
277
278
  }
278
279
 
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 });
280
+ async getConnection() {
281
+ return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
283
282
  if (this.blockReconnect) {
284
283
  debug('rabbit: block reconnect');
285
284
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
286
285
  // @ts-ignore
287
286
  return resolve();
288
287
  }
289
- if (connection !== null) {
290
- if (this.options?.disableReconnect || connection?.isConnected()) {
288
+ if (this.connection !== null) {
289
+ if (this.options?.disableReconnect || this.connection?.isConnected()) {
291
290
  debug('rabbit: connection - is connected');
292
291
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
293
292
  // @ts-ignore
294
- return resolve(connection);
293
+ return resolve(this.connection);
295
294
  }
296
295
  debug('rabbit: connection - reconnecting');
297
296
  }
298
- if (creatingConnection) {
297
+ if (this.creatingConnection) {
299
298
  debug('rabbit: creating connection emi');
300
299
  this.em.once(CONNECTION_CREATED_CONST, resolve);
301
300
  this.em.once(CONNECTION_FAILED_CONST, reject);
302
301
  return;
303
302
  }
304
- this.connectionsMap[connectionPurpose].creatingConnection = true;
303
+ this.creatingConnection = true;
305
304
  let isResolved = false;
306
305
 
307
306
  // It is import to use it as a function and not as a variable
@@ -318,13 +317,12 @@ class RabbitMq implements IAfRabbitMq {
318
317
  };
319
318
 
320
319
  const defaultUrls = findServers();
321
- const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
320
+ const connection: AmqpConnectionManager = await connect(defaultUrls, {
322
321
  findServers,
323
322
  });
324
323
 
325
- this.connectionsMap[connectionPurpose].connection = newConnection;
326
-
327
- newConnection.on('error', (err) => {
324
+ this.connection = connection;
325
+ this.connection.on('error', (err) => {
328
326
  logger.error('rabbit: connection error', { err });
329
327
  if (!isResolved) {
330
328
  isResolved = true;
@@ -333,7 +331,7 @@ class RabbitMq implements IAfRabbitMq {
333
331
  }
334
332
  });
335
333
 
336
- newConnection.on('connectFailed', (err) => {
334
+ this.connection.on('connectFailed', (err) => {
337
335
  this.consumersTags = [];
338
336
  logger.error('rabbit: connection connectFailed', { err });
339
337
  if (!isResolved) {
@@ -343,7 +341,8 @@ class RabbitMq implements IAfRabbitMq {
343
341
  }
344
342
  });
345
343
 
346
- newConnection.on('disconnect', ({ err }) => {
344
+ this.connection.on('disconnect', ({ err }) => {
345
+ // this.channel = null;
347
346
  this.consumersTags = [];
348
347
  debug('rabbit: connection closed');
349
348
  if (this.options?.disableReconnect) {
@@ -353,29 +352,25 @@ class RabbitMq implements IAfRabbitMq {
353
352
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
354
353
  }
355
354
  });
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);
355
+
356
+ this.connection.once('connect', async () => {
357
+ debug('rabbit: connection established');
358
+ this.creatingConnection = false;
359
+ this.em.emit(CONNECTION_CREATED_CONST, connection);
360
360
  isResolved = true;
361
- resolve(newConnection);
361
+ resolve(connection);
362
362
  });
363
363
  });
364
364
  }
365
365
 
366
- async getNewChannel({
367
- name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
368
- }: newChannelOpts): Promise<ChannelWrapper> {
369
- let connection!: AmqpConnectionManager | undefined | null;
366
+ async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
367
+ let connection!: AmqpConnectionManager;
370
368
  try {
371
- connection = await this.getConnection(connectionPurpose);
369
+ connection = await this.getConnection();
372
370
  } catch (e) {
373
371
  logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
374
372
  throw e;
375
373
  }
376
- if (!connection) {
377
- throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
378
- }
379
374
  const channel = connection.createChannel({ ...options });
380
375
  once(channel, 'close').then((args) => {
381
376
  logger.error(`rabbit: channel ${name} closed`);
@@ -391,8 +386,7 @@ class RabbitMq implements IAfRabbitMq {
391
386
  }
392
387
  }
393
388
 
394
- async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
395
- debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.channel });
389
+ async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
396
390
  if (!this.publishChannelSetupPromise) {
397
391
  this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
398
392
  if (this.channel && !force) {
@@ -400,8 +394,7 @@ class RabbitMq implements IAfRabbitMq {
400
394
  }
401
395
 
402
396
  try {
403
- const channel = await this.getNewChannel({ connectionPurpose });
404
- debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
397
+ const channel = await this.getNewChannel({});
405
398
  channel.on('error', (err) => {
406
399
  logger.error('rabbit: channel error', { err });
407
400
  });
@@ -415,73 +408,102 @@ class RabbitMq implements IAfRabbitMq {
415
408
  return this.publishChannelSetupPromise;
416
409
  }
417
410
 
418
- async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
419
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
411
+ async assertExchange(exchangeName: string, options?: any) {
412
+ const channel: ChannelWrapper = await this.assertChannel();
413
+
420
414
  if (this.exchanges[exchangeName]) {
415
+ delete this.assertExchangePromises[exchangeName];
421
416
  return this.exchanges[exchangeName];
422
417
  }
423
- const exchange = await assertExchangeFanout(channel, exchangeName);
424
- this.exchanges[exchangeName] = exchange;
425
- return exchange;
418
+
419
+ if (this.assertExchangePromises[exchangeName]) {
420
+ return this.assertExchangePromises[exchangeName];
421
+ }
422
+
423
+ this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
424
+ this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
425
+ return this.exchanges[exchangeName];
426
426
  }
427
427
 
428
- async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
428
+ async getQueueLength(queue: string) {
429
429
  RabbitMq.validateName('queue', queue);
430
- const { connection } = this.connectionsMap[connectionPurpose];
431
430
  const { channel } = this;
432
431
  if (!channel) {
433
432
  throw new Error('channel is not defined');
434
433
  }
435
- debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
434
+ debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
436
435
  return channel?.checkQueue(queue);
437
436
  }
438
437
 
439
- private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
438
+ private async deleteQueue(queue: string) {
440
439
  RabbitMq.validateName('queue', queue);
441
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
440
+ const channel: ChannelWrapper = await this.assertChannel();
442
441
  logger.info('rabbit: deleting queue', { queue });
443
442
  const deleteQueueRes = await channel.deleteQueue(queue);
444
443
  debug('queue deleted', deleteQueueRes);
445
444
  return deleteQueueRes;
446
445
  }
447
446
 
448
- async bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose) {
449
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
447
+ async bindQueue(queue: string, exchange: string) {
448
+ const channel: ChannelWrapper = await this.assertChannel();
450
449
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
451
450
  return channel.bindQueue(queue, exchange, '');
452
451
  }
453
452
 
454
- async setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
453
+ async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
455
454
  let queue: Replies.AssertQueue;
456
- debug('rabbit: start setup queue', { queueName, connectionPurpose });
455
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
456
+ const localeOptions = {
457
+ ...options,
458
+ durable: true,
459
+ arguments: {
460
+ ...options?.arguments,
461
+ 'x-consumer-timeout': 1000 * 60 * 60 * 24,
462
+ 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
463
+ },
464
+ };
457
465
  try {
458
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
466
+ const channel: ChannelWrapper = await this.assertChannel();
459
467
  debug('assertQueue->channel.addSetup', { queueName });
460
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
468
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
461
469
  debug('assertQueue->channel.assertQueue', { queueName });
462
- queue = await channel.assertQueue(queueName, options);
470
+ queue = await channel.assertQueue(queueName, localeOptions);
463
471
  } catch (e) {
464
472
  logger.error('rabbit: assertQueue error', { queueName, options, error: e });
465
473
  if (!this.options?.dontRetryAssert) {
466
474
  debug('retrying assertQueue', { queueName });
467
- const channel = await this.assertChannel({ force: true, connectionPurpose });
468
- await this.deleteQueue(queueName, connectionPurpose);
475
+ const channel = await this.assertChannel({ force: true });
476
+ await this.deleteQueue(queueName);
469
477
 
470
478
  debug('retrying assertQueue->channel.addSetup', { queueName });
471
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
479
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
472
480
  debug('retrying assertQueue->channel.assertQueue', { queueName });
473
- queue = await channel.assertQueue(queueName, options);
481
+ queue = await channel.assertQueue(queueName, localeOptions);
474
482
  } else {
475
483
  throw e;
476
484
  }
477
485
  }
478
- debug('rabbit: done setup queue', { queueName, connectionPurpose });
479
- this.queues[queueName] = queueName;
486
+
487
+ this.queues[queueName] = queue;
480
488
  return queue;
481
489
  }
482
490
 
483
- async assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue) {
484
- debug('rabbit: start assert queue', { connectionPurpose, queueName });
491
+ static shouldUseQuorum(queueName: string): boolean {
492
+ const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
493
+
494
+ if (envQuorumQueuesWhitelist === '*') {
495
+ return true;
496
+ }
497
+
498
+ if (envQuorumQueuesWhitelist) {
499
+ const whitelist = envQuorumQueuesWhitelist.split(',');
500
+ return whitelist.includes(queueName);
501
+ }
502
+
503
+ return false;
504
+ }
505
+
506
+ async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
485
507
  RabbitMq.validateName('queue', queueName);
486
508
  if (this.queues[queueName]) {
487
509
  delete this.queueSetupPromises[queueName];
@@ -492,13 +514,12 @@ class RabbitMq implements IAfRabbitMq {
492
514
  return this.queueSetupPromises[queueName];
493
515
  }
494
516
 
495
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
496
- debug('rabbit: done assert queue', { connectionPurpose, queueName });
517
+ this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
497
518
  return this.queueSetupPromises[queueName];
498
519
  }
499
520
 
500
521
  private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
501
- const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
522
+ const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
502
523
  if (!isConsumerExist) {
503
524
  logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
504
525
  this.consumers.push({
@@ -547,10 +568,10 @@ class RabbitMq implements IAfRabbitMq {
547
568
  }
548
569
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
549
570
  }
550
- const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
571
+ const channel = await this.getNewChannel({});
551
572
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
552
- await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
553
- await confirmChannel.prefetch(limit, true);
573
+ const q = await this.assertQueue(queue, optionsWithDefaults);
574
+ await confirmChannel.prefetch(limit, false);
554
575
  const { consumerTag } = await confirmChannel.consume(
555
576
  queue,
556
577
  async (msg: ConsumeMessageOrNull) => {
@@ -560,6 +581,7 @@ class RabbitMq implements IAfRabbitMq {
560
581
 
561
582
  const traceId = msg.properties.headers[TRACING_HEADER];
562
583
  const userId = msg.properties.headers[USER_TRACING_HEADER];
584
+ const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
563
585
  const parsedMessage = RabbitMq.parseMsg(msg);
564
586
  const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
565
587
  const trace = newTrace(traceTypes.RABBIT);
@@ -585,7 +607,10 @@ class RabbitMq implements IAfRabbitMq {
585
607
  }
586
608
 
587
609
  if (auditContext) {
588
- await auditContext(queue);
610
+ await auditContext(queue, {
611
+ userId,
612
+ automationId,
613
+ });
589
614
  }
590
615
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
591
616
  if (!shouldConsume) {
@@ -633,13 +658,13 @@ class RabbitMq implements IAfRabbitMq {
633
658
  });
634
659
  }
635
660
 
636
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
661
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
637
662
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
638
663
  RabbitMq.validateName('exchange', exchange);
639
664
  RabbitMq.validateName('queue', queue);
640
665
  const { limit, deadMessageTtl } = optionsWithDefaults;
641
666
  await this.saveConsumer(queue, callback, options);
642
- const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
667
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
643
668
 
644
669
  return channel.addSetup(async (c: ConfirmChannel) => {
645
670
  const assertExchange = await assertExchangeFanout(c, exchange);
@@ -657,11 +682,11 @@ class RabbitMq implements IAfRabbitMq {
657
682
  });
658
683
  }
659
684
 
660
- async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
685
+ async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
661
686
  return wrapSetImmediate(async () => {
662
687
  RabbitMq.validateName('exchange', exchange);
663
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
664
- await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
688
+ const channel: ChannelWrapper = await this.assertChannel();
689
+ await this.assertExchange(exchange);
665
690
  await channel.publish(exchange, '',
666
691
  Buffer.from(JSON.stringify(content)),
667
692
  RabbitMq.getPublishOptions(customHeaders));
@@ -675,7 +700,7 @@ class RabbitMq implements IAfRabbitMq {
675
700
  customHeaders?: any,
676
701
  ): Promise<boolean | undefined> {
677
702
  try {
678
- await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
703
+ await this.assertChannel();
679
704
  } catch (e) {
680
705
  logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
681
706
  throw e;
@@ -683,7 +708,7 @@ class RabbitMq implements IAfRabbitMq {
683
708
 
684
709
  try {
685
710
  RabbitMq.validateName('queue', queue);
686
- await this.assertQueue(queue, ConnectionPurpose.Publish, options);
711
+ await this.assertQueue(queue, options);
687
712
  } catch (e) {
688
713
  logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
689
714
  throw e;
@@ -696,41 +721,34 @@ class RabbitMq implements IAfRabbitMq {
696
721
  debug(`rabbit: sending to queue ${queue}`, { res });
697
722
  return res;
698
723
  } catch (e) {
699
- logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
724
+ const isConnected = await this.isConnected();
725
+ logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
700
726
  throw e;
701
727
  }
702
728
  }
703
729
 
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);
730
+ async isConnected() : Promise<boolean> {
731
+ const connection = await this.getConnection();
732
+ const isConnected = connection.isConnected();
733
+ if (!isConnected) {
734
+ logger.error('rabbit: isConnected - false');
735
+ return false;
736
+ }
737
+ const channel: any = await this.assertChannel();
738
+ try {
739
+ await Promise.all([
740
+ channel.waitForConnect(),
741
+ ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
742
+ ]);
743
+ } catch (e) {
744
+ logger.error('rabbit: isConnected - false');
745
+ return false;
746
+ }
747
+ logger.info('rabbit: isConnected - true');
748
+ return true;
731
749
  }
732
750
 
733
- async gracefulShutdown(signal: string): Promise<void> {
751
+ async gracefulShutdown(signal: string) : Promise<void> {
734
752
  const tagsNumber = this.consumersTags.length;
735
753
  logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
736
754
  const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
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
- }
package/src/lib/utils.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { ChannelWrapper } from 'amqp-connection-manager';
2
- import { ConfirmChannel } from 'amqplib';
2
+ import { ConfirmChannel, Replies } from 'amqplib';
3
+
4
+ export const assertExchangeFanout = async (
5
+ c: ChannelWrapper | ConfirmChannel,
6
+ exchangeName: string,
7
+ ): Promise<Replies.AssertExchange> => c.assertExchange(exchangeName, 'fanout');
3
8
 
4
- export const assertExchangeFanout = async (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
5
9
  export const wrapSetImmediate = (callback: () => any) => new Promise<any>((resolve, reject) => {
6
10
  setImmediate(async () => {
7
11
  try {