@autofleet/rabbit 3.2.0 → 3.2.2-2.beta-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -1
- package/dist/index.d.ts +27 -21
- package/dist/index.js +254 -175
- package/dist/lib/redis.d.ts +1 -1
- package/dist/lib/types.d.ts +20 -8
- package/dist/lib/types.js +7 -1
- package/dist/lib/utils.js +6 -3
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +5 -2
- package/package.json +12 -13
- package/src/index.ts +259 -167
- package/src/lib/types.ts +16 -1
- package/src/logger.ts +1 -1
- package/tsconfig.json +5 -2
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ 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
12
|
const zehut_1 = require("@autofleet/zehut");
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
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"));
|
|
@@ -17,10 +18,43 @@ const utils_1 = require("./lib/utils");
|
|
|
17
18
|
const consts_1 = require("./lib/consts");
|
|
18
19
|
const types_1 = require("./lib/types");
|
|
19
20
|
// const debug = nodeDebug('af-rabbitmq')
|
|
20
|
-
const debug = logger_1.default.
|
|
21
|
+
const debug = logger_1.default.debug.bind(logger_1.default);
|
|
21
22
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
22
23
|
const HEARTBEAT = '60';
|
|
23
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, zehut_1.getCurrentPayload)();
|
|
44
|
+
const user = trace?.context?.get(consts_1.USER_OBJECT);
|
|
45
|
+
const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
|
|
46
|
+
const outbreakTrace = zehut_1.outbreak.getCurrentContext();
|
|
47
|
+
return {
|
|
48
|
+
timestamp: (0, moment_1.default)().unix(),
|
|
49
|
+
timeout: PUBLISH_TIMEOUT,
|
|
50
|
+
headers: {
|
|
51
|
+
creationTimestamp: (0, moment_1.default)().valueOf(),
|
|
52
|
+
...customHeaders,
|
|
53
|
+
[consts_1.USER_TRACING_HEADER]: user?.id,
|
|
54
|
+
[consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
24
58
|
constructor(options, redisConfig) {
|
|
25
59
|
this.DISCONNECT_MSG = 'rabbit: connection disconnect';
|
|
26
60
|
this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
@@ -39,6 +73,7 @@ class RabbitMq {
|
|
|
39
73
|
};
|
|
40
74
|
this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
|
|
41
75
|
if (msg) {
|
|
76
|
+
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
42
77
|
await channel.ack(msg);
|
|
43
78
|
const { properties: { headers } } = msg;
|
|
44
79
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -71,28 +106,30 @@ class RabbitMq {
|
|
|
71
106
|
: 1,
|
|
72
107
|
});
|
|
73
108
|
}
|
|
109
|
+
debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
|
|
74
110
|
await channel.ack(msg);
|
|
75
111
|
}
|
|
76
112
|
else {
|
|
77
113
|
logger_1.default.error('no channel or msg', {
|
|
78
|
-
channel: channel ? channel.name : '',
|
|
79
114
|
msg,
|
|
80
115
|
});
|
|
81
116
|
}
|
|
82
117
|
};
|
|
83
|
-
this.userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
84
|
-
this.password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
85
|
-
this.host = options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
86
118
|
this.em = new events_1.EventEmitter();
|
|
87
119
|
this.channel = null;
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
120
|
+
this.publishChannelSetupPromise = null;
|
|
121
|
+
this.connectionsMap = {
|
|
122
|
+
[types_1.ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
|
|
123
|
+
[types_1.ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
|
|
124
|
+
};
|
|
90
125
|
this.exchanges = {};
|
|
91
126
|
this.queues = {};
|
|
127
|
+
this.queueSetupPromises = {};
|
|
128
|
+
this.consumers = [];
|
|
92
129
|
this.options = options;
|
|
93
|
-
this.redisClient = redisConfig && redis_1.default(redisConfig);
|
|
130
|
+
this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
|
|
94
131
|
if (this.redisClient) {
|
|
95
|
-
this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
|
|
132
|
+
this.redisLock = (0, util_1.promisify)((0, redis_lock_1.default)(this.redisClient));
|
|
96
133
|
}
|
|
97
134
|
this.consumersTags = [];
|
|
98
135
|
logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
|
|
@@ -105,68 +142,52 @@ class RabbitMq {
|
|
|
105
142
|
});
|
|
106
143
|
}
|
|
107
144
|
}
|
|
108
|
-
|
|
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
|
-
}
|
|
141
|
-
async getConnection() {
|
|
145
|
+
async getConnection(connectionPurpose) {
|
|
142
146
|
return new Promise(async (resolve, reject) => {
|
|
147
|
+
let { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
|
|
143
148
|
if (this.blockReconnect) {
|
|
144
149
|
debug('rabbit: block reconnect');
|
|
145
150
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
146
151
|
// @ts-ignore
|
|
147
152
|
return resolve();
|
|
148
153
|
}
|
|
149
|
-
if (
|
|
150
|
-
if (this.options?.disableReconnect ||
|
|
154
|
+
if (connection !== null) {
|
|
155
|
+
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
151
156
|
debug('rabbit: connection - is connected');
|
|
152
157
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
153
158
|
// @ts-ignore
|
|
154
|
-
return resolve(
|
|
159
|
+
return resolve(connection);
|
|
155
160
|
}
|
|
156
161
|
debug('rabbit: connection - reconnecting');
|
|
157
162
|
}
|
|
158
|
-
if (
|
|
163
|
+
if (creatingConnection) {
|
|
159
164
|
debug('rabbit: creating connection emi');
|
|
160
165
|
this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
|
|
161
166
|
this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
|
|
162
167
|
return;
|
|
163
168
|
}
|
|
164
|
-
this.creatingConnection = true;
|
|
169
|
+
this.connectionsMap[connectionPurpose].creatingConnection = true;
|
|
165
170
|
let isResolved = false;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
// It is import to use it as a function and not as a variable
|
|
172
|
+
// because of k8s changes the env variables
|
|
173
|
+
// and we want to use the new values
|
|
174
|
+
const findServers = () => {
|
|
175
|
+
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
176
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
177
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
178
|
+
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
179
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
180
|
+
};
|
|
181
|
+
const defaultUrls = findServers();
|
|
182
|
+
const newConnection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
183
|
+
findServers,
|
|
184
|
+
});
|
|
185
|
+
if (!newConnection) {
|
|
186
|
+
logger_1.default.error('rabbit: couldnt create a connection');
|
|
187
|
+
return resolve(connection);
|
|
188
|
+
}
|
|
189
|
+
this.connectionsMap[connectionPurpose].connection = newConnection;
|
|
190
|
+
newConnection.on('error', (err) => {
|
|
170
191
|
logger_1.default.error('rabbit: connection error', { err });
|
|
171
192
|
if (!isResolved) {
|
|
172
193
|
isResolved = true;
|
|
@@ -174,7 +195,8 @@ class RabbitMq {
|
|
|
174
195
|
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
175
196
|
}
|
|
176
197
|
});
|
|
177
|
-
|
|
198
|
+
newConnection.on('connectFailed', (err) => {
|
|
199
|
+
this.consumersTags = [];
|
|
178
200
|
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
179
201
|
if (!isResolved) {
|
|
180
202
|
isResolved = true;
|
|
@@ -182,10 +204,9 @@ class RabbitMq {
|
|
|
182
204
|
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
183
205
|
}
|
|
184
206
|
});
|
|
185
|
-
|
|
207
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
208
|
+
this.consumersTags = [];
|
|
186
209
|
debug('rabbit: connection closed');
|
|
187
|
-
this.exchanges = {};
|
|
188
|
-
this.queues = {};
|
|
189
210
|
if (this.options?.disableReconnect) {
|
|
190
211
|
logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
191
212
|
this.blockReconnect = true;
|
|
@@ -194,95 +215,99 @@ class RabbitMq {
|
|
|
194
215
|
logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
195
216
|
}
|
|
196
217
|
});
|
|
197
|
-
|
|
218
|
+
newConnection.once('connect', async () => {
|
|
198
219
|
debug('rabbit: connection established');
|
|
199
|
-
|
|
200
|
-
this.
|
|
201
|
-
this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
|
|
220
|
+
this.connectionsMap[connectionPurpose].creatingConnection = false;
|
|
221
|
+
this.em.emit(consts_1.CONNECTION_CREATED_CONST, newConnection);
|
|
202
222
|
isResolved = true;
|
|
203
|
-
resolve(
|
|
223
|
+
resolve(newConnection);
|
|
204
224
|
});
|
|
205
225
|
});
|
|
206
226
|
}
|
|
207
|
-
async getNewChannel({ name = utils_1.rand().toString(), onClose = null
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connectionPurpose = types_1.ConnectionPurpose.Consume, }) {
|
|
228
|
+
let connection;
|
|
229
|
+
try {
|
|
230
|
+
connection = await this.getConnection(connectionPurpose);
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
234
|
+
throw e;
|
|
235
|
+
}
|
|
236
|
+
if (!connection) {
|
|
237
|
+
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
238
|
+
}
|
|
239
|
+
const channel = connection.createChannel({ ...options });
|
|
240
|
+
(0, events_1.once)(channel, 'close').then((args) => {
|
|
241
|
+
logger_1.default.error(`rabbit: channel ${name} closed`);
|
|
242
|
+
onClose?.(args);
|
|
243
|
+
});
|
|
244
|
+
try {
|
|
245
|
+
await (0, events_1.once)(channel, 'connect');
|
|
246
|
+
debug(`rabbit: channel ${name} CONNECTED`);
|
|
247
|
+
return channel;
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
logger_1.default.error(`rabbit: channel error ${name} error`, { err });
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async assertChannel({ force = false, connectionPurpose = types_1.ConnectionPurpose.Consume }) {
|
|
255
|
+
if (!this.publishChannelSetupPromise) {
|
|
256
|
+
this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
|
|
257
|
+
if (this.channel && !force) {
|
|
258
|
+
return resolve(this.channel);
|
|
217
259
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
260
|
+
try {
|
|
261
|
+
const channel = await this.getNewChannel({ connectionPurpose });
|
|
262
|
+
channel.on('error', (err) => {
|
|
263
|
+
logger_1.default.error('rabbit: channel error', { err });
|
|
264
|
+
});
|
|
265
|
+
this.channel = channel;
|
|
266
|
+
resolve(channel);
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
reject(e);
|
|
223
270
|
}
|
|
224
271
|
});
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
isResolved = true;
|
|
228
|
-
resolve(channel);
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
async assertChannel({ force = false } = {}) {
|
|
233
|
-
return new Promise(async (resolve, reject) => {
|
|
234
|
-
if (this.channel && !force) {
|
|
235
|
-
return resolve(this.channel);
|
|
236
|
-
}
|
|
237
|
-
try {
|
|
238
|
-
const channel = await this.getNewChannel({});
|
|
239
|
-
channel.on('error', (err) => {
|
|
240
|
-
logger_1.default.error('rabbit: channel error', { err });
|
|
241
|
-
});
|
|
242
|
-
this.channel = channel;
|
|
243
|
-
resolve(channel);
|
|
244
|
-
}
|
|
245
|
-
catch (e) {
|
|
246
|
-
reject(e);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
272
|
+
}
|
|
273
|
+
return this.publishChannelSetupPromise;
|
|
249
274
|
}
|
|
250
275
|
async assertExchange(exchangeName, options) {
|
|
251
|
-
const channel = await this.assertChannel();
|
|
276
|
+
const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
|
|
252
277
|
if (this.exchanges[exchangeName]) {
|
|
253
278
|
return this.exchanges[exchangeName];
|
|
254
279
|
}
|
|
255
|
-
const exchange = await utils_1.assertExchangeFanout(channel, exchangeName);
|
|
280
|
+
const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
|
|
256
281
|
this.exchanges[exchangeName] = exchange;
|
|
257
282
|
return exchange;
|
|
258
283
|
}
|
|
259
|
-
async getQueueLength(queue) {
|
|
284
|
+
async getQueueLength(queue, connectionPurpose = types_1.ConnectionPurpose.Consume) {
|
|
260
285
|
RabbitMq.validateName('queue', queue);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
286
|
+
let { connection } = this.connectionsMap[connectionPurpose];
|
|
287
|
+
const { channel } = this;
|
|
288
|
+
if (!channel) {
|
|
289
|
+
throw new Error('channel is not defined');
|
|
290
|
+
}
|
|
291
|
+
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
292
|
+
return channel?.checkQueue(queue);
|
|
264
293
|
}
|
|
265
|
-
async deleteQueue(queue) {
|
|
294
|
+
async deleteQueue(queue, connectionPurpose) {
|
|
266
295
|
RabbitMq.validateName('queue', queue);
|
|
267
|
-
const channel = await this.assertChannel();
|
|
296
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
268
297
|
logger_1.default.info('rabbit: deleting queue', { queue });
|
|
269
298
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
270
299
|
debug('queue deleted', deleteQueueRes);
|
|
271
300
|
return deleteQueueRes;
|
|
272
301
|
}
|
|
273
|
-
async bindQueue(queue, exchange) {
|
|
274
|
-
const channel = await this.assertChannel();
|
|
302
|
+
async bindQueue(queue, exchange, connectionPurpose) {
|
|
303
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
275
304
|
await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
276
305
|
return channel.bindQueue(queue, exchange, '');
|
|
277
306
|
}
|
|
278
|
-
async
|
|
279
|
-
let queue
|
|
280
|
-
RabbitMq.validateName('queue', queueName);
|
|
281
|
-
if (this.queues[queueName]) {
|
|
282
|
-
return this.queues[queueName];
|
|
283
|
-
}
|
|
307
|
+
async setupQueue(queueName, connectionPurpose, options) {
|
|
308
|
+
let queue;
|
|
284
309
|
try {
|
|
285
|
-
const channel = await this.assertChannel();
|
|
310
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
286
311
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
287
312
|
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
|
|
288
313
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
@@ -292,11 +317,11 @@ class RabbitMq {
|
|
|
292
317
|
logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
293
318
|
if (!this.options?.dontRetryAssert) {
|
|
294
319
|
debug('retrying assertQueue', { queueName });
|
|
295
|
-
const channel = await this.assertChannel({ force: true });
|
|
296
|
-
await this.deleteQueue(queueName);
|
|
297
|
-
debug('
|
|
320
|
+
const channel = await this.assertChannel({ force: true, connectionPurpose });
|
|
321
|
+
await this.deleteQueue(queueName, connectionPurpose);
|
|
322
|
+
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
298
323
|
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
|
|
299
|
-
debug('
|
|
324
|
+
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
300
325
|
queue = await channel.assertQueue(queueName, options);
|
|
301
326
|
}
|
|
302
327
|
else {
|
|
@@ -306,25 +331,31 @@ class RabbitMq {
|
|
|
306
331
|
this.queues[queueName] = queueName;
|
|
307
332
|
return queue;
|
|
308
333
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
334
|
+
async assertQueue(queueName, connectionPurpose, options) {
|
|
335
|
+
RabbitMq.validateName('queue', queueName);
|
|
336
|
+
if (this.queues[queueName]) {
|
|
337
|
+
delete this.queueSetupPromises[queueName];
|
|
338
|
+
return this.queues[queueName];
|
|
339
|
+
}
|
|
340
|
+
if (this.queueSetupPromises[queueName]) {
|
|
341
|
+
return this.queueSetupPromises[queueName];
|
|
342
|
+
}
|
|
343
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
|
|
344
|
+
return this.queueSetupPromises[queueName];
|
|
315
345
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
|
|
346
|
+
saveConsumer(queue, callback, options) {
|
|
347
|
+
const isConsumerExist = this.consumers.some((consumer) => consumer.queue === queue);
|
|
348
|
+
if (!isConsumerExist) {
|
|
349
|
+
logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
350
|
+
this.consumers.push({
|
|
351
|
+
queue,
|
|
352
|
+
callback,
|
|
353
|
+
options,
|
|
354
|
+
});
|
|
321
355
|
}
|
|
322
356
|
}
|
|
323
357
|
async consume(queue, callback, options) {
|
|
324
|
-
|
|
325
|
-
this.consumeFromRabbit(queue, callback, options);
|
|
326
|
-
}
|
|
327
|
-
return this.saveConsumer(queue, callback, options);
|
|
358
|
+
await this.consumeFromRabbit(queue, callback, options);
|
|
328
359
|
}
|
|
329
360
|
async lockRedisIfNeeded(msg, options) {
|
|
330
361
|
const { properties: { headers } } = msg;
|
|
@@ -343,6 +374,8 @@ class RabbitMq {
|
|
|
343
374
|
async consumeFromRabbit(queue, callback, options) {
|
|
344
375
|
const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
|
|
345
376
|
RabbitMq.validateName('queue', queue);
|
|
377
|
+
this.saveConsumer(queue, callback, options);
|
|
378
|
+
const uniqueId = (0, node_crypto_1.randomUUID)();
|
|
346
379
|
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
|
|
347
380
|
if (useConsumeWithLock) {
|
|
348
381
|
if (!this.redisLock) {
|
|
@@ -350,11 +383,11 @@ class RabbitMq {
|
|
|
350
383
|
}
|
|
351
384
|
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
352
385
|
}
|
|
353
|
-
const channel = await this.getNewChannel({
|
|
354
|
-
return channel.addSetup(async (
|
|
355
|
-
await
|
|
356
|
-
await
|
|
357
|
-
const { consumerTag } = await
|
|
386
|
+
const channel = await this.getNewChannel({ connectionPurpose: types_1.ConnectionPurpose.Consume });
|
|
387
|
+
return channel.addSetup(async (confirmChannel) => {
|
|
388
|
+
await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
|
|
389
|
+
await confirmChannel.prefetch(limit, true);
|
|
390
|
+
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
358
391
|
if (!msg) {
|
|
359
392
|
return null;
|
|
360
393
|
}
|
|
@@ -362,7 +395,7 @@ class RabbitMq {
|
|
|
362
395
|
const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
|
|
363
396
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
364
397
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
365
|
-
const trace = zehut_1.newTrace(zehut_1.traceTypes.RABBIT);
|
|
398
|
+
const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
|
|
366
399
|
// setting also outbreak trace as part of legacy code
|
|
367
400
|
const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
|
|
368
401
|
// enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
|
|
@@ -370,13 +403,13 @@ class RabbitMq {
|
|
|
370
403
|
if (userId && enableRabbitTrace) {
|
|
371
404
|
try {
|
|
372
405
|
await Promise.all([
|
|
373
|
-
zehut_1.createOrSetRabbitTrace(trace, userId),
|
|
374
|
-
zehut_1.createOrSetRabbitTrace(outbreakTrace, userId),
|
|
406
|
+
(0, zehut_1.createOrSetRabbitTrace)(trace, userId),
|
|
407
|
+
(0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
|
|
375
408
|
]);
|
|
376
409
|
}
|
|
377
410
|
catch (e) {
|
|
378
411
|
logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
379
|
-
|
|
412
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
380
413
|
}
|
|
381
414
|
}
|
|
382
415
|
if (traceId) {
|
|
@@ -389,13 +422,30 @@ class RabbitMq {
|
|
|
389
422
|
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
390
423
|
if (!shouldConsume) {
|
|
391
424
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
392
|
-
return this.ack(
|
|
425
|
+
return this.ack(confirmChannel, msg)(msg);
|
|
393
426
|
}
|
|
427
|
+
let messageAcked = false;
|
|
428
|
+
// setting the localAck function to be used in the callback
|
|
429
|
+
const localAck = async () => {
|
|
430
|
+
if (messageAcked) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
messageAcked = true;
|
|
434
|
+
return this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
435
|
+
};
|
|
436
|
+
const localNack = async (_, nackOptions = {}) => {
|
|
437
|
+
if (messageAcked) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
441
|
+
messageAcked = true;
|
|
442
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
443
|
+
};
|
|
394
444
|
try {
|
|
395
|
-
await callback(parsedMessage,
|
|
445
|
+
await callback(parsedMessage, localAck, localNack);
|
|
396
446
|
}
|
|
397
447
|
catch (e) {
|
|
398
|
-
await
|
|
448
|
+
await localNack(msg);
|
|
399
449
|
}
|
|
400
450
|
}, types_1.CONSUMER_DEFAULT_OPTIONS);
|
|
401
451
|
if (!consumerTag) {
|
|
@@ -403,7 +453,7 @@ class RabbitMq {
|
|
|
403
453
|
}
|
|
404
454
|
else {
|
|
405
455
|
logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
406
|
-
this.consumersTags.push([
|
|
456
|
+
this.consumersTags.push([confirmChannel, consumerTag]);
|
|
407
457
|
}
|
|
408
458
|
});
|
|
409
459
|
}
|
|
@@ -412,9 +462,10 @@ class RabbitMq {
|
|
|
412
462
|
RabbitMq.validateName('exchange', exchange);
|
|
413
463
|
RabbitMq.validateName('queue', queue);
|
|
414
464
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
415
|
-
|
|
465
|
+
await this.saveConsumer(queue, callback, options);
|
|
466
|
+
const channel = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}`, connectionPurpose: types_1.ConnectionPurpose.Consume });
|
|
416
467
|
return channel.addSetup(async (c) => {
|
|
417
|
-
const assertExchange = await utils_1.assertExchangeFanout(c, exchange);
|
|
468
|
+
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
418
469
|
await c.assertQueue(queue);
|
|
419
470
|
this.exchanges[exchange] = assertExchange;
|
|
420
471
|
await c.prefetch(limit, true);
|
|
@@ -425,36 +476,64 @@ class RabbitMq {
|
|
|
425
476
|
});
|
|
426
477
|
}
|
|
427
478
|
async publish(exchange, content, customHeaders) {
|
|
428
|
-
return utils_1.wrapSetImmediate(async () => {
|
|
479
|
+
return (0, utils_1.wrapSetImmediate)(async () => {
|
|
429
480
|
RabbitMq.validateName('exchange', exchange);
|
|
430
|
-
const channel = await this.assertChannel();
|
|
431
|
-
await this.assertExchange(exchange);
|
|
481
|
+
const channel = await this.assertChannel({ connectionPurpose: types_1.ConnectionPurpose.Publish });
|
|
482
|
+
await this.assertExchange(exchange, { connectionPurpose: types_1.ConnectionPurpose.Publish });
|
|
432
483
|
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
433
484
|
});
|
|
434
485
|
}
|
|
435
|
-
async sendToQueue(queue, content, options, customHeaders
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
486
|
+
async sendToQueue(queue, content, options, customHeaders) {
|
|
487
|
+
try {
|
|
488
|
+
await this.assertChannel({ connectionPurpose: types_1.ConnectionPurpose.Publish });
|
|
489
|
+
}
|
|
490
|
+
catch (e) {
|
|
491
|
+
logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
492
|
+
throw e;
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
RabbitMq.validateName('queue', queue);
|
|
496
|
+
await this.assertQueue(queue, types_1.ConnectionPurpose.Publish, options);
|
|
497
|
+
}
|
|
498
|
+
catch (e) {
|
|
499
|
+
logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
500
|
+
throw e;
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
504
|
+
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
505
|
+
return res;
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
const isConnected = await this.isConnected(types_1.ConnectionPurpose.Publish);
|
|
509
|
+
logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
510
|
+
throw e;
|
|
452
511
|
}
|
|
453
|
-
return utils_1.wrapSetImmediate(callback);
|
|
454
512
|
}
|
|
455
|
-
async isConnected() {
|
|
456
|
-
const connection = await this.getConnection();
|
|
457
|
-
|
|
513
|
+
async isConnected(connectionPurpose) {
|
|
514
|
+
const connection = await this.getConnection(connectionPurpose);
|
|
515
|
+
if (!connection) {
|
|
516
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
const isConnected = connection.isConnected();
|
|
520
|
+
if (!isConnected) {
|
|
521
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
525
|
+
try {
|
|
526
|
+
await Promise.all([
|
|
527
|
+
channel.waitForConnect(),
|
|
528
|
+
...this.consumers.map((c) => channel.checkQueue(c.queue)),
|
|
529
|
+
]);
|
|
530
|
+
}
|
|
531
|
+
catch (e) {
|
|
532
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
logger_1.default.info('rabbit: isConnected - true');
|
|
536
|
+
return true;
|
|
458
537
|
}
|
|
459
538
|
async gracefulShutdown(signal) {
|
|
460
539
|
const tagsNumber = this.consumersTags.length;
|
package/dist/lib/redis.d.ts
CHANGED