@autofleet/rabbit 3.2.21-beta.0 → 3.2.21-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/package.json +1 -3
- package/src/index.ts +77 -8
- package/src/lib/types.ts +4 -1
- package/src/lib/utils.ts +6 -2
- package/dist/index.d.ts +0 -99
- package/dist/index.js +0 -541
- package/dist/lib/consts.d.ts +0 -18
- package/dist/lib/consts.js +0 -21
- package/dist/lib/rabbitError.d.ts +0 -3
- package/dist/lib/rabbitError.js +0 -9
- package/dist/lib/redis.d.ts +0 -7
- package/dist/lib/redis.js +0 -11
- package/dist/lib/types.d.ts +0 -41
- package/dist/lib/types.js +0 -11
- package/dist/lib/utils.d.ts +0 -5
- package/dist/lib/utils.js +0 -19
- package/dist/logger.d.ts +0 -2
- package/dist/logger.js +0 -8
- package/dist/mock.d.ts +0 -14
- package/dist/mock.js +0 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/rabbit",
|
|
3
|
-
"version": "3.2.21-beta.0",
|
|
3
|
+
"version": "3.2.21-beta.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"amqp-connection-manager": "4.1.9",
|
|
20
20
|
"amqplib": "0.10.3",
|
|
21
21
|
"bluebird": "^3.7.2",
|
|
22
|
-
"lodash": "^4.17.21",
|
|
23
22
|
"moment": "^2.29.1",
|
|
24
23
|
"redis": "^3.1.2",
|
|
25
24
|
"redis-lock": "^0.1.4"
|
|
@@ -31,7 +30,6 @@
|
|
|
31
30
|
"@autofleet/logger": "4.0.6",
|
|
32
31
|
"@types/amqplib": "0.8.2",
|
|
33
32
|
"@types/jest": "^29.5.12",
|
|
34
|
-
"@types/lodash": "^4.17.12",
|
|
35
33
|
"@types/node": "^20.14.11",
|
|
36
34
|
"@typescript-eslint/eslint-plugin": "^4.8.1",
|
|
37
35
|
"eslint": "^7.13.0",
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { EventEmitter, once } from 'events';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
|
-
import _ from 'lodash';
|
|
6
5
|
import moment from 'moment';
|
|
7
6
|
import RedisLock from 'redis-lock';
|
|
8
7
|
import {
|
|
@@ -36,7 +35,10 @@ import {
|
|
|
36
35
|
CustomMessageHeaders,
|
|
37
36
|
QueuesCache,
|
|
38
37
|
RedisLockType,
|
|
39
|
-
ExchangesCache,
|
|
38
|
+
ExchangesCache,
|
|
39
|
+
CONSUMER_DEFAULT_OPTIONS,
|
|
40
|
+
QueueSetupPromisesDictionary,
|
|
41
|
+
AssertExchangePromisesDictionary,
|
|
40
42
|
} from './lib/types';
|
|
41
43
|
|
|
42
44
|
// const debug = nodeDebug('af-rabbitmq')
|
|
@@ -158,6 +160,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
158
160
|
|
|
159
161
|
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
160
162
|
|
|
163
|
+
assertExchangePromises: AssertExchangePromisesDictionary;
|
|
164
|
+
|
|
161
165
|
options: AfRabbitOptions | undefined;
|
|
162
166
|
|
|
163
167
|
redisClient: any;
|
|
@@ -178,6 +182,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
178
182
|
this.exchanges = {};
|
|
179
183
|
this.queues = {};
|
|
180
184
|
this.queueSetupPromises = {};
|
|
185
|
+
this.assertExchangePromises = {};
|
|
181
186
|
this.consumers = [];
|
|
182
187
|
this.options = options;
|
|
183
188
|
this.redisClient = redisConfig && getRedisInstance(redisConfig);
|
|
@@ -336,6 +341,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
336
341
|
});
|
|
337
342
|
|
|
338
343
|
this.connection.on('disconnect', ({ err }) => {
|
|
344
|
+
// this.channel = null;
|
|
339
345
|
this.consumersTags = [];
|
|
340
346
|
debug('rabbit: connection closed');
|
|
341
347
|
if (this.options?.disableReconnect) {
|
|
@@ -345,6 +351,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
345
351
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
346
352
|
}
|
|
347
353
|
});
|
|
354
|
+
|
|
348
355
|
this.connection.once('connect', async () => {
|
|
349
356
|
debug('rabbit: connection established');
|
|
350
357
|
this.creatingConnection = false;
|
|
@@ -402,12 +409,19 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
402
409
|
|
|
403
410
|
async assertExchange(exchangeName: string, options?: any) {
|
|
404
411
|
const channel: ChannelWrapper = await this.assertChannel();
|
|
412
|
+
|
|
405
413
|
if (this.exchanges[exchangeName]) {
|
|
414
|
+
delete this.assertExchangePromises[exchangeName];
|
|
406
415
|
return this.exchanges[exchangeName];
|
|
407
416
|
}
|
|
408
|
-
|
|
409
|
-
this.
|
|
410
|
-
|
|
417
|
+
|
|
418
|
+
if (this.assertExchangePromises[exchangeName]) {
|
|
419
|
+
return this.assertExchangePromises[exchangeName];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
423
|
+
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
424
|
+
return this.exchanges[exchangeName];
|
|
411
425
|
}
|
|
412
426
|
|
|
413
427
|
async getQueueLength(queue: string) {
|
|
@@ -532,7 +546,6 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
532
546
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
533
547
|
await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
|
|
534
548
|
await confirmChannel.prefetch(limit, true);
|
|
535
|
-
const channelConsumerOptions = optionsWithDefaults.channelConsumeOptions ? _.merge(CONSUMER_DEFAULT_OPTIONS, optionsWithDefaults.channelConsumeOptions) : CONSUMER_DEFAULT_OPTIONS;
|
|
536
549
|
const { consumerTag } = await confirmChannel.consume(
|
|
537
550
|
queue,
|
|
538
551
|
async (msg: ConsumeMessageOrNull) => {
|
|
@@ -604,8 +617,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
604
617
|
} catch (e) {
|
|
605
618
|
await localNack(msg);
|
|
606
619
|
}
|
|
607
|
-
},
|
|
608
|
-
channelConsumerOptions,
|
|
620
|
+
}, CONSUMER_DEFAULT_OPTIONS,
|
|
609
621
|
);
|
|
610
622
|
if (!consumerTag) {
|
|
611
623
|
logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
@@ -685,6 +697,63 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
685
697
|
}
|
|
686
698
|
}
|
|
687
699
|
|
|
700
|
+
|
|
701
|
+
async sendToCeleryQueue(
|
|
702
|
+
queue: string,
|
|
703
|
+
taskName: string,
|
|
704
|
+
data: any,
|
|
705
|
+
options?: any,
|
|
706
|
+
customHeaders?: any,
|
|
707
|
+
): Promise<boolean | undefined> {
|
|
708
|
+
try {
|
|
709
|
+
await this.assertChannel();
|
|
710
|
+
} catch (e) {
|
|
711
|
+
logger.error(`rabbit sendToCeleryQueue: failed to assert channel when sending to queue ${queue}`, { e });
|
|
712
|
+
throw e;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
RabbitMq.validateName('queue', queue);
|
|
717
|
+
await this.assertQueue(queue, options);
|
|
718
|
+
} catch (e) {
|
|
719
|
+
logger.error(`rabbit sendToCeleryQueue: failed to assert queue ${queue}`, { e });
|
|
720
|
+
throw e;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
try {
|
|
724
|
+
// Create Celery-compatible message format
|
|
725
|
+
const celeryMessage = {
|
|
726
|
+
task: taskName,
|
|
727
|
+
id: randomUUID(),
|
|
728
|
+
args: [data],
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
// Merge default Celery properties with custom headers
|
|
732
|
+
const celeryHeaders = {
|
|
733
|
+
...RabbitMq.getPublishOptions(customHeaders),
|
|
734
|
+
properties: {
|
|
735
|
+
...RabbitMq.getPublishOptions(customHeaders)?.properties,
|
|
736
|
+
delivery_mode: 2,
|
|
737
|
+
content_type: 'application/json',
|
|
738
|
+
content_encoding: 'utf-8',
|
|
739
|
+
},
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
const res = await this.channel?.sendToQueue(
|
|
743
|
+
queue,
|
|
744
|
+
Buffer.from(JSON.stringify(celeryMessage)),
|
|
745
|
+
celeryHeaders
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
debug(`rabbit: sending to celery queue ${queue}`, { res });
|
|
749
|
+
return res;
|
|
750
|
+
} catch (e) {
|
|
751
|
+
const isConnected = await this.isConnected();
|
|
752
|
+
logger.error(`rabbit sendToCeleryQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
753
|
+
throw e;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
688
757
|
async isConnected() : Promise<boolean> {
|
|
689
758
|
const connection = await this.getConnection();
|
|
690
759
|
const isConnected = connection.isConnected();
|
package/src/lib/types.ts
CHANGED
|
@@ -12,6 +12,10 @@ export interface QueueSetupPromisesDictionary {
|
|
|
12
12
|
[key: string]: Promise<Replies.AssertQueue> | undefined
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface AssertExchangePromisesDictionary {
|
|
16
|
+
[key: string]: Promise<Replies.AssertExchange> | undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
export type CustomMessageHeaders = {
|
|
16
20
|
redisTimestampValidationKey?: string;
|
|
17
21
|
}
|
|
@@ -27,7 +31,6 @@ export interface ConsumeOptions {
|
|
|
27
31
|
useConsumeWithLock?: boolean;
|
|
28
32
|
auditContext?: any;
|
|
29
33
|
enableRabbitTrace?: boolean;
|
|
30
|
-
channelConsumeOptions?: Options.Consume;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>
|
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 {
|
package/dist/index.d.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { EventEmitter } from 'events';
|
|
3
|
-
import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from 'amqp-connection-manager';
|
|
4
|
-
import { ConfirmChannel, ConsumeMessage, Options, Replies } from 'amqplib';
|
|
5
|
-
import { RedisConfig } from './lib/redis';
|
|
6
|
-
import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary } from './lib/types';
|
|
7
|
-
export interface IAfRabbitMq {
|
|
8
|
-
ack: any;
|
|
9
|
-
nack: any;
|
|
10
|
-
assertChannel: any;
|
|
11
|
-
assertExchange: any;
|
|
12
|
-
assertQueue: any;
|
|
13
|
-
consume: any;
|
|
14
|
-
consumeFromExchange: any;
|
|
15
|
-
publish: any;
|
|
16
|
-
sendToQueue: any;
|
|
17
|
-
redisClient?: any;
|
|
18
|
-
}
|
|
19
|
-
interface NackOptions {
|
|
20
|
-
skipRetry?: boolean;
|
|
21
|
-
}
|
|
22
|
-
export interface AfRabbitOptions {
|
|
23
|
-
disableReconnect?: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* When you want your own grace-full shutdown, set this to true.
|
|
26
|
-
* @default false
|
|
27
|
-
*/
|
|
28
|
-
dontGracefulShutdown?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
* dont retry on creation error
|
|
31
|
-
* @default false
|
|
32
|
-
*/
|
|
33
|
-
dontRetryAssert?: boolean;
|
|
34
|
-
rabbitHost?: string;
|
|
35
|
-
}
|
|
36
|
-
type newChannelOpts = {
|
|
37
|
-
name?: string;
|
|
38
|
-
onClose?: null | ((args: any | null) => void);
|
|
39
|
-
options?: CreateChannelOpts | undefined;
|
|
40
|
-
};
|
|
41
|
-
type assertChannelOpts = {
|
|
42
|
-
channelName?: string;
|
|
43
|
-
force?: boolean;
|
|
44
|
-
};
|
|
45
|
-
declare class RabbitMq implements IAfRabbitMq {
|
|
46
|
-
static parseMsg(msg: any): any;
|
|
47
|
-
static validateName(type: string, name: string): void;
|
|
48
|
-
static getPublishOptions(customHeaders?: CustomMessageHeaders): {
|
|
49
|
-
timestamp: number;
|
|
50
|
-
timeout: number;
|
|
51
|
-
headers: {
|
|
52
|
-
"x-af-user-id": any;
|
|
53
|
-
"x-trace-id": any;
|
|
54
|
-
redisTimestampValidationKey?: string | undefined;
|
|
55
|
-
creationTimestamp: number;
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
DISCONNECT_MSG: string;
|
|
59
|
-
RECONNECT_MSG: string;
|
|
60
|
-
channel: ChannelWrapper | null;
|
|
61
|
-
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
62
|
-
blockReconnect: boolean | null | undefined;
|
|
63
|
-
connection: AmqpConnectionManager | null | undefined;
|
|
64
|
-
em: EventEmitter;
|
|
65
|
-
creatingConnection: boolean;
|
|
66
|
-
exchanges: ExchangesCache;
|
|
67
|
-
queues: QueuesCache;
|
|
68
|
-
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
69
|
-
options: AfRabbitOptions | undefined;
|
|
70
|
-
redisClient: any;
|
|
71
|
-
redisLock?: RedisLockType;
|
|
72
|
-
/** Array of consumers tags used for canceling consumption */
|
|
73
|
-
consumersTags: Array<[ConfirmChannel, string]>;
|
|
74
|
-
private consumers;
|
|
75
|
-
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
|
|
76
|
-
private shouldConsumeMessageByTimestamp;
|
|
77
|
-
ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
|
|
78
|
-
nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
|
|
79
|
-
getConnection(): Promise<AmqpConnectionManager>;
|
|
80
|
-
getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
|
|
81
|
-
assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
|
|
82
|
-
assertExchange(exchangeName: string, options?: any): Promise<any>;
|
|
83
|
-
getQueueLength(queue: string): Promise<Replies.AssertQueue>;
|
|
84
|
-
private deleteQueue;
|
|
85
|
-
bindQueue(queue: string, exchange: string): Promise<void>;
|
|
86
|
-
setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
87
|
-
assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
|
|
88
|
-
private saveConsumer;
|
|
89
|
-
consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
|
|
90
|
-
private lockRedisIfNeeded;
|
|
91
|
-
private unlockRedisIfNeeded;
|
|
92
|
-
private consumeFromRabbit;
|
|
93
|
-
consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
|
|
94
|
-
publish(exchange: string, content: any, customHeaders?: any): Promise<boolean>;
|
|
95
|
-
sendToQueue(queue: string, content: any, options?: any, customHeaders?: any): Promise<boolean | undefined>;
|
|
96
|
-
isConnected(): Promise<boolean>;
|
|
97
|
-
gracefulShutdown(signal: string): Promise<void>;
|
|
98
|
-
}
|
|
99
|
-
export default RabbitMq;
|
package/dist/index.js
DELETED
|
@@ -1,541 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* eslint-disable no-empty,consistent-return,no-async-promise-executor,@typescript-eslint/no-unused-vars */
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const events_1 = require("events");
|
|
8
|
-
const util_1 = require("util");
|
|
9
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
10
|
-
const moment_1 = __importDefault(require("moment"));
|
|
11
|
-
const redis_lock_1 = __importDefault(require("redis-lock"));
|
|
12
|
-
const amqp_connection_manager_1 = require("amqp-connection-manager");
|
|
13
|
-
const zehut_1 = require("@autofleet/zehut");
|
|
14
|
-
const node_crypto_1 = require("node:crypto");
|
|
15
|
-
const logger_1 = __importDefault(require("./logger"));
|
|
16
|
-
const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
|
|
17
|
-
const redis_1 = __importDefault(require("./lib/redis"));
|
|
18
|
-
const utils_1 = require("./lib/utils");
|
|
19
|
-
const consts_1 = require("./lib/consts");
|
|
20
|
-
const types_1 = require("./lib/types");
|
|
21
|
-
// const debug = nodeDebug('af-rabbitmq')
|
|
22
|
-
const debug = logger_1.default.debug.bind(logger_1.default);
|
|
23
|
-
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
24
|
-
const HEARTBEAT = '60';
|
|
25
|
-
class RabbitMq {
|
|
26
|
-
static parseMsg(msg) {
|
|
27
|
-
let { content } = msg;
|
|
28
|
-
content = content.toString();
|
|
29
|
-
try {
|
|
30
|
-
content = JSON.parse(content);
|
|
31
|
-
}
|
|
32
|
-
catch (e) { }
|
|
33
|
-
return {
|
|
34
|
-
...msg,
|
|
35
|
-
content,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
static validateName(type, name) {
|
|
39
|
-
if (!name || name === '') {
|
|
40
|
-
throw new rabbitError_1.default(`error while using ${type} with no name`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
static getPublishOptions(customHeaders = {}) {
|
|
44
|
-
const trace = (0, zehut_1.getCurrentPayload)();
|
|
45
|
-
const user = trace?.context?.get(consts_1.USER_OBJECT);
|
|
46
|
-
const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
|
|
47
|
-
const outbreakTrace = zehut_1.outbreak.getCurrentContext();
|
|
48
|
-
return {
|
|
49
|
-
timestamp: (0, moment_1.default)().unix(),
|
|
50
|
-
timeout: PUBLISH_TIMEOUT,
|
|
51
|
-
headers: {
|
|
52
|
-
creationTimestamp: (0, moment_1.default)().valueOf(),
|
|
53
|
-
...customHeaders,
|
|
54
|
-
[consts_1.USER_TRACING_HEADER]: user?.id,
|
|
55
|
-
[consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
constructor(options, redisConfig) {
|
|
60
|
-
this.DISCONNECT_MSG = 'rabbit: connection disconnect';
|
|
61
|
-
this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
62
|
-
this.consumers = [];
|
|
63
|
-
this.shouldConsumeMessageByTimestamp = async (msg) => {
|
|
64
|
-
if (msg) {
|
|
65
|
-
const { properties: { headers } } = msg;
|
|
66
|
-
const timestamp = headers?.creationTimestamp;
|
|
67
|
-
if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
|
|
68
|
-
const lastMessageTimestamp = await this.redisClient.getAsync(headers.redisTimestampValidationKey);
|
|
69
|
-
return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
|
|
70
|
-
}
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
return false;
|
|
74
|
-
};
|
|
75
|
-
this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
|
|
76
|
-
if (msg) {
|
|
77
|
-
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
78
|
-
await channel.ack(msg);
|
|
79
|
-
const { properties: { headers } } = msg;
|
|
80
|
-
const timestamp = headers?.creationTimestamp;
|
|
81
|
-
if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
|
|
82
|
-
const parsedTimestamp = parseInt(timestamp, 10);
|
|
83
|
-
await this.redisClient.setAsync(headers.redisTimestampValidationKey, parsedTimestamp, 'EX', 3600);
|
|
84
|
-
await this.unlockRedisIfNeeded(releaseLock);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
this.nack = (channel, queue, options, deadQueueOptions, msg, releaseLock) => async (userMsg, { skipRetry = false, } = {}) => {
|
|
89
|
-
await this.unlockRedisIfNeeded(releaseLock);
|
|
90
|
-
if (channel && msg) {
|
|
91
|
-
if (!skipRetry
|
|
92
|
-
&& (!msg.properties.headers[consts_1.RETRY_HEADER]
|
|
93
|
-
|| parseInt(msg.properties.headers[consts_1.RETRY_HEADER], 10) < options.retries)) {
|
|
94
|
-
await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
|
|
95
|
-
...msg.properties.headers,
|
|
96
|
-
[consts_1.RETRY_HEADER]: msg.properties.headers[consts_1.RETRY_HEADER]
|
|
97
|
-
? msg.properties.headers[consts_1.RETRY_HEADER] + 1
|
|
98
|
-
: 1,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
const deadQueue = `${queue}-dead`;
|
|
103
|
-
await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
|
|
104
|
-
...msg.properties.headers,
|
|
105
|
-
[consts_1.RETRY_HEADER]: msg.properties.headers[consts_1.RETRY_HEADER]
|
|
106
|
-
? msg.properties.headers[consts_1.RETRY_HEADER] + 1
|
|
107
|
-
: 1,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
|
|
111
|
-
await channel.ack(msg);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
logger_1.default.error('no channel or msg', {
|
|
115
|
-
msg,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
this.em = new events_1.EventEmitter();
|
|
120
|
-
this.channel = null;
|
|
121
|
-
this.publishChannelSetupPromise = null;
|
|
122
|
-
this.connection = null;
|
|
123
|
-
this.creatingConnection = false;
|
|
124
|
-
this.exchanges = {};
|
|
125
|
-
this.queues = {};
|
|
126
|
-
this.queueSetupPromises = {};
|
|
127
|
-
this.consumers = [];
|
|
128
|
-
this.options = options;
|
|
129
|
-
this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
|
|
130
|
-
if (this.redisClient) {
|
|
131
|
-
this.redisLock = (0, util_1.promisify)((0, redis_lock_1.default)(this.redisClient));
|
|
132
|
-
}
|
|
133
|
-
this.consumersTags = [];
|
|
134
|
-
logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
|
|
135
|
-
if (!this.options?.dontGracefulShutdown) {
|
|
136
|
-
process.on('SIGTERM', async () => {
|
|
137
|
-
await this.gracefulShutdown('SIGTERM');
|
|
138
|
-
});
|
|
139
|
-
process.on('SIGINT', async () => {
|
|
140
|
-
await this.gracefulShutdown('SIGINT');
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
async getConnection() {
|
|
145
|
-
return new Promise(async (resolve, reject) => {
|
|
146
|
-
if (this.blockReconnect) {
|
|
147
|
-
debug('rabbit: block reconnect');
|
|
148
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
149
|
-
// @ts-ignore
|
|
150
|
-
return resolve();
|
|
151
|
-
}
|
|
152
|
-
if (this.connection !== null) {
|
|
153
|
-
if (this.options?.disableReconnect || this.connection?.isConnected()) {
|
|
154
|
-
debug('rabbit: connection - is connected');
|
|
155
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
156
|
-
// @ts-ignore
|
|
157
|
-
return resolve(this.connection);
|
|
158
|
-
}
|
|
159
|
-
debug('rabbit: connection - reconnecting');
|
|
160
|
-
}
|
|
161
|
-
if (this.creatingConnection) {
|
|
162
|
-
debug('rabbit: creating connection emi');
|
|
163
|
-
this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
|
|
164
|
-
this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
this.creatingConnection = true;
|
|
168
|
-
let isResolved = false;
|
|
169
|
-
// It is import to use it as a function and not as a variable
|
|
170
|
-
// because of k8s changes the env variables
|
|
171
|
-
// and we want to use the new values
|
|
172
|
-
const findServers = () => {
|
|
173
|
-
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
174
|
-
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
175
|
-
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
176
|
-
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
177
|
-
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
178
|
-
};
|
|
179
|
-
const defaultUrls = findServers();
|
|
180
|
-
const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
181
|
-
findServers,
|
|
182
|
-
});
|
|
183
|
-
this.connection = connection;
|
|
184
|
-
this.connection.on('error', (err) => {
|
|
185
|
-
logger_1.default.error('rabbit: connection error', { err });
|
|
186
|
-
if (!isResolved) {
|
|
187
|
-
isResolved = true;
|
|
188
|
-
reject(err);
|
|
189
|
-
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
this.connection.on('connectFailed', (err) => {
|
|
193
|
-
this.consumersTags = [];
|
|
194
|
-
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
195
|
-
if (!isResolved) {
|
|
196
|
-
isResolved = true;
|
|
197
|
-
reject(err);
|
|
198
|
-
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
this.connection.on('disconnect', ({ err }) => {
|
|
202
|
-
this.consumersTags = [];
|
|
203
|
-
debug('rabbit: connection closed');
|
|
204
|
-
if (this.options?.disableReconnect) {
|
|
205
|
-
logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
206
|
-
this.blockReconnect = true;
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
this.connection.once('connect', async () => {
|
|
213
|
-
debug('rabbit: connection established');
|
|
214
|
-
this.creatingConnection = false;
|
|
215
|
-
this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
|
|
216
|
-
isResolved = true;
|
|
217
|
-
resolve(connection);
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
|
|
222
|
-
let connection;
|
|
223
|
-
try {
|
|
224
|
-
connection = await this.getConnection();
|
|
225
|
-
}
|
|
226
|
-
catch (e) {
|
|
227
|
-
logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
228
|
-
throw e;
|
|
229
|
-
}
|
|
230
|
-
const channel = connection.createChannel({ ...options });
|
|
231
|
-
(0, events_1.once)(channel, 'close').then((args) => {
|
|
232
|
-
logger_1.default.error(`rabbit: channel ${name} closed`);
|
|
233
|
-
onClose?.(args);
|
|
234
|
-
});
|
|
235
|
-
try {
|
|
236
|
-
await (0, events_1.once)(channel, 'connect');
|
|
237
|
-
debug(`rabbit: channel ${name} CONNECTED`);
|
|
238
|
-
return channel;
|
|
239
|
-
}
|
|
240
|
-
catch (err) {
|
|
241
|
-
logger_1.default.error(`rabbit: channel error ${name} error`, { err });
|
|
242
|
-
throw err;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
async assertChannel({ force = false } = {}) {
|
|
246
|
-
if (!this.publishChannelSetupPromise) {
|
|
247
|
-
this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
|
|
248
|
-
if (this.channel && !force) {
|
|
249
|
-
return resolve(this.channel);
|
|
250
|
-
}
|
|
251
|
-
try {
|
|
252
|
-
const channel = await this.getNewChannel({});
|
|
253
|
-
channel.on('error', (err) => {
|
|
254
|
-
logger_1.default.error('rabbit: channel error', { err });
|
|
255
|
-
});
|
|
256
|
-
this.channel = channel;
|
|
257
|
-
resolve(channel);
|
|
258
|
-
}
|
|
259
|
-
catch (e) {
|
|
260
|
-
reject(e);
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
return this.publishChannelSetupPromise;
|
|
265
|
-
}
|
|
266
|
-
async assertExchange(exchangeName, options) {
|
|
267
|
-
const channel = await this.assertChannel();
|
|
268
|
-
if (this.exchanges[exchangeName]) {
|
|
269
|
-
return this.exchanges[exchangeName];
|
|
270
|
-
}
|
|
271
|
-
const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
|
|
272
|
-
this.exchanges[exchangeName] = exchange;
|
|
273
|
-
return exchange;
|
|
274
|
-
}
|
|
275
|
-
async getQueueLength(queue) {
|
|
276
|
-
RabbitMq.validateName('queue', queue);
|
|
277
|
-
const { channel } = this;
|
|
278
|
-
if (!channel) {
|
|
279
|
-
throw new Error('channel is not defined');
|
|
280
|
-
}
|
|
281
|
-
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
282
|
-
return channel?.checkQueue(queue);
|
|
283
|
-
}
|
|
284
|
-
async deleteQueue(queue) {
|
|
285
|
-
RabbitMq.validateName('queue', queue);
|
|
286
|
-
const channel = await this.assertChannel();
|
|
287
|
-
logger_1.default.info('rabbit: deleting queue', { queue });
|
|
288
|
-
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
289
|
-
debug('queue deleted', deleteQueueRes);
|
|
290
|
-
return deleteQueueRes;
|
|
291
|
-
}
|
|
292
|
-
async bindQueue(queue, exchange) {
|
|
293
|
-
const channel = await this.assertChannel();
|
|
294
|
-
await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
295
|
-
return channel.bindQueue(queue, exchange, '');
|
|
296
|
-
}
|
|
297
|
-
async setupQueue(queueName, options) {
|
|
298
|
-
let queue;
|
|
299
|
-
try {
|
|
300
|
-
const channel = await this.assertChannel();
|
|
301
|
-
debug('assertQueue->channel.addSetup', { queueName });
|
|
302
|
-
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
|
|
303
|
-
debug('assertQueue->channel.assertQueue', { queueName });
|
|
304
|
-
queue = await channel.assertQueue(queueName, options);
|
|
305
|
-
}
|
|
306
|
-
catch (e) {
|
|
307
|
-
logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
308
|
-
if (!this.options?.dontRetryAssert) {
|
|
309
|
-
debug('retrying assertQueue', { queueName });
|
|
310
|
-
const channel = await this.assertChannel({ force: true });
|
|
311
|
-
await this.deleteQueue(queueName);
|
|
312
|
-
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
313
|
-
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
|
|
314
|
-
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
315
|
-
queue = await channel.assertQueue(queueName, options);
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
throw e;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
this.queues[queueName] = queueName;
|
|
322
|
-
return queue;
|
|
323
|
-
}
|
|
324
|
-
async assertQueue(queueName, options) {
|
|
325
|
-
RabbitMq.validateName('queue', queueName);
|
|
326
|
-
if (this.queues[queueName]) {
|
|
327
|
-
delete this.queueSetupPromises[queueName];
|
|
328
|
-
return this.queues[queueName];
|
|
329
|
-
}
|
|
330
|
-
if (this.queueSetupPromises[queueName]) {
|
|
331
|
-
return this.queueSetupPromises[queueName];
|
|
332
|
-
}
|
|
333
|
-
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
334
|
-
return this.queueSetupPromises[queueName];
|
|
335
|
-
}
|
|
336
|
-
saveConsumer(queue, callback, options) {
|
|
337
|
-
const isConsumerExist = this.consumers.some((consumer) => consumer.queue === queue);
|
|
338
|
-
if (!isConsumerExist) {
|
|
339
|
-
logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
340
|
-
this.consumers.push({
|
|
341
|
-
queue,
|
|
342
|
-
callback,
|
|
343
|
-
options,
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
async consume(queue, callback, options) {
|
|
348
|
-
await this.consumeFromRabbit(queue, callback, options);
|
|
349
|
-
}
|
|
350
|
-
async lockRedisIfNeeded(msg, options) {
|
|
351
|
-
const { properties: { headers } } = msg;
|
|
352
|
-
const timestamp = headers?.creationTimestamp;
|
|
353
|
-
let releaseLock = null;
|
|
354
|
-
if (options.useConsumeWithLock && timestamp && headers?.redisTimestampValidationKey && this.redisLock) {
|
|
355
|
-
releaseLock = await this.redisLock(headers.redisTimestampValidationKey, options?.lockTimeout || consts_1.DEFAULT_LOCK_TIMEOUT);
|
|
356
|
-
}
|
|
357
|
-
return releaseLock;
|
|
358
|
-
}
|
|
359
|
-
async unlockRedisIfNeeded(releaseLock) {
|
|
360
|
-
if (this.redisLock && releaseLock) {
|
|
361
|
-
await releaseLock();
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
async consumeFromRabbit(queue, callback, options) {
|
|
365
|
-
const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
|
|
366
|
-
RabbitMq.validateName('queue', queue);
|
|
367
|
-
this.saveConsumer(queue, callback, options);
|
|
368
|
-
const uniqueId = (0, node_crypto_1.randomUUID)();
|
|
369
|
-
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
|
|
370
|
-
if (useConsumeWithLock) {
|
|
371
|
-
if (!this.redisLock) {
|
|
372
|
-
throw new Error('Usage of consumeWithLock requires RedisInstance');
|
|
373
|
-
}
|
|
374
|
-
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
375
|
-
}
|
|
376
|
-
const channel = await this.getNewChannel({});
|
|
377
|
-
return channel.addSetup(async (confirmChannel) => {
|
|
378
|
-
await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
|
|
379
|
-
await confirmChannel.prefetch(limit, true);
|
|
380
|
-
const channelConsumerOptions = optionsWithDefaults.channelConsumeOptions ? lodash_1.default.merge(types_1.CONSUMER_DEFAULT_OPTIONS, optionsWithDefaults.channelConsumeOptions) : types_1.CONSUMER_DEFAULT_OPTIONS;
|
|
381
|
-
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
382
|
-
if (!msg) {
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
|
|
386
|
-
const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
|
|
387
|
-
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
388
|
-
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
389
|
-
const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
|
|
390
|
-
// setting also outbreak trace as part of legacy code
|
|
391
|
-
const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
|
|
392
|
-
// enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
|
|
393
|
-
// and we don't want to fail the flow because of it
|
|
394
|
-
if (userId && enableRabbitTrace) {
|
|
395
|
-
try {
|
|
396
|
-
await Promise.all([
|
|
397
|
-
(0, zehut_1.createOrSetRabbitTrace)(trace, userId),
|
|
398
|
-
(0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
|
|
399
|
-
]);
|
|
400
|
-
}
|
|
401
|
-
catch (e) {
|
|
402
|
-
logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
403
|
-
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (traceId) {
|
|
407
|
-
trace?.context?.set(consts_1.TRACING_HEADER, traceId);
|
|
408
|
-
outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
|
|
409
|
-
}
|
|
410
|
-
if (auditContext) {
|
|
411
|
-
await auditContext(queue);
|
|
412
|
-
}
|
|
413
|
-
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
414
|
-
if (!shouldConsume) {
|
|
415
|
-
await this.unlockRedisIfNeeded(releaseLock);
|
|
416
|
-
return this.ack(confirmChannel, msg)(msg);
|
|
417
|
-
}
|
|
418
|
-
let messageAcked = false;
|
|
419
|
-
// setting the localAck function to be used in the callback
|
|
420
|
-
const localAck = async () => {
|
|
421
|
-
if (messageAcked) {
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
messageAcked = true;
|
|
425
|
-
return this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
426
|
-
};
|
|
427
|
-
const localNack = async (_, nackOptions = {}) => {
|
|
428
|
-
if (messageAcked) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
432
|
-
messageAcked = true;
|
|
433
|
-
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
434
|
-
};
|
|
435
|
-
try {
|
|
436
|
-
await callback(parsedMessage, localAck, localNack);
|
|
437
|
-
}
|
|
438
|
-
catch (e) {
|
|
439
|
-
await localNack(msg);
|
|
440
|
-
}
|
|
441
|
-
}, channelConsumerOptions);
|
|
442
|
-
if (!consumerTag) {
|
|
443
|
-
logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
447
|
-
this.consumersTags.push([confirmChannel, consumerTag]);
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
async consumeFromExchange(queue, exchange, callback, options) {
|
|
452
|
-
const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
|
|
453
|
-
RabbitMq.validateName('exchange', exchange);
|
|
454
|
-
RabbitMq.validateName('queue', queue);
|
|
455
|
-
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
456
|
-
await this.saveConsumer(queue, callback, options);
|
|
457
|
-
const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
458
|
-
return channel.addSetup(async (c) => {
|
|
459
|
-
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
460
|
-
await c.assertQueue(queue);
|
|
461
|
-
this.exchanges[exchange] = assertExchange;
|
|
462
|
-
await c.prefetch(limit, true);
|
|
463
|
-
return Promise.all([
|
|
464
|
-
c.bindQueue(queue, exchange, ''),
|
|
465
|
-
this.consume(queue, callback, options),
|
|
466
|
-
]);
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
async publish(exchange, content, customHeaders) {
|
|
470
|
-
return (0, utils_1.wrapSetImmediate)(async () => {
|
|
471
|
-
RabbitMq.validateName('exchange', exchange);
|
|
472
|
-
const channel = await this.assertChannel();
|
|
473
|
-
await this.assertExchange(exchange);
|
|
474
|
-
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
async sendToQueue(queue, content, options, customHeaders) {
|
|
478
|
-
try {
|
|
479
|
-
await this.assertChannel();
|
|
480
|
-
}
|
|
481
|
-
catch (e) {
|
|
482
|
-
logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
483
|
-
throw e;
|
|
484
|
-
}
|
|
485
|
-
try {
|
|
486
|
-
RabbitMq.validateName('queue', queue);
|
|
487
|
-
await this.assertQueue(queue, options);
|
|
488
|
-
}
|
|
489
|
-
catch (e) {
|
|
490
|
-
logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
491
|
-
throw e;
|
|
492
|
-
}
|
|
493
|
-
try {
|
|
494
|
-
const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
495
|
-
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
496
|
-
return res;
|
|
497
|
-
}
|
|
498
|
-
catch (e) {
|
|
499
|
-
const isConnected = await this.isConnected();
|
|
500
|
-
logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
501
|
-
throw e;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
async isConnected() {
|
|
505
|
-
const connection = await this.getConnection();
|
|
506
|
-
const isConnected = connection.isConnected();
|
|
507
|
-
if (!isConnected) {
|
|
508
|
-
logger_1.default.error('rabbit: isConnected - false');
|
|
509
|
-
return false;
|
|
510
|
-
}
|
|
511
|
-
const channel = await this.assertChannel();
|
|
512
|
-
try {
|
|
513
|
-
await Promise.all([
|
|
514
|
-
channel.waitForConnect(),
|
|
515
|
-
...this.consumers.map((c) => channel.checkQueue(c.queue)),
|
|
516
|
-
]);
|
|
517
|
-
}
|
|
518
|
-
catch (e) {
|
|
519
|
-
logger_1.default.error('rabbit: isConnected - false');
|
|
520
|
-
return false;
|
|
521
|
-
}
|
|
522
|
-
logger_1.default.info('rabbit: isConnected - true');
|
|
523
|
-
return true;
|
|
524
|
-
}
|
|
525
|
-
async gracefulShutdown(signal) {
|
|
526
|
-
const tagsNumber = this.consumersTags.length;
|
|
527
|
-
logger_1.default.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
528
|
-
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
529
|
-
// Clean the array to avoid race
|
|
530
|
-
this.consumersTags = [];
|
|
531
|
-
const results = await Promise.allSettled(cancelTagPromises);
|
|
532
|
-
const rejected = results.filter((p) => p.status === 'rejected');
|
|
533
|
-
if (rejected.length > 0) {
|
|
534
|
-
logger_1.default.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
logger_1.default.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
exports.default = RabbitMq;
|
package/dist/lib/consts.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export declare const DEFAULT_DEAD_TTL_TWO_DAYS: number;
|
|
2
|
-
export declare const DEFAULT_LOCK_TIMEOUT: number;
|
|
3
|
-
export declare const RETRY_HEADER = "x-retry-count";
|
|
4
|
-
export declare const TRACING_HEADER = "x-trace-id";
|
|
5
|
-
export declare const USER_TRACING_HEADER = "x-af-user-id";
|
|
6
|
-
export declare const USER_OBJECT = "userObject";
|
|
7
|
-
export declare const DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
8
|
-
export declare const CONNECTION_CREATED_CONST = "connectionCreated";
|
|
9
|
-
export declare const CONNECTION_FAILED_CONST = "connectionFailed";
|
|
10
|
-
export declare const DEFAULT_OPTIONS: {
|
|
11
|
-
limit: number;
|
|
12
|
-
retries: number;
|
|
13
|
-
deadMessageTtl: number;
|
|
14
|
-
lockTimeout: number;
|
|
15
|
-
useConsumeWithLock: boolean;
|
|
16
|
-
auditContext: null;
|
|
17
|
-
enableRabbitTrace: boolean;
|
|
18
|
-
};
|
package/dist/lib/consts.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
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;
|
|
4
|
-
exports.DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 12;
|
|
5
|
-
exports.DEFAULT_LOCK_TIMEOUT = 1000 * 5;
|
|
6
|
-
exports.RETRY_HEADER = 'x-retry-count';
|
|
7
|
-
exports.TRACING_HEADER = 'x-trace-id';
|
|
8
|
-
exports.USER_TRACING_HEADER = 'x-af-user-id';
|
|
9
|
-
exports.USER_OBJECT = 'userObject';
|
|
10
|
-
exports.DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
11
|
-
exports.CONNECTION_CREATED_CONST = 'connectionCreated';
|
|
12
|
-
exports.CONNECTION_FAILED_CONST = 'connectionFailed';
|
|
13
|
-
exports.DEFAULT_OPTIONS = {
|
|
14
|
-
limit: 1,
|
|
15
|
-
retries: 1,
|
|
16
|
-
deadMessageTtl: exports.DEFAULT_DEAD_TTL_TWO_DAYS,
|
|
17
|
-
lockTimeout: exports.DEFAULT_LOCK_TIMEOUT,
|
|
18
|
-
useConsumeWithLock: exports.DEFAULT_USE_CONSUME_WITH_LOCK,
|
|
19
|
-
auditContext: null,
|
|
20
|
-
enableRabbitTrace: false,
|
|
21
|
-
};
|
package/dist/lib/rabbitError.js
DELETED
package/dist/lib/redis.d.ts
DELETED
package/dist/lib/redis.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const bluebird_1 = __importDefault(require("bluebird"));
|
|
7
|
-
const redis = require('redis');
|
|
8
|
-
bluebird_1.default.promisifyAll(redis.RedisClient.prototype);
|
|
9
|
-
bluebird_1.default.promisifyAll(redis.Multi.prototype);
|
|
10
|
-
const getRedisInstance = (config) => redis.createClient(config);
|
|
11
|
-
exports.default = getRedisInstance;
|
package/dist/lib/types.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { ConsumeMessage, Options, Replies } from 'amqplib';
|
|
2
|
-
export interface ExchangesCache {
|
|
3
|
-
[key: string]: any;
|
|
4
|
-
}
|
|
5
|
-
export interface QueuesCache {
|
|
6
|
-
[key: string]: any;
|
|
7
|
-
}
|
|
8
|
-
export interface QueueSetupPromisesDictionary {
|
|
9
|
-
[key: string]: Promise<Replies.AssertQueue> | undefined;
|
|
10
|
-
}
|
|
11
|
-
export type CustomMessageHeaders = {
|
|
12
|
-
redisTimestampValidationKey?: string;
|
|
13
|
-
};
|
|
14
|
-
export type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
|
|
15
|
-
export type ConsumeMessageOrNull = ConsumeMessage | null;
|
|
16
|
-
export interface ConsumeOptions {
|
|
17
|
-
retries?: number;
|
|
18
|
-
deadMessageTtl?: number;
|
|
19
|
-
messageTtl?: number;
|
|
20
|
-
limit?: number;
|
|
21
|
-
lockTimeout?: number;
|
|
22
|
-
useConsumeWithLock?: boolean;
|
|
23
|
-
auditContext?: any;
|
|
24
|
-
enableRabbitTrace?: boolean;
|
|
25
|
-
channelConsumeOptions?: Options.Consume;
|
|
26
|
-
}
|
|
27
|
-
export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
|
|
28
|
-
export type newChannelOpts = {
|
|
29
|
-
name?: string;
|
|
30
|
-
onClose?: null | ((args: any | null) => void);
|
|
31
|
-
};
|
|
32
|
-
export type assertChannelOpts = {
|
|
33
|
-
channelName?: string;
|
|
34
|
-
force?: boolean;
|
|
35
|
-
};
|
|
36
|
-
export type AfConsumer = {
|
|
37
|
-
queue: string;
|
|
38
|
-
callback: CallbackFunction;
|
|
39
|
-
options: ConsumeOptions | undefined;
|
|
40
|
-
};
|
|
41
|
-
export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
|
package/dist/lib/types.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CONSUMER_DEFAULT_OPTIONS = void 0;
|
|
4
|
-
const HA_PROMOTE_ON_FAILURE = 'ha-promote-on-failure';
|
|
5
|
-
const HA_PROMOTE_ON_SHUTDOWN = 'ha-promote-on-shutdown';
|
|
6
|
-
exports.CONSUMER_DEFAULT_OPTIONS = {
|
|
7
|
-
arguments: {
|
|
8
|
-
[HA_PROMOTE_ON_FAILURE]: 'always',
|
|
9
|
-
[HA_PROMOTE_ON_SHUTDOWN]: 'always',
|
|
10
|
-
},
|
|
11
|
-
};
|
package/dist/lib/utils.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { ChannelWrapper } from 'amqp-connection-manager';
|
|
2
|
-
import { ConfirmChannel } from 'amqplib';
|
|
3
|
-
export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<import("amqplib").Replies.AssertExchange>;
|
|
4
|
-
export declare const wrapSetImmediate: (callback: () => any) => Promise<any>;
|
|
5
|
-
export declare const rand: () => number;
|
package/dist/lib/utils.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.rand = exports.wrapSetImmediate = exports.assertExchangeFanout = void 0;
|
|
4
|
-
const assertExchangeFanout = async (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
|
|
5
|
-
exports.assertExchangeFanout = assertExchangeFanout;
|
|
6
|
-
const wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
|
|
7
|
-
setImmediate(async () => {
|
|
8
|
-
try {
|
|
9
|
-
const value = await callback();
|
|
10
|
-
resolve(value);
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
reject(error);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
exports.wrapSetImmediate = wrapSetImmediate;
|
|
18
|
-
const rand = () => Math.floor(Math.random() * 100000);
|
|
19
|
-
exports.rand = rand;
|
package/dist/logger.d.ts
DELETED
package/dist/logger.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const logger_1 = __importDefault(require("@autofleet/logger"));
|
|
7
|
-
const logger = (0, logger_1.default)();
|
|
8
|
-
exports.default = logger;
|
package/dist/mock.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import 'jest';
|
|
2
|
-
import { IAfRabbitMq } from './index';
|
|
3
|
-
declare class RabbitMq implements IAfRabbitMq {
|
|
4
|
-
ack: any;
|
|
5
|
-
nack: any;
|
|
6
|
-
assertChannel: any;
|
|
7
|
-
assertExchange: any;
|
|
8
|
-
assertQueue: any;
|
|
9
|
-
consume: any;
|
|
10
|
-
consumeFromExchange: any;
|
|
11
|
-
publish: any;
|
|
12
|
-
sendToQueue: any;
|
|
13
|
-
}
|
|
14
|
-
export default RabbitMq;
|
package/dist/mock.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
4
|
-
require("jest");
|
|
5
|
-
class RabbitMq {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.ack = jest.fn();
|
|
8
|
-
this.nack = jest.fn();
|
|
9
|
-
this.assertChannel = jest.fn();
|
|
10
|
-
this.assertExchange = jest.fn();
|
|
11
|
-
this.assertQueue = jest.fn();
|
|
12
|
-
this.consume = jest.fn();
|
|
13
|
-
this.consumeFromExchange = jest.fn();
|
|
14
|
-
this.publish = jest.fn();
|
|
15
|
-
this.sendToQueue = jest.fn();
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
exports.default = RabbitMq;
|