@autofleet/rabbit 3.2.6-testing → 3.2.6

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
@@ -1,8 +1,9 @@
1
1
  /// <reference types="node" />
2
2
  import { EventEmitter } from 'events';
3
- import { AmqpConnectionManager, ChannelWrapper } from 'amqp-connection-manager';
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 { TRACING_HEADER, USER_TRACING_HEADER } from './lib/consts';
6
7
  import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache } from './lib/types';
7
8
  export interface IAfRabbitMq {
8
9
  ack: any;
@@ -16,6 +17,9 @@ export interface IAfRabbitMq {
16
17
  sendToQueue: any;
17
18
  redisClient?: any;
18
19
  }
20
+ interface NackOptions {
21
+ skipRetry?: boolean;
22
+ }
19
23
  export interface AfRabbitOptions {
20
24
  disableReconnect?: boolean;
21
25
  /**
@@ -24,16 +28,18 @@ export interface AfRabbitOptions {
24
28
  */
25
29
  dontGracefulShutdown?: boolean;
26
30
  /**
27
- * retry on creation error
31
+ * dont retry on creation error
28
32
  * @default false
29
33
  */
30
- retryAssert?: boolean;
34
+ dontRetryAssert?: boolean;
35
+ rabbitHost?: string;
31
36
  }
32
- type newChannelOpts = {
37
+ declare type newChannelOpts = {
33
38
  name?: string;
34
39
  onClose?: null | ((args: any | null) => void);
40
+ options?: CreateChannelOpts | undefined;
35
41
  };
36
- type assertChannelOpts = {
42
+ declare type assertChannelOpts = {
37
43
  channelName?: string;
38
44
  force?: boolean;
39
45
  };
@@ -42,14 +48,14 @@ declare class RabbitMq implements IAfRabbitMq {
42
48
  static validateName(type: string, name: string): void;
43
49
  static getPublishOptions(customHeaders?: CustomMessageHeaders): {
44
50
  timestamp: number;
51
+ timeout: number;
45
52
  headers: {
53
+ "x-af-user-id": any;
46
54
  "x-trace-id": any;
47
55
  redisTimestampValidationKey?: string | undefined;
48
56
  creationTimestamp: number;
49
57
  };
50
58
  };
51
- PUBLISH_TIMEOUT: number;
52
- PUBLISH_ERROR_MSG: string;
53
59
  DISCONNECT_MSG: string;
54
60
  RECONNECT_MSG: string;
55
61
  channel: ChannelWrapper | null;
@@ -67,10 +73,10 @@ declare class RabbitMq implements IAfRabbitMq {
67
73
  private consumers;
68
74
  constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
69
75
  private shouldConsumeMessageByTimestamp;
70
- ack: (channel: ChannelWrapper, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
71
- nack: (channel: ChannelWrapper, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: any) => Promise<any>;
76
+ ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
77
+ nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
72
78
  getConnection(): Promise<AmqpConnectionManager>;
73
- getNewChannel({ name, onClose }?: newChannelOpts): Promise<ChannelWrapper>;
79
+ getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
74
80
  assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
75
81
  assertExchange(exchangeName: string, options?: any): Promise<any>;
76
82
  getQueueLength(queue: string): Promise<Replies.AssertQueue>;
@@ -78,7 +84,6 @@ declare class RabbitMq implements IAfRabbitMq {
78
84
  bindQueue(queue: string, exchange: string): Promise<void>;
79
85
  assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
80
86
  private saveConsumer;
81
- private loadConsumers;
82
87
  consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
83
88
  private lockRedisIfNeeded;
84
89
  private unlockRedisIfNeeded;
package/dist/index.js CHANGED
@@ -9,50 +9,20 @@ const util_1 = require("util");
9
9
  const moment_1 = __importDefault(require("moment"));
10
10
  const redis_lock_1 = __importDefault(require("redis-lock"));
11
11
  const amqp_connection_manager_1 = require("amqp-connection-manager");
12
- const outbreak_1 = require("@autofleet/outbreak");
12
+ const zehut_1 = require("@autofleet/zehut");
13
+ const uuid_1 = require("uuid");
13
14
  const logger_1 = __importDefault(require("./logger"));
14
15
  const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
15
16
  const redis_1 = __importDefault(require("./lib/redis"));
16
17
  const utils_1 = require("./lib/utils");
17
18
  const consts_1 = require("./lib/consts");
19
+ const types_1 = require("./lib/types");
18
20
  // const debug = nodeDebug('af-rabbitmq')
19
21
  const debug = logger_1.default.info;
20
- const USERNAME = process.env.RABBITMQ_USERNAME || 'guest';
21
- const PASSWORD = process.env.RABBITMQ_PASSWORD || 'guest';
22
- const HOST = process.env.RABBITMQ_SERVICE_HOST || 'localhost';
22
+ const PUBLISH_TIMEOUT = 1000 * 10;
23
23
  const HEARTBEAT = '60';
24
24
  class RabbitMq {
25
- static parseMsg(msg) {
26
- let { content } = msg;
27
- content = content.toString();
28
- try {
29
- content = JSON.parse(content);
30
- }
31
- catch (e) { }
32
- return {
33
- ...msg,
34
- content,
35
- };
36
- }
37
- static validateName(type, name) {
38
- if (!name || name === '') {
39
- throw new rabbitError_1.default(`error while using ${type} with no name`);
40
- }
41
- }
42
- static getPublishOptions(customHeaders = {}) {
43
- const trace = (0, outbreak_1.getCurrentContext)();
44
- return {
45
- timestamp: (0, moment_1.default)().unix(),
46
- headers: {
47
- creationTimestamp: (0, moment_1.default)().valueOf(),
48
- ...customHeaders,
49
- [consts_1.TRACING_HEADER]: trace?.context?.get(consts_1.TRACING_HEADER),
50
- },
51
- };
52
- }
53
25
  constructor(options, redisConfig) {
54
- this.PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
55
- this.PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
56
26
  this.DISCONNECT_MSG = 'rabbit: connection disconnect';
57
27
  this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
58
28
  this.consumers = [];
@@ -70,6 +40,7 @@ class RabbitMq {
70
40
  };
71
41
  this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
72
42
  if (msg) {
43
+ debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
73
44
  await channel.ack(msg);
74
45
  const { properties: { headers } } = msg;
75
46
  const timestamp = headers?.creationTimestamp;
@@ -102,11 +73,11 @@ class RabbitMq {
102
73
  : 1,
103
74
  });
104
75
  }
76
+ debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
105
77
  await channel.ack(msg);
106
78
  }
107
79
  else {
108
80
  logger_1.default.error('no channel or msg', {
109
- channel: channel ? channel.name : '',
110
81
  msg,
111
82
  });
112
83
  }
@@ -118,9 +89,9 @@ class RabbitMq {
118
89
  this.exchanges = {};
119
90
  this.queues = {};
120
91
  this.options = options;
121
- this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
92
+ this.redisClient = redisConfig && redis_1.default(redisConfig);
122
93
  if (this.redisClient) {
123
- this.redisLock = (0, util_1.promisify)((0, redis_lock_1.default)(this.redisClient));
94
+ this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
124
95
  }
125
96
  this.consumersTags = [];
126
97
  logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
@@ -133,8 +104,41 @@ class RabbitMq {
133
104
  });
134
105
  }
135
106
  }
107
+ static parseMsg(msg) {
108
+ let { content } = msg;
109
+ content = content.toString();
110
+ try {
111
+ content = JSON.parse(content);
112
+ }
113
+ catch (e) { }
114
+ return {
115
+ ...msg,
116
+ content,
117
+ };
118
+ }
119
+ static validateName(type, name) {
120
+ if (!name || name === '') {
121
+ throw new rabbitError_1.default(`error while using ${type} with no name`);
122
+ }
123
+ }
124
+ static getPublishOptions(customHeaders = {}) {
125
+ const trace = zehut_1.getCurrentPayload();
126
+ const user = trace?.context?.get(consts_1.USER_OBJECT);
127
+ const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
128
+ const outbreakTrace = zehut_1.outbreak.getCurrentContext();
129
+ return {
130
+ timestamp: moment_1.default().unix(),
131
+ timeout: PUBLISH_TIMEOUT,
132
+ headers: {
133
+ creationTimestamp: moment_1.default().valueOf(),
134
+ ...customHeaders,
135
+ [consts_1.USER_TRACING_HEADER]: user?.id,
136
+ [consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
137
+ },
138
+ };
139
+ }
136
140
  async getConnection() {
137
- return new Promise(async (resolve) => {
141
+ return new Promise(async (resolve, reject) => {
138
142
  if (this.blockReconnect) {
139
143
  debug('rabbit: block reconnect');
140
144
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -142,29 +146,57 @@ class RabbitMq {
142
146
  return resolve();
143
147
  }
144
148
  if (this.connection !== null) {
145
- debug('rabbit: connection already exist');
146
149
  if (this.options?.disableReconnect || this.connection?.isConnected()) {
150
+ debug('rabbit: connection - is connected');
147
151
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
148
152
  // @ts-ignore
149
153
  return resolve(this.connection);
150
154
  }
151
- debug('rabbit: connection already exist - reconnecting');
155
+ debug('rabbit: connection - reconnecting');
152
156
  }
153
157
  if (this.creatingConnection) {
154
158
  debug('rabbit: creating connection emi');
155
159
  this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
160
+ this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
156
161
  return;
157
162
  }
158
163
  this.creatingConnection = true;
159
- debug('rabbit: creating connection', { HOST, USERNAME, HEARTBEAT });
160
- const connection = await (0, amqp_connection_manager_1.connect)([`amqp://${USERNAME}:${PASSWORD}@${HOST}?heartbeat=${HEARTBEAT}}`]);
164
+ let isResolved = false;
165
+ // It is import to use it as a function and not as a variable
166
+ // because of k8s changes the env variables
167
+ // and we want to use the new values
168
+ const findServers = () => {
169
+ const userName = process.env.RABBITMQ_USERNAME || 'guest';
170
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
171
+ const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
172
+ debug('rabbit: creating connection', { host, userName, HEARTBEAT });
173
+ return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
174
+ };
175
+ const defaultUrls = findServers();
176
+ const connection = await amqp_connection_manager_1.connect(defaultUrls, {
177
+ findServers,
178
+ });
161
179
  this.connection = connection;
180
+ this.connection.on('error', (err) => {
181
+ logger_1.default.error('rabbit: connection error', { err });
182
+ if (!isResolved) {
183
+ isResolved = true;
184
+ reject(err);
185
+ this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
186
+ }
187
+ });
188
+ this.connection.on('connectFailed', (err) => {
189
+ this.consumersTags = [];
190
+ logger_1.default.error('rabbit: connection connectFailed', { err });
191
+ if (!isResolved) {
192
+ isResolved = true;
193
+ reject(err);
194
+ this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
195
+ }
196
+ });
162
197
  this.connection.on('disconnect', ({ err }) => {
198
+ this.consumersTags = [];
163
199
  debug('rabbit: connection closed');
164
- this.exchanges = {};
165
- this.queues = {};
166
- this.connection = null;
167
- this.channel = null;
168
200
  if (this.options?.disableReconnect) {
169
201
  logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
170
202
  this.blockReconnect = true;
@@ -175,19 +207,26 @@ class RabbitMq {
175
207
  });
176
208
  this.connection.once('connect', async () => {
177
209
  debug('rabbit: connection established');
178
- await this.loadConsumers();
179
210
  this.creatingConnection = false;
180
211
  this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
212
+ isResolved = true;
181
213
  resolve(connection);
182
214
  });
183
215
  });
184
216
  }
185
- async getNewChannel({ name = '', onClose = null } = {}) {
217
+ async getNewChannel({ name = utils_1.rand().toString(), onClose = null, options = {} } = {}) {
186
218
  return new Promise(async (resolve, reject) => {
187
219
  const connection = await this.getConnection();
188
- const channel = connection.createChannel({});
220
+ const channel = connection.createChannel({
221
+ ...options,
222
+ });
223
+ let isResolved = false;
189
224
  channel.on('error', (err) => {
190
- logger_1.default.error(`rabbit: channel ${name} error`, { err });
225
+ logger_1.default.error(`rabbit: channel error ${name} error`, { err });
226
+ if (!isResolved) {
227
+ isResolved = true;
228
+ reject(err);
229
+ }
191
230
  });
192
231
  channel.on('close', (...args) => {
193
232
  logger_1.default.error(`rabbit: channel ${name} closed`, { args });
@@ -197,6 +236,7 @@ class RabbitMq {
197
236
  });
198
237
  channel.once('connect', () => {
199
238
  debug(`rabbit: channel ${name} CONNECTED`);
239
+ isResolved = true;
200
240
  resolve(channel);
201
241
  });
202
242
  });
@@ -207,10 +247,9 @@ class RabbitMq {
207
247
  return resolve(this.channel);
208
248
  }
209
249
  try {
210
- const channel = await this.getNewChannel({
211
- onClose: () => {
212
- this.channel = null;
213
- },
250
+ const channel = await this.getNewChannel({});
251
+ channel.on('error', (err) => {
252
+ logger_1.default.error('rabbit: channel error', { err });
214
253
  });
215
254
  this.channel = channel;
216
255
  resolve(channel);
@@ -225,25 +264,26 @@ class RabbitMq {
225
264
  if (this.exchanges[exchangeName]) {
226
265
  return this.exchanges[exchangeName];
227
266
  }
228
- const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
267
+ const exchange = await utils_1.assertExchangeFanout(channel, exchangeName);
229
268
  this.exchanges[exchangeName] = exchange;
230
269
  return exchange;
231
270
  }
232
271
  async getQueueLength(queue) {
233
272
  RabbitMq.validateName('queue', queue);
234
- const channel = await this.assertChannel();
273
+ const { channel } = this;
274
+ if (!channel) {
275
+ throw new Error('channel is not defined');
276
+ }
235
277
  debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
236
- return channel.checkQueue(queue);
278
+ return channel?.checkQueue(queue);
237
279
  }
238
280
  async deleteQueue(queue) {
239
- return new Promise(async (resolve, reject) => {
240
- RabbitMq.validateName('queue', queue);
241
- const channel = await this.assertChannel();
242
- logger_1.default.info('rabbit: deleting queue', { queue });
243
- const deleteQueueRes = await channel.deleteQueue(queue);
244
- debug('queue deleted', deleteQueueRes);
245
- resolve(deleteQueueRes);
246
- });
281
+ RabbitMq.validateName('queue', queue);
282
+ const channel = await this.assertChannel();
283
+ logger_1.default.info('rabbit: deleting queue', { queue });
284
+ const deleteQueueRes = await channel.deleteQueue(queue);
285
+ debug('queue deleted', deleteQueueRes);
286
+ return deleteQueueRes;
247
287
  }
248
288
  async bindQueue(queue, exchange) {
249
289
  const channel = await this.assertChannel();
@@ -265,7 +305,7 @@ class RabbitMq {
265
305
  }
266
306
  catch (e) {
267
307
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
268
- if (this.options?.retryAssert) {
308
+ if (!this.options?.dontRetryAssert) {
269
309
  debug('retrying assertQueue', { queueName });
270
310
  const channel = await this.assertChannel({ force: true });
271
311
  await this.deleteQueue(queueName);
@@ -288,18 +328,8 @@ class RabbitMq {
288
328
  options,
289
329
  });
290
330
  }
291
- async loadConsumers() {
292
- debug('rabbit: loading consumers', { consumers: this.consumers.length });
293
- if (this.consumers.length > 0) {
294
- await Promise.all(this.consumers.map((consumer) => this
295
- .consumeFromRabbit(consumer.queue, consumer.callback, consumer.options)));
296
- }
297
- }
298
331
  async consume(queue, callback, options) {
299
- if (this.connection && !this.creatingConnection) {
300
- this.consumeFromRabbit(queue, callback, options);
301
- }
302
- return this.saveConsumer(queue, callback, options);
332
+ return this.consumeFromRabbit(queue, callback, options);
303
333
  }
304
334
  async lockRedisIfNeeded(msg, options) {
305
335
  const { properties: { headers } } = msg;
@@ -318,46 +348,86 @@ class RabbitMq {
318
348
  async consumeFromRabbit(queue, callback, options) {
319
349
  const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
320
350
  RabbitMq.validateName('queue', queue);
321
- const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, } = optionsWithDefaults;
351
+ const uniqueId = uuid_1.v4();
352
+ const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
322
353
  if (useConsumeWithLock) {
323
354
  if (!this.redisLock) {
324
355
  throw new Error('Usage of consumeWithLock requires RedisInstance');
325
356
  }
326
357
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
327
358
  }
328
- const channel = await this.getNewChannel({ name: `consume-${queue}` });
329
- return channel.addSetup(async (c) => {
330
- await c.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
331
- await c.prefetch(limit, true);
332
- const { consumerTag } = await c.consume(queue, async (msg) => {
359
+ const channel = await this.getNewChannel({ name: `consume-queue-${queue}` });
360
+ return channel.addSetup(async (confirmChannel) => {
361
+ await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
362
+ await confirmChannel.prefetch(limit, true);
363
+ const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
333
364
  if (!msg) {
334
365
  return null;
335
366
  }
336
367
  const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
368
+ const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
369
+ const parsedMessage = RabbitMq.parseMsg(msg);
370
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
371
+ const trace = zehut_1.newTrace(zehut_1.traceTypes.RABBIT);
372
+ // setting also outbreak trace as part of legacy code
373
+ const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
374
+ // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
375
+ // and we don't want to fail the flow because of it
376
+ if (userId && enableRabbitTrace) {
377
+ try {
378
+ await Promise.all([
379
+ zehut_1.createOrSetRabbitTrace(trace, userId),
380
+ zehut_1.createOrSetRabbitTrace(outbreakTrace, userId),
381
+ ]);
382
+ }
383
+ catch (e) {
384
+ logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
385
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
386
+ }
387
+ }
337
388
  if (traceId) {
338
- const trace = (0, outbreak_1.newTrace)(outbreak_1.traceTypes.RABBIT);
339
389
  trace?.context?.set(consts_1.TRACING_HEADER, traceId);
390
+ outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
391
+ }
392
+ if (auditContext) {
393
+ await auditContext(queue);
340
394
  }
341
- const parsedMessage = RabbitMq.parseMsg(msg);
342
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
343
395
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
344
396
  if (!shouldConsume) {
345
397
  await this.unlockRedisIfNeeded(releaseLock);
346
- return this.ack(channel, msg)(msg);
398
+ return this.ack(confirmChannel, msg)(msg);
347
399
  }
400
+ let messageAcked = false;
401
+ // setting the localAck function to be used in the callback
402
+ const localAck = async () => {
403
+ if (messageAcked) {
404
+ return;
405
+ }
406
+ debug('rabbit localAck', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
407
+ messageAcked = true;
408
+ return this.ack(confirmChannel, msg, true, releaseLock)(msg);
409
+ };
410
+ const localNack = async (_, nackOptions = {}) => {
411
+ if (messageAcked) {
412
+ return;
413
+ }
414
+ debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
415
+ messageAcked = true;
416
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
417
+ };
348
418
  try {
349
- await callback(parsedMessage, this.ack(channel, msg, true, releaseLock), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock));
419
+ await callback(parsedMessage, localAck, localNack);
350
420
  }
351
421
  catch (e) {
352
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
422
+ await localNack(msg);
353
423
  }
354
- });
424
+ }, types_1.CONSUMER_DEFAULT_OPTIONS);
355
425
  if (!consumerTag) {
356
426
  logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
357
427
  }
358
428
  else {
359
429
  logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
360
- this.consumersTags.push([c, consumerTag]);
430
+ this.consumersTags.push([confirmChannel, consumerTag]);
361
431
  }
362
432
  });
363
433
  }
@@ -366,9 +436,9 @@ class RabbitMq {
366
436
  RabbitMq.validateName('exchange', exchange);
367
437
  RabbitMq.validateName('queue', queue);
368
438
  const { limit, deadMessageTtl } = optionsWithDefaults;
369
- const channel = await this.getNewChannel();
439
+ const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
370
440
  return channel.addSetup(async (c) => {
371
- const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
441
+ const assertExchange = await utils_1.assertExchangeFanout(c, exchange);
372
442
  await c.assertQueue(queue);
373
443
  this.exchanges[exchange] = assertExchange;
374
444
  await c.prefetch(limit, true);
@@ -379,7 +449,7 @@ class RabbitMq {
379
449
  });
380
450
  }
381
451
  async publish(exchange, content, customHeaders) {
382
- return (0, utils_1.wrapSetImmediate)(async () => {
452
+ return utils_1.wrapSetImmediate(async () => {
383
453
  RabbitMq.validateName('exchange', exchange);
384
454
  const channel = await this.assertChannel();
385
455
  await this.assertExchange(exchange);
@@ -388,21 +458,41 @@ class RabbitMq {
388
458
  }
389
459
  async sendToQueue(queue, content, options, customHeaders, isBlocking) {
390
460
  const callback = async () => {
391
- RabbitMq.validateName('queue', queue);
392
- await this.assertChannel();
393
- await this.assertQueue(queue, options);
394
- const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
395
- debug(`rabbit: sending to queue ${queue}`, { res });
396
- return res;
461
+ try {
462
+ RabbitMq.validateName('queue', queue);
463
+ await this.assertChannel();
464
+ await this.assertQueue(queue, options);
465
+ const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
466
+ debug(`rabbit: sending to queue ${queue}`, { res });
467
+ return res;
468
+ }
469
+ catch (e) {
470
+ logger_1.default.error(`rabbit: failed to send to queue ${queue}`, { e });
471
+ throw e;
472
+ }
397
473
  };
398
474
  if (isBlocking) {
399
475
  return callback();
400
476
  }
401
- return (0, utils_1.wrapSetImmediate)(callback);
477
+ return utils_1.wrapSetImmediate(callback);
402
478
  }
403
479
  async isConnected() {
404
480
  const connection = await this.getConnection();
405
- return connection.isConnected();
481
+ const isConnected = connection.isConnected();
482
+ if (!isConnected) {
483
+ logger_1.default.error('rabbit: isConnected - false');
484
+ return false;
485
+ }
486
+ const channel = await this.assertChannel();
487
+ try {
488
+ await channel.waitForConnect();
489
+ }
490
+ catch (e) {
491
+ logger_1.default.error('rabbit: isConnected - false');
492
+ return false;
493
+ }
494
+ logger_1.default.info('rabbit: isConnected - true');
495
+ return true;
406
496
  }
407
497
  async gracefulShutdown(signal) {
408
498
  const tagsNumber = this.consumersTags.length;
@@ -2,12 +2,17 @@ export declare const DEFAULT_DEAD_TTL_TWO_DAYS: number;
2
2
  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
+ export declare const USER_TRACING_HEADER = "x-af-user-id";
6
+ export declare const USER_OBJECT = "userObject";
5
7
  export declare const DEFAULT_USE_CONSUME_WITH_LOCK = false;
6
8
  export declare const CONNECTION_CREATED_CONST = "connectionCreated";
9
+ export declare const CONNECTION_FAILED_CONST = "connectionFailed";
7
10
  export declare const DEFAULT_OPTIONS: {
8
11
  limit: number;
9
12
  retries: number;
10
13
  deadMessageTtl: number;
11
14
  lockTimeout: number;
12
15
  useConsumeWithLock: boolean;
16
+ auditContext: null;
17
+ enableRabbitTrace: boolean;
13
18
  };
@@ -1,16 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_OPTIONS = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = 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.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
+ exports.USER_TRACING_HEADER = 'x-af-user-id';
9
+ exports.USER_OBJECT = 'userObject';
8
10
  exports.DEFAULT_USE_CONSUME_WITH_LOCK = false;
9
11
  exports.CONNECTION_CREATED_CONST = 'connectionCreated';
12
+ exports.CONNECTION_FAILED_CONST = 'connectionFailed';
10
13
  exports.DEFAULT_OPTIONS = {
11
14
  limit: 1,
12
15
  retries: 1,
13
16
  deadMessageTtl: exports.DEFAULT_DEAD_TTL_TWO_DAYS,
14
17
  lockTimeout: exports.DEFAULT_LOCK_TIMEOUT,
15
18
  useConsumeWithLock: exports.DEFAULT_USE_CONSUME_WITH_LOCK,
19
+ auditContext: null,
20
+ enableRabbitTrace: false,
16
21
  };
@@ -1,4 +1,4 @@
1
- export type RedisConfig = {
1
+ export declare type RedisConfig = {
2
2
  host: string;
3
3
  port: number | undefined;
4
4
  prefix?: string;
@@ -1,15 +1,15 @@
1
- import { ConsumeMessage } from 'amqplib';
1
+ import { ConsumeMessage, Options } from 'amqplib';
2
2
  export interface ExchangesCache {
3
3
  [key: string]: any;
4
4
  }
5
5
  export interface QueuesCache {
6
6
  [key: string]: any;
7
7
  }
8
- export type CustomMessageHeaders = {
8
+ export declare type CustomMessageHeaders = {
9
9
  redisTimestampValidationKey?: string;
10
10
  };
11
- export type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
12
- export type ConsumeMessageOrNull = ConsumeMessage | null;
11
+ export declare type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
12
+ export declare type ConsumeMessageOrNull = ConsumeMessage | null;
13
13
  export interface ConsumeOptions {
14
14
  retries?: number;
15
15
  deadMessageTtl?: number;
@@ -17,5 +17,21 @@ export interface ConsumeOptions {
17
17
  limit?: number;
18
18
  lockTimeout?: number;
19
19
  useConsumeWithLock?: boolean;
20
+ auditContext?: any;
21
+ enableRabbitTrace?: boolean;
20
22
  }
21
- export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
23
+ export declare type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
24
+ export declare type newChannelOpts = {
25
+ name?: string;
26
+ onClose?: null | ((args: any | null) => void);
27
+ };
28
+ export declare type assertChannelOpts = {
29
+ channelName?: string;
30
+ force?: boolean;
31
+ };
32
+ export declare type AfConsumer = {
33
+ queue: string;
34
+ callback: CallbackFunction;
35
+ options: ConsumeOptions | undefined;
36
+ };
37
+ export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
package/dist/lib/types.js CHANGED
@@ -1,2 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONSUMER_DEFAULT_OPTIONS = void 0;
4
+ const HA_PROMOTE_ON_FAILURE = 'ha-promote-on-failure';
5
+ const HA_PROMOTE_ON_SHUTDOWN = 'ha-promote-on-shutdown';
6
+ exports.CONSUMER_DEFAULT_OPTIONS = {
7
+ arguments: {
8
+ [HA_PROMOTE_ON_FAILURE]: 'always',
9
+ [HA_PROMOTE_ON_SHUTDOWN]: 'always',
10
+ },
11
+ };
@@ -2,3 +2,4 @@ import { ChannelWrapper } from 'amqp-connection-manager';
2
2
  import { ConfirmChannel } from 'amqplib';
3
3
  export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<import("amqplib").Replies.AssertExchange>;
4
4
  export declare const wrapSetImmediate: (callback: () => any) => Promise<any>;
5
+ export declare const rand: () => number;
package/dist/lib/utils.js CHANGED
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.wrapSetImmediate = exports.assertExchangeFanout = void 0;
4
- const assertExchangeFanout = async (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
5
- exports.assertExchangeFanout = assertExchangeFanout;
6
- const wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
3
+ exports.rand = exports.wrapSetImmediate = exports.assertExchangeFanout = void 0;
4
+ exports.assertExchangeFanout = async (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
5
+ exports.wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
7
6
  setImmediate(async () => {
8
7
  try {
9
8
  const value = await callback();
@@ -14,4 +13,4 @@ const wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
14
13
  }
15
14
  });
16
15
  });
17
- exports.wrapSetImmediate = wrapSetImmediate;
16
+ exports.rand = () => Math.floor(Math.random() * 100000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "3.2.6-testing",
3
+ "version": "3.2.6",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/src/index.ts CHANGED
@@ -171,7 +171,6 @@ class RabbitMq implements IAfRabbitMq {
171
171
  this.creatingConnection = false;
172
172
  this.exchanges = {};
173
173
  this.queues = {};
174
- this.consumers = [];
175
174
  this.options = options;
176
175
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
177
176
  if (this.redisClient) {
@@ -384,7 +383,8 @@ class RabbitMq implements IAfRabbitMq {
384
383
  }
385
384
 
386
385
  try {
387
- const channel = await this.getNewChannel({});
386
+ const channel = await this.getNewChannel({
387
+ });
388
388
  channel.on('error', (err) => {
389
389
  logger.error('rabbit: channel error', { err });
390
390
  });
@@ -472,8 +472,7 @@ class RabbitMq implements IAfRabbitMq {
472
472
  }
473
473
 
474
474
  async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
475
- await this.consumeFromRabbit(queue, callback, options);
476
- return this.saveConsumer(queue, callback, options);
475
+ return this.consumeFromRabbit(queue, callback, options);
477
476
  }
478
477
 
479
478
  private async lockRedisIfNeeded(msg: any, options: any) {
@@ -509,7 +508,7 @@ class RabbitMq implements IAfRabbitMq {
509
508
  }
510
509
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
511
510
  }
512
- const channel = await this.assertChannel();
511
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-queue-${queue}` });
513
512
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
514
513
  await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
515
514
  await confirmChannel.prefetch(limit, true);
@@ -667,12 +666,7 @@ class RabbitMq implements IAfRabbitMq {
667
666
  }
668
667
  const channel = await this.assertChannel();
669
668
  try {
670
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
671
- // @ts-ignore
672
- await Promise.all([
673
- channel.waitForConnect(),
674
- ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
675
- ]);
669
+ await channel.waitForConnect();
676
670
  } catch (e) {
677
671
  logger.error('rabbit: isConnected - false');
678
672
  return false;
package/.env DELETED
@@ -1,3 +0,0 @@
1
- #RABBITMQ_SERVICE_HOST=
2
- RABBITMQ_USERNAME=default_user_w2_vEYNVrLWiAnWKmPH
3
- RABBITMQ_PASSWORD=GUAPvNXJbkgbkGeeFUYbZfOxVfR4cuf8