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