amqp-connection-manager 2.1.2 → 2.3.2

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/README.md CHANGED
@@ -7,14 +7,11 @@
7
7
  [![devDependency Status](https://david-dm.org/benbria/node-amqp-connection-manager/dev-status.svg)](https://david-dm.org/benbria/node-amqp-connection-manager#info=devDependencies)
8
8
  [![peerDependency Status](https://david-dm.org/benbria/node-amqp-connection-manager/peer-status.svg)](https://david-dm.org/benbria/node-amqp-connection-manager#info=peerDependencies)
9
9
 
10
-
11
10
  Connection management for amqplib.
12
11
 
13
- amqp-connection-manager
14
- =======================
12
+ # amqp-connection-manager
15
13
 
16
- Features:
17
- ---------
14
+ ## Features
18
15
 
19
16
  * Automatically reconnect when your amqplib broker dies in a fire.
20
17
  * Round-robin connections between multiple brokers in a cluster.
@@ -22,24 +19,26 @@ Features:
22
19
  * Supports both promises and callbacks (using [promise-breaker](https://github.com/jwalton/node-promise-breaker))
23
20
  * Very un-opinionated library - a thin wrapper around amqplib.
24
21
 
25
- Installation:
26
- -------------
22
+ ## Installation
27
23
 
28
24
  npm install --save amqplib amqp-connection-manager
29
25
 
30
- Basics:
31
- -------
26
+ ## Basics
32
27
 
33
- The basic idea here is that, usually, when you create a new channel, you do some setup work at the beginning (like
34
- asserting that various queues or exchanges exist, or binding to queues), and then you send and receive messages and
35
- you never touch that stuff again.
28
+ The basic idea here is that, usually, when you create a new channel, you do some
29
+ setup work at the beginning (like asserting that various queues or exchanges
30
+ exist, or binding to queues), and then you send and receive messages and you
31
+ never touch that stuff again.
36
32
 
37
- amqp-connection-manager will reconnect to a new broker whenever the broker it is currently connected to dies. When you
38
- ask amqp-connection-manager for a channel, you specify one or more `setup` functions to run; the setup functions will
39
- be run every time amqp-connection-manager reconnects, to make sure your channel and broker are in a sane state.
33
+ amqp-connection-manager will reconnect to a new broker whenever the broker it is
34
+ currently connected to dies. When you ask amqp-connection-manager for a
35
+ channel, you specify one or more `setup` functions to run; the setup functions
36
+ will be run every time amqp-connection-manager reconnects, to make sure your
37
+ channel and broker are in a sane state.
40
38
 
41
- Before we get into an example, note this example is written using Promises, however much like amqplib, any
42
- function which returns a Promise will also accept a callback as an optional parameter.
39
+ Before we get into an example, note this example is written using Promises,
40
+ however much like amqplib, any function which returns a Promise will also accept
41
+ a callback as an optional parameter.
43
42
 
44
43
  Here's the example:
45
44
 
@@ -49,13 +48,14 @@ var amqp = require('amqp-connection-manager');
49
48
  // Create a new connection manager
50
49
  var connection = amqp.connect(['amqp://localhost']);
51
50
 
52
- // Ask the connection manager for a ChannelWrapper. Specify a setup function to run every time we reconnect
53
- // to the broker.
51
+ // Ask the connection manager for a ChannelWrapper. Specify a setup function to
52
+ // run every time we reconnect to the broker.
54
53
  var channelWrapper = connection.createChannel({
55
54
  json: true,
56
55
  setup: function(channel) {
57
56
  // `channel` here is a regular amqplib `ConfirmChannel`.
58
- return channel.assertQueue('rxQueueName', {durable: true}),
57
+ // Note that `this` here is the channelWrapper instance.
58
+ return channel.assertQueue('rxQueueName', {durable: true});
59
59
  }
60
60
  });
61
61
 
@@ -64,15 +64,16 @@ var channelWrapper = connection.createChannel({
64
64
  // when the message is actually sent (or not sent.)
65
65
  channelWrapper.sendToQueue('rxQueueName', {hello: 'world'})
66
66
  .then(function() {
67
- return console.log("Message was sent! Hooray!");
67
+ return console.log("Message was sent! Hooray!");
68
68
  }).catch(function(err) {
69
- return console.log("Message was rejected... Boo!");
69
+ return console.log("Message was rejected... Boo!");
70
70
  });
71
71
  ```
72
72
 
73
- Sometimes it's handy to modify a channel at run time. For example, suppose you have a channel that's listening to
74
- one kind of message, and you decide you now also want to listen to some other kind of message. This can be done
75
- by adding a new setup function to an existing ChannelWrapper:
73
+ Sometimes it's handy to modify a channel at run time. For example, suppose you
74
+ have a channel that's listening to one kind of message, and you decide you now
75
+ also want to listen to some other kind of message. This can be done by adding a
76
+ new setup function to an existing ChannelWrapper:
76
77
 
77
78
  ```js
78
79
  channelWrapper.addSetup(function(channel) {
@@ -84,21 +85,24 @@ channelWrapper.addSetup(function(channel) {
84
85
  });
85
86
  ```
86
87
 
87
- `addSetup()` returns a Promise which resolves when the setup function is finished (or immediately, if the underlying
88
- connection is not currently connected to a broker.) There is also a `removeSetup(setup, teardown)` which will run
89
- `teardown(channel)` if the channel is currently connected to a broker (and will not run `teardown` at all otherwise.)
90
- Note that `setup` and `teardown` *must* either accept a callback or return a Promise.
88
+ `addSetup()` returns a Promise which resolves when the setup function is
89
+ finished (or immediately, if the underlying connection is not currently
90
+ connected to a broker.) There is also a `removeSetup(setup, teardown)` which
91
+ will run `teardown(channel)` if the channel is currently connected to a broker
92
+ (and will not run `teardown` at all otherwise.) Note that `setup` and `teardown`
93
+ *must* either accept a callback or return a Promise.
91
94
 
92
95
  See a complete example in the [examples](./examples) folder.
93
96
 
94
- API:
95
- ----
97
+ ## API
96
98
 
97
99
  ### connect(urls, options)
100
+
98
101
  Creates a new AmqpConnectionManager, which will connect to one of the URLs provided in `urls`. If a broker is
99
102
  unreachable or dies, then AmqpConnectionManager will try the next available broker, round-robin.
100
103
 
101
104
  Options:
105
+
102
106
  * `options.heartbeatIntervalInSeconds` - Interval to send heartbeats to broker. Defaults to 5 seconds.
103
107
  * `options.reconnectTimeInSeconds` - The time to wait before trying to reconnect. If not specified,
104
108
  defaults to `heartbeatIntervalInSeconds`.
@@ -109,37 +113,43 @@ Options:
109
113
  * `options.connectionOptions` is passed as options to the amqplib connect method.
110
114
 
111
115
  ### AmqpConnectionManager events
116
+
112
117
  * `connect({connection, url})` - Emitted whenever we successfully connect to a broker.
113
118
  * `disconnect({err})` - Emitted whenever we disconnect from a broker.
114
119
 
115
-
116
120
  ### AmqpConnectionManager#createChannel(options)
121
+
117
122
  Create a new ChannelWrapper. This is a proxy for the actual channel (which may or may not exist at any moment,
118
123
  depending on whether or not we are currently connected.)
119
124
 
120
125
  Options:
126
+
121
127
  * `options.name` - Name for this channel. Used for debugging.
122
- * `options.setup(channel, [cb])` - A function to call whenever we reconnect to the broker (and therefore create a new
123
- underlying channel.) This function should either accept a callback, or return a Promise. See `addSetup` below.
128
+ * `options.setup(channel, [cb])` - A function to call whenever we reconnect to the
129
+ broker (and therefore create a new underlying channel.) This function should
130
+ either accept a callback, or return a Promise. See `addSetup` below.
131
+ Note that `this` inside the setup function will the returned ChannelWrapper.
132
+ The ChannelWrapper has a special `context` member you can use to store
133
+ arbitrary data in.
124
134
  * `options.json` if true, then ChannelWrapper assumes all messages passed to `publish()` and `sendToQueue()`
125
135
  are plain JSON objects. These will be encoded automatically before being sent.
126
136
 
127
-
128
137
  ### AmqpConnectionManager#isConnected()
129
- Returns true if the AmqpConnectionManager is connected to a broker, false otherwise.
130
138
 
139
+ Returns true if the AmqpConnectionManager is connected to a broker, false otherwise.
131
140
 
132
141
  ### AmqpConnectionManager#close()
133
- Close this AmqpConnectionManager and free all associated resources.
134
142
 
143
+ Close this AmqpConnectionManager and free all associated resources.
135
144
 
136
145
  ### ChannelWrapper events
146
+
137
147
  * `connect` - emitted every time this channel connects or reconnects.
138
148
  * `error(err, {name})` - emitted if an error occurs setting up the channel.
139
149
  * `close` - emitted when this channel closes via a call to `close()`
140
150
 
141
-
142
151
  ### ChannelWrapper#addSetup(setup)
152
+
143
153
  Adds a new 'setup handler'.
144
154
 
145
155
  `setup(channel, [cb])` is a function to call when a new underlying channel is created - handy for asserting
@@ -155,27 +165,27 @@ reconnects, even if it throws an error.)
155
165
  Setup functions should, ideally, not throw errors, but if they do then the ChannelWrapper will emit an 'error'
156
166
  event.
157
167
 
158
-
159
168
  ### ChannelWrapper#removeSetup(setup, teardown)
169
+
160
170
  Removes a setup handler. If the channel is currently connected, will call `teardown(channel)`, passing in the
161
171
  underlying amqplib ConfirmChannel. `teardown` should either take a callback or return a Promise.
162
172
 
163
-
164
173
  ### ChannelWrapper#publish and ChannelWrapper#sendToQueue
174
+
165
175
  These work exactly like their counterparts in amqplib's Channel, except that they return a Promise (or accept a
166
176
  callback) which resolves when the message is confirmed to have been delivered to the broker. The promise rejects if
167
177
  either the broker refuses the message, or if `close()` is called on the ChannelWrapper before the message can be
168
178
  delivered.
169
179
 
170
-
171
180
  ### ChannelWrapper#ack and ChannelWrapper#nack
181
+
172
182
  These are just aliases for calling `ack()` and `nack()` on the underlying channel. They do nothing if the underlying
173
183
  channel is not connected.
174
184
 
175
-
176
185
  ### ChannelWrapper#queueLength()
177
- Returns a count of messages currently waiting to be sent to the underlying channel.
178
186
 
187
+ Returns a count of messages currently waiting to be sent to the underlying channel.
179
188
 
180
189
  ### ChannelWrapper#close()
190
+
181
191
  Close a channel, clean up resources associated with it.
@@ -1,207 +1,203 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
+ exports.default = void 0;
6
7
 
7
- var _events = require('events');
8
+ var _events = require("events");
8
9
 
9
- var _amqplib = require('amqplib');
10
+ var _amqplib = _interopRequireDefault(require("amqplib"));
10
11
 
11
- var _amqplib2 = _interopRequireDefault(_amqplib);
12
+ var _url = _interopRequireDefault(require("url"));
12
13
 
13
- var _url = require('url');
14
+ var _ChannelWrapper = _interopRequireDefault(require("./ChannelWrapper"));
14
15
 
15
- var _url2 = _interopRequireDefault(_url);
16
+ var _helpers = require("./helpers");
16
17
 
17
- var _ChannelWrapper = require('./ChannelWrapper');
18
-
19
- var _ChannelWrapper2 = _interopRequireDefault(_ChannelWrapper);
20
-
21
- var _helpers = require('./helpers');
22
-
23
- var _promiseBreaker = require('promise-breaker');
24
-
25
- var _promiseBreaker2 = _interopRequireDefault(_promiseBreaker);
18
+ var _promiseBreaker = _interopRequireDefault(require("promise-breaker"));
26
19
 
27
20
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
28
21
 
29
22
  // Default heartbeat time.
30
23
  const HEARTBEAT_IN_SECONDS = 5;
31
-
32
24
  /* istanbul ignore next */
33
- function neverThrows() {
34
- return err => setImmediate(() => {
35
- throw new Error(`AmqpConnectionManager - should never get here: ${err.message}\n` + err.stack);
36
- });
37
- }
38
25
 
39
- //
26
+ function neverThrows() {
27
+ return err => setImmediate(() => {
28
+ throw new Error(`AmqpConnectionManager - should never get here: ${err.message}\n` + err.stack);
29
+ });
30
+ } //
40
31
  // Events:
41
32
  // * `connect({connection, url})` - Emitted whenever we connect to a broker.
42
33
  // * `disconnect({err})` - Emitted whenever we disconnect from a broker.
43
34
  //
35
+
36
+
44
37
  class AmqpConnectionManager extends _events.EventEmitter {
45
- /**
46
- * Create a new AmqplibConnectionManager.
47
- *
48
- * @param {(string|Object)[]} urls - An array of brokers to connect to.
49
- * Takes url strings or objects {url: string, connectionOptions?: object}
50
- * If present, a broker's [connectionOptions] will be used instead
51
- * of [options.connectionOptions] when passed to the amqplib connect method.
52
- * AmqplibConnectionManager will round-robin between them whenever it
53
- * needs to create a new connection.
54
- * @param {Object} [options={}] -
55
- * @param {number} [options.heartbeatIntervalInSeconds=5] - The interval,
56
- * in seconds, to send heartbeats.
57
- * @param {number} [options.reconnectTimeInSeconds] - The time to wait
58
- * before trying to reconnect. If not specified, defaults to
59
- * `heartbeatIntervalInSeconds`.
60
- * @param {Object} [options.connectionOptions] - Passed to the amqplib
61
- * connect method.
62
- * @param {function} [options.findServers] - A `fn(callback)` or a `fn()`
63
- * which returns a Promise. This should resolve to one or more servers
64
- * to connect to, either a single URL or an array of URLs. This is handy
65
- * when you're using a service discovery mechanism such as Consul or etcd.
66
- * Note that if this is supplied, then `urls` is ignored.
67
- */
68
- constructor(urls, options = {}) {
69
- super();
70
- if (!urls && !options.findServers) {
71
- throw new Error("Must supply either `urls` or `findServers`");
72
- }
73
- this._channels = [];
38
+ /**
39
+ * Create a new AmqplibConnectionManager.
40
+ *
41
+ * @param {(string|Object)[]} urls - An array of brokers to connect to.
42
+ * Takes url strings or objects {url: string, connectionOptions?: object}
43
+ * If present, a broker's [connectionOptions] will be used instead
44
+ * of [options.connectionOptions] when passed to the amqplib connect method.
45
+ * AmqplibConnectionManager will round-robin between them whenever it
46
+ * needs to create a new connection.
47
+ * @param {Object} [options={}] -
48
+ * @param {number} [options.heartbeatIntervalInSeconds=5] - The interval,
49
+ * in seconds, to send heartbeats.
50
+ * @param {number} [options.reconnectTimeInSeconds] - The time to wait
51
+ * before trying to reconnect. If not specified, defaults to
52
+ * `heartbeatIntervalInSeconds`.
53
+ * @param {Object} [options.connectionOptions] - Passed to the amqplib
54
+ * connect method.
55
+ * @param {function} [options.findServers] - A `fn(callback)` or a `fn()`
56
+ * which returns a Promise. This should resolve to one or more servers
57
+ * to connect to, either a single URL or an array of URLs. This is handy
58
+ * when you're using a service discovery mechanism such as Consul or etcd.
59
+ * Note that if this is supplied, then `urls` is ignored.
60
+ */
61
+ constructor(urls, options = {}) {
62
+ super();
63
+
64
+ if (!urls && !options.findServers) {
65
+ throw new Error("Must supply either `urls` or `findServers`");
66
+ }
74
67
 
75
- this._currentUrl = 0;
76
- this.connectionOptions = options.connectionOptions;
68
+ this._channels = [];
69
+ this._currentUrl = 0;
70
+ this.connectionOptions = options.connectionOptions;
71
+ this.heartbeatIntervalInSeconds = options.heartbeatIntervalInSeconds || HEARTBEAT_IN_SECONDS;
72
+ this.reconnectTimeInSeconds = options.reconnectTimeInSeconds || this.heartbeatIntervalInSeconds; // There will be one listener per channel, and there could be a lot of channels, so disable warnings from node.
77
73
 
78
- this.heartbeatIntervalInSeconds = options.heartbeatIntervalInSeconds || HEARTBEAT_IN_SECONDS;
79
- this.reconnectTimeInSeconds = options.reconnectTimeInSeconds || this.heartbeatIntervalInSeconds;
74
+ this.setMaxListeners(0);
80
75
 
81
- // There will be one listener per channel, and there could be a lot of channels, so disable warnings from node.
82
- this.setMaxListeners(0);
76
+ this._findServers = options.findServers || (() => Promise.resolve(urls));
83
77
 
84
- this._findServers = options.findServers || (() => Promise.resolve(urls));
78
+ this._connect();
79
+ } // `options` here are any options that can be passed to ChannelWrapper.
85
80
 
86
- this._connect();
87
- }
88
81
 
89
- // `options` here are any options that can be passed to ChannelWrapper.
90
- createChannel(options = {}) {
91
- const channel = new _ChannelWrapper2.default(this, options);
92
- this._channels.push(channel);
93
- channel.once('close', () => {
94
- this._channels = this._channels.filter(c => c !== channel);
95
- });
96
- return channel;
97
- }
82
+ createChannel(options = {}) {
83
+ const channel = new _ChannelWrapper.default(this, options);
98
84
 
99
- close() {
100
- if (this._closed) {
101
- return Promise.resolve();
102
- }
103
- this._closed = true;
104
-
105
- return Promise.all(this._channels.map(channel => channel.close())).catch(function () {
106
- // Ignore errors closing channels.
107
- }).then(() => {
108
- this._channels = [];
109
- if (this._currentConnection) {
110
- this._currentConnection.removeAllListeners('close');
111
- this._currentConnection.close();
112
- }
113
- this._currentConnection = null;
114
- });
85
+ this._channels.push(channel);
86
+
87
+ channel.once('close', () => {
88
+ this._channels = this._channels.filter(c => c !== channel);
89
+ });
90
+ return channel;
91
+ }
92
+
93
+ close() {
94
+ if (this._closed) {
95
+ return Promise.resolve();
115
96
  }
116
97
 
117
- isConnected() {
118
- return !!this._currentConnection;
98
+ this._closed = true;
99
+ return Promise.all(this._channels.map(channel => channel.close())).catch(function () {// Ignore errors closing channels.
100
+ }).then(() => {
101
+ this._channels = [];
102
+
103
+ if (this._currentConnection) {
104
+ this._currentConnection.removeAllListeners('close');
105
+
106
+ this._currentConnection.close();
107
+ }
108
+
109
+ this._currentConnection = null;
110
+ });
111
+ }
112
+
113
+ isConnected() {
114
+ return !!this._currentConnection;
115
+ }
116
+
117
+ _connect() {
118
+ if (this._closed || this._connecting || this.isConnected()) {
119
+ return Promise.resolve();
119
120
  }
120
121
 
121
- _connect() {
122
- if (this._closed || this._connecting || this.isConnected()) {
123
- return Promise.resolve();
124
- }
125
-
126
- this._connecting = true;
127
-
128
- return Promise.resolve().then(() => {
129
- if (!this._urls || this._currentUrl >= this._urls.length) {
130
- this._currentUrl = 0;
131
- return _promiseBreaker2.default.callFn(this._findServers, 0, null);
132
- } else {
133
- return this._urls;
134
- }
135
- }).then(urls => {
136
- if (urls && !Array.isArray(urls)) {
137
- urls = [urls];
138
- }
139
- this._urls = urls;
140
-
141
- if (!urls || urls.length === 0) {
142
- throw new Error('amqp-connection-manager: No servers found');
143
- }
144
-
145
- // Round robin between brokers
146
- const url = urls[this._currentUrl];
147
- this._currentUrl++;
148
-
149
- // url can be a string or object {url: string, connectionOptions?: object}
150
- const urlString = url.url || url;
151
- const connectionOptions = url.connectionOptions || this.connectionOptions;
152
-
153
- const amqpUrl = _url2.default.parse(urlString);
154
- if (amqpUrl.search) {
155
- amqpUrl.search += `&heartbeat=${this.heartbeatIntervalInSeconds}`;
156
- } else {
157
- amqpUrl.search = `?heartbeat=${this.heartbeatIntervalInSeconds}`;
158
- }
159
-
160
- return _amqplib2.default.connect(_url2.default.format(amqpUrl), connectionOptions).then(connection => {
161
- this._currentConnection = connection;
162
-
163
- //emit 'blocked' when RabbitMQ server decides to block the connection (resources running low)
164
- connection.on('blocked', reason => this.emit('blocked', { reason }));
165
-
166
- connection.on('unblocked', () => this.emit('unblocked'));
167
-
168
- // Reconnect if the broker goes away.
169
- connection.on('error', err => Promise.resolve().then(() => this._currentConnection.close()).catch(() => {/* Ignore */}).then(() => {
170
- this._currentConnection = null;
171
- this.emit('disconnect', { err });
172
- return this._connect();
173
- })
174
- // `_connect()` should never throw.
175
- .catch(neverThrows));
176
-
177
- // Reconnect if the connection closes gracefully
178
- connection.on('close', err => {
179
- this._currentConnection = null;
180
- this.emit('disconnect', { err });
181
-
182
- (0, _helpers.wait)(this.reconnectTimeInSeconds * 1000).then(() => this._connect())
183
- // `_connect()` should never throw.
184
- .catch(neverThrows);
185
- });
186
-
187
- this._connecting = false;
188
- this.emit('connect', { connection, url: urlString });
189
-
190
- return null;
191
- });
192
- }).catch(err => {
193
- this.emit('disconnect', { err });
194
-
195
- // Connection failed...
196
- this._currentConnection = null;
197
-
198
- // TODO: Probably want to try right away here, especially if there are multiple brokers to try...
199
- return (0, _helpers.wait)(this.reconnectTimeInSeconds * 1000).then(() => {
200
- this._connecting = false;
201
- return this._connect();
202
- });
122
+ this._connecting = true;
123
+ return Promise.resolve().then(() => {
124
+ if (!this._urls || this._currentUrl >= this._urls.length) {
125
+ this._currentUrl = 0;
126
+ return _promiseBreaker.default.callFn(this._findServers, 0, null);
127
+ } else {
128
+ return this._urls;
129
+ }
130
+ }).then(urls => {
131
+ if (urls && !Array.isArray(urls)) {
132
+ urls = [urls];
133
+ }
134
+
135
+ this._urls = urls;
136
+
137
+ if (!urls || urls.length === 0) {
138
+ throw new Error('amqp-connection-manager: No servers found');
139
+ } // Round robin between brokers
140
+
141
+
142
+ const url = urls[this._currentUrl];
143
+ this._currentUrl++; // url can be a string or object {url: string, connectionOptions?: object}
144
+
145
+ const urlString = url.url || url;
146
+ const connectionOptions = url.connectionOptions || this.connectionOptions;
147
+
148
+ const amqpUrl = _url.default.parse(urlString);
149
+
150
+ if (amqpUrl.search) {
151
+ amqpUrl.search += `&heartbeat=${this.heartbeatIntervalInSeconds}`;
152
+ } else {
153
+ amqpUrl.search = `?heartbeat=${this.heartbeatIntervalInSeconds}`;
154
+ }
155
+
156
+ return _amqplib.default.connect(_url.default.format(amqpUrl), connectionOptions).then(connection => {
157
+ this._currentConnection = connection; //emit 'blocked' when RabbitMQ server decides to block the connection (resources running low)
158
+
159
+ connection.on('blocked', reason => this.emit('blocked', {
160
+ reason
161
+ }));
162
+ connection.on('unblocked', () => this.emit('unblocked'));
163
+ connection.on('error', () =>
164
+ /* err */
165
+ {// if this event was emitted, then the connection was already closed,
166
+ // so no need to call #close here
167
+ // also, 'close' is emitted after 'error',
168
+ // so no need for work already done in 'close' handler
169
+ }); // Reconnect if the connection closes
170
+
171
+ connection.on('close', err => {
172
+ this._currentConnection = null;
173
+ this.emit('disconnect', {
174
+ err
175
+ });
176
+ (0, _helpers.wait)(this.reconnectTimeInSeconds * 1000).then(() => this._connect()) // `_connect()` should never throw.
177
+ .catch(neverThrows);
203
178
  });
204
- }
179
+ this._connecting = false;
180
+ this.emit('connect', {
181
+ connection,
182
+ url: urlString
183
+ });
184
+ return null;
185
+ });
186
+ }).catch(err => {
187
+ this.emit('disconnect', {
188
+ err
189
+ }); // Connection failed...
190
+
191
+ this._currentConnection = null; // TODO: Probably want to try right away here, especially if there are multiple brokers to try...
192
+
193
+ return (0, _helpers.wait)(this.reconnectTimeInSeconds * 1000).then(() => {
194
+ this._connecting = false;
195
+ return this._connect();
196
+ });
197
+ });
198
+ }
199
+
205
200
  }
201
+
206
202
  exports.default = AmqpConnectionManager;
207
203
  //# sourceMappingURL=AmqpConnectionManager.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/AmqpConnectionManager.js"],"names":["HEARTBEAT_IN_SECONDS","neverThrows","err","setImmediate","Error","message","stack","AmqpConnectionManager","EventEmitter","constructor","urls","options","findServers","_channels","_currentUrl","connectionOptions","heartbeatIntervalInSeconds","reconnectTimeInSeconds","setMaxListeners","_findServers","Promise","resolve","_connect","createChannel","channel","ChannelWrapper","push","once","filter","c","close","_closed","all","map","catch","then","_currentConnection","removeAllListeners","isConnected","_connecting","_urls","length","pb","callFn","Array","isArray","url","urlString","amqpUrl","urlUtils","parse","search","amqp","connect","format","connection","on","reason","emit"],"mappings":"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","file":"AmqpConnectionManager.js","sourcesContent":["import { EventEmitter } from 'events';\nimport amqp from 'amqplib';\nimport urlUtils from 'url';\n\nimport ChannelWrapper from './ChannelWrapper';\nimport { wait } from './helpers';\nimport pb from 'promise-breaker';\n\n// Default heartbeat time.\nconst HEARTBEAT_IN_SECONDS = 5;\n\n/* istanbul ignore next */\nfunction neverThrows() {\n return err =>\n setImmediate(() => {\n throw new Error(`AmqpConnectionManager - should never get here: ${err.message}\\n` +\n err.stack);\n });\n}\n\n//\n// Events:\n// * `connect({connection, url})` - Emitted whenever we connect to a broker.\n// * `disconnect({err})` - Emitted whenever we disconnect from a broker.\n//\nexport default class AmqpConnectionManager extends EventEmitter {\n /**\n * Create a new AmqplibConnectionManager.\n *\n * @param {(string|Object)[]} urls - An array of brokers to connect to.\n * Takes url strings or objects {url: string, connectionOptions?: object}\n * If present, a broker's [connectionOptions] will be used instead\n * of [options.connectionOptions] when passed to the amqplib connect method.\n * AmqplibConnectionManager will round-robin between them whenever it\n * needs to create a new connection.\n * @param {Object} [options={}] -\n * @param {number} [options.heartbeatIntervalInSeconds=5] - The interval,\n * in seconds, to send heartbeats.\n * @param {number} [options.reconnectTimeInSeconds] - The time to wait\n * before trying to reconnect. If not specified, defaults to\n * `heartbeatIntervalInSeconds`.\n * @param {Object} [options.connectionOptions] - Passed to the amqplib\n * connect method.\n * @param {function} [options.findServers] - A `fn(callback)` or a `fn()`\n * which returns a Promise. This should resolve to one or more servers\n * to connect to, either a single URL or an array of URLs. This is handy\n * when you're using a service discovery mechanism such as Consul or etcd.\n * Note that if this is supplied, then `urls` is ignored.\n */\n constructor(urls, options = {}) {\n super();\n if(!urls && !options.findServers) {\n throw new Error(\"Must supply either `urls` or `findServers`\");\n }\n this._channels = [];\n\n this._currentUrl = 0;\n this.connectionOptions = options.connectionOptions;\n\n this.heartbeatIntervalInSeconds = options.heartbeatIntervalInSeconds || HEARTBEAT_IN_SECONDS;\n this.reconnectTimeInSeconds = options.reconnectTimeInSeconds || this.heartbeatIntervalInSeconds;\n\n // There will be one listener per channel, and there could be a lot of channels, so disable warnings from node.\n this.setMaxListeners(0);\n\n this._findServers = options.findServers || (() => Promise.resolve(urls));\n\n this._connect();\n }\n\n // `options` here are any options that can be passed to ChannelWrapper.\n createChannel(options = {}) {\n const channel = new ChannelWrapper(this, options);\n this._channels.push(channel);\n channel.once('close', () => {\n this._channels = this._channels.filter(c => c !== channel);\n });\n return channel;\n }\n\n close() {\n if(this._closed) { return Promise.resolve(); }\n this._closed = true;\n\n return Promise.all(this._channels.map(channel => channel.close()))\n .catch(function() {\n // Ignore errors closing channels.\n })\n .then(() => {\n this._channels = [];\n if(this._currentConnection) {\n this._currentConnection.removeAllListeners('close');\n this._currentConnection.close();\n }\n this._currentConnection = null;\n });\n }\n\n isConnected() {\n return !!this._currentConnection;\n }\n\n _connect() {\n if(this._closed || this._connecting || this.isConnected()) {\n return Promise.resolve();\n }\n\n this._connecting = true;\n\n return Promise.resolve()\n .then(() => {\n if(!this._urls || (this._currentUrl >= this._urls.length)) {\n this._currentUrl = 0;\n return pb.callFn(this._findServers, 0, null);\n } else {\n return this._urls;\n }\n })\n .then(urls => {\n if(urls && !Array.isArray(urls)) { urls = [urls]; }\n this._urls = urls;\n\n if(!urls || (urls.length === 0)) {\n throw new Error('amqp-connection-manager: No servers found');\n }\n\n // Round robin between brokers\n const url = urls[this._currentUrl];\n this._currentUrl++;\n\n // url can be a string or object {url: string, connectionOptions?: object}\n const urlString = url.url || url;\n const connectionOptions = url.connectionOptions || this.connectionOptions;\n\n const amqpUrl = urlUtils.parse(urlString);\n if(amqpUrl.search) {\n amqpUrl.search += `&heartbeat=${this.heartbeatIntervalInSeconds}`;\n } else {\n amqpUrl.search = `?heartbeat=${this.heartbeatIntervalInSeconds}`;\n }\n\n return amqp.connect(urlUtils.format(amqpUrl), connectionOptions)\n .then(connection => {\n this._currentConnection = connection;\n\n //emit 'blocked' when RabbitMQ server decides to block the connection (resources running low)\n connection.on('blocked', reason => this.emit('blocked', { reason }));\n\n connection.on('unblocked', () => this.emit('unblocked'));\n\n // Reconnect if the broker goes away.\n connection.on('error', err =>\n Promise.resolve()\n .then(() =>this._currentConnection.close())\n .catch(() => { /* Ignore */ })\n .then(() => {\n this._currentConnection = null;\n this.emit('disconnect', { err });\n return this._connect();\n })\n // `_connect()` should never throw.\n .catch(neverThrows)\n );\n\n // Reconnect if the connection closes gracefully\n connection.on('close', err => {\n this._currentConnection = null;\n this.emit('disconnect', { err });\n\n wait(this.reconnectTimeInSeconds * 1000)\n .then(() => this._connect())\n // `_connect()` should never throw.\n .catch(neverThrows);\n });\n\n this._connecting = false;\n this.emit('connect', { connection, url: urlString });\n\n return null;\n });\n })\n .catch(err => {\n this.emit('disconnect', { err });\n\n // Connection failed...\n this._currentConnection = null;\n\n // TODO: Probably want to try right away here, especially if there are multiple brokers to try...\n return wait(this.reconnectTimeInSeconds * 1000)\n .then(() => {\n this._connecting = false;\n return this._connect();\n });\n });\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/AmqpConnectionManager.js"],"names":["HEARTBEAT_IN_SECONDS","neverThrows","err","setImmediate","Error","message","stack","AmqpConnectionManager","EventEmitter","constructor","urls","options","findServers","_channels","_currentUrl","connectionOptions","heartbeatIntervalInSeconds","reconnectTimeInSeconds","setMaxListeners","_findServers","Promise","resolve","_connect","createChannel","channel","ChannelWrapper","push","once","filter","c","close","_closed","all","map","catch","then","_currentConnection","removeAllListeners","isConnected","_connecting","_urls","length","pb","callFn","Array","isArray","url","urlString","amqpUrl","urlUtils","parse","search","amqp","connect","format","connection","on","reason","emit"],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport amqp from 'amqplib';\nimport urlUtils from 'url';\n\nimport ChannelWrapper from './ChannelWrapper';\nimport { wait } from './helpers';\nimport pb from 'promise-breaker';\n\n// Default heartbeat time.\nconst HEARTBEAT_IN_SECONDS = 5;\n\n/* istanbul ignore next */\nfunction neverThrows() {\n return err =>\n setImmediate(() => {\n throw new Error(`AmqpConnectionManager - should never get here: ${err.message}\\n` +\n err.stack);\n });\n}\n\n//\n// Events:\n// * `connect({connection, url})` - Emitted whenever we connect to a broker.\n// * `disconnect({err})` - Emitted whenever we disconnect from a broker.\n//\nexport default class AmqpConnectionManager extends EventEmitter {\n /**\n * Create a new AmqplibConnectionManager.\n *\n * @param {(string|Object)[]} urls - An array of brokers to connect to.\n * Takes url strings or objects {url: string, connectionOptions?: object}\n * If present, a broker's [connectionOptions] will be used instead\n * of [options.connectionOptions] when passed to the amqplib connect method.\n * AmqplibConnectionManager will round-robin between them whenever it\n * needs to create a new connection.\n * @param {Object} [options={}] -\n * @param {number} [options.heartbeatIntervalInSeconds=5] - The interval,\n * in seconds, to send heartbeats.\n * @param {number} [options.reconnectTimeInSeconds] - The time to wait\n * before trying to reconnect. If not specified, defaults to\n * `heartbeatIntervalInSeconds`.\n * @param {Object} [options.connectionOptions] - Passed to the amqplib\n * connect method.\n * @param {function} [options.findServers] - A `fn(callback)` or a `fn()`\n * which returns a Promise. This should resolve to one or more servers\n * to connect to, either a single URL or an array of URLs. This is handy\n * when you're using a service discovery mechanism such as Consul or etcd.\n * Note that if this is supplied, then `urls` is ignored.\n */\n constructor(urls, options = {}) {\n super();\n if(!urls && !options.findServers) {\n throw new Error(\"Must supply either `urls` or `findServers`\");\n }\n this._channels = [];\n\n this._currentUrl = 0;\n this.connectionOptions = options.connectionOptions;\n\n this.heartbeatIntervalInSeconds = options.heartbeatIntervalInSeconds || HEARTBEAT_IN_SECONDS;\n this.reconnectTimeInSeconds = options.reconnectTimeInSeconds || this.heartbeatIntervalInSeconds;\n\n // There will be one listener per channel, and there could be a lot of channels, so disable warnings from node.\n this.setMaxListeners(0);\n\n this._findServers = options.findServers || (() => Promise.resolve(urls));\n\n this._connect();\n }\n\n // `options` here are any options that can be passed to ChannelWrapper.\n createChannel(options = {}) {\n const channel = new ChannelWrapper(this, options);\n this._channels.push(channel);\n channel.once('close', () => {\n this._channels = this._channels.filter(c => c !== channel);\n });\n return channel;\n }\n\n close() {\n if(this._closed) { return Promise.resolve(); }\n this._closed = true;\n\n return Promise.all(this._channels.map(channel => channel.close()))\n .catch(function() {\n // Ignore errors closing channels.\n })\n .then(() => {\n this._channels = [];\n if(this._currentConnection) {\n this._currentConnection.removeAllListeners('close');\n this._currentConnection.close();\n }\n this._currentConnection = null;\n });\n }\n\n isConnected() {\n return !!this._currentConnection;\n }\n\n _connect() {\n if(this._closed || this._connecting || this.isConnected()) {\n return Promise.resolve();\n }\n\n this._connecting = true;\n\n return Promise.resolve()\n .then(() => {\n if(!this._urls || (this._currentUrl >= this._urls.length)) {\n this._currentUrl = 0;\n return pb.callFn(this._findServers, 0, null);\n } else {\n return this._urls;\n }\n })\n .then(urls => {\n if(urls && !Array.isArray(urls)) { urls = [urls]; }\n this._urls = urls;\n\n if(!urls || (urls.length === 0)) {\n throw new Error('amqp-connection-manager: No servers found');\n }\n\n // Round robin between brokers\n const url = urls[this._currentUrl];\n this._currentUrl++;\n\n // url can be a string or object {url: string, connectionOptions?: object}\n const urlString = url.url || url;\n const connectionOptions = url.connectionOptions || this.connectionOptions;\n\n const amqpUrl = urlUtils.parse(urlString);\n if(amqpUrl.search) {\n amqpUrl.search += `&heartbeat=${this.heartbeatIntervalInSeconds}`;\n } else {\n amqpUrl.search = `?heartbeat=${this.heartbeatIntervalInSeconds}`;\n }\n\n return amqp.connect(urlUtils.format(amqpUrl), connectionOptions)\n .then(connection => {\n this._currentConnection = connection;\n\n //emit 'blocked' when RabbitMQ server decides to block the connection (resources running low)\n connection.on('blocked', reason => this.emit('blocked', { reason }));\n\n connection.on('unblocked', () => this.emit('unblocked'));\n\n connection.on('error', (/* err */) => {\n // if this event was emitted, then the connection was already closed,\n // so no need to call #close here\n // also, 'close' is emitted after 'error',\n // so no need for work already done in 'close' handler\n });\n\n // Reconnect if the connection closes\n connection.on('close', err => {\n this._currentConnection = null;\n this.emit('disconnect', { err });\n\n wait(this.reconnectTimeInSeconds * 1000)\n .then(() => this._connect())\n // `_connect()` should never throw.\n .catch(neverThrows);\n });\n\n this._connecting = false;\n this.emit('connect', { connection, url: urlString });\n\n return null;\n });\n })\n .catch(err => {\n this.emit('disconnect', { err });\n\n // Connection failed...\n this._currentConnection = null;\n\n // TODO: Probably want to try right away here, especially if there are multiple brokers to try...\n return wait(this.reconnectTimeInSeconds * 1000)\n .then(() => {\n this._connecting = false;\n return this._connect();\n });\n });\n }\n}\n"],"file":"AmqpConnectionManager.js"}