@autofleet/rabbit 2.1.8-beta3 → 2.1.8-beta4

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
@@ -2,15 +2,35 @@
2
2
  import { Options } from 'amqplib';
3
3
  import { AmqpConnectionManager, ChannelWrapper } from 'amqp-connection-manager';
4
4
  import { EventEmitter } from 'events';
5
+ import { RedisConfig } from './redis';
6
+ export interface IAfRabbitMq {
7
+ ack: any;
8
+ nack: any;
9
+ assertChannel: any;
10
+ assertExchange: any;
11
+ assertQueue: any;
12
+ consume: any;
13
+ consumeFromExchange: any;
14
+ publish: any;
15
+ sendToQueue: any;
16
+ redisClient?: any;
17
+ }
5
18
  interface ExchangesCache {
6
19
  [key: string]: any;
7
20
  }
21
+ declare type CustomMessageHeaders = {
22
+ redisTimestampValidationKey?: string;
23
+ };
8
24
  export interface AfRabbitOptions {
9
25
  reconnect: boolean;
10
26
  }
11
- declare class RabbitMq {
27
+ declare class RabbitMq implements IAfRabbitMq {
12
28
  static parseMsg(msg: any): any;
13
29
  static validateName(type: string, name: string): void;
30
+ static getPublishOptions(customHeaders?: CustomMessageHeaders): {
31
+ timestamp: number;
32
+ headers: CustomMessageHeaders;
33
+ };
14
34
  PUBLISH_TIMEOUT: number;
15
35
  PUBLISH_ERROR_MSG: string;
16
36
  DISCONNECT_MSG: string;
@@ -21,9 +41,11 @@ declare class RabbitMq {
21
41
  creatingConnection: boolean;
22
42
  exchanges: ExchangesCache;
23
43
  options: AfRabbitOptions | undefined;
24
- constructor(options?: AfRabbitOptions);
25
- ack: (msg: any) => Promise<void>;
26
- nack: (queue: string, options: any, deadQueueOptions: Options.AssertQueue) => (msg: any, { skipRetry, }?: any) => Promise<void>;
44
+ redisClient: any;
45
+ constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
46
+ private shouldConsumeMessageByTimestamp;
47
+ ack: (channel: ChannelWrapper, shouldUpdateRedisTimestamp?: boolean) => (msg: any) => Promise<void>;
48
+ nack: (channel: ChannelWrapper, queue: string, options: any, deadQueueOptions: Options.AssertQueue) => (msg: any, { skipRetry, }?: any) => Promise<void>;
27
49
  getConnection(): Promise<AmqpConnectionManager>;
28
50
  getNewChannel(): Promise<ChannelWrapper>;
29
51
  assertChannel(): Promise<ChannelWrapper>;
@@ -33,7 +55,8 @@ declare class RabbitMq {
33
55
  assertQueue(queue: string, options?: Options.AssertQueue): Promise<void>;
34
56
  consume(queue: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any): Promise<void>;
35
57
  consumeFromExchange(queue: string, exchange: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any): Promise<void>;
36
- publish(exchange: string, content: any): Promise<unknown>;
37
- sendToQueue(queue: string, content: any, options?: any): Promise<void>;
58
+ publish(exchange: string, content: any, customHeaders?: any): Promise<unknown>;
59
+ sendToQueue(queue: string, content: any, options?: any, customHeaders?: any): Promise<void>;
60
+ isConnected(): Promise<boolean>;
38
61
  }
39
62
  export default RabbitMq;
package/dist/index.js CHANGED
@@ -34,31 +34,49 @@ Object.defineProperty(exports, "__esModule", { value: true });
34
34
  /* 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 */
35
35
  // eslint-disable-next-line max-classes-per-file
36
36
  const bluebird = __importStar(require("bluebird"));
37
+ const moment_1 = __importDefault(require("moment"));
37
38
  const amqp_connection_manager_1 = require("amqp-connection-manager");
38
39
  const events_1 = require("events");
39
- const outbreak_1 = require("@autofleet/outbreak");
40
+ // import { newTrace, traceTypes } from '@autofleet/outbreak';
41
+ const logger_1 = __importDefault(require("./logger"));
40
42
  const rabbitError_1 = __importDefault(require("./rabbitError"));
43
+ const redis_1 = __importDefault(require("./redis"));
41
44
  const DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 48;
42
45
  const defaultOptions = {
43
46
  limit: 1,
44
47
  retries: 1,
45
48
  deadMessageTtl: DEFAULT_DEAD_TTL_TWO_DAYS,
46
49
  };
50
+ const SECONDS_TO_MILLISECONDS = 1000;
47
51
  const assertExchangeFanout = (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
48
52
  const connectionCreatedConst = 'connectionCreated';
49
53
  class RabbitMq {
50
- constructor(options) {
54
+ constructor(options, redisConfig) {
51
55
  this.PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
52
56
  this.PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
53
57
  this.DISCONNECT_MSG = 'rabbit: connection disconnect';
54
58
  this.RESCONNECT_MSG = 'rabbit: reconnecting';
55
- this.ack = (msg) => __awaiter(this, void 0, void 0, function* () {
56
- if (this.channel) {
57
- yield this.channel.ack(msg);
59
+ this.shouldConsumeMessageByTimestamp = (msg) => __awaiter(this, void 0, void 0, function* () {
60
+ const { properties: { timestamp, headers } } = msg;
61
+ if (timestamp && (headers === null || headers === void 0 ? void 0 : headers.redisTimestampValidationKey) && this.redisClient) {
62
+ const lastMessageTimestamp = yield this.redisClient.getAsync(headers.redisTimestampValidationKey);
63
+ logger_1.default.info('rabbit: checking if should consume params', {
64
+ msg, timestamp, headers, lastMessageTimestamp,
65
+ });
66
+ return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
67
+ }
68
+ return true;
69
+ });
70
+ this.ack = (channel, shouldUpdateRedisTimestamp = false) => (msg) => __awaiter(this, void 0, void 0, function* () {
71
+ yield channel.ack(msg);
72
+ const { properties: { timestamp, headers } } = msg;
73
+ if (shouldUpdateRedisTimestamp && timestamp && (headers === null || headers === void 0 ? void 0 : headers.redisTimestampValidationKey) && this.redisClient) {
74
+ const parsedTimestamp = parseInt(timestamp, 10);
75
+ yield this.redisClient.setAsync(headers.redisTimestampValidationKey, parsedTimestamp, 'EX', 3600);
58
76
  }
59
77
  });
60
- this.nack = (queue, options, deadQueueOptions) => (msg, { skipRetry = false, } = {}) => __awaiter(this, void 0, void 0, function* () {
61
- if (this.channel) {
78
+ this.nack = (channel, queue, options, deadQueueOptions) => (msg, { skipRetry = false, } = {}) => __awaiter(this, void 0, void 0, function* () {
79
+ if (channel) {
62
80
  if (!skipRetry
63
81
  && (!msg.content['x-retry-count']
64
82
  || msg.content['x-retry-count'] < options.retries)) {
@@ -70,7 +88,7 @@ class RabbitMq {
70
88
  yield this.assertQueue(deadQueue, deadQueueOptions);
71
89
  yield this.sendToQueue(deadQueue, msg.content, deadQueueOptions);
72
90
  }
73
- yield this.channel.ack(msg);
91
+ yield channel.ack(msg);
74
92
  }
75
93
  });
76
94
  this.em = new events_1.EventEmitter();
@@ -79,6 +97,7 @@ class RabbitMq {
79
97
  this.creatingConnection = false;
80
98
  this.exchanges = {};
81
99
  this.options = options;
100
+ this.redisClient = redisConfig && redis_1.default(redisConfig);
82
101
  }
83
102
  static parseMsg(msg) {
84
103
  let { content } = msg;
@@ -94,19 +113,27 @@ class RabbitMq {
94
113
  throw new rabbitError_1.default(`error while using ${type} with no name`);
95
114
  }
96
115
  }
116
+ static getPublishOptions(customHeaders = {}) {
117
+ return {
118
+ timestamp: moment_1.default().unix(),
119
+ headers: customHeaders,
120
+ };
121
+ }
97
122
  getConnection() {
98
123
  return __awaiter(this, void 0, void 0, function* () {
99
124
  return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
100
125
  if (this.creatingConnection) {
101
- this.em.on(connectionCreatedConst, resolve);
126
+ this.em.once(connectionCreatedConst, resolve);
102
127
  return;
103
128
  }
104
129
  if (this.connection !== null) {
105
- return this.connection;
130
+ return resolve(this.connection);
106
131
  }
107
132
  this.creatingConnection = true;
133
+ const username = process.env.RABBITMQ_USERNAME || 'guest';
134
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
108
135
  const host = process.env.RABBITMQ_SERVICE_HOST || '';
109
- const connection = yield amqp_connection_manager_1.connect([`amqp://${host}`]);
136
+ const connection = yield amqp_connection_manager_1.connect([`amqp://${username}:${password}@${host}`]);
110
137
  this.connection = connection;
111
138
  this.connection.on('disconnect', ({ err }) => {
112
139
  var _a;
@@ -136,8 +163,8 @@ class RabbitMq {
136
163
  if (this.channel) {
137
164
  return resolve(this.channel);
138
165
  }
139
- const connection = yield this.getConnection();
140
166
  try {
167
+ const connection = yield this.getConnection();
141
168
  if (this.channel === null) {
142
169
  this.channel = connection.createChannel({});
143
170
  }
@@ -194,10 +221,16 @@ class RabbitMq {
194
221
  yield c.assertQueue(queue);
195
222
  yield c.prefetch(limit, true);
196
223
  return Promise.all([
197
- c.consume(queue, (msg) => {
198
- outbreak_1.newTrace(outbreak_1.traceTypes.RABBIT);
199
- callback(RabbitMq.parseMsg(msg), this.ack, this.nack(queue, optionsWithDefaults, { messageTtl: deadMessageTtl }));
200
- }),
224
+ c.consume(queue, (msg) => __awaiter(this, void 0, void 0, function* () {
225
+ const parsedMessage = RabbitMq.parseMsg(msg);
226
+ // newTrace(traceTypes.RABBIT);
227
+ const shouldConsume = yield this.shouldConsumeMessageByTimestamp(parsedMessage);
228
+ logger_1.default.info('rabbit: trying to consume', { queue, shouldConsume, msg });
229
+ if (!shouldConsume) {
230
+ return this.ack(channel)(msg);
231
+ }
232
+ callback(parsedMessage, this.ack(channel, true), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }));
233
+ })),
201
234
  ]);
202
235
  }));
203
236
  });
@@ -216,31 +249,49 @@ class RabbitMq {
216
249
  yield c.prefetch(limit, true);
217
250
  return Promise.all([
218
251
  c.bindQueue(queue, exchange, ''),
219
- c.consume(queue, (msg) => {
220
- outbreak_1.newTrace(outbreak_1.traceTypes.RABBIT);
221
- callback(RabbitMq.parseMsg(msg), this.ack, this.nack(queue, optionsWithDefaults, { messageTtl: deadMessageTtl }));
222
- }),
252
+ c.consume(queue, (msg) => __awaiter(this, void 0, void 0, function* () {
253
+ const parsedMessage = RabbitMq.parseMsg(msg);
254
+ // newTrace(traceTypes.RABBIT);
255
+ const shouldConsume = yield this.shouldConsumeMessageByTimestamp(parsedMessage);
256
+ logger_1.default.info('rabbit: trying to consume', { queue, shouldConsume, msg });
257
+ if (!shouldConsume) {
258
+ return this.ack(channel)(msg);
259
+ }
260
+ callback(parsedMessage, this.ack(channel, true), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }));
261
+ })),
223
262
  ]);
224
263
  }));
225
264
  });
226
265
  }
227
- publish(exchange, content) {
266
+ publish(exchange, content, customHeaders) {
228
267
  return __awaiter(this, void 0, void 0, function* () {
229
268
  RabbitMq.validateName('exchange', exchange);
230
269
  const channel = yield this.assertChannel();
231
270
  yield this.assertExchange(exchange);
232
- return new bluebird.Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
233
- yield channel.publish(exchange, '', Buffer.from(JSON.stringify(content)));
234
- return resolve();
271
+ return new bluebird.Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
272
+ try {
273
+ yield channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
274
+ return resolve();
275
+ }
276
+ catch (e) {
277
+ reject(e);
278
+ }
235
279
  })).timeout(this.PUBLISH_TIMEOUT, `${this.PUBLISH_ERROR_MSG}${exchange}`);
236
280
  });
237
281
  }
238
- sendToQueue(queue, content, options) {
282
+ sendToQueue(queue, content, options, customHeaders) {
239
283
  return __awaiter(this, void 0, void 0, function* () {
240
284
  RabbitMq.validateName('queue', queue);
241
285
  const channel = yield this.assertChannel();
242
286
  yield this.assertQueue(queue, options);
243
- return channel.sendToQueue(queue, Buffer.from(JSON.stringify(content)));
287
+ logger_1.default.info('rabbit: sending to queue', { queue, content, customHeaders });
288
+ return channel.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
289
+ });
290
+ }
291
+ isConnected() {
292
+ return __awaiter(this, void 0, void 0, function* () {
293
+ const connection = yield this.getConnection();
294
+ return connection.isConnected();
244
295
  });
245
296
  }
246
297
  }
@@ -0,0 +1,2 @@
1
+ declare const logger: any;
2
+ export default logger;
package/dist/logger.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Logger = require('@autofleet/logger');
4
+ const logger = Logger();
5
+ exports.default = logger;
package/dist/mock.d.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import 'jest';
2
- declare class RabbitMq {
3
- ack: jest.Mock<unknown>;
4
- nack: jest.Mock<unknown>;
5
- assertChannel: jest.Mock<unknown>;
6
- assertExchange: jest.Mock<unknown>;
7
- assertQueue: jest.Mock<unknown>;
8
- consume: jest.Mock<unknown>;
9
- consumeFromExchange: jest.Mock<unknown>;
10
- publish: jest.Mock<unknown>;
11
- sendToQueue: jest.Mock<unknown>;
2
+ import { IAfRabbitMq } from './index';
3
+ declare class RabbitMq implements IAfRabbitMq {
4
+ ack: any;
5
+ nack: any;
6
+ assertChannel: any;
7
+ assertExchange: any;
8
+ assertQueue: any;
9
+ consume: any;
10
+ consumeFromExchange: any;
11
+ publish: any;
12
+ sendToQueue: any;
12
13
  }
13
14
  export default RabbitMq;
package/dist/mock.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ // eslint-disable-next-line import/no-extraneous-dependencies
3
4
  require("jest");
4
5
  class RabbitMq {
5
6
  constructor() {
@@ -0,0 +1,7 @@
1
+ export declare type RedisConfig = {
2
+ host: string;
3
+ port: number | undefined;
4
+ prefix?: string;
5
+ };
6
+ declare const getRedisInstance: (config: RedisConfig) => any;
7
+ export default getRedisInstance;
package/dist/redis.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const bluebird_1 = __importDefault(require("bluebird"));
7
+ const redis = require('redis');
8
+ bluebird_1.default.promisifyAll(redis.RedisClient.prototype);
9
+ bluebird_1.default.promisifyAll(redis.Multi.prototype);
10
+ const getRedisInstance = (config) => redis.createClient(config);
11
+ exports.default = getRedisInstance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "2.1.8-beta3",
3
+ "version": "2.1.8-beta4",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {