@autofleet/rabbit 3.2.6-testing → 3.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +16 -11
- package/dist/index.js +196 -101
- package/dist/lib/consts.d.ts +5 -0
- package/dist/lib/consts.js +6 -1
- package/dist/lib/redis.d.ts +1 -1
- package/dist/lib/types.d.ts +21 -5
- package/dist/lib/types.js +9 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +4 -5
- package/package.json +1 -1
- package/src/index.ts +1 -3
- package/.env +0 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
|
-
import { AmqpConnectionManager, ChannelWrapper } from 'amqp-connection-manager';
|
|
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 { TRACING_HEADER, USER_TRACING_HEADER } from './lib/consts';
|
|
6
7
|
import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache } from './lib/types';
|
|
7
8
|
export interface IAfRabbitMq {
|
|
8
9
|
ack: any;
|
|
@@ -16,6 +17,9 @@ export interface IAfRabbitMq {
|
|
|
16
17
|
sendToQueue: any;
|
|
17
18
|
redisClient?: any;
|
|
18
19
|
}
|
|
20
|
+
interface NackOptions {
|
|
21
|
+
skipRetry?: boolean;
|
|
22
|
+
}
|
|
19
23
|
export interface AfRabbitOptions {
|
|
20
24
|
disableReconnect?: boolean;
|
|
21
25
|
/**
|
|
@@ -24,16 +28,18 @@ export interface AfRabbitOptions {
|
|
|
24
28
|
*/
|
|
25
29
|
dontGracefulShutdown?: boolean;
|
|
26
30
|
/**
|
|
27
|
-
* retry on creation error
|
|
31
|
+
* dont retry on creation error
|
|
28
32
|
* @default false
|
|
29
33
|
*/
|
|
30
|
-
|
|
34
|
+
dontRetryAssert?: boolean;
|
|
35
|
+
rabbitHost?: string;
|
|
31
36
|
}
|
|
32
|
-
type newChannelOpts = {
|
|
37
|
+
declare type newChannelOpts = {
|
|
33
38
|
name?: string;
|
|
34
39
|
onClose?: null | ((args: any | null) => void);
|
|
40
|
+
options?: CreateChannelOpts | undefined;
|
|
35
41
|
};
|
|
36
|
-
type assertChannelOpts = {
|
|
42
|
+
declare type assertChannelOpts = {
|
|
37
43
|
channelName?: string;
|
|
38
44
|
force?: boolean;
|
|
39
45
|
};
|
|
@@ -42,14 +48,14 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
42
48
|
static validateName(type: string, name: string): void;
|
|
43
49
|
static getPublishOptions(customHeaders?: CustomMessageHeaders): {
|
|
44
50
|
timestamp: number;
|
|
51
|
+
timeout: number;
|
|
45
52
|
headers: {
|
|
53
|
+
"x-af-user-id": any;
|
|
46
54
|
"x-trace-id": any;
|
|
47
55
|
redisTimestampValidationKey?: string | undefined;
|
|
48
56
|
creationTimestamp: number;
|
|
49
57
|
};
|
|
50
58
|
};
|
|
51
|
-
PUBLISH_TIMEOUT: number;
|
|
52
|
-
PUBLISH_ERROR_MSG: string;
|
|
53
59
|
DISCONNECT_MSG: string;
|
|
54
60
|
RECONNECT_MSG: string;
|
|
55
61
|
channel: ChannelWrapper | null;
|
|
@@ -67,10 +73,10 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
67
73
|
private consumers;
|
|
68
74
|
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
|
|
69
75
|
private shouldConsumeMessageByTimestamp;
|
|
70
|
-
ack: (channel:
|
|
71
|
-
nack: (channel:
|
|
76
|
+
ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
|
|
77
|
+
nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
|
|
72
78
|
getConnection(): Promise<AmqpConnectionManager>;
|
|
73
|
-
getNewChannel({ name, onClose }?: newChannelOpts): Promise<ChannelWrapper>;
|
|
79
|
+
getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
|
|
74
80
|
assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
|
|
75
81
|
assertExchange(exchangeName: string, options?: any): Promise<any>;
|
|
76
82
|
getQueueLength(queue: string): Promise<Replies.AssertQueue>;
|
|
@@ -78,7 +84,6 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
78
84
|
bindQueue(queue: string, exchange: string): Promise<void>;
|
|
79
85
|
assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
|
|
80
86
|
private saveConsumer;
|
|
81
|
-
private loadConsumers;
|
|
82
87
|
consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
|
|
83
88
|
private lockRedisIfNeeded;
|
|
84
89
|
private unlockRedisIfNeeded;
|
package/dist/index.js
CHANGED
|
@@ -9,50 +9,20 @@ const util_1 = require("util");
|
|
|
9
9
|
const moment_1 = __importDefault(require("moment"));
|
|
10
10
|
const redis_lock_1 = __importDefault(require("redis-lock"));
|
|
11
11
|
const amqp_connection_manager_1 = require("amqp-connection-manager");
|
|
12
|
-
const
|
|
12
|
+
const zehut_1 = require("@autofleet/zehut");
|
|
13
|
+
const uuid_1 = require("uuid");
|
|
13
14
|
const logger_1 = __importDefault(require("./logger"));
|
|
14
15
|
const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
|
|
15
16
|
const redis_1 = __importDefault(require("./lib/redis"));
|
|
16
17
|
const utils_1 = require("./lib/utils");
|
|
17
18
|
const consts_1 = require("./lib/consts");
|
|
19
|
+
const types_1 = require("./lib/types");
|
|
18
20
|
// const debug = nodeDebug('af-rabbitmq')
|
|
19
21
|
const debug = logger_1.default.info;
|
|
20
|
-
const
|
|
21
|
-
const PASSWORD = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
22
|
-
const HOST = process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
22
|
+
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
23
23
|
const HEARTBEAT = '60';
|
|
24
24
|
class RabbitMq {
|
|
25
|
-
static parseMsg(msg) {
|
|
26
|
-
let { content } = msg;
|
|
27
|
-
content = content.toString();
|
|
28
|
-
try {
|
|
29
|
-
content = JSON.parse(content);
|
|
30
|
-
}
|
|
31
|
-
catch (e) { }
|
|
32
|
-
return {
|
|
33
|
-
...msg,
|
|
34
|
-
content,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
static validateName(type, name) {
|
|
38
|
-
if (!name || name === '') {
|
|
39
|
-
throw new rabbitError_1.default(`error while using ${type} with no name`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
static getPublishOptions(customHeaders = {}) {
|
|
43
|
-
const trace = (0, outbreak_1.getCurrentContext)();
|
|
44
|
-
return {
|
|
45
|
-
timestamp: (0, moment_1.default)().unix(),
|
|
46
|
-
headers: {
|
|
47
|
-
creationTimestamp: (0, moment_1.default)().valueOf(),
|
|
48
|
-
...customHeaders,
|
|
49
|
-
[consts_1.TRACING_HEADER]: trace?.context?.get(consts_1.TRACING_HEADER),
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
25
|
constructor(options, redisConfig) {
|
|
54
|
-
this.PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
|
|
55
|
-
this.PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
|
|
56
26
|
this.DISCONNECT_MSG = 'rabbit: connection disconnect';
|
|
57
27
|
this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
58
28
|
this.consumers = [];
|
|
@@ -70,6 +40,7 @@ class RabbitMq {
|
|
|
70
40
|
};
|
|
71
41
|
this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
|
|
72
42
|
if (msg) {
|
|
43
|
+
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
73
44
|
await channel.ack(msg);
|
|
74
45
|
const { properties: { headers } } = msg;
|
|
75
46
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -102,11 +73,11 @@ class RabbitMq {
|
|
|
102
73
|
: 1,
|
|
103
74
|
});
|
|
104
75
|
}
|
|
76
|
+
debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
|
|
105
77
|
await channel.ack(msg);
|
|
106
78
|
}
|
|
107
79
|
else {
|
|
108
80
|
logger_1.default.error('no channel or msg', {
|
|
109
|
-
channel: channel ? channel.name : '',
|
|
110
81
|
msg,
|
|
111
82
|
});
|
|
112
83
|
}
|
|
@@ -117,10 +88,11 @@ class RabbitMq {
|
|
|
117
88
|
this.creatingConnection = false;
|
|
118
89
|
this.exchanges = {};
|
|
119
90
|
this.queues = {};
|
|
91
|
+
this.consumers = [];
|
|
120
92
|
this.options = options;
|
|
121
|
-
this.redisClient = redisConfig &&
|
|
93
|
+
this.redisClient = redisConfig && redis_1.default(redisConfig);
|
|
122
94
|
if (this.redisClient) {
|
|
123
|
-
this.redisLock =
|
|
95
|
+
this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
|
|
124
96
|
}
|
|
125
97
|
this.consumersTags = [];
|
|
126
98
|
logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
|
|
@@ -133,8 +105,41 @@ class RabbitMq {
|
|
|
133
105
|
});
|
|
134
106
|
}
|
|
135
107
|
}
|
|
108
|
+
static parseMsg(msg) {
|
|
109
|
+
let { content } = msg;
|
|
110
|
+
content = content.toString();
|
|
111
|
+
try {
|
|
112
|
+
content = JSON.parse(content);
|
|
113
|
+
}
|
|
114
|
+
catch (e) { }
|
|
115
|
+
return {
|
|
116
|
+
...msg,
|
|
117
|
+
content,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
static validateName(type, name) {
|
|
121
|
+
if (!name || name === '') {
|
|
122
|
+
throw new rabbitError_1.default(`error while using ${type} with no name`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
static getPublishOptions(customHeaders = {}) {
|
|
126
|
+
const trace = zehut_1.getCurrentPayload();
|
|
127
|
+
const user = trace?.context?.get(consts_1.USER_OBJECT);
|
|
128
|
+
const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
|
|
129
|
+
const outbreakTrace = zehut_1.outbreak.getCurrentContext();
|
|
130
|
+
return {
|
|
131
|
+
timestamp: moment_1.default().unix(),
|
|
132
|
+
timeout: PUBLISH_TIMEOUT,
|
|
133
|
+
headers: {
|
|
134
|
+
creationTimestamp: moment_1.default().valueOf(),
|
|
135
|
+
...customHeaders,
|
|
136
|
+
[consts_1.USER_TRACING_HEADER]: user?.id,
|
|
137
|
+
[consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
136
141
|
async getConnection() {
|
|
137
|
-
return new Promise(async (resolve) => {
|
|
142
|
+
return new Promise(async (resolve, reject) => {
|
|
138
143
|
if (this.blockReconnect) {
|
|
139
144
|
debug('rabbit: block reconnect');
|
|
140
145
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
@@ -142,29 +147,57 @@ class RabbitMq {
|
|
|
142
147
|
return resolve();
|
|
143
148
|
}
|
|
144
149
|
if (this.connection !== null) {
|
|
145
|
-
debug('rabbit: connection already exist');
|
|
146
150
|
if (this.options?.disableReconnect || this.connection?.isConnected()) {
|
|
151
|
+
debug('rabbit: connection - is connected');
|
|
147
152
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
148
153
|
// @ts-ignore
|
|
149
154
|
return resolve(this.connection);
|
|
150
155
|
}
|
|
151
|
-
debug('rabbit: connection
|
|
156
|
+
debug('rabbit: connection - reconnecting');
|
|
152
157
|
}
|
|
153
158
|
if (this.creatingConnection) {
|
|
154
159
|
debug('rabbit: creating connection emi');
|
|
155
160
|
this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
|
|
161
|
+
this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
|
|
156
162
|
return;
|
|
157
163
|
}
|
|
158
164
|
this.creatingConnection = true;
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
let isResolved = false;
|
|
166
|
+
// It is import to use it as a function and not as a variable
|
|
167
|
+
// because of k8s changes the env variables
|
|
168
|
+
// and we want to use the new values
|
|
169
|
+
const findServers = () => {
|
|
170
|
+
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
171
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
172
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
173
|
+
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
174
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
175
|
+
};
|
|
176
|
+
const defaultUrls = findServers();
|
|
177
|
+
const connection = await amqp_connection_manager_1.connect(defaultUrls, {
|
|
178
|
+
findServers,
|
|
179
|
+
});
|
|
161
180
|
this.connection = connection;
|
|
181
|
+
this.connection.on('error', (err) => {
|
|
182
|
+
logger_1.default.error('rabbit: connection error', { err });
|
|
183
|
+
if (!isResolved) {
|
|
184
|
+
isResolved = true;
|
|
185
|
+
reject(err);
|
|
186
|
+
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
this.connection.on('connectFailed', (err) => {
|
|
190
|
+
this.consumersTags = [];
|
|
191
|
+
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
192
|
+
if (!isResolved) {
|
|
193
|
+
isResolved = true;
|
|
194
|
+
reject(err);
|
|
195
|
+
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
162
198
|
this.connection.on('disconnect', ({ err }) => {
|
|
199
|
+
this.consumersTags = [];
|
|
163
200
|
debug('rabbit: connection closed');
|
|
164
|
-
this.exchanges = {};
|
|
165
|
-
this.queues = {};
|
|
166
|
-
this.connection = null;
|
|
167
|
-
this.channel = null;
|
|
168
201
|
if (this.options?.disableReconnect) {
|
|
169
202
|
logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
170
203
|
this.blockReconnect = true;
|
|
@@ -175,19 +208,26 @@ class RabbitMq {
|
|
|
175
208
|
});
|
|
176
209
|
this.connection.once('connect', async () => {
|
|
177
210
|
debug('rabbit: connection established');
|
|
178
|
-
await this.loadConsumers();
|
|
179
211
|
this.creatingConnection = false;
|
|
180
212
|
this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
|
|
213
|
+
isResolved = true;
|
|
181
214
|
resolve(connection);
|
|
182
215
|
});
|
|
183
216
|
});
|
|
184
217
|
}
|
|
185
|
-
async getNewChannel({ name =
|
|
218
|
+
async getNewChannel({ name = utils_1.rand().toString(), onClose = null, options = {} } = {}) {
|
|
186
219
|
return new Promise(async (resolve, reject) => {
|
|
187
220
|
const connection = await this.getConnection();
|
|
188
|
-
const channel = connection.createChannel({
|
|
221
|
+
const channel = connection.createChannel({
|
|
222
|
+
...options,
|
|
223
|
+
});
|
|
224
|
+
let isResolved = false;
|
|
189
225
|
channel.on('error', (err) => {
|
|
190
|
-
logger_1.default.error(`rabbit: channel ${name} error`, { err });
|
|
226
|
+
logger_1.default.error(`rabbit: channel error ${name} error`, { err });
|
|
227
|
+
if (!isResolved) {
|
|
228
|
+
isResolved = true;
|
|
229
|
+
reject(err);
|
|
230
|
+
}
|
|
191
231
|
});
|
|
192
232
|
channel.on('close', (...args) => {
|
|
193
233
|
logger_1.default.error(`rabbit: channel ${name} closed`, { args });
|
|
@@ -197,6 +237,7 @@ class RabbitMq {
|
|
|
197
237
|
});
|
|
198
238
|
channel.once('connect', () => {
|
|
199
239
|
debug(`rabbit: channel ${name} CONNECTED`);
|
|
240
|
+
isResolved = true;
|
|
200
241
|
resolve(channel);
|
|
201
242
|
});
|
|
202
243
|
});
|
|
@@ -207,10 +248,9 @@ class RabbitMq {
|
|
|
207
248
|
return resolve(this.channel);
|
|
208
249
|
}
|
|
209
250
|
try {
|
|
210
|
-
const channel = await this.getNewChannel({
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
},
|
|
251
|
+
const channel = await this.getNewChannel({});
|
|
252
|
+
channel.on('error', (err) => {
|
|
253
|
+
logger_1.default.error('rabbit: channel error', { err });
|
|
214
254
|
});
|
|
215
255
|
this.channel = channel;
|
|
216
256
|
resolve(channel);
|
|
@@ -225,25 +265,26 @@ class RabbitMq {
|
|
|
225
265
|
if (this.exchanges[exchangeName]) {
|
|
226
266
|
return this.exchanges[exchangeName];
|
|
227
267
|
}
|
|
228
|
-
const exchange = await
|
|
268
|
+
const exchange = await utils_1.assertExchangeFanout(channel, exchangeName);
|
|
229
269
|
this.exchanges[exchangeName] = exchange;
|
|
230
270
|
return exchange;
|
|
231
271
|
}
|
|
232
272
|
async getQueueLength(queue) {
|
|
233
273
|
RabbitMq.validateName('queue', queue);
|
|
234
|
-
const channel =
|
|
274
|
+
const { channel } = this;
|
|
275
|
+
if (!channel) {
|
|
276
|
+
throw new Error('channel is not defined');
|
|
277
|
+
}
|
|
235
278
|
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
236
|
-
return channel
|
|
279
|
+
return channel?.checkQueue(queue);
|
|
237
280
|
}
|
|
238
281
|
async deleteQueue(queue) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
resolve(deleteQueueRes);
|
|
246
|
-
});
|
|
282
|
+
RabbitMq.validateName('queue', queue);
|
|
283
|
+
const channel = await this.assertChannel();
|
|
284
|
+
logger_1.default.info('rabbit: deleting queue', { queue });
|
|
285
|
+
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
286
|
+
debug('queue deleted', deleteQueueRes);
|
|
287
|
+
return deleteQueueRes;
|
|
247
288
|
}
|
|
248
289
|
async bindQueue(queue, exchange) {
|
|
249
290
|
const channel = await this.assertChannel();
|
|
@@ -265,7 +306,7 @@ class RabbitMq {
|
|
|
265
306
|
}
|
|
266
307
|
catch (e) {
|
|
267
308
|
logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
268
|
-
if (this.options?.
|
|
309
|
+
if (!this.options?.dontRetryAssert) {
|
|
269
310
|
debug('retrying assertQueue', { queueName });
|
|
270
311
|
const channel = await this.assertChannel({ force: true });
|
|
271
312
|
await this.deleteQueue(queueName);
|
|
@@ -288,17 +329,8 @@ class RabbitMq {
|
|
|
288
329
|
options,
|
|
289
330
|
});
|
|
290
331
|
}
|
|
291
|
-
async loadConsumers() {
|
|
292
|
-
debug('rabbit: loading consumers', { consumers: this.consumers.length });
|
|
293
|
-
if (this.consumers.length > 0) {
|
|
294
|
-
await Promise.all(this.consumers.map((consumer) => this
|
|
295
|
-
.consumeFromRabbit(consumer.queue, consumer.callback, consumer.options)));
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
332
|
async consume(queue, callback, options) {
|
|
299
|
-
|
|
300
|
-
this.consumeFromRabbit(queue, callback, options);
|
|
301
|
-
}
|
|
333
|
+
await this.consumeFromRabbit(queue, callback, options);
|
|
302
334
|
return this.saveConsumer(queue, callback, options);
|
|
303
335
|
}
|
|
304
336
|
async lockRedisIfNeeded(msg, options) {
|
|
@@ -318,46 +350,86 @@ class RabbitMq {
|
|
|
318
350
|
async consumeFromRabbit(queue, callback, options) {
|
|
319
351
|
const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
|
|
320
352
|
RabbitMq.validateName('queue', queue);
|
|
321
|
-
const
|
|
353
|
+
const uniqueId = uuid_1.v4();
|
|
354
|
+
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
|
|
322
355
|
if (useConsumeWithLock) {
|
|
323
356
|
if (!this.redisLock) {
|
|
324
357
|
throw new Error('Usage of consumeWithLock requires RedisInstance');
|
|
325
358
|
}
|
|
326
359
|
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
327
360
|
}
|
|
328
|
-
const channel = await this.
|
|
329
|
-
return channel.addSetup(async (
|
|
330
|
-
await
|
|
331
|
-
await
|
|
332
|
-
const { consumerTag } = await
|
|
361
|
+
const channel = await this.assertChannel();
|
|
362
|
+
return channel.addSetup(async (confirmChannel) => {
|
|
363
|
+
await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
|
|
364
|
+
await confirmChannel.prefetch(limit, true);
|
|
365
|
+
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
333
366
|
if (!msg) {
|
|
334
367
|
return null;
|
|
335
368
|
}
|
|
336
369
|
const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
|
|
370
|
+
const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
|
|
371
|
+
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
372
|
+
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
373
|
+
const trace = zehut_1.newTrace(zehut_1.traceTypes.RABBIT);
|
|
374
|
+
// setting also outbreak trace as part of legacy code
|
|
375
|
+
const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
|
|
376
|
+
// enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
|
|
377
|
+
// and we don't want to fail the flow because of it
|
|
378
|
+
if (userId && enableRabbitTrace) {
|
|
379
|
+
try {
|
|
380
|
+
await Promise.all([
|
|
381
|
+
zehut_1.createOrSetRabbitTrace(trace, userId),
|
|
382
|
+
zehut_1.createOrSetRabbitTrace(outbreakTrace, userId),
|
|
383
|
+
]);
|
|
384
|
+
}
|
|
385
|
+
catch (e) {
|
|
386
|
+
logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
387
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
337
390
|
if (traceId) {
|
|
338
|
-
const trace = (0, outbreak_1.newTrace)(outbreak_1.traceTypes.RABBIT);
|
|
339
391
|
trace?.context?.set(consts_1.TRACING_HEADER, traceId);
|
|
392
|
+
outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
|
|
393
|
+
}
|
|
394
|
+
if (auditContext) {
|
|
395
|
+
await auditContext(queue);
|
|
340
396
|
}
|
|
341
|
-
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
342
|
-
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
343
397
|
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
344
398
|
if (!shouldConsume) {
|
|
345
399
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
346
|
-
return this.ack(
|
|
400
|
+
return this.ack(confirmChannel, msg)(msg);
|
|
347
401
|
}
|
|
402
|
+
let messageAcked = false;
|
|
403
|
+
// setting the localAck function to be used in the callback
|
|
404
|
+
const localAck = async () => {
|
|
405
|
+
if (messageAcked) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
debug('rabbit localAck', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
409
|
+
messageAcked = true;
|
|
410
|
+
return this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
411
|
+
};
|
|
412
|
+
const localNack = async (_, nackOptions = {}) => {
|
|
413
|
+
if (messageAcked) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
417
|
+
messageAcked = true;
|
|
418
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
419
|
+
};
|
|
348
420
|
try {
|
|
349
|
-
await callback(parsedMessage,
|
|
421
|
+
await callback(parsedMessage, localAck, localNack);
|
|
350
422
|
}
|
|
351
423
|
catch (e) {
|
|
352
|
-
await
|
|
424
|
+
await localNack(msg);
|
|
353
425
|
}
|
|
354
|
-
});
|
|
426
|
+
}, types_1.CONSUMER_DEFAULT_OPTIONS);
|
|
355
427
|
if (!consumerTag) {
|
|
356
428
|
logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
|
|
357
429
|
}
|
|
358
430
|
else {
|
|
359
431
|
logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
360
|
-
this.consumersTags.push([
|
|
432
|
+
this.consumersTags.push([confirmChannel, consumerTag]);
|
|
361
433
|
}
|
|
362
434
|
});
|
|
363
435
|
}
|
|
@@ -366,9 +438,9 @@ class RabbitMq {
|
|
|
366
438
|
RabbitMq.validateName('exchange', exchange);
|
|
367
439
|
RabbitMq.validateName('queue', queue);
|
|
368
440
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
369
|
-
const channel = await this.getNewChannel();
|
|
441
|
+
const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
370
442
|
return channel.addSetup(async (c) => {
|
|
371
|
-
const assertExchange = await
|
|
443
|
+
const assertExchange = await utils_1.assertExchangeFanout(c, exchange);
|
|
372
444
|
await c.assertQueue(queue);
|
|
373
445
|
this.exchanges[exchange] = assertExchange;
|
|
374
446
|
await c.prefetch(limit, true);
|
|
@@ -379,7 +451,7 @@ class RabbitMq {
|
|
|
379
451
|
});
|
|
380
452
|
}
|
|
381
453
|
async publish(exchange, content, customHeaders) {
|
|
382
|
-
return
|
|
454
|
+
return utils_1.wrapSetImmediate(async () => {
|
|
383
455
|
RabbitMq.validateName('exchange', exchange);
|
|
384
456
|
const channel = await this.assertChannel();
|
|
385
457
|
await this.assertExchange(exchange);
|
|
@@ -388,21 +460,44 @@ class RabbitMq {
|
|
|
388
460
|
}
|
|
389
461
|
async sendToQueue(queue, content, options, customHeaders, isBlocking) {
|
|
390
462
|
const callback = async () => {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
463
|
+
try {
|
|
464
|
+
RabbitMq.validateName('queue', queue);
|
|
465
|
+
await this.assertChannel();
|
|
466
|
+
await this.assertQueue(queue, options);
|
|
467
|
+
const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
468
|
+
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
469
|
+
return res;
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
logger_1.default.error(`rabbit: failed to send to queue ${queue}`, { e });
|
|
473
|
+
throw e;
|
|
474
|
+
}
|
|
397
475
|
};
|
|
398
476
|
if (isBlocking) {
|
|
399
477
|
return callback();
|
|
400
478
|
}
|
|
401
|
-
return
|
|
479
|
+
return utils_1.wrapSetImmediate(callback);
|
|
402
480
|
}
|
|
403
481
|
async isConnected() {
|
|
404
482
|
const connection = await this.getConnection();
|
|
405
|
-
|
|
483
|
+
const isConnected = connection.isConnected();
|
|
484
|
+
if (!isConnected) {
|
|
485
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
const channel = await this.assertChannel();
|
|
489
|
+
try {
|
|
490
|
+
await Promise.all([
|
|
491
|
+
channel.waitForConnect(),
|
|
492
|
+
...this.consumers.map((c) => channel.checkQueue(c.queue)),
|
|
493
|
+
]);
|
|
494
|
+
}
|
|
495
|
+
catch (e) {
|
|
496
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
logger_1.default.info('rabbit: isConnected - true');
|
|
500
|
+
return true;
|
|
406
501
|
}
|
|
407
502
|
async gracefulShutdown(signal) {
|
|
408
503
|
const tagsNumber = this.consumersTags.length;
|
package/dist/lib/consts.d.ts
CHANGED
|
@@ -2,12 +2,17 @@ export declare const DEFAULT_DEAD_TTL_TWO_DAYS: number;
|
|
|
2
2
|
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
|
+
export declare const USER_TRACING_HEADER = "x-af-user-id";
|
|
6
|
+
export declare const USER_OBJECT = "userObject";
|
|
5
7
|
export declare const DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
6
8
|
export declare const CONNECTION_CREATED_CONST = "connectionCreated";
|
|
9
|
+
export declare const CONNECTION_FAILED_CONST = "connectionFailed";
|
|
7
10
|
export declare const DEFAULT_OPTIONS: {
|
|
8
11
|
limit: number;
|
|
9
12
|
retries: number;
|
|
10
13
|
deadMessageTtl: number;
|
|
11
14
|
lockTimeout: number;
|
|
12
15
|
useConsumeWithLock: boolean;
|
|
16
|
+
auditContext: null;
|
|
17
|
+
enableRabbitTrace: boolean;
|
|
13
18
|
};
|
package/dist/lib/consts.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_OPTIONS = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = 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.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
|
+
exports.USER_TRACING_HEADER = 'x-af-user-id';
|
|
9
|
+
exports.USER_OBJECT = 'userObject';
|
|
8
10
|
exports.DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
9
11
|
exports.CONNECTION_CREATED_CONST = 'connectionCreated';
|
|
12
|
+
exports.CONNECTION_FAILED_CONST = 'connectionFailed';
|
|
10
13
|
exports.DEFAULT_OPTIONS = {
|
|
11
14
|
limit: 1,
|
|
12
15
|
retries: 1,
|
|
13
16
|
deadMessageTtl: exports.DEFAULT_DEAD_TTL_TWO_DAYS,
|
|
14
17
|
lockTimeout: exports.DEFAULT_LOCK_TIMEOUT,
|
|
15
18
|
useConsumeWithLock: exports.DEFAULT_USE_CONSUME_WITH_LOCK,
|
|
19
|
+
auditContext: null,
|
|
20
|
+
enableRabbitTrace: false,
|
|
16
21
|
};
|
package/dist/lib/redis.d.ts
CHANGED
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { ConsumeMessage } from 'amqplib';
|
|
1
|
+
import { ConsumeMessage, Options } from 'amqplib';
|
|
2
2
|
export interface ExchangesCache {
|
|
3
3
|
[key: string]: any;
|
|
4
4
|
}
|
|
5
5
|
export interface QueuesCache {
|
|
6
6
|
[key: string]: any;
|
|
7
7
|
}
|
|
8
|
-
export type CustomMessageHeaders = {
|
|
8
|
+
export declare type CustomMessageHeaders = {
|
|
9
9
|
redisTimestampValidationKey?: string;
|
|
10
10
|
};
|
|
11
|
-
export type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
|
|
12
|
-
export type ConsumeMessageOrNull = ConsumeMessage | null;
|
|
11
|
+
export declare type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
|
|
12
|
+
export declare type ConsumeMessageOrNull = ConsumeMessage | null;
|
|
13
13
|
export interface ConsumeOptions {
|
|
14
14
|
retries?: number;
|
|
15
15
|
deadMessageTtl?: number;
|
|
@@ -17,5 +17,21 @@ export interface ConsumeOptions {
|
|
|
17
17
|
limit?: number;
|
|
18
18
|
lockTimeout?: number;
|
|
19
19
|
useConsumeWithLock?: boolean;
|
|
20
|
+
auditContext?: any;
|
|
21
|
+
enableRabbitTrace?: boolean;
|
|
20
22
|
}
|
|
21
|
-
export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
|
|
23
|
+
export declare type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
|
|
24
|
+
export declare type newChannelOpts = {
|
|
25
|
+
name?: string;
|
|
26
|
+
onClose?: null | ((args: any | null) => void);
|
|
27
|
+
};
|
|
28
|
+
export declare type assertChannelOpts = {
|
|
29
|
+
channelName?: string;
|
|
30
|
+
force?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare type AfConsumer = {
|
|
33
|
+
queue: string;
|
|
34
|
+
callback: CallbackFunction;
|
|
35
|
+
options: ConsumeOptions | undefined;
|
|
36
|
+
};
|
|
37
|
+
export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
|
package/dist/lib/types.js
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
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
CHANGED
|
@@ -2,3 +2,4 @@ import { ChannelWrapper } from 'amqp-connection-manager';
|
|
|
2
2
|
import { ConfirmChannel } from 'amqplib';
|
|
3
3
|
export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<import("amqplib").Replies.AssertExchange>;
|
|
4
4
|
export declare const wrapSetImmediate: (callback: () => any) => Promise<any>;
|
|
5
|
+
export declare const rand: () => number;
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.wrapSetImmediate = exports.assertExchangeFanout = void 0;
|
|
4
|
-
|
|
5
|
-
exports.
|
|
6
|
-
const wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
|
|
3
|
+
exports.rand = exports.wrapSetImmediate = exports.assertExchangeFanout = void 0;
|
|
4
|
+
exports.assertExchangeFanout = async (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
|
|
5
|
+
exports.wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
|
|
7
6
|
setImmediate(async () => {
|
|
8
7
|
try {
|
|
9
8
|
const value = await callback();
|
|
@@ -14,4 +13,4 @@ const wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
|
|
|
14
13
|
}
|
|
15
14
|
});
|
|
16
15
|
});
|
|
17
|
-
exports.
|
|
16
|
+
exports.rand = () => Math.floor(Math.random() * 100000);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -665,10 +665,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
665
665
|
logger.error('rabbit: isConnected - false');
|
|
666
666
|
return false;
|
|
667
667
|
}
|
|
668
|
-
const channel = await this.assertChannel();
|
|
668
|
+
const channel: any = await this.assertChannel();
|
|
669
669
|
try {
|
|
670
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
671
|
-
// @ts-ignore
|
|
672
670
|
await Promise.all([
|
|
673
671
|
channel.waitForConnect(),
|
|
674
672
|
...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
package/.env
DELETED