arnavmq 0.14.1 → 0.15.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.
@@ -1,9 +1,4 @@
1
1
  version: 2
2
- registries:
3
- npm-registry-registry-npmjs-org:
4
- type: npm-registry
5
- url: https://registry.npmjs.org
6
- token: "${{secrets.NPM_REGISTRY_REGISTRY_NPMJS_ORG_TOKEN}}"
7
2
 
8
3
  updates:
9
4
  - package-ecosystem: npm
@@ -17,5 +12,3 @@ updates:
17
12
  - yosiat
18
13
  - shamil
19
14
  - ramhr
20
- registries:
21
- - npm-registry-registry-npmjs-org
package/.prettierignore CHANGED
@@ -17,3 +17,4 @@
17
17
  /.idea/**
18
18
  /.github/**
19
19
  /samples/**
20
+ .nyc_output/
package/README.md CHANGED
@@ -137,9 +137,6 @@ const arnavmq = require('arnavmq')({
137
137
  // generate a hostname so we can track this connection on the broker (rabbitmq management plugin)
138
138
  hostname: process.env.HOSTNAME || process.env.USER || uuid.v4(),
139
139
 
140
- // Deprecated. Use 'logger' instead. The transport to use to debug. If provided, arnavmq will show some logs
141
- transport: utils.emptyLogger,
142
-
143
140
  /**
144
141
  * A logger object with a log function for each of the log levels ("debug", "info", "warn", or "error").
145
142
  * Each log function receives one parameter containing a log event with the following fields:
@@ -153,7 +150,7 @@ const arnavmq = require('arnavmq')({
153
150
 
154
151
  You can override any or no of the property above.
155
152
 
156
- **Note:** if you enable the debug mode using the `AMQP_DEBUG=true` env var, but you do not attach any transport logger, the module will fallback to console.
153
+ **Note:** if you enable the debug mode using the `AMQP_DEBUG=true` env var, but you do not attach any logger, the module will fallback to console.
157
154
 
158
155
  ## Documentation & resources
159
156
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arnavmq",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "ArnavMQ is a RabbitMQ wrapper",
5
5
  "keywords": [
6
6
  "rabbitmq",
@@ -16,7 +16,7 @@
16
16
  "lint": "eslint . && prettier -c .",
17
17
  "format": "eslint --fix . && prettier --write .",
18
18
  "cover": "test -d .nyc_output && nyc report --reporter lcov",
19
- "test": "dot-only-hunter test && nyc mocha --recursive --timeout=30000 --exit"
19
+ "test": "dot-only-hunter test && nyc mocha --recursive --exit"
20
20
  },
21
21
  "prettier": {
22
22
  "singleQuote": true,
@@ -48,7 +48,7 @@
48
48
  "mocha": "^10.0.0",
49
49
  "nyc": "^15.1.0",
50
50
  "prettier": "^2.8.3",
51
- "sinon": "^14.0.1"
51
+ "sinon": "^15.0.1"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=14"
package/src/index.js CHANGED
@@ -30,9 +30,6 @@ module.exports = (config) => {
30
30
  // generate a hostname so we can track this connection on the broker (rabbitmq management plugin)
31
31
  hostname: process.env.HOSTNAME || process.env.USER || uuid.v4(),
32
32
 
33
- // Deprecated. Use 'logger' instead. The transport to use to debug. If provided, arnavmq will show some logs
34
- transport: utils.emptyLogger,
35
-
36
33
  /**
37
34
  * A logger object with a log function for each of the log levels ("debug", "info", "warn", or "error").
38
35
  * Each log function receives one parameter containing a log event with the following fields:
@@ -45,11 +42,8 @@ module.exports = (config) => {
45
42
  ...config,
46
43
  };
47
44
 
48
- if (configuration.transport !== utils.emptyLogger) {
49
- process.emitWarning(
50
- "The 'transport' configuration option is deprecated. Please use the 'logger' option instead.",
51
- 'DeprecationWarning'
52
- );
45
+ if (configuration.transport) {
46
+ throw new Error('Using removed deprecated "transport" option. Use the "logger" option instead.');
53
47
  }
54
48
 
55
49
  configuration.prefetch = parseInt(configuration.prefetch, 10) || 0;
@@ -26,68 +26,82 @@ class Channels {
26
26
  this._channels = new Map();
27
27
  }
28
28
 
29
- get(queue, config) {
29
+ async get(queue, config) {
30
30
  // If we don't have custom prefetch create a new channel
31
31
  const defaultPrefetch = this._config.prefetch;
32
32
  const requestedPrefetch = config.prefetch || defaultPrefetch;
33
33
  if (typeof requestedPrefetch === 'number' && requestedPrefetch !== defaultPrefetch) {
34
- return this._get(queue, config);
34
+ return await this._get(queue, config);
35
35
  }
36
36
 
37
- return this.defaultChannel();
37
+ return await this.defaultChannel();
38
+ }
39
+
40
+ async defaultChannel() {
41
+ return await this._get(DEFAULT_CHANNEL, { prefetch: this._config.prefetch });
38
42
  }
39
43
 
40
44
  /**
41
45
  * Creates or returns an existing channel by it's key and config.
42
46
  * @return {Promise} A promise that resolve with an amqp.node channel object
43
47
  */
44
- _get(key, config = {}) {
45
- let channel = this._channels.get(key);
48
+ async _get(key, config = {}) {
49
+ const existingChannel = this._channels.get(key);
46
50
 
47
- if (channel) {
48
- if (!isSameConfig(channel.config, config)) {
49
- return Promise.reject(new ChannelAlreadyExistsError(key, config));
51
+ if (existingChannel) {
52
+ if (!isSameConfig(existingChannel.config, config)) {
53
+ throw new ChannelAlreadyExistsError(key, config);
50
54
  }
51
55
 
52
56
  // cache handling, if channel already opened, return it
53
- return channel.chann;
57
+ return await existingChannel.chann;
54
58
  }
55
59
 
56
- channel = this._connection
57
- .createChannel()
58
- .then((_channel) => {
59
- _channel.prefetch(config.prefetch);
60
+ const channelPromise = this._initNewChannel(key, config);
61
+ this._channels.set(key, { chann: channelPromise, config });
60
62
 
61
- // on error we remove the channel so the next call will recreate it (auto-reconnect are handled by connection users)
62
- _channel.on('close', () => {
63
- this._channels.delete(key);
64
- });
65
- _channel.on('error', (error) => {
66
- this._config.logger.error({
67
- message: `Got channel error [${error.message}] for [${key}]`,
68
- error,
69
- });
70
- });
63
+ return await channelPromise;
64
+ }
65
+
66
+ async _initNewChannel(key, config) {
67
+ let channel;
68
+ try {
69
+ channel = await this._connection.createChannel();
70
+
71
+ channel.prefetch(config.prefetch);
71
72
 
72
- return _channel;
73
- })
74
- .catch((error) => {
73
+ // on error we remove the channel so the next call will recreate it (auto-reconnect are handled by connection users)
74
+ channel.on('close', () => {
75
75
  this._channels.delete(key);
76
+ });
77
+ channel.on('error', (error) => {
76
78
  this._config.logger.error({
77
- message: `Failed to create channel for [${key}] - [${error.message}]`,
79
+ message: `Got channel error [${error.message}] for [${key}]`,
78
80
  error,
79
81
  });
80
-
81
- return Promise.reject(error);
82
82
  });
83
83
 
84
- this._channels.set(key, { chann: channel, config });
85
-
86
- return channel;
87
- }
84
+ return channel;
85
+ } catch (error) {
86
+ this._channels.delete(key);
87
+ this._config.logger.error({
88
+ message: `Failed to create channel for [${key}] - [${error.message}]`,
89
+ error,
90
+ });
88
91
 
89
- defaultChannel() {
90
- return this._get(DEFAULT_CHANNEL, { prefetch: this._config.prefetch });
92
+ // Should not happen, but just in case
93
+ if (channel) {
94
+ try {
95
+ await channel.close();
96
+ } catch (closeError) {
97
+ this._config.logger.error({
98
+ message: `Failed to cleanup channel after failed initialization for [${key}] - [${closeError.message}]`,
99
+ error: closeError,
100
+ });
101
+ }
102
+ }
103
+ throw error;
104
+ }
91
105
  }
92
106
  }
93
107
 
@@ -7,7 +7,7 @@ class Connection {
7
7
  constructor(config) {
8
8
  this._config = config;
9
9
 
10
- this._connection = null; // Promise of amqp connection
10
+ this._connectionPromise = null; // Promise of amqp connection
11
11
  this._channels = null;
12
12
  this.startedAt = new Date().toISOString();
13
13
  }
@@ -16,39 +16,40 @@ class Connection {
16
16
  * Connect to the broker. We keep only 1 connection for each connection string provided in config, as advised by RabbitMQ
17
17
  * @return {Promise} A promise that resolve with an amqp.node connection object
18
18
  */
19
- getConnection() {
19
+ async getConnection() {
20
20
  // cache handling, if connection already opened, return it
21
- if (this._connection) {
22
- return this._connection;
21
+ if (!this._connectionPromise) {
22
+ this._connectionPromise = this._connect();
23
23
  }
24
24
 
25
- // prepare the connection internal object, and reset channel if connection has been closed
26
- this._connection = amqp
27
- .connect(this._config.host, {
25
+ return await this._connectionPromise;
26
+ }
27
+
28
+ async _connect() {
29
+ try {
30
+ const connection = await amqp.connect(this._config.host, {
28
31
  clientProperties: {
29
32
  hostname: this._config.hostname,
30
33
  arnavmq: packageVersion,
31
34
  startedAt: this.startedAt,
32
35
  connectedAt: new Date().toISOString(),
33
36
  },
34
- })
35
- .then((conn) => {
36
- this._channels = new Channels(conn, this._config);
37
- // on connection close, delete connection
38
- conn.on('close', () => {
39
- this._connection = null;
40
- this._channels = null;
41
- });
42
- conn.on('error', this._onError.bind(this));
43
- return conn;
44
- })
45
- .catch((e) => {
46
- this._connection = null;
37
+ });
38
+
39
+ this._channels = new Channels(connection, this._config);
40
+ // on connection close, delete connection
41
+ connection.on('close', () => {
42
+ this._connectionPromise = null;
47
43
  this._channels = null;
48
- throw e;
49
44
  });
45
+ connection.on('error', this._onError.bind(this));
50
46
 
51
- return this._connection;
47
+ return connection;
48
+ } catch (error) {
49
+ this._connectionPromise = null;
50
+ this._channels = null;
51
+ throw error;
52
+ }
52
53
  }
53
54
 
54
55
  /**
@@ -56,19 +57,20 @@ class Connection {
56
57
  * @param {Error} error
57
58
  */
58
59
  _onError(error) {
59
- this._config.transport.error(error);
60
60
  this._config.logger.error({
61
61
  message: error.message,
62
62
  error,
63
63
  });
64
64
  }
65
65
 
66
- getChannel(queue, config) {
67
- return this.getConnection().then(() => this._channels.get(queue, config));
66
+ async getChannel(queue, config) {
67
+ await this.getConnection();
68
+ return await this._channels.get(queue, config);
68
69
  }
69
70
 
70
- getDefaultChannel() {
71
- return this.getConnection().then(() => this._channels.defaultChannel());
71
+ async getDefaultChannel() {
72
+ await this.getConnection();
73
+ return await this._channels.defaultChannel();
72
74
  }
73
75
 
74
76
  /**
@@ -76,10 +78,9 @@ class Connection {
76
78
  * @param {string} on the channel event name to be bound with
77
79
  * @param {function} func the callback function to execute when the event is called
78
80
  */
79
- addListener(on, func) {
80
- this.getDefaultChannel().then((channel) => {
81
- channel.on(on, func);
82
- });
81
+ async addListener(on, func) {
82
+ const channel = await this.getDefaultChannel();
83
+ channel.on(on, func);
83
84
  }
84
85
 
85
86
  get config() {
@@ -21,36 +21,28 @@ class Consumer {
21
21
 
22
22
  /**
23
23
  * Get a function to execute on incoming messages to handle RPC
24
- * @param {any} msg An amqp.node message object
24
+ * @param {any} messageProperties An amqp.node message properties object, containing the rpc settings
25
25
  * @param {string} queue The initial queue on which the handler received the message
26
- * @return {function} a function to use in a chaining on incoming messages
26
+ * @param {any} reply the received message to reply the rpc if needed:
27
+ * @return {any} object, string, number... the current received message
27
28
  */
28
- checkRpc(msg, queue) {
29
- /**
30
- * When message contains a replyTo property, we try to send the answer back
31
- * @param {any} content the received message:
32
- * @return {any} object, string, number... the current received message
33
- */
34
- return (content) => {
35
- if (msg.properties.replyTo) {
36
- const options = {
37
- correlationId: msg.properties.correlationId,
38
- persistent: true,
39
- durable: true,
40
- };
41
- this._connection.config.transport.debug(loggerAlias, `[${queue}][${msg.properties.replyTo}] >`, content);
42
- this._connection.config.logger.debug({
43
- message: `${loggerAlias} [${queue}][${msg.properties.replyTo}] > ${content}`,
44
- params: { content },
45
- });
29
+ async checkRpc(messageProperties, queue, reply) {
30
+ if (messageProperties.replyTo) {
31
+ const options = {
32
+ correlationId: messageProperties.correlationId,
33
+ persistent: true,
34
+ durable: true,
35
+ };
36
+ this._connection.config.logger.debug({
37
+ message: `${loggerAlias} [${queue}][${messageProperties.replyTo}] > ${reply}`,
38
+ params: { content: reply },
39
+ });
46
40
 
47
- return this._connection.getDefaultChannel().then((channel) => {
48
- return channel.sendToQueue(msg.properties.replyTo, parsers.out(content, options), options);
49
- });
50
- }
41
+ const defaultChannel = await this._connection.getDefaultChannel();
42
+ return await defaultChannel.sendToQueue(messageProperties.replyTo, parsers.out(reply, options), options);
43
+ }
51
44
 
52
- return msg;
53
- };
45
+ return messageProperties;
54
46
  }
55
47
 
56
48
  /**
@@ -66,7 +58,7 @@ class Consumer {
66
58
  return this.subscribe(queue, options, callback);
67
59
  }
68
60
 
69
- subscribe(queue, options, callback) {
61
+ async subscribe(queue, options, callback) {
70
62
  const defaultOptions = {
71
63
  persistent: true,
72
64
  durable: true,
@@ -93,67 +85,128 @@ class Consumer {
93
85
  // ex: service-something with suffix :ci becomes service-suffix:ci etc.
94
86
  const suffixedQueue = `${queue}${this._connection.config.consumerSuffix || ''}`;
95
87
 
96
- return this._connection
97
- .getChannel(queue, options.channel || {})
98
- .then((channel) => {
99
- // when channel is closed, we want to be sure we recreate the queue ASAP so we trigger a reconnect by recreating the consumer
100
- channel.addListener('close', () => {
101
- this.subscribe(queue, options, callback);
102
- });
88
+ const channel = await this._initializeChannel(queue, options || {}, callback);
89
+ if (!channel) {
90
+ // in case of any error creating the channel, wait for some time and then try to reconnect again (to avoid overflow)
91
+ await utils.timeoutPromise(this._connection.config.timeout);
92
+ return await this.subscribe(queue, options, callback);
93
+ }
103
94
 
104
- return channel.assertQueue(suffixedQueue, options).then((q) => {
105
- this._connection.config.transport.debug(loggerAlias, 'init', q.queue);
106
- this._connection.config.logger.debug({
107
- message: `${loggerAlias} init ${q.queue}`,
108
- params: { queue: q.queue },
95
+ try {
96
+ await channel.assertQueue(suffixedQueue, options);
97
+ } catch (error) {
98
+ this._connection.config.logger.error({
99
+ message: `${loggerAlias} Failed to assert queue ${queue}: ${error.message}`,
100
+ error,
101
+ params: { queue },
102
+ });
103
+ }
104
+
105
+ this._connection.config.logger.debug({
106
+ message: `${loggerAlias} init ${queue}`,
107
+ params: { queue },
108
+ });
109
+
110
+ await this._consumeQueue(channel, queue, callback);
111
+ return true;
112
+ }
113
+
114
+ async _initializeChannel(queue, options, callback) {
115
+ let channel;
116
+ try {
117
+ channel = await this._connection.getChannel(queue, options.channel || {});
118
+ // when channel is closed, we want to be sure we recreate the queue ASAP so we trigger a reconnect by recreating the consumer
119
+ channel.addListener('close', () => {
120
+ this.subscribe(queue, options, callback);
121
+ });
122
+ return channel;
123
+ } catch (err) {
124
+ if (err instanceof ChannelAlreadyExistsError) {
125
+ throw err;
126
+ }
127
+
128
+ if (channel) {
129
+ try {
130
+ // Just in the odd chance the channel was open but the listener failed.
131
+ await channel.close();
132
+ } catch (closeError) {
133
+ this._connection.config.logger.error({
134
+ message: `${loggerAlias} Failed to close channel after initialization error ${queue}: ${closeError.message}`,
135
+ error: closeError,
136
+ params: { queue },
109
137
  });
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+ }
110
143
 
111
- channel.consume(
112
- q.queue,
113
- (msg) => {
114
- const messageString = msg.content.toString();
115
- this._connection.config.transport.debug(loggerAlias, `[${q.queue}] < ${messageString}`);
116
- this._connection.config.logger.debug({
117
- message: `${loggerAlias} [${q.queue}] < ${messageString}`,
118
- params: { queue: q.queue, message: messageString },
119
- });
120
-
121
- // main answer management chaining
122
- // receive message, parse it, execute callback, check if should answer, ack/reject message
123
- Promise.resolve(parsers.in(msg))
124
- .then((body) => callback(body, msg.properties))
125
- .then(this.checkRpc(msg, q.queue))
126
- .then(() => {
127
- channel.ack(msg);
128
- })
129
- .catch((error) => {
130
- // if something bad happened in the callback, reject the message so we can requeue it (or not)
131
- this._connection.config.transport.error(loggerAlias, error);
132
- this._connection.config.logger.error({
133
- message: `${loggerAlias} Failed processing message from queue ${q.queue}: ${error.message}`,
134
- error,
135
- params: { queue: q.queue, message: messageString },
136
- });
137
-
138
- channel.reject(msg, this._connection.config.requeue);
139
- });
140
- },
141
- { noAck: false }
142
- );
143
-
144
- return true;
144
+ async _consumeQueue(channel, queue, callback) {
145
+ const consumeFunc = async (msg) => {
146
+ if (!msg) {
147
+ // When forcefully cancelled by rabbitmq, consumer would receive a null message.
148
+ // https://amqp-node.github.io/amqplib/channel_api.html#channel_consume
149
+ this._connection.config.logger.warn({
150
+ message: `${loggerAlias} Consumer was cancelled by server for queue '${queue}'`,
151
+ error: null,
152
+ params: { queue },
145
153
  });
146
- // in case of any error creating the channel, wait for some time and then try to reconnect again (to avoid overflow)
147
- })
148
- .catch((err) => {
149
- if (err instanceof ChannelAlreadyExistsError) {
150
- throw err;
151
- } else {
152
- return utils
153
- .timeoutPromise(this._connection.config.timeout)
154
- .then(() => this.subscribe(queue, options, callback));
154
+ return;
155
+ }
156
+
157
+ const messageString = msg.content.toString();
158
+ this._connection.config.logger.debug({
159
+ message: `${loggerAlias} [${queue}] < ${messageString}`,
160
+ params: { queue, message: messageString },
161
+ });
162
+
163
+ // main answer management chaining
164
+ // receive message, parse it, execute callback, check if should answer, ack/reject message
165
+ const body = parsers.in(msg);
166
+ try {
167
+ const res = await callback(body, msg.properties);
168
+ await this.checkRpc(msg.properties, queue, res);
169
+ } catch (error) {
170
+ // if something bad happened in the callback, reject the message so we can requeue it (or not)
171
+ this._connection.config.logger.error({
172
+ message: `${loggerAlias} Failed processing message from queue ${queue}: ${error.message}`,
173
+ error,
174
+ params: { queue, message: messageString },
175
+ });
176
+
177
+ try {
178
+ channel.reject(msg, this._connection.config.requeue);
179
+ } catch (rejectError) {
180
+ this._connection.config.logger.error({
181
+ message: `${loggerAlias} Failed to reject message after processing failure on queue ${queue}: ${rejectError.message}`,
182
+ error: rejectError,
183
+ params: { queue },
184
+ });
155
185
  }
186
+
187
+ return;
188
+ }
189
+
190
+ try {
191
+ channel.ack(msg);
192
+ } catch (ackError) {
193
+ this._connection.config.logger.error({
194
+ message: `${loggerAlias} Failed to ack message after processing finished on queue ${queue}: ${ackError.message}`,
195
+ error: ackError,
196
+ params: { queue },
197
+ });
198
+ }
199
+ };
200
+
201
+ try {
202
+ await channel.consume(queue, consumeFunc, { noAck: false });
203
+ } catch (error) {
204
+ this._connection.config.logger.error({
205
+ message: `${loggerAlias} Failed to start consuming from queue ${queue}: ${error.message}`,
206
+ error,
207
+ params: { queue },
156
208
  });
209
+ }
157
210
  }
158
211
  }
159
212
 
@@ -47,20 +47,19 @@ class Producer {
47
47
  const { correlationId } = msg.properties;
48
48
  const responsePromise = rpcQueue[correlationId];
49
49
 
50
+ // On timeout the waiter is deleted, so we need to handle the race when the response arrives too late.
50
51
  if (responsePromise === undefined) {
51
52
  const error = new Error(`Receiving RPC message from previous session: callback no more in memory. ${queue}`);
52
- this._connection.config.transport.warn(loggerAlias, error);
53
53
  this._connection.config.logger.warn({
54
54
  message: `${loggerAlias} ${error.message}`,
55
55
  error,
56
- params: { queue, rpcQueue },
56
+ params: { queue, correlationId },
57
57
  });
58
58
 
59
59
  return;
60
60
  }
61
61
 
62
62
  // if we found one, we execute the callback and delete it because it will never be received again anyway
63
- this._connection.config.transport.info(loggerAlias, `[${queue}] < answer`);
64
63
  this._connection.config.logger.debug({
65
64
  message: `${loggerAlias} [${queue}] < answer`,
66
65
  params: { queue },
@@ -79,56 +78,55 @@ class Producer {
79
78
  /**
80
79
  * Create a RPC-ready queue
81
80
  * @param {string} queue the queue name in which we send a RPC request
82
- * @return {Promise} Resolves when answer response queue is ready to receive messages
81
+ * @return {Promise} Resolves with the the response queue name when the answer response queue is ready to receive messages
83
82
  */
84
- createRpcQueue(queue) {
83
+ async createRpcQueue(queue) {
85
84
  this.amqpRPCQueues[queue] = this.amqpRPCQueues[queue] || {};
86
85
 
87
- const rpcQueue = this.amqpRPCQueues[queue];
88
- if (rpcQueue.queue) return Promise.resolve(rpcQueue.queue);
86
+ const rpcData = this.amqpRPCQueues[queue];
87
+ if (rpcData.resQueuePromise) {
88
+ return await rpcData.resQueuePromise;
89
+ }
90
+
91
+ rpcData.resQueuePromise = this._initializeRpcQueue(queue);
92
+ return await rpcData.resQueuePromise;
93
+ }
94
+
95
+ async _initializeRpcQueue(sourceQueue) {
96
+ const rpcQueue = this.amqpRPCQueues[sourceQueue];
89
97
 
90
98
  // we create the callback queue using base queue name + appending config hostname and :res for clarity
91
99
  // ie. if hostname is gateway-http and queue is service-oauth, response queue will be service-oauth:gateway-http:res
92
100
  // it is important to have different hostname or no hostname on each module sending message or there will be conflicts
93
- const resQueue = `${queue}:${this._connection.config.hostname}:${process.pid}:res`;
94
- rpcQueue.queue = this._connection
95
- .getDefaultChannel()
96
- .then((channel) =>
97
- channel
98
- .assertQueue(resQueue, {
99
- durable: true,
100
- exclusive: true,
101
- })
102
- .then((q) => {
103
- rpcQueue.queue = q.queue;
104
-
105
- // if channel is closed, we want to make sure we cleanup the queue so future calls will recreate it
106
- this._connection.addListener('close', () => {
107
- delete rpcQueue.queue;
108
- this.createRpcQueue(queue);
109
- });
110
-
111
- return channel.consume(q.queue, this.maybeAnswer(queue), {
112
- noAck: true,
113
- });
114
- })
115
- .then(() => rpcQueue.queue)
116
- )
117
- .catch(() => {
118
- delete rpcQueue.queue;
119
- return utils.timeoutPromise(this._connection.config.timeout).then(() => this.createRpcQueue(queue));
101
+ const resQueue = `${sourceQueue}:${this._connection.config.hostname}:${process.pid}:res`;
102
+ try {
103
+ const channel = await this._connection.getDefaultChannel();
104
+ await channel.assertQueue(resQueue, { durable: true, exclusive: true });
105
+
106
+ // if channel is closed, we want to make sure we cleanup the queue so future calls will recreate it
107
+ this._connection.addListener('close', () => {
108
+ delete rpcQueue.resQueuePromise;
109
+ this.createRpcQueue(sourceQueue);
120
110
  });
121
111
 
122
- return rpcQueue.queue;
112
+ await channel.consume(resQueue, this.maybeAnswer(sourceQueue), {
113
+ noAck: true,
114
+ });
115
+ return resQueue;
116
+ } catch (error) {
117
+ delete rpcQueue.resQueuePromise;
118
+ await utils.timeoutPromise(this._connection.config.timeout);
119
+ return await this.createRpcQueue(sourceQueue);
120
+ }
123
121
  }
124
122
 
125
123
  async publishOrSendToQueue(queue, msg, options) {
126
124
  const channel = await this._connection.getDefaultChannel();
127
125
 
128
126
  if (!options.routingKey) {
129
- return channel.sendToQueue(queue, msg, options);
127
+ return await channel.sendToQueue(queue, msg, options);
130
128
  }
131
- return channel.publish(queue, options.routingKey, msg, options);
129
+ return await channel.publish(queue, options.routingKey, msg, options);
132
130
  }
133
131
 
134
132
  /**
@@ -156,35 +154,34 @@ class Producer {
156
154
  * @param {object} options contain rpc property (if true, enable rpc for this message)
157
155
  * @return {Promise} Resolves when message is correctly sent, or when response is received when rpc is enabled
158
156
  */
159
- checkRpc(queue, msg, options) {
157
+ async checkRpc(queue, msg, options) {
160
158
  // messages are persistent
161
159
  options.persistent = true;
162
160
 
163
161
  if (options.rpc) {
164
- return this.createRpcQueue(queue).then(() => {
165
- // generates a correlationId (random uuid) so we know which callback to execute on received response
166
- const corrId = uuid.v4();
167
- options.correlationId = corrId;
168
- // reply to us if you receive this message!
169
- options.replyTo = this.amqpRPCQueues[queue].queue;
170
-
171
- // defered promise that will resolve when response is received
172
- const responsePromise = pDefer();
173
- this.amqpRPCQueues[queue][corrId] = responsePromise;
174
-
175
- this.publishOrSendToQueue(queue, msg, options);
176
-
177
- // Using given timeout or default one
178
- const timeout = options.timeout || this._connection.config.rpcTimeout || 0;
179
- if (timeout > 0) {
180
- this.prepareTimeoutRpc(queue, corrId, timeout);
181
- }
182
-
183
- return responsePromise.promise;
184
- });
162
+ await this.createRpcQueue(queue);
163
+ // generates a correlationId (random uuid) so we know which callback to execute on received response
164
+ const corrId = uuid.v4();
165
+ options.correlationId = corrId;
166
+ // reply to us if you receive this message!
167
+ options.replyTo = await this.amqpRPCQueues[queue].resQueuePromise;
168
+
169
+ // deferred promise that will resolve when response is received
170
+ const responsePromise = pDefer();
171
+ this.amqpRPCQueues[queue][corrId] = responsePromise;
172
+
173
+ await this.publishOrSendToQueue(queue, msg, options);
174
+
175
+ // Using given timeout or default one
176
+ const timeout = options.timeout || this._connection.config.rpcTimeout || 0;
177
+ if (timeout > 0) {
178
+ this.prepareTimeoutRpc(queue, corrId, timeout);
179
+ }
180
+
181
+ return await responsePromise.promise;
185
182
  }
186
183
 
187
- return this.publishOrSendToQueue(queue, msg, options);
184
+ return await this.publishOrSendToQueue(queue, msg, options);
188
185
  }
189
186
 
190
187
  /**
@@ -208,7 +205,7 @@ class Producer {
208
205
  * @return {Promise} checkRpc response
209
206
  */
210
207
  /* eslint no-param-reassign: "off" */
211
- publish(queue, msg, options) {
208
+ async publish(queue, msg, options) {
212
209
  // default options are persistent and durable because we do not want to miss any outgoing message
213
210
  // unless user specify it
214
211
  const settings = { persistent: true, durable: true, ...options };
@@ -220,35 +217,36 @@ class Producer {
220
217
  message = { ...msg };
221
218
  }
222
219
 
223
- return this._sendToQueue(queue, message, settings, 0);
220
+ return await this._sendToQueue(queue, message, settings, 0);
224
221
  }
225
222
 
226
- _sendToQueue(queue, message, settings, currentRetryNumber) {
223
+ async _sendToQueue(queue, message, settings, currentRetryNumber) {
227
224
  // undefined can't be serialized/buffered :p
228
- if (!message) message = null;
225
+ if (!message) {
226
+ message = null;
227
+ }
229
228
 
230
- this._connection.config.transport.info(loggerAlias, `[${queue}] > `, message);
231
229
  this._connection.config.logger.debug({
232
230
  message: `${loggerAlias} [${queue}] > ${message}`,
233
231
  params: { queue, message },
234
232
  });
235
233
 
236
- return this.checkRpc(queue, parsers.out(message, settings), settings).catch((error) => {
234
+ try {
235
+ return await this.checkRpc(queue, parsers.out(message, settings), settings);
236
+ } catch (error) {
237
237
  if (!this._shouldRetry(error, currentRetryNumber)) {
238
238
  throw error;
239
239
  }
240
240
 
241
241
  // add timeout between retries because we don't want to overflow the CPU
242
- this._connection.config.transport.error(loggerAlias, error);
243
242
  this._connection.config.logger.error({
244
243
  message: `${loggerAlias} Failed sending message to queue ${queue}: ${error.message}`,
245
244
  error,
246
245
  params: { queue, message },
247
246
  });
248
- return utils
249
- .timeoutPromise(this._connection.config.timeout)
250
- .then(() => this._sendToQueue(queue, message, settings, currentRetryNumber + 1));
251
- });
247
+ await utils.timeoutPromise(this._connection.config.timeout);
248
+ return await this._sendToQueue(queue, message, settings, currentRetryNumber + 1);
249
+ }
252
250
  }
253
251
 
254
252
  _shouldRetry(error, currentRetryNumber) {
@@ -10,17 +10,11 @@ const emptyLogger = {
10
10
 
11
11
  module.exports = {
12
12
  /**
13
- * Default transport to prevent any printing in the terminal
13
+ * Default logger to prevent any printing in the terminal
14
14
  * @type {Object} - empty logger overwriting the console object methods
15
15
  */
16
16
  emptyLogger,
17
17
 
18
- /**
19
- * @deprecated
20
- * For backwards compatibility with the `transport` configuration.
21
- */
22
- emptyTransport: emptyLogger,
23
-
24
18
  /**
25
19
  * A function to generate a pause in promise chaining
26
20
  * @param {number} timer How much ws to wait