rascal 16.3.0 → 17.0.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.
Files changed (44) hide show
  1. package/.eslintrc.json +3 -2
  2. package/CHANGELOG.md +10 -1
  3. package/LICENSE.txt +19 -12
  4. package/README.md +49 -22
  5. package/examples/busy-publisher/config.json +0 -1
  6. package/examples/default-exchange/config.json +0 -1
  7. package/examples/promises/config.json +3 -1
  8. package/index.js +1 -1
  9. package/lib/amqp/Broker.js +9 -8
  10. package/lib/amqp/BrokerAsPromised.js +5 -5
  11. package/lib/amqp/Publication.js +2 -1
  12. package/lib/amqp/SubscriberError.js +2 -2
  13. package/lib/amqp/SubscriberSession.js +25 -7
  14. package/lib/amqp/SubscriberSessionAsPromised.js +9 -0
  15. package/lib/amqp/Subscription.js +31 -21
  16. package/lib/amqp/Vhost.js +7 -9
  17. package/lib/amqp/tasks/applyBindings.js +1 -1
  18. package/lib/amqp/tasks/assertExchanges.js +1 -1
  19. package/lib/amqp/tasks/assertQueues.js +1 -1
  20. package/lib/amqp/tasks/assertVhost.js +1 -1
  21. package/lib/amqp/tasks/checkExchanges.js +1 -1
  22. package/lib/amqp/tasks/checkQueues.js +1 -1
  23. package/lib/amqp/tasks/checkVhost.js +1 -1
  24. package/lib/amqp/tasks/closeChannels.js +1 -1
  25. package/lib/amqp/tasks/createChannels.js +1 -1
  26. package/lib/amqp/tasks/createConnection.js +1 -1
  27. package/lib/amqp/tasks/deleteExchanges.js +1 -1
  28. package/lib/amqp/tasks/deleteQueues.js +1 -1
  29. package/lib/amqp/tasks/deleteVhost.js +1 -1
  30. package/lib/amqp/tasks/initCounters.js +1 -1
  31. package/lib/amqp/tasks/initPublications.js +1 -1
  32. package/lib/amqp/tasks/initShovels.js +1 -1
  33. package/lib/amqp/tasks/initSubscriptions.js +1 -1
  34. package/lib/amqp/tasks/initVhosts.js +1 -1
  35. package/lib/amqp/tasks/purgeQueues.js +1 -1
  36. package/lib/config/configure.js +71 -31
  37. package/lib/config/schema.json +4 -0
  38. package/lib/config/tests.js +1 -1
  39. package/lib/config/validate.js +1 -0
  40. package/lib/counters/inMemoryCluster.js +1 -1
  41. package/lib/management/Client.js +6 -6
  42. package/package.json +4 -9
  43. package/.prettierrc.json +0 -4
  44. package/dependabot.yml +0 -6
package/.eslintrc.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
- "extends": ["eslint-config-airbnb-base", "prettier"],
2
+ "extends": ["eslint-config-airbnb-base"],
3
3
  "env": {
4
4
  "node": true
5
5
  },
6
6
  "parserOptions": {
7
- "ecmaVersion": "2015"
7
+ "ecmaVersion": "2018"
8
8
  },
9
9
  "rules": {
10
10
  "arrow-body-style": 0,
11
11
  "consistent-return": 0,
12
12
  "func-names": 0,
13
+ "max-len": 0,
13
14
  "no-param-reassign": 0,
14
15
  "no-plusplus": 0,
15
16
  "no-restricted-properties": 0,
package/CHANGELOG.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # Change Log
2
2
 
3
- ## 16.3.0 Unreleased
3
+ ## 17.0.0
4
+
5
+ - Updated the configuration processing as per https://github.com/guidesmiths/rascal/issues/219
6
+ - Test on Node 20
7
+ - Abandon dependabot (too noisy)
8
+ - Bump deps
9
+ - Add support for channel level prefetch as per https://github.com/guidesmiths/rascal/issues/221
10
+ - Dropped prettier (it does nothing of the sort!)
11
+
12
+ ## 16.3.0
4
13
 
5
14
  - Simplify logic which determines whether ackOrNack should be a callback or a promise
6
15
  - Report repeated calls to ackOrNack
package/LICENSE.txt CHANGED
@@ -1,15 +1,22 @@
1
- ISC License
1
+ MIT License
2
2
 
3
- Copyright (c) 2016, GuideSmiths Ltd.
3
+ Copyright (c) 2016-2022 GuideSmiths Ltd.
4
+ Copyright (c) 2022-present One Beyond
4
5
 
5
- Permission to use, copy, modify, and/or distribute this software for any
6
- purpose with or without fee is hereby granted, provided that the above
7
- copyright notice and this permission notice appear in all copies.
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
8
12
 
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14
- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- PERFORMANCE OF THIS SOFTWARE.
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -7,7 +7,6 @@ Rascal is a rich pub/sub wrapper around [amqplib](https://www.npmjs.com/package/
7
7
  [![Node.js CI](https://github.com/guidesmiths/rascal/workflows/Node.js%20CI/badge.svg)](https://github.com/guidesmiths/rascal/actions?query=workflow%3A%22Node.js+CI%22)
8
8
  [![Code Climate](https://codeclimate.com/github/guidesmiths/rascal/badges/gpa.svg)](https://codeclimate.com/github/guidesmiths/rascal)
9
9
  [![Test Coverage](https://codeclimate.com/github/guidesmiths/rascal/badges/coverage.svg)](https://codeclimate.com/github/guidesmiths/rascal/coverage)
10
- [![Code Style](https://img.shields.io/badge/code%20style-prettier-brightgreen.svg)](https://github.com/prettier/prettier)
11
10
  [![rascal](https://snyk.io/advisor/npm-package/rascal/badge.svg)](https://snyk.io/advisor/npm-package/rascal)
12
11
  [![Discover zUnit](https://img.shields.io/badge/Discover-zUnit-brightgreen)](https://www.npmjs.com/package/zunit)
13
12
 
@@ -46,11 +45,11 @@ Rascal extends the existing [RabbitMQ Concepts](https://www.rabbitmq.com/tutoria
46
45
  1. Publications
47
46
  1. Subscriptions
48
47
 
49
- A **publication** is a named configuration for publishing a message, including the destination queue or exchange, routing configuration, encryption profile and reliability guarantees, message options, etc. A **subscription** is a named configuration for consuming messages, including the source queue, encryption profile, content encoding, delivery options (e.g. acknowledgement handling and prefetch), etc. These must be [configured](#configuration) and supplied when creating the Rascal broker. After the broker has been created the subscriptions and publications can be retrivied from the broker and used to publish and consume messages.
48
+ A **publication** is a named configuration for publishing a message, including the destination queue or exchange, routing configuration, encryption profile and reliability guarantees, message options, etc. A **subscription** is a named configuration for consuming messages, including the source queue, encryption profile, content encoding, delivery options (e.g. acknowledgement handling and prefetch), etc. These must be [configured](#configuration) and supplied when creating the Rascal broker. After the broker has been created the subscriptions and publications can be retrieved from the broker and used to publish and consume messages.
50
49
 
51
50
  ### Breaking Changes in Rascal@14
52
51
 
53
- Rascal@14 waits for inflight messages to be acknowledged before closing subscriber channels. Prior to this version Rascal just waited an arbitary amount of time. If your application does not acknowledge a message for some reason (quite likely in tests) calling `subscription.cancel`, `broker.unsubscribeAll`, `broker.bounce`, `broker.shutdown` or `broker.nuke` will wait indefinitely. You can specify a `closeTimeout` in your subscription config, however if this is exceeded the `subscription.cancel` and `broker.unsubscribeAll` methods will yield an error via callback or rejection, while the `broker.bounce`, `broker.shutdown` and `broker.nuke` methods will emit an error event, but attempt to continue. In both cases the error will have a code of `ETIMEDOUT`.
52
+ Rascal@14 waits for inflight messages to be acknowledged before closing subscriber channels. Prior to this version Rascal just waited an arbitrary amount of time. If your application does not acknowledge a message for some reason (quite likely in tests) calling `subscription.cancel`, `broker.unsubscribeAll`, `broker.bounce`, `broker.shutdown` or `broker.nuke` will wait indefinitely. You can specify a `closeTimeout` in your subscription config, however if this is exceeded the `subscription.cancel` and `broker.unsubscribeAll` methods will yield an error via callback or rejection, while the `broker.bounce`, `broker.shutdown` and `broker.nuke` methods will emit an error event, but attempt to continue. In both cases the error will have a code of `ETIMEDOUT`.
54
53
 
55
54
  ### Special Note
56
55
 
@@ -225,7 +224,7 @@ The reason Rascal nacks the message is because the alternatives are to leave the
225
224
 
226
225
  #### vhost_initialised
227
226
 
228
- The broker emits the `vhost_initialised` event after recovering from a connection error. An object containing the vhost name and connection url (with obfuscated password) are passed to he event handler. e.g.
227
+ The broker emits the `vhost_initialised` event after recovering from a connection error. An object containing the vhost name and connection url (with obfuscated password) are passed to the event handler. e.g.
229
228
 
230
229
  ```js
231
230
  broker.on('vhost_initialised', ({ vhost, connectionUrl }) => {
@@ -565,7 +564,7 @@ Rascal useds pools channels it uses for publishing messages. It creates two pool
565
564
  }
566
565
  ```
567
566
 
568
- Unfortunately there is a [bug](https://github.com/coopernurse/node-pool/issues/197#issuecomment-477862861) in generic-pool's implementation, which means that if the pool fails to create a channel, it can enter a tight loop, thrashing your CPU and potentially crashing your node process due to a memory leak. While we assess the long term use of pooling, we have put in a workaround. Errors will only be rejected after a configurable delay. This defaults to one second but can be overriden through the `rejectionDelayMillis` pool attribute. Special thanks to @willthrom for helping diagnose and fix this issue.
567
+ Unfortunately there is a [bug](https://github.com/coopernurse/node-pool/issues/197#issuecomment-477862861) in generic-pool's implementation, which means that if the pool fails to create a channel, it can enter a tight loop, thrashing your CPU and potentially crashing your node process due to a memory leak. While we assess the long term use of pooling, we have put in a workaround. Errors will only be rejected after a configurable delay. This defaults to one second but can be overridden through the `rejectionDelayMillis` pool attribute. Special thanks to @willthrom for helping diagnose and fix this issue.
569
568
 
570
569
  #### Flow Control
571
570
 
@@ -626,7 +625,7 @@ If you don't want to create exchanges on initialisation, but still want to valid
626
625
 
627
626
  ##### type
628
627
 
629
- Declares the exchange type. Must be one of direct, topic, headers or fanout. The default configuration sets the exchange type to "topic" unless overriden.
628
+ Declares the exchange type. Must be one of direct, topic, headers or fanout. The default configuration sets the exchange type to "topic" unless overridden.
630
629
 
631
630
  ##### options
632
631
 
@@ -696,7 +695,7 @@ Enable to purge the queue during initialisation. Useful when running automated t
696
695
 
697
696
  ##### replyTo
698
697
 
699
- Sometimes you want to publish a message, and have the consumer of the message send a reply to the same application instance that published the original message. This can be difficult if you application is deployed using multiple instances which share a common configuration. Quite often the solution is to make your application stateless so it doesn't matter which instance receives the reply. An altnernative is to mark the queue as a reply queue using the replyTo.
698
+ Sometimes you want to publish a message, and have the consumer of the message send a reply to the same application instance that published the original message. This can be difficult if you application is deployed using multiple instances which share a common configuration. Quite often the solution is to make your application stateless so it doesn't matter which instance receives the reply. An alternative is to mark the queue as a reply queue using the replyTo.
700
699
 
701
700
  ```json
702
701
  {
@@ -712,7 +711,7 @@ Sometimes you want to publish a message, and have the consumer of the message se
712
711
  }
713
712
  ```
714
713
 
715
- When true, Rascal will append a uuid to the queue name so that it is unique for each instance of the application. Use this conjunction with the publication replyTo property, to automaticlaly set the replyTo property on outbound messages to the unique queue name. You may also want to make make the queue non durable and exclusive too (see below).
714
+ When true, Rascal will append a uuid to the queue name so that it is unique for each instance of the application. Use this conjunction with the publication replyTo property, to automatically set the replyTo property on outbound messages to the unique queue name. You may also want to make the queue non durable and exclusive too (see below).
716
715
 
717
716
  ##### options
718
717
 
@@ -946,7 +945,7 @@ try {
946
945
  }
947
946
  ```
948
947
 
949
- One publish option you should be aware of is the "persistent". Unless persistent is true, your messages will be discarded when you restart Rabbit. Despite having an impact on performance Rascal sets this in it's default configuration.
948
+ One publish option you should be aware of is the "persistent". Unless persistent is true, your messages will be discarded when you restart Rabbit. Despite having an impact on performance Rascal sets this in its default configuration.
950
949
 
951
950
  Refer to the [amqplib](https://www.squaremobius.net/amqp.node/channel_api.html) documentation for further exchange options.
952
951
 
@@ -978,7 +977,7 @@ See the "default-exchange" in the examples directory for a full working example.
978
977
 
979
978
  #### Timeouts
980
979
 
981
- When you publish a message using a confirm channel, amqplib will wait for an acknowledgement that the message was safely received by the broker, and in a clustered environment replicated to all nodes. If something goes wrong, the broker will not send the acknowledgement, amqplib will never execute the callback, and the associated flow of execution will never be resumed. Rascal guards against this by adding publication timeouts. If the timeout expires, then Rascal will close the channel and emit a error event from the publication, however there will still be an unavoidable memory leak as amqplib's callback will never be cleared up. The default timeout is 10 seconds but can be overriden in config. The setting is ignored for normal channels and can be disabled by specifying 0.
980
+ When you publish a message using a confirm channel, amqplib will wait for an acknowledgement that the message was safely received by the broker, and in a clustered environment replicated to all nodes. If something goes wrong, the broker will not send the acknowledgement, amqplib will never execute the callback, and the associated flow of execution will never be resumed. Rascal guards against this by adding publication timeouts. If the timeout expires, then Rascal will close the channel and emit a error event from the publication, however there will still be an unavoidable memory leak as amqplib's callback will never be cleared up. The default timeout is 10 seconds but can be overridden in config. The setting is ignored for normal channels and can be disabled by specifying 0.
982
981
 
983
982
  ```json
984
983
  {
@@ -1106,7 +1105,7 @@ try {
1106
1105
 
1107
1106
  Prior to version 10.0.0, if you used Rascal to consume a forwarded message, the subscriber would automatically restore the original routing key and exchange to the message.fields before emitting it. This was added to support the delayed retry loop advanced recovery strategy, but should not have been applied to `broker.forward`. From version 10.0.0 this behaviour has been disabled for `broker.forward` but you can turn it back on by setting `restoreRoutingHeaders` to true in the overrides. You can also disable this behaviour in the `forward` and `republish` recovery strategies by setting `restoreRoutingHeaders` to false.
1108
1107
 
1109
- **Since there is no native, transactional support for forwarding in amqplib, you are at risk of receiving duplicate messages when using `broker.foward`**
1108
+ **Since there is no native, transactional support for forwarding in amqplib, you are at risk of receiving duplicate messages when using `broker.forward`**
1110
1109
 
1111
1110
  ### Subscriptions
1112
1111
 
@@ -1153,7 +1152,7 @@ try {
1153
1152
 
1154
1153
  It's **very** important that you handle errors emitted by the subscriber. If not an underlying channel error will bubble up to the uncaught error handler and crash your node process.
1155
1154
 
1156
- Prior to Rascal 4.0.0 it was also **very** important not to go async between getting the subscription and listening for the message or error events. If you did, you risked leaking messages and not handling errors. For Rascal 4.0.0 and beyond, subsciptions are lazily applied when you add the `message` handller. Because registering event handlers is synchronous, but setting up RabbitMQ consumers is asynchronous, we've also added the `subscribed` event in case you need to wait until the subscription has been successfully established.
1155
+ Prior to Rascal 4.0.0 it was also **very** important not to go async between getting the subscription and listening for the message or error events. If you did, you risked leaking messages and not handling errors. For Rascal 4.0.0 and beyond, subscriptions are lazily applied when you add the `message` handler. Because registering event handlers is synchronous, but setting up RabbitMQ consumers is asynchronous, we've also added the `subscribed` event in case you need to wait until the subscription has been successfully established.
1157
1156
 
1158
1157
  Rascal supports text, buffers and anything it can JSON.parse, providing the contentType message property is set correctly. Text messages should be set to "text/plain" and JSON messages to "application/json". Other content types will be returned as a Buffer. If the publisher doesn't set the contentType or you want to override it you can do so in the subscriber configuration.
1159
1158
 
@@ -1416,7 +1415,7 @@ For messages which are not auto-acknowledged (the default) calling `ackOrNack()`
1416
1415
 
1417
1416
  When using the callback API, you can call ackOrNack without a callback and errors will be emitted by the subscription. Alternatively you can specify a callback as the final argument irrespective of what other arguments you provide.
1418
1417
 
1419
- When using the promises API, ackOrNack will work as for the callback API unless you explicity set promisifyAckOrNack to true on the subscription. If you do enable this feature, be sure to catch rejections.
1418
+ When using the promises API, ackOrNack will work as for the callback API unless you explicitly set `promisifyAckOrNack` to true on the subscription. If you do enable this feature, be sure to catch rejections.
1420
1419
 
1421
1420
  ##### Nack (Reject or Dead Letter)
1422
1421
 
@@ -1427,7 +1426,7 @@ ackOrNack(err, { strategy: 'nack' });
1427
1426
  Nack causes the message to be discarded or routed to a dead letter exchange if configured. You can also negatively acknowledge all outstanding messages on a channel as follows
1428
1427
 
1429
1428
  ```js
1430
- ackOrNac(err, { strategy: 'nack', all: true });
1429
+ ackOrNack(err, { strategy: 'nack', all: true });
1431
1430
  ```
1432
1431
 
1433
1432
  ##### Nack with Requeue
@@ -1436,7 +1435,7 @@ ackOrNac(err, { strategy: 'nack', all: true });
1436
1435
  ackOrNack(err, { strategy: 'nack', defer: 1000, requeue: true });
1437
1436
  ```
1438
1437
 
1439
- The defer option is not mandatory, but without it you are likely retry your message thousands of times a second. Even then requeueing is a inadequate strategy for error handling, since the message will be rolled back to the front of the queue and there is no simple way to detect how many times the message has been redelivered.
1438
+ The defer option is not mandatory, but without it you are likely retry your message thousands of times a second. Even then requeuing is an inadequate strategy for error handling, since the message will be rolled back to the front of the queue and there is no simple way to detect how many times the message has been redelivered.
1440
1439
 
1441
1440
  Dead lettering is a good option for invalid messages but with one major flaw - because the message cannot be modified it cannot be annotated with the error details. This makes it difficult to do anything useful with messages once dead lettered.
1442
1441
 
@@ -1485,7 +1484,7 @@ ackOrNack(err, { strategy: 'forward', publication: 'some_exchange' });
1485
1484
  ```
1486
1485
 
1487
1486
  **Danger**
1488
- As with the Republish strategy, you can limit the number of foward attempts. Whenever you specify a number of attempts you should always chain a fallback strategy, otherwise if the attempts are exceeded your message will be neither acked or nacked.
1487
+ As with the Republish strategy, you can limit the number of forward attempts. Whenever you specify a number of attempts you should always chain a fallback strategy, otherwise if the attempts are exceeded your message will be neither acked or nacked.
1489
1488
 
1490
1489
  Furthermore if the message is forwarded but cannot be routed (e.g. due to an incorrect binding), the message will be returned **after** Rascal receives a 'success' event from amqplib. Consequently the message will have been ack'd. Any subsequent fallback strategy which attempts to ack or nack the message will fail, and so the message may lost. The subscription will emit an error event under such circumstances.
1491
1490
 
@@ -1526,7 +1525,7 @@ ackOrNack(err, { strategy: 'ack' });
1526
1525
 
1527
1526
  #### Chaining Recovery Strategies
1528
1527
 
1529
- By chaining Rascal's recovery strategies and leveraging some of RabbitMQs lesser used features such as message you can achieve some quite sophisticated error handling. A simple combination of republish and nack (with dead letter) will enable you to retry the message a maximum number of times before dead letting it.
1528
+ By chaining Rascal's recovery strategies and leveraging some of RabbitMQ's lesser used features such as message you can achieve some quite sophisticated error handling. A simple combination of republish and nack (with dead letter) will enable you to retry the message a maximum number of times before dead letting it.
1530
1529
 
1531
1530
  ```js
1532
1531
  ackOrNack(err, [
@@ -1559,7 +1558,35 @@ Steps 3 - 7 will repeat up to `attempts` times. If all attempts fail...
1559
1558
 
1560
1559
  #### prefetch
1561
1560
 
1562
- Prefetch limits the number of unacknowledged messages your application can have outstanding. It's a great way to ensure that you don't overload your event loop or a downstream service. Rascal's default configuration sets the prefetch to 10 which may seem low, but we've managed to knock out firewalls, breach AWS thresholds and all sorts of other things by setting it to higher values.
1561
+ Prefetch limits the number of unacknowledged messages a subscription can have outstanding. It's a great way to ensure that you don't overload your event loop or a downstream service. Rascal's default configuration sets the prefetch to 10 which may seem low, but we've managed to knock out firewalls, breach AWS thresholds and all sorts of other things by setting it to higher values.
1562
+
1563
+ #### channelPrefetch
1564
+
1565
+ Channel prefetch is like prefetch but operates at a channel rather than a consumer level. Since Rascal uses a dedicated channel per subscriber there is rarely any point setting it. Moreover, a channel prefetch has a considerable overhead, especially in a clustered environment so can significantly impact performance. The only reason to set a channel prefetch is if you want to adjust the prefetch dynamically after the subscription has started, and without cancelling the subscription, e.g.
1566
+
1567
+ ```js
1568
+ broker.subscribe('s1', { prefetch: 0, channelPrefetch: 5 }, (err, subscription) => {
1569
+ if (err) throw err;
1570
+ subscription.on('message', (message, content, ackOrNack) => {
1571
+ ackOrNack();
1572
+ const prefetch = tunePrefetch();
1573
+ subscription.setChannelPrefetch(prefetch, (err) => {
1574
+ if (err) throw err;
1575
+ });
1576
+ });
1577
+ });
1578
+ ```
1579
+
1580
+ ```js
1581
+ const subscription = await broker.subscribe('s1', { prefetch: 0, channelPrefetch: 5 });
1582
+ subscription.on('message', (message, content, ackOrNack) => {
1583
+ ackOrNack();
1584
+ const prefetch = tunePrefetch();
1585
+ await subscription.setChannelPrefetch(prefetch);
1586
+ });
1587
+ ```
1588
+
1589
+ Don't forget to zero the regular consumer prefetch when specifying a channelPrefetch to prevent potential conflicts.
1563
1590
 
1564
1591
  #### retry
1565
1592
 
@@ -1643,7 +1670,7 @@ You can cancel subscriptions as follows
1643
1670
  broker.subscribe('s1', (err, subscription) => {
1644
1671
  if (err) throw err; // subscription didn't exist
1645
1672
  subscription.cancel((err) => {
1646
- console.err(err);
1673
+ console.error(err);
1647
1674
  });
1648
1675
  });
1649
1676
  ```
@@ -1657,7 +1684,7 @@ try {
1657
1684
  }
1658
1685
  ```
1659
1686
 
1660
- Cancelling a subscribion will stop consuming messages, but leave the channel open until any outstanding messages have been acknowledged, or the timeout specified by through the `closeTimeout` subscription property is exceeded.
1687
+ Cancelling a subscription will stop consuming messages, but leave the channel open until any outstanding messages have been acknowledged, or the timeout specified by through the `closeTimeout` subscription property is exceeded.
1661
1688
 
1662
1689
  ## Shutdown
1663
1690
 
@@ -1793,7 +1820,7 @@ after(async () => {
1793
1820
 
1794
1821
  ### Bounce
1795
1822
 
1796
- Bounce disconnects and reinistialises the broker.
1823
+ Bounce disconnects and reinitialises the broker.
1797
1824
 
1798
1825
  ```js
1799
1826
  beforeEach((done) => {
@@ -1809,7 +1836,7 @@ beforeEach(async () => {
1809
1836
 
1810
1837
  ### Shovels
1811
1838
 
1812
- RabbitMQ enables you to transfer messages between brokers using the [Shovel plugin](https://www.rabbitmq.com/shovel.html). You can do something similar with rascal by connecting a subscription to a publication. Shovel relies on rascals 'forward' feature, so all the caveates about duplicate messages apply.
1839
+ RabbitMQ enables you to transfer messages between brokers using the [Shovel plugin](https://www.rabbitmq.com/shovel.html). You can do something similar with rascal by connecting a subscription to a publication. Shovel relies on rascals 'forward' feature, so all the caveats about duplicate messages apply.
1813
1840
 
1814
1841
  ```json
1815
1842
  {
@@ -12,7 +12,6 @@
12
12
  }
13
13
  },
14
14
  "connection": {
15
- "heartbeat": 5,
16
15
  "socketOptions": {
17
16
  "timeout": 1000
18
17
  }
@@ -3,7 +3,6 @@
3
3
  "vhosts": {
4
4
  "/": {
5
5
  "connection": {
6
- "heartbeat": 1,
7
6
  "socketOptions": {
8
7
  "timeout": 1000
9
8
  }
@@ -3,7 +3,9 @@
3
3
  "vhosts": {
4
4
  "/": {
5
5
  "connection": {
6
- "heartbeat": 1,
6
+ "options": {
7
+ "heartbeat": 5
8
+ },
7
9
  "socketOptions": {
8
10
  "timeout": 1000
9
11
  }
package/index.js CHANGED
@@ -21,4 +21,4 @@ module.exports = (function () {
21
21
  },
22
22
  counters,
23
23
  };
24
- })();
24
+ }());
@@ -81,7 +81,7 @@ function Broker(config, components) {
81
81
  if (err) return next(err);
82
82
  debug('Finished purging all queues in all vhosts');
83
83
  next();
84
- }
84
+ },
85
85
  );
86
86
  };
87
87
 
@@ -106,10 +106,10 @@ function Broker(config, components) {
106
106
  clearInterval(self.keepActive);
107
107
  debug('Finished shutting down broker');
108
108
  next();
109
- }
109
+ },
110
110
  );
111
111
  });
112
- }
112
+ },
113
113
  );
114
114
  };
115
115
 
@@ -126,7 +126,7 @@ function Broker(config, components) {
126
126
  if (err) return next(err);
127
127
  debug('Finished bouncing broker');
128
128
  next();
129
- }
129
+ },
130
130
  );
131
131
  });
132
132
  };
@@ -148,7 +148,7 @@ function Broker(config, components) {
148
148
  clearInterval(self.keepActive);
149
149
  debug('Finished nuking broker');
150
150
  next();
151
- }
151
+ },
152
152
  );
153
153
  });
154
154
  };
@@ -178,10 +178,11 @@ function Broker(config, components) {
178
178
  };
179
179
 
180
180
  this.subscribeAll = function (filter, next) {
181
- if (arguments.length === 1)
181
+ if (arguments.length === 1) {
182
182
  return self.subscribeAll(() => {
183
183
  return true;
184
184
  }, arguments[0]);
185
+ }
185
186
  const filteredSubscriptions = _.chain(config.subscriptions).values().filter(filter).value();
186
187
  async.mapSeries(
187
188
  filteredSubscriptions,
@@ -191,7 +192,7 @@ function Broker(config, components) {
191
192
  cb(null, subscription);
192
193
  });
193
194
  },
194
- next
195
+ next,
195
196
  );
196
197
  };
197
198
 
@@ -202,7 +203,7 @@ function Broker(config, components) {
202
203
  sessions.shift();
203
204
  session.cancel(cb);
204
205
  },
205
- next
206
+ next,
206
207
  );
207
208
  };
208
209
 
@@ -21,7 +21,7 @@ module.exports = {
21
21
  value: brokerAsPromised,
22
22
  });
23
23
  return reject(err);
24
- })
24
+ }),
25
25
  );
26
26
  });
27
27
  },
@@ -43,7 +43,7 @@ function BrokerAsPromised(broker) {
43
43
  ...args.concat((err, result) => {
44
44
  if (err) return reject(err);
45
45
  resolve(result);
46
- })
46
+ }),
47
47
  );
48
48
  });
49
49
  };
@@ -59,7 +59,7 @@ function BrokerAsPromised(broker) {
59
59
  ...args.concat((err, session) => {
60
60
  if (err) return reject(err);
61
61
  resolve(new SubscriberSessionAsPromised(session));
62
- })
62
+ }),
63
63
  );
64
64
  });
65
65
  };
@@ -73,9 +73,9 @@ function BrokerAsPromised(broker) {
73
73
  resolve(
74
74
  sessions.map((session) => {
75
75
  return new SubscriberSessionAsPromised(session);
76
- })
76
+ }),
77
77
  );
78
- })
78
+ }),
79
79
  );
80
80
  });
81
81
  };
@@ -53,7 +53,8 @@ function Publication(vhost, borrowChannelFn, returnChannelFn, destroyChannelFn,
53
53
  _.set(publishConfig, 'options.headers.rascal.restoreRoutingHeaders', !!publishConfig.restoreRoutingHeaders);
54
54
  _.set(publishConfig, 'options.headers.rascal.originalExchange', message.fields.exchange);
55
55
  _.set(publishConfig, 'options.headers.rascal.originalRoutingKey', message.fields.routingKey);
56
- _.set(publishConfig, 'options.CC', _.chain([]).concat(publishConfig.options.CC, format('%s.%s', originalQueue, publishConfig.routingKey)).uniq().compact().value());
56
+ _.set(publishConfig, 'options.CC', _.chain([]).concat(publishConfig.options.CC, format('%s.%s', originalQueue, publishConfig.routingKey)).uniq().compact()
57
+ .value());
57
58
 
58
59
  _publish(message.content, publishConfig, next);
59
60
  };
@@ -31,7 +31,7 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
31
31
  });
32
32
  }, recoveryConfig.defer);
33
33
  },
34
- next
34
+ next,
35
35
  );
36
36
  };
37
37
 
@@ -178,7 +178,7 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
178
178
  },
179
179
  },
180
180
  ],
181
- 'name'
181
+ 'name',
182
182
  );
183
183
 
184
184
  function getStrategy(recoveryConfig) {
@@ -26,7 +26,9 @@ function SubscriberSession(sequentialChannelOperations, config) {
26
26
  this._open = function (channel, consumerTag, next) {
27
27
  if (cancelled) return next(new Error('Subscriber has been cancelled'));
28
28
  debug('Opening subscriber session: %s on channel: %s', consumerTag, channel._rascal_id);
29
- channels[consumerTag] = { index: index++, channel, consumerTag, unacknowledgedMessages: 0 };
29
+ channels[consumerTag] = {
30
+ index: index++, channel, consumerTag, unacknowledgedMessages: 0,
31
+ };
30
32
  channel.once('close', unref.bind(null, consumerTag));
31
33
  channel.once('error', unref.bind(null, consumerTag));
32
34
  next();
@@ -40,6 +42,22 @@ function SubscriberSession(sequentialChannelOperations, config) {
40
42
  }, next);
41
43
  };
42
44
 
45
+ this.setChannelPrefetch = function (prefetch, next) {
46
+ sequentialChannelOperations.push((done) => {
47
+ config.channelPrefetch = prefetch;
48
+ withCurrentChannel(
49
+ (channel) => {
50
+ debug('Setting channel prefetch to %d on channel: %s', prefetch, channel._rascal_id);
51
+ channel.prefetch(prefetch, true, done);
52
+ },
53
+ () => {
54
+ debug('No current channel on which to set prefetch');
55
+ done();
56
+ },
57
+ );
58
+ }, next);
59
+ };
60
+
43
61
  this._close = function (next) {
44
62
  sequentialChannelOperations.push((done) => {
45
63
  self._unsafeClose(done);
@@ -63,9 +81,9 @@ function SubscriberSession(sequentialChannelOperations, config) {
63
81
  });
64
82
  },
65
83
  () => {
66
- debug('No current subscriber session');
84
+ debug('No current channel to close');
67
85
  next();
68
- }
86
+ },
69
87
  );
70
88
  };
71
89
 
@@ -116,7 +134,7 @@ function SubscriberSession(sequentialChannelOperations, config) {
116
134
  setImmediate(() => {
117
135
  next(new Error('The channel has been closed. Unable to ack message'));
118
136
  });
119
- }
137
+ },
120
138
  );
121
139
  };
122
140
 
@@ -133,7 +151,7 @@ function SubscriberSession(sequentialChannelOperations, config) {
133
151
  setImmediate(() => {
134
152
  next(new Error('The channel has been closed. Unable to ack messages'));
135
153
  });
136
- }
154
+ },
137
155
  );
138
156
  };
139
157
 
@@ -151,7 +169,7 @@ function SubscriberSession(sequentialChannelOperations, config) {
151
169
  setImmediate(() => {
152
170
  next(new Error('The channel has been closed. Unable to nack message'));
153
171
  });
154
- }
172
+ },
155
173
  );
156
174
  };
157
175
 
@@ -169,7 +187,7 @@ function SubscriberSession(sequentialChannelOperations, config) {
169
187
  setImmediate(() => {
170
188
  next(new Error('The channel has been closed. Unable to nack messages'));
171
189
  });
172
- }
190
+ },
173
191
  );
174
192
  };
175
193
 
@@ -20,4 +20,13 @@ function SubscriberSessionAsPromised(session) {
20
20
  });
21
21
  });
22
22
  };
23
+
24
+ this.setChannelPrefetch = function (prefetch) {
25
+ return new Promise((resolve, reject) => {
26
+ session.setChannelPrefetch(prefetch, (err) => {
27
+ if (err) return reject(err);
28
+ resolve();
29
+ });
30
+ });
31
+ };
23
32
  }
@@ -31,8 +31,9 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
31
31
  };
32
32
 
33
33
  this.subscribe = function (overrides, next) {
34
- const session = new SubscriberSession(sequentialChannelOperations, subscriptionConfig);
35
- subscribeLater(session, _.defaultsDeep(overrides, subscriptionConfig));
34
+ const config = _.defaultsDeep(overrides, subscriptionConfig);
35
+ const session = new SubscriberSession(sequentialChannelOperations, config);
36
+ subscribeLater(session, config);
36
37
  return next(null, session);
37
38
  };
38
39
 
@@ -57,27 +58,36 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
57
58
  if (err) return done(err);
58
59
  if (!channel) return done();
59
60
 
60
- if (config.prefetch) channel.prefetch(config.prefetch);
61
-
62
- const removeErrorHandlers = attachErrorHandlers(channel, session, config);
63
- const onMessage = _onMessage.bind(null, session, config, removeErrorHandlers);
64
-
65
- channel.consume(config.source, onMessage, config.options, (err, response) => {
66
- if (err) {
67
- debug('Error subscribing to %s using channel: %s. %s', config.source, channel._rascal_id, err.message);
68
- removeErrorHandlers();
69
- return done(err);
70
- }
71
- session._open(channel, response.consumerTag, (err) => {
72
- if (err) return done(err);
73
- timer.reset();
74
- done();
61
+ _configureQos(config, channel, (err) => {
62
+ if (err) return done(err);
63
+
64
+ const removeErrorHandlers = attachErrorHandlers(channel, session, config);
65
+ const onMessage = _onMessage.bind(null, session, config, removeErrorHandlers);
66
+
67
+ channel.consume(config.source, onMessage, config.options, (err, response) => {
68
+ if (err) {
69
+ debug('Error subscribing to %s using channel: %s. %s', config.source, channel._rascal_id, err.message);
70
+ removeErrorHandlers();
71
+ return done(err);
72
+ }
73
+ session._open(channel, response.consumerTag, (err) => {
74
+ if (err) return done(err);
75
+ timer.reset();
76
+ done();
77
+ });
75
78
  });
76
79
  });
77
80
  });
78
81
  }, next);
79
82
  }
80
83
 
84
+ function _configureQos(config, channel, next) {
85
+ const qos = [];
86
+ if (config.prefetch) qos.push((cb) => channel.prefetch(config.prefetch, false, cb));
87
+ if (config.channelPrefetch) qos.push((cb) => channel.prefetch(config.channelPrefetch, true, cb));
88
+ async.series(qos, next);
89
+ }
90
+
81
91
  function _onMessage(session, config, removeErrorHandlers, message) {
82
92
  if (!message) return handleConsumerCancel(session, config, removeErrorHandlers);
83
93
 
@@ -261,8 +271,8 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
261
271
  debug('Handling channel error: %s from %s using channel: %s', err.message, config.name, session._getRascalChannelId());
262
272
  if (removeErrorHandlers) removeErrorHandlers();
263
273
  session.emit('error', err);
264
- config.retry &&
265
- subscribeNow(session, config, (err) => {
274
+ config.retry
275
+ && subscribeNow(session, config, (err) => {
266
276
  if (!err) return;
267
277
  const delay = timer.next();
268
278
  debug('Will attempt resubscription(%d) to %s in %dms', attempts + 1, config.name, delay);
@@ -277,8 +287,8 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
277
287
  if (err) debug('Error cancelling subscription: %s', err.message);
278
288
  const cancelErr = new Error(format('Subscription: %s was cancelled by the broker', config.name));
279
289
  session.emit('cancelled', cancelErr) || session.emit('error', cancelErr);
280
- config.retry &&
281
- subscribeNow(session, config, (err) => {
290
+ config.retry
291
+ && subscribeNow(session, config, (err) => {
282
292
  if (!err) return;
283
293
  const delay = timer.next();
284
294
  debug('Will attempt resubscription(%d) to %s in %dms', 1, config.name, delay);
package/lib/amqp/Vhost.js CHANGED
@@ -447,15 +447,13 @@ function Vhost(vhostConfig, components) {
447
447
  }
448
448
 
449
449
  function ensureChannelPools() {
450
- regularChannelPool =
451
- regularChannelPool ||
452
- createChannelPool({
450
+ regularChannelPool = regularChannelPool
451
+ || createChannelPool({
453
452
  confirm: false,
454
453
  pool: vhostConfig.publicationChannelPools.regularPool,
455
454
  });
456
- confirmChannelPool =
457
- confirmChannelPool ||
458
- createChannelPool({
455
+ confirmChannelPool = confirmChannelPool
456
+ || createChannelPool({
459
457
  confirm: true,
460
458
  pool: vhostConfig.publicationChannelPools.confirmPool,
461
459
  });
@@ -471,7 +469,7 @@ function Vhost(vhostConfig, components) {
471
469
  confirmChannelPool ? confirmChannelPool.drain(cb) : cb();
472
470
  },
473
471
  ],
474
- next
472
+ next,
475
473
  );
476
474
  }
477
475
 
@@ -488,8 +486,8 @@ function Vhost(vhostConfig, components) {
488
486
  connection = undefined;
489
487
  self.emit('disconnect');
490
488
  self.emit('error', err, self.getConnectionDetails());
491
- connectionConfig.retry &&
492
- self.init((err) => {
489
+ connectionConfig.retry
490
+ && self.init((err) => {
493
491
  if (!err) return;
494
492
  const delay = timer.next();
495
493
  debug('Will attempt reconnection in in %dms', delay);
@@ -18,7 +18,7 @@ module.exports = _.curry((config, ctx, next) => {
18
18
  },
19
19
  (err) => {
20
20
  next(err, config, ctx);
21
- }
21
+ },
22
22
  );
23
23
  });
24
24
 
@@ -12,7 +12,7 @@ module.exports = _.curry((config, ctx, next) => {
12
12
  },
13
13
  (err) => {
14
14
  next(err, config, ctx);
15
- }
15
+ },
16
16
  );
17
17
  });
18
18
 
@@ -12,7 +12,7 @@ module.exports = _.curry((config, ctx, next) => {
12
12
  },
13
13
  (err) => {
14
14
  next(err, config, ctx);
15
- }
15
+ },
16
16
  );
17
17
  });
18
18
 
@@ -24,6 +24,6 @@ module.exports = _.curry((config, ctx, next) => {
24
24
  },
25
25
  (err) => {
26
26
  next(err, config, ctx);
27
- }
27
+ },
28
28
  );
29
29
  });
@@ -12,7 +12,7 @@ module.exports = _.curry((config, ctx, next) => {
12
12
  },
13
13
  (err) => {
14
14
  next(err, config, ctx);
15
- }
15
+ },
16
16
  );
17
17
  });
18
18
 
@@ -12,7 +12,7 @@ module.exports = _.curry((config, ctx, next) => {
12
12
  },
13
13
  (err) => {
14
14
  next(err, config, ctx);
15
- }
15
+ },
16
16
  );
17
17
  });
18
18
 
@@ -24,6 +24,6 @@ module.exports = _.curry((config, ctx, next) => {
24
24
  },
25
25
  (err) => {
26
26
  next(err, config, ctx);
27
- }
27
+ },
28
28
  );
29
29
  });
@@ -13,6 +13,6 @@ module.exports = _.curry((config, ctx, next) => {
13
13
  (err) => {
14
14
  delete ctx.channels;
15
15
  return next(err, config, ctx);
16
- }
16
+ },
17
17
  );
18
18
  });
@@ -14,6 +14,6 @@ module.exports = _.curry((config, ctx, next) => {
14
14
  if (err) return next(err, config, ctx);
15
15
  ctx.channels = channels;
16
16
  next(null, config, ctx);
17
- }
17
+ },
18
18
  );
19
19
  });
@@ -24,7 +24,7 @@ module.exports = _.curry((config, ctx, next) => {
24
24
  },
25
25
  (err) => {
26
26
  next(err, config, ctx);
27
- }
27
+ },
28
28
  );
29
29
  });
30
30
 
@@ -12,7 +12,7 @@ module.exports = _.curry((config, ctx, next) => {
12
12
  },
13
13
  (err) => {
14
14
  next(err, config, ctx);
15
- }
15
+ },
16
16
  );
17
17
  });
18
18
 
@@ -12,7 +12,7 @@ module.exports = _.curry((config, ctx, next) => {
12
12
  },
13
13
  (err) => {
14
14
  next(err, config, ctx);
15
- }
15
+ },
16
16
  );
17
17
  });
18
18
 
@@ -25,6 +25,6 @@ module.exports = _.curry((config, ctx, next) => {
25
25
  },
26
26
  (err) => {
27
27
  next(err, config, ctx);
28
- }
28
+ },
29
29
  );
30
30
  });
@@ -15,7 +15,7 @@ module.exports = _.curry((config, ctx, next) => {
15
15
  },
16
16
  (err) => {
17
17
  next(err, config, ctx);
18
- }
18
+ },
19
19
  );
20
20
  });
21
21
 
@@ -15,7 +15,7 @@ module.exports = _.curry((config, ctx, next) => {
15
15
  },
16
16
  (err) => {
17
17
  next(err, config, ctx);
18
- }
18
+ },
19
19
  );
20
20
  });
21
21
 
@@ -10,7 +10,7 @@ module.exports = _.curry((config, ctx, next) => {
10
10
  },
11
11
  (err) => {
12
12
  next(err, config, ctx);
13
- }
13
+ },
14
14
  );
15
15
  });
16
16
 
@@ -14,7 +14,7 @@ module.exports = _.curry((config, ctx, next) => {
14
14
  },
15
15
  (err) => {
16
16
  next(err, config, ctx);
17
- }
17
+ },
18
18
  );
19
19
  });
20
20
 
@@ -22,7 +22,7 @@ module.exports = _.curry((config, ctx, next) => {
22
22
  },
23
23
  (err) => {
24
24
  next(err, config, ctx);
25
- }
25
+ },
26
26
  );
27
27
  });
28
28
 
@@ -12,7 +12,7 @@ module.exports = _.curry((config, ctx, next) => {
12
12
  },
13
13
  (err) => {
14
14
  next(err, config, ctx);
15
- }
15
+ },
16
16
  );
17
17
  });
18
18
 
@@ -1,6 +1,7 @@
1
1
  const debug = require('debug')('rascal:config:configure');
2
2
  const format = require('util').format;
3
3
  const url = require('url');
4
+ const { URL } = require('node:url');
4
5
  const _ = require('lodash');
5
6
  const uuid = require('uuid').v4;
6
7
  const XRegExp = require('xregexp');
@@ -47,7 +48,7 @@ module.exports = _.curry((rascalConfig, next) => {
47
48
  connectionStrategy: rascalConfig.defaults.vhosts.connectionStrategy,
48
49
  publicationChannelPools: rascalConfig.defaults.vhosts.publicationChannelPools,
49
50
  },
50
- { defaults: rascalConfig.defaults.vhosts }
51
+ { defaults: rascalConfig.defaults.vhosts },
51
52
  );
52
53
  rascalConfig.vhosts[name].namespace = vhostConfig.namespace === true ? uuid() : vhostConfig.namespace;
53
54
  configureConnections(vhostConfig, name);
@@ -66,39 +67,77 @@ module.exports = _.curry((rascalConfig, next) => {
66
67
  _.each(vhostConfig.connections, (connection, index) => {
67
68
  configureConnection(vhostConfig, vhostName, connection, index);
68
69
  });
69
- vhostConfig.connections = _.sortBy(vhostConfig.connections, 'index');
70
+ vhostConfig.connections = _.sortBy(vhostConfig.connections, 'index').map((connection) => _.omit(connection, 'index'));
70
71
  delete vhostConfig.connection;
71
72
  }
72
73
 
73
74
  function configureConnection(vhostConfig, vhostName, connection, index) {
74
- _.defaultsDeep(connection, vhostConfig.defaults.connection);
75
- connection.vhost = connection.vhost !== undefined ? connection.vhost : vhostName;
76
- connection.auth = connection.auth || getAuth(connection.user, connection.password);
77
- connection.pathname = connection.vhost === '/' ? '' : connection.vhost;
78
- connection.query = connection.options;
79
- connection.url = connection.url || url.format(connection);
80
- connection.loggableUrl = connection.url.replace(/:[^:]*@/, ':***@');
81
- connection.index = getConnectionIndex(vhostConfig.connectionStrategy, connection, index);
75
+ const attributesFromUrl = parseConnectionUrl(connection.url);
76
+ const attributesFromConfig = getConnectionAttributes(connection);
77
+ const defaults = { ...vhostConfig.defaults.connection, vhost: vhostName };
78
+ const connectionAttributes = _.defaultsDeep({}, attributesFromUrl, attributesFromConfig, defaults);
79
+
80
+ setConnectionAttributes(connection, connectionAttributes);
81
+ setConnectionUrls(connection);
82
+ setConnectionIndex(connection, vhostConfig.connectionStrategy, index);
83
+
82
84
  configureManagementConnection(vhostConfig, vhostName, connection);
83
85
  }
84
86
 
87
+ function parseConnectionUrl(connectionString) {
88
+ if (!connectionString) return {};
89
+ const {
90
+ protocol, username: user, password, hostname, port, pathname: vhost, searchParams,
91
+ } = new URL(connectionString);
92
+ const options = Array.from(searchParams).reduce((attributes, entry) => ({ ...attributes, [entry[0]]: entry[1] }), {});
93
+ return {
94
+ protocol, hostname, port, user, password, vhost, options,
95
+ };
96
+ }
97
+
98
+ function getConnectionAttributes(attributes) {
99
+ const {
100
+ protocol, hostname, port, user, password, vhost, options, socketOptions, management,
101
+ } = attributes;
102
+ return {
103
+ protocol, hostname, port, user, password, vhost, options, socketOptions, management,
104
+ };
105
+ }
106
+
85
107
  function configureManagementConnection(vhostConfig, vhostName, connection) {
86
108
  _.defaultsDeep(connection.management, { hostname: connection.hostname });
87
- connection.management.auth = connection.management.auth || getAuth(connection.management.user, connection.management.password) || connection.auth;
88
- connection.management.url = connection.management.url || url.format(connection.management);
109
+ const auth = connection.management.auth || getAuth(connection.management.user, connection.management.password) || getAuth(connection.user, connection.password);
110
+ connection.management.url = connection.management.url || url.format({ ...connection.management, auth });
89
111
  connection.management.loggableUrl = connection.management.url.replace(/:[^:]*?@/, ':***@');
90
112
  }
91
113
 
114
+ function setConnectionAttributes(connection, attributes, defaults) {
115
+ Object.keys(connection).forEach((key) => delete connection[key]);
116
+ _.defaultsDeep(connection, attributes, defaults);
117
+ }
118
+
119
+ function setConnectionUrls(connection) {
120
+ const auth = getAuth(connection.user, connection.password);
121
+ const pathname = connection.vhost === '/' ? '' : connection.vhost;
122
+ const query = connection.options;
123
+
124
+ connection.url = url.format({
125
+ slashes: true, ...connection, auth, pathname, query,
126
+ });
127
+ connection.loggableUrl = connection.url.replace(/:[^:]*@/, ':***@');
128
+ }
129
+
92
130
  function getAuth(user, password) {
93
131
  return user && password ? `${user}:${password}` : undefined;
94
132
  }
95
133
 
96
- function getConnectionIndex(strategy, connection, index) {
97
- if (strategy === 'fixed') return index;
98
- const host = url.parse(connection.url).host;
99
- if (_.has(connectionIndexes, host)) return connectionIndexes[host];
100
- connectionIndexes[host] = Math.random();
101
- return connectionIndexes[host];
134
+ function setConnectionIndex(connection, strategy, index) {
135
+ connection.index = strategy === 'fixed' ? index : getConnectionIndex(strategy, `${connection.host}:${connection.port}`);
136
+ }
137
+
138
+ function getConnectionIndex(strategy, hostname) {
139
+ if (connectionIndexes[hostname] === undefined) connectionIndexes[hostname] = Math.random();
140
+ return connectionIndexes[hostname];
102
141
  }
103
142
 
104
143
  function configureVhostPublications(vhostConfig) {
@@ -132,7 +171,7 @@ module.exports = _.curry((rascalConfig, next) => {
132
171
  });
133
172
  return publications;
134
173
  },
135
- {}
174
+ {},
136
175
  );
137
176
  _.each(_.defaults({}, publications, defaultPublications), configurePublication);
138
177
  }
@@ -181,7 +220,7 @@ module.exports = _.curry((rascalConfig, next) => {
181
220
  });
182
221
  return subscriptions;
183
222
  },
184
- {}
223
+ {},
185
224
  );
186
225
  _.each(_.defaults({}, subscriptions, defaultSubscriptions), configureSubscription);
187
226
  }
@@ -211,10 +250,10 @@ module.exports = _.curry((rascalConfig, next) => {
211
250
  const match = XRegExp.exec(name, pattern);
212
251
  return match
213
252
  ? {
214
- name,
215
- subscription: match.groups.subscription,
216
- publication: match.groups.publication,
217
- }
253
+ name,
254
+ subscription: match.groups.subscription,
255
+ publication: match.groups.publication,
256
+ }
218
257
  : { name };
219
258
  }
220
259
 
@@ -252,7 +291,7 @@ module.exports = _.curry((rascalConfig, next) => {
252
291
  name,
253
292
  fullyQualifiedName: fqn.qualify(name, config.namespace, queueConfig.replyTo),
254
293
  },
255
- config.defaults.queues
294
+ config.defaults.queues,
256
295
  );
257
296
  });
258
297
  }
@@ -276,11 +315,11 @@ module.exports = _.curry((rascalConfig, next) => {
276
315
  const match = XRegExp.exec(name, pattern);
277
316
  return match
278
317
  ? {
279
- name,
280
- source: match.groups.source,
281
- destination: match.groups.destination,
282
- bindingKeys: splitBindingKeys(match.groups.keys),
283
- }
318
+ name,
319
+ source: match.groups.source,
320
+ destination: match.groups.destination,
321
+ bindingKeys: splitBindingKeys(match.groups.keys),
322
+ }
284
323
  : { name };
285
324
  }
286
325
 
@@ -292,7 +331,8 @@ module.exports = _.curry((rascalConfig, next) => {
292
331
  const result = {};
293
332
  _.each(definitions, (bindingConfig, name) => {
294
333
  const parsedConfig = parseBindingName(name);
295
- const bindingKeys = _.chain([]).concat(bindingConfig.bindingKeys, bindingConfig.bindingKey, parsedConfig.bindingKeys).compact().uniq().value();
334
+ const bindingKeys = _.chain([]).concat(bindingConfig.bindingKeys, bindingConfig.bindingKey, parsedConfig.bindingKeys).compact().uniq()
335
+ .value();
296
336
  if (bindingKeys.length <= 1) {
297
337
  result[name] = _({ bindingKey: bindingKeys[0] }).defaults(bindingConfig, parsedConfig).omit('bindingKeys').value();
298
338
  return result[name];
@@ -576,6 +576,10 @@
576
576
  "type": "integer",
577
577
  "minimum": 0
578
578
  },
579
+ "channelPrefetch": {
580
+ "type": "integer",
581
+ "minimum": 0
582
+ },
579
583
  "retry": {
580
584
  "$ref": "#/definitions/retry"
581
585
  },
@@ -40,5 +40,5 @@ module.exports = _.defaultsDeep(
40
40
  },
41
41
  },
42
42
  },
43
- defaultConfig
43
+ defaultConfig,
44
44
  );
@@ -132,6 +132,7 @@ module.exports = _.curry((config, next) => {
132
132
  'contentType',
133
133
  'options',
134
134
  'prefetch',
135
+ 'channelPrefetch',
135
136
  'retry',
136
137
  'source',
137
138
  'recovery',
@@ -63,7 +63,7 @@ module.exports = {
63
63
  correlationId,
64
64
  cmd: 'incrementAndGet',
65
65
  });
66
- }
66
+ },
67
67
  );
68
68
  },
69
69
  };
@@ -12,8 +12,8 @@ function Client(suppliedAgent) {
12
12
  const options = getVhostOptions(name, config);
13
13
  self._request('put', options.url, options.timeout, (err) => {
14
14
  if (!err) return next();
15
- const message = err.status ? format('Failed to assert vhost: %s. %s returned status %d', name, config.loggableUrl, err.status) : format('Failed to assert vhost: %s. %s errored with: %s', name, config.loggableUrl, err.message);
16
- return next(new Error(message));
15
+ const _err = err.status ? new Error(format('Failed to assert vhost: %s. %s returned status %d', name, config.loggableUrl, err.status)) : err;
16
+ return next(_err);
17
17
  });
18
18
  };
19
19
 
@@ -22,8 +22,8 @@ function Client(suppliedAgent) {
22
22
  const options = getVhostOptions(name, config);
23
23
  self._request('get', options.url, options.timeout, (err) => {
24
24
  if (!err) return next();
25
- const message = err.status ? format('Failed to check vhost: %s. %s returned status %d', name, config.loggableUrl, err.status) : format('Failed to check vhost: %s. %s errored with: %s', name, config.loggableUrl, err.message);
26
- return next(new Error(message));
25
+ const _err = err.status ? new Error(format('Failed to check vhost: %s. %s returned status %d', name, config.loggableUrl, err.status)) : err;
26
+ return next(_err);
27
27
  });
28
28
  };
29
29
 
@@ -32,8 +32,8 @@ function Client(suppliedAgent) {
32
32
  const options = getVhostOptions(name, config);
33
33
  self._request('delete', options.url, options.timeout, (err) => {
34
34
  if (!err) return next();
35
- const message = err.status ? format('Failed to delete vhost: %s. %s returned status %d', name, config.loggableUrl, err.status) : format('Failed to delete vhost: %s. %s errored with: %s', name, config.loggableUrl, err.message);
36
- return next(new Error(message));
35
+ const _err = err.status ? new Error(format('Failed to delete vhost: %s. %s returned status %d', name, config.loggableUrl, err.status)) : err;
36
+ return next(_err);
37
37
  });
38
38
  };
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rascal",
3
- "version": "16.3.0",
3
+ "version": "17.0.0",
4
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",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -20,14 +20,12 @@
20
20
  "amqplib": "^0.10.2",
21
21
  "chalk": "^4.1.2",
22
22
  "chance": "^1.1.8",
23
- "eslint": "^8.33.0",
23
+ "eslint": "^8.45.0",
24
24
  "eslint-config-airbnb-base": "^15.0.0",
25
- "eslint-config-prettier": "^8.3.0",
26
25
  "eslint-plugin-import": "^2.27.5",
27
- "husky": "^8.0.1",
26
+ "husky": "^8.0.3",
28
27
  "lint-staged": "^11.2.4",
29
28
  "nyc": "^15.1.0",
30
- "prettier": "^2.4.1",
31
29
  "random-readable": "^1.0.1",
32
30
  "superagent-defaults": "^0.1.14",
33
31
  "zunit": "^4.0.0"
@@ -40,8 +38,6 @@
40
38
  },
41
39
  "scripts": {
42
40
  "test": "zUnit",
43
- "prettier": "prettier --check .",
44
- "prettier:fix": "prettier --write .",
45
41
  "lint": "eslint .",
46
42
  "lint:fix": "eslint --fix .",
47
43
  "lint-staged": "lint-staged",
@@ -50,7 +46,6 @@
50
46
  "prepare": "husky install"
51
47
  },
52
48
  "lint-staged": {
53
- "**/*": "prettier --write --ignore-unknown",
54
49
  "**/*.js": "eslint --fix"
55
50
  },
56
51
  "keywords": [
@@ -71,7 +66,7 @@
71
66
  },
72
67
  "homepage": "https://guidesmiths.github.io/rascal/",
73
68
  "author": "Stephen Cresswell",
74
- "license": "ISC",
69
+ "license": "MIT",
75
70
  "zUnit": {
76
71
  "pollute": true,
77
72
  "pattern": "^[\\w-]+.tests.js$"
package/.prettierrc.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "singleQuote": true,
3
- "printWidth": 300
4
- }
package/dependabot.yml DELETED
@@ -1,6 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: 'npm'
4
- directory: '/'
5
- allow:
6
- - dependency-type: 'direct'