@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/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,11 +35,13 @@ 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
|
|
|
41
46
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
42
47
|
export interface IAfRabbitMq {
|
|
@@ -52,6 +57,10 @@ export interface IAfRabbitMq {
|
|
|
52
57
|
redisClient?: any;
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
interface NackOptions {
|
|
61
|
+
skipRetry?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
export interface AfRabbitOptions {
|
|
56
65
|
disableReconnect?: boolean;
|
|
57
66
|
|
|
@@ -73,11 +82,14 @@ export interface AfRabbitOptions {
|
|
|
73
82
|
type newChannelOpts = {
|
|
74
83
|
name?: string;
|
|
75
84
|
onClose?: null | ((args: any | null) => void);
|
|
85
|
+
options?: CreateChannelOpts | undefined;
|
|
86
|
+
connectionPurpose: ConnectionPurpose,
|
|
76
87
|
};
|
|
77
88
|
|
|
78
89
|
type assertChannelOpts = {
|
|
79
90
|
channelName?: string;
|
|
80
91
|
force?: boolean;
|
|
92
|
+
connectionPurpose: ConnectionPurpose;
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
type AfConsumer = {
|
|
@@ -89,7 +101,7 @@ type AfConsumer = {
|
|
|
89
101
|
const HEARTBEAT = '60';
|
|
90
102
|
|
|
91
103
|
class RabbitMq implements IAfRabbitMq {
|
|
92
|
-
static parseMsg(msg: any)
|
|
104
|
+
static parseMsg(msg: any): any {
|
|
93
105
|
let { content } = msg;
|
|
94
106
|
content = content.toString();
|
|
95
107
|
|
|
@@ -103,7 +115,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
103
115
|
};
|
|
104
116
|
}
|
|
105
117
|
|
|
106
|
-
static validateName(type: string, name: string) {
|
|
118
|
+
static validateName(type: string, name: string): void {
|
|
107
119
|
if (!name || name === '') {
|
|
108
120
|
throw new RabbitError(`error while using ${type} with no name`);
|
|
109
121
|
}
|
|
@@ -132,48 +144,46 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
132
144
|
|
|
133
145
|
channel: ChannelWrapper | null;
|
|
134
146
|
|
|
135
|
-
|
|
147
|
+
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
136
148
|
|
|
137
|
-
|
|
149
|
+
blockReconnect: boolean | null | undefined;
|
|
138
150
|
|
|
139
|
-
|
|
151
|
+
connectionsMap: {
|
|
152
|
+
[ConnectionPurpose.Consume]: ConnectionData;
|
|
153
|
+
[ConnectionPurpose.Publish]: ConnectionData;
|
|
154
|
+
};
|
|
140
155
|
|
|
141
|
-
|
|
156
|
+
em: EventEmitter;
|
|
142
157
|
|
|
143
158
|
exchanges: ExchangesCache;
|
|
144
159
|
|
|
145
160
|
queues: QueuesCache;
|
|
146
161
|
|
|
162
|
+
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
163
|
+
|
|
147
164
|
options: AfRabbitOptions | undefined;
|
|
148
165
|
|
|
149
166
|
redisClient: any;
|
|
150
167
|
|
|
151
168
|
redisLock?: RedisLockType;
|
|
152
169
|
|
|
153
|
-
userName: string;
|
|
154
|
-
|
|
155
|
-
password: string;
|
|
156
|
-
|
|
157
|
-
host: string;
|
|
158
|
-
|
|
159
170
|
/** Array of consumers tags used for canceling consumption */
|
|
160
171
|
consumersTags: Array<[ConfirmChannel, string]>;
|
|
161
172
|
|
|
162
173
|
private consumers: Array<AfConsumer> = [];
|
|
163
174
|
|
|
164
175
|
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
|
|
165
|
-
this.userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
166
|
-
|
|
167
|
-
this.password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
168
|
-
|
|
169
|
-
this.host = options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
170
|
-
|
|
171
176
|
this.em = new EventEmitter();
|
|
172
177
|
this.channel = null;
|
|
173
|
-
this.
|
|
174
|
-
this.
|
|
178
|
+
this.publishChannelSetupPromise = null;
|
|
179
|
+
this.connectionsMap = {
|
|
180
|
+
[ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
|
|
181
|
+
[ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
|
|
182
|
+
};
|
|
175
183
|
this.exchanges = {};
|
|
176
184
|
this.queues = {};
|
|
185
|
+
this.queueSetupPromises = {};
|
|
186
|
+
this.consumers = [];
|
|
177
187
|
this.options = options;
|
|
178
188
|
this.redisClient = redisConfig && getRedisInstance(redisConfig);
|
|
179
189
|
if (this.redisClient) {
|
|
@@ -205,8 +215,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
205
215
|
return false;
|
|
206
216
|
}
|
|
207
217
|
|
|
208
|
-
public ack = (channel:
|
|
218
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
209
219
|
if (msg) {
|
|
220
|
+
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
210
221
|
await channel.ack(msg);
|
|
211
222
|
const { properties: { headers } } = msg;
|
|
212
223
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -220,7 +231,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
220
231
|
}
|
|
221
232
|
|
|
222
233
|
public nack = (
|
|
223
|
-
channel:
|
|
234
|
+
channel: ConfirmChannel,
|
|
224
235
|
queue: string,
|
|
225
236
|
options: any,
|
|
226
237
|
deadQueueOptions: Options.AssertQueue,
|
|
@@ -230,8 +241,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
230
241
|
userMsg: ConsumeMessageOrNull,
|
|
231
242
|
{
|
|
232
243
|
skipRetry = false,
|
|
233
|
-
}:
|
|
234
|
-
)
|
|
244
|
+
}: NackOptions = {},
|
|
245
|
+
): Promise<any> => {
|
|
235
246
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
236
247
|
if (channel && msg) {
|
|
237
248
|
if (
|
|
@@ -256,44 +267,68 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
256
267
|
: 1,
|
|
257
268
|
});
|
|
258
269
|
}
|
|
270
|
+
debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
|
|
259
271
|
await channel.ack(msg);
|
|
260
272
|
} else {
|
|
261
273
|
logger.error('no channel or msg', {
|
|
262
|
-
channel: channel ? channel.name : '',
|
|
263
274
|
msg,
|
|
264
275
|
});
|
|
265
276
|
}
|
|
266
277
|
}
|
|
267
278
|
|
|
268
|
-
async getConnection() {
|
|
269
|
-
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];
|
|
270
282
|
if (this.blockReconnect) {
|
|
271
283
|
debug('rabbit: block reconnect');
|
|
272
284
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
273
285
|
// @ts-ignore
|
|
274
286
|
return resolve();
|
|
275
287
|
}
|
|
276
|
-
if (
|
|
277
|
-
if (this.options?.disableReconnect ||
|
|
288
|
+
if (connection !== null) {
|
|
289
|
+
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
278
290
|
debug('rabbit: connection - is connected');
|
|
279
291
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
280
292
|
// @ts-ignore
|
|
281
|
-
return resolve(
|
|
293
|
+
return resolve(connection);
|
|
282
294
|
}
|
|
283
295
|
debug('rabbit: connection - reconnecting');
|
|
284
296
|
}
|
|
285
|
-
if (
|
|
297
|
+
if (creatingConnection) {
|
|
286
298
|
debug('rabbit: creating connection emi');
|
|
287
299
|
this.em.once(CONNECTION_CREATED_CONST, resolve);
|
|
288
300
|
this.em.once(CONNECTION_FAILED_CONST, reject);
|
|
289
301
|
return;
|
|
290
302
|
}
|
|
291
|
-
this.creatingConnection = true;
|
|
303
|
+
this.connectionsMap[connectionPurpose].creatingConnection = true;
|
|
292
304
|
let isResolved = false;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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) => {
|
|
297
332
|
logger.error('rabbit: connection error', { err });
|
|
298
333
|
if (!isResolved) {
|
|
299
334
|
isResolved = true;
|
|
@@ -302,7 +337,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
302
337
|
}
|
|
303
338
|
});
|
|
304
339
|
|
|
305
|
-
|
|
340
|
+
newConnection.on('connectFailed', (err) => {
|
|
341
|
+
this.consumersTags = [];
|
|
306
342
|
logger.error('rabbit: connection connectFailed', { err });
|
|
307
343
|
if (!isResolved) {
|
|
308
344
|
isResolved = true;
|
|
@@ -311,10 +347,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
311
347
|
}
|
|
312
348
|
});
|
|
313
349
|
|
|
314
|
-
|
|
350
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
351
|
+
this.consumersTags = [];
|
|
315
352
|
debug('rabbit: connection closed');
|
|
316
|
-
this.exchanges = {};
|
|
317
|
-
this.queues = {};
|
|
318
353
|
if (this.options?.disableReconnect) {
|
|
319
354
|
logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
320
355
|
this.blockReconnect = true;
|
|
@@ -322,67 +357,68 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
322
357
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
323
358
|
}
|
|
324
359
|
});
|
|
325
|
-
|
|
360
|
+
newConnection.once('connect', async () => {
|
|
326
361
|
debug('rabbit: connection established');
|
|
327
|
-
|
|
328
|
-
this.
|
|
329
|
-
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
362
|
+
this.connectionsMap[connectionPurpose].creatingConnection = false;
|
|
363
|
+
this.em.emit(CONNECTION_CREATED_CONST, newConnection);
|
|
330
364
|
isResolved = true;
|
|
331
|
-
resolve(
|
|
365
|
+
resolve(newConnection);
|
|
332
366
|
});
|
|
333
367
|
});
|
|
334
368
|
}
|
|
335
369
|
|
|
336
|
-
async getNewChannel({
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
onClose(args);
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
channel.once('connect', () => {
|
|
357
|
-
debug(`rabbit: channel ${name} CONNECTED`);
|
|
358
|
-
isResolved = true;
|
|
359
|
-
resolve(channel);
|
|
360
|
-
});
|
|
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);
|
|
361
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
|
+
}
|
|
362
396
|
}
|
|
363
397
|
|
|
364
|
-
async assertChannel({ force = false }
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
+
}
|
|
369
404
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
}
|
|
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;
|
|
382
418
|
}
|
|
383
419
|
|
|
384
420
|
async assertExchange(exchangeName: string, options?: any) {
|
|
385
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
421
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
|
|
386
422
|
if (this.exchanges[exchangeName]) {
|
|
387
423
|
return this.exchanges[exchangeName];
|
|
388
424
|
}
|
|
@@ -391,36 +427,36 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
391
427
|
return exchange;
|
|
392
428
|
}
|
|
393
429
|
|
|
394
|
-
async getQueueLength(queue: string) {
|
|
430
|
+
async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
|
|
395
431
|
RabbitMq.validateName('queue', queue);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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);
|
|
399
439
|
}
|
|
400
440
|
|
|
401
|
-
private async deleteQueue(queue: string) {
|
|
441
|
+
private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
|
|
402
442
|
RabbitMq.validateName('queue', queue);
|
|
403
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
443
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
404
444
|
logger.info('rabbit: deleting queue', { queue });
|
|
405
445
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
406
446
|
debug('queue deleted', deleteQueueRes);
|
|
407
447
|
return deleteQueueRes;
|
|
408
448
|
}
|
|
409
449
|
|
|
410
|
-
async bindQueue(queue: string, exchange: string) {
|
|
411
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
450
|
+
async bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose) {
|
|
451
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
412
452
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
413
453
|
return channel.bindQueue(queue, exchange, '');
|
|
414
454
|
}
|
|
415
455
|
|
|
416
|
-
async
|
|
417
|
-
let queue: Replies.AssertQueue
|
|
418
|
-
RabbitMq.validateName('queue', queueName);
|
|
419
|
-
if (this.queues[queueName]) {
|
|
420
|
-
return this.queues[queueName];
|
|
421
|
-
}
|
|
456
|
+
async setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
457
|
+
let queue: Replies.AssertQueue;
|
|
422
458
|
try {
|
|
423
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
459
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
424
460
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
425
461
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
|
|
426
462
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
@@ -429,12 +465,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
429
465
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
430
466
|
if (!this.options?.dontRetryAssert) {
|
|
431
467
|
debug('retrying assertQueue', { queueName });
|
|
432
|
-
const channel = await this.assertChannel({ force: true });
|
|
433
|
-
await this.deleteQueue(queueName);
|
|
468
|
+
const channel = await this.assertChannel({ force: true, connectionPurpose });
|
|
469
|
+
await this.deleteQueue(queueName, connectionPurpose);
|
|
434
470
|
|
|
435
|
-
debug('
|
|
471
|
+
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
436
472
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
|
|
437
|
-
debug('
|
|
473
|
+
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
438
474
|
queue = await channel.assertQueue(queueName, options);
|
|
439
475
|
} else {
|
|
440
476
|
throw e;
|
|
@@ -445,29 +481,35 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
445
481
|
return queue;
|
|
446
482
|
}
|
|
447
483
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
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];
|
|
454
497
|
}
|
|
455
498
|
|
|
456
|
-
private
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
+
});
|
|
463
508
|
}
|
|
464
509
|
}
|
|
465
510
|
|
|
466
511
|
async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
467
|
-
|
|
468
|
-
this.consumeFromRabbit(queue, callback, options);
|
|
469
|
-
}
|
|
470
|
-
return this.saveConsumer(queue, callback, options);
|
|
512
|
+
await this.consumeFromRabbit(queue, callback, options);
|
|
471
513
|
}
|
|
472
514
|
|
|
473
515
|
private async lockRedisIfNeeded(msg: any, options: any) {
|
|
@@ -493,6 +535,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
493
535
|
private async consumeFromRabbit(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
494
536
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
495
537
|
RabbitMq.validateName('queue', queue);
|
|
538
|
+
this.saveConsumer(queue, callback, options);
|
|
539
|
+
const uniqueId = randomUUID();
|
|
496
540
|
const {
|
|
497
541
|
limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
|
|
498
542
|
} = optionsWithDefaults;
|
|
@@ -502,11 +546,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
502
546
|
}
|
|
503
547
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
504
548
|
}
|
|
505
|
-
const channel
|
|
506
|
-
return channel.addSetup(async (
|
|
507
|
-
await
|
|
508
|
-
await
|
|
509
|
-
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(
|
|
510
554
|
queue,
|
|
511
555
|
async (msg: ConsumeMessageOrNull) => {
|
|
512
556
|
if (!msg) {
|
|
@@ -530,7 +574,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
530
574
|
]);
|
|
531
575
|
} catch (e) {
|
|
532
576
|
logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
533
|
-
|
|
577
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
534
578
|
}
|
|
535
579
|
}
|
|
536
580
|
|
|
@@ -545,16 +589,37 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
545
589
|
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
546
590
|
if (!shouldConsume) {
|
|
547
591
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
548
|
-
return this.ack(
|
|
592
|
+
return this.ack(confirmChannel, msg)(msg);
|
|
549
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
|
+
|
|
550
615
|
try {
|
|
551
616
|
await callback(
|
|
552
617
|
parsedMessage,
|
|
553
|
-
|
|
554
|
-
|
|
618
|
+
localAck,
|
|
619
|
+
localNack,
|
|
555
620
|
);
|
|
556
621
|
} catch (e) {
|
|
557
|
-
await
|
|
622
|
+
await localNack(msg);
|
|
558
623
|
}
|
|
559
624
|
}, CONSUMER_DEFAULT_OPTIONS,
|
|
560
625
|
);
|
|
@@ -562,17 +627,18 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
562
627
|
logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
563
628
|
} else {
|
|
564
629
|
logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
565
|
-
this.consumersTags.push([
|
|
630
|
+
this.consumersTags.push([confirmChannel, consumerTag]);
|
|
566
631
|
}
|
|
567
632
|
});
|
|
568
633
|
}
|
|
569
634
|
|
|
570
|
-
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions)
|
|
635
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
571
636
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
572
637
|
RabbitMq.validateName('exchange', exchange);
|
|
573
638
|
RabbitMq.validateName('queue', queue);
|
|
574
639
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
575
|
-
|
|
640
|
+
await this.saveConsumer(queue, callback, options);
|
|
641
|
+
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}`, connectionPurpose: ConnectionPurpose.Consume });
|
|
576
642
|
|
|
577
643
|
return channel.addSetup(async (c: ConfirmChannel) => {
|
|
578
644
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
@@ -590,11 +656,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
590
656
|
});
|
|
591
657
|
}
|
|
592
658
|
|
|
593
|
-
async publish(exchange: string, content: any, customHeaders?: any)
|
|
659
|
+
async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
|
|
594
660
|
return wrapSetImmediate(async () => {
|
|
595
661
|
RabbitMq.validateName('exchange', exchange);
|
|
596
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
597
|
-
await this.assertExchange(exchange);
|
|
662
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
663
|
+
await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
|
|
598
664
|
await channel.publish(exchange, '',
|
|
599
665
|
Buffer.from(JSON.stringify(content)),
|
|
600
666
|
RabbitMq.getPublishOptions(customHeaders));
|
|
@@ -606,35 +672,61 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
606
672
|
content: any,
|
|
607
673
|
options?: any,
|
|
608
674
|
customHeaders?: any,
|
|
609
|
-
isBlocking?: boolean,
|
|
610
675
|
): Promise<boolean | undefined> {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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 {
|
|
684
|
+
RabbitMq.validateName('queue', queue);
|
|
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 {
|
|
692
|
+
const res = await this.channel?.sendToQueue(queue,
|
|
693
|
+
Buffer.from(JSON.stringify(content)),
|
|
694
|
+
RabbitMq.getPublishOptions(customHeaders));
|
|
695
|
+
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
696
|
+
return res;
|
|
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;
|
|
628
701
|
}
|
|
629
|
-
return wrapSetImmediate(callback);
|
|
630
702
|
}
|
|
631
703
|
|
|
632
|
-
async isConnected()
|
|
633
|
-
const connection = await this.getConnection();
|
|
634
|
-
|
|
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;
|
|
635
727
|
}
|
|
636
728
|
|
|
637
|
-
async gracefulShutdown(signal: string)
|
|
729
|
+
async gracefulShutdown(signal: string): Promise<void> {
|
|
638
730
|
const tagsNumber = this.consumersTags.length;
|
|
639
731
|
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
640
732
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|