@droidsolutions-oss/amqp-ts 3.0.4 → 4.0.0-develop.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -45
- package/lib/AmqpProperties.js +1 -2
- package/lib/Binding.d.ts +36 -6
- package/lib/Binding.js +244 -75
- package/lib/Binding.js.map +1 -1
- package/lib/Connection/Connection.d.ts +87 -46
- package/lib/Connection/Connection.js +360 -181
- package/lib/Connection/Connection.js.map +1 -1
- package/lib/Connection/ConnectionMetrics.js +1 -2
- package/lib/Connection/RecoveryOptions.d.ts +6 -0
- package/lib/Connection/RecoveryOptions.js +2 -0
- package/lib/Connection/RecoveryOptions.js.map +1 -0
- package/lib/Connection/Topology.d.ts +1 -1
- package/lib/Connection/Topology.js +1 -2
- package/lib/Deferred.d.ts +18 -0
- package/lib/Deferred.js +33 -0
- package/lib/Deferred.js.map +1 -0
- package/lib/Exchange/DeclarationOptions.js +1 -2
- package/lib/Exchange/Exchange.d.ts +38 -17
- package/lib/Exchange/Exchange.js +204 -159
- package/lib/Exchange/Exchange.js.map +1 -1
- package/lib/Exchange/ExchangeType.js +1 -2
- package/lib/Exchange/InitializeResult.js +1 -2
- package/lib/LoggerFactory.js +1 -5
- package/lib/LoggerFactory.js.map +1 -1
- package/lib/Message.d.ts +11 -10
- package/lib/Message.js +66 -37
- package/lib/Message.js.map +1 -1
- package/lib/Queue/DeclarationOptions.js +1 -2
- package/lib/Queue/DeleteResult.js +1 -2
- package/lib/Queue/InitializeResult.js +1 -2
- package/lib/Queue/Queue.d.ts +87 -26
- package/lib/Queue/Queue.js +440 -207
- package/lib/Queue/Queue.js.map +1 -1
- package/lib/Queue/StartConsumerResult.js +1 -2
- package/lib/amqp-ts.d.ts +0 -1
- package/lib/amqp-ts.js +2 -7
- package/lib/amqp-ts.js.map +1 -1
- package/lib/errorMessage.d.ts +1 -0
- package/lib/errorMessage.js +18 -0
- package/lib/errorMessage.js.map +1 -0
- package/lib/index.d.ts +19 -14
- package/lib/index.js +7 -13
- package/lib/index.js.map +1 -1
- package/package.json +38 -60
- package/lib/Connection/ReconnectStrategy.d.ts +0 -4
- package/lib/Connection/ReconnectStrategy.js +0 -3
- package/lib/Connection/ReconnectStrategy.js.map +0 -1
|
@@ -1,151 +1,365 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Connection extends
|
|
1
|
+
import { connect } from "amqplib";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { Binding } from "../Binding.js";
|
|
4
|
+
import { Deferred } from "../Deferred.js";
|
|
5
|
+
import { Exchange } from "../Exchange/Exchange.js";
|
|
6
|
+
import { errorMessage } from "../errorMessage.js";
|
|
7
|
+
import { EmtpyLogger } from "../LoggerFactory.js";
|
|
8
|
+
import { Queue } from "../Queue/Queue.js";
|
|
9
|
+
export class Connection extends EventEmitter {
|
|
10
|
+
loggerFactory;
|
|
11
|
+
/**
|
|
12
|
+
* Resolves once the connection is established and the declared topology was set up. It is replaced by a pending
|
|
13
|
+
* promise when the connection is lost and resolves again after the next successful reconnect. It rejects when
|
|
14
|
+
* recovery gives up or the connection is closed before it was established.
|
|
15
|
+
*
|
|
16
|
+
* It only reflects the connection: an exchange, queue, binding or consumer that fails on its own does not keep it
|
|
17
|
+
* from resolving. Use {@link completeConfiguration} to wait for the topology.
|
|
18
|
+
*/
|
|
19
|
+
initialized;
|
|
20
|
+
isConnected = false;
|
|
21
|
+
/**
|
|
22
|
+
* The current underlying amqplib model, `undefined` while disconnected.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
_model;
|
|
26
|
+
/**
|
|
27
|
+
* Identifies the current underlying model. Changes on each successful connect, so entities can tell whether they
|
|
28
|
+
* are initialized on the current connection.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
_generation = 0;
|
|
32
|
+
/** @internal */
|
|
33
|
+
_exchanges;
|
|
34
|
+
/** @internal */
|
|
35
|
+
_queues;
|
|
36
|
+
/** @internal */
|
|
37
|
+
_bindings;
|
|
38
|
+
_deferred;
|
|
39
|
+
_recovering;
|
|
40
|
+
/** Counter for connect attempts that reach `setup`; each of them gets its own generation. */
|
|
41
|
+
_lastGeneration = 0;
|
|
42
|
+
/** The model `setup` is currently running for, and its generation. */
|
|
43
|
+
_setupModel;
|
|
44
|
+
_setupGeneration = 0;
|
|
45
|
+
_closedModels = new WeakSet();
|
|
46
|
+
_connectedBefore = false;
|
|
47
|
+
_isClosing = false;
|
|
48
|
+
_closing;
|
|
49
|
+
/** Why recovery is over, set by {@link close} or when recovery gave up. Nothing is initialized after that. */
|
|
50
|
+
_stopError;
|
|
51
|
+
/** Why exchanges, queues and bindings failed on their own, until the same one is declared again. */
|
|
52
|
+
_failures = new Map();
|
|
53
|
+
log;
|
|
54
|
+
_sentMessagesCounter = 0;
|
|
55
|
+
_receivedMessagesCounter = 0;
|
|
10
56
|
/**
|
|
11
57
|
*
|
|
12
58
|
* @param url The url to RabbitMQ
|
|
13
|
-
* @param socketOptions Socket options are passed to
|
|
14
|
-
* @param
|
|
59
|
+
* @param socketOptions Socket options are passed to amqplib's `connect`.
|
|
60
|
+
* @param recovery Options for the automatic reconnect, passed to amqplib's connection recovery. By default it retries
|
|
61
|
+
* forever with exponential backoff.
|
|
15
62
|
* @param loggerFactory A factory function that returns a logger.
|
|
16
63
|
*/
|
|
17
|
-
constructor(url = "amqp://localhost", socketOptions = {},
|
|
64
|
+
constructor(url = "amqp://localhost", socketOptions = {}, recovery = {}, loggerFactory) {
|
|
18
65
|
super();
|
|
19
66
|
this.loggerFactory = loggerFactory;
|
|
20
|
-
this.isConnected = false;
|
|
21
|
-
this.connectedBefore = false;
|
|
22
|
-
this._rebuilding = false;
|
|
23
|
-
this._isClosing = false;
|
|
24
|
-
this._sentMessagesCounter = 0;
|
|
25
|
-
this._receivedMessagesCounter = 0;
|
|
26
67
|
if (!this.loggerFactory) {
|
|
27
|
-
this.loggerFactory = (_, __) => new
|
|
68
|
+
this.loggerFactory = (_, __) => new EmtpyLogger();
|
|
28
69
|
}
|
|
29
70
|
this.log = this.loggerFactory(this.constructor, { module: "amqp-ts" });
|
|
30
|
-
this.url = url;
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
32
|
-
this.socketOptions = socketOptions;
|
|
33
|
-
this.reconnectStrategy = reconnectStrategy;
|
|
34
71
|
this._exchanges = {};
|
|
35
72
|
this._queues = {};
|
|
36
73
|
this._bindings = {};
|
|
37
|
-
this.
|
|
74
|
+
this._deferred = new Deferred();
|
|
75
|
+
this.initialized = this._deferred.promise;
|
|
76
|
+
// waitForConnect: false resolves right away and defers the first attempt, so the listeners are attached in time
|
|
77
|
+
this._recovering = connect(url, {
|
|
78
|
+
...socketOptions,
|
|
79
|
+
recovery: { ...recovery, waitForConnect: false, setup: (model) => this._setup(model) },
|
|
80
|
+
});
|
|
81
|
+
this._recovering.then((recovering) => this._listen(recovering),
|
|
82
|
+
/* istanbul ignore next */ (err) => this._giveUp(err));
|
|
38
83
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
close() {
|
|
85
|
+
if (this._closing === undefined) {
|
|
86
|
+
this._isClosing = true;
|
|
87
|
+
// buffered sends and anyone awaiting initialized must not wait for a connection that never comes
|
|
88
|
+
this._stop(new Error("Connection closed"));
|
|
89
|
+
this._closing = this._close();
|
|
90
|
+
}
|
|
91
|
+
return this._closing;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A new deferred for an entity's promise. Once the connection was closed or recovery gave up nothing would
|
|
95
|
+
* ever settle it, so it is rejected right away.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
_createDeferred() {
|
|
99
|
+
const deferred = new Deferred();
|
|
100
|
+
if (this._stopError !== undefined) {
|
|
101
|
+
deferred.reject(this._stopError);
|
|
102
|
+
}
|
|
103
|
+
return deferred;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* An exchange, queue or binding failed on its own, not because the connection was lost: it is no longer
|
|
107
|
+
* declared and its {@link initialized} is rejected.
|
|
108
|
+
*
|
|
109
|
+
* The bindings of a failed exchange or queue can never be created any more, so they are rejected and removed as
|
|
110
|
+
* well. The failure is emitted as `error_connection`, see {@link _reportFailure}.
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
_onEntityFailed(entity, err) {
|
|
114
|
+
if (!(entity instanceof Binding)) {
|
|
115
|
+
for (const [id, binding] of Object.entries(this._bindings)) {
|
|
116
|
+
if (binding._source === entity || binding._destination === entity) {
|
|
117
|
+
delete this._bindings[id];
|
|
118
|
+
binding._rejectPending(err);
|
|
62
119
|
}
|
|
63
120
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
121
|
+
}
|
|
122
|
+
this._failures.set(Connection._failureKey(entity), err);
|
|
123
|
+
this._reportFailure(err);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* An exchange, queue or binding is declared (again): a failure of an earlier one with the same name no longer
|
|
127
|
+
* counts for {@link completeConfiguration}.
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
_forgetFailure(entity) {
|
|
131
|
+
this._failures.delete(Connection._failureKey(entity));
|
|
132
|
+
}
|
|
133
|
+
static _failureKey(entity) {
|
|
134
|
+
if (entity instanceof Binding) {
|
|
135
|
+
return `binding ${entity._id}`;
|
|
136
|
+
}
|
|
137
|
+
return `${entity instanceof Queue ? "queue" : "exchange"} ${entity.name}`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Emits `error_connection` for an exchange, queue, binding or consumer that failed on its own, whether it was
|
|
141
|
+
* declared for the first time or again during recovery. Nobody has to await the entity's promise, for example after
|
|
142
|
+
* a `declareQueue` nobody awaits or during recovery, so this is the one signal that always arrives.
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
_reportFailure(err) {
|
|
146
|
+
this._emitSafely("error_connection", err);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The model entities are initialized on right now: the current one, or while `setup` runs the one it runs for.
|
|
150
|
+
* `undefined` while disconnected.
|
|
151
|
+
* @internal
|
|
152
|
+
*/
|
|
153
|
+
get _activeModel() {
|
|
154
|
+
return this._model ?? this._setupModel;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Whether the connection was closed or recovery gave up, so nothing is initialized any more.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
get _isStopped() {
|
|
161
|
+
return this._isClosing || this._stopError !== undefined;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Whether the given model has closed.
|
|
165
|
+
* @internal
|
|
166
|
+
*/
|
|
167
|
+
_isModelClosed(model) {
|
|
168
|
+
return this._closedModels.has(model);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Whether a channel of the given model may be re-created after it closed: the model is open, and it is
|
|
172
|
+
* either the current one or the one `setup` is running for.
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
_isModelUsable(model) {
|
|
176
|
+
return !this._isClosing && !this._closedModels.has(model) && (model === this._model || model === this._setupModel);
|
|
177
|
+
}
|
|
178
|
+
async _close() {
|
|
179
|
+
const recovering = await this._recovering.catch(() => undefined);
|
|
180
|
+
if (recovering) {
|
|
181
|
+
await recovering.close();
|
|
182
|
+
}
|
|
183
|
+
this._model = undefined;
|
|
184
|
+
this.isConnected = false;
|
|
185
|
+
this.log.info("AMQP Connection closed.");
|
|
186
|
+
this._emitSafely("close_connection");
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Recovery is over, because of {@link close} or because it gave up: rejects every promise that is still pending,
|
|
190
|
+
* the connection's and the entities', since nothing settles them any more.
|
|
191
|
+
*/
|
|
192
|
+
_stop(err) {
|
|
193
|
+
this._stopError ??= err;
|
|
194
|
+
if (!this._deferred.settled) {
|
|
195
|
+
this._deferred.reject(err);
|
|
196
|
+
}
|
|
197
|
+
for (const exchange of Object.values(this._exchanges)) {
|
|
198
|
+
exchange._rejectPending(err);
|
|
199
|
+
exchange._notifyChanged();
|
|
200
|
+
}
|
|
201
|
+
for (const queue of Object.values(this._queues)) {
|
|
202
|
+
queue._rejectPending(err);
|
|
203
|
+
queue._notifyChanged();
|
|
204
|
+
}
|
|
205
|
+
for (const binding of Object.values(this._bindings)) {
|
|
206
|
+
binding._rejectPending(err);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Emits an event to the user's listeners. Most events are emitted from within amqplib's recovery, where a throwing
|
|
211
|
+
* listener would stop the reconnect from being scheduled, or from a promise chain, where it would be an unhandled
|
|
212
|
+
* rejection. So a listener's error is logged instead, and the listeners after it still get the event. The order is
|
|
213
|
+
* that of `emit`, and `once` listeners remove themselves as usual.
|
|
214
|
+
*/
|
|
215
|
+
_emitSafely(event, ...args) {
|
|
216
|
+
for (const listener of this.rawListeners(event)) {
|
|
217
|
+
try {
|
|
218
|
+
listener.apply(this, args);
|
|
84
219
|
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
this.log.error({ err }, "A listener for '%s' threw: %s", event, errorMessage(err));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
_listen(recovering) {
|
|
226
|
+
recovering.on("connect", (model) => this._onConnect(model));
|
|
227
|
+
recovering.on("disconnect", (err) => this._onDisconnect(err));
|
|
228
|
+
recovering.on("connect-failed", (err) => {
|
|
229
|
+
this.log.warn({ err }, "Connection failed.");
|
|
230
|
+
});
|
|
231
|
+
recovering.on("reconnect-scheduled", ({ attempt, delay }) => {
|
|
232
|
+
this.log.warn("Connection retry %d in %d ms", attempt, delay);
|
|
233
|
+
this._emitSafely("trying_connect");
|
|
234
|
+
});
|
|
235
|
+
recovering.on("reconnect-failed", (err) => this._giveUp(err));
|
|
236
|
+
recovering.on("error", (err) => {
|
|
237
|
+
this.log.warn({ err }, "Connection error: %s", err.message);
|
|
238
|
+
});
|
|
239
|
+
recovering.on("handler-error", (err, event) => {
|
|
240
|
+
this.log.error({ err }, "A listener for '%s' threw: %s", event, err.message);
|
|
241
|
+
});
|
|
242
|
+
recovering.on("blocked", (reason) => {
|
|
243
|
+
this.log.warn("Connection blocked by the broker: %s", reason);
|
|
244
|
+
});
|
|
245
|
+
recovering.on("unblocked", () => {
|
|
246
|
+
this.log.info("Connection unblocked by the broker.");
|
|
85
247
|
});
|
|
86
248
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
249
|
+
/**
|
|
250
|
+
* Runs after every successful (re)connect, before amqplib installs the model: rebuilds the topology on fresh
|
|
251
|
+
* channels. It only throws when the model itself closed meanwhile, which makes amqplib schedule another reconnect.
|
|
252
|
+
*/
|
|
253
|
+
async _setup(model) {
|
|
254
|
+
const generation = ++this._lastGeneration;
|
|
255
|
+
this._setupModel = model;
|
|
256
|
+
this._setupGeneration = generation;
|
|
257
|
+
// until amqplib installs the model it has no listeners on it, and an "error" without a listener would throw
|
|
258
|
+
model.on("error", (err) => {
|
|
259
|
+
this.log.debug({ err }, "Connection error during setup: %s", err.message);
|
|
260
|
+
});
|
|
261
|
+
model.on("close", () => {
|
|
262
|
+
this._closedModels.add(model);
|
|
263
|
+
});
|
|
264
|
+
this.log.debug("Initializing the topology on a new connection.");
|
|
265
|
+
await this._declareEntities(model, generation);
|
|
266
|
+
if (this._closedModels.has(model)) {
|
|
267
|
+
throw new Error("Connection closed while the topology was initialized");
|
|
96
268
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Initializes every entity that is not initialized on the given generation yet: exchanges, then queues, then
|
|
272
|
+
* bindings, then queue consumers. Entity failures are handled by the entities themselves.
|
|
273
|
+
*
|
|
274
|
+
* A binding whose source exchange belongs to another connection waits for that connection, so it is not awaited:
|
|
275
|
+
* an outage of the other connection must not hold up this one's recovery.
|
|
276
|
+
*/
|
|
277
|
+
async _declareEntities(model, generation) {
|
|
278
|
+
const settle = async (promises) => {
|
|
279
|
+
await Promise.allSettled(promises);
|
|
280
|
+
};
|
|
281
|
+
await settle(Object.values(this._exchanges).map((exchange) => exchange._initialize(model, generation)));
|
|
282
|
+
await settle(Object.values(this._queues).map((queue) => queue._initialize(model, generation)));
|
|
283
|
+
await settle(Object.values(this._bindings).flatMap((binding) => {
|
|
284
|
+
const initializing = binding._initialize(model, generation);
|
|
285
|
+
if (binding._isCrossConnection) {
|
|
286
|
+
initializing.catch(() => { });
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
return [initializing];
|
|
290
|
+
}));
|
|
291
|
+
await settle(Object.values(this._queues).map((queue) => queue._initializeConsumer()));
|
|
292
|
+
}
|
|
293
|
+
_onConnect(model) {
|
|
294
|
+
const generation = this._setupGeneration;
|
|
295
|
+
this._setupModel = undefined;
|
|
296
|
+
this._model = model;
|
|
297
|
+
this._generation = generation;
|
|
298
|
+
this.isConnected = true;
|
|
299
|
+
// entities declared while setup was running were not part of it
|
|
300
|
+
void this._declareEntities(model, generation).then(() => {
|
|
301
|
+
if (this._isClosing || this._model !== model) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this._deferred.resolve();
|
|
305
|
+
if (this._connectedBefore) {
|
|
306
|
+
this.log.info("AMQP Connection re-established");
|
|
307
|
+
this._emitSafely("re_established_connection");
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
this._connectedBefore = true;
|
|
311
|
+
this.log.info("AQMP Connection established.");
|
|
312
|
+
this._emitSafely("open_connection");
|
|
105
313
|
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
_onDisconnect(err) {
|
|
317
|
+
this._model = undefined;
|
|
318
|
+
this.isConnected = false;
|
|
319
|
+
for (const exchange of Object.values(this._exchanges)) {
|
|
320
|
+
exchange._invalidate();
|
|
106
321
|
}
|
|
107
|
-
for (const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
322
|
+
for (const queue of Object.values(this._queues)) {
|
|
323
|
+
queue._invalidate();
|
|
324
|
+
}
|
|
325
|
+
for (const binding of Object.values(this._bindings)) {
|
|
326
|
+
binding._invalidate();
|
|
327
|
+
}
|
|
328
|
+
this._invalidate();
|
|
329
|
+
if (!this._isClosing) {
|
|
330
|
+
this.log.warn({ err }, "Connection lost: %s", err.message);
|
|
331
|
+
this._emitSafely("lost_connection");
|
|
111
332
|
}
|
|
112
|
-
return new Promise((resolve, reject) => {
|
|
113
|
-
this.completeConfiguration().then(() => {
|
|
114
|
-
this.log.debug("Rebuild success.");
|
|
115
|
-
resolve(null);
|
|
116
|
-
},
|
|
117
|
-
/* istanbul ignore next */ (rejectReason) => {
|
|
118
|
-
this.log.debug("Rebuild failed.");
|
|
119
|
-
reject(rejectReason);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
333
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
139
|
-
});
|
|
334
|
+
/** Recovery gave up: no further reconnect is attempted. */
|
|
335
|
+
_giveUp(err) {
|
|
336
|
+
this._model = undefined;
|
|
337
|
+
this.isConnected = false;
|
|
338
|
+
this._invalidate();
|
|
339
|
+
this._stop(err);
|
|
340
|
+
this.log.warn({ err }, "Connection failed, giving up: %s", errorMessage(err));
|
|
341
|
+
this._emitSafely("error_connection", err);
|
|
342
|
+
}
|
|
343
|
+
/** Replaces a settled {@link initialized} with a pending one. */
|
|
344
|
+
_invalidate() {
|
|
345
|
+
if (this._deferred.settled) {
|
|
346
|
+
this._deferred = new Deferred();
|
|
347
|
+
this.initialized = this._deferred.promise;
|
|
348
|
+
}
|
|
140
349
|
}
|
|
141
350
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
351
|
+
* Waits until the whole declared topology is initialized: every exchange, queue and binding, and the consumer of
|
|
352
|
+
* every queue with an activated consumer. Use it to gate the start of an application, since {@link initialized}
|
|
353
|
+
* only reflects the connection.
|
|
144
354
|
*
|
|
145
|
-
*
|
|
355
|
+
* It rejects when one of them failed on its own, at the first connect or later. A failed exchange, queue or binding
|
|
356
|
+
* counts until one with the same name is declared again (or {@link deleteConfiguration} is called), a refused
|
|
357
|
+
* consumer until it is registered on the next reconnect. While the connection is down it waits for the reconnect.
|
|
358
|
+
*
|
|
359
|
+
* @return A promise that fulfills after all defined exchanges, queues, bindings and consumers are initialized.
|
|
146
360
|
*/
|
|
147
361
|
completeConfiguration() {
|
|
148
|
-
const promises = [];
|
|
362
|
+
const promises = [...this._failures.values()].map((err) => Promise.reject(err));
|
|
149
363
|
for (const exchangeId in this._exchanges) {
|
|
150
364
|
const exchange = this._exchanges[exchangeId];
|
|
151
365
|
promises.push(exchange.initialized);
|
|
@@ -164,27 +378,31 @@ class Connection extends events_1.EventEmitter {
|
|
|
164
378
|
return Promise.all(promises);
|
|
165
379
|
}
|
|
166
380
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
381
|
+
* Deletes the whole defined topology in phases: first the bindings, then the queues together with their consumers,
|
|
382
|
+
* then the exchanges. Deleting a queue or an exchange deletes its bindings as well, so running the phases one after
|
|
383
|
+
* the other unbinds every binding only once. A phase that fails does not stop the next one.
|
|
384
|
+
*
|
|
385
|
+
* @return A promise that fulfills after all defined exchanges, queues and bindings have been removed, and rejects
|
|
386
|
+
* with the first failure otherwise.
|
|
169
387
|
*/
|
|
170
|
-
deleteConfiguration() {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
promises.push(queue.stopConsumer());
|
|
388
|
+
async deleteConfiguration() {
|
|
389
|
+
// what failed is not declared any more, so there is nothing to delete
|
|
390
|
+
this._failures.clear();
|
|
391
|
+
const failures = [];
|
|
392
|
+
const phase = async (deletions) => {
|
|
393
|
+
for (const result of await Promise.allSettled(deletions)) {
|
|
394
|
+
if (result.status === "rejected") {
|
|
395
|
+
failures.push(result.reason);
|
|
396
|
+
}
|
|
180
397
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
398
|
+
};
|
|
399
|
+
await phase(Object.values(this._bindings).map((binding) => binding.delete()));
|
|
400
|
+
// deleting a queue stops its consumer first
|
|
401
|
+
await phase(Object.values(this._queues).map((queue) => queue.delete()));
|
|
402
|
+
await phase(Object.values(this._exchanges).map((exchange) => exchange.delete()));
|
|
403
|
+
if (failures.length > 0) {
|
|
404
|
+
throw failures[0];
|
|
186
405
|
}
|
|
187
|
-
return Promise.all(promises);
|
|
188
406
|
}
|
|
189
407
|
/**
|
|
190
408
|
* Declares a new exchange. Dependent on the options it auto creates the exchange if it does not already exists.
|
|
@@ -197,7 +415,7 @@ class Connection extends events_1.EventEmitter {
|
|
|
197
415
|
declareExchange(name, type, options) {
|
|
198
416
|
let exchange = this._exchanges[name];
|
|
199
417
|
if (exchange === undefined) {
|
|
200
|
-
exchange = new
|
|
418
|
+
exchange = new Exchange(this, name, type, options);
|
|
201
419
|
}
|
|
202
420
|
return exchange;
|
|
203
421
|
}
|
|
@@ -224,7 +442,7 @@ class Connection extends events_1.EventEmitter {
|
|
|
224
442
|
declareQueue(name, options) {
|
|
225
443
|
let queue = this._queues[name];
|
|
226
444
|
if (queue === undefined) {
|
|
227
|
-
queue = new
|
|
445
|
+
queue = new Queue(this, name, options);
|
|
228
446
|
}
|
|
229
447
|
return queue;
|
|
230
448
|
}
|
|
@@ -273,8 +491,9 @@ class Connection extends events_1.EventEmitter {
|
|
|
273
491
|
}
|
|
274
492
|
return Promise.all(promises);
|
|
275
493
|
}
|
|
494
|
+
/** The current underlying amqplib model, `undefined` while disconnected. */
|
|
276
495
|
get getConnection() {
|
|
277
|
-
return this.
|
|
496
|
+
return this._model;
|
|
278
497
|
}
|
|
279
498
|
/**
|
|
280
499
|
* Returns an object with metrics about the connection.
|
|
@@ -286,50 +505,11 @@ class Connection extends events_1.EventEmitter {
|
|
|
286
505
|
sentMessagesCounter: this._sentMessagesCounter,
|
|
287
506
|
};
|
|
288
507
|
}
|
|
289
|
-
rebuildConnection() {
|
|
290
|
-
if (this._rebuilding) {
|
|
291
|
-
// only one rebuild process can be active at any time
|
|
292
|
-
this.log.debug("AMQP Connection rebuild already in progress, joining active rebuild attempt.");
|
|
293
|
-
return this.initialized;
|
|
294
|
-
}
|
|
295
|
-
this._retry = -1;
|
|
296
|
-
this._rebuilding = true;
|
|
297
|
-
this._isClosing = false;
|
|
298
|
-
// rebuild the connection
|
|
299
|
-
this.initialized = new Promise((resolve, reject) => {
|
|
300
|
-
this.tryToConnect(this, 0, (err) => {
|
|
301
|
-
/* istanbul ignore if */
|
|
302
|
-
if (err) {
|
|
303
|
-
this._rebuilding = false;
|
|
304
|
-
reject(err);
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
this._rebuilding = false;
|
|
308
|
-
if (this.connectedBefore) {
|
|
309
|
-
this.log.info("AMQP Connection re-established");
|
|
310
|
-
this.emit("re_established_connection");
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
this.log.info("AQMP Connection established.");
|
|
314
|
-
this.emit("open_connection");
|
|
315
|
-
this.connectedBefore = true;
|
|
316
|
-
}
|
|
317
|
-
resolve(null);
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
/* istanbul ignore next */
|
|
322
|
-
this.initialized.catch((err) => {
|
|
323
|
-
this.log.warn({ err }, "Error creating connection!");
|
|
324
|
-
this.emit("error_connection", err);
|
|
325
|
-
//throw (err);
|
|
326
|
-
});
|
|
327
|
-
return this.initialized;
|
|
328
|
-
}
|
|
329
508
|
/**
|
|
330
509
|
* Increments an internal counter.
|
|
331
510
|
* @param counter The counter to increase.
|
|
332
511
|
* @param value The increment, default is 1.
|
|
512
|
+
* @internal
|
|
333
513
|
*/
|
|
334
514
|
_increaseCounter(counter, value = 1) {
|
|
335
515
|
switch (counter) {
|
|
@@ -342,5 +522,4 @@ class Connection extends events_1.EventEmitter {
|
|
|
342
522
|
}
|
|
343
523
|
}
|
|
344
524
|
}
|
|
345
|
-
exports.Connection = Connection;
|
|
346
525
|
//# sourceMappingURL=Connection.js.map
|