@autofleet/rabbit 3.2.4 → 3.2.6-testing

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