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.
- package/.eslintrc.json +3 -2
- package/CHANGELOG.md +10 -1
- package/LICENSE.txt +19 -12
- package/README.md +49 -22
- package/examples/busy-publisher/config.json +0 -1
- package/examples/default-exchange/config.json +0 -1
- package/examples/promises/config.json +3 -1
- package/index.js +1 -1
- package/lib/amqp/Broker.js +9 -8
- package/lib/amqp/BrokerAsPromised.js +5 -5
- package/lib/amqp/Publication.js +2 -1
- package/lib/amqp/SubscriberError.js +2 -2
- package/lib/amqp/SubscriberSession.js +25 -7
- package/lib/amqp/SubscriberSessionAsPromised.js +9 -0
- package/lib/amqp/Subscription.js +31 -21
- package/lib/amqp/Vhost.js +7 -9
- package/lib/amqp/tasks/applyBindings.js +1 -1
- package/lib/amqp/tasks/assertExchanges.js +1 -1
- package/lib/amqp/tasks/assertQueues.js +1 -1
- package/lib/amqp/tasks/assertVhost.js +1 -1
- package/lib/amqp/tasks/checkExchanges.js +1 -1
- package/lib/amqp/tasks/checkQueues.js +1 -1
- package/lib/amqp/tasks/checkVhost.js +1 -1
- package/lib/amqp/tasks/closeChannels.js +1 -1
- package/lib/amqp/tasks/createChannels.js +1 -1
- package/lib/amqp/tasks/createConnection.js +1 -1
- package/lib/amqp/tasks/deleteExchanges.js +1 -1
- package/lib/amqp/tasks/deleteQueues.js +1 -1
- package/lib/amqp/tasks/deleteVhost.js +1 -1
- package/lib/amqp/tasks/initCounters.js +1 -1
- package/lib/amqp/tasks/initPublications.js +1 -1
- package/lib/amqp/tasks/initShovels.js +1 -1
- package/lib/amqp/tasks/initSubscriptions.js +1 -1
- package/lib/amqp/tasks/initVhosts.js +1 -1
- package/lib/amqp/tasks/purgeQueues.js +1 -1
- package/lib/config/configure.js +71 -31
- package/lib/config/schema.json +4 -0
- package/lib/config/tests.js +1 -1
- package/lib/config/validate.js +1 -0
- package/lib/counters/inMemoryCluster.js +1 -1
- package/lib/management/Client.js +6 -6
- package/package.json +4 -9
- package/.prettierrc.json +0 -4
- package/dependabot.yml +0 -6
package/.eslintrc.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": ["eslint-config-airbnb-base"
|
|
2
|
+
"extends": ["eslint-config-airbnb-base"],
|
|
3
3
|
"env": {
|
|
4
4
|
"node": true
|
|
5
5
|
},
|
|
6
6
|
"parserOptions": {
|
|
7
|
-
"ecmaVersion": "
|
|
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
|
-
##
|
|
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
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2016
|
|
3
|
+
Copyright (c) 2016-2022 GuideSmiths Ltd.
|
|
4
|
+
Copyright (c) 2022-present One Beyond
|
|
4
5
|
|
|
5
|
-
Permission
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
[](https://github.com/guidesmiths/rascal/actions?query=workflow%3A%22Node.js+CI%22)
|
|
8
8
|
[](https://codeclimate.com/github/guidesmiths/rascal)
|
|
9
9
|
[](https://codeclimate.com/github/guidesmiths/rascal/coverage)
|
|
10
|
-
[](https://github.com/prettier/prettier)
|
|
11
10
|
[](https://snyk.io/advisor/npm-package/rascal)
|
|
12
11
|
[](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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
1673
|
+
console.error(err);
|
|
1647
1674
|
});
|
|
1648
1675
|
});
|
|
1649
1676
|
```
|
|
@@ -1657,7 +1684,7 @@ try {
|
|
|
1657
1684
|
}
|
|
1658
1685
|
```
|
|
1659
1686
|
|
|
1660
|
-
Cancelling a
|
|
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
|
|
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
|
|
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
|
{
|
package/index.js
CHANGED
package/lib/amqp/Broker.js
CHANGED
|
@@ -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
|
};
|
package/lib/amqp/Publication.js
CHANGED
|
@@ -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()
|
|
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] = {
|
|
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
|
|
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
|
}
|
package/lib/amqp/Subscription.js
CHANGED
|
@@ -31,8 +31,9 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
this.subscribe = function (overrides, next) {
|
|
34
|
-
const
|
|
35
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
452
|
-
createChannelPool({
|
|
450
|
+
regularChannelPool = regularChannelPool
|
|
451
|
+
|| createChannelPool({
|
|
453
452
|
confirm: false,
|
|
454
453
|
pool: vhostConfig.publicationChannelPools.regularPool,
|
|
455
454
|
});
|
|
456
|
-
confirmChannelPool =
|
|
457
|
-
|
|
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);
|
package/lib/config/configure.js
CHANGED
|
@@ -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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
connection
|
|
80
|
-
connection
|
|
81
|
-
connection
|
|
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
|
-
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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()
|
|
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];
|
package/lib/config/schema.json
CHANGED
package/lib/config/tests.js
CHANGED
package/lib/config/validate.js
CHANGED
package/lib/management/Client.js
CHANGED
|
@@ -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
|
|
16
|
-
return next(
|
|
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
|
|
26
|
-
return next(
|
|
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
|
|
36
|
-
return next(
|
|
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": "
|
|
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.
|
|
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.
|
|
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": "
|
|
69
|
+
"license": "MIT",
|
|
75
70
|
"zUnit": {
|
|
76
71
|
"pollute": true,
|
|
77
72
|
"pattern": "^[\\w-]+.tests.js$"
|
package/.prettierrc.json
DELETED