@codefresh-io/eventbus 2.4.0 → 3.0.0-alpha.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 +39 -1
- package/lib/Bus.js +256 -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, 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((consumerTag) => ch.cancel(consumerTag))
|
|
75
|
+
);
|
|
76
|
+
for (let i = 0; i < results.length; i++) {
|
|
77
|
+
const result = results[i];
|
|
78
|
+
const consumerTag = consumerTags[i];
|
|
79
|
+
if (result.status === 'rejected') {
|
|
80
|
+
this._logger.error(`Failed to cancel consumer with tag "${consumerTag}". Ignoring error`, { error: result.reason });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
this._logger.debug(`Consumer with tag "${consumerTag}" 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 eventbus 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 eventbus 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 eventbus connection');
|
|
125
|
+
await this.connection?.close();
|
|
42
126
|
this.subscribers = {};
|
|
43
|
-
|
|
127
|
+
this._logger.info('Eventbus connection 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('Eventbus connecting...');
|
|
50
138
|
this.openPromise = amqplib.connect(this.url);
|
|
51
139
|
this.openPromise
|
|
52
140
|
.then((newConnection) => {
|
|
53
|
-
debug('
|
|
141
|
+
this._logger.debug('Eventbus connected');
|
|
54
142
|
if (this.reconnect && this.connection) {
|
|
55
143
|
this.publishChannel = undefined;
|
|
56
|
-
debug('reconnected');
|
|
144
|
+
this._logger.debug('Eventbus reconnected');
|
|
57
145
|
}
|
|
58
146
|
this.reconnect = false;
|
|
59
147
|
this.connection = newConnection;
|
|
60
148
|
|
|
61
|
-
debug(
|
|
149
|
+
this._logger.debug('Eventbus 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('Eventbus 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('Eventbus connection blocked');
|
|
76
164
|
});
|
|
77
165
|
|
|
78
166
|
this.connection.on('unblocked', () => {
|
|
79
|
-
debug('connection unblocked');
|
|
167
|
+
this._logger.debug('Eventbus connection unblocked');
|
|
80
168
|
});
|
|
81
169
|
|
|
82
170
|
return this._reSubscribe();
|
|
@@ -90,165 +178,198 @@ 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 eventbus reconnect because eventbus is initializing or terminating');
|
|
95
183
|
return;
|
|
96
184
|
}
|
|
97
185
|
|
|
98
|
-
debug(`reconnecting in
|
|
186
|
+
this._logger.debug(`Eventbus reconnecting in ${this.reconnectIntervalMs / 1000} seconds`);
|
|
99
187
|
this.reconnect = true;
|
|
100
188
|
setTimeout(() => {
|
|
101
|
-
debug(
|
|
189
|
+
this._logger.debug('Eventbus reconnecting...');
|
|
102
190
|
this.init();
|
|
103
|
-
}, this.
|
|
191
|
+
}, this.reconnectIntervalMs);
|
|
104
192
|
}
|
|
105
193
|
|
|
106
|
-
_reSubscribe() {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
194
|
+
async _reSubscribe() {
|
|
195
|
+
for (const [eventName, subscribers] of Object.entries(this.subscribers)) {
|
|
196
|
+
this._logger.debug(`Re-subscribing ${subscribers.length} subscribers to event "${eventName}"`);
|
|
197
|
+
for (const { eventName, handler, options } of subscribers) {
|
|
198
|
+
this.subscribe(eventName, handler, options, true);
|
|
199
|
+
}
|
|
200
|
+
this._logger.debug(`Finished re-subscribing ${subscribers.length} subscribers to event "${eventName}"`);
|
|
201
|
+
}
|
|
113
202
|
}
|
|
114
203
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
204
|
+
/**
|
|
205
|
+
*
|
|
206
|
+
* @param {string} eventName
|
|
207
|
+
* @param {any} msg
|
|
208
|
+
* @param {string} [routingKey]
|
|
209
|
+
* @returns {Promise<boolean>} Returns true if the message was published successfully, false otherwise.
|
|
210
|
+
*/
|
|
211
|
+
async publish(eventName, msg, routingKey = 'default') {
|
|
212
|
+
if (!this.publishChannel) {
|
|
213
|
+
await this._createPublishChannel();
|
|
214
|
+
}
|
|
215
|
+
await this.publishChannel?.assertExchange(eventName, 'fanout', { durable: true });
|
|
216
|
+
return !!(this.publishChannel?.publish(
|
|
217
|
+
eventName,
|
|
218
|
+
routingKey,
|
|
219
|
+
Buffer.from(this._serialize(msg)),
|
|
220
|
+
));
|
|
128
221
|
}
|
|
129
222
|
|
|
223
|
+
/**
|
|
224
|
+
* @param {string} eventName
|
|
225
|
+
* @param {Handler} handler
|
|
226
|
+
* @param {object} [options]
|
|
227
|
+
* @param {boolean} [options.pubSub]
|
|
228
|
+
* @param {boolean} [options.waitForAck]
|
|
229
|
+
* @param {number} [options.concurrencyLimit]
|
|
230
|
+
* @param {number} [options.ttl]
|
|
231
|
+
* @param {number} [options.timeout]
|
|
232
|
+
* @param {boolean} [options.ackOnTimeout]
|
|
233
|
+
* @param {string} [options.serviceRole]
|
|
234
|
+
* @param {string} [options.additionalIdentifier]
|
|
235
|
+
* @param {boolean} [restarting]
|
|
236
|
+
* @returns {EventEmitter} An EventEmitter that emits 'error' event when an error occurs in the handler or during subscription process.
|
|
237
|
+
*/
|
|
130
238
|
subscribe(eventName, handler, options = {}, restarting) {
|
|
131
|
-
|
|
132
|
-
this.subscribers[eventName] = this.subscribers[eventName] || [];
|
|
133
|
-
this.subscribers[eventName].push({eventName, handler, options});
|
|
134
|
-
}
|
|
239
|
+
this._logger.debug(`Subscribing to event "${eventName}", options:`, options);
|
|
135
240
|
const subscriberEmitter = new EventEmitter();
|
|
136
241
|
subscriberEmitter.on('error', () => {});
|
|
242
|
+
|
|
243
|
+
if (this._forbidNewConsumers || this._terminating || this._terminated) {
|
|
244
|
+
this._logger.warn(`Skipping new consumer subscription to "${eventName}" because eventbus is terminating or terminated or new consumers are forbidden`);
|
|
245
|
+
return subscriberEmitter;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!restarting) {
|
|
249
|
+
this.subscribers[eventName] ??= [];
|
|
250
|
+
this.subscribers[eventName].push({ eventName, handler, options });
|
|
251
|
+
}
|
|
252
|
+
|
|
137
253
|
const pubSub = options.pubSub || false;
|
|
138
254
|
const waitForAck = options.waitForAck || false;
|
|
139
|
-
const concurrencyLimit =
|
|
255
|
+
const concurrencyLimit = options.concurrencyLimit || 0; // 0 means no limit
|
|
140
256
|
const messageTtl = options.ttl || false;
|
|
141
257
|
const timeout = options.timeout;
|
|
142
258
|
const ackOnTimeout = options.ackOnTimeout;
|
|
143
259
|
|
|
144
|
-
const serviceRole = options.serviceRole
|
|
145
|
-
|
|
146
|
-
|
|
260
|
+
const serviceRole = options.serviceRole
|
|
261
|
+
? `${options.serviceRole}_`
|
|
262
|
+
: '';
|
|
263
|
+
const additionalIdentifier = options.additionalIdentifier
|
|
264
|
+
? `${options.additionalIdentifier}_`
|
|
265
|
+
: '';
|
|
266
|
+
const pubSubIdentifier = options.pubSub
|
|
267
|
+
? `${globalThis.crypto.randomUUID()}_`
|
|
268
|
+
: '';
|
|
147
269
|
const queueName = `${this.microServiceName}_${serviceRole}${additionalIdentifier}${pubSubIdentifier}${eventName}`;
|
|
148
270
|
Promise.resolve()
|
|
149
|
-
.then(() =>
|
|
150
|
-
|
|
271
|
+
.then(() => this._getSubscribeChannel())
|
|
272
|
+
.then(async (ch) => {
|
|
273
|
+
if (!ch) {
|
|
274
|
+
throw new Error('Channel was not created. Perhaps initial connection failed or not established yet');
|
|
275
|
+
}
|
|
276
|
+
await ch.assertExchange(eventName, 'fanout', { durable: true });
|
|
277
|
+
this._logger.debug(`Durable exchange "${eventName}" of type "fanout" asserted`);
|
|
278
|
+
return ch;
|
|
279
|
+
})
|
|
280
|
+
.then(async (ch) => {
|
|
281
|
+
const queueOptions = {
|
|
282
|
+
...(!pubSub && { durable: true }),
|
|
283
|
+
...(pubSub && { exclusive: true }),
|
|
284
|
+
...(messageTtl && { messageTtl }),
|
|
285
|
+
};
|
|
286
|
+
await ch.assertQueue(queueName, queueOptions);
|
|
287
|
+
this._logger.debug(`Queue "${queueName}" asserted`, { queueOptions });
|
|
288
|
+
return ch;
|
|
151
289
|
})
|
|
152
|
-
.then((ch) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
290
|
+
.then(async (ch) => {
|
|
291
|
+
await ch.bindQueue(queueName, eventName, '');
|
|
292
|
+
this._logger.debug(`Queue "${queueName}" bound to exchange "${eventName}", pattern ""`);
|
|
293
|
+
return ch;
|
|
294
|
+
})
|
|
295
|
+
.then(async (ch) => {
|
|
296
|
+
this._logger.debug(`Setting concurrency limit "${concurrencyLimit}" for queue "${queueName}"`);
|
|
297
|
+
await ch.prefetch(concurrencyLimit);
|
|
298
|
+
return ch;
|
|
299
|
+
})
|
|
300
|
+
.then(async (ch) => {
|
|
301
|
+
const { consumerTag } = await ch.consume(queueName, async (msg) => {
|
|
302
|
+
let finished = false;
|
|
303
|
+
try {
|
|
304
|
+
if (waitForAck && timeout) {
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
if (finished) return;
|
|
307
|
+
finished = true;
|
|
308
|
+
subscriberEmitter.emit('error', new Error('Handler timedout'));
|
|
309
|
+
if (ackOnTimeout) {
|
|
310
|
+
ch.ack(msg);
|
|
311
|
+
} else {
|
|
312
|
+
ch.nack(msg);
|
|
313
|
+
}
|
|
314
|
+
}, timeout);
|
|
315
|
+
} else {
|
|
316
|
+
ch.ack(msg);
|
|
317
|
+
}
|
|
318
|
+
const content = this._deserialize(msg.content.toString());
|
|
319
|
+
this._consumersInProgressCount++;
|
|
320
|
+
await handler(content).finally(() => this._consumersInProgressCount--);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
subscriberEmitter.emit('error', error);
|
|
323
|
+
if (!finished && waitForAck) {
|
|
324
|
+
finished = true;
|
|
325
|
+
ch.nack(msg);
|
|
326
|
+
} else {
|
|
327
|
+
finished = true;
|
|
161
328
|
}
|
|
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
|
-
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
const consumerTags = this.consumerTagsByChannel.get(ch) ?? [];
|
|
332
|
+
consumerTags.push(consumerTag);
|
|
333
|
+
this.consumerTagsByChannel.set(ch, consumerTags);
|
|
217
334
|
})
|
|
218
335
|
.catch((err) => {
|
|
219
336
|
subscriberEmitter.emit('error', { connection: err });
|
|
220
|
-
})
|
|
221
|
-
.done();
|
|
337
|
+
});
|
|
222
338
|
|
|
223
339
|
return subscriberEmitter;
|
|
224
340
|
}
|
|
225
341
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.publishChannel = ch;
|
|
233
|
-
});
|
|
342
|
+
/**
|
|
343
|
+
* @returns {Promise<void>}
|
|
344
|
+
*/
|
|
345
|
+
async _createPublishChannel() {
|
|
346
|
+
const model = await this.openPromise;
|
|
347
|
+
this.publishChannel = await model?.createChannel();
|
|
234
348
|
}
|
|
235
349
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
350
|
+
/**
|
|
351
|
+
* @returns {Promise<amqplib.Channel | undefined>}
|
|
352
|
+
*/
|
|
353
|
+
async _getSubscribeChannel() {
|
|
354
|
+
const model = await this.openPromise;
|
|
355
|
+
return model?.createChannel();
|
|
241
356
|
}
|
|
242
357
|
|
|
358
|
+
/**
|
|
359
|
+
* @param {unknown} body
|
|
360
|
+
* @returns {string}
|
|
361
|
+
*/
|
|
243
362
|
_serialize(body) {
|
|
244
363
|
return JSON.stringify(body);
|
|
245
364
|
}
|
|
246
365
|
|
|
366
|
+
/**
|
|
367
|
+
* @param {string} content
|
|
368
|
+
* @returns {unknown}
|
|
369
|
+
*/
|
|
247
370
|
_deserialize(content) {
|
|
248
371
|
return JSON.parse(content);
|
|
249
372
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
373
|
}
|
|
253
374
|
|
|
254
375
|
|