@autofleet/rabbit 3.1.2 → 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 +29 -18
- package/dist/index.js +251 -169
- 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 +14 -14
- package/src/index.ts +257 -155
- package/src/lib/types.ts +16 -1
- package/src/logger.ts +1 -1
- package/tsconfig.json +5 -2
package/src/index.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/* eslint-disable no-empty,consistent-return,no-async-promise-executor,@typescript-eslint/no-unused-vars */
|
|
2
2
|
|
|
3
|
-
import { EventEmitter } from 'events';
|
|
3
|
+
import { EventEmitter, once } from 'events';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import moment from 'moment';
|
|
6
6
|
import RedisLock from 'redis-lock';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AmqpConnectionManager, ChannelWrapper, connect, CreateChannelOpts,
|
|
9
|
+
} from 'amqp-connection-manager';
|
|
8
10
|
import {
|
|
9
11
|
ConfirmChannel, ConsumeMessage, Options, Replies,
|
|
10
12
|
} from 'amqplib';
|
|
11
13
|
import {
|
|
12
14
|
getCurrentPayload, newTrace, traceTypes, createOrSetRabbitTrace, outbreak,
|
|
13
15
|
} from '@autofleet/zehut';
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
14
17
|
import logger from './logger';
|
|
15
18
|
import RabbitError from './lib/rabbitError';
|
|
16
19
|
import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
@@ -32,12 +35,15 @@ import {
|
|
|
32
35
|
CustomMessageHeaders,
|
|
33
36
|
QueuesCache,
|
|
34
37
|
RedisLockType,
|
|
35
|
-
ExchangesCache, CONSUMER_DEFAULT_OPTIONS,
|
|
38
|
+
ExchangesCache, CONSUMER_DEFAULT_OPTIONS, QueueSetupPromisesDictionary,
|
|
39
|
+
ConnectionPurpose,
|
|
40
|
+
ConnectionData,
|
|
36
41
|
} from './lib/types';
|
|
37
42
|
|
|
38
43
|
// const debug = nodeDebug('af-rabbitmq')
|
|
39
|
-
const debug = logger.
|
|
44
|
+
const debug = logger.debug.bind(logger);
|
|
40
45
|
|
|
46
|
+
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
41
47
|
export interface IAfRabbitMq {
|
|
42
48
|
ack: any;
|
|
43
49
|
nack: any;
|
|
@@ -51,6 +57,10 @@ export interface IAfRabbitMq {
|
|
|
51
57
|
redisClient?: any;
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
interface NackOptions {
|
|
61
|
+
skipRetry?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
export interface AfRabbitOptions {
|
|
55
65
|
disableReconnect?: boolean;
|
|
56
66
|
|
|
@@ -65,16 +75,21 @@ export interface AfRabbitOptions {
|
|
|
65
75
|
* @default false
|
|
66
76
|
*/
|
|
67
77
|
dontRetryAssert?: boolean;
|
|
78
|
+
|
|
79
|
+
rabbitHost?: string;
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
type newChannelOpts = {
|
|
71
83
|
name?: string;
|
|
72
84
|
onClose?: null | ((args: any | null) => void);
|
|
85
|
+
options?: CreateChannelOpts | undefined;
|
|
86
|
+
connectionPurpose: ConnectionPurpose,
|
|
73
87
|
};
|
|
74
88
|
|
|
75
89
|
type assertChannelOpts = {
|
|
76
90
|
channelName?: string;
|
|
77
91
|
force?: boolean;
|
|
92
|
+
connectionPurpose: ConnectionPurpose;
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
type AfConsumer = {
|
|
@@ -83,16 +98,10 @@ type AfConsumer = {
|
|
|
83
98
|
options: ConsumeOptions | undefined;
|
|
84
99
|
}
|
|
85
100
|
|
|
86
|
-
const USERNAME: string = process.env.RABBITMQ_USERNAME || 'guest';
|
|
87
|
-
|
|
88
|
-
const PASSWORD: string = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
89
|
-
|
|
90
|
-
const HOST: string = process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
91
|
-
|
|
92
101
|
const HEARTBEAT = '60';
|
|
93
102
|
|
|
94
103
|
class RabbitMq implements IAfRabbitMq {
|
|
95
|
-
static parseMsg(msg: any)
|
|
104
|
+
static parseMsg(msg: any): any {
|
|
96
105
|
let { content } = msg;
|
|
97
106
|
content = content.toString();
|
|
98
107
|
|
|
@@ -106,7 +115,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
106
115
|
};
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
static validateName(type: string, name: string) {
|
|
118
|
+
static validateName(type: string, name: string): void {
|
|
110
119
|
if (!name || name === '') {
|
|
111
120
|
throw new RabbitError(`error while using ${type} with no name`);
|
|
112
121
|
}
|
|
@@ -119,6 +128,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
119
128
|
const outbreakTrace = outbreak.getCurrentContext();
|
|
120
129
|
return {
|
|
121
130
|
timestamp: moment().unix(),
|
|
131
|
+
timeout: PUBLISH_TIMEOUT,
|
|
122
132
|
headers: {
|
|
123
133
|
creationTimestamp: moment().valueOf(),
|
|
124
134
|
...customHeaders,
|
|
@@ -128,28 +138,29 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
128
138
|
};
|
|
129
139
|
}
|
|
130
140
|
|
|
131
|
-
// PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
|
|
132
|
-
|
|
133
|
-
// PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
|
|
134
|
-
|
|
135
141
|
DISCONNECT_MSG = 'rabbit: connection disconnect';
|
|
136
142
|
|
|
137
143
|
RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
138
144
|
|
|
139
145
|
channel: ChannelWrapper | null;
|
|
140
146
|
|
|
141
|
-
|
|
147
|
+
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
142
148
|
|
|
143
|
-
|
|
149
|
+
blockReconnect: boolean | null | undefined;
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
connectionsMap: {
|
|
152
|
+
[ConnectionPurpose.Consume]: ConnectionData;
|
|
153
|
+
[ConnectionPurpose.Publish]: ConnectionData;
|
|
154
|
+
};
|
|
146
155
|
|
|
147
|
-
|
|
156
|
+
em: EventEmitter;
|
|
148
157
|
|
|
149
158
|
exchanges: ExchangesCache;
|
|
150
159
|
|
|
151
160
|
queues: QueuesCache;
|
|
152
161
|
|
|
162
|
+
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
163
|
+
|
|
153
164
|
options: AfRabbitOptions | undefined;
|
|
154
165
|
|
|
155
166
|
redisClient: any;
|
|
@@ -164,10 +175,15 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
164
175
|
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
|
|
165
176
|
this.em = new EventEmitter();
|
|
166
177
|
this.channel = null;
|
|
167
|
-
this.
|
|
168
|
-
this.
|
|
178
|
+
this.publishChannelSetupPromise = null;
|
|
179
|
+
this.connectionsMap = {
|
|
180
|
+
[ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
|
|
181
|
+
[ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
|
|
182
|
+
};
|
|
169
183
|
this.exchanges = {};
|
|
170
184
|
this.queues = {};
|
|
185
|
+
this.queueSetupPromises = {};
|
|
186
|
+
this.consumers = [];
|
|
171
187
|
this.options = options;
|
|
172
188
|
this.redisClient = redisConfig && getRedisInstance(redisConfig);
|
|
173
189
|
if (this.redisClient) {
|
|
@@ -199,8 +215,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
199
215
|
return false;
|
|
200
216
|
}
|
|
201
217
|
|
|
202
|
-
public ack = (channel:
|
|
218
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
203
219
|
if (msg) {
|
|
220
|
+
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
204
221
|
await channel.ack(msg);
|
|
205
222
|
const { properties: { headers } } = msg;
|
|
206
223
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -214,7 +231,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
214
231
|
}
|
|
215
232
|
|
|
216
233
|
public nack = (
|
|
217
|
-
channel:
|
|
234
|
+
channel: ConfirmChannel,
|
|
218
235
|
queue: string,
|
|
219
236
|
options: any,
|
|
220
237
|
deadQueueOptions: Options.AssertQueue,
|
|
@@ -224,8 +241,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
224
241
|
userMsg: ConsumeMessageOrNull,
|
|
225
242
|
{
|
|
226
243
|
skipRetry = false,
|
|
227
|
-
}:
|
|
228
|
-
)
|
|
244
|
+
}: NackOptions = {},
|
|
245
|
+
): Promise<any> => {
|
|
229
246
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
230
247
|
if (channel && msg) {
|
|
231
248
|
if (
|
|
@@ -250,44 +267,68 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
250
267
|
: 1,
|
|
251
268
|
});
|
|
252
269
|
}
|
|
270
|
+
debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
|
|
253
271
|
await channel.ack(msg);
|
|
254
272
|
} else {
|
|
255
273
|
logger.error('no channel or msg', {
|
|
256
|
-
channel: channel ? channel.name : '',
|
|
257
274
|
msg,
|
|
258
275
|
});
|
|
259
276
|
}
|
|
260
277
|
}
|
|
261
278
|
|
|
262
|
-
async getConnection() {
|
|
263
|
-
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
279
|
+
async getConnection(connectionPurpose: ConnectionPurpose) {
|
|
280
|
+
return new Promise<AmqpConnectionManager | undefined | null>(async (resolve, reject) => {
|
|
281
|
+
let { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
|
|
264
282
|
if (this.blockReconnect) {
|
|
265
283
|
debug('rabbit: block reconnect');
|
|
266
284
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
267
285
|
// @ts-ignore
|
|
268
286
|
return resolve();
|
|
269
287
|
}
|
|
270
|
-
if (
|
|
271
|
-
if (this.options?.disableReconnect ||
|
|
288
|
+
if (connection !== null) {
|
|
289
|
+
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
272
290
|
debug('rabbit: connection - is connected');
|
|
273
291
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
274
292
|
// @ts-ignore
|
|
275
|
-
return resolve(
|
|
293
|
+
return resolve(connection);
|
|
276
294
|
}
|
|
277
295
|
debug('rabbit: connection - reconnecting');
|
|
278
296
|
}
|
|
279
|
-
if (
|
|
297
|
+
if (creatingConnection) {
|
|
280
298
|
debug('rabbit: creating connection emi');
|
|
281
299
|
this.em.once(CONNECTION_CREATED_CONST, resolve);
|
|
282
300
|
this.em.once(CONNECTION_FAILED_CONST, reject);
|
|
283
301
|
return;
|
|
284
302
|
}
|
|
285
|
-
this.creatingConnection = true;
|
|
303
|
+
this.connectionsMap[connectionPurpose].creatingConnection = true;
|
|
286
304
|
let isResolved = false;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
305
|
+
|
|
306
|
+
// It is import to use it as a function and not as a variable
|
|
307
|
+
// because of k8s changes the env variables
|
|
308
|
+
// and we want to use the new values
|
|
309
|
+
const findServers = () => {
|
|
310
|
+
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
311
|
+
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
312
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
313
|
+
|
|
314
|
+
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
315
|
+
|
|
316
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const defaultUrls = findServers();
|
|
320
|
+
const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
321
|
+
findServers,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (!newConnection) {
|
|
325
|
+
logger.error('rabbit: couldnt create a connection');
|
|
326
|
+
return resolve(connection);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.connectionsMap[connectionPurpose].connection = newConnection;
|
|
330
|
+
|
|
331
|
+
newConnection.on('error', (err) => {
|
|
291
332
|
logger.error('rabbit: connection error', { err });
|
|
292
333
|
if (!isResolved) {
|
|
293
334
|
isResolved = true;
|
|
@@ -296,7 +337,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
296
337
|
}
|
|
297
338
|
});
|
|
298
339
|
|
|
299
|
-
|
|
340
|
+
newConnection.on('connectFailed', (err) => {
|
|
341
|
+
this.consumersTags = [];
|
|
300
342
|
logger.error('rabbit: connection connectFailed', { err });
|
|
301
343
|
if (!isResolved) {
|
|
302
344
|
isResolved = true;
|
|
@@ -305,12 +347,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
305
347
|
}
|
|
306
348
|
});
|
|
307
349
|
|
|
308
|
-
|
|
350
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
351
|
+
this.consumersTags = [];
|
|
309
352
|
debug('rabbit: connection closed');
|
|
310
|
-
this.exchanges = {};
|
|
311
|
-
this.queues = {};
|
|
312
|
-
this.connection = null;
|
|
313
|
-
this.channel = null;
|
|
314
353
|
if (this.options?.disableReconnect) {
|
|
315
354
|
logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
316
355
|
this.blockReconnect = true;
|
|
@@ -318,66 +357,68 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
318
357
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
319
358
|
}
|
|
320
359
|
});
|
|
321
|
-
|
|
360
|
+
newConnection.once('connect', async () => {
|
|
322
361
|
debug('rabbit: connection established');
|
|
323
|
-
|
|
324
|
-
this.
|
|
325
|
-
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
362
|
+
this.connectionsMap[connectionPurpose].creatingConnection = false;
|
|
363
|
+
this.em.emit(CONNECTION_CREATED_CONST, newConnection);
|
|
326
364
|
isResolved = true;
|
|
327
|
-
resolve(
|
|
365
|
+
resolve(newConnection);
|
|
328
366
|
});
|
|
329
367
|
});
|
|
330
368
|
}
|
|
331
369
|
|
|
332
|
-
async getNewChannel({
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
channel.once('connect', () => {
|
|
352
|
-
debug(`rabbit: channel ${name} CONNECTED`);
|
|
353
|
-
isResolved = true;
|
|
354
|
-
resolve(channel);
|
|
355
|
-
});
|
|
370
|
+
async getNewChannel({
|
|
371
|
+
name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
|
|
372
|
+
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
373
|
+
let connection!: AmqpConnectionManager | undefined | null;
|
|
374
|
+
try {
|
|
375
|
+
connection = await this.getConnection(connectionPurpose);
|
|
376
|
+
} catch (e) {
|
|
377
|
+
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
378
|
+
throw e;
|
|
379
|
+
}
|
|
380
|
+
if (!connection) {
|
|
381
|
+
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
382
|
+
}
|
|
383
|
+
const channel = connection.createChannel({ ...options });
|
|
384
|
+
once(channel, 'close').then((args) => {
|
|
385
|
+
logger.error(`rabbit: channel ${name} closed`);
|
|
386
|
+
onClose?.(args);
|
|
356
387
|
});
|
|
388
|
+
try {
|
|
389
|
+
await once(channel, 'connect');
|
|
390
|
+
debug(`rabbit: channel ${name} CONNECTED`);
|
|
391
|
+
return channel;
|
|
392
|
+
} catch (err) {
|
|
393
|
+
logger.error(`rabbit: channel error ${name} error`, { err });
|
|
394
|
+
throw err;
|
|
395
|
+
}
|
|
357
396
|
}
|
|
358
397
|
|
|
359
|
-
async assertChannel({ force = false }
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
398
|
+
async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
|
|
399
|
+
if (!this.publishChannelSetupPromise) {
|
|
400
|
+
this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
401
|
+
if (this.channel && !force) {
|
|
402
|
+
return resolve(this.channel);
|
|
403
|
+
}
|
|
364
404
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
}
|
|
405
|
+
try {
|
|
406
|
+
const channel = await this.getNewChannel({ connectionPurpose });
|
|
407
|
+
channel.on('error', (err) => {
|
|
408
|
+
logger.error('rabbit: channel error', { err });
|
|
409
|
+
});
|
|
410
|
+
this.channel = channel;
|
|
411
|
+
resolve(channel);
|
|
412
|
+
} catch (e) {
|
|
413
|
+
reject(e);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return this.publishChannelSetupPromise;
|
|
377
418
|
}
|
|
378
419
|
|
|
379
420
|
async assertExchange(exchangeName: string, options?: any) {
|
|
380
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
421
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
|
|
381
422
|
if (this.exchanges[exchangeName]) {
|
|
382
423
|
return this.exchanges[exchangeName];
|
|
383
424
|
}
|
|
@@ -386,36 +427,36 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
386
427
|
return exchange;
|
|
387
428
|
}
|
|
388
429
|
|
|
389
|
-
async getQueueLength(queue: string) {
|
|
430
|
+
async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
|
|
390
431
|
RabbitMq.validateName('queue', queue);
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
432
|
+
let { connection } = this.connectionsMap[connectionPurpose];
|
|
433
|
+
const { channel } = this;
|
|
434
|
+
if (!channel) {
|
|
435
|
+
throw new Error('channel is not defined');
|
|
436
|
+
}
|
|
437
|
+
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
438
|
+
return channel?.checkQueue(queue);
|
|
394
439
|
}
|
|
395
440
|
|
|
396
|
-
private async deleteQueue(queue: string) {
|
|
441
|
+
private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
|
|
397
442
|
RabbitMq.validateName('queue', queue);
|
|
398
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
443
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
399
444
|
logger.info('rabbit: deleting queue', { queue });
|
|
400
445
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
401
446
|
debug('queue deleted', deleteQueueRes);
|
|
402
447
|
return deleteQueueRes;
|
|
403
448
|
}
|
|
404
449
|
|
|
405
|
-
async bindQueue(queue: string, exchange: string) {
|
|
406
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
450
|
+
async bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose) {
|
|
451
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
407
452
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
408
453
|
return channel.bindQueue(queue, exchange, '');
|
|
409
454
|
}
|
|
410
455
|
|
|
411
|
-
async
|
|
412
|
-
let queue: Replies.AssertQueue
|
|
413
|
-
RabbitMq.validateName('queue', queueName);
|
|
414
|
-
if (this.queues[queueName]) {
|
|
415
|
-
return this.queues[queueName];
|
|
416
|
-
}
|
|
456
|
+
async setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
457
|
+
let queue: Replies.AssertQueue;
|
|
417
458
|
try {
|
|
418
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
459
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
419
460
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
420
461
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
|
|
421
462
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
@@ -424,12 +465,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
424
465
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
425
466
|
if (!this.options?.dontRetryAssert) {
|
|
426
467
|
debug('retrying assertQueue', { queueName });
|
|
427
|
-
const channel = await this.assertChannel({ force: true });
|
|
428
|
-
await this.deleteQueue(queueName);
|
|
468
|
+
const channel = await this.assertChannel({ force: true, connectionPurpose });
|
|
469
|
+
await this.deleteQueue(queueName, connectionPurpose);
|
|
429
470
|
|
|
430
|
-
debug('
|
|
471
|
+
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
431
472
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
|
|
432
|
-
debug('
|
|
473
|
+
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
433
474
|
queue = await channel.assertQueue(queueName, options);
|
|
434
475
|
} else {
|
|
435
476
|
throw e;
|
|
@@ -440,29 +481,35 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
440
481
|
return queue;
|
|
441
482
|
}
|
|
442
483
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
484
|
+
async assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue) {
|
|
485
|
+
RabbitMq.validateName('queue', queueName);
|
|
486
|
+
if (this.queues[queueName]) {
|
|
487
|
+
delete this.queueSetupPromises[queueName];
|
|
488
|
+
return this.queues[queueName];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (this.queueSetupPromises[queueName]) {
|
|
492
|
+
return this.queueSetupPromises[queueName];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
|
|
496
|
+
return this.queueSetupPromises[queueName];
|
|
449
497
|
}
|
|
450
498
|
|
|
451
|
-
private
|
|
452
|
-
|
|
453
|
-
if (
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
499
|
+
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
500
|
+
const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
|
|
501
|
+
if (!isConsumerExist) {
|
|
502
|
+
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
503
|
+
this.consumers.push({
|
|
504
|
+
queue,
|
|
505
|
+
callback,
|
|
506
|
+
options,
|
|
507
|
+
});
|
|
458
508
|
}
|
|
459
509
|
}
|
|
460
510
|
|
|
461
511
|
async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
462
|
-
|
|
463
|
-
this.consumeFromRabbit(queue, callback, options);
|
|
464
|
-
}
|
|
465
|
-
return this.saveConsumer(queue, callback, options);
|
|
512
|
+
await this.consumeFromRabbit(queue, callback, options);
|
|
466
513
|
}
|
|
467
514
|
|
|
468
515
|
private async lockRedisIfNeeded(msg: any, options: any) {
|
|
@@ -488,6 +535,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
488
535
|
private async consumeFromRabbit(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
489
536
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
490
537
|
RabbitMq.validateName('queue', queue);
|
|
538
|
+
this.saveConsumer(queue, callback, options);
|
|
539
|
+
const uniqueId = randomUUID();
|
|
491
540
|
const {
|
|
492
541
|
limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
|
|
493
542
|
} = optionsWithDefaults;
|
|
@@ -497,11 +546,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
497
546
|
}
|
|
498
547
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
499
548
|
}
|
|
500
|
-
const channel
|
|
501
|
-
return channel.addSetup(async (
|
|
502
|
-
await
|
|
503
|
-
await
|
|
504
|
-
const { consumerTag } = await
|
|
549
|
+
const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
|
|
550
|
+
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
551
|
+
await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
|
|
552
|
+
await confirmChannel.prefetch(limit, true);
|
|
553
|
+
const { consumerTag } = await confirmChannel.consume(
|
|
505
554
|
queue,
|
|
506
555
|
async (msg: ConsumeMessageOrNull) => {
|
|
507
556
|
if (!msg) {
|
|
@@ -525,7 +574,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
525
574
|
]);
|
|
526
575
|
} catch (e) {
|
|
527
576
|
logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
528
|
-
|
|
577
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
529
578
|
}
|
|
530
579
|
}
|
|
531
580
|
|
|
@@ -540,16 +589,37 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
540
589
|
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
541
590
|
if (!shouldConsume) {
|
|
542
591
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
543
|
-
return this.ack(
|
|
592
|
+
return this.ack(confirmChannel, msg)(msg);
|
|
544
593
|
}
|
|
594
|
+
|
|
595
|
+
let messageAcked = false;
|
|
596
|
+
// setting the localAck function to be used in the callback
|
|
597
|
+
|
|
598
|
+
const localAck = async () => {
|
|
599
|
+
if (messageAcked) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
messageAcked = true;
|
|
603
|
+
return this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const localNack = async (_: ConsumeMessageOrNull, nackOptions: NackOptions = {}) => {
|
|
607
|
+
if (messageAcked) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
611
|
+
messageAcked = true;
|
|
612
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
613
|
+
};
|
|
614
|
+
|
|
545
615
|
try {
|
|
546
616
|
await callback(
|
|
547
617
|
parsedMessage,
|
|
548
|
-
|
|
549
|
-
|
|
618
|
+
localAck,
|
|
619
|
+
localNack,
|
|
550
620
|
);
|
|
551
621
|
} catch (e) {
|
|
552
|
-
await
|
|
622
|
+
await localNack(msg);
|
|
553
623
|
}
|
|
554
624
|
}, CONSUMER_DEFAULT_OPTIONS,
|
|
555
625
|
);
|
|
@@ -557,17 +627,18 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
557
627
|
logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
558
628
|
} else {
|
|
559
629
|
logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
560
|
-
this.consumersTags.push([
|
|
630
|
+
this.consumersTags.push([confirmChannel, consumerTag]);
|
|
561
631
|
}
|
|
562
632
|
});
|
|
563
633
|
}
|
|
564
634
|
|
|
565
|
-
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions)
|
|
635
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
566
636
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
567
637
|
RabbitMq.validateName('exchange', exchange);
|
|
568
638
|
RabbitMq.validateName('queue', queue);
|
|
569
639
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
570
|
-
|
|
640
|
+
await this.saveConsumer(queue, callback, options);
|
|
641
|
+
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}`, connectionPurpose: ConnectionPurpose.Consume });
|
|
571
642
|
|
|
572
643
|
return channel.addSetup(async (c: ConfirmChannel) => {
|
|
573
644
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
@@ -585,11 +656,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
585
656
|
});
|
|
586
657
|
}
|
|
587
658
|
|
|
588
|
-
async publish(exchange: string, content: any, customHeaders?: any)
|
|
659
|
+
async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
|
|
589
660
|
return wrapSetImmediate(async () => {
|
|
590
661
|
RabbitMq.validateName('exchange', exchange);
|
|
591
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
592
|
-
await this.assertExchange(exchange);
|
|
662
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
663
|
+
await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
|
|
593
664
|
await channel.publish(exchange, '',
|
|
594
665
|
Buffer.from(JSON.stringify(content)),
|
|
595
666
|
RabbitMq.getPublishOptions(customHeaders));
|
|
@@ -601,30 +672,61 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
601
672
|
content: any,
|
|
602
673
|
options?: any,
|
|
603
674
|
customHeaders?: any,
|
|
604
|
-
isBlocking?: boolean,
|
|
605
675
|
): Promise<boolean | undefined> {
|
|
606
|
-
|
|
676
|
+
try {
|
|
677
|
+
await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
678
|
+
} catch (e) {
|
|
679
|
+
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
680
|
+
throw e;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
try {
|
|
607
684
|
RabbitMq.validateName('queue', queue);
|
|
608
|
-
await this.
|
|
609
|
-
|
|
685
|
+
await this.assertQueue(queue, ConnectionPurpose.Publish, options);
|
|
686
|
+
} catch (e) {
|
|
687
|
+
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
688
|
+
throw e;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
try {
|
|
610
692
|
const res = await this.channel?.sendToQueue(queue,
|
|
611
693
|
Buffer.from(JSON.stringify(content)),
|
|
612
694
|
RabbitMq.getPublishOptions(customHeaders));
|
|
613
695
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
614
696
|
return res;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
697
|
+
} catch (e) {
|
|
698
|
+
const isConnected = await this.isConnected(ConnectionPurpose.Publish);
|
|
699
|
+
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
700
|
+
throw e;
|
|
618
701
|
}
|
|
619
|
-
return wrapSetImmediate(callback);
|
|
620
702
|
}
|
|
621
703
|
|
|
622
|
-
async isConnected()
|
|
623
|
-
const connection = await this.getConnection();
|
|
624
|
-
|
|
704
|
+
async isConnected(connectionPurpose: ConnectionPurpose): Promise<boolean> {
|
|
705
|
+
const connection = await this.getConnection(connectionPurpose);
|
|
706
|
+
if (!connection) {
|
|
707
|
+
logger.error('rabbit: isConnected - false');
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
const isConnected = connection.isConnected();
|
|
711
|
+
if (!isConnected) {
|
|
712
|
+
logger.error('rabbit: isConnected - false');
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
const channel: any = await this.assertChannel({ connectionPurpose });
|
|
716
|
+
try {
|
|
717
|
+
await Promise.all([
|
|
718
|
+
channel.waitForConnect(),
|
|
719
|
+
...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
|
720
|
+
]);
|
|
721
|
+
} catch (e) {
|
|
722
|
+
logger.error('rabbit: isConnected - false');
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
logger.info('rabbit: isConnected - true');
|
|
726
|
+
return true;
|
|
625
727
|
}
|
|
626
728
|
|
|
627
|
-
async gracefulShutdown(signal: string)
|
|
729
|
+
async gracefulShutdown(signal: string): Promise<void> {
|
|
628
730
|
const tagsNumber = this.consumersTags.length;
|
|
629
731
|
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
630
732
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|