@autofleet/rabbit 2.1.16 → 2.2.0

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,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { Options } from 'amqplib';
2
+ import { Options, ConsumeMessage } from 'amqplib';
3
3
  import { AmqpConnectionManager, ChannelWrapper } from 'amqp-connection-manager';
4
4
  import { EventEmitter } from 'events';
5
5
  import { RedisConfig } from './redis';
@@ -24,6 +24,15 @@ interface QueuesCache {
24
24
  declare type CustomMessageHeaders = {
25
25
  redisTimestampValidationKey?: string;
26
26
  };
27
+ declare type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
28
+ interface ConsumeOptions {
29
+ retries?: number;
30
+ deadMessageTtl?: number;
31
+ limit?: number;
32
+ lockTimeout?: number;
33
+ useConsumeWithLock?: boolean;
34
+ }
35
+ declare type CallbackFunction = (msg: ConsumeMessage, ack: Function, nack: Function) => Promise<any>;
27
36
  export interface AfRabbitOptions {
28
37
  reconnect: boolean;
29
38
  }
@@ -46,9 +55,10 @@ declare class RabbitMq implements IAfRabbitMq {
46
55
  queues: QueuesCache;
47
56
  options: AfRabbitOptions | undefined;
48
57
  redisClient: any;
58
+ redisLock?: RedisLockType;
49
59
  constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
50
60
  private shouldConsumeMessageByTimestamp;
51
- ack: (channel: ChannelWrapper, shouldUpdateRedisTimestamp?: boolean) => (msg: any) => Promise<void>;
61
+ ack: (channel: ChannelWrapper, shouldUpdateRedisTimestamp?: boolean) => (msg: ConsumeMessage) => Promise<any>;
52
62
  nack: (channel: ChannelWrapper, queue: string, options: any, deadQueueOptions: Options.AssertQueue) => (msg: any, { skipRetry, }?: any) => Promise<void>;
53
63
  getConnection(): Promise<AmqpConnectionManager>;
54
64
  getNewChannel(): Promise<ChannelWrapper>;
@@ -57,9 +67,11 @@ declare class RabbitMq implements IAfRabbitMq {
57
67
  getQueueLength(queue: string): Promise<import("amqplib").Replies.AssertQueue>;
58
68
  bindQueue(queue: string, exchange: string): Promise<void>;
59
69
  assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
60
- consume(queue: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any): Promise<void>;
61
- consumeFromExchange(queue: string, exchange: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any): Promise<void>;
62
- publish(exchange: string, content: any, customHeaders?: any): Promise<void>;
70
+ consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
71
+ private consumeWithLock;
72
+ private consumeFromRabbit;
73
+ consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
74
+ publish(exchange: string, content: any, customHeaders?: any): Promise<any>;
63
75
  sendToQueue(queue: string, content: any, options?: any, customHeaders?: any): Promise<boolean>;
64
76
  isConnected(): Promise<boolean>;
65
77
  }
package/dist/index.js CHANGED
@@ -15,9 +15,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/ban-types,@typescript-eslint/no-unused-vars,consistent-return,no-async-promise-executor,no-param-reassign,no-empty */
16
16
  // eslint-disable-next-line max-classes-per-file
17
17
  const moment_1 = __importDefault(require("moment"));
18
+ const redis_lock_1 = __importDefault(require("redis-lock"));
18
19
  const amqp_connection_manager_1 = require("amqp-connection-manager");
19
20
  const events_1 = require("events");
20
21
  // import { newTrace, traceTypes } from '@autofleet/outbreak';
22
+ const util_1 = require("util");
21
23
  const logger_1 = __importDefault(require("./logger"));
22
24
  const rabbitError_1 = __importDefault(require("./rabbitError"));
23
25
  const redis_1 = __importDefault(require("./redis"));
@@ -27,9 +29,9 @@ const defaultOptions = {
27
29
  retries: 1,
28
30
  deadMessageTtl: DEFAULT_DEAD_TTL_TWO_DAYS,
29
31
  };
30
- const SECONDS_TO_MILLISECONDS = 1000;
31
32
  const assertExchangeFanout = (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
32
33
  const connectionCreatedConst = 'connectionCreated';
34
+ const DEFAULT_LOCK_TIMEOUT = 1000;
33
35
  class RabbitMq {
34
36
  constructor(options, redisConfig) {
35
37
  this.PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
@@ -37,15 +39,18 @@ class RabbitMq {
37
39
  this.DISCONNECT_MSG = 'rabbit: connection disconnect';
38
40
  this.RESCONNECT_MSG = 'rabbit: reconnecting';
39
41
  this.shouldConsumeMessageByTimestamp = (msg) => __awaiter(this, void 0, void 0, function* () {
40
- const { properties: { timestamp, headers } } = msg;
41
- if (timestamp && (headers === null || headers === void 0 ? void 0 : headers.redisTimestampValidationKey) && this.redisClient) {
42
- const lastMessageTimestamp = yield this.redisClient.getAsync(headers.redisTimestampValidationKey);
43
- logger_1.default.info('rabbit: checking if should consume params', {
44
- msg, timestamp, headers, lastMessageTimestamp,
45
- });
46
- return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
42
+ if (msg) {
43
+ const { properties: { timestamp, headers } } = msg;
44
+ if (timestamp && (headers === null || headers === void 0 ? void 0 : headers.redisTimestampValidationKey) && this.redisClient) {
45
+ const lastMessageTimestamp = yield this.redisClient.getAsync(headers.redisTimestampValidationKey);
46
+ logger_1.default.info('rabbit: checking if should consume params', {
47
+ msg, timestamp, headers, lastMessageTimestamp,
48
+ });
49
+ return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
50
+ }
51
+ return true;
47
52
  }
48
- return true;
53
+ return false;
49
54
  });
50
55
  this.ack = (channel, shouldUpdateRedisTimestamp = false) => (msg) => __awaiter(this, void 0, void 0, function* () {
51
56
  yield channel.ack(msg);
@@ -79,6 +84,9 @@ class RabbitMq {
79
84
  this.queues = {};
80
85
  this.options = options;
81
86
  this.redisClient = redisConfig && redis_1.default(redisConfig);
87
+ if (this.redisClient) {
88
+ this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
89
+ }
82
90
  }
83
91
  static parseMsg(msg) {
84
92
  let { content } = msg;
@@ -121,11 +129,11 @@ class RabbitMq {
121
129
  this.exchanges = {};
122
130
  this.queues = {};
123
131
  if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.reconnect) {
124
- console.error(`${this.RESCONNECT_MSG}${err && ` - ${err}`}`);
132
+ logger_1.default.error(`${this.RESCONNECT_MSG}${err && ` - ${err}`}`);
125
133
  this.connection = null;
126
134
  }
127
135
  else {
128
- console.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
136
+ logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
129
137
  }
130
138
  });
131
139
  this.creatingConnection = false;
@@ -198,6 +206,45 @@ class RabbitMq {
198
206
  });
199
207
  }
200
208
  consume(queue, callback, options) {
209
+ return __awaiter(this, void 0, void 0, function* () {
210
+ if (options && options.useConsumeWithLock) {
211
+ return this.consumeWithLock(queue, callback, options);
212
+ }
213
+ return this.consumeFromRabbit(queue, callback, options);
214
+ });
215
+ }
216
+ consumeWithLock(queue, callback, options) {
217
+ return __awaiter(this, void 0, void 0, function* () {
218
+ if (!this.redisLock) {
219
+ throw new Error('Usage of consumeWithLock requires RedisInstance');
220
+ }
221
+ const lockTimeout = (options === null || options === void 0 ? void 0 : options.lockTimeout) || DEFAULT_LOCK_TIMEOUT;
222
+ logger_1.default.info(`Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
223
+ return this.consumeFromRabbit(queue, (msg, ack, nack) => __awaiter(this, void 0, void 0, function* () {
224
+ if (!msg) {
225
+ return null;
226
+ }
227
+ const { properties: { timestamp, headers } } = msg;
228
+ let releaseLock = null;
229
+ try {
230
+ if (timestamp && (headers === null || headers === void 0 ? void 0 : headers.redisTimestampValidationKey)) {
231
+ if (this.redisLock) {
232
+ releaseLock = yield this.redisLock(headers.redisTimestampValidationKey, lockTimeout);
233
+ yield callback(msg, ack, nack);
234
+ yield releaseLock();
235
+ }
236
+ }
237
+ }
238
+ catch (err) {
239
+ if (releaseLock) {
240
+ yield releaseLock();
241
+ }
242
+ throw err;
243
+ }
244
+ }), options);
245
+ });
246
+ }
247
+ consumeFromRabbit(queue, callback, options) {
201
248
  return __awaiter(this, void 0, void 0, function* () {
202
249
  const optionsWithDefaults = Object.assign(Object.assign({}, defaultOptions), options);
203
250
  RabbitMq.validateName('queue', queue);
@@ -208,8 +255,10 @@ class RabbitMq {
208
255
  yield c.prefetch(limit, true);
209
256
  return Promise.all([
210
257
  c.consume(queue, (msg) => __awaiter(this, void 0, void 0, function* () {
258
+ if (!msg) {
259
+ return null;
260
+ }
211
261
  const parsedMessage = RabbitMq.parseMsg(msg);
212
- // newTrace(traceTypes.RABBIT);
213
262
  const shouldConsume = yield this.shouldConsumeMessageByTimestamp(parsedMessage);
214
263
  logger_1.default.info('rabbit: trying to consume', { queue, shouldConsume, msg });
215
264
  if (!shouldConsume) {
@@ -240,16 +289,7 @@ class RabbitMq {
240
289
  yield c.prefetch(limit, true);
241
290
  return Promise.all([
242
291
  c.bindQueue(queue, exchange, ''),
243
- c.consume(queue, (msg) => __awaiter(this, void 0, void 0, function* () {
244
- const parsedMessage = RabbitMq.parseMsg(msg);
245
- // newTrace(traceTypes.RABBIT);
246
- const shouldConsume = yield this.shouldConsumeMessageByTimestamp(parsedMessage);
247
- logger_1.default.info('rabbit: trying to consume', { queue, shouldConsume, msg });
248
- if (!shouldConsume) {
249
- return this.ack(channel)(msg);
250
- }
251
- callback(parsedMessage, this.ack(channel, true), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }));
252
- })),
292
+ this.consume(queue, callback, options),
253
293
  ]);
254
294
  }));
255
295
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "2.1.16",
3
+ "version": "2.2.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -24,6 +24,7 @@
24
24
  "bluebird": "^3.7.2",
25
25
  "moment": "^2.29.1",
26
26
  "redis": "^3.1.2",
27
+ "redis-lock": "^0.1.4",
27
28
  "uuid": "^8.3.2"
28
29
  },
29
30
  "devDependencies": {
package/src/index.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/ban-types,@typescript-eslint/no-unused-vars,consistent-return,no-async-promise-executor,no-param-reassign,no-empty */
2
2
  // eslint-disable-next-line max-classes-per-file
3
3
  import moment from 'moment';
4
- import { ConfirmChannel, Options } from 'amqplib';
4
+ import RedisLock from 'redis-lock';
5
+ import { ConfirmChannel, Options, ConsumeMessage } from 'amqplib';
5
6
  import { AmqpConnectionManager, ChannelWrapper, connect } from 'amqp-connection-manager';
6
7
  import { EventEmitter } from 'events';
7
8
  // import { newTrace, traceTypes } from '@autofleet/outbreak';
9
+ import { promisify } from 'util';
8
10
  import logger from './logger';
9
11
  import RabbitError from './rabbitError';
10
12
  import getRedisInstance, { RedisConfig } from './redis';
@@ -33,6 +35,19 @@ type CustomMessageHeaders = {
33
35
  redisTimestampValidationKey?: string;
34
36
  }
35
37
 
38
+ type RedisLockType = (args0?: string, arg1?: number) => Promise<any>
39
+
40
+ type ConsumeMessageOrNull = ConsumeMessage | null;
41
+ interface ConsumeOptions {
42
+ retries?: number;
43
+ deadMessageTtl?: number;
44
+ limit?: number;
45
+ lockTimeout?: number;
46
+ useConsumeWithLock?: boolean;
47
+ }
48
+
49
+ type CallbackFunction = (msg: ConsumeMessage, ack: Function, nack: Function) => Promise<any>
50
+
36
51
  export interface AfRabbitOptions {
37
52
  reconnect: boolean;
38
53
  }
@@ -44,13 +59,15 @@ const defaultOptions = {
44
59
  retries: 1,
45
60
  deadMessageTtl: DEFAULT_DEAD_TTL_TWO_DAYS,
46
61
  };
47
- const SECONDS_TO_MILLISECONDS = 1000;
48
62
 
49
63
  const assertExchangeFanout = (c: any, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
50
64
 
51
65
  const connectionCreatedConst = 'connectionCreated';
66
+
67
+ const DEFAULT_LOCK_TIMEOUT = 1000;
68
+
52
69
  class RabbitMq implements IAfRabbitMq {
53
- static parseMsg(msg: any) {
70
+ static parseMsg(msg: any) : any {
54
71
  let { content } = msg;
55
72
  content = content.toString();
56
73
 
@@ -101,6 +118,8 @@ class RabbitMq implements IAfRabbitMq {
101
118
 
102
119
  redisClient: any;
103
120
 
121
+ redisLock?: RedisLockType;
122
+
104
123
  constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
105
124
  this.em = new EventEmitter();
106
125
  this.channel = null;
@@ -110,21 +129,27 @@ class RabbitMq implements IAfRabbitMq {
110
129
  this.queues = {};
111
130
  this.options = options;
112
131
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
132
+ if (this.redisClient) {
133
+ this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
134
+ }
113
135
  }
114
136
 
115
- private shouldConsumeMessageByTimestamp = async (msg: any) => {
116
- const { properties: { timestamp, headers } } = msg;
117
- if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
118
- const lastMessageTimestamp = await this.redisClient.getAsync(headers.redisTimestampValidationKey);
119
- logger.info('rabbit: checking if should consume params', {
120
- msg, timestamp, headers, lastMessageTimestamp,
121
- });
122
- return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
137
+ private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
138
+ if (msg) {
139
+ const { properties: { timestamp, headers } } = msg;
140
+ if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
141
+ const lastMessageTimestamp = await this.redisClient.getAsync(headers.redisTimestampValidationKey);
142
+ logger.info('rabbit: checking if should consume params', {
143
+ msg, timestamp, headers, lastMessageTimestamp,
144
+ });
145
+ return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
146
+ }
147
+ return true;
123
148
  }
124
- return true;
149
+ return false;
125
150
  }
126
151
 
127
- public ack = (channel: ChannelWrapper, shouldUpdateRedisTimestamp = false) => async (msg: any) => {
152
+ public ack = (channel: ChannelWrapper, shouldUpdateRedisTimestamp = false) => async (msg: ConsumeMessage) : Promise<any> => {
128
153
  await channel.ack(msg);
129
154
  const { properties: { timestamp, headers } } = msg;
130
155
  if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
@@ -181,10 +206,10 @@ class RabbitMq implements IAfRabbitMq {
181
206
  this.exchanges = {};
182
207
  this.queues = {};
183
208
  if (this.options?.reconnect) {
184
- console.error(`${this.RESCONNECT_MSG}${err && ` - ${err}`}`);
209
+ logger.error(`${this.RESCONNECT_MSG}${err && ` - ${err}`}`);
185
210
  this.connection = null;
186
211
  } else {
187
- console.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
212
+ logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
188
213
  }
189
214
  });
190
215
  this.creatingConnection = false;
@@ -250,7 +275,46 @@ class RabbitMq implements IAfRabbitMq {
250
275
  return queue;
251
276
  }
252
277
 
253
- async consume(queue: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any) {
278
+ async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
279
+ if (options && options.useConsumeWithLock) {
280
+ return this.consumeWithLock(queue, callback, options);
281
+ }
282
+ return this.consumeFromRabbit(queue, callback, options);
283
+ }
284
+
285
+ private async consumeWithLock(queue: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
286
+ if (!this.redisLock) {
287
+ throw new Error('Usage of consumeWithLock requires RedisInstance');
288
+ }
289
+ const lockTimeout = options?.lockTimeout || DEFAULT_LOCK_TIMEOUT;
290
+ logger.info(`Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
291
+ return this.consumeFromRabbit(queue, async (msg, ack, nack) => {
292
+ if (!msg) {
293
+ return null;
294
+ }
295
+ const { properties: { timestamp, headers } } = msg;
296
+ let releaseLock = null;
297
+ try {
298
+ if (timestamp && headers?.redisTimestampValidationKey) {
299
+ if (this.redisLock) {
300
+ releaseLock = await this.redisLock(
301
+ headers.redisTimestampValidationKey,
302
+ lockTimeout,
303
+ );
304
+ await callback(msg, ack, nack);
305
+ await releaseLock();
306
+ }
307
+ }
308
+ } catch (err) {
309
+ if (releaseLock) {
310
+ await releaseLock();
311
+ }
312
+ throw err;
313
+ }
314
+ }, options);
315
+ }
316
+
317
+ private async consumeFromRabbit(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
254
318
  const optionsWithDefaults = { ...defaultOptions, ...options };
255
319
  RabbitMq.validateName('queue', queue);
256
320
  const { limit, deadMessageTtl } = optionsWithDefaults;
@@ -261,9 +325,11 @@ class RabbitMq implements IAfRabbitMq {
261
325
  return Promise.all([
262
326
  c.consume(
263
327
  queue,
264
- async (msg: any) => {
328
+ async (msg: ConsumeMessageOrNull) => {
329
+ if (!msg) {
330
+ return null;
331
+ }
265
332
  const parsedMessage = RabbitMq.parseMsg(msg);
266
- // newTrace(traceTypes.RABBIT);
267
333
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
268
334
  logger.info('rabbit: trying to consume', { queue, shouldConsume, msg });
269
335
  if (!shouldConsume) {
@@ -284,7 +350,7 @@ class RabbitMq implements IAfRabbitMq {
284
350
  });
285
351
  }
286
352
 
287
- async consumeFromExchange(queue: string, exchange: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any) {
353
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
288
354
  const optionsWithDefaults = { ...defaultOptions, ...options };
289
355
  RabbitMq.validateName('exchange', exchange);
290
356
  RabbitMq.validateName('queue', queue);
@@ -298,24 +364,16 @@ class RabbitMq implements IAfRabbitMq {
298
364
  await c.prefetch(limit, true);
299
365
  return Promise.all([
300
366
  c.bindQueue(queue, exchange, ''),
301
- c.consume(
367
+ this.consume(
302
368
  queue,
303
- async (msg: any) => {
304
- const parsedMessage = RabbitMq.parseMsg(msg);
305
- // newTrace(traceTypes.RABBIT);
306
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
307
- logger.info('rabbit: trying to consume', { queue, shouldConsume, msg });
308
- if (!shouldConsume) {
309
- return this.ack(channel)(msg);
310
- }
311
- callback(parsedMessage, this.ack(channel, true), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }));
312
- },
369
+ callback,
370
+ options,
313
371
  ),
314
372
  ]);
315
373
  });
316
374
  }
317
375
 
318
- async publish(exchange: string, content: any, customHeaders?: any) {
376
+ async publish(exchange: string, content: any, customHeaders?: any) : Promise<any> {
319
377
  RabbitMq.validateName('exchange', exchange);
320
378
  const channel: ChannelWrapper = await this.assertChannel();
321
379
  await this.assertExchange(exchange);
@@ -324,7 +382,7 @@ class RabbitMq implements IAfRabbitMq {
324
382
  RabbitMq.getPublishOptions(customHeaders));
325
383
  }
326
384
 
327
- async sendToQueue(queue: string, content: any, options?: any, customHeaders?: any) {
385
+ async sendToQueue(queue: string, content: any, options?: any, customHeaders?: any) : Promise<boolean> {
328
386
  RabbitMq.validateName('queue', queue);
329
387
  const channel: ChannelWrapper = await this.assertChannel();
330
388
  await this.assertQueue(queue, options);
@@ -0,0 +1,4 @@
1
+ declare module 'redis-lock' {
2
+ const lock: any;
3
+ export = lock;
4
+ }