@autofleet/rabbit 3.2.6-testing → 3.2.7

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
  }
@@ -117,10 +88,11 @@ class RabbitMq {
117
88
  this.creatingConnection = false;
118
89
  this.exchanges = {};
119
90
  this.queues = {};
91
+ this.consumers = [];
120
92
  this.options = options;
121
- this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
93
+ this.redisClient = redisConfig && redis_1.default(redisConfig);
122
94
  if (this.redisClient) {
123
- this.redisLock = (0, util_1.promisify)((0, redis_lock_1.default)(this.redisClient));
95
+ this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
124
96
  }
125
97
  this.consumersTags = [];
126
98
  logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
@@ -133,8 +105,41 @@ class RabbitMq {
133
105
  });
134
106
  }
135
107
  }
108
+ static parseMsg(msg) {
109
+ let { content } = msg;
110
+ content = content.toString();
111
+ try {
112
+ content = JSON.parse(content);
113
+ }
114
+ catch (e) { }
115
+ return {
116
+ ...msg,
117
+ content,
118
+ };
119
+ }
120
+ static validateName(type, name) {
121
+ if (!name || name === '') {
122
+ throw new rabbitError_1.default(`error while using ${type} with no name`);
123
+ }
124
+ }
125
+ static getPublishOptions(customHeaders = {}) {
126
+ const trace = zehut_1.getCurrentPayload();
127
+ const user = trace?.context?.get(consts_1.USER_OBJECT);
128
+ const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
129
+ const outbreakTrace = zehut_1.outbreak.getCurrentContext();
130
+ return {
131
+ timestamp: moment_1.default().unix(),
132
+ timeout: PUBLISH_TIMEOUT,
133
+ headers: {
134
+ creationTimestamp: moment_1.default().valueOf(),
135
+ ...customHeaders,
136
+ [consts_1.USER_TRACING_HEADER]: user?.id,
137
+ [consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
138
+ },
139
+ };
140
+ }
136
141
  async getConnection() {
137
- return new Promise(async (resolve) => {
142
+ return new Promise(async (resolve, reject) => {
138
143
  if (this.blockReconnect) {
139
144
  debug('rabbit: block reconnect');
140
145
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -142,29 +147,57 @@ class RabbitMq {
142
147
  return resolve();
143
148
  }
144
149
  if (this.connection !== null) {
145
- debug('rabbit: connection already exist');
146
150
  if (this.options?.disableReconnect || this.connection?.isConnected()) {
151
+ debug('rabbit: connection - is connected');
147
152
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
148
153
  // @ts-ignore
149
154
  return resolve(this.connection);
150
155
  }
151
- debug('rabbit: connection already exist - reconnecting');
156
+ debug('rabbit: connection - reconnecting');
152
157
  }
153
158
  if (this.creatingConnection) {
154
159
  debug('rabbit: creating connection emi');
155
160
  this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
161
+ this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
156
162
  return;
157
163
  }
158
164
  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}}`]);
165
+ let isResolved = false;
166
+ // It is import to use it as a function and not as a variable
167
+ // because of k8s changes the env variables
168
+ // and we want to use the new values
169
+ const findServers = () => {
170
+ const userName = process.env.RABBITMQ_USERNAME || 'guest';
171
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
172
+ const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
173
+ debug('rabbit: creating connection', { host, userName, HEARTBEAT });
174
+ return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
175
+ };
176
+ const defaultUrls = findServers();
177
+ const connection = await amqp_connection_manager_1.connect(defaultUrls, {
178
+ findServers,
179
+ });
161
180
  this.connection = connection;
181
+ this.connection.on('error', (err) => {
182
+ logger_1.default.error('rabbit: connection error', { err });
183
+ if (!isResolved) {
184
+ isResolved = true;
185
+ reject(err);
186
+ this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
187
+ }
188
+ });
189
+ this.connection.on('connectFailed', (err) => {
190
+ this.consumersTags = [];
191
+ logger_1.default.error('rabbit: connection connectFailed', { err });
192
+ if (!isResolved) {
193
+ isResolved = true;
194
+ reject(err);
195
+ this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
196
+ }
197
+ });
162
198
  this.connection.on('disconnect', ({ err }) => {
199
+ this.consumersTags = [];
163
200
  debug('rabbit: connection closed');
164
- this.exchanges = {};
165
- this.queues = {};
166
- this.connection = null;
167
- this.channel = null;
168
201
  if (this.options?.disableReconnect) {
169
202
  logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
170
203
  this.blockReconnect = true;
@@ -175,19 +208,26 @@ class RabbitMq {
175
208
  });
176
209
  this.connection.once('connect', async () => {
177
210
  debug('rabbit: connection established');
178
- await this.loadConsumers();
179
211
  this.creatingConnection = false;
180
212
  this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
213
+ isResolved = true;
181
214
  resolve(connection);
182
215
  });
183
216
  });
184
217
  }
185
- async getNewChannel({ name = '', onClose = null } = {}) {
218
+ async getNewChannel({ name = utils_1.rand().toString(), onClose = null, options = {} } = {}) {
186
219
  return new Promise(async (resolve, reject) => {
187
220
  const connection = await this.getConnection();
188
- const channel = connection.createChannel({});
221
+ const channel = connection.createChannel({
222
+ ...options,
223
+ });
224
+ let isResolved = false;
189
225
  channel.on('error', (err) => {
190
- logger_1.default.error(`rabbit: channel ${name} error`, { err });
226
+ logger_1.default.error(`rabbit: channel error ${name} error`, { err });
227
+ if (!isResolved) {
228
+ isResolved = true;
229
+ reject(err);
230
+ }
191
231
  });
192
232
  channel.on('close', (...args) => {
193
233
  logger_1.default.error(`rabbit: channel ${name} closed`, { args });
@@ -197,6 +237,7 @@ class RabbitMq {
197
237
  });
198
238
  channel.once('connect', () => {
199
239
  debug(`rabbit: channel ${name} CONNECTED`);
240
+ isResolved = true;
200
241
  resolve(channel);
201
242
  });
202
243
  });
@@ -207,10 +248,9 @@ class RabbitMq {
207
248
  return resolve(this.channel);
208
249
  }
209
250
  try {
210
- const channel = await this.getNewChannel({
211
- onClose: () => {
212
- this.channel = null;
213
- },
251
+ const channel = await this.getNewChannel({});
252
+ channel.on('error', (err) => {
253
+ logger_1.default.error('rabbit: channel error', { err });
214
254
  });
215
255
  this.channel = channel;
216
256
  resolve(channel);
@@ -225,25 +265,26 @@ class RabbitMq {
225
265
  if (this.exchanges[exchangeName]) {
226
266
  return this.exchanges[exchangeName];
227
267
  }
228
- const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
268
+ const exchange = await utils_1.assertExchangeFanout(channel, exchangeName);
229
269
  this.exchanges[exchangeName] = exchange;
230
270
  return exchange;
231
271
  }
232
272
  async getQueueLength(queue) {
233
273
  RabbitMq.validateName('queue', queue);
234
- const channel = await this.assertChannel();
274
+ const { channel } = this;
275
+ if (!channel) {
276
+ throw new Error('channel is not defined');
277
+ }
235
278
  debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
236
- return channel.checkQueue(queue);
279
+ return channel?.checkQueue(queue);
237
280
  }
238
281
  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
- });
282
+ RabbitMq.validateName('queue', queue);
283
+ const channel = await this.assertChannel();
284
+ logger_1.default.info('rabbit: deleting queue', { queue });
285
+ const deleteQueueRes = await channel.deleteQueue(queue);
286
+ debug('queue deleted', deleteQueueRes);
287
+ return deleteQueueRes;
247
288
  }
248
289
  async bindQueue(queue, exchange) {
249
290
  const channel = await this.assertChannel();
@@ -265,7 +306,7 @@ class RabbitMq {
265
306
  }
266
307
  catch (e) {
267
308
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
268
- if (this.options?.retryAssert) {
309
+ if (!this.options?.dontRetryAssert) {
269
310
  debug('retrying assertQueue', { queueName });
270
311
  const channel = await this.assertChannel({ force: true });
271
312
  await this.deleteQueue(queueName);
@@ -288,17 +329,8 @@ class RabbitMq {
288
329
  options,
289
330
  });
290
331
  }
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
332
  async consume(queue, callback, options) {
299
- if (this.connection && !this.creatingConnection) {
300
- this.consumeFromRabbit(queue, callback, options);
301
- }
333
+ await this.consumeFromRabbit(queue, callback, options);
302
334
  return this.saveConsumer(queue, callback, options);
303
335
  }
304
336
  async lockRedisIfNeeded(msg, options) {
@@ -318,46 +350,86 @@ class RabbitMq {
318
350
  async consumeFromRabbit(queue, callback, options) {
319
351
  const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
320
352
  RabbitMq.validateName('queue', queue);
321
- const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, } = optionsWithDefaults;
353
+ const uniqueId = uuid_1.v4();
354
+ const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
322
355
  if (useConsumeWithLock) {
323
356
  if (!this.redisLock) {
324
357
  throw new Error('Usage of consumeWithLock requires RedisInstance');
325
358
  }
326
359
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
327
360
  }
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) => {
361
+ const channel = await this.assertChannel();
362
+ return channel.addSetup(async (confirmChannel) => {
363
+ await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
364
+ await confirmChannel.prefetch(limit, true);
365
+ const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
333
366
  if (!msg) {
334
367
  return null;
335
368
  }
336
369
  const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
370
+ const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
371
+ const parsedMessage = RabbitMq.parseMsg(msg);
372
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
373
+ const trace = zehut_1.newTrace(zehut_1.traceTypes.RABBIT);
374
+ // setting also outbreak trace as part of legacy code
375
+ const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
376
+ // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
377
+ // and we don't want to fail the flow because of it
378
+ if (userId && enableRabbitTrace) {
379
+ try {
380
+ await Promise.all([
381
+ zehut_1.createOrSetRabbitTrace(trace, userId),
382
+ zehut_1.createOrSetRabbitTrace(outbreakTrace, userId),
383
+ ]);
384
+ }
385
+ catch (e) {
386
+ logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
387
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
388
+ }
389
+ }
337
390
  if (traceId) {
338
- const trace = (0, outbreak_1.newTrace)(outbreak_1.traceTypes.RABBIT);
339
391
  trace?.context?.set(consts_1.TRACING_HEADER, traceId);
392
+ outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
393
+ }
394
+ if (auditContext) {
395
+ await auditContext(queue);
340
396
  }
341
- const parsedMessage = RabbitMq.parseMsg(msg);
342
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
343
397
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
344
398
  if (!shouldConsume) {
345
399
  await this.unlockRedisIfNeeded(releaseLock);
346
- return this.ack(channel, msg)(msg);
400
+ return this.ack(confirmChannel, msg)(msg);
347
401
  }
402
+ let messageAcked = false;
403
+ // setting the localAck function to be used in the callback
404
+ const localAck = async () => {
405
+ if (messageAcked) {
406
+ return;
407
+ }
408
+ debug('rabbit localAck', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
409
+ messageAcked = true;
410
+ return this.ack(confirmChannel, msg, true, releaseLock)(msg);
411
+ };
412
+ const localNack = async (_, nackOptions = {}) => {
413
+ if (messageAcked) {
414
+ return;
415
+ }
416
+ debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
417
+ messageAcked = true;
418
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
419
+ };
348
420
  try {
349
- await callback(parsedMessage, this.ack(channel, msg, true, releaseLock), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock));
421
+ await callback(parsedMessage, localAck, localNack);
350
422
  }
351
423
  catch (e) {
352
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
424
+ await localNack(msg);
353
425
  }
354
- });
426
+ }, types_1.CONSUMER_DEFAULT_OPTIONS);
355
427
  if (!consumerTag) {
356
428
  logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
357
429
  }
358
430
  else {
359
431
  logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
360
- this.consumersTags.push([c, consumerTag]);
432
+ this.consumersTags.push([confirmChannel, consumerTag]);
361
433
  }
362
434
  });
363
435
  }
@@ -366,9 +438,9 @@ class RabbitMq {
366
438
  RabbitMq.validateName('exchange', exchange);
367
439
  RabbitMq.validateName('queue', queue);
368
440
  const { limit, deadMessageTtl } = optionsWithDefaults;
369
- const channel = await this.getNewChannel();
441
+ const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
370
442
  return channel.addSetup(async (c) => {
371
- const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
443
+ const assertExchange = await utils_1.assertExchangeFanout(c, exchange);
372
444
  await c.assertQueue(queue);
373
445
  this.exchanges[exchange] = assertExchange;
374
446
  await c.prefetch(limit, true);
@@ -379,7 +451,7 @@ class RabbitMq {
379
451
  });
380
452
  }
381
453
  async publish(exchange, content, customHeaders) {
382
- return (0, utils_1.wrapSetImmediate)(async () => {
454
+ return utils_1.wrapSetImmediate(async () => {
383
455
  RabbitMq.validateName('exchange', exchange);
384
456
  const channel = await this.assertChannel();
385
457
  await this.assertExchange(exchange);
@@ -388,21 +460,44 @@ class RabbitMq {
388
460
  }
389
461
  async sendToQueue(queue, content, options, customHeaders, isBlocking) {
390
462
  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;
463
+ try {
464
+ RabbitMq.validateName('queue', queue);
465
+ await this.assertChannel();
466
+ await this.assertQueue(queue, options);
467
+ const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
468
+ debug(`rabbit: sending to queue ${queue}`, { res });
469
+ return res;
470
+ }
471
+ catch (e) {
472
+ logger_1.default.error(`rabbit: failed to send to queue ${queue}`, { e });
473
+ throw e;
474
+ }
397
475
  };
398
476
  if (isBlocking) {
399
477
  return callback();
400
478
  }
401
- return (0, utils_1.wrapSetImmediate)(callback);
479
+ return utils_1.wrapSetImmediate(callback);
402
480
  }
403
481
  async isConnected() {
404
482
  const connection = await this.getConnection();
405
- return connection.isConnected();
483
+ const isConnected = connection.isConnected();
484
+ if (!isConnected) {
485
+ logger_1.default.error('rabbit: isConnected - false');
486
+ return false;
487
+ }
488
+ const channel = await this.assertChannel();
489
+ try {
490
+ await Promise.all([
491
+ channel.waitForConnect(),
492
+ ...this.consumers.map((c) => channel.checkQueue(c.queue)),
493
+ ]);
494
+ }
495
+ catch (e) {
496
+ logger_1.default.error('rabbit: isConnected - false');
497
+ return false;
498
+ }
499
+ logger_1.default.info('rabbit: isConnected - true');
500
+ return true;
406
501
  }
407
502
  async gracefulShutdown(signal) {
408
503
  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.7",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/src/index.ts CHANGED
@@ -665,10 +665,8 @@ class RabbitMq implements IAfRabbitMq {
665
665
  logger.error('rabbit: isConnected - false');
666
666
  return false;
667
667
  }
668
- const channel = await this.assertChannel();
668
+ const channel: any = await this.assertChannel();
669
669
  try {
670
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
671
- // @ts-ignore
672
670
  await Promise.all([
673
671
  channel.waitForConnect(),
674
672
  ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
package/.env DELETED
@@ -1,3 +0,0 @@
1
- #RABBITMQ_SERVICE_HOST=
2
- RABBITMQ_USERNAME=default_user_w2_vEYNVrLWiAnWKmPH
3
- RABBITMQ_PASSWORD=GUAPvNXJbkgbkGeeFUYbZfOxVfR4cuf8