@autofleet/rabbit 3.2.22-beta.5 → 3.2.23-beta.0.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 +14 -14
- package/dist/index.js +108 -93
- package/dist/lib/celery.d.ts +9 -0
- package/dist/lib/celery.js +54 -0
- 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 +5 -2
- package/src/index.ts +141 -118
- package/src/lib/celery.ts +89 -0
- package/src/lib/consts.ts +1 -0
- package/src/lib/types.ts +4 -11
- package/src/lib/utils.ts +6 -2
package/src/index.ts
CHANGED
|
@@ -18,7 +18,9 @@ import logger from './logger';
|
|
|
18
18
|
import RabbitError from './lib/rabbitError';
|
|
19
19
|
import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
20
20
|
import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
|
|
21
|
+
import { sendCeleryTaskViaHttp } from './lib/celery';
|
|
21
22
|
import {
|
|
23
|
+
AUTOMATION_ID_HEADER,
|
|
22
24
|
CONNECTION_CREATED_CONST,
|
|
23
25
|
CONNECTION_FAILED_CONST,
|
|
24
26
|
DEFAULT_LOCK_TIMEOUT,
|
|
@@ -35,15 +37,17 @@ import {
|
|
|
35
37
|
CustomMessageHeaders,
|
|
36
38
|
QueuesCache,
|
|
37
39
|
RedisLockType,
|
|
38
|
-
ExchangesCache,
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
ExchangesCache,
|
|
41
|
+
CONSUMER_DEFAULT_OPTIONS,
|
|
42
|
+
QueueSetupPromisesDictionary,
|
|
43
|
+
AssertExchangePromisesDictionary,
|
|
41
44
|
} from './lib/types';
|
|
42
45
|
|
|
43
46
|
// const debug = nodeDebug('af-rabbitmq')
|
|
44
47
|
const debug = logger.debug.bind(logger);
|
|
45
48
|
|
|
46
49
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
50
|
+
|
|
47
51
|
export interface IAfRabbitMq {
|
|
48
52
|
ack: any;
|
|
49
53
|
nack: any;
|
|
@@ -83,13 +87,11 @@ type newChannelOpts = {
|
|
|
83
87
|
name?: string;
|
|
84
88
|
onClose?: null | ((args: any | null) => void);
|
|
85
89
|
options?: CreateChannelOpts | undefined;
|
|
86
|
-
connectionPurpose?: ConnectionPurpose,
|
|
87
90
|
};
|
|
88
91
|
|
|
89
92
|
type assertChannelOpts = {
|
|
90
93
|
channelName?: string;
|
|
91
94
|
force?: boolean;
|
|
92
|
-
connectionPurpose?: ConnectionPurpose;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
type AfConsumer = {
|
|
@@ -101,7 +103,7 @@ type AfConsumer = {
|
|
|
101
103
|
const HEARTBEAT = '60';
|
|
102
104
|
|
|
103
105
|
class RabbitMq implements IAfRabbitMq {
|
|
104
|
-
static parseMsg(msg: any): any {
|
|
106
|
+
static parseMsg(msg: any) : any {
|
|
105
107
|
let { content } = msg;
|
|
106
108
|
content = content.toString();
|
|
107
109
|
|
|
@@ -146,21 +148,22 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
146
148
|
|
|
147
149
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
148
150
|
|
|
149
|
-
blockReconnect: boolean | null | undefined
|
|
151
|
+
blockReconnect: boolean | null | undefined
|
|
150
152
|
|
|
151
|
-
|
|
152
|
-
[ConnectionPurpose.Consume]: ConnectionData;
|
|
153
|
-
[ConnectionPurpose.Publish]: ConnectionData;
|
|
154
|
-
};
|
|
153
|
+
connection: AmqpConnectionManager | null | undefined
|
|
155
154
|
|
|
156
155
|
em: EventEmitter;
|
|
157
156
|
|
|
157
|
+
creatingConnection: boolean;
|
|
158
|
+
|
|
158
159
|
exchanges: ExchangesCache;
|
|
159
160
|
|
|
160
161
|
queues: QueuesCache;
|
|
161
162
|
|
|
162
163
|
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
163
164
|
|
|
165
|
+
assertExchangePromises: AssertExchangePromisesDictionary;
|
|
166
|
+
|
|
164
167
|
options: AfRabbitOptions | undefined;
|
|
165
168
|
|
|
166
169
|
redisClient: any;
|
|
@@ -176,13 +179,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
176
179
|
this.em = new EventEmitter();
|
|
177
180
|
this.channel = null;
|
|
178
181
|
this.publishChannelSetupPromise = null;
|
|
179
|
-
this.
|
|
180
|
-
|
|
181
|
-
[ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
|
|
182
|
-
};
|
|
182
|
+
this.connection = null;
|
|
183
|
+
this.creatingConnection = false;
|
|
183
184
|
this.exchanges = {};
|
|
184
185
|
this.queues = {};
|
|
185
186
|
this.queueSetupPromises = {};
|
|
187
|
+
this.assertExchangePromises = {};
|
|
186
188
|
this.consumers = [];
|
|
187
189
|
this.options = options;
|
|
188
190
|
this.redisClient = redisConfig && getRedisInstance(redisConfig);
|
|
@@ -215,7 +217,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
215
217
|
return false;
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
220
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
|
|
219
221
|
if (msg) {
|
|
220
222
|
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
221
223
|
await channel.ack(msg);
|
|
@@ -241,8 +243,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
241
243
|
userMsg: ConsumeMessageOrNull,
|
|
242
244
|
{
|
|
243
245
|
skipRetry = false,
|
|
244
|
-
}: NackOptions = {},
|
|
245
|
-
): Promise<any> => {
|
|
246
|
+
}: NackOptions = { },
|
|
247
|
+
) : Promise<any> => {
|
|
246
248
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
247
249
|
if (channel && msg) {
|
|
248
250
|
if (
|
|
@@ -276,32 +278,30 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
280
|
|
|
279
|
-
async getConnection(
|
|
280
|
-
return new Promise<AmqpConnectionManager
|
|
281
|
-
const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
|
|
282
|
-
debug('rabbit: start getting new connection', { connection, creatingConnection, connectionPurpose });
|
|
281
|
+
async getConnection() {
|
|
282
|
+
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
283
283
|
if (this.blockReconnect) {
|
|
284
284
|
debug('rabbit: block reconnect');
|
|
285
285
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
286
286
|
// @ts-ignore
|
|
287
287
|
return resolve();
|
|
288
288
|
}
|
|
289
|
-
if (connection !== null) {
|
|
290
|
-
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
289
|
+
if (this.connection !== null) {
|
|
290
|
+
if (this.options?.disableReconnect || this.connection?.isConnected()) {
|
|
291
291
|
debug('rabbit: connection - is connected');
|
|
292
292
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
293
293
|
// @ts-ignore
|
|
294
|
-
return resolve(connection);
|
|
294
|
+
return resolve(this.connection);
|
|
295
295
|
}
|
|
296
296
|
debug('rabbit: connection - reconnecting');
|
|
297
297
|
}
|
|
298
|
-
if (creatingConnection) {
|
|
298
|
+
if (this.creatingConnection) {
|
|
299
299
|
debug('rabbit: creating connection emi');
|
|
300
300
|
this.em.once(CONNECTION_CREATED_CONST, resolve);
|
|
301
301
|
this.em.once(CONNECTION_FAILED_CONST, reject);
|
|
302
302
|
return;
|
|
303
303
|
}
|
|
304
|
-
this.
|
|
304
|
+
this.creatingConnection = true;
|
|
305
305
|
let isResolved = false;
|
|
306
306
|
|
|
307
307
|
// It is import to use it as a function and not as a variable
|
|
@@ -318,13 +318,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
318
318
|
};
|
|
319
319
|
|
|
320
320
|
const defaultUrls = findServers();
|
|
321
|
-
const
|
|
321
|
+
const connection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
322
322
|
findServers,
|
|
323
323
|
});
|
|
324
324
|
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
newConnection.on('error', (err) => {
|
|
325
|
+
this.connection = connection;
|
|
326
|
+
this.connection.on('error', (err) => {
|
|
328
327
|
logger.error('rabbit: connection error', { err });
|
|
329
328
|
if (!isResolved) {
|
|
330
329
|
isResolved = true;
|
|
@@ -333,7 +332,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
333
332
|
}
|
|
334
333
|
});
|
|
335
334
|
|
|
336
|
-
|
|
335
|
+
this.connection.on('connectFailed', (err) => {
|
|
337
336
|
this.consumersTags = [];
|
|
338
337
|
logger.error('rabbit: connection connectFailed', { err });
|
|
339
338
|
if (!isResolved) {
|
|
@@ -343,7 +342,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
343
342
|
}
|
|
344
343
|
});
|
|
345
344
|
|
|
346
|
-
|
|
345
|
+
this.connection.on('disconnect', ({ err }) => {
|
|
346
|
+
// this.channel = null;
|
|
347
347
|
this.consumersTags = [];
|
|
348
348
|
debug('rabbit: connection closed');
|
|
349
349
|
if (this.options?.disableReconnect) {
|
|
@@ -353,29 +353,25 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
353
353
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
354
354
|
}
|
|
355
355
|
});
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.
|
|
356
|
+
|
|
357
|
+
this.connection.once('connect', async () => {
|
|
358
|
+
debug('rabbit: connection established');
|
|
359
|
+
this.creatingConnection = false;
|
|
360
|
+
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
360
361
|
isResolved = true;
|
|
361
|
-
resolve(
|
|
362
|
+
resolve(connection);
|
|
362
363
|
});
|
|
363
364
|
});
|
|
364
365
|
}
|
|
365
366
|
|
|
366
|
-
async getNewChannel({
|
|
367
|
-
|
|
368
|
-
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
369
|
-
let connection!: AmqpConnectionManager | undefined | null;
|
|
367
|
+
async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
|
|
368
|
+
let connection!: AmqpConnectionManager;
|
|
370
369
|
try {
|
|
371
|
-
connection = await this.getConnection(
|
|
370
|
+
connection = await this.getConnection();
|
|
372
371
|
} catch (e) {
|
|
373
372
|
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
374
373
|
throw e;
|
|
375
374
|
}
|
|
376
|
-
if (!connection) {
|
|
377
|
-
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
378
|
-
}
|
|
379
375
|
const channel = connection.createChannel({ ...options });
|
|
380
376
|
once(channel, 'close').then((args) => {
|
|
381
377
|
logger.error(`rabbit: channel ${name} closed`);
|
|
@@ -391,8 +387,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
391
387
|
}
|
|
392
388
|
}
|
|
393
389
|
|
|
394
|
-
async assertChannel({ force = false
|
|
395
|
-
debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.channel });
|
|
390
|
+
async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
|
|
396
391
|
if (!this.publishChannelSetupPromise) {
|
|
397
392
|
this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
398
393
|
if (this.channel && !force) {
|
|
@@ -400,8 +395,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
400
395
|
}
|
|
401
396
|
|
|
402
397
|
try {
|
|
403
|
-
const channel = await this.getNewChannel({
|
|
404
|
-
debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
|
|
398
|
+
const channel = await this.getNewChannel({});
|
|
405
399
|
channel.on('error', (err) => {
|
|
406
400
|
logger.error('rabbit: channel error', { err });
|
|
407
401
|
});
|
|
@@ -415,73 +409,102 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
415
409
|
return this.publishChannelSetupPromise;
|
|
416
410
|
}
|
|
417
411
|
|
|
418
|
-
async assertExchange(exchangeName: string, options
|
|
419
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
412
|
+
async assertExchange(exchangeName: string, options?: any) {
|
|
413
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
414
|
+
|
|
420
415
|
if (this.exchanges[exchangeName]) {
|
|
416
|
+
delete this.assertExchangePromises[exchangeName];
|
|
421
417
|
return this.exchanges[exchangeName];
|
|
422
418
|
}
|
|
423
|
-
|
|
424
|
-
this.
|
|
425
|
-
|
|
419
|
+
|
|
420
|
+
if (this.assertExchangePromises[exchangeName]) {
|
|
421
|
+
return this.assertExchangePromises[exchangeName];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
425
|
+
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
426
|
+
return this.exchanges[exchangeName];
|
|
426
427
|
}
|
|
427
428
|
|
|
428
|
-
async getQueueLength(queue: string
|
|
429
|
+
async getQueueLength(queue: string) {
|
|
429
430
|
RabbitMq.validateName('queue', queue);
|
|
430
|
-
const { connection } = this.connectionsMap[connectionPurpose];
|
|
431
431
|
const { channel } = this;
|
|
432
432
|
if (!channel) {
|
|
433
433
|
throw new Error('channel is not defined');
|
|
434
434
|
}
|
|
435
|
-
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
435
|
+
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
436
436
|
return channel?.checkQueue(queue);
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
private async deleteQueue(queue: string
|
|
439
|
+
private async deleteQueue(queue: string) {
|
|
440
440
|
RabbitMq.validateName('queue', queue);
|
|
441
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
441
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
442
442
|
logger.info('rabbit: deleting queue', { queue });
|
|
443
443
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
444
444
|
debug('queue deleted', deleteQueueRes);
|
|
445
445
|
return deleteQueueRes;
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
-
async bindQueue(queue: string, exchange: string
|
|
449
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
448
|
+
async bindQueue(queue: string, exchange: string) {
|
|
449
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
450
450
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
451
451
|
return channel.bindQueue(queue, exchange, '');
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
-
async setupQueue(queueName: string,
|
|
454
|
+
async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
455
455
|
let queue: Replies.AssertQueue;
|
|
456
|
-
|
|
456
|
+
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
457
|
+
const localeOptions = {
|
|
458
|
+
...options,
|
|
459
|
+
durable: true,
|
|
460
|
+
arguments: {
|
|
461
|
+
...options?.arguments,
|
|
462
|
+
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
463
|
+
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
464
|
+
},
|
|
465
|
+
};
|
|
457
466
|
try {
|
|
458
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
467
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
459
468
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
460
|
-
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName,
|
|
469
|
+
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
461
470
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
462
|
-
queue = await channel.assertQueue(queueName,
|
|
471
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
463
472
|
} catch (e) {
|
|
464
473
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
465
474
|
if (!this.options?.dontRetryAssert) {
|
|
466
475
|
debug('retrying assertQueue', { queueName });
|
|
467
|
-
const channel = await this.assertChannel({ force: true
|
|
468
|
-
await this.deleteQueue(queueName
|
|
476
|
+
const channel = await this.assertChannel({ force: true });
|
|
477
|
+
await this.deleteQueue(queueName);
|
|
469
478
|
|
|
470
479
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
471
|
-
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName,
|
|
480
|
+
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
472
481
|
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
473
|
-
queue = await channel.assertQueue(queueName,
|
|
482
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
474
483
|
} else {
|
|
475
484
|
throw e;
|
|
476
485
|
}
|
|
477
486
|
}
|
|
478
|
-
|
|
479
|
-
this.queues[queueName] =
|
|
487
|
+
|
|
488
|
+
this.queues[queueName] = queue;
|
|
480
489
|
return queue;
|
|
481
490
|
}
|
|
482
491
|
|
|
483
|
-
|
|
484
|
-
|
|
492
|
+
static shouldUseQuorum(queueName: string): boolean {
|
|
493
|
+
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
494
|
+
|
|
495
|
+
if (envQuorumQueuesWhitelist === '*') {
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (envQuorumQueuesWhitelist) {
|
|
500
|
+
const whitelist = envQuorumQueuesWhitelist.split(',');
|
|
501
|
+
return whitelist.includes(queueName);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
|
|
485
508
|
RabbitMq.validateName('queue', queueName);
|
|
486
509
|
if (this.queues[queueName]) {
|
|
487
510
|
delete this.queueSetupPromises[queueName];
|
|
@@ -492,13 +515,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
492
515
|
return this.queueSetupPromises[queueName];
|
|
493
516
|
}
|
|
494
517
|
|
|
495
|
-
this.queueSetupPromises[queueName] = this.setupQueue(queueName,
|
|
496
|
-
debug('rabbit: done assert queue', { connectionPurpose, queueName });
|
|
518
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
497
519
|
return this.queueSetupPromises[queueName];
|
|
498
520
|
}
|
|
499
521
|
|
|
500
522
|
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
501
|
-
const isConsumerExist:
|
|
523
|
+
const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
|
|
502
524
|
if (!isConsumerExist) {
|
|
503
525
|
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
504
526
|
this.consumers.push({
|
|
@@ -547,10 +569,10 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
547
569
|
}
|
|
548
570
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
549
571
|
}
|
|
550
|
-
const channel = await this.getNewChannel({
|
|
572
|
+
const channel = await this.getNewChannel({});
|
|
551
573
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
552
|
-
await
|
|
553
|
-
await confirmChannel.prefetch(limit,
|
|
574
|
+
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
575
|
+
await confirmChannel.prefetch(limit, false);
|
|
554
576
|
const { consumerTag } = await confirmChannel.consume(
|
|
555
577
|
queue,
|
|
556
578
|
async (msg: ConsumeMessageOrNull) => {
|
|
@@ -560,6 +582,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
560
582
|
|
|
561
583
|
const traceId = msg.properties.headers[TRACING_HEADER];
|
|
562
584
|
const userId = msg.properties.headers[USER_TRACING_HEADER];
|
|
585
|
+
const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
|
|
563
586
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
564
587
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
565
588
|
const trace = newTrace(traceTypes.RABBIT);
|
|
@@ -585,7 +608,10 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
585
608
|
}
|
|
586
609
|
|
|
587
610
|
if (auditContext) {
|
|
588
|
-
await auditContext(queue
|
|
611
|
+
await auditContext(queue, {
|
|
612
|
+
userId,
|
|
613
|
+
automationId,
|
|
614
|
+
});
|
|
589
615
|
}
|
|
590
616
|
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
591
617
|
if (!shouldConsume) {
|
|
@@ -633,13 +659,13 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
633
659
|
});
|
|
634
660
|
}
|
|
635
661
|
|
|
636
|
-
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
662
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
|
|
637
663
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
638
664
|
RabbitMq.validateName('exchange', exchange);
|
|
639
665
|
RabbitMq.validateName('queue', queue);
|
|
640
666
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
641
667
|
await this.saveConsumer(queue, callback, options);
|
|
642
|
-
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume
|
|
668
|
+
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
643
669
|
|
|
644
670
|
return channel.addSetup(async (c: ConfirmChannel) => {
|
|
645
671
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
@@ -657,11 +683,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
657
683
|
});
|
|
658
684
|
}
|
|
659
685
|
|
|
660
|
-
async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
|
|
686
|
+
async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
|
|
661
687
|
return wrapSetImmediate(async () => {
|
|
662
688
|
RabbitMq.validateName('exchange', exchange);
|
|
663
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
664
|
-
await this.assertExchange(exchange
|
|
689
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
690
|
+
await this.assertExchange(exchange);
|
|
665
691
|
await channel.publish(exchange, '',
|
|
666
692
|
Buffer.from(JSON.stringify(content)),
|
|
667
693
|
RabbitMq.getPublishOptions(customHeaders));
|
|
@@ -675,7 +701,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
675
701
|
customHeaders?: any,
|
|
676
702
|
): Promise<boolean | undefined> {
|
|
677
703
|
try {
|
|
678
|
-
await this.assertChannel(
|
|
704
|
+
await this.assertChannel();
|
|
679
705
|
} catch (e) {
|
|
680
706
|
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
681
707
|
throw e;
|
|
@@ -683,7 +709,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
683
709
|
|
|
684
710
|
try {
|
|
685
711
|
RabbitMq.validateName('queue', queue);
|
|
686
|
-
await this.assertQueue(queue,
|
|
712
|
+
await this.assertQueue(queue, options);
|
|
687
713
|
} catch (e) {
|
|
688
714
|
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
689
715
|
throw e;
|
|
@@ -696,41 +722,34 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
696
722
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
697
723
|
return res;
|
|
698
724
|
} catch (e) {
|
|
699
|
-
|
|
725
|
+
const isConnected = await this.isConnected();
|
|
726
|
+
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
700
727
|
throw e;
|
|
701
728
|
}
|
|
702
729
|
}
|
|
703
730
|
|
|
704
|
-
async isConnected(): Promise<boolean> {
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
logger.error('rabbit: isConnected - false');
|
|
724
|
-
return false;
|
|
725
|
-
}
|
|
726
|
-
logger.info('rabbit: isConnected - true');
|
|
727
|
-
return true;
|
|
728
|
-
}),
|
|
729
|
-
);
|
|
730
|
-
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
731
|
+
async isConnected() : Promise<boolean> {
|
|
732
|
+
const connection = await this.getConnection();
|
|
733
|
+
const isConnected = connection.isConnected();
|
|
734
|
+
if (!isConnected) {
|
|
735
|
+
logger.error('rabbit: isConnected - false');
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
const channel: any = await this.assertChannel();
|
|
739
|
+
try {
|
|
740
|
+
await Promise.all([
|
|
741
|
+
channel.waitForConnect(),
|
|
742
|
+
...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
|
743
|
+
]);
|
|
744
|
+
} catch (e) {
|
|
745
|
+
logger.error('rabbit: isConnected - false');
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
logger.info('rabbit: isConnected - true');
|
|
749
|
+
return true;
|
|
731
750
|
}
|
|
732
751
|
|
|
733
|
-
async gracefulShutdown(signal: string): Promise<void> {
|
|
752
|
+
async gracefulShutdown(signal: string) : Promise<void> {
|
|
734
753
|
const tagsNumber = this.consumersTags.length;
|
|
735
754
|
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
736
755
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
@@ -747,3 +766,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
747
766
|
}
|
|
748
767
|
|
|
749
768
|
export default RabbitMq;
|
|
769
|
+
|
|
770
|
+
export {
|
|
771
|
+
sendCeleryTaskViaHttp,
|
|
772
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import logger from '../logger';
|
|
3
|
+
// Environment configuration
|
|
4
|
+
const config = {
|
|
5
|
+
host: process.env.RABBITMQ_SERVICE_HOST || 'localhost',
|
|
6
|
+
username: process.env.RABBITMQ_USERNAME || 'guest',
|
|
7
|
+
password: process.env.RABBITMQ_PASSWORD || 'guest',
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
// Type definitions
|
|
11
|
+
interface TaskData {
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface TaskMessage {
|
|
16
|
+
task: string;
|
|
17
|
+
id: string;
|
|
18
|
+
args: TaskData[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface PublishPayload {
|
|
22
|
+
properties: {
|
|
23
|
+
// eslint-disable-next-line camelcase
|
|
24
|
+
delivery_mode: number;
|
|
25
|
+
// eslint-disable-next-line camelcase
|
|
26
|
+
content_type: string;
|
|
27
|
+
};
|
|
28
|
+
// eslint-disable-next-line camelcase
|
|
29
|
+
routing_key: string;
|
|
30
|
+
payload: string;
|
|
31
|
+
// eslint-disable-next-line camelcase
|
|
32
|
+
payload_encoding: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface PublishResponse {
|
|
36
|
+
routed: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SendTaskOptions {
|
|
40
|
+
taskName: string;
|
|
41
|
+
queueName: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function sendCeleryTaskViaHttp(
|
|
45
|
+
data: TaskData,
|
|
46
|
+
{ taskName, queueName }: SendTaskOptions,
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
const apiUrl = `http://${config.host}:15672/api/exchanges/%2f/amq.default/publish`;
|
|
49
|
+
|
|
50
|
+
const message: TaskMessage = {
|
|
51
|
+
task: taskName,
|
|
52
|
+
id: randomUUID(),
|
|
53
|
+
args: [data],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const payload: PublishPayload = {
|
|
57
|
+
properties: {
|
|
58
|
+
delivery_mode: 2,
|
|
59
|
+
content_type: 'application/json',
|
|
60
|
+
},
|
|
61
|
+
routing_key: queueName,
|
|
62
|
+
payload: JSON.stringify(message),
|
|
63
|
+
payload_encoding: 'string',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(apiUrl, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
Authorization: `Basic ${Buffer.from(`${config.username}:${config.password}`).toString('base64')}`,
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify(payload),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (response.ok) {
|
|
77
|
+
const result: PublishResponse = await response.json();
|
|
78
|
+
logger.info('Successfully published message:', result);
|
|
79
|
+
} else {
|
|
80
|
+
logger.error(`Failed to publish message. Status code: ${response.status}`);
|
|
81
|
+
logger.error(`Response: ${await response.text()}`);
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
logger.error('Error sending request:', error instanceof Error ? error.message : String(error));
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { sendCeleryTaskViaHttp, TaskData, SendTaskOptions };
|
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
|
-
}
|