rascal 20.0.0 → 20.1.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Change Log
2
2
 
3
+ ## 20.1.1
4
+ - npm search algorithm has changed. Updating metadata accordingly.
5
+
6
+ ## 20.1.0
7
+ - Ignore and remove immediateNack header based on the xDeath header. See https://github.com/onebeyond/rascal/issues/237
8
+
3
9
  ## 20.0.0
4
10
  - Replaced superagent with native node http client as per https://github.com/onebeyond/rascal/issues/234
5
11
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Rascal
1
+ # Rascal - A RabbitMQ / AMQP Client
2
2
 
3
- Rascal is a rich pub/sub wrapper around [amqplib](https://www.npmjs.com/package/amqplib).
3
+ Rascal is an advanced RabbitMQ / AMQP client built on [amqplib](https://www.npmjs.com/package/amqplib).
4
4
 
5
5
  [![NPM version](https://img.shields.io/npm/v/rascal.svg?style=flat-square)](https://www.npmjs.com/package/rascal)
6
6
  [![NPM downloads](https://img.shields.io/npm/dm/rascal.svg?style=flat-square)](https://www.npmjs.com/package/rascal)
@@ -12,7 +12,7 @@ Rascal is a rich pub/sub wrapper around [amqplib](https://www.npmjs.com/package/
12
12
 
13
13
  ## About
14
14
 
15
- Rascal is a rich pub/sub wrapper for the excellent [amqplib](https://www.npmjs.com/package/amqplib). One of the best things about amqplib is that it doesn't make assumptions about how you use it. Another is that it doesn't attempt to abstract away [AMQP Concepts](https://www.rabbitmq.com/tutorials/amqp-concepts.html). As a result the library offers a great deal of control and flexibility, but the onus is on you adopt appropriate patterns and configuration. You need to be aware that:
15
+ Rascal is an advanced RabbitMQ / AMQP client built on [amqplib](https://www.npmjs.com/package/amqplib). One of the best things about amqplib is that it doesn't make assumptions about how you use it. Another is that it doesn't attempt to abstract away [AMQP Concepts](https://www.rabbitmq.com/tutorials/amqp-concepts.html). As a result the library offers a great deal of control and flexibility, but the onus is on you adopt appropriate patterns and configuration. You need to be aware that:
16
16
 
17
17
  - Messages are not persistent by default and will be lost if your broker restarts
18
18
  - Messages that crash your app will be infinitely retried
@@ -601,7 +601,7 @@ Running automated tests against shared queues and exchanges is problematic. Mess
601
601
  }
602
602
  ```
603
603
 
604
- If you specify `"namespace" :true` Rascal will prefix the queues and exchanges it creates with a uuid. Alternatively you can specify your own namespace, `"namespace": "foo"`. Namespaces are also if you want to use a single vhost locally but multiple vhosts in other environments.
604
+ If you specify `"namespace" :true` Rascal will prefix the queues and exchanges it creates with a uuid. Alternatively you can specify your own namespace, `"namespace": "foo"`. Namespaces may also be helpful if you want to use a single vhost locally but multiple vhosts in other environments.
605
605
 
606
606
  #### Exchanges
607
607
 
@@ -1534,7 +1534,7 @@ As mentioned previously, dead lettering invalid messages is a good strategy with
1534
1534
  ackOrNack(err, { strategy: 'republish', immediateNack: true });
1535
1535
  ```
1536
1536
 
1537
- If you ever want to resend the message to the same queue you will have to remove the `properties.headers.rascal.<queue>.immediateNack` header first.
1537
+ Prior to Rascal v20.1.0, if you wanted to resend the message to the original queue you had to remove the `properties.headers.rascal.<queue>.immediateNack` header first. From v20.1.0, Rascal will ignore and remove the immediateNack header if the message's xDeath header indicates that the message was dead lettered after it was republished with immediateNack.
1538
1538
 
1539
1539
  ##### Forward
1540
1540
 
@@ -3,10 +3,11 @@ const format = require('util').format;
3
3
  const _ = require('lodash');
4
4
  const async = require('async');
5
5
  const setTimeoutUnref = require('../utils/setTimeoutUnref');
6
+ const { EMPTY_X_DEATH } = require('./XDeath');
6
7
 
7
8
  module.exports = function SubscriptionRecovery(broker, vhost) {
8
9
  this.handle = function (session, message, err, recoveryOptions, next) {
9
- debug('Handling subscriber error for message: %s', message.properties.messageId);
10
+ debug('Handling subscriber error for message: %s with error: %s', message.properties.messageId, err.message);
10
11
 
11
12
  async.eachSeries(
12
13
  [].concat(recoveryOptions || []).concat({ strategy: 'fallback-nack' }),
@@ -84,7 +85,11 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
84
85
  _.set(publishOptions, 'headers.rascal.error.code', err.code);
85
86
  _.set(publishOptions, 'headers.rascal.restoreRoutingHeaders', _.has(strategyConfig, 'restoreRoutingHeaders') ? strategyConfig.restoreRoutingHeaders : true);
86
87
 
87
- if (strategyConfig.immediateNack) _.set(publishOptions, ['headers', 'rascal', 'recovery', originalQueue, 'immediateNack'], true);
88
+ if (strategyConfig.immediateNack) {
89
+ const xDeathRecords = message.properties.headers['x-death'] || [];
90
+ const xDeath = xDeathRecords.find(({ queue, reason }) => queue === originalQueue && reason === 'rejected') || EMPTY_X_DEATH;
91
+ _.set(publishOptions, ['headers', 'rascal', 'recovery', originalQueue], { immediateNack: true, xDeath });
92
+ }
88
93
 
89
94
  const ackMessage = () => {
90
95
  session._ack(message, (err) => {
@@ -8,6 +8,7 @@ const SubscriberSession = require('./SubscriberSession');
8
8
  const SubscriberError = require('./SubscriberError');
9
9
  const backoff = require('../backoff');
10
10
  const setTimeoutUnref = require('../utils/setTimeoutUnref');
11
+ const { EMPTY_X_DEATH } = require('./XDeath');
11
12
 
12
13
  module.exports = {
13
14
  create(broker, vhost, counter, config, next) {
@@ -95,7 +96,11 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
95
96
  session._incrementUnacknowledgeMessageCount(message.fields.consumerTag);
96
97
 
97
98
  decorateWithRoutingHeaders(message);
98
- if (immediateNack(message)) return ackOrNack(session, message, true);
99
+ if (immediateNack(message)) {
100
+ debug('Immediately nacking message: %s from queue: %s', message.properties.messageId, config.queue);
101
+ ackOrNack(session, message, new Error('Immediate nack'));
102
+ return;
103
+ }
99
104
 
100
105
  decorateWithRedeliveries(message, (err) => {
101
106
  if (err) return handleRedeliveriesError(err, session, message);
@@ -208,8 +213,24 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
208
213
  }
209
214
 
210
215
  function immediateNack(message) {
211
- if (_.get(message, ['properties', 'headers', 'rascal', 'recovery', message.properties.headers.rascal.originalQueue, 'immediateNack'])) return true;
212
- return false;
216
+ const originalQueue = message.properties.headers.rascal.originalQueue;
217
+ const xDeathRecords = message.properties.headers['x-death'] || [];
218
+ const currentXDeath = xDeathRecords.find(({ queue, reason }) => queue === originalQueue && reason === 'rejected') || EMPTY_X_DEATH;
219
+ const previousXDeath = _.get(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'xDeath'], EMPTY_X_DEATH);
220
+ const hasImmediateNackHeader = _.has(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'immediateNack']);
221
+ if (!hasImmediateNackHeader) return false;
222
+ debug('Message %s has been marked for immediate nack. Previous xDeath is %o. Current xDeath is %o.', message.properties.messageId, previousXDeath, currentXDeath);
223
+ // See https://github.com/rabbitmq/rabbitmq-server/issues/11331
224
+ // RabbitMQ v3.13 stopped updating the xDeath record's count property.
225
+ // RabbitMQ v3.12 does not update the xDeath record's time property.
226
+ // Therefore having test them both
227
+ if (currentXDeath.count > previousXDeath.count || currentXDeath.time.value > previousXDeath.time.value) {
228
+ debug('Message %s has been replayed after being dead lettered. Removing immediate nack.', message.properties.messageId);
229
+ _.unset(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'immediateNack']);
230
+ _.unset(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'xDeath']);
231
+ return false;
232
+ }
233
+ return true;
213
234
  }
214
235
 
215
236
  function getAckOrNack(session, message) {
@@ -0,0 +1,5 @@
1
+ const EMPTY_X_DEATH = { count: 0, time: { value: 0 } };
2
+
3
+ module.exports = {
4
+ EMPTY_X_DEATH,
5
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rascal",
3
- "version": "20.0.0",
4
- "description": "A config driven wrapper for amqplib supporting multi-host connections, automatic error recovery, redelivery flood protection, transparent encryption / decryption, channel pooling and publication timeouts",
3
+ "version": "20.1.1",
4
+ "description": "An advanced RabbitMQ / AMQP client built on amqplib",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
7
  "async": "^3.2.4",
@@ -40,20 +40,17 @@
40
40
  "lint:fix": "eslint --fix .",
41
41
  "lint-staged": "lint-staged",
42
42
  "coverage": "nyc --report html --reporter lcov --reporter text-summary zUnit",
43
- "docker": "docker run -d --name rascal-rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management",
43
+ "docker": "docker run -d --name rascal-rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12.9-management-alpine",
44
44
  "prepare": "husky install"
45
45
  },
46
46
  "lint-staged": {
47
47
  "**/*.js": "eslint --fix"
48
48
  },
49
49
  "keywords": [
50
- "amqplib",
51
- "amqp",
52
50
  "rabbitmq",
53
- "callback",
54
- "promise",
55
- "await",
56
- "async"
51
+ "rabbit",
52
+ "amqplib",
53
+ "amqp"
57
54
  ],
58
55
  "repository": {
59
56
  "type": "git",