@codefresh-io/eventbus 2.4.0 → 3.0.0-alpha.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 +39 -1
- package/lib/Bus.js +255 -135
- package/lib/Store.js +89 -94
- package/lib/index.js +74 -61
- package/lib/logger.js +5 -0
- package/package.json +19 -30
- package/.dockerignore +0 -2
- package/.eslintrc +0 -71
- package/Dockerfile +0 -15
- package/codefresh.yml +0 -23
- package/event.json +0 -9
- package/examples/etl.js +0 -64
- package/examples/test-consumer.js +0 -49
- package/examples/test-producer.js +0 -56
- package/gulpfile.js +0 -75
- package/lib/tests/test.unit.spec.js +0 -28
- package/yarn-error.log +0 -3182
- package/yarn.lock +0 -3120
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
## cf eventbus
|
|
2
2
|
|
|
3
|
+
```TS
|
|
3
4
|
const eventbus = require('cf-eventbus');
|
|
4
5
|
|
|
5
6
|
eventbus.init({
|
|
@@ -14,4 +15,41 @@ eventbus.init({
|
|
|
14
15
|
password: 'postgres' // postgres password
|
|
15
16
|
},
|
|
16
17
|
microServiceName: 'service-name' // client name
|
|
17
|
-
});
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Graceful shutdown
|
|
22
|
+
|
|
23
|
+
`quit()` method can be used to gracefully terminate the bus connection by cancelling all consumers, waiting for all consumers in progress to finish, and then closing the connection.
|
|
24
|
+
If it's not desired, use `quit({ force: true })` to terminate the connection immediately.
|
|
25
|
+
|
|
26
|
+
```TS
|
|
27
|
+
const eventbus = require('cf-eventbus');
|
|
28
|
+
|
|
29
|
+
process.on('SIGTERM', async () => {
|
|
30
|
+
try {
|
|
31
|
+
await eventbus.quit();
|
|
32
|
+
// ...
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// ...
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
It's may be required to perform some additional cleanup after all consumers are cancelled and before the connection is closed. In that case, use the following pattern:
|
|
40
|
+
|
|
41
|
+
```TS
|
|
42
|
+
const eventbus = require('cf-eventbus');
|
|
43
|
+
|
|
44
|
+
process.on('SIGTERM', async () => {
|
|
45
|
+
try {
|
|
46
|
+
await eventbus.cancelAllConsumers();
|
|
47
|
+
await eventbus.waitForConsumersToFinish();
|
|
48
|
+
// perform additional cleanup here, such as waiting for HTTP requests to finish
|
|
49
|
+
await eventbus.quit();
|
|
50
|
+
// ...
|
|
51
|
+
} catch (err) {
|
|
52
|
+
// ...
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
```
|
package/lib/Bus.js
CHANGED
|
@@ -1,27 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
|
-
const debug = require('debug')('eventbus:bus');
|
|
3
|
+
const EventEmitter = require('node:events');
|
|
4
|
+
const timers = require('node:timers/promises');
|
|
7
5
|
const amqplib = require('amqplib');
|
|
8
6
|
const isFatalError = require('amqplib/lib/connection').isFatalError;
|
|
9
|
-
const
|
|
10
|
-
const uuid = require('uuid');
|
|
7
|
+
const { logger } = require('./logger');
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {(...args: any[]) => Promise<void>} Handler
|
|
12
|
+
*
|
|
13
|
+
* @typedef {object} Subscriber
|
|
14
|
+
* @property {string} eventName
|
|
15
|
+
* @property {Handler} handler
|
|
16
|
+
* @property {object} options
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const RECONNECT_INTERVAL_DEFAULT_SEC = 1;
|
|
13
20
|
|
|
14
21
|
class Bus extends EventEmitter {
|
|
22
|
+
_logger = logger.child(this.constructor.name);
|
|
23
|
+
_consumersInProgressCount = 0;
|
|
24
|
+
_terminating = false;
|
|
25
|
+
_terminated = false;
|
|
15
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} microServiceName
|
|
29
|
+
* @param {object} options
|
|
30
|
+
* @param {string} options.url RabbitMQ connection url
|
|
31
|
+
* @param {number} [options.reconnectInterval] Reconnect interval in seconds. Default is 1 second.
|
|
32
|
+
*/
|
|
16
33
|
constructor(microServiceName, options) {
|
|
17
34
|
super();
|
|
18
|
-
this.url
|
|
19
|
-
this.microServiceName
|
|
20
|
-
this.
|
|
35
|
+
this.url = options.url;
|
|
36
|
+
this.microServiceName = microServiceName;
|
|
37
|
+
this.reconnectIntervalMs = (options.reconnectInterval || RECONNECT_INTERVAL_DEFAULT_SEC) * 1000;
|
|
38
|
+
/** @type {Record<string, Subscriber[]>} */
|
|
21
39
|
this.subscribers = {};
|
|
22
40
|
this.initializing = false;
|
|
41
|
+
/** @type {boolean} */
|
|
42
|
+
this._forbidNewConsumers = false;
|
|
23
43
|
this._reSubscribe = this._reSubscribe.bind(this);
|
|
24
44
|
this._reconnect = this._reconnect.bind(this);
|
|
45
|
+
/** @type {Map<amqplib.Channel, { queueName: string, consumerTag: string }[]>} */
|
|
46
|
+
this.consumerTagsByChannel = new Map();
|
|
25
47
|
}
|
|
26
48
|
|
|
27
49
|
_createErrorHandler() {
|
|
@@ -37,28 +59,94 @@ class Bus extends EventEmitter {
|
|
|
37
59
|
});
|
|
38
60
|
}
|
|
39
61
|
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Unsubscribes all consumers and forbids new consumers from being subscribed.
|
|
64
|
+
* This should be used when you want to gracefully terminate the connection and ensure that no new messages are consumed while terminating.
|
|
65
|
+
* @returns {Promise<void>}
|
|
66
|
+
*/
|
|
67
|
+
async cancelAllConsumers() {
|
|
68
|
+
this._logger.info('Cancelling all consumers on all channels. New subscriptions will be forbidden from now on.');
|
|
69
|
+
this._forbidNewConsumers = true;
|
|
70
|
+
|
|
71
|
+
for (const ch of this.consumerTagsByChannel.keys()) {
|
|
72
|
+
const consumerTags = this.consumerTagsByChannel.get(ch) ?? [];
|
|
73
|
+
const results = await Promise.allSettled(
|
|
74
|
+
consumerTags.map((tagInfo) => ch.cancel(tagInfo.consumerTag))
|
|
75
|
+
);
|
|
76
|
+
for (let i = 0; i < results.length; i++) {
|
|
77
|
+
const result = results[i];
|
|
78
|
+
const tagInfo = consumerTags[i];
|
|
79
|
+
if (result.status === 'rejected') {
|
|
80
|
+
this._logger.error(`Failed to cancel consumer with tag "${tagInfo.consumerTag}" on queue "${tagInfo.queueName}". Ignoring error`, { error: result.reason });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
this._logger.debug(`Consumer with tag "${tagInfo.consumerTag}" on queue "${tagInfo.queueName}" was cancelled successfully`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.consumerTagsByChannel.clear();
|
|
87
|
+
this._logger.info('All consumers were cancelled');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Waits for all consumers in progress to finish.
|
|
92
|
+
* This should be used before terminating the connection
|
|
93
|
+
* to ensure that all messages are processed before the connection is closed.
|
|
94
|
+
* @returns {Promise<void>}
|
|
95
|
+
*/
|
|
96
|
+
async waitForConsumersToFinish() {
|
|
97
|
+
while (this._consumersInProgressCount > 0) {
|
|
98
|
+
this._logger.info(`Waiting for ${this._consumersInProgressCount} consumers in progress to finish...`);
|
|
99
|
+
await timers.setTimeout(1000);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Terminates the connection gracefully by cancelling all consumers, waiting for all consumers in progress to finish, and then closing the connection.
|
|
105
|
+
* @param {object} [options]
|
|
106
|
+
* @param {boolean} [options.force] If `true`, closes connection immediately.
|
|
107
|
+
*/
|
|
108
|
+
async quit(options = {}) {
|
|
109
|
+
this._terminating = true;
|
|
110
|
+
if (options.force) {
|
|
111
|
+
this._logger.warn('Forcefully terminating bus connection. All consumers in progress will be interrupted and may not finish processing messages');
|
|
112
|
+
await this._closeConnection();
|
|
113
|
+
this._terminated = true;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this._logger.info('Gracefully terminating bus connection');
|
|
117
|
+
await this.cancelAllConsumers();
|
|
118
|
+
await this.waitForConsumersToFinish();
|
|
119
|
+
await this._closeConnection();
|
|
120
|
+
this._terminated = true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async _closeConnection() {
|
|
124
|
+
this._logger.info('Closing bus connection');
|
|
125
|
+
await this.connection?.close();
|
|
42
126
|
this.subscribers = {};
|
|
43
|
-
|
|
127
|
+
this._logger.info('Bus connection is closed');
|
|
44
128
|
}
|
|
45
129
|
|
|
130
|
+
/**
|
|
131
|
+
* @returns {void}
|
|
132
|
+
*/
|
|
46
133
|
init() {
|
|
47
134
|
this.initializing = true;
|
|
48
135
|
this._createErrorHandler();
|
|
49
136
|
|
|
137
|
+
this._logger.debug('Bus connecting...');
|
|
50
138
|
this.openPromise = amqplib.connect(this.url);
|
|
51
139
|
this.openPromise
|
|
52
140
|
.then((newConnection) => {
|
|
53
|
-
debug('
|
|
141
|
+
this._logger.debug('Bus connected');
|
|
54
142
|
if (this.reconnect && this.connection) {
|
|
55
143
|
this.publishChannel = undefined;
|
|
56
|
-
debug('reconnected');
|
|
144
|
+
this._logger.debug('Bus reconnected');
|
|
57
145
|
}
|
|
58
146
|
this.reconnect = false;
|
|
59
147
|
this.connection = newConnection;
|
|
60
148
|
|
|
61
|
-
debug(
|
|
149
|
+
this._logger.debug('Bus is ready');
|
|
62
150
|
this.emit('ready');
|
|
63
151
|
|
|
64
152
|
this.connection.on('error', (err) => {
|
|
@@ -66,17 +154,17 @@ class Bus extends EventEmitter {
|
|
|
66
154
|
});
|
|
67
155
|
|
|
68
156
|
this.connection.on('close', () => {
|
|
69
|
-
debug(
|
|
157
|
+
this._logger.debug('Bus connection closed');
|
|
70
158
|
this.emit('close');
|
|
71
159
|
this._reconnect();
|
|
72
160
|
});
|
|
73
161
|
|
|
74
162
|
this.connection.on('blocked', () => {
|
|
75
|
-
debug('connection blocked');
|
|
163
|
+
this._logger.debug('Bus connection blocked');
|
|
76
164
|
});
|
|
77
165
|
|
|
78
166
|
this.connection.on('unblocked', () => {
|
|
79
|
-
debug('connection unblocked');
|
|
167
|
+
this._logger.debug('Bus connection unblocked');
|
|
80
168
|
});
|
|
81
169
|
|
|
82
170
|
return this._reSubscribe();
|
|
@@ -90,165 +178,197 @@ class Bus extends EventEmitter {
|
|
|
90
178
|
}
|
|
91
179
|
|
|
92
180
|
_reconnect() {
|
|
93
|
-
if (this.initializing) {
|
|
94
|
-
debug('
|
|
181
|
+
if (this.initializing || this._terminating || this._terminated) {
|
|
182
|
+
this._logger.debug('Skipping bus reconnect because it is initializing or terminating');
|
|
95
183
|
return;
|
|
96
184
|
}
|
|
97
185
|
|
|
98
|
-
debug(`reconnecting in
|
|
186
|
+
this._logger.debug(`Bus reconnecting in ${this.reconnectIntervalMs / 1000} seconds...`);
|
|
99
187
|
this.reconnect = true;
|
|
100
188
|
setTimeout(() => {
|
|
101
|
-
debug(`reconnecting`);
|
|
102
189
|
this.init();
|
|
103
|
-
}, this.
|
|
190
|
+
}, this.reconnectIntervalMs);
|
|
104
191
|
}
|
|
105
192
|
|
|
106
|
-
_reSubscribe() {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
193
|
+
async _reSubscribe() {
|
|
194
|
+
for (const [eventName, subscribers] of Object.entries(this.subscribers)) {
|
|
195
|
+
this._logger.debug(`Re-subscribing ${subscribers.length} subscribers to event "${eventName}"`);
|
|
196
|
+
for (const { eventName, handler, options } of subscribers) {
|
|
197
|
+
this.subscribe(eventName, handler, options, true);
|
|
198
|
+
}
|
|
199
|
+
this._logger.debug(`Finished re-subscribing ${subscribers.length} subscribers to event "${eventName}"`);
|
|
200
|
+
}
|
|
113
201
|
}
|
|
114
202
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
203
|
+
/**
|
|
204
|
+
*
|
|
205
|
+
* @param {string} eventName
|
|
206
|
+
* @param {any} msg
|
|
207
|
+
* @param {string} [routingKey]
|
|
208
|
+
* @returns {Promise<boolean>} Returns true if the message was published successfully, false otherwise.
|
|
209
|
+
*/
|
|
210
|
+
async publish(eventName, msg, routingKey = 'default') {
|
|
211
|
+
if (!this.publishChannel) {
|
|
212
|
+
await this._createPublishChannel();
|
|
213
|
+
}
|
|
214
|
+
await this.publishChannel?.assertExchange(eventName, 'fanout', { durable: true });
|
|
215
|
+
return !!(this.publishChannel?.publish(
|
|
216
|
+
eventName,
|
|
217
|
+
routingKey,
|
|
218
|
+
Buffer.from(this._serialize(msg)),
|
|
219
|
+
));
|
|
128
220
|
}
|
|
129
221
|
|
|
222
|
+
/**
|
|
223
|
+
* @param {string} eventName
|
|
224
|
+
* @param {Handler} handler
|
|
225
|
+
* @param {object} [options]
|
|
226
|
+
* @param {boolean} [options.pubSub]
|
|
227
|
+
* @param {boolean} [options.waitForAck]
|
|
228
|
+
* @param {number} [options.concurrencyLimit]
|
|
229
|
+
* @param {number} [options.ttl]
|
|
230
|
+
* @param {number} [options.timeout]
|
|
231
|
+
* @param {boolean} [options.ackOnTimeout]
|
|
232
|
+
* @param {string} [options.serviceRole]
|
|
233
|
+
* @param {string} [options.additionalIdentifier]
|
|
234
|
+
* @param {boolean} [restarting]
|
|
235
|
+
* @returns {EventEmitter} An EventEmitter that emits 'error' event when an error occurs in the handler or during subscription process.
|
|
236
|
+
*/
|
|
130
237
|
subscribe(eventName, handler, options = {}, restarting) {
|
|
131
|
-
|
|
132
|
-
this.subscribers[eventName] = this.subscribers[eventName] || [];
|
|
133
|
-
this.subscribers[eventName].push({eventName, handler, options});
|
|
134
|
-
}
|
|
238
|
+
this._logger.debug(`Subscribing to event "${eventName}", options:`, { options });
|
|
135
239
|
const subscriberEmitter = new EventEmitter();
|
|
136
240
|
subscriberEmitter.on('error', () => {});
|
|
241
|
+
|
|
242
|
+
if (this._forbidNewConsumers || this._terminating || this._terminated) {
|
|
243
|
+
this._logger.warn(`Skipping new consumer subscription to "${eventName}" because bus is terminating or terminated or new consumers are forbidden`);
|
|
244
|
+
return subscriberEmitter;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!restarting) {
|
|
248
|
+
this.subscribers[eventName] ??= [];
|
|
249
|
+
this.subscribers[eventName].push({ eventName, handler, options });
|
|
250
|
+
}
|
|
251
|
+
|
|
137
252
|
const pubSub = options.pubSub || false;
|
|
138
253
|
const waitForAck = options.waitForAck || false;
|
|
139
|
-
const concurrencyLimit =
|
|
254
|
+
const concurrencyLimit = options.concurrencyLimit || 0; // 0 means no limit
|
|
140
255
|
const messageTtl = options.ttl || false;
|
|
141
256
|
const timeout = options.timeout;
|
|
142
257
|
const ackOnTimeout = options.ackOnTimeout;
|
|
143
258
|
|
|
144
|
-
const serviceRole = options.serviceRole
|
|
145
|
-
|
|
146
|
-
|
|
259
|
+
const serviceRole = options.serviceRole
|
|
260
|
+
? `${options.serviceRole}_`
|
|
261
|
+
: '';
|
|
262
|
+
const additionalIdentifier = options.additionalIdentifier
|
|
263
|
+
? `${options.additionalIdentifier}_`
|
|
264
|
+
: '';
|
|
265
|
+
const pubSubIdentifier = options.pubSub
|
|
266
|
+
? `${globalThis.crypto.randomUUID()}_`
|
|
267
|
+
: '';
|
|
147
268
|
const queueName = `${this.microServiceName}_${serviceRole}${additionalIdentifier}${pubSubIdentifier}${eventName}`;
|
|
148
269
|
Promise.resolve()
|
|
149
|
-
.then(() =>
|
|
150
|
-
|
|
270
|
+
.then(() => this._getSubscribeChannel())
|
|
271
|
+
.then(async (ch) => {
|
|
272
|
+
if (!ch) {
|
|
273
|
+
throw new Error('Channel was not created. Perhaps initial connection failed or not established yet');
|
|
274
|
+
}
|
|
275
|
+
await ch.assertExchange(eventName, 'fanout', { durable: true });
|
|
276
|
+
this._logger.debug(`Durable exchange "${eventName}" of type "fanout" asserted`);
|
|
277
|
+
return ch;
|
|
278
|
+
})
|
|
279
|
+
.then(async (ch) => {
|
|
280
|
+
const queueOptions = {
|
|
281
|
+
...(!pubSub && { durable: true }),
|
|
282
|
+
...(pubSub && { exclusive: true }),
|
|
283
|
+
...(messageTtl && { messageTtl }),
|
|
284
|
+
};
|
|
285
|
+
await ch.assertQueue(queueName, queueOptions);
|
|
286
|
+
this._logger.debug(`Queue "${queueName}" asserted`, { queueOptions });
|
|
287
|
+
return ch;
|
|
151
288
|
})
|
|
152
|
-
.then((ch) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
289
|
+
.then(async (ch) => {
|
|
290
|
+
await ch.bindQueue(queueName, eventName, '');
|
|
291
|
+
this._logger.debug(`Queue "${queueName}" bound to exchange "${eventName}", pattern ""`);
|
|
292
|
+
return ch;
|
|
293
|
+
})
|
|
294
|
+
.then(async (ch) => {
|
|
295
|
+
this._logger.debug(`Setting concurrency limit "${concurrencyLimit}" for queue "${queueName}"`);
|
|
296
|
+
await ch.prefetch(concurrencyLimit);
|
|
297
|
+
return ch;
|
|
298
|
+
})
|
|
299
|
+
.then(async (ch) => {
|
|
300
|
+
const { consumerTag } = await ch.consume(queueName, async (msg) => {
|
|
301
|
+
let finished = false;
|
|
302
|
+
try {
|
|
303
|
+
if (waitForAck && timeout) {
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
if (finished) return;
|
|
306
|
+
finished = true;
|
|
307
|
+
subscriberEmitter.emit('error', new Error('Handler timedout'));
|
|
308
|
+
if (ackOnTimeout) {
|
|
309
|
+
ch.ack(msg);
|
|
310
|
+
} else {
|
|
311
|
+
ch.nack(msg);
|
|
312
|
+
}
|
|
313
|
+
}, timeout);
|
|
314
|
+
} else {
|
|
315
|
+
ch.ack(msg);
|
|
316
|
+
}
|
|
317
|
+
const content = this._deserialize(msg.content.toString());
|
|
318
|
+
this._consumersInProgressCount++;
|
|
319
|
+
await handler(content).finally(() => this._consumersInProgressCount--);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
subscriberEmitter.emit('error', error);
|
|
322
|
+
if (!finished && waitForAck) {
|
|
323
|
+
finished = true;
|
|
324
|
+
ch.nack(msg);
|
|
325
|
+
} else {
|
|
326
|
+
finished = true;
|
|
161
327
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
.then(() => {
|
|
168
|
-
return ch.prefetch(concurrencyLimit);
|
|
169
|
-
})
|
|
170
|
-
.then(() => {
|
|
171
|
-
debug(`queue: ${queueName} concurrencyLimit: ${concurrencyLimit} was set`);
|
|
172
|
-
return ch.consume(queueName, (msg) => {
|
|
173
|
-
const d = domain.create();
|
|
174
|
-
d.on('error', (err) => {
|
|
175
|
-
subscriberEmitter.emit('error', err);
|
|
176
|
-
});
|
|
177
|
-
d.run(() => {
|
|
178
|
-
let finished = false;
|
|
179
|
-
if (waitForAck) {
|
|
180
|
-
if (timeout) {
|
|
181
|
-
setTimeout(() => {
|
|
182
|
-
if (!finished) {
|
|
183
|
-
finished = true;
|
|
184
|
-
subscriberEmitter.emit('error', new Error('Handler timedout'));
|
|
185
|
-
if (ackOnTimeout) {
|
|
186
|
-
ch.ack(msg);
|
|
187
|
-
} else {
|
|
188
|
-
ch.nack(msg);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}, timeout);
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
ch.ack(msg);
|
|
195
|
-
}
|
|
196
|
-
handler(this._deserialize(msg.content.toString()))
|
|
197
|
-
.then(() => {
|
|
198
|
-
if (!finished && waitForAck) {
|
|
199
|
-
finished = true;
|
|
200
|
-
ch.ack(msg);
|
|
201
|
-
} else {
|
|
202
|
-
finished = true;
|
|
203
|
-
}
|
|
204
|
-
}, (err) => {
|
|
205
|
-
subscriberEmitter.emit('error', err);
|
|
206
|
-
if (!finished && waitForAck) {
|
|
207
|
-
finished = true;
|
|
208
|
-
ch.nack(msg);
|
|
209
|
-
} else {
|
|
210
|
-
finished = true;
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
});
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
const consumerTags = this.consumerTagsByChannel.get(ch) ?? [];
|
|
331
|
+
consumerTags.push({ queueName, consumerTag });
|
|
332
|
+
this.consumerTagsByChannel.set(ch, consumerTags);
|
|
217
333
|
})
|
|
218
334
|
.catch((err) => {
|
|
219
335
|
subscriberEmitter.emit('error', { connection: err });
|
|
220
|
-
})
|
|
221
|
-
.done();
|
|
336
|
+
});
|
|
222
337
|
|
|
223
338
|
return subscriberEmitter;
|
|
224
339
|
}
|
|
225
340
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.publishChannel = ch;
|
|
233
|
-
});
|
|
341
|
+
/**
|
|
342
|
+
* @returns {Promise<void>}
|
|
343
|
+
*/
|
|
344
|
+
async _createPublishChannel() {
|
|
345
|
+
const model = await this.openPromise;
|
|
346
|
+
this.publishChannel = await model?.createChannel();
|
|
234
347
|
}
|
|
235
348
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
349
|
+
/**
|
|
350
|
+
* @returns {Promise<amqplib.Channel | undefined>}
|
|
351
|
+
*/
|
|
352
|
+
async _getSubscribeChannel() {
|
|
353
|
+
const model = await this.openPromise;
|
|
354
|
+
return await model?.createChannel();
|
|
241
355
|
}
|
|
242
356
|
|
|
357
|
+
/**
|
|
358
|
+
* @param {unknown} body
|
|
359
|
+
* @returns {string}
|
|
360
|
+
*/
|
|
243
361
|
_serialize(body) {
|
|
244
362
|
return JSON.stringify(body);
|
|
245
363
|
}
|
|
246
364
|
|
|
365
|
+
/**
|
|
366
|
+
* @param {string} content
|
|
367
|
+
* @returns {unknown}
|
|
368
|
+
*/
|
|
247
369
|
_deserialize(content) {
|
|
248
370
|
return JSON.parse(content);
|
|
249
371
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
372
|
}
|
|
253
373
|
|
|
254
374
|
|