amqp-connection-manager 3.5.1 → 3.8.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/README.md +9 -6
- package/dist/cjs/AmqpConnectionManager.js +37 -3
- package/dist/cjs/ChannelWrapper.js +260 -100
- package/dist/cjs/index.js +7 -2
- package/dist/esm/AmqpConnectionManager.d.ts +16 -2
- package/dist/esm/AmqpConnectionManager.js +35 -1
- package/dist/esm/AmqpConnectionManager.js.map +1 -1
- package/dist/esm/ChannelWrapper.d.ts +60 -6
- package/dist/esm/ChannelWrapper.js +260 -100
- package/dist/esm/ChannelWrapper.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +6 -1
- package/dist/esm/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://npmjs.org/package/amqp-connection-manager)
|
|
4
4
|

|
|
5
|
-
[](https://coveralls.io/github/jwalton/node-amqp-connection-manager?branch=master)
|
|
6
5
|
[](https://github.com/semantic-release/semantic-release)
|
|
7
6
|
|
|
8
|
-
[](https://david-dm.org/jwalton/node-amqp-connection-manager)
|
|
9
|
-
[](https://david-dm.org/jwalton/node-amqp-connection-manager#info=devDependencies)
|
|
10
|
-
[](https://david-dm.org/jwalton/node-amqp-connection-manager#info=peerDependencies)
|
|
11
|
-
|
|
12
7
|
Connection management for amqplib. This is a wrapper around [amqplib](http://www.squaremobius.net/amqp.node/) which provides automatic reconnects.
|
|
13
8
|
|
|
14
9
|
## Features
|
|
@@ -137,8 +132,10 @@ Options:
|
|
|
137
132
|
Note that `this` inside the setup function will the returned ChannelWrapper.
|
|
138
133
|
The ChannelWrapper has a special `context` member you can use to store
|
|
139
134
|
arbitrary data in.
|
|
140
|
-
- `options.json` if true, then ChannelWrapper assumes all messages passed to `publish()` and `sendToQueue()`
|
|
135
|
+
- `options.json` - if true, then ChannelWrapper assumes all messages passed to `publish()` and `sendToQueue()`
|
|
141
136
|
are plain JSON objects. These will be encoded automatically before being sent.
|
|
137
|
+
- `options.confirm` - if true (default), the created channel will be a ConfirmChannel
|
|
138
|
+
- `options.publishTimeout` - a default timeout for messages published to this channel.
|
|
142
139
|
|
|
143
140
|
### AmqpConnectionManager#isConnected()
|
|
144
141
|
|
|
@@ -183,6 +180,12 @@ callback) which resolves when the message is confirmed to have been delivered to
|
|
|
183
180
|
either the broker refuses the message, or if `close()` is called on the ChannelWrapper before the message can be
|
|
184
181
|
delivered.
|
|
185
182
|
|
|
183
|
+
Both of these functions take an additional option when passing options:
|
|
184
|
+
|
|
185
|
+
- `timeout` - If specified, if a messages is not acked by the amqp broker within the specified number of milliseconds,
|
|
186
|
+
the message will be rejected. Note that the message _may_ still end up getting delivered after the timeout, as we
|
|
187
|
+
have no way to cancel the in-flight request.
|
|
188
|
+
|
|
186
189
|
### ChannelWrapper#ack and ChannelWrapper#nack
|
|
187
190
|
|
|
188
191
|
These are just aliases for calling `ack()` and `nack()` on the underlying channel. They do nothing if the underlying
|
|
@@ -72,7 +72,41 @@ class AmqpConnectionManager extends events_1.EventEmitter {
|
|
|
72
72
|
// There will be one listener per channel, and there could be a lot of channels, so disable warnings from node.
|
|
73
73
|
this.setMaxListeners(0);
|
|
74
74
|
this._findServers = options.findServers || (() => Promise.resolve(urls));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Start the connect retries and await the first connect result. Even if the initial connect fails or timeouts, the
|
|
78
|
+
* reconnect attempts will continue in the background.
|
|
79
|
+
* @param [options={}] -
|
|
80
|
+
* @param [options.timeout] - Time to wait for initial connect
|
|
81
|
+
*/
|
|
82
|
+
async connect({ timeout } = {}) {
|
|
75
83
|
this._connect();
|
|
84
|
+
let reject;
|
|
85
|
+
const onDisconnect = ({ err }) => {
|
|
86
|
+
// Ignore disconnects caused by dead servers etc., but throw on operational errors like bad credentials.
|
|
87
|
+
if (err.isOperational) {
|
|
88
|
+
reject(err);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
await Promise.race([
|
|
93
|
+
(0, events_1.once)(this, 'connect'),
|
|
94
|
+
new Promise((_resolve, innerReject) => {
|
|
95
|
+
reject = innerReject;
|
|
96
|
+
this.on('disconnect', onDisconnect);
|
|
97
|
+
}),
|
|
98
|
+
...(timeout
|
|
99
|
+
? [
|
|
100
|
+
(0, helpers_js_1.wait)(timeout).promise.then(() => {
|
|
101
|
+
throw new Error('amqp-connection-manager: connect timeout');
|
|
102
|
+
}),
|
|
103
|
+
]
|
|
104
|
+
: []),
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
this.removeListener('disconnect', onDisconnect);
|
|
109
|
+
}
|
|
76
110
|
}
|
|
77
111
|
// `options` here are any options that can be passed to ChannelWrapper.
|
|
78
112
|
createChannel(options = {}) {
|
|
@@ -221,7 +255,7 @@ class AmqpConnectionManager extends events_1.EventEmitter {
|
|
|
221
255
|
this._currentConnection = undefined;
|
|
222
256
|
this._disconnectSent = true;
|
|
223
257
|
this.emit('disconnect', { err });
|
|
224
|
-
const handle = helpers_js_1.wait(this.reconnectTimeInSeconds * 1000);
|
|
258
|
+
const handle = (0, helpers_js_1.wait)(this.reconnectTimeInSeconds * 1000);
|
|
225
259
|
this._cancelRetriesHandler = handle.cancel;
|
|
226
260
|
handle.promise
|
|
227
261
|
.then(() => this._connect())
|
|
@@ -245,10 +279,10 @@ class AmqpConnectionManager extends events_1.EventEmitter {
|
|
|
245
279
|
this._connectPromise = undefined;
|
|
246
280
|
let handle;
|
|
247
281
|
if (err.name === 'OperationalError' && err.message === 'connect ETIMEDOUT') {
|
|
248
|
-
handle = helpers_js_1.wait(0);
|
|
282
|
+
handle = (0, helpers_js_1.wait)(0);
|
|
249
283
|
}
|
|
250
284
|
else {
|
|
251
|
-
handle = helpers_js_1.wait(this.reconnectTimeInSeconds * 1000);
|
|
285
|
+
handle = (0, helpers_js_1.wait)(this.reconnectTimeInSeconds * 1000);
|
|
252
286
|
}
|
|
253
287
|
this._cancelRetriesHandler = handle.cancel;
|
|
254
288
|
return handle.promise.then(() => this._connect());
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const events_1 = require("events");
|
|
7
7
|
const promise_breaker_1 = __importDefault(require("promise-breaker"));
|
|
8
|
+
const MAX_MESSAGES_PER_BATCH = 1000;
|
|
8
9
|
const IRRECOVERABLE_ERRORS = [
|
|
9
10
|
403,
|
|
10
11
|
404,
|
|
@@ -46,7 +47,7 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
46
47
|
*
|
|
47
48
|
*/
|
|
48
49
|
constructor(connectionManager, options = {}) {
|
|
49
|
-
var _a;
|
|
50
|
+
var _a, _b;
|
|
50
51
|
super();
|
|
51
52
|
/** If we're in the process of creating a channel, this is a Promise which
|
|
52
53
|
* will resolve when the channel is set up. Otherwise, this is `null`.
|
|
@@ -56,6 +57,12 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
56
57
|
this._messages = [];
|
|
57
58
|
/** Oublished, but not yet confirmed messages. */
|
|
58
59
|
this._unconfirmedMessages = [];
|
|
60
|
+
/** Consumers which will be reconnected on channel errors etc. */
|
|
61
|
+
this._consumers = [];
|
|
62
|
+
/**
|
|
63
|
+
* True to create a ConfirmChannel. False to create a regular Channel.
|
|
64
|
+
*/
|
|
65
|
+
this._confirm = true;
|
|
59
66
|
/**
|
|
60
67
|
* True if the "worker" is busy sending messages. False if we need to
|
|
61
68
|
* start the worker to get stuff done.
|
|
@@ -67,13 +74,20 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
67
74
|
* workers ever do wake up, they'll know to stop working.
|
|
68
75
|
*/
|
|
69
76
|
this._workerNumber = 0;
|
|
77
|
+
/**
|
|
78
|
+
* True if the underlying channel has room for more messages.
|
|
79
|
+
*/
|
|
80
|
+
this._channelHasRoom = true;
|
|
70
81
|
this._onConnect = this._onConnect.bind(this);
|
|
71
82
|
this._onDisconnect = this._onDisconnect.bind(this);
|
|
72
83
|
this._connectionManager = connectionManager;
|
|
84
|
+
this._confirm = (_a = options.confirm) !== null && _a !== void 0 ? _a : true;
|
|
73
85
|
this.name = options.name;
|
|
74
|
-
this.
|
|
86
|
+
this._publishTimeout = options.publishTimeout;
|
|
87
|
+
this._json = (_b = options.json) !== null && _b !== void 0 ? _b : false;
|
|
75
88
|
// Array of setup functions to call.
|
|
76
89
|
this._setups = [];
|
|
90
|
+
this._consumers = [];
|
|
77
91
|
if (options.setup) {
|
|
78
92
|
this._setups.push(options.setup);
|
|
79
93
|
}
|
|
@@ -172,15 +186,17 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
172
186
|
*/
|
|
173
187
|
publish(exchange, routingKey, content, options, done) {
|
|
174
188
|
return promise_breaker_1.default.addCallback(done, new Promise((resolve, reject) => {
|
|
175
|
-
|
|
189
|
+
const { timeout, ...opts } = options || {};
|
|
190
|
+
this._enqueueMessage({
|
|
176
191
|
type: 'publish',
|
|
177
192
|
exchange,
|
|
178
193
|
routingKey,
|
|
179
|
-
content,
|
|
180
|
-
options,
|
|
194
|
+
content: this._getEncodedMessage(content),
|
|
181
195
|
resolve,
|
|
182
196
|
reject,
|
|
183
|
-
|
|
197
|
+
options: opts,
|
|
198
|
+
isTimedout: false,
|
|
199
|
+
}, timeout || this._publishTimeout);
|
|
184
200
|
this._startWorker();
|
|
185
201
|
}));
|
|
186
202
|
}
|
|
@@ -194,25 +210,55 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
194
210
|
* `message` here should be a JSON-able object.
|
|
195
211
|
*/
|
|
196
212
|
sendToQueue(queue, content, options, done) {
|
|
213
|
+
const encodedContent = this._getEncodedMessage(content);
|
|
197
214
|
return promise_breaker_1.default.addCallback(done, new Promise((resolve, reject) => {
|
|
198
|
-
|
|
215
|
+
const { timeout, ...opts } = options || {};
|
|
216
|
+
this._enqueueMessage({
|
|
199
217
|
type: 'sendToQueue',
|
|
200
218
|
queue,
|
|
201
|
-
content,
|
|
202
|
-
options,
|
|
219
|
+
content: encodedContent,
|
|
203
220
|
resolve,
|
|
204
221
|
reject,
|
|
205
|
-
|
|
206
|
-
|
|
222
|
+
options: opts,
|
|
223
|
+
isTimedout: false,
|
|
224
|
+
}, timeout || this._publishTimeout);
|
|
225
|
+
this._startWorker();
|
|
207
226
|
}));
|
|
208
227
|
}
|
|
228
|
+
_enqueueMessage(message, timeout) {
|
|
229
|
+
if (timeout) {
|
|
230
|
+
message.timeout = setTimeout(() => {
|
|
231
|
+
let idx = this._messages.indexOf(message);
|
|
232
|
+
if (idx !== -1) {
|
|
233
|
+
this._messages.splice(idx, 1);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
idx = this._unconfirmedMessages.indexOf(message);
|
|
237
|
+
if (idx !== -1) {
|
|
238
|
+
this._unconfirmedMessages.splice(idx, 1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
message.isTimedout = true;
|
|
242
|
+
message.reject(new Error('timeout'));
|
|
243
|
+
}, timeout);
|
|
244
|
+
}
|
|
245
|
+
this._messages.push(message);
|
|
246
|
+
}
|
|
209
247
|
// Called whenever we connect to the broker.
|
|
210
248
|
async _onConnect({ connection }) {
|
|
211
249
|
this._irrecoverableCode = undefined;
|
|
212
250
|
try {
|
|
213
|
-
|
|
251
|
+
let channel;
|
|
252
|
+
if (this._confirm) {
|
|
253
|
+
channel = await connection.createConfirmChannel();
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
channel = await connection.createChannel();
|
|
257
|
+
}
|
|
214
258
|
this._channel = channel;
|
|
259
|
+
this._channelHasRoom = true;
|
|
215
260
|
channel.on('close', () => this._onChannelClose(channel));
|
|
261
|
+
channel.on('drain', () => this._onChannelDrain());
|
|
216
262
|
this._settingUp = Promise.all(this._setups.map((setupFn) =>
|
|
217
263
|
// TODO: Use a timeout here to guard against setupFns that never resolve?
|
|
218
264
|
promise_breaker_1.default.call(setupFn, this, channel).catch((err) => {
|
|
@@ -221,7 +267,11 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
221
267
|
return;
|
|
222
268
|
}
|
|
223
269
|
this.emit('error', err, { name: this.name });
|
|
224
|
-
})))
|
|
270
|
+
})))
|
|
271
|
+
.then(() => {
|
|
272
|
+
return Promise.all(this._consumers.map((c) => this._reconnectConsumer(c)));
|
|
273
|
+
})
|
|
274
|
+
.then(() => {
|
|
225
275
|
this._settingUp = undefined;
|
|
226
276
|
});
|
|
227
277
|
await this._settingUp;
|
|
@@ -229,13 +279,6 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
229
279
|
// Can happen if channel closes while we're setting up.
|
|
230
280
|
return;
|
|
231
281
|
}
|
|
232
|
-
if (this._unconfirmedMessages.length > 0) {
|
|
233
|
-
// requeue any messages that were left unconfirmed when connection was lost
|
|
234
|
-
let message;
|
|
235
|
-
while ((message = this._unconfirmedMessages.shift())) {
|
|
236
|
-
this._messages.push(message);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
282
|
// Since we just connected, publish any queued messages
|
|
240
283
|
this._startWorker();
|
|
241
284
|
this.emit('connect');
|
|
@@ -253,6 +296,11 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
253
296
|
}
|
|
254
297
|
// Wait for another reconnect to create a new channel.
|
|
255
298
|
}
|
|
299
|
+
/** Called whenever the channel drains. */
|
|
300
|
+
_onChannelDrain() {
|
|
301
|
+
this._channelHasRoom = true;
|
|
302
|
+
this._startWorker();
|
|
303
|
+
}
|
|
256
304
|
// Called whenever we disconnect from the AMQP server.
|
|
257
305
|
_onDisconnect(ex) {
|
|
258
306
|
this._irrecoverableCode = ex.err instanceof Error ? ex.err.code : undefined;
|
|
@@ -275,11 +323,21 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
275
323
|
this._working = false;
|
|
276
324
|
if (this._messages.length !== 0) {
|
|
277
325
|
// Reject any unsent messages.
|
|
278
|
-
this._messages.forEach((message) =>
|
|
326
|
+
this._messages.forEach((message) => {
|
|
327
|
+
if (message.timeout) {
|
|
328
|
+
clearTimeout(message.timeout);
|
|
329
|
+
}
|
|
330
|
+
message.reject(new Error('Channel closed'));
|
|
331
|
+
});
|
|
279
332
|
}
|
|
280
333
|
if (this._unconfirmedMessages.length !== 0) {
|
|
281
334
|
// Reject any unconfirmed messages.
|
|
282
|
-
this._unconfirmedMessages.forEach((message) =>
|
|
335
|
+
this._unconfirmedMessages.forEach((message) => {
|
|
336
|
+
if (message.timeout) {
|
|
337
|
+
clearTimeout(message.timeout);
|
|
338
|
+
}
|
|
339
|
+
message.reject(new Error('Channel closed'));
|
|
340
|
+
});
|
|
283
341
|
}
|
|
284
342
|
this._connectionManager.removeListener('connect', this._onConnect);
|
|
285
343
|
this._connectionManager.removeListener('disconnect', this._onDisconnect);
|
|
@@ -290,7 +348,7 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
290
348
|
});
|
|
291
349
|
}
|
|
292
350
|
_shouldPublish() {
|
|
293
|
-
return this._messages.length > 0 && !this._settingUp && !!this._channel;
|
|
351
|
+
return (this._messages.length > 0 && !this._settingUp && !!this._channel && this._channelHasRoom);
|
|
294
352
|
}
|
|
295
353
|
// Start publishing queued messages, if there isn't already a worker doing this.
|
|
296
354
|
_startWorker() {
|
|
@@ -304,6 +362,44 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
304
362
|
_canWaitReconnection() {
|
|
305
363
|
return !this._irrecoverableCode || !IRRECOVERABLE_ERRORS.includes(this._irrecoverableCode);
|
|
306
364
|
}
|
|
365
|
+
_messageResolved(message, result) {
|
|
366
|
+
removeUnconfirmedMessage(this._unconfirmedMessages, message);
|
|
367
|
+
message.resolve(result);
|
|
368
|
+
}
|
|
369
|
+
_messageRejected(message, err) {
|
|
370
|
+
if (!this._channel && this._canWaitReconnection()) {
|
|
371
|
+
// Tried to write to a closed channel. Leave the message in the queue and we'll try again when
|
|
372
|
+
// we reconnect.
|
|
373
|
+
removeUnconfirmedMessage(this._unconfirmedMessages, message);
|
|
374
|
+
this._messages.push(message);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
// Something went wrong trying to send this message - could be JSON.stringify failed, could be
|
|
378
|
+
// the broker rejected the message. Either way, reject it back
|
|
379
|
+
removeUnconfirmedMessage(this._unconfirmedMessages, message);
|
|
380
|
+
message.reject(err);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
_getEncodedMessage(content) {
|
|
384
|
+
let encodedMessage;
|
|
385
|
+
if (this._json) {
|
|
386
|
+
encodedMessage = Buffer.from(JSON.stringify(content));
|
|
387
|
+
}
|
|
388
|
+
else if (typeof content === 'string') {
|
|
389
|
+
encodedMessage = Buffer.from(content);
|
|
390
|
+
}
|
|
391
|
+
else if (content instanceof Buffer) {
|
|
392
|
+
encodedMessage = content;
|
|
393
|
+
}
|
|
394
|
+
else if (typeof content === 'object' && typeof content.toString === 'function') {
|
|
395
|
+
encodedMessage = Buffer.from(content.toString());
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
console.warn('amqp-connection-manager: Sending JSON message, but json option not speicifed');
|
|
399
|
+
encodedMessage = Buffer.from(JSON.stringify(content));
|
|
400
|
+
}
|
|
401
|
+
return encodedMessage;
|
|
402
|
+
}
|
|
307
403
|
_publishQueuedMessages(workerNumber) {
|
|
308
404
|
const channel = this._channel;
|
|
309
405
|
if (!channel ||
|
|
@@ -314,90 +410,154 @@ class ChannelWrapper extends events_1.EventEmitter {
|
|
|
314
410
|
this._working = false;
|
|
315
411
|
return;
|
|
316
412
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
else if (typeof message.content === 'string') {
|
|
327
|
-
encodedMessage = Buffer.from(message.content);
|
|
328
|
-
}
|
|
329
|
-
else if (message.content instanceof Buffer) {
|
|
330
|
-
encodedMessage = message.content;
|
|
331
|
-
}
|
|
332
|
-
else if (typeof message.content === 'object' &&
|
|
333
|
-
typeof message.content.toString === 'function') {
|
|
334
|
-
encodedMessage = Buffer.from(message.content.toString());
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
throw new Error('Invalid message content');
|
|
413
|
+
try {
|
|
414
|
+
// Send messages in batches of 1000 - don't want to starve the event loop.
|
|
415
|
+
let sendsLeft = MAX_MESSAGES_PER_BATCH;
|
|
416
|
+
while (this._channelHasRoom && this._messages.length > 0 && sendsLeft > 0) {
|
|
417
|
+
sendsLeft--;
|
|
418
|
+
const message = this._messages.shift();
|
|
419
|
+
if (!message) {
|
|
420
|
+
break;
|
|
338
421
|
}
|
|
339
|
-
let
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
422
|
+
let thisCanSend = true;
|
|
423
|
+
switch (message.type) {
|
|
424
|
+
case 'publish': {
|
|
425
|
+
if (this._confirm) {
|
|
426
|
+
this._unconfirmedMessages.push(message);
|
|
427
|
+
thisCanSend = this._channelHasRoom = channel.publish(message.exchange, message.routingKey, message.content, message.options, (err) => {
|
|
428
|
+
if (message.isTimedout) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (message.timeout) {
|
|
432
|
+
clearTimeout(message.timeout);
|
|
433
|
+
}
|
|
434
|
+
if (err) {
|
|
435
|
+
this._messageRejected(message, err);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
this._messageResolved(message, thisCanSend);
|
|
439
|
+
}
|
|
352
440
|
});
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
if (message.timeout) {
|
|
444
|
+
clearTimeout(message.timeout);
|
|
445
|
+
}
|
|
446
|
+
thisCanSend = this._channelHasRoom = channel.publish(message.exchange, message.routingKey, message.content, message.options);
|
|
447
|
+
message.resolve(thisCanSend);
|
|
448
|
+
}
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case 'sendToQueue': {
|
|
452
|
+
if (this._confirm) {
|
|
453
|
+
this._unconfirmedMessages.push(message);
|
|
454
|
+
thisCanSend = this._channelHasRoom = channel.sendToQueue(message.queue, message.content, message.options, (err) => {
|
|
455
|
+
if (message.isTimedout) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (message.timeout) {
|
|
459
|
+
clearTimeout(message.timeout);
|
|
460
|
+
}
|
|
461
|
+
if (err) {
|
|
462
|
+
this._messageRejected(message, err);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
this._messageResolved(message, thisCanSend);
|
|
466
|
+
}
|
|
363
467
|
});
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
if (message.timeout) {
|
|
471
|
+
clearTimeout(message.timeout);
|
|
472
|
+
}
|
|
473
|
+
thisCanSend = this._channelHasRoom = channel.sendToQueue(message.queue, message.content, message.options);
|
|
474
|
+
message.resolve(thisCanSend);
|
|
475
|
+
}
|
|
476
|
+
break;
|
|
367
477
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
478
|
+
/* istanbul ignore next */
|
|
479
|
+
default:
|
|
480
|
+
throw new Error(`Unhandled message type ${message.type}`);
|
|
371
481
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// we reconnect.
|
|
384
|
-
removeUnconfirmedMessage(this._unconfirmedMessages, message);
|
|
385
|
-
this._messages.push(message);
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
// Something went wrong trying to send this message - could be JSON.stringify failed, could be
|
|
389
|
-
// the broker rejected the message. Either way, reject it back
|
|
390
|
-
removeUnconfirmedMessage(this._unconfirmedMessages, message);
|
|
391
|
-
message.reject(err);
|
|
392
|
-
}
|
|
393
|
-
})
|
|
394
|
-
.catch(
|
|
395
|
-
/* istanbul ignore next */ (err) => {
|
|
396
|
-
this.emit('error', err);
|
|
397
|
-
this._working = false;
|
|
398
|
-
});
|
|
482
|
+
}
|
|
483
|
+
// If we didn't send all the messages, send some more...
|
|
484
|
+
if (this._channelHasRoom && this._messages.length > 0) {
|
|
485
|
+
setImmediate(() => this._publishQueuedMessages(workerNumber));
|
|
486
|
+
}
|
|
487
|
+
this._working = false;
|
|
488
|
+
/* istanbul ignore next */
|
|
489
|
+
}
|
|
490
|
+
catch (err) {
|
|
491
|
+
this._working = false;
|
|
492
|
+
this.emit('error', err);
|
|
399
493
|
}
|
|
400
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Setup a consumer
|
|
497
|
+
* This consumer will be reconnected on cancellation and channel errors.
|
|
498
|
+
*/
|
|
499
|
+
async consume(queue, onMessage, options = {}) {
|
|
500
|
+
const consumer = {
|
|
501
|
+
consumerTag: null,
|
|
502
|
+
queue,
|
|
503
|
+
onMessage,
|
|
504
|
+
options,
|
|
505
|
+
};
|
|
506
|
+
this._consumers.push(consumer);
|
|
507
|
+
await this._consume(consumer);
|
|
508
|
+
}
|
|
509
|
+
async _consume(consumer) {
|
|
510
|
+
if (!this._channel) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const { prefetch, ...options } = consumer.options;
|
|
514
|
+
if (typeof prefetch === 'number') {
|
|
515
|
+
this._channel.prefetch(prefetch, false);
|
|
516
|
+
}
|
|
517
|
+
const { consumerTag } = await this._channel.consume(consumer.queue, (msg) => {
|
|
518
|
+
if (!msg) {
|
|
519
|
+
consumer.consumerTag = null;
|
|
520
|
+
this._reconnectConsumer(consumer).catch((err) => {
|
|
521
|
+
if (err.isOperational && err.message.includes('BasicConsume; 404')) {
|
|
522
|
+
// Ignore errors caused by queue not declared. In
|
|
523
|
+
// those cases the connection will reconnect and
|
|
524
|
+
// then consumers reestablished. The full reconnect
|
|
525
|
+
// might be avoided if we assert the queue again
|
|
526
|
+
// before starting to consume.
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
throw err;
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
consumer.onMessage(msg);
|
|
534
|
+
}, options);
|
|
535
|
+
consumer.consumerTag = consumerTag;
|
|
536
|
+
}
|
|
537
|
+
async _reconnectConsumer(consumer) {
|
|
538
|
+
if (!this._consumers.includes(consumer)) {
|
|
539
|
+
// Intentionally canceled
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
await this._consume(consumer);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Cancel all consumers
|
|
546
|
+
*/
|
|
547
|
+
async cancelAll() {
|
|
548
|
+
const consumers = this._consumers;
|
|
549
|
+
this._consumers = [];
|
|
550
|
+
if (!this._channel) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const channel = this._channel;
|
|
554
|
+
await Promise.all(consumers.reduce((acc, consumer) => {
|
|
555
|
+
if (consumer.consumerTag) {
|
|
556
|
+
acc.push(channel.cancel(consumer.consumerTag));
|
|
557
|
+
}
|
|
558
|
+
return acc;
|
|
559
|
+
}, []));
|
|
560
|
+
}
|
|
401
561
|
/** Send an `ack` to the underlying channel. */
|
|
402
562
|
ack(message, allUpTo) {
|
|
403
563
|
this._channel && this._channel.ack(message, allUpTo);
|
package/dist/cjs/index.js
CHANGED
|
@@ -3,10 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.connect = void 0;
|
|
6
|
+
exports.AmqpConnectionManagerClass = exports.connect = void 0;
|
|
7
7
|
const AmqpConnectionManager_js_1 = __importDefault(require("./AmqpConnectionManager.js"));
|
|
8
|
+
exports.AmqpConnectionManagerClass = AmqpConnectionManager_js_1.default;
|
|
8
9
|
function connect(urls, options) {
|
|
9
|
-
|
|
10
|
+
const conn = new AmqpConnectionManager_js_1.default(urls, options);
|
|
11
|
+
conn.connect().catch(() => {
|
|
12
|
+
/* noop */
|
|
13
|
+
});
|
|
14
|
+
return conn;
|
|
10
15
|
}
|
|
11
16
|
exports.connect = connect;
|
|
12
17
|
const amqp = { connect };
|
|
@@ -59,6 +59,7 @@ export interface IAmqpConnectionManager {
|
|
|
59
59
|
addListener(event: 'disconnect', listener: (arg: {
|
|
60
60
|
err: Error;
|
|
61
61
|
}) => void): this;
|
|
62
|
+
listeners(eventName: string | symbol): Function[];
|
|
62
63
|
on(event: string, listener: (...args: any[]) => void): this;
|
|
63
64
|
on(event: 'connect', listener: ConnectListener): this;
|
|
64
65
|
on(event: 'blocked', listener: (arg: {
|
|
@@ -96,13 +97,17 @@ export interface IAmqpConnectionManager {
|
|
|
96
97
|
err: Error;
|
|
97
98
|
}) => void): this;
|
|
98
99
|
removeListener(event: string, listener: (...args: any[]) => void): this;
|
|
100
|
+
connect(options?: {
|
|
101
|
+
timeout?: number;
|
|
102
|
+
}): Promise<void>;
|
|
103
|
+
reconnect(): void;
|
|
99
104
|
createChannel(options?: CreateChannelOpts): ChannelWrapper;
|
|
100
105
|
close(): Promise<void>;
|
|
101
106
|
isConnected(): boolean;
|
|
102
107
|
/** The current connection. */
|
|
103
|
-
|
|
108
|
+
readonly connection: Connection | undefined;
|
|
104
109
|
/** Returns the number of registered channels. */
|
|
105
|
-
|
|
110
|
+
readonly channelCount: number;
|
|
106
111
|
}
|
|
107
112
|
export default class AmqpConnectionManager extends EventEmitter implements IAmqpConnectionManager {
|
|
108
113
|
private _channels;
|
|
@@ -148,6 +153,15 @@ export default class AmqpConnectionManager extends EventEmitter implements IAmqp
|
|
|
148
153
|
* Note that if this is supplied, then `urls` is ignored.
|
|
149
154
|
*/
|
|
150
155
|
constructor(urls: ConnectionUrl | ConnectionUrl[] | undefined | null, options?: AmqpConnectionManagerOptions);
|
|
156
|
+
/**
|
|
157
|
+
* Start the connect retries and await the first connect result. Even if the initial connect fails or timeouts, the
|
|
158
|
+
* reconnect attempts will continue in the background.
|
|
159
|
+
* @param [options={}] -
|
|
160
|
+
* @param [options.timeout] - Time to wait for initial connect
|
|
161
|
+
*/
|
|
162
|
+
connect({ timeout }?: {
|
|
163
|
+
timeout?: number;
|
|
164
|
+
}): Promise<void>;
|
|
151
165
|
createChannel(options?: CreateChannelOpts): ChannelWrapper;
|
|
152
166
|
close(): Promise<void>;
|
|
153
167
|
isConnected(): boolean;
|