@autofleet/rabbit 1.5.1 → 1.5.3

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/example.js CHANGED
@@ -10,10 +10,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const index_1 = require("./index");
13
- // process.env.RABBITMQ_SERVICE_HOST = '34.76.123.137:5672';
13
+ process.env.RABBITMQ_SERVICE_HOST = '34.76.123.137:5672';
14
14
  const init = () => __awaiter(void 0, void 0, void 0, function* () {
15
15
  const rabbit = new index_1.default();
16
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
16
  yield rabbit.consumeFromExchange('test-q2', 'test-e', (msg, ack) => {
18
17
  console.log('consumeFromExchange: msg2', msg.content);
19
18
  ack(msg);
@@ -29,5 +28,7 @@ const init = () => __awaiter(void 0, void 0, void 0, function* () {
29
28
  ack(msg);
30
29
  });
31
30
  yield rabbit.sendToQueue('test-q-2', 'hey-q');
31
+ const getQueueLength = yield rabbit.getQueueLength('test-q2');
32
+ console.log(`getQueueLength:${getQueueLength}`);
32
33
  });
33
34
  init();
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  /// <reference types="node" />
2
2
  import { AmqpConnectionManager, ChannelWrapper } from 'amqp-connection-manager';
3
3
  import { EventEmitter } from 'events';
4
+ import RabbitWrapperInterface from './interfaces/RabbitWrapperInterface';
4
5
  interface ExchangesCache {
5
6
  [key: string]: any;
6
7
  }
7
- declare class RabbitMq {
8
+ declare class RabbitMq implements RabbitWrapperInterface {
8
9
  static parseMsg(msg: any): any;
9
10
  static validateName(type: string, name: string): void;
10
11
  PUBLISH_TIMEOUT: number;
@@ -15,12 +16,19 @@ declare class RabbitMq {
15
16
  em: EventEmitter;
16
17
  creatingConnection: boolean;
17
18
  exchanges: ExchangesCache;
18
- constructor();
19
+ isTestEnv: boolean;
20
+ constructor({ isTestEnv }?: {
21
+ isTestEnv?: boolean | undefined;
22
+ });
23
+ private getConnectFunc;
24
+ private connect;
19
25
  ack: (msg: any) => Promise<void>;
20
26
  nack: (queue: string) => (msg: any, retries: number) => Promise<void>;
21
27
  getConnection(): Promise<AmqpConnectionManager>;
22
28
  assertChannel(): Promise<ChannelWrapper>;
23
29
  assertExchange(exchangeName: string, options?: any): Promise<any>;
30
+ getQueueLength(queue: string): Promise<any>;
31
+ bindQueue(queue: string, exchange: string): Promise<any>;
24
32
  assertQueue(queue: string, options?: any): Promise<void>;
25
33
  consume(queue: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any): Promise<void>;
26
34
  consumeFromExchange(queue: string, exchange: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any): Promise<void>;
package/dist/index.js CHANGED
@@ -9,18 +9,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ /* 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 */
12
13
  // eslint-disable-next-line max-classes-per-file
13
14
  const bluebird = require("bluebird");
14
15
  const amqp_connection_manager_1 = require("amqp-connection-manager");
15
16
  const events_1 = require("events");
17
+ const mockImpl_1 = require("./mockImpl");
16
18
  const rabbitError_1 = require("./rabbitError");
17
19
  const assertExchangeFanout = (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
18
20
  const connectionCreatedConst = 'connectionCreated';
19
21
  class RabbitMq {
20
- constructor() {
22
+ constructor({ isTestEnv = false } = {}) {
21
23
  this.PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
22
24
  this.PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
23
25
  this.DISCONNECT_MSG = 'rabbit: connection disconnect';
26
+ this.getConnectFunc = () => (this.isTestEnv ? mockImpl_1.connect : amqp_connection_manager_1.connect);
27
+ this.connect = (urls = []) => this.getConnectFunc()(urls);
24
28
  this.ack = (msg) => __awaiter(this, void 0, void 0, function* () {
25
29
  if (this.channel) {
26
30
  yield this.channel.ack(msg);
@@ -29,7 +33,6 @@ class RabbitMq {
29
33
  this.nack = (queue) => (msg, retries) => __awaiter(this, void 0, void 0, function* () {
30
34
  if (this.channel) {
31
35
  if (!msg.content['x-retry-count'] || msg.content['x-retry-count'] <= retries) {
32
- // eslint-disable-next-line no-param-reassign
33
36
  msg.content['x-retry-count'] = msg.content['x-retry-count'] ? msg.content['x-retry-count'] + 1 : 1;
34
37
  yield this.sendToQueue(queue, msg.content);
35
38
  }
@@ -46,6 +49,7 @@ class RabbitMq {
46
49
  this.connection = null;
47
50
  this.creatingConnection = false;
48
51
  this.exchanges = {};
52
+ this.isTestEnv = isTestEnv;
49
53
  }
50
54
  static parseMsg(msg) {
51
55
  let { content } = msg;
@@ -53,7 +57,7 @@ class RabbitMq {
53
57
  try {
54
58
  content = JSON.parse(content);
55
59
  }
56
- catch (e) { } // eslint-disable-line no-empty
60
+ catch (e) { }
57
61
  return Object.assign(Object.assign({}, msg), { content });
58
62
  }
59
63
  static validateName(type, name) {
@@ -63,19 +67,17 @@ class RabbitMq {
63
67
  }
64
68
  getConnection() {
65
69
  return __awaiter(this, void 0, void 0, function* () {
66
- // eslint-disable-next-line no-async-promise-executor
67
70
  return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
68
71
  if (this.creatingConnection) {
69
72
  this.em.on(connectionCreatedConst, resolve);
70
73
  return;
71
74
  }
72
75
  if (this.connection !== null) {
73
- // eslint-disable-next-line consistent-return
74
76
  return this.connection;
75
77
  }
76
78
  this.creatingConnection = true;
77
79
  const host = process.env.RABBITMQ_SERVICE_HOST || '';
78
- const connection = yield amqp_connection_manager_1.connect([`amqp://${host}`]);
80
+ const connection = yield this.connect([`amqp://${host}`]);
79
81
  this.connection = connection;
80
82
  this.connection.on('disconnect', ({ err }) => console.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`));
81
83
  this.creatingConnection = false;
@@ -86,7 +88,6 @@ class RabbitMq {
86
88
  }
87
89
  assertChannel() {
88
90
  return __awaiter(this, void 0, void 0, function* () {
89
- // eslint-disable-next-line no-async-promise-executor,consistent-return
90
91
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
91
92
  if (this.channel) {
92
93
  return resolve(this.channel);
@@ -94,7 +95,7 @@ class RabbitMq {
94
95
  const connection = yield this.getConnection();
95
96
  try {
96
97
  if (this.channel === null) {
97
- this.channel = connection.createChannel({});
98
+ this.channel = yield connection.createChannel({});
98
99
  }
99
100
  resolve(this.channel);
100
101
  }
@@ -104,7 +105,6 @@ class RabbitMq {
104
105
  }));
105
106
  });
106
107
  }
107
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
108
108
  assertExchange(exchangeName, options) {
109
109
  return __awaiter(this, void 0, void 0, function* () {
110
110
  const channel = yield this.assertChannel();
@@ -118,7 +118,21 @@ class RabbitMq {
118
118
  }));
119
119
  });
120
120
  }
121
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
121
+ getQueueLength(queue) {
122
+ return __awaiter(this, void 0, void 0, function* () {
123
+ RabbitMq.validateName('queue', queue);
124
+ const channel = yield this.assertChannel();
125
+ // @ts-ignore
126
+ return channel.checkQueue(queue);
127
+ });
128
+ }
129
+ bindQueue(queue, exchange) {
130
+ return __awaiter(this, void 0, void 0, function* () {
131
+ const channel = yield this.assertChannel();
132
+ // @ts-ignore
133
+ return channel.bindQueue(queue, exchange, '');
134
+ });
135
+ }
122
136
  assertQueue(queue, options) {
123
137
  return __awaiter(this, void 0, void 0, function* () {
124
138
  RabbitMq.validateName('queue', queue);
@@ -126,7 +140,6 @@ class RabbitMq {
126
140
  return channel.addSetup((c) => c.assertQueue(queue));
127
141
  });
128
142
  }
129
- // eslint-disable-next-line @typescript-eslint/ban-types
130
143
  consume(queue, callback, options) {
131
144
  return __awaiter(this, void 0, void 0, function* () {
132
145
  RabbitMq.validateName('queue', queue);
@@ -141,7 +154,6 @@ class RabbitMq {
141
154
  }));
142
155
  });
143
156
  }
144
- // eslint-disable-next-line @typescript-eslint/ban-types
145
157
  consumeFromExchange(queue, exchange, callback, options) {
146
158
  return __awaiter(this, void 0, void 0, function* () {
147
159
  RabbitMq.validateName('queue', queue);
@@ -9,8 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/no-unused-vars */
12
13
  const index_1 = require("./index");
13
- // import RabbitError from './rabbitError';
14
14
  jest.setTimeout(120000);
15
15
  const rabbit = new index_1.default();
16
16
  const delay = () => new Promise((resolve) => setTimeout(() => resolve(), 500));
@@ -48,7 +48,6 @@ describe('RabbitMQ', () => {
48
48
  expect(err).toBeDefined();
49
49
  }));
50
50
  it('cant publish due timeout', () => __awaiter(void 0, void 0, void 0, function* () {
51
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
52
51
  // @ts-ignore
53
52
  process.env.RABBITMQ_PUBLISH_TIMEOUT = 1;
54
53
  const rabbit2 = new index_1.default();
@@ -61,4 +60,25 @@ describe('RabbitMQ', () => {
61
60
  }
62
61
  expect(err).toBeDefined();
63
62
  }));
63
+ it('get queue length', () => __awaiter(void 0, void 0, void 0, function* () {
64
+ const queueName = 'test-queue-length';
65
+ const exchangeName = 'test-e2';
66
+ yield rabbit.assertQueue(queueName);
67
+ yield rabbit.assertExchange(exchangeName);
68
+ yield rabbit.bindQueue(queueName, exchangeName);
69
+ yield rabbit.publish(exchangeName, 'hey');
70
+ let messageCount;
71
+ ({ messageCount } = yield rabbit.getQueueLength(queueName));
72
+ expect(messageCount).toEqual(1);
73
+ yield rabbit.publish(exchangeName, 'hey');
74
+ ({ messageCount } = yield rabbit.getQueueLength(queueName));
75
+ expect(messageCount).toEqual(2);
76
+ yield rabbit.consumeFromExchange(queueName, exchangeName, (msg, ack) => __awaiter(void 0, void 0, void 0, function* () {
77
+ expect(msg.content).toEqual('hey');
78
+ ack(msg);
79
+ }));
80
+ yield delay();
81
+ ({ messageCount } = yield rabbit.getQueueLength(queueName));
82
+ expect(messageCount).toEqual(0);
83
+ }));
64
84
  });
@@ -0,0 +1,15 @@
1
+ interface RabbitWrapperInterface {
2
+ ack: any;
3
+ nack: any;
4
+ getConnection: any;
5
+ assertChannel: any;
6
+ assertExchange: any;
7
+ getQueueLength: any;
8
+ bindQueue: any;
9
+ assertQueue: any;
10
+ consume: any;
11
+ consumeFromExchange: any;
12
+ publish: any;
13
+ sendToQueue: any;
14
+ }
15
+ export default RabbitWrapperInterface;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,25 @@
1
+ export interface channel {
2
+ assertQueue: any;
3
+ assertExchange: any;
4
+ bindQueue: any;
5
+ publish: any;
6
+ consume: any;
7
+ deleteQueue: any;
8
+ ack: any;
9
+ nack: any;
10
+ prefetch: any;
11
+ on: any;
12
+ once: any;
13
+ close: any;
14
+ addSetup: any;
15
+ checkQueue: any;
16
+ sendToQueue: any;
17
+ }
18
+ export interface connection {
19
+ createChannel: any;
20
+ createConfirmChannel: any;
21
+ on: any;
22
+ once: any;
23
+ close: any;
24
+ }
25
+ export declare const connect: () => any;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.connect = void 0;
13
+ const Bluebird = require("bluebird");
14
+ let exchanges = {};
15
+ let queues = {};
16
+ const setIfUndef = (object, prop, value) => {
17
+ if (!object[prop]) {
18
+ // eslint-disable-next-line no-param-reassign
19
+ object[prop] = value;
20
+ }
21
+ };
22
+ class ChannelImpl {
23
+ constructor() {
24
+ this.assertQueue = (queue, qOptions) => new Bluebird((resolve) => {
25
+ setIfUndef(queues, queue, { messages: [], subscribers: [], options: qOptions });
26
+ return resolve();
27
+ });
28
+ this.assertExchange = (exchange, type, exchOptions = {}) => new Bluebird((resolve) => {
29
+ setIfUndef(exchanges, exchange, { bindings: [], options: exchOptions, type });
30
+ return resolve();
31
+ });
32
+ this.bindQueue = (queue, exchange, key) => new Bluebird((resolve, reject) => {
33
+ if (!exchanges[exchange])
34
+ return reject(`Bind to non-existing exchange ${exchange}`);
35
+ const re = `^${key.replace('.', '\\.').replace('#', '(\\w|\\.)+').replace('*', '\\w+')}$`;
36
+ exchanges[exchange].bindings.push({ regex: new RegExp(re), queueName: queue });
37
+ return resolve();
38
+ });
39
+ this.publish = (exchange, routingKey, content, props) => new Bluebird((resolve, reject) => {
40
+ if (!exchanges[exchange])
41
+ return reject(`Publish to non-existing exchange ${exchange}`);
42
+ const { bindings } = exchanges[exchange];
43
+ const matchingBindings = bindings.filter((b) => b.regex.test(routingKey));
44
+ matchingBindings.forEach((binding) => {
45
+ const subscribers = queues[binding.queueName] ? queues[binding.queueName].subscribers : [];
46
+ subscribers.forEach((sub) => {
47
+ const message = { fields: { routingKey }, properties: props, content };
48
+ sub(message);
49
+ });
50
+ });
51
+ return resolve();
52
+ });
53
+ this.consume = (queue, handler) => {
54
+ queues[queue].subscribers.push(handler);
55
+ };
56
+ this.sendToQueue = (queue, content, routingKey = '', props = {}) => {
57
+ const subscribers = queues[queue] ? queues[queue].subscribers : [];
58
+ subscribers.forEach((sub) => {
59
+ const message = { fields: { routingKey }, properties: props, content };
60
+ sub(message);
61
+ });
62
+ };
63
+ this.deleteQueue = (queue) => {
64
+ setImmediate(() => {
65
+ delete queues[queue];
66
+ });
67
+ };
68
+ this.checkQueue = () => ({ messageCount: 0 });
69
+ this.ack = () => null;
70
+ this.nack = () => null;
71
+ this.prefetch = () => null;
72
+ this.on = () => null;
73
+ this.once = () => null;
74
+ this.close = () => Bluebird.resolve();
75
+ }
76
+ addSetup(funcToRun) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ const val = yield funcToRun(this);
79
+ return val;
80
+ });
81
+ }
82
+ }
83
+ const createChannel = () => new Bluebird((resolve) => resolve(new ChannelImpl()));
84
+ exports.connect = () => new Bluebird((resolve) => {
85
+ const connectionImpl = {
86
+ createChannel,
87
+ createConfirmChannel: createChannel,
88
+ on: () => null,
89
+ once: () => null,
90
+ close: () => Bluebird.resolve(),
91
+ };
92
+ return resolve(connectionImpl);
93
+ });
94
+ const resetMock = () => {
95
+ queues = {};
96
+ exchanges = {};
97
+ };
98
+ module.exports = { connect: exports.connect, resetMock };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/no-unused-vars */
13
+ const index_1 = require("../index");
14
+ jest.setTimeout(120000);
15
+ const rabbit = new index_1.default({ isTestEnv: true });
16
+ describe('RabbitMQ', () => {
17
+ it('check exchange', () => __awaiter(void 0, void 0, void 0, function* () {
18
+ const callback = (msg, ack) => {
19
+ expect(msg.content).toEqual('hey');
20
+ ack(msg);
21
+ };
22
+ const mockFn = jest.fn(callback);
23
+ yield rabbit.consumeFromExchange('test-q2', 'test-e', mockFn);
24
+ yield rabbit.publish('test-e', 'hey');
25
+ expect(mockFn).toBeCalled();
26
+ }));
27
+ it('check queue', () => __awaiter(void 0, void 0, void 0, function* () {
28
+ const callback = (msg, ack) => {
29
+ expect(msg.content).toEqual('hey-q');
30
+ ack(msg);
31
+ };
32
+ const mockFn = jest.fn(callback);
33
+ yield rabbit.consume('test-q-2', mockFn);
34
+ yield rabbit.sendToQueue('test-q-2', 'hey-q');
35
+ expect(mockFn).toBeCalled();
36
+ }));
37
+ it('cant publish to unknown exchange', () => __awaiter(void 0, void 0, void 0, function* () {
38
+ let err;
39
+ try {
40
+ yield rabbit.publish('', 'hey');
41
+ }
42
+ catch (e) {
43
+ err = e;
44
+ }
45
+ expect(err).toBeDefined();
46
+ }));
47
+ it('get queue length', () => __awaiter(void 0, void 0, void 0, function* () {
48
+ const queueName = 'test-queue-length';
49
+ const exchangeName = 'test-e2';
50
+ yield rabbit.assertQueue(queueName);
51
+ yield rabbit.assertExchange(exchangeName);
52
+ yield rabbit.bindQueue(queueName, exchangeName);
53
+ yield rabbit.publish(exchangeName, 'hey');
54
+ let messageCount;
55
+ ({ messageCount } = yield rabbit.getQueueLength(queueName));
56
+ expect(messageCount).toEqual(0);
57
+ yield rabbit.publish(exchangeName, 'hey');
58
+ ({ messageCount } = yield rabbit.getQueueLength(queueName));
59
+ expect(messageCount).toEqual(0);
60
+ yield rabbit.consumeFromExchange(queueName, exchangeName, (msg, ack) => __awaiter(void 0, void 0, void 0, function* () {
61
+ expect(msg.content).toEqual('hey');
62
+ ack(msg);
63
+ }));
64
+ ({ messageCount } = yield rabbit.getQueueLength(queueName));
65
+ expect(messageCount).toEqual(0);
66
+ }));
67
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -14,9 +14,9 @@
14
14
  "dev": "nodemon"
15
15
  },
16
16
  "dependencies": {
17
+ "@types/amqp-connection-manager": "^2.0.10",
18
+ "@types/amqplib": "^0.5.16",
17
19
  "@types/jest": "^22.0.0",
18
- "@types/amqp-connection-manager": "^2.0.2",
19
- "@types/amqplib": "^0.5.9",
20
20
  "amqp-connection-manager": "^3.2.1",
21
21
  "amqplib": "^0.6.0",
22
22
  "bluebird": "^3.7.2",
package/src/example.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import RabbitMq from './index';
2
2
 
3
- // process.env.RABBITMQ_SERVICE_HOST = '34.76.123.137:5672';
3
+ process.env.RABBITMQ_SERVICE_HOST = '34.76.123.137:5672';
4
4
 
5
5
  const init = async () => {
6
6
  const rabbit = new RabbitMq();
7
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+
8
8
  await rabbit.consumeFromExchange('test-q2', 'test-e', (msg, ack) => {
9
9
  console.log('consumeFromExchange: msg2', msg.content);
10
10
  ack(msg);
@@ -21,7 +21,11 @@ const init = async () => {
21
21
  console.log('consume: msg-q', msg.content);
22
22
  ack(msg);
23
23
  });
24
+
24
25
  await rabbit.sendToQueue('test-q-2', 'hey-q');
26
+
27
+ const getQueueLength = await rabbit.getQueueLength('test-q2');
28
+ console.log(`getQueueLength:${getQueueLength}`);
25
29
  };
26
30
 
27
31
  init();
package/src/index.test.ts CHANGED
@@ -1,5 +1,5 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/no-unused-vars */
1
2
  import RabbitMq from './index';
2
- // import RabbitError from './rabbitError';
3
3
 
4
4
  jest.setTimeout(120000);
5
5
 
@@ -43,7 +43,6 @@ describe('RabbitMQ', () => {
43
43
  });
44
44
 
45
45
  it('cant publish due timeout', async () => {
46
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
47
46
  // @ts-ignore
48
47
  process.env.RABBITMQ_PUBLISH_TIMEOUT = 1;
49
48
  const rabbit2 = new RabbitMq();
@@ -56,4 +55,28 @@ describe('RabbitMQ', () => {
56
55
  }
57
56
  expect(err).toBeDefined();
58
57
  });
58
+
59
+ it('get queue length', async () => {
60
+ const queueName = 'test-queue-length';
61
+ const exchangeName = 'test-e2';
62
+
63
+ await rabbit.assertQueue(queueName);
64
+ await rabbit.assertExchange(exchangeName);
65
+ await rabbit.bindQueue(queueName, exchangeName);
66
+
67
+ await rabbit.publish(exchangeName, 'hey');
68
+ let messageCount: any;
69
+ ({ messageCount } = await rabbit.getQueueLength(queueName));
70
+ expect(messageCount).toEqual(1);
71
+ await rabbit.publish(exchangeName, 'hey');
72
+ ({ messageCount } = await rabbit.getQueueLength(queueName));
73
+ expect(messageCount).toEqual(2);
74
+ await rabbit.consumeFromExchange(queueName, exchangeName, async (msg: any, ack: any) => {
75
+ expect(msg.content).toEqual('hey');
76
+ ack(msg);
77
+ });
78
+ await delay();
79
+ ({ messageCount } = await rabbit.getQueueLength(queueName));
80
+ expect(messageCount).toEqual(0);
81
+ });
59
82
  });
package/src/index.ts CHANGED
@@ -1,8 +1,14 @@
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 */
1
2
  // eslint-disable-next-line max-classes-per-file
2
3
  import * as bluebird from 'bluebird';
3
4
  import { ConfirmChannel } from 'amqplib';
4
- import { AmqpConnectionManager, ChannelWrapper, connect } from 'amqp-connection-manager';
5
+ import {
6
+ AmqpConnectionManager, ChannelWrapper,
7
+ connect as connectAmqp,
8
+ } from 'amqp-connection-manager';
5
9
  import { EventEmitter } from 'events';
10
+ import { connect as connectMock } from './mockImpl';
11
+ import RabbitWrapperInterface from './interfaces/RabbitWrapperInterface';
6
12
  import RabbitError from './rabbitError';
7
13
 
8
14
  interface ExchangesCache {
@@ -12,14 +18,14 @@ interface ExchangesCache {
12
18
  const assertExchangeFanout = (c: any, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
13
19
 
14
20
  const connectionCreatedConst = 'connectionCreated';
15
- class RabbitMq {
21
+ class RabbitMq implements RabbitWrapperInterface {
16
22
  static parseMsg(msg: any) {
17
23
  let { content } = msg;
18
24
  content = content.toString();
19
25
 
20
26
  try {
21
27
  content = JSON.parse(content);
22
- } catch (e) { } // eslint-disable-line no-empty
28
+ } catch (e) { }
23
29
 
24
30
  return {
25
31
  ...msg,
@@ -49,14 +55,21 @@ class RabbitMq {
49
55
 
50
56
  exchanges: ExchangesCache;
51
57
 
52
- constructor() {
58
+ isTestEnv: boolean;
59
+
60
+ constructor({ isTestEnv = false } = {}) {
53
61
  this.em = new EventEmitter();
54
62
  this.channel = null;
55
63
  this.connection = null;
56
64
  this.creatingConnection = false;
57
65
  this.exchanges = {};
66
+ this.isTestEnv = isTestEnv;
58
67
  }
59
68
 
69
+ private getConnectFunc = () => (this.isTestEnv ? connectMock : connectAmqp)
70
+
71
+ private connect = (urls:any[] = []) => this.getConnectFunc()(urls);
72
+
60
73
  public ack = async (msg: any) => {
61
74
  if (this.channel) {
62
75
  await this.channel.ack(msg);
@@ -66,7 +79,6 @@ class RabbitMq {
66
79
  public nack = (queue: string) => async (msg: any, retries: number) => {
67
80
  if (this.channel) {
68
81
  if (!msg.content['x-retry-count'] || msg.content['x-retry-count'] <= retries) {
69
- // eslint-disable-next-line no-param-reassign
70
82
  msg.content['x-retry-count'] = msg.content['x-retry-count'] ? msg.content['x-retry-count'] + 1 : 1;
71
83
  await this.sendToQueue(queue, msg.content);
72
84
  } else {
@@ -79,19 +91,17 @@ class RabbitMq {
79
91
  }
80
92
 
81
93
  async getConnection() {
82
- // eslint-disable-next-line no-async-promise-executor
83
94
  return new Promise<AmqpConnectionManager>(async (resolve) => {
84
95
  if (this.creatingConnection) {
85
96
  this.em.on(connectionCreatedConst, resolve);
86
97
  return;
87
98
  }
88
99
  if (this.connection !== null) {
89
- // eslint-disable-next-line consistent-return
90
100
  return this.connection;
91
101
  }
92
102
  this.creatingConnection = true;
93
103
  const host: string = process.env.RABBITMQ_SERVICE_HOST || '';
94
- const connection: AmqpConnectionManager = await connect([`amqp://${host}`]);
104
+ const connection: AmqpConnectionManager = await this.connect([`amqp://${host}`]);
95
105
  this.connection = connection;
96
106
  this.connection.on('disconnect',
97
107
  ({ err }) => console.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`));
@@ -102,7 +112,6 @@ class RabbitMq {
102
112
  }
103
113
 
104
114
  async assertChannel() {
105
- // eslint-disable-next-line no-async-promise-executor,consistent-return
106
115
  return new Promise<ChannelWrapper>(async (resolve, reject) => {
107
116
  if (this.channel) {
108
117
  return resolve(this.channel);
@@ -112,7 +121,7 @@ class RabbitMq {
112
121
 
113
122
  try {
114
123
  if (this.channel === null) {
115
- this.channel = connection.createChannel({});
124
+ this.channel = await connection.createChannel({});
116
125
  }
117
126
  resolve(this.channel);
118
127
  } catch (e) {
@@ -121,7 +130,6 @@ class RabbitMq {
121
130
  });
122
131
  }
123
132
 
124
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
125
133
  async assertExchange(exchangeName: string, options?: any) {
126
134
  const channel: ChannelWrapper = await this.assertChannel();
127
135
  if (this.exchanges[exchangeName]) {
@@ -134,14 +142,25 @@ class RabbitMq {
134
142
  });
135
143
  }
136
144
 
137
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
145
+ async getQueueLength(queue: string) {
146
+ RabbitMq.validateName('queue', queue);
147
+ const channel: ChannelWrapper = await this.assertChannel();
148
+ // @ts-ignore
149
+ return channel.checkQueue(queue);
150
+ }
151
+
152
+ async bindQueue(queue: string, exchange: string) {
153
+ const channel: ChannelWrapper = await this.assertChannel();
154
+ // @ts-ignore
155
+ return channel.bindQueue(queue, exchange, '');
156
+ }
157
+
138
158
  async assertQueue(queue: string, options?: any) {
139
159
  RabbitMq.validateName('queue', queue);
140
160
  const channel: ChannelWrapper = await this.assertChannel();
141
161
  return channel.addSetup((c: ConfirmChannel) => c.assertQueue(queue));
142
162
  }
143
163
 
144
- // eslint-disable-next-line @typescript-eslint/ban-types
145
164
  async consume(queue: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any) {
146
165
  RabbitMq.validateName('queue', queue);
147
166
  const { limit } = (options && options.limit) ? options : { limit: 1 };
@@ -155,7 +174,6 @@ class RabbitMq {
155
174
  });
156
175
  }
157
176
 
158
- // eslint-disable-next-line @typescript-eslint/ban-types
159
177
  async consumeFromExchange(queue: string, exchange: string, callback: (msg: any, ack: Function, nack: Function) => any, options?: any) {
160
178
  RabbitMq.validateName('queue', queue);
161
179
  RabbitMq.validateName('exchange', exchange);
@@ -0,0 +1,17 @@
1
+ interface RabbitWrapperInterface {
2
+ ack:any;
3
+ nack: any;
4
+ getConnection: any;
5
+ assertChannel: any;
6
+ assertExchange: any;
7
+ getQueueLength: any;
8
+ bindQueue: any;
9
+ assertQueue: any;
10
+ consume: any;
11
+ consumeFromExchange: any;
12
+ publish: any;
13
+ sendToQueue: any;
14
+
15
+ }
16
+
17
+ export default RabbitWrapperInterface;
@@ -0,0 +1,63 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/no-unused-vars */
2
+ import RabbitMq from '../index';
3
+
4
+ jest.setTimeout(120000);
5
+
6
+ const rabbit = new RabbitMq({ isTestEnv: true });
7
+
8
+ describe('RabbitMQ', () => {
9
+ it('check exchange', async () => {
10
+ const callback = (msg: any, ack: any) => {
11
+ expect(msg.content).toEqual('hey');
12
+ ack(msg);
13
+ };
14
+ const mockFn = jest.fn(callback);
15
+ await rabbit.consumeFromExchange('test-q2', 'test-e', mockFn);
16
+ await rabbit.publish('test-e', 'hey');
17
+ expect(mockFn).toBeCalled();
18
+ });
19
+
20
+ it('check queue', async () => {
21
+ const callback = (msg: any, ack: any) => {
22
+ expect(msg.content).toEqual('hey-q');
23
+ ack(msg);
24
+ };
25
+ const mockFn = jest.fn(callback);
26
+ await rabbit.consume('test-q-2', mockFn);
27
+ await rabbit.sendToQueue('test-q-2', 'hey-q');
28
+ expect(mockFn).toBeCalled();
29
+ });
30
+
31
+ it('cant publish to unknown exchange', async () => {
32
+ let err;
33
+ try {
34
+ await rabbit.publish('', 'hey');
35
+ } catch (e) {
36
+ err = e;
37
+ }
38
+ expect(err).toBeDefined();
39
+ });
40
+
41
+ it('get queue length', async () => {
42
+ const queueName = 'test-queue-length';
43
+ const exchangeName = 'test-e2';
44
+
45
+ await rabbit.assertQueue(queueName);
46
+ await rabbit.assertExchange(exchangeName);
47
+ await rabbit.bindQueue(queueName, exchangeName);
48
+
49
+ await rabbit.publish(exchangeName, 'hey');
50
+ let messageCount: any;
51
+ ({ messageCount } = await rabbit.getQueueLength(queueName));
52
+ expect(messageCount).toEqual(0);
53
+ await rabbit.publish(exchangeName, 'hey');
54
+ ({ messageCount } = await rabbit.getQueueLength(queueName));
55
+ expect(messageCount).toEqual(0);
56
+ await rabbit.consumeFromExchange(queueName, exchangeName, async (msg: any, ack: any) => {
57
+ expect(msg.content).toEqual('hey');
58
+ ack(msg);
59
+ });
60
+ ({ messageCount } = await rabbit.getQueueLength(queueName));
61
+ expect(messageCount).toEqual(0);
62
+ });
63
+ });
@@ -0,0 +1,132 @@
1
+ import * as Bluebird from 'bluebird';
2
+
3
+ let exchanges: any = {};
4
+ let queues: any = {};
5
+
6
+ export interface channel {
7
+ assertQueue: any;
8
+ assertExchange: any;
9
+ bindQueue: any;
10
+ publish: any;
11
+ consume: any;
12
+ deleteQueue: any;
13
+ ack: any;
14
+ nack: any;
15
+ prefetch: any;
16
+ on: any;
17
+ once: any;
18
+ close: any;
19
+ addSetup: any;
20
+ checkQueue: any;
21
+ sendToQueue: any;
22
+ }
23
+ export interface connection {
24
+ createChannel: any;
25
+ createConfirmChannel: any;
26
+ on: any;
27
+ once: any;
28
+ close: any;
29
+ }
30
+
31
+ const setIfUndef = (object: any, prop: any, value: any) => {
32
+ if (!object[prop]) {
33
+ // eslint-disable-next-line no-param-reassign
34
+ object[prop] = value;
35
+ }
36
+ };
37
+
38
+ class ChannelImpl implements channel {
39
+ public assertQueue = (queue: any, qOptions: any) => new Bluebird((resolve: () => any) => {
40
+ setIfUndef(queues, queue, { messages: [], subscribers: [], options: qOptions });
41
+ return resolve();
42
+ });
43
+
44
+ public assertExchange = (exchange: any, type: any, exchOptions: any = {}) => new Bluebird((resolve: () => any) => {
45
+ setIfUndef(exchanges, exchange, { bindings: [], options: exchOptions, type });
46
+
47
+ return resolve();
48
+ });
49
+
50
+ public bindQueue = (queue: any, exchange: string, key: string) => new Bluebird((resolve: () => any, reject: (arg0: string) => any) => {
51
+ if (!exchanges[exchange]) return reject(`Bind to non-existing exchange ${exchange}`);
52
+
53
+ const re = `^${key.replace('.', '\\.').replace('#', '(\\w|\\.)+').replace('*', '\\w+')}$`;
54
+ exchanges[exchange].bindings.push({ regex: new RegExp(re), queueName: queue });
55
+
56
+ return resolve();
57
+ });
58
+
59
+ public publish = (exchange: string, routingKey: any, content: any, props: any) => new Bluebird((resolve: () => any, reject: (arg0: string) => any) => {
60
+ if (!exchanges[exchange]) return reject(`Publish to non-existing exchange ${exchange}`);
61
+
62
+ const { bindings } = exchanges[exchange];
63
+ const matchingBindings = bindings.filter((b: any) => b.regex.test(routingKey));
64
+
65
+ matchingBindings.forEach((binding: { queueName: string | number; }) => {
66
+ const subscribers = queues[binding.queueName] ? queues[binding.queueName].subscribers : [];
67
+ subscribers.forEach((sub: (arg0: { fields: { routingKey: any; }; properties: any; content: any; }) => void) => {
68
+ const message = { fields: { routingKey }, properties: props, content };
69
+ sub(message);
70
+ });
71
+ });
72
+
73
+ return resolve();
74
+ });
75
+
76
+ public consume = (queue: string | number, handler: any) => {
77
+ queues[queue].subscribers.push(handler);
78
+ };
79
+
80
+ public sendToQueue = (queue: string, content: any, routingKey = '', props: any = {}) => {
81
+ const subscribers = queues[queue] ? queues[queue].subscribers : [];
82
+ subscribers.forEach((sub: (arg0: { fields: { routingKey: any; }; properties: any; content: any; }) => void) => {
83
+ const message = { fields: { routingKey }, properties: props, content };
84
+ sub(message);
85
+ });
86
+ };
87
+
88
+ public deleteQueue = (queue: string | number) => {
89
+ setImmediate(() => {
90
+ delete queues[queue];
91
+ });
92
+ };
93
+
94
+ public async addSetup(funcToRun: any) {
95
+ const val = await funcToRun(this);
96
+ return val;
97
+ }
98
+
99
+ public checkQueue = () => ({ messageCount: 0 });
100
+
101
+ public ack = () => null;
102
+
103
+ public nack = () => null;
104
+
105
+ public prefetch = () => null;
106
+
107
+ public on = () => null;
108
+
109
+ public once = () => null;
110
+
111
+ public close = () => Bluebird.resolve();
112
+ }
113
+
114
+ const createChannel = () => new Bluebird((resolve: any) => resolve(new ChannelImpl()));
115
+ export const connect = (): any => new Bluebird((resolve: any) => {
116
+ const connectionImpl: connection = {
117
+ createChannel,
118
+ createConfirmChannel: createChannel,
119
+ on: () => null,
120
+ once: () => null,
121
+ close: () => Bluebird.resolve(),
122
+ };
123
+
124
+ return resolve(connectionImpl);
125
+ });
126
+
127
+ const resetMock = () => {
128
+ queues = {};
129
+ exchanges = {};
130
+ };
131
+
132
+ module.exports = { connect, resetMock };