@autofleet/rabbit 3.2.25 → 3.2.26-beta.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/dist/index.d.ts +16 -11
- package/dist/index.js +105 -78
- package/dist/lib/consts.d.ts +4 -2
- package/dist/lib/consts.js +6 -3
- package/dist/lib/types.d.ts +7 -0
- package/package.json +2 -2
- package/src/index.ts +135 -98
- package/src/lib/consts.ts +5 -2
- package/src/lib/types.ts +8 -0
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { EventEmitter } from 'events';
|
|
|
3
3
|
import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from 'amqp-connection-manager';
|
|
4
4
|
import { ConfirmChannel, ConsumeMessage, Options, Replies } from 'amqplib';
|
|
5
5
|
import { RedisConfig } from './lib/redis';
|
|
6
|
-
import {
|
|
6
|
+
import { ConnectionPurpose } from './lib/consts';
|
|
7
|
+
import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, AssertExchangePromisesDictionary, ConnectionData } from './lib/types';
|
|
7
8
|
export interface IAfRabbitMq {
|
|
8
9
|
ack: any;
|
|
9
10
|
nack: any;
|
|
@@ -37,10 +38,12 @@ type newChannelOpts = {
|
|
|
37
38
|
name?: string;
|
|
38
39
|
onClose?: null | ((args: any | null) => void);
|
|
39
40
|
options?: CreateChannelOpts | undefined;
|
|
41
|
+
connectionPurpose?: ConnectionPurpose;
|
|
40
42
|
};
|
|
41
43
|
type assertChannelOpts = {
|
|
42
44
|
channelName?: string;
|
|
43
45
|
force?: boolean;
|
|
46
|
+
connectionPurpose?: ConnectionPurpose;
|
|
44
47
|
};
|
|
45
48
|
declare class RabbitMq implements IAfRabbitMq {
|
|
46
49
|
static parseMsg(msg: any): any;
|
|
@@ -57,12 +60,14 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
57
60
|
};
|
|
58
61
|
DISCONNECT_MSG: string;
|
|
59
62
|
RECONNECT_MSG: string;
|
|
60
|
-
|
|
63
|
+
publishChannel: ChannelWrapper | null;
|
|
61
64
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
62
65
|
blockReconnect: boolean | null | undefined;
|
|
63
|
-
|
|
66
|
+
connectionsMap: {
|
|
67
|
+
[ConnectionPurpose.Consume]: ConnectionData;
|
|
68
|
+
[ConnectionPurpose.Publish]: ConnectionData;
|
|
69
|
+
};
|
|
64
70
|
em: EventEmitter;
|
|
65
|
-
creatingConnection: boolean;
|
|
66
71
|
exchanges: ExchangesCache;
|
|
67
72
|
queues: QueuesCache;
|
|
68
73
|
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
@@ -77,16 +82,16 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
77
82
|
private shouldConsumeMessageByTimestamp;
|
|
78
83
|
ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
|
|
79
84
|
nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
|
|
80
|
-
getConnection(): Promise<AmqpConnectionManager>;
|
|
81
|
-
getNewChannel({ name, onClose, options }
|
|
82
|
-
assertChannel({ force }
|
|
85
|
+
getConnection(connectionPurpose: ConnectionPurpose): Promise<AmqpConnectionManager | null | undefined>;
|
|
86
|
+
getNewChannel({ name, onClose, options, connectionPurpose, }: newChannelOpts): Promise<ChannelWrapper>;
|
|
87
|
+
assertChannel({ force, connectionPurpose }: assertChannelOpts): Promise<ChannelWrapper>;
|
|
83
88
|
assertExchange(exchangeName: string, options?: any): Promise<any>;
|
|
84
|
-
getQueueLength(queue: string): Promise<Replies.AssertQueue>;
|
|
89
|
+
getQueueLength(queue: string, connectionPurpose?: ConnectionPurpose): Promise<Replies.AssertQueue>;
|
|
85
90
|
private deleteQueue;
|
|
86
|
-
bindQueue(queue: string, exchange: string): Promise<void>;
|
|
87
|
-
setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
91
|
+
bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose): Promise<void>;
|
|
92
|
+
setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
88
93
|
static shouldUseQuorum(queueName: string): boolean;
|
|
89
|
-
assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
|
|
94
|
+
assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<any>;
|
|
90
95
|
private saveConsumer;
|
|
91
96
|
consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
|
|
92
97
|
private lockRedisIfNeeded;
|
package/dist/index.js
CHANGED
|
@@ -117,10 +117,22 @@ class RabbitMq {
|
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
119
|
this.em = new events_1.EventEmitter();
|
|
120
|
-
this.
|
|
120
|
+
this.publishChannel = null;
|
|
121
121
|
this.publishChannelSetupPromise = null;
|
|
122
|
-
this.
|
|
123
|
-
|
|
122
|
+
this.connectionsMap = {
|
|
123
|
+
[consts_1.ConnectionPurpose.Consume]: {
|
|
124
|
+
connection: null,
|
|
125
|
+
creatingConnection: false,
|
|
126
|
+
connectionCreatedEventName: 'consumeConnectionCreated',
|
|
127
|
+
connectionFailedEventName: 'consumeConnectionFailed',
|
|
128
|
+
},
|
|
129
|
+
[consts_1.ConnectionPurpose.Publish]: {
|
|
130
|
+
connection: null,
|
|
131
|
+
creatingConnection: false,
|
|
132
|
+
connectionCreatedEventName: 'publishConnectionCreated',
|
|
133
|
+
connectionFailedEventName: 'publishConnectionFailed',
|
|
134
|
+
},
|
|
135
|
+
};
|
|
124
136
|
this.exchanges = {};
|
|
125
137
|
this.queues = {};
|
|
126
138
|
this.queueSetupPromises = {};
|
|
@@ -142,30 +154,31 @@ class RabbitMq {
|
|
|
142
154
|
});
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
|
-
async getConnection() {
|
|
157
|
+
async getConnection(connectionPurpose) {
|
|
146
158
|
return new Promise(async (resolve, reject) => {
|
|
159
|
+
const { connection, creatingConnection, connectionCreatedEventName, connectionFailedEventName, } = this.connectionsMap[connectionPurpose];
|
|
147
160
|
if (this.blockReconnect) {
|
|
148
161
|
debug('rabbit: block reconnect');
|
|
149
162
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
150
163
|
// @ts-ignore
|
|
151
164
|
return resolve();
|
|
152
165
|
}
|
|
153
|
-
if (
|
|
154
|
-
if (this.options?.disableReconnect ||
|
|
166
|
+
if (connection !== null) {
|
|
167
|
+
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
155
168
|
debug('rabbit: connection - is connected');
|
|
156
169
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
157
170
|
// @ts-ignore
|
|
158
|
-
return resolve(
|
|
171
|
+
return resolve(connection);
|
|
159
172
|
}
|
|
160
173
|
debug('rabbit: connection - reconnecting');
|
|
161
174
|
}
|
|
162
|
-
if (
|
|
175
|
+
if (creatingConnection) {
|
|
163
176
|
debug('rabbit: creating connection emi');
|
|
164
|
-
this.em.once(
|
|
165
|
-
this.em.once(
|
|
177
|
+
this.em.once(connectionCreatedEventName, resolve);
|
|
178
|
+
this.em.once(connectionFailedEventName, reject);
|
|
166
179
|
return;
|
|
167
180
|
}
|
|
168
|
-
this.creatingConnection = true;
|
|
181
|
+
this.connectionsMap[connectionPurpose].creatingConnection = true;
|
|
169
182
|
let isResolved = false;
|
|
170
183
|
// It is import to use it as a function and not as a variable
|
|
171
184
|
// because of k8s changes the env variables
|
|
@@ -178,29 +191,29 @@ class RabbitMq {
|
|
|
178
191
|
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
179
192
|
};
|
|
180
193
|
const defaultUrls = findServers();
|
|
181
|
-
const
|
|
194
|
+
const newConnection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
182
195
|
findServers,
|
|
183
196
|
});
|
|
184
|
-
this.connection =
|
|
185
|
-
|
|
197
|
+
this.connectionsMap[connectionPurpose].connection = newConnection;
|
|
198
|
+
logger_1.default.info(`rabbit: created new connection ${connectionPurpose}`);
|
|
199
|
+
newConnection.on('error', (err) => {
|
|
186
200
|
logger_1.default.error('rabbit: connection error', { err });
|
|
187
201
|
if (!isResolved) {
|
|
188
202
|
isResolved = true;
|
|
189
203
|
reject(err);
|
|
190
|
-
this.em.emit(
|
|
204
|
+
this.em.emit(connectionFailedEventName, err);
|
|
191
205
|
}
|
|
192
206
|
});
|
|
193
|
-
|
|
207
|
+
newConnection.on('connectFailed', (err) => {
|
|
194
208
|
this.consumersTags = [];
|
|
195
209
|
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
196
210
|
if (!isResolved) {
|
|
197
211
|
isResolved = true;
|
|
198
212
|
reject(err);
|
|
199
|
-
this.em.emit(
|
|
213
|
+
this.em.emit(connectionFailedEventName, err);
|
|
200
214
|
}
|
|
201
215
|
});
|
|
202
|
-
|
|
203
|
-
// this.channel = null;
|
|
216
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
204
217
|
this.consumersTags = [];
|
|
205
218
|
debug('rabbit: connection closed');
|
|
206
219
|
if (this.options?.disableReconnect) {
|
|
@@ -211,24 +224,26 @@ class RabbitMq {
|
|
|
211
224
|
logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
212
225
|
}
|
|
213
226
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
this.
|
|
217
|
-
this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
|
|
227
|
+
newConnection.once('connect', async () => {
|
|
228
|
+
this.connectionsMap[connectionPurpose].creatingConnection = false;
|
|
229
|
+
this.em.emit(connectionCreatedEventName, newConnection);
|
|
218
230
|
isResolved = true;
|
|
219
|
-
resolve(
|
|
231
|
+
resolve(newConnection);
|
|
220
232
|
});
|
|
221
233
|
});
|
|
222
234
|
}
|
|
223
|
-
async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}
|
|
235
|
+
async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connectionPurpose = consts_1.ConnectionPurpose.Consume, }) {
|
|
224
236
|
let connection;
|
|
225
237
|
try {
|
|
226
|
-
connection = await this.getConnection();
|
|
238
|
+
connection = await this.getConnection(connectionPurpose);
|
|
227
239
|
}
|
|
228
240
|
catch (e) {
|
|
229
241
|
logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
230
242
|
throw e;
|
|
231
243
|
}
|
|
244
|
+
if (!connection) {
|
|
245
|
+
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
246
|
+
}
|
|
232
247
|
const channel = connection.createChannel({ ...options });
|
|
233
248
|
(0, events_1.once)(channel, 'close').then((args) => {
|
|
234
249
|
logger_1.default.error(`rabbit: channel ${name} closed`);
|
|
@@ -244,18 +259,20 @@ class RabbitMq {
|
|
|
244
259
|
throw err;
|
|
245
260
|
}
|
|
246
261
|
}
|
|
247
|
-
async assertChannel({ force = false
|
|
262
|
+
async assertChannel({ force = false, connectionPurpose = consts_1.ConnectionPurpose.Consume }) {
|
|
263
|
+
debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
|
|
248
264
|
if (!this.publishChannelSetupPromise) {
|
|
249
265
|
this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
|
|
250
|
-
if (this.
|
|
251
|
-
return resolve(this.
|
|
266
|
+
if (this.publishChannel && !force) {
|
|
267
|
+
return resolve(this.publishChannel);
|
|
252
268
|
}
|
|
253
269
|
try {
|
|
254
|
-
const channel = await this.getNewChannel({});
|
|
270
|
+
const channel = await this.getNewChannel({ connectionPurpose });
|
|
271
|
+
debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
|
|
255
272
|
channel.on('error', (err) => {
|
|
256
273
|
logger_1.default.error('rabbit: channel error', { err });
|
|
257
274
|
});
|
|
258
|
-
this.
|
|
275
|
+
this.publishChannel = channel;
|
|
259
276
|
resolve(channel);
|
|
260
277
|
}
|
|
261
278
|
catch (e) {
|
|
@@ -265,8 +282,8 @@ class RabbitMq {
|
|
|
265
282
|
}
|
|
266
283
|
return this.publishChannelSetupPromise;
|
|
267
284
|
}
|
|
268
|
-
async assertExchange(exchangeName, options) {
|
|
269
|
-
const channel = await this.assertChannel();
|
|
285
|
+
async assertExchange(exchangeName, options = { connectionPurpose: consts_1.ConnectionPurpose.Consume }) {
|
|
286
|
+
const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
|
|
270
287
|
if (this.exchanges[exchangeName]) {
|
|
271
288
|
delete this.assertExchangePromises[exchangeName];
|
|
272
289
|
return this.exchanges[exchangeName];
|
|
@@ -278,29 +295,30 @@ class RabbitMq {
|
|
|
278
295
|
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
279
296
|
return this.exchanges[exchangeName];
|
|
280
297
|
}
|
|
281
|
-
async getQueueLength(queue) {
|
|
298
|
+
async getQueueLength(queue, connectionPurpose = consts_1.ConnectionPurpose.Consume) {
|
|
282
299
|
RabbitMq.validateName('queue', queue);
|
|
283
|
-
const {
|
|
284
|
-
|
|
300
|
+
const { connection } = this.connectionsMap[connectionPurpose];
|
|
301
|
+
const { publishChannel } = this;
|
|
302
|
+
if (!publishChannel) {
|
|
285
303
|
throw new Error('channel is not defined');
|
|
286
304
|
}
|
|
287
|
-
debug('rabbit: getting queue length', { queue, connected:
|
|
288
|
-
return
|
|
305
|
+
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
306
|
+
return publishChannel?.checkQueue(queue);
|
|
289
307
|
}
|
|
290
|
-
async deleteQueue(queue) {
|
|
308
|
+
async deleteQueue(queue, connectionPurpose) {
|
|
291
309
|
RabbitMq.validateName('queue', queue);
|
|
292
|
-
const channel = await this.assertChannel();
|
|
310
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
293
311
|
logger_1.default.info('rabbit: deleting queue', { queue });
|
|
294
312
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
295
313
|
debug('queue deleted', deleteQueueRes);
|
|
296
314
|
return deleteQueueRes;
|
|
297
315
|
}
|
|
298
|
-
async bindQueue(queue, exchange) {
|
|
299
|
-
const channel = await this.assertChannel();
|
|
316
|
+
async bindQueue(queue, exchange, connectionPurpose) {
|
|
317
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
300
318
|
await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
301
319
|
return channel.bindQueue(queue, exchange, '');
|
|
302
320
|
}
|
|
303
|
-
async setupQueue(queueName, options) {
|
|
321
|
+
async setupQueue(queueName, connectionPurpose, options) {
|
|
304
322
|
let queue;
|
|
305
323
|
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
306
324
|
const localeOptions = {
|
|
@@ -313,7 +331,7 @@ class RabbitMq {
|
|
|
313
331
|
},
|
|
314
332
|
};
|
|
315
333
|
try {
|
|
316
|
-
const channel = await this.assertChannel();
|
|
334
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
317
335
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
318
336
|
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
319
337
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
@@ -323,8 +341,8 @@ class RabbitMq {
|
|
|
323
341
|
logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
324
342
|
if (!this.options?.dontRetryAssert) {
|
|
325
343
|
debug('retrying assertQueue', { queueName });
|
|
326
|
-
const channel = await this.assertChannel({ force: true });
|
|
327
|
-
await this.deleteQueue(queueName);
|
|
344
|
+
const channel = await this.assertChannel({ force: true, connectionPurpose });
|
|
345
|
+
await this.deleteQueue(queueName, connectionPurpose);
|
|
328
346
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
329
347
|
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
330
348
|
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
@@ -348,7 +366,8 @@ class RabbitMq {
|
|
|
348
366
|
}
|
|
349
367
|
return false;
|
|
350
368
|
}
|
|
351
|
-
async assertQueue(queueName, options) {
|
|
369
|
+
async assertQueue(queueName, connectionPurpose, options) {
|
|
370
|
+
debug('rabbit: start assert queue', { connectionPurpose, queueName });
|
|
352
371
|
RabbitMq.validateName('queue', queueName);
|
|
353
372
|
if (this.queues[queueName]) {
|
|
354
373
|
delete this.queueSetupPromises[queueName];
|
|
@@ -357,7 +376,8 @@ class RabbitMq {
|
|
|
357
376
|
if (this.queueSetupPromises[queueName]) {
|
|
358
377
|
return this.queueSetupPromises[queueName];
|
|
359
378
|
}
|
|
360
|
-
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
379
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
|
|
380
|
+
debug('rabbit: done assert queue', { connectionPurpose, queueName });
|
|
361
381
|
return this.queueSetupPromises[queueName];
|
|
362
382
|
}
|
|
363
383
|
saveConsumer(queue, callback, options) {
|
|
@@ -400,9 +420,9 @@ class RabbitMq {
|
|
|
400
420
|
}
|
|
401
421
|
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
402
422
|
}
|
|
403
|
-
const channel = await this.getNewChannel({});
|
|
423
|
+
const channel = await this.getNewChannel({ connectionPurpose: consts_1.ConnectionPurpose.Consume });
|
|
404
424
|
return channel.addSetup(async (confirmChannel) => {
|
|
405
|
-
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
425
|
+
const q = await this.assertQueue(queue, consts_1.ConnectionPurpose.Consume, optionsWithDefaults);
|
|
406
426
|
await confirmChannel.prefetch(limit, false);
|
|
407
427
|
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
408
428
|
if (!msg) {
|
|
@@ -484,12 +504,12 @@ class RabbitMq {
|
|
|
484
504
|
RabbitMq.validateName('queue', queue);
|
|
485
505
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
486
506
|
await this.saveConsumer(queue, callback, options);
|
|
487
|
-
const channel = await this.getNewChannel({ name: `consume-exchange
|
|
507
|
+
const channel = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
|
|
488
508
|
return channel.addSetup(async (c) => {
|
|
489
509
|
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
490
510
|
await c.assertQueue(queue);
|
|
491
511
|
this.exchanges[exchange] = assertExchange;
|
|
492
|
-
await c.prefetch(limit,
|
|
512
|
+
await c.prefetch(limit, true);
|
|
493
513
|
return Promise.all([
|
|
494
514
|
c.bindQueue(queue, exchange, ''),
|
|
495
515
|
this.consume(queue, callback, options),
|
|
@@ -497,16 +517,17 @@ class RabbitMq {
|
|
|
497
517
|
});
|
|
498
518
|
}
|
|
499
519
|
async publish(exchange, content, customHeaders) {
|
|
520
|
+
debug('rabbit: start publish msg');
|
|
500
521
|
return (0, utils_1.wrapSetImmediate)(async () => {
|
|
501
522
|
RabbitMq.validateName('exchange', exchange);
|
|
502
|
-
const channel = await this.assertChannel();
|
|
503
|
-
await this.assertExchange(exchange);
|
|
523
|
+
const channel = await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
|
|
524
|
+
await this.assertExchange(exchange, { connectionPurpose: consts_1.ConnectionPurpose.Publish });
|
|
504
525
|
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
505
526
|
});
|
|
506
527
|
}
|
|
507
528
|
async sendToQueue(queue, content, options, customHeaders) {
|
|
508
529
|
try {
|
|
509
|
-
await this.assertChannel();
|
|
530
|
+
await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
|
|
510
531
|
}
|
|
511
532
|
catch (e) {
|
|
512
533
|
logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
@@ -514,43 +535,49 @@ class RabbitMq {
|
|
|
514
535
|
}
|
|
515
536
|
try {
|
|
516
537
|
RabbitMq.validateName('queue', queue);
|
|
517
|
-
await this.assertQueue(queue, options);
|
|
538
|
+
await this.assertQueue(queue, consts_1.ConnectionPurpose.Publish, options);
|
|
518
539
|
}
|
|
519
540
|
catch (e) {
|
|
520
541
|
logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
521
542
|
throw e;
|
|
522
543
|
}
|
|
523
544
|
try {
|
|
524
|
-
const res = await this.
|
|
545
|
+
const res = await this.publishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
525
546
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
526
547
|
return res;
|
|
527
548
|
}
|
|
528
549
|
catch (e) {
|
|
529
|
-
|
|
530
|
-
logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
550
|
+
logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
|
|
531
551
|
throw e;
|
|
532
552
|
}
|
|
533
553
|
}
|
|
534
554
|
async isConnected() {
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
555
|
+
debug('rabbit: start is connected');
|
|
556
|
+
const isEachConnectionConnected = await Promise.all(Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
|
|
557
|
+
const { connection } = connectionData;
|
|
558
|
+
debug('rabbit: is connected inside map', { connection, connectionPurpose });
|
|
559
|
+
const isConnected = connection?.isConnected();
|
|
560
|
+
if (!isConnected) {
|
|
561
|
+
logger_1.default.error('rabbit: isConnected - false', { connectionPurpose });
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
if (connectionPurpose === consts_1.ConnectionPurpose.Publish) {
|
|
565
|
+
const channel = await this.assertChannel({ connectionPurpose: connectionPurpose });
|
|
566
|
+
try {
|
|
567
|
+
await Promise.all([
|
|
568
|
+
channel.waitForConnect(),
|
|
569
|
+
...this.consumers.map((c) => channel.checkQueue(c.queue)),
|
|
570
|
+
]);
|
|
571
|
+
}
|
|
572
|
+
catch (e) {
|
|
573
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
logger_1.default.info('rabbit: isConnected - true');
|
|
578
|
+
return true;
|
|
579
|
+
}));
|
|
580
|
+
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
554
581
|
}
|
|
555
582
|
async gracefulShutdown(signal) {
|
|
556
583
|
const tagsNumber = this.consumersTags.length;
|
package/dist/lib/consts.d.ts
CHANGED
|
@@ -6,8 +6,6 @@ export declare const USER_TRACING_HEADER = "x-af-user-id";
|
|
|
6
6
|
export declare const AUTOMATION_ID_HEADER = "x-af-automation-id";
|
|
7
7
|
export declare const USER_OBJECT = "userObject";
|
|
8
8
|
export declare const DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
9
|
-
export declare const CONNECTION_CREATED_CONST = "connectionCreated";
|
|
10
|
-
export declare const CONNECTION_FAILED_CONST = "connectionFailed";
|
|
11
9
|
export declare const DEFAULT_OPTIONS: {
|
|
12
10
|
limit: number;
|
|
13
11
|
retries: number;
|
|
@@ -17,3 +15,7 @@ export declare const DEFAULT_OPTIONS: {
|
|
|
17
15
|
auditContext: null;
|
|
18
16
|
enableRabbitTrace: boolean;
|
|
19
17
|
};
|
|
18
|
+
export declare enum ConnectionPurpose {
|
|
19
|
+
Consume = "consume",
|
|
20
|
+
Publish = "publish"
|
|
21
|
+
}
|
package/dist/lib/consts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ConnectionPurpose = exports.DEFAULT_OPTIONS = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.AUTOMATION_ID_HEADER = exports.USER_TRACING_HEADER = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
|
|
4
4
|
exports.DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 12;
|
|
5
5
|
exports.DEFAULT_LOCK_TIMEOUT = 1000 * 5;
|
|
6
6
|
exports.RETRY_HEADER = 'x-retry-count';
|
|
@@ -9,8 +9,6 @@ exports.USER_TRACING_HEADER = 'x-af-user-id';
|
|
|
9
9
|
exports.AUTOMATION_ID_HEADER = 'x-af-automation-id';
|
|
10
10
|
exports.USER_OBJECT = 'userObject';
|
|
11
11
|
exports.DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
12
|
-
exports.CONNECTION_CREATED_CONST = 'connectionCreated';
|
|
13
|
-
exports.CONNECTION_FAILED_CONST = 'connectionFailed';
|
|
14
12
|
exports.DEFAULT_OPTIONS = {
|
|
15
13
|
limit: 1,
|
|
16
14
|
retries: 1,
|
|
@@ -20,3 +18,8 @@ exports.DEFAULT_OPTIONS = {
|
|
|
20
18
|
auditContext: null,
|
|
21
19
|
enableRabbitTrace: false,
|
|
22
20
|
};
|
|
21
|
+
var ConnectionPurpose;
|
|
22
|
+
(function (ConnectionPurpose) {
|
|
23
|
+
ConnectionPurpose["Consume"] = "consume";
|
|
24
|
+
ConnectionPurpose["Publish"] = "publish";
|
|
25
|
+
})(ConnectionPurpose = exports.ConnectionPurpose || (exports.ConnectionPurpose = {}));
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AmqpConnectionManager } from 'amqp-connection-manager';
|
|
1
2
|
import { ConsumeMessage, Options, Replies } from 'amqplib';
|
|
2
3
|
export interface ExchangesCache {
|
|
3
4
|
[key: string]: any;
|
|
@@ -41,3 +42,9 @@ export type AfConsumer = {
|
|
|
41
42
|
options: ConsumeOptions | undefined;
|
|
42
43
|
};
|
|
43
44
|
export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
|
|
45
|
+
export type ConnectionData = {
|
|
46
|
+
connection: AmqpConnectionManager | null;
|
|
47
|
+
creatingConnection: boolean;
|
|
48
|
+
connectionCreatedEventName: string;
|
|
49
|
+
connectionFailedEventName: string;
|
|
50
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/rabbit",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.26-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"dev": "nodemon"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@autofleet/zehut": "^3.
|
|
21
|
+
"@autofleet/zehut": "^3.0.10",
|
|
22
22
|
"amqp-connection-manager": "4.1.9",
|
|
23
23
|
"amqplib": "0.10.3",
|
|
24
24
|
"bluebird": "^3.7.2",
|
package/src/index.ts
CHANGED
|
@@ -20,8 +20,7 @@ import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
|
20
20
|
import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
|
|
21
21
|
import {
|
|
22
22
|
AUTOMATION_ID_HEADER,
|
|
23
|
-
|
|
24
|
-
CONNECTION_FAILED_CONST,
|
|
23
|
+
ConnectionPurpose,
|
|
25
24
|
DEFAULT_LOCK_TIMEOUT,
|
|
26
25
|
DEFAULT_OPTIONS,
|
|
27
26
|
RETRY_HEADER,
|
|
@@ -40,13 +39,13 @@ import {
|
|
|
40
39
|
CONSUMER_DEFAULT_OPTIONS,
|
|
41
40
|
QueueSetupPromisesDictionary,
|
|
42
41
|
AssertExchangePromisesDictionary,
|
|
42
|
+
ConnectionData,
|
|
43
43
|
} from './lib/types';
|
|
44
44
|
|
|
45
45
|
// const debug = nodeDebug('af-rabbitmq')
|
|
46
46
|
const debug = logger.debug.bind(logger);
|
|
47
47
|
|
|
48
48
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
49
|
-
|
|
50
49
|
export interface IAfRabbitMq {
|
|
51
50
|
ack: any;
|
|
52
51
|
nack: any;
|
|
@@ -86,11 +85,13 @@ type newChannelOpts = {
|
|
|
86
85
|
name?: string;
|
|
87
86
|
onClose?: null | ((args: any | null) => void);
|
|
88
87
|
options?: CreateChannelOpts | undefined;
|
|
88
|
+
connectionPurpose?: ConnectionPurpose,
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
type assertChannelOpts = {
|
|
92
92
|
channelName?: string;
|
|
93
93
|
force?: boolean;
|
|
94
|
+
connectionPurpose?: ConnectionPurpose;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
type AfConsumer = {
|
|
@@ -102,7 +103,7 @@ type AfConsumer = {
|
|
|
102
103
|
const HEARTBEAT = '60';
|
|
103
104
|
|
|
104
105
|
class RabbitMq implements IAfRabbitMq {
|
|
105
|
-
static parseMsg(msg: any)
|
|
106
|
+
static parseMsg(msg: any): any {
|
|
106
107
|
let { content } = msg;
|
|
107
108
|
content = content.toString();
|
|
108
109
|
|
|
@@ -143,18 +144,19 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
143
144
|
|
|
144
145
|
RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
publishChannel: ChannelWrapper | null;
|
|
147
148
|
|
|
148
149
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
149
150
|
|
|
150
|
-
blockReconnect: boolean | null | undefined
|
|
151
|
+
blockReconnect: boolean | null | undefined;
|
|
151
152
|
|
|
152
|
-
|
|
153
|
+
connectionsMap: {
|
|
154
|
+
[ConnectionPurpose.Consume]: ConnectionData;
|
|
155
|
+
[ConnectionPurpose.Publish]: ConnectionData;
|
|
156
|
+
};
|
|
153
157
|
|
|
154
158
|
em: EventEmitter;
|
|
155
159
|
|
|
156
|
-
creatingConnection: boolean;
|
|
157
|
-
|
|
158
160
|
exchanges: ExchangesCache;
|
|
159
161
|
|
|
160
162
|
queues: QueuesCache;
|
|
@@ -176,10 +178,22 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
176
178
|
|
|
177
179
|
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
|
|
178
180
|
this.em = new EventEmitter();
|
|
179
|
-
this.
|
|
181
|
+
this.publishChannel = null;
|
|
180
182
|
this.publishChannelSetupPromise = null;
|
|
181
|
-
this.
|
|
182
|
-
|
|
183
|
+
this.connectionsMap = {
|
|
184
|
+
[ConnectionPurpose.Consume]: {
|
|
185
|
+
connection: null,
|
|
186
|
+
creatingConnection: false,
|
|
187
|
+
connectionCreatedEventName: 'consumeConnectionCreated',
|
|
188
|
+
connectionFailedEventName: 'consumeConnectionFailed',
|
|
189
|
+
},
|
|
190
|
+
[ConnectionPurpose.Publish]: {
|
|
191
|
+
connection: null,
|
|
192
|
+
creatingConnection: false,
|
|
193
|
+
connectionCreatedEventName: 'publishConnectionCreated',
|
|
194
|
+
connectionFailedEventName: 'publishConnectionFailed',
|
|
195
|
+
},
|
|
196
|
+
};
|
|
183
197
|
this.exchanges = {};
|
|
184
198
|
this.queues = {};
|
|
185
199
|
this.queueSetupPromises = {};
|
|
@@ -216,7 +230,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
216
230
|
return false;
|
|
217
231
|
}
|
|
218
232
|
|
|
219
|
-
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage)
|
|
233
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
220
234
|
if (msg) {
|
|
221
235
|
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
222
236
|
await channel.ack(msg);
|
|
@@ -242,8 +256,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
242
256
|
userMsg: ConsumeMessageOrNull,
|
|
243
257
|
{
|
|
244
258
|
skipRetry = false,
|
|
245
|
-
}: NackOptions = {
|
|
246
|
-
)
|
|
259
|
+
}: NackOptions = {},
|
|
260
|
+
): Promise<any> => {
|
|
247
261
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
248
262
|
if (channel && msg) {
|
|
249
263
|
if (
|
|
@@ -277,30 +291,36 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
277
291
|
}
|
|
278
292
|
}
|
|
279
293
|
|
|
280
|
-
async getConnection() {
|
|
281
|
-
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
294
|
+
async getConnection(connectionPurpose: ConnectionPurpose) {
|
|
295
|
+
return new Promise<AmqpConnectionManager | undefined | null>(async (resolve, reject) => {
|
|
296
|
+
const {
|
|
297
|
+
connection,
|
|
298
|
+
creatingConnection,
|
|
299
|
+
connectionCreatedEventName,
|
|
300
|
+
connectionFailedEventName,
|
|
301
|
+
} = this.connectionsMap[connectionPurpose];
|
|
282
302
|
if (this.blockReconnect) {
|
|
283
303
|
debug('rabbit: block reconnect');
|
|
284
304
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
285
305
|
// @ts-ignore
|
|
286
306
|
return resolve();
|
|
287
307
|
}
|
|
288
|
-
if (
|
|
289
|
-
if (this.options?.disableReconnect ||
|
|
308
|
+
if (connection !== null) {
|
|
309
|
+
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
290
310
|
debug('rabbit: connection - is connected');
|
|
291
311
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
292
312
|
// @ts-ignore
|
|
293
|
-
return resolve(
|
|
313
|
+
return resolve(connection);
|
|
294
314
|
}
|
|
295
315
|
debug('rabbit: connection - reconnecting');
|
|
296
316
|
}
|
|
297
|
-
if (
|
|
317
|
+
if (creatingConnection) {
|
|
298
318
|
debug('rabbit: creating connection emi');
|
|
299
|
-
this.em.once(
|
|
300
|
-
this.em.once(
|
|
319
|
+
this.em.once(connectionCreatedEventName, resolve);
|
|
320
|
+
this.em.once(connectionFailedEventName, reject);
|
|
301
321
|
return;
|
|
302
322
|
}
|
|
303
|
-
this.creatingConnection = true;
|
|
323
|
+
this.connectionsMap[connectionPurpose].creatingConnection = true;
|
|
304
324
|
let isResolved = false;
|
|
305
325
|
|
|
306
326
|
// It is import to use it as a function and not as a variable
|
|
@@ -317,32 +337,33 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
317
337
|
};
|
|
318
338
|
|
|
319
339
|
const defaultUrls = findServers();
|
|
320
|
-
const
|
|
340
|
+
const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
321
341
|
findServers,
|
|
322
342
|
});
|
|
323
343
|
|
|
324
|
-
this.connection =
|
|
325
|
-
|
|
344
|
+
this.connectionsMap[connectionPurpose].connection = newConnection;
|
|
345
|
+
logger.info(`rabbit: created new connection ${connectionPurpose}`);
|
|
346
|
+
|
|
347
|
+
newConnection.on('error', (err) => {
|
|
326
348
|
logger.error('rabbit: connection error', { err });
|
|
327
349
|
if (!isResolved) {
|
|
328
350
|
isResolved = true;
|
|
329
351
|
reject(err);
|
|
330
|
-
this.em.emit(
|
|
352
|
+
this.em.emit(connectionFailedEventName, err);
|
|
331
353
|
}
|
|
332
354
|
});
|
|
333
355
|
|
|
334
|
-
|
|
356
|
+
newConnection.on('connectFailed', (err) => {
|
|
335
357
|
this.consumersTags = [];
|
|
336
358
|
logger.error('rabbit: connection connectFailed', { err });
|
|
337
359
|
if (!isResolved) {
|
|
338
360
|
isResolved = true;
|
|
339
361
|
reject(err);
|
|
340
|
-
this.em.emit(
|
|
362
|
+
this.em.emit(connectionFailedEventName, err);
|
|
341
363
|
}
|
|
342
364
|
});
|
|
343
365
|
|
|
344
|
-
|
|
345
|
-
// this.channel = null;
|
|
366
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
346
367
|
this.consumersTags = [];
|
|
347
368
|
debug('rabbit: connection closed');
|
|
348
369
|
if (this.options?.disableReconnect) {
|
|
@@ -352,25 +373,28 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
352
373
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
353
374
|
}
|
|
354
375
|
});
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
this.creatingConnection = false;
|
|
359
|
-
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
376
|
+
newConnection.once('connect', async () => {
|
|
377
|
+
this.connectionsMap[connectionPurpose].creatingConnection = false;
|
|
378
|
+
this.em.emit(connectionCreatedEventName, newConnection);
|
|
360
379
|
isResolved = true;
|
|
361
|
-
resolve(
|
|
380
|
+
resolve(newConnection);
|
|
362
381
|
});
|
|
363
382
|
});
|
|
364
383
|
}
|
|
365
384
|
|
|
366
|
-
async getNewChannel({
|
|
367
|
-
|
|
385
|
+
async getNewChannel({
|
|
386
|
+
name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
|
|
387
|
+
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
388
|
+
let connection!: AmqpConnectionManager | undefined | null;
|
|
368
389
|
try {
|
|
369
|
-
connection = await this.getConnection();
|
|
390
|
+
connection = await this.getConnection(connectionPurpose);
|
|
370
391
|
} catch (e) {
|
|
371
392
|
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
372
393
|
throw e;
|
|
373
394
|
}
|
|
395
|
+
if (!connection) {
|
|
396
|
+
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
397
|
+
}
|
|
374
398
|
const channel = connection.createChannel({ ...options });
|
|
375
399
|
once(channel, 'close').then((args) => {
|
|
376
400
|
logger.error(`rabbit: channel ${name} closed`);
|
|
@@ -386,19 +410,21 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
386
410
|
}
|
|
387
411
|
}
|
|
388
412
|
|
|
389
|
-
async assertChannel({ force = false }
|
|
413
|
+
async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
|
|
414
|
+
debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
|
|
390
415
|
if (!this.publishChannelSetupPromise) {
|
|
391
416
|
this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
392
|
-
if (this.
|
|
393
|
-
return resolve(this.
|
|
417
|
+
if (this.publishChannel && !force) {
|
|
418
|
+
return resolve(this.publishChannel);
|
|
394
419
|
}
|
|
395
420
|
|
|
396
421
|
try {
|
|
397
|
-
const channel = await this.getNewChannel({});
|
|
422
|
+
const channel = await this.getNewChannel({ connectionPurpose });
|
|
423
|
+
debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
|
|
398
424
|
channel.on('error', (err) => {
|
|
399
425
|
logger.error('rabbit: channel error', { err });
|
|
400
426
|
});
|
|
401
|
-
this.
|
|
427
|
+
this.publishChannel = channel;
|
|
402
428
|
resolve(channel);
|
|
403
429
|
} catch (e) {
|
|
404
430
|
reject(e);
|
|
@@ -408,9 +434,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
408
434
|
return this.publishChannelSetupPromise;
|
|
409
435
|
}
|
|
410
436
|
|
|
411
|
-
async assertExchange(exchangeName: string, options
|
|
412
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
413
|
-
|
|
437
|
+
async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
|
|
438
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
|
|
414
439
|
if (this.exchanges[exchangeName]) {
|
|
415
440
|
delete this.assertExchangePromises[exchangeName];
|
|
416
441
|
return this.exchanges[exchangeName];
|
|
@@ -425,32 +450,33 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
425
450
|
return this.exchanges[exchangeName];
|
|
426
451
|
}
|
|
427
452
|
|
|
428
|
-
async getQueueLength(queue: string) {
|
|
453
|
+
async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
|
|
429
454
|
RabbitMq.validateName('queue', queue);
|
|
430
|
-
const {
|
|
431
|
-
|
|
455
|
+
const { connection } = this.connectionsMap[connectionPurpose];
|
|
456
|
+
const { publishChannel } = this;
|
|
457
|
+
if (!publishChannel) {
|
|
432
458
|
throw new Error('channel is not defined');
|
|
433
459
|
}
|
|
434
|
-
debug('rabbit: getting queue length', { queue, connected:
|
|
435
|
-
return
|
|
460
|
+
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
461
|
+
return publishChannel?.checkQueue(queue);
|
|
436
462
|
}
|
|
437
463
|
|
|
438
|
-
private async deleteQueue(queue: string) {
|
|
464
|
+
private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
|
|
439
465
|
RabbitMq.validateName('queue', queue);
|
|
440
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
466
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
441
467
|
logger.info('rabbit: deleting queue', { queue });
|
|
442
468
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
443
469
|
debug('queue deleted', deleteQueueRes);
|
|
444
470
|
return deleteQueueRes;
|
|
445
471
|
}
|
|
446
472
|
|
|
447
|
-
async bindQueue(queue: string, exchange: string) {
|
|
448
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
473
|
+
async bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose) {
|
|
474
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
449
475
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
450
476
|
return channel.bindQueue(queue, exchange, '');
|
|
451
477
|
}
|
|
452
478
|
|
|
453
|
-
async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
479
|
+
async setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
454
480
|
let queue: Replies.AssertQueue;
|
|
455
481
|
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
456
482
|
const localeOptions = {
|
|
@@ -463,7 +489,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
463
489
|
},
|
|
464
490
|
};
|
|
465
491
|
try {
|
|
466
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
492
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
467
493
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
468
494
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
469
495
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
@@ -472,8 +498,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
472
498
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
473
499
|
if (!this.options?.dontRetryAssert) {
|
|
474
500
|
debug('retrying assertQueue', { queueName });
|
|
475
|
-
const channel = await this.assertChannel({ force: true });
|
|
476
|
-
await this.deleteQueue(queueName);
|
|
501
|
+
const channel = await this.assertChannel({ force: true, connectionPurpose });
|
|
502
|
+
await this.deleteQueue(queueName, connectionPurpose);
|
|
477
503
|
|
|
478
504
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
479
505
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
@@ -503,7 +529,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
503
529
|
return false;
|
|
504
530
|
}
|
|
505
531
|
|
|
506
|
-
async assertQueue(queueName: string, options?: Options.AssertQueue)
|
|
532
|
+
async assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue) {
|
|
533
|
+
debug('rabbit: start assert queue', { connectionPurpose, queueName });
|
|
507
534
|
RabbitMq.validateName('queue', queueName);
|
|
508
535
|
if (this.queues[queueName]) {
|
|
509
536
|
delete this.queueSetupPromises[queueName];
|
|
@@ -514,12 +541,13 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
514
541
|
return this.queueSetupPromises[queueName];
|
|
515
542
|
}
|
|
516
543
|
|
|
517
|
-
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
544
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
|
|
545
|
+
debug('rabbit: done assert queue', { connectionPurpose, queueName });
|
|
518
546
|
return this.queueSetupPromises[queueName];
|
|
519
547
|
}
|
|
520
548
|
|
|
521
549
|
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
522
|
-
const isConsumerExist
|
|
550
|
+
const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
|
|
523
551
|
if (!isConsumerExist) {
|
|
524
552
|
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
525
553
|
this.consumers.push({
|
|
@@ -568,9 +596,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
568
596
|
}
|
|
569
597
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
570
598
|
}
|
|
571
|
-
const channel = await this.getNewChannel({});
|
|
599
|
+
const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
|
|
572
600
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
573
|
-
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
601
|
+
const q = await this.assertQueue(queue, ConnectionPurpose.Consume, optionsWithDefaults);
|
|
574
602
|
await confirmChannel.prefetch(limit, false);
|
|
575
603
|
const { consumerTag } = await confirmChannel.consume(
|
|
576
604
|
queue,
|
|
@@ -658,19 +686,19 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
658
686
|
});
|
|
659
687
|
}
|
|
660
688
|
|
|
661
|
-
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions)
|
|
689
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
662
690
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
663
691
|
RabbitMq.validateName('exchange', exchange);
|
|
664
692
|
RabbitMq.validateName('queue', queue);
|
|
665
693
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
666
694
|
await this.saveConsumer(queue, callback, options);
|
|
667
|
-
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange
|
|
695
|
+
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
|
|
668
696
|
|
|
669
697
|
return channel.addSetup(async (c: ConfirmChannel) => {
|
|
670
698
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
671
699
|
await c.assertQueue(queue);
|
|
672
700
|
this.exchanges[exchange] = assertExchange;
|
|
673
|
-
await c.prefetch(limit,
|
|
701
|
+
await c.prefetch(limit, true);
|
|
674
702
|
return Promise.all([
|
|
675
703
|
c.bindQueue(queue, exchange, ''),
|
|
676
704
|
this.consume(
|
|
@@ -682,11 +710,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
682
710
|
});
|
|
683
711
|
}
|
|
684
712
|
|
|
685
|
-
async publish(exchange: string, content: any, customHeaders?: any)
|
|
713
|
+
async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
|
|
714
|
+
debug('rabbit: start publish msg');
|
|
686
715
|
return wrapSetImmediate(async () => {
|
|
687
716
|
RabbitMq.validateName('exchange', exchange);
|
|
688
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
689
|
-
await this.assertExchange(exchange);
|
|
717
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
718
|
+
await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
|
|
690
719
|
await channel.publish(exchange, '',
|
|
691
720
|
Buffer.from(JSON.stringify(content)),
|
|
692
721
|
RabbitMq.getPublishOptions(customHeaders));
|
|
@@ -700,7 +729,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
700
729
|
customHeaders?: any,
|
|
701
730
|
): Promise<boolean | undefined> {
|
|
702
731
|
try {
|
|
703
|
-
await this.assertChannel();
|
|
732
|
+
await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
704
733
|
} catch (e) {
|
|
705
734
|
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
706
735
|
throw e;
|
|
@@ -708,47 +737,55 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
708
737
|
|
|
709
738
|
try {
|
|
710
739
|
RabbitMq.validateName('queue', queue);
|
|
711
|
-
await this.assertQueue(queue, options);
|
|
740
|
+
await this.assertQueue(queue, ConnectionPurpose.Publish, options);
|
|
712
741
|
} catch (e) {
|
|
713
742
|
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
714
743
|
throw e;
|
|
715
744
|
}
|
|
716
745
|
|
|
717
746
|
try {
|
|
718
|
-
const res = await this.
|
|
747
|
+
const res = await this.publishChannel?.sendToQueue(queue,
|
|
719
748
|
Buffer.from(JSON.stringify(content)),
|
|
720
749
|
RabbitMq.getPublishOptions(customHeaders));
|
|
721
750
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
722
751
|
return res;
|
|
723
752
|
} catch (e) {
|
|
724
|
-
|
|
725
|
-
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
753
|
+
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
|
|
726
754
|
throw e;
|
|
727
755
|
}
|
|
728
756
|
}
|
|
729
757
|
|
|
730
|
-
async isConnected()
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
758
|
+
async isConnected(): Promise<boolean> {
|
|
759
|
+
debug('rabbit: start is connected');
|
|
760
|
+
const isEachConnectionConnected = await Promise.all(
|
|
761
|
+
Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
|
|
762
|
+
const { connection } = connectionData;
|
|
763
|
+
debug('rabbit: is connected inside map', { connection, connectionPurpose });
|
|
764
|
+
const isConnected = connection?.isConnected();
|
|
765
|
+
if (!isConnected) {
|
|
766
|
+
logger.error('rabbit: isConnected - false', { connectionPurpose });
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
if (connectionPurpose === ConnectionPurpose.Publish) {
|
|
770
|
+
const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
|
|
771
|
+
try {
|
|
772
|
+
await Promise.all([
|
|
773
|
+
channel.waitForConnect(),
|
|
774
|
+
...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
|
775
|
+
]);
|
|
776
|
+
} catch (e) {
|
|
777
|
+
logger.error('rabbit: isConnected - false');
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
logger.info('rabbit: isConnected - true');
|
|
782
|
+
return true;
|
|
783
|
+
}),
|
|
784
|
+
);
|
|
785
|
+
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
749
786
|
}
|
|
750
787
|
|
|
751
|
-
async gracefulShutdown(signal: string)
|
|
788
|
+
async gracefulShutdown(signal: string): Promise<void> {
|
|
752
789
|
const tagsNumber = this.consumersTags.length;
|
|
753
790
|
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
754
791
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
package/src/lib/consts.ts
CHANGED
|
@@ -6,8 +6,6 @@ export const USER_TRACING_HEADER = 'x-af-user-id';
|
|
|
6
6
|
export const AUTOMATION_ID_HEADER = 'x-af-automation-id';
|
|
7
7
|
export const USER_OBJECT = 'userObject';
|
|
8
8
|
export const DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
9
|
-
export const CONNECTION_CREATED_CONST = 'connectionCreated';
|
|
10
|
-
export const CONNECTION_FAILED_CONST = 'connectionFailed';
|
|
11
9
|
export const DEFAULT_OPTIONS = {
|
|
12
10
|
limit: 1,
|
|
13
11
|
retries: 1,
|
|
@@ -17,3 +15,8 @@ export const DEFAULT_OPTIONS = {
|
|
|
17
15
|
auditContext: null,
|
|
18
16
|
enableRabbitTrace: false,
|
|
19
17
|
};
|
|
18
|
+
|
|
19
|
+
export enum ConnectionPurpose {
|
|
20
|
+
Consume = 'consume',
|
|
21
|
+
Publish = 'publish',
|
|
22
|
+
}
|
package/src/lib/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AmqpConnectionManager } from 'amqp-connection-manager';
|
|
1
2
|
import { ConsumeMessage, Options, Replies } from 'amqplib';
|
|
2
3
|
|
|
3
4
|
export interface ExchangesCache {
|
|
@@ -59,3 +60,10 @@ export const CONSUMER_DEFAULT_OPTIONS: Options.Consume = {
|
|
|
59
60
|
[HA_PROMOTE_ON_SHUTDOWN]: 'always',
|
|
60
61
|
},
|
|
61
62
|
};
|
|
63
|
+
|
|
64
|
+
export type ConnectionData = {
|
|
65
|
+
connection: AmqpConnectionManager | null;
|
|
66
|
+
creatingConnection: boolean;
|
|
67
|
+
connectionCreatedEventName: string;
|
|
68
|
+
connectionFailedEventName: string;
|
|
69
|
+
};
|