@autofleet/rabbit 3.2.22-beta.5 → 3.2.22
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 +12 -14
- package/dist/index.js +105 -93
- package/dist/lib/consts.d.ts +1 -0
- package/dist/lib/consts.js +2 -1
- package/dist/lib/types.d.ts +3 -9
- package/dist/lib/types.js +1 -6
- package/dist/lib/utils.d.ts +2 -2
- package/package.json +1 -1
- package/src/index.ts +136 -118
- package/src/lib/consts.ts +1 -0
- package/src/lib/types.ts +4 -11
- package/src/lib/utils.ts +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { EventEmitter } from 'events';
|
|
|
3
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 { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary,
|
|
6
|
+
import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, AssertExchangePromisesDictionary } from './lib/types';
|
|
7
7
|
export interface IAfRabbitMq {
|
|
8
8
|
ack: any;
|
|
9
9
|
nack: any;
|
|
@@ -37,12 +37,10 @@ type newChannelOpts = {
|
|
|
37
37
|
name?: string;
|
|
38
38
|
onClose?: null | ((args: any | null) => void);
|
|
39
39
|
options?: CreateChannelOpts | undefined;
|
|
40
|
-
connectionPurpose?: ConnectionPurpose;
|
|
41
40
|
};
|
|
42
41
|
type assertChannelOpts = {
|
|
43
42
|
channelName?: string;
|
|
44
43
|
force?: boolean;
|
|
45
|
-
connectionPurpose?: ConnectionPurpose;
|
|
46
44
|
};
|
|
47
45
|
declare class RabbitMq implements IAfRabbitMq {
|
|
48
46
|
static parseMsg(msg: any): any;
|
|
@@ -62,14 +60,13 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
62
60
|
channel: ChannelWrapper | null;
|
|
63
61
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
64
62
|
blockReconnect: boolean | null | undefined;
|
|
65
|
-
|
|
66
|
-
[ConnectionPurpose.Consume]: ConnectionData;
|
|
67
|
-
[ConnectionPurpose.Publish]: ConnectionData;
|
|
68
|
-
};
|
|
63
|
+
connection: AmqpConnectionManager | null | undefined;
|
|
69
64
|
em: EventEmitter;
|
|
65
|
+
creatingConnection: boolean;
|
|
70
66
|
exchanges: ExchangesCache;
|
|
71
67
|
queues: QueuesCache;
|
|
72
68
|
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
69
|
+
assertExchangePromises: AssertExchangePromisesDictionary;
|
|
73
70
|
options: AfRabbitOptions | undefined;
|
|
74
71
|
redisClient: any;
|
|
75
72
|
redisLock?: RedisLockType;
|
|
@@ -80,15 +77,16 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
80
77
|
private shouldConsumeMessageByTimestamp;
|
|
81
78
|
ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
|
|
82
79
|
nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
|
|
83
|
-
getConnection(
|
|
84
|
-
getNewChannel({ name, onClose, options
|
|
85
|
-
assertChannel({ force
|
|
80
|
+
getConnection(): Promise<AmqpConnectionManager>;
|
|
81
|
+
getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
|
|
82
|
+
assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
|
|
86
83
|
assertExchange(exchangeName: string, options?: any): Promise<any>;
|
|
87
|
-
getQueueLength(queue: string
|
|
84
|
+
getQueueLength(queue: string): Promise<Replies.AssertQueue>;
|
|
88
85
|
private deleteQueue;
|
|
89
|
-
bindQueue(queue: string, exchange: string
|
|
90
|
-
setupQueue(queueName: string,
|
|
91
|
-
|
|
86
|
+
bindQueue(queue: string, exchange: string): Promise<void>;
|
|
87
|
+
setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
88
|
+
static shouldUseQuorum(queueName: string): boolean;
|
|
89
|
+
assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
|
|
92
90
|
private saveConsumer;
|
|
93
91
|
consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
|
|
94
92
|
private lockRedisIfNeeded;
|
package/dist/index.js
CHANGED
|
@@ -118,13 +118,12 @@ class RabbitMq {
|
|
|
118
118
|
this.em = new events_1.EventEmitter();
|
|
119
119
|
this.channel = null;
|
|
120
120
|
this.publishChannelSetupPromise = null;
|
|
121
|
-
this.
|
|
122
|
-
|
|
123
|
-
[types_1.ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
|
|
124
|
-
};
|
|
121
|
+
this.connection = null;
|
|
122
|
+
this.creatingConnection = false;
|
|
125
123
|
this.exchanges = {};
|
|
126
124
|
this.queues = {};
|
|
127
125
|
this.queueSetupPromises = {};
|
|
126
|
+
this.assertExchangePromises = {};
|
|
128
127
|
this.consumers = [];
|
|
129
128
|
this.options = options;
|
|
130
129
|
this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
|
|
@@ -142,32 +141,30 @@ class RabbitMq {
|
|
|
142
141
|
});
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
|
-
async getConnection(
|
|
144
|
+
async getConnection() {
|
|
146
145
|
return new Promise(async (resolve, reject) => {
|
|
147
|
-
const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
|
|
148
|
-
debug('rabbit: start getting new connection', { connection, creatingConnection, connectionPurpose });
|
|
149
146
|
if (this.blockReconnect) {
|
|
150
147
|
debug('rabbit: block reconnect');
|
|
151
148
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
152
149
|
// @ts-ignore
|
|
153
150
|
return resolve();
|
|
154
151
|
}
|
|
155
|
-
if (connection !== null) {
|
|
156
|
-
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
152
|
+
if (this.connection !== null) {
|
|
153
|
+
if (this.options?.disableReconnect || this.connection?.isConnected()) {
|
|
157
154
|
debug('rabbit: connection - is connected');
|
|
158
155
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
159
156
|
// @ts-ignore
|
|
160
|
-
return resolve(connection);
|
|
157
|
+
return resolve(this.connection);
|
|
161
158
|
}
|
|
162
159
|
debug('rabbit: connection - reconnecting');
|
|
163
160
|
}
|
|
164
|
-
if (creatingConnection) {
|
|
161
|
+
if (this.creatingConnection) {
|
|
165
162
|
debug('rabbit: creating connection emi');
|
|
166
163
|
this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
|
|
167
164
|
this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
|
|
168
165
|
return;
|
|
169
166
|
}
|
|
170
|
-
this.
|
|
167
|
+
this.creatingConnection = true;
|
|
171
168
|
let isResolved = false;
|
|
172
169
|
// It is import to use it as a function and not as a variable
|
|
173
170
|
// because of k8s changes the env variables
|
|
@@ -180,11 +177,11 @@ class RabbitMq {
|
|
|
180
177
|
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
181
178
|
};
|
|
182
179
|
const defaultUrls = findServers();
|
|
183
|
-
const
|
|
180
|
+
const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
184
181
|
findServers,
|
|
185
182
|
});
|
|
186
|
-
this.
|
|
187
|
-
|
|
183
|
+
this.connection = connection;
|
|
184
|
+
this.connection.on('error', (err) => {
|
|
188
185
|
logger_1.default.error('rabbit: connection error', { err });
|
|
189
186
|
if (!isResolved) {
|
|
190
187
|
isResolved = true;
|
|
@@ -192,7 +189,7 @@ class RabbitMq {
|
|
|
192
189
|
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
193
190
|
}
|
|
194
191
|
});
|
|
195
|
-
|
|
192
|
+
this.connection.on('connectFailed', (err) => {
|
|
196
193
|
this.consumersTags = [];
|
|
197
194
|
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
198
195
|
if (!isResolved) {
|
|
@@ -201,7 +198,8 @@ class RabbitMq {
|
|
|
201
198
|
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
202
199
|
}
|
|
203
200
|
});
|
|
204
|
-
|
|
201
|
+
this.connection.on('disconnect', ({ err }) => {
|
|
202
|
+
// this.channel = null;
|
|
205
203
|
this.consumersTags = [];
|
|
206
204
|
debug('rabbit: connection closed');
|
|
207
205
|
if (this.options?.disableReconnect) {
|
|
@@ -212,27 +210,24 @@ class RabbitMq {
|
|
|
212
210
|
logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
213
211
|
}
|
|
214
212
|
});
|
|
215
|
-
|
|
216
|
-
debug('rabbit: connection established'
|
|
217
|
-
this.
|
|
218
|
-
this.em.emit(consts_1.CONNECTION_CREATED_CONST,
|
|
213
|
+
this.connection.once('connect', async () => {
|
|
214
|
+
debug('rabbit: connection established');
|
|
215
|
+
this.creatingConnection = false;
|
|
216
|
+
this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
|
|
219
217
|
isResolved = true;
|
|
220
|
-
resolve(
|
|
218
|
+
resolve(connection);
|
|
221
219
|
});
|
|
222
220
|
});
|
|
223
221
|
}
|
|
224
|
-
async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}
|
|
222
|
+
async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
|
|
225
223
|
let connection;
|
|
226
224
|
try {
|
|
227
|
-
connection = await this.getConnection(
|
|
225
|
+
connection = await this.getConnection();
|
|
228
226
|
}
|
|
229
227
|
catch (e) {
|
|
230
228
|
logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
231
229
|
throw e;
|
|
232
230
|
}
|
|
233
|
-
if (!connection) {
|
|
234
|
-
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
235
|
-
}
|
|
236
231
|
const channel = connection.createChannel({ ...options });
|
|
237
232
|
(0, events_1.once)(channel, 'close').then((args) => {
|
|
238
233
|
logger_1.default.error(`rabbit: channel ${name} closed`);
|
|
@@ -248,16 +243,14 @@ class RabbitMq {
|
|
|
248
243
|
throw err;
|
|
249
244
|
}
|
|
250
245
|
}
|
|
251
|
-
async assertChannel({ force = false
|
|
252
|
-
debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.channel });
|
|
246
|
+
async assertChannel({ force = false } = {}) {
|
|
253
247
|
if (!this.publishChannelSetupPromise) {
|
|
254
248
|
this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
|
|
255
249
|
if (this.channel && !force) {
|
|
256
250
|
return resolve(this.channel);
|
|
257
251
|
}
|
|
258
252
|
try {
|
|
259
|
-
const channel = await this.getNewChannel({
|
|
260
|
-
debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
|
|
253
|
+
const channel = await this.getNewChannel({});
|
|
261
254
|
channel.on('error', (err) => {
|
|
262
255
|
logger_1.default.error('rabbit: channel error', { err });
|
|
263
256
|
});
|
|
@@ -271,69 +264,90 @@ class RabbitMq {
|
|
|
271
264
|
}
|
|
272
265
|
return this.publishChannelSetupPromise;
|
|
273
266
|
}
|
|
274
|
-
async assertExchange(exchangeName, options
|
|
275
|
-
const channel = await this.assertChannel(
|
|
267
|
+
async assertExchange(exchangeName, options) {
|
|
268
|
+
const channel = await this.assertChannel();
|
|
276
269
|
if (this.exchanges[exchangeName]) {
|
|
270
|
+
delete this.assertExchangePromises[exchangeName];
|
|
277
271
|
return this.exchanges[exchangeName];
|
|
278
272
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
273
|
+
if (this.assertExchangePromises[exchangeName]) {
|
|
274
|
+
return this.assertExchangePromises[exchangeName];
|
|
275
|
+
}
|
|
276
|
+
this.assertExchangePromises[exchangeName] = (0, utils_1.assertExchangeFanout)(channel, exchangeName);
|
|
277
|
+
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
278
|
+
return this.exchanges[exchangeName];
|
|
282
279
|
}
|
|
283
|
-
async getQueueLength(queue
|
|
280
|
+
async getQueueLength(queue) {
|
|
284
281
|
RabbitMq.validateName('queue', queue);
|
|
285
|
-
const { connection } = this.connectionsMap[connectionPurpose];
|
|
286
282
|
const { channel } = this;
|
|
287
283
|
if (!channel) {
|
|
288
284
|
throw new Error('channel is not defined');
|
|
289
285
|
}
|
|
290
|
-
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
286
|
+
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
291
287
|
return channel?.checkQueue(queue);
|
|
292
288
|
}
|
|
293
|
-
async deleteQueue(queue
|
|
289
|
+
async deleteQueue(queue) {
|
|
294
290
|
RabbitMq.validateName('queue', queue);
|
|
295
|
-
const channel = await this.assertChannel(
|
|
291
|
+
const channel = await this.assertChannel();
|
|
296
292
|
logger_1.default.info('rabbit: deleting queue', { queue });
|
|
297
293
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
298
294
|
debug('queue deleted', deleteQueueRes);
|
|
299
295
|
return deleteQueueRes;
|
|
300
296
|
}
|
|
301
|
-
async bindQueue(queue, exchange
|
|
302
|
-
const channel = await this.assertChannel(
|
|
297
|
+
async bindQueue(queue, exchange) {
|
|
298
|
+
const channel = await this.assertChannel();
|
|
303
299
|
await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
304
300
|
return channel.bindQueue(queue, exchange, '');
|
|
305
301
|
}
|
|
306
|
-
async setupQueue(queueName,
|
|
302
|
+
async setupQueue(queueName, options) {
|
|
307
303
|
let queue;
|
|
308
|
-
|
|
304
|
+
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
305
|
+
const localeOptions = {
|
|
306
|
+
...options,
|
|
307
|
+
durable: true,
|
|
308
|
+
arguments: {
|
|
309
|
+
...options?.arguments,
|
|
310
|
+
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
311
|
+
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
312
|
+
},
|
|
313
|
+
};
|
|
309
314
|
try {
|
|
310
|
-
const channel = await this.assertChannel(
|
|
315
|
+
const channel = await this.assertChannel();
|
|
311
316
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
312
|
-
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName,
|
|
317
|
+
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
313
318
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
314
|
-
queue = await channel.assertQueue(queueName,
|
|
319
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
315
320
|
}
|
|
316
321
|
catch (e) {
|
|
317
322
|
logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
318
323
|
if (!this.options?.dontRetryAssert) {
|
|
319
324
|
debug('retrying assertQueue', { queueName });
|
|
320
|
-
const channel = await this.assertChannel({ force: true
|
|
321
|
-
await this.deleteQueue(queueName
|
|
325
|
+
const channel = await this.assertChannel({ force: true });
|
|
326
|
+
await this.deleteQueue(queueName);
|
|
322
327
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
323
|
-
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName,
|
|
328
|
+
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
324
329
|
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
325
|
-
queue = await channel.assertQueue(queueName,
|
|
330
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
326
331
|
}
|
|
327
332
|
else {
|
|
328
333
|
throw e;
|
|
329
334
|
}
|
|
330
335
|
}
|
|
331
|
-
|
|
332
|
-
this.queues[queueName] = queueName;
|
|
336
|
+
this.queues[queueName] = queue;
|
|
333
337
|
return queue;
|
|
334
338
|
}
|
|
335
|
-
|
|
336
|
-
|
|
339
|
+
static shouldUseQuorum(queueName) {
|
|
340
|
+
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
341
|
+
if (envQuorumQueuesWhitelist === '*') {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
if (envQuorumQueuesWhitelist) {
|
|
345
|
+
const whitelist = envQuorumQueuesWhitelist.split(',');
|
|
346
|
+
return whitelist.includes(queueName);
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
async assertQueue(queueName, options) {
|
|
337
351
|
RabbitMq.validateName('queue', queueName);
|
|
338
352
|
if (this.queues[queueName]) {
|
|
339
353
|
delete this.queueSetupPromises[queueName];
|
|
@@ -342,8 +356,7 @@ class RabbitMq {
|
|
|
342
356
|
if (this.queueSetupPromises[queueName]) {
|
|
343
357
|
return this.queueSetupPromises[queueName];
|
|
344
358
|
}
|
|
345
|
-
this.queueSetupPromises[queueName] = this.setupQueue(queueName,
|
|
346
|
-
debug('rabbit: done assert queue', { connectionPurpose, queueName });
|
|
359
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
347
360
|
return this.queueSetupPromises[queueName];
|
|
348
361
|
}
|
|
349
362
|
saveConsumer(queue, callback, options) {
|
|
@@ -386,16 +399,17 @@ class RabbitMq {
|
|
|
386
399
|
}
|
|
387
400
|
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
388
401
|
}
|
|
389
|
-
const channel = await this.getNewChannel({
|
|
402
|
+
const channel = await this.getNewChannel({});
|
|
390
403
|
return channel.addSetup(async (confirmChannel) => {
|
|
391
|
-
await
|
|
392
|
-
await confirmChannel.prefetch(limit,
|
|
404
|
+
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
405
|
+
await confirmChannel.prefetch(limit, false);
|
|
393
406
|
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
394
407
|
if (!msg) {
|
|
395
408
|
return null;
|
|
396
409
|
}
|
|
397
410
|
const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
|
|
398
411
|
const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
|
|
412
|
+
const automationId = msg.properties.headers[consts_1.AUTOMATION_ID_HEADER];
|
|
399
413
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
400
414
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
401
415
|
const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
|
|
@@ -420,7 +434,10 @@ class RabbitMq {
|
|
|
420
434
|
outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
|
|
421
435
|
}
|
|
422
436
|
if (auditContext) {
|
|
423
|
-
await auditContext(queue
|
|
437
|
+
await auditContext(queue, {
|
|
438
|
+
userId,
|
|
439
|
+
automationId,
|
|
440
|
+
});
|
|
424
441
|
}
|
|
425
442
|
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
426
443
|
if (!shouldConsume) {
|
|
@@ -466,7 +483,7 @@ class RabbitMq {
|
|
|
466
483
|
RabbitMq.validateName('queue', queue);
|
|
467
484
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
468
485
|
await this.saveConsumer(queue, callback, options);
|
|
469
|
-
const channel = await this.getNewChannel({ name: `consume
|
|
486
|
+
const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
470
487
|
return channel.addSetup(async (c) => {
|
|
471
488
|
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
472
489
|
await c.assertQueue(queue);
|
|
@@ -481,14 +498,14 @@ class RabbitMq {
|
|
|
481
498
|
async publish(exchange, content, customHeaders) {
|
|
482
499
|
return (0, utils_1.wrapSetImmediate)(async () => {
|
|
483
500
|
RabbitMq.validateName('exchange', exchange);
|
|
484
|
-
const channel = await this.assertChannel(
|
|
485
|
-
await this.assertExchange(exchange
|
|
501
|
+
const channel = await this.assertChannel();
|
|
502
|
+
await this.assertExchange(exchange);
|
|
486
503
|
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
487
504
|
});
|
|
488
505
|
}
|
|
489
506
|
async sendToQueue(queue, content, options, customHeaders) {
|
|
490
507
|
try {
|
|
491
|
-
await this.assertChannel(
|
|
508
|
+
await this.assertChannel();
|
|
492
509
|
}
|
|
493
510
|
catch (e) {
|
|
494
511
|
logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
@@ -496,7 +513,7 @@ class RabbitMq {
|
|
|
496
513
|
}
|
|
497
514
|
try {
|
|
498
515
|
RabbitMq.validateName('queue', queue);
|
|
499
|
-
await this.assertQueue(queue,
|
|
516
|
+
await this.assertQueue(queue, options);
|
|
500
517
|
}
|
|
501
518
|
catch (e) {
|
|
502
519
|
logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
@@ -508,36 +525,31 @@ class RabbitMq {
|
|
|
508
525
|
return res;
|
|
509
526
|
}
|
|
510
527
|
catch (e) {
|
|
511
|
-
|
|
528
|
+
const isConnected = await this.isConnected();
|
|
529
|
+
logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
512
530
|
throw e;
|
|
513
531
|
}
|
|
514
532
|
}
|
|
515
533
|
async isConnected() {
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
logger_1.default.info('rabbit: isConnected - true');
|
|
538
|
-
return true;
|
|
539
|
-
}));
|
|
540
|
-
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
534
|
+
const connection = await this.getConnection();
|
|
535
|
+
const isConnected = connection.isConnected();
|
|
536
|
+
if (!isConnected) {
|
|
537
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
const channel = await this.assertChannel();
|
|
541
|
+
try {
|
|
542
|
+
await Promise.all([
|
|
543
|
+
channel.waitForConnect(),
|
|
544
|
+
...this.consumers.map((c) => channel.checkQueue(c.queue)),
|
|
545
|
+
]);
|
|
546
|
+
}
|
|
547
|
+
catch (e) {
|
|
548
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
logger_1.default.info('rabbit: isConnected - true');
|
|
552
|
+
return true;
|
|
541
553
|
}
|
|
542
554
|
async gracefulShutdown(signal) {
|
|
543
555
|
const tagsNumber = this.consumersTags.length;
|
package/dist/lib/consts.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ 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
5
|
export declare const USER_TRACING_HEADER = "x-af-user-id";
|
|
6
|
+
export declare const AUTOMATION_ID_HEADER = "x-af-automation-id";
|
|
6
7
|
export declare const USER_OBJECT = "userObject";
|
|
7
8
|
export declare const DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
8
9
|
export declare const CONNECTION_CREATED_CONST = "connectionCreated";
|
package/dist/lib/consts.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.USER_TRACING_HEADER = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
|
|
3
|
+
exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.AUTOMATION_ID_HEADER = 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
8
|
exports.USER_TRACING_HEADER = 'x-af-user-id';
|
|
9
|
+
exports.AUTOMATION_ID_HEADER = 'x-af-automation-id';
|
|
9
10
|
exports.USER_OBJECT = 'userObject';
|
|
10
11
|
exports.DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
11
12
|
exports.CONNECTION_CREATED_CONST = 'connectionCreated';
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AmqpConnectionManager } from 'amqp-connection-manager';
|
|
2
1
|
import { ConsumeMessage, Options, Replies } from 'amqplib';
|
|
3
2
|
export interface ExchangesCache {
|
|
4
3
|
[key: string]: any;
|
|
@@ -9,6 +8,9 @@ export interface QueuesCache {
|
|
|
9
8
|
export interface QueueSetupPromisesDictionary {
|
|
10
9
|
[key: string]: Promise<Replies.AssertQueue> | undefined;
|
|
11
10
|
}
|
|
11
|
+
export interface AssertExchangePromisesDictionary {
|
|
12
|
+
[key: string]: Promise<Replies.AssertExchange> | undefined;
|
|
13
|
+
}
|
|
12
14
|
export type CustomMessageHeaders = {
|
|
13
15
|
redisTimestampValidationKey?: string;
|
|
14
16
|
};
|
|
@@ -39,11 +41,3 @@ export type AfConsumer = {
|
|
|
39
41
|
options: ConsumeOptions | undefined;
|
|
40
42
|
};
|
|
41
43
|
export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
|
|
42
|
-
export type ConnectionData = {
|
|
43
|
-
connection: AmqpConnectionManager | null;
|
|
44
|
-
creatingConnection: boolean;
|
|
45
|
-
};
|
|
46
|
-
export declare enum ConnectionPurpose {
|
|
47
|
-
Consume = "consume",
|
|
48
|
-
Publish = "publish"
|
|
49
|
-
}
|
package/dist/lib/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.CONSUMER_DEFAULT_OPTIONS = void 0;
|
|
4
4
|
const HA_PROMOTE_ON_FAILURE = 'ha-promote-on-failure';
|
|
5
5
|
const HA_PROMOTE_ON_SHUTDOWN = 'ha-promote-on-shutdown';
|
|
6
6
|
exports.CONSUMER_DEFAULT_OPTIONS = {
|
|
@@ -9,8 +9,3 @@ exports.CONSUMER_DEFAULT_OPTIONS = {
|
|
|
9
9
|
[HA_PROMOTE_ON_SHUTDOWN]: 'always',
|
|
10
10
|
},
|
|
11
11
|
};
|
|
12
|
-
var ConnectionPurpose;
|
|
13
|
-
(function (ConnectionPurpose) {
|
|
14
|
-
ConnectionPurpose["Consume"] = "consume";
|
|
15
|
-
ConnectionPurpose["Publish"] = "publish";
|
|
16
|
-
})(ConnectionPurpose = exports.ConnectionPurpose || (exports.ConnectionPurpose = {}));
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChannelWrapper } from 'amqp-connection-manager';
|
|
2
|
-
import { ConfirmChannel } from 'amqplib';
|
|
3
|
-
export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<
|
|
2
|
+
import { ConfirmChannel, Replies } from 'amqplib';
|
|
3
|
+
export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<Replies.AssertExchange>;
|
|
4
4
|
export declare const wrapSetImmediate: (callback: () => any) => Promise<any>;
|
|
5
5
|
export declare const rand: () => number;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import RabbitError from './lib/rabbitError';
|
|
|
19
19
|
import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
20
20
|
import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
|
|
21
21
|
import {
|
|
22
|
+
AUTOMATION_ID_HEADER,
|
|
22
23
|
CONNECTION_CREATED_CONST,
|
|
23
24
|
CONNECTION_FAILED_CONST,
|
|
24
25
|
DEFAULT_LOCK_TIMEOUT,
|
|
@@ -35,15 +36,17 @@ import {
|
|
|
35
36
|
CustomMessageHeaders,
|
|
36
37
|
QueuesCache,
|
|
37
38
|
RedisLockType,
|
|
38
|
-
ExchangesCache,
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
ExchangesCache,
|
|
40
|
+
CONSUMER_DEFAULT_OPTIONS,
|
|
41
|
+
QueueSetupPromisesDictionary,
|
|
42
|
+
AssertExchangePromisesDictionary,
|
|
41
43
|
} from './lib/types';
|
|
42
44
|
|
|
43
45
|
// const debug = nodeDebug('af-rabbitmq')
|
|
44
46
|
const debug = logger.debug.bind(logger);
|
|
45
47
|
|
|
46
48
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
49
|
+
|
|
47
50
|
export interface IAfRabbitMq {
|
|
48
51
|
ack: any;
|
|
49
52
|
nack: any;
|
|
@@ -83,13 +86,11 @@ type newChannelOpts = {
|
|
|
83
86
|
name?: string;
|
|
84
87
|
onClose?: null | ((args: any | null) => void);
|
|
85
88
|
options?: CreateChannelOpts | undefined;
|
|
86
|
-
connectionPurpose?: ConnectionPurpose,
|
|
87
89
|
};
|
|
88
90
|
|
|
89
91
|
type assertChannelOpts = {
|
|
90
92
|
channelName?: string;
|
|
91
93
|
force?: boolean;
|
|
92
|
-
connectionPurpose?: ConnectionPurpose;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
type AfConsumer = {
|
|
@@ -101,7 +102,7 @@ type AfConsumer = {
|
|
|
101
102
|
const HEARTBEAT = '60';
|
|
102
103
|
|
|
103
104
|
class RabbitMq implements IAfRabbitMq {
|
|
104
|
-
static parseMsg(msg: any): any {
|
|
105
|
+
static parseMsg(msg: any) : any {
|
|
105
106
|
let { content } = msg;
|
|
106
107
|
content = content.toString();
|
|
107
108
|
|
|
@@ -146,21 +147,22 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
146
147
|
|
|
147
148
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
148
149
|
|
|
149
|
-
blockReconnect: boolean | null | undefined
|
|
150
|
+
blockReconnect: boolean | null | undefined
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
[ConnectionPurpose.Consume]: ConnectionData;
|
|
153
|
-
[ConnectionPurpose.Publish]: ConnectionData;
|
|
154
|
-
};
|
|
152
|
+
connection: AmqpConnectionManager | null | undefined
|
|
155
153
|
|
|
156
154
|
em: EventEmitter;
|
|
157
155
|
|
|
156
|
+
creatingConnection: boolean;
|
|
157
|
+
|
|
158
158
|
exchanges: ExchangesCache;
|
|
159
159
|
|
|
160
160
|
queues: QueuesCache;
|
|
161
161
|
|
|
162
162
|
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
163
163
|
|
|
164
|
+
assertExchangePromises: AssertExchangePromisesDictionary;
|
|
165
|
+
|
|
164
166
|
options: AfRabbitOptions | undefined;
|
|
165
167
|
|
|
166
168
|
redisClient: any;
|
|
@@ -176,13 +178,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
176
178
|
this.em = new EventEmitter();
|
|
177
179
|
this.channel = null;
|
|
178
180
|
this.publishChannelSetupPromise = null;
|
|
179
|
-
this.
|
|
180
|
-
|
|
181
|
-
[ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
|
|
182
|
-
};
|
|
181
|
+
this.connection = null;
|
|
182
|
+
this.creatingConnection = false;
|
|
183
183
|
this.exchanges = {};
|
|
184
184
|
this.queues = {};
|
|
185
185
|
this.queueSetupPromises = {};
|
|
186
|
+
this.assertExchangePromises = {};
|
|
186
187
|
this.consumers = [];
|
|
187
188
|
this.options = options;
|
|
188
189
|
this.redisClient = redisConfig && getRedisInstance(redisConfig);
|
|
@@ -215,7 +216,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
215
216
|
return false;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
219
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
|
|
219
220
|
if (msg) {
|
|
220
221
|
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
221
222
|
await channel.ack(msg);
|
|
@@ -241,8 +242,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
241
242
|
userMsg: ConsumeMessageOrNull,
|
|
242
243
|
{
|
|
243
244
|
skipRetry = false,
|
|
244
|
-
}: NackOptions = {},
|
|
245
|
-
): Promise<any> => {
|
|
245
|
+
}: NackOptions = { },
|
|
246
|
+
) : Promise<any> => {
|
|
246
247
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
247
248
|
if (channel && msg) {
|
|
248
249
|
if (
|
|
@@ -276,32 +277,30 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
276
277
|
}
|
|
277
278
|
}
|
|
278
279
|
|
|
279
|
-
async getConnection(
|
|
280
|
-
return new Promise<AmqpConnectionManager
|
|
281
|
-
const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
|
|
282
|
-
debug('rabbit: start getting new connection', { connection, creatingConnection, connectionPurpose });
|
|
280
|
+
async getConnection() {
|
|
281
|
+
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
283
282
|
if (this.blockReconnect) {
|
|
284
283
|
debug('rabbit: block reconnect');
|
|
285
284
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
286
285
|
// @ts-ignore
|
|
287
286
|
return resolve();
|
|
288
287
|
}
|
|
289
|
-
if (connection !== null) {
|
|
290
|
-
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
288
|
+
if (this.connection !== null) {
|
|
289
|
+
if (this.options?.disableReconnect || this.connection?.isConnected()) {
|
|
291
290
|
debug('rabbit: connection - is connected');
|
|
292
291
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
293
292
|
// @ts-ignore
|
|
294
|
-
return resolve(connection);
|
|
293
|
+
return resolve(this.connection);
|
|
295
294
|
}
|
|
296
295
|
debug('rabbit: connection - reconnecting');
|
|
297
296
|
}
|
|
298
|
-
if (creatingConnection) {
|
|
297
|
+
if (this.creatingConnection) {
|
|
299
298
|
debug('rabbit: creating connection emi');
|
|
300
299
|
this.em.once(CONNECTION_CREATED_CONST, resolve);
|
|
301
300
|
this.em.once(CONNECTION_FAILED_CONST, reject);
|
|
302
301
|
return;
|
|
303
302
|
}
|
|
304
|
-
this.
|
|
303
|
+
this.creatingConnection = true;
|
|
305
304
|
let isResolved = false;
|
|
306
305
|
|
|
307
306
|
// It is import to use it as a function and not as a variable
|
|
@@ -318,13 +317,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
318
317
|
};
|
|
319
318
|
|
|
320
319
|
const defaultUrls = findServers();
|
|
321
|
-
const
|
|
320
|
+
const connection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
322
321
|
findServers,
|
|
323
322
|
});
|
|
324
323
|
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
newConnection.on('error', (err) => {
|
|
324
|
+
this.connection = connection;
|
|
325
|
+
this.connection.on('error', (err) => {
|
|
328
326
|
logger.error('rabbit: connection error', { err });
|
|
329
327
|
if (!isResolved) {
|
|
330
328
|
isResolved = true;
|
|
@@ -333,7 +331,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
333
331
|
}
|
|
334
332
|
});
|
|
335
333
|
|
|
336
|
-
|
|
334
|
+
this.connection.on('connectFailed', (err) => {
|
|
337
335
|
this.consumersTags = [];
|
|
338
336
|
logger.error('rabbit: connection connectFailed', { err });
|
|
339
337
|
if (!isResolved) {
|
|
@@ -343,7 +341,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
343
341
|
}
|
|
344
342
|
});
|
|
345
343
|
|
|
346
|
-
|
|
344
|
+
this.connection.on('disconnect', ({ err }) => {
|
|
345
|
+
// this.channel = null;
|
|
347
346
|
this.consumersTags = [];
|
|
348
347
|
debug('rabbit: connection closed');
|
|
349
348
|
if (this.options?.disableReconnect) {
|
|
@@ -353,29 +352,25 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
353
352
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
354
353
|
}
|
|
355
354
|
});
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.
|
|
355
|
+
|
|
356
|
+
this.connection.once('connect', async () => {
|
|
357
|
+
debug('rabbit: connection established');
|
|
358
|
+
this.creatingConnection = false;
|
|
359
|
+
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
360
360
|
isResolved = true;
|
|
361
|
-
resolve(
|
|
361
|
+
resolve(connection);
|
|
362
362
|
});
|
|
363
363
|
});
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
async getNewChannel({
|
|
367
|
-
|
|
368
|
-
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
369
|
-
let connection!: AmqpConnectionManager | undefined | null;
|
|
366
|
+
async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
|
|
367
|
+
let connection!: AmqpConnectionManager;
|
|
370
368
|
try {
|
|
371
|
-
connection = await this.getConnection(
|
|
369
|
+
connection = await this.getConnection();
|
|
372
370
|
} catch (e) {
|
|
373
371
|
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
374
372
|
throw e;
|
|
375
373
|
}
|
|
376
|
-
if (!connection) {
|
|
377
|
-
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
378
|
-
}
|
|
379
374
|
const channel = connection.createChannel({ ...options });
|
|
380
375
|
once(channel, 'close').then((args) => {
|
|
381
376
|
logger.error(`rabbit: channel ${name} closed`);
|
|
@@ -391,8 +386,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
391
386
|
}
|
|
392
387
|
}
|
|
393
388
|
|
|
394
|
-
async assertChannel({ force = false
|
|
395
|
-
debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.channel });
|
|
389
|
+
async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
|
|
396
390
|
if (!this.publishChannelSetupPromise) {
|
|
397
391
|
this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
398
392
|
if (this.channel && !force) {
|
|
@@ -400,8 +394,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
400
394
|
}
|
|
401
395
|
|
|
402
396
|
try {
|
|
403
|
-
const channel = await this.getNewChannel({
|
|
404
|
-
debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
|
|
397
|
+
const channel = await this.getNewChannel({});
|
|
405
398
|
channel.on('error', (err) => {
|
|
406
399
|
logger.error('rabbit: channel error', { err });
|
|
407
400
|
});
|
|
@@ -415,73 +408,102 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
415
408
|
return this.publishChannelSetupPromise;
|
|
416
409
|
}
|
|
417
410
|
|
|
418
|
-
async assertExchange(exchangeName: string, options
|
|
419
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
411
|
+
async assertExchange(exchangeName: string, options?: any) {
|
|
412
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
413
|
+
|
|
420
414
|
if (this.exchanges[exchangeName]) {
|
|
415
|
+
delete this.assertExchangePromises[exchangeName];
|
|
421
416
|
return this.exchanges[exchangeName];
|
|
422
417
|
}
|
|
423
|
-
|
|
424
|
-
this.
|
|
425
|
-
|
|
418
|
+
|
|
419
|
+
if (this.assertExchangePromises[exchangeName]) {
|
|
420
|
+
return this.assertExchangePromises[exchangeName];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
424
|
+
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
425
|
+
return this.exchanges[exchangeName];
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
async getQueueLength(queue: string
|
|
428
|
+
async getQueueLength(queue: string) {
|
|
429
429
|
RabbitMq.validateName('queue', queue);
|
|
430
|
-
const { connection } = this.connectionsMap[connectionPurpose];
|
|
431
430
|
const { channel } = this;
|
|
432
431
|
if (!channel) {
|
|
433
432
|
throw new Error('channel is not defined');
|
|
434
433
|
}
|
|
435
|
-
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
434
|
+
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
436
435
|
return channel?.checkQueue(queue);
|
|
437
436
|
}
|
|
438
437
|
|
|
439
|
-
private async deleteQueue(queue: string
|
|
438
|
+
private async deleteQueue(queue: string) {
|
|
440
439
|
RabbitMq.validateName('queue', queue);
|
|
441
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
440
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
442
441
|
logger.info('rabbit: deleting queue', { queue });
|
|
443
442
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
444
443
|
debug('queue deleted', deleteQueueRes);
|
|
445
444
|
return deleteQueueRes;
|
|
446
445
|
}
|
|
447
446
|
|
|
448
|
-
async bindQueue(queue: string, exchange: string
|
|
449
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
447
|
+
async bindQueue(queue: string, exchange: string) {
|
|
448
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
450
449
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
451
450
|
return channel.bindQueue(queue, exchange, '');
|
|
452
451
|
}
|
|
453
452
|
|
|
454
|
-
async setupQueue(queueName: string,
|
|
453
|
+
async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
455
454
|
let queue: Replies.AssertQueue;
|
|
456
|
-
|
|
455
|
+
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
456
|
+
const localeOptions = {
|
|
457
|
+
...options,
|
|
458
|
+
durable: true,
|
|
459
|
+
arguments: {
|
|
460
|
+
...options?.arguments,
|
|
461
|
+
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
462
|
+
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
463
|
+
},
|
|
464
|
+
};
|
|
457
465
|
try {
|
|
458
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
466
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
459
467
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
460
|
-
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName,
|
|
468
|
+
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
461
469
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
462
|
-
queue = await channel.assertQueue(queueName,
|
|
470
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
463
471
|
} catch (e) {
|
|
464
472
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
465
473
|
if (!this.options?.dontRetryAssert) {
|
|
466
474
|
debug('retrying assertQueue', { queueName });
|
|
467
|
-
const channel = await this.assertChannel({ force: true
|
|
468
|
-
await this.deleteQueue(queueName
|
|
475
|
+
const channel = await this.assertChannel({ force: true });
|
|
476
|
+
await this.deleteQueue(queueName);
|
|
469
477
|
|
|
470
478
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
471
|
-
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName,
|
|
479
|
+
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
472
480
|
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
473
|
-
queue = await channel.assertQueue(queueName,
|
|
481
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
474
482
|
} else {
|
|
475
483
|
throw e;
|
|
476
484
|
}
|
|
477
485
|
}
|
|
478
|
-
|
|
479
|
-
this.queues[queueName] =
|
|
486
|
+
|
|
487
|
+
this.queues[queueName] = queue;
|
|
480
488
|
return queue;
|
|
481
489
|
}
|
|
482
490
|
|
|
483
|
-
|
|
484
|
-
|
|
491
|
+
static shouldUseQuorum(queueName: string): boolean {
|
|
492
|
+
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
493
|
+
|
|
494
|
+
if (envQuorumQueuesWhitelist === '*') {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (envQuorumQueuesWhitelist) {
|
|
499
|
+
const whitelist = envQuorumQueuesWhitelist.split(',');
|
|
500
|
+
return whitelist.includes(queueName);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
|
|
485
507
|
RabbitMq.validateName('queue', queueName);
|
|
486
508
|
if (this.queues[queueName]) {
|
|
487
509
|
delete this.queueSetupPromises[queueName];
|
|
@@ -492,13 +514,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
492
514
|
return this.queueSetupPromises[queueName];
|
|
493
515
|
}
|
|
494
516
|
|
|
495
|
-
this.queueSetupPromises[queueName] = this.setupQueue(queueName,
|
|
496
|
-
debug('rabbit: done assert queue', { connectionPurpose, queueName });
|
|
517
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
497
518
|
return this.queueSetupPromises[queueName];
|
|
498
519
|
}
|
|
499
520
|
|
|
500
521
|
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
501
|
-
const isConsumerExist:
|
|
522
|
+
const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
|
|
502
523
|
if (!isConsumerExist) {
|
|
503
524
|
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
504
525
|
this.consumers.push({
|
|
@@ -547,10 +568,10 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
547
568
|
}
|
|
548
569
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
549
570
|
}
|
|
550
|
-
const channel = await this.getNewChannel({
|
|
571
|
+
const channel = await this.getNewChannel({});
|
|
551
572
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
552
|
-
await
|
|
553
|
-
await confirmChannel.prefetch(limit,
|
|
573
|
+
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
574
|
+
await confirmChannel.prefetch(limit, false);
|
|
554
575
|
const { consumerTag } = await confirmChannel.consume(
|
|
555
576
|
queue,
|
|
556
577
|
async (msg: ConsumeMessageOrNull) => {
|
|
@@ -560,6 +581,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
560
581
|
|
|
561
582
|
const traceId = msg.properties.headers[TRACING_HEADER];
|
|
562
583
|
const userId = msg.properties.headers[USER_TRACING_HEADER];
|
|
584
|
+
const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
|
|
563
585
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
564
586
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
565
587
|
const trace = newTrace(traceTypes.RABBIT);
|
|
@@ -585,7 +607,10 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
585
607
|
}
|
|
586
608
|
|
|
587
609
|
if (auditContext) {
|
|
588
|
-
await auditContext(queue
|
|
610
|
+
await auditContext(queue, {
|
|
611
|
+
userId,
|
|
612
|
+
automationId,
|
|
613
|
+
});
|
|
589
614
|
}
|
|
590
615
|
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
591
616
|
if (!shouldConsume) {
|
|
@@ -633,13 +658,13 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
633
658
|
});
|
|
634
659
|
}
|
|
635
660
|
|
|
636
|
-
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
661
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
|
|
637
662
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
638
663
|
RabbitMq.validateName('exchange', exchange);
|
|
639
664
|
RabbitMq.validateName('queue', queue);
|
|
640
665
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
641
666
|
await this.saveConsumer(queue, callback, options);
|
|
642
|
-
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume
|
|
667
|
+
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
643
668
|
|
|
644
669
|
return channel.addSetup(async (c: ConfirmChannel) => {
|
|
645
670
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
@@ -657,11 +682,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
657
682
|
});
|
|
658
683
|
}
|
|
659
684
|
|
|
660
|
-
async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
|
|
685
|
+
async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
|
|
661
686
|
return wrapSetImmediate(async () => {
|
|
662
687
|
RabbitMq.validateName('exchange', exchange);
|
|
663
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
664
|
-
await this.assertExchange(exchange
|
|
688
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
689
|
+
await this.assertExchange(exchange);
|
|
665
690
|
await channel.publish(exchange, '',
|
|
666
691
|
Buffer.from(JSON.stringify(content)),
|
|
667
692
|
RabbitMq.getPublishOptions(customHeaders));
|
|
@@ -675,7 +700,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
675
700
|
customHeaders?: any,
|
|
676
701
|
): Promise<boolean | undefined> {
|
|
677
702
|
try {
|
|
678
|
-
await this.assertChannel(
|
|
703
|
+
await this.assertChannel();
|
|
679
704
|
} catch (e) {
|
|
680
705
|
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
681
706
|
throw e;
|
|
@@ -683,7 +708,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
683
708
|
|
|
684
709
|
try {
|
|
685
710
|
RabbitMq.validateName('queue', queue);
|
|
686
|
-
await this.assertQueue(queue,
|
|
711
|
+
await this.assertQueue(queue, options);
|
|
687
712
|
} catch (e) {
|
|
688
713
|
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
689
714
|
throw e;
|
|
@@ -696,41 +721,34 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
696
721
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
697
722
|
return res;
|
|
698
723
|
} catch (e) {
|
|
699
|
-
|
|
724
|
+
const isConnected = await this.isConnected();
|
|
725
|
+
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
700
726
|
throw e;
|
|
701
727
|
}
|
|
702
728
|
}
|
|
703
729
|
|
|
704
|
-
async isConnected(): Promise<boolean> {
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
logger.error('rabbit: isConnected - false');
|
|
724
|
-
return false;
|
|
725
|
-
}
|
|
726
|
-
logger.info('rabbit: isConnected - true');
|
|
727
|
-
return true;
|
|
728
|
-
}),
|
|
729
|
-
);
|
|
730
|
-
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
730
|
+
async isConnected() : Promise<boolean> {
|
|
731
|
+
const connection = await this.getConnection();
|
|
732
|
+
const isConnected = connection.isConnected();
|
|
733
|
+
if (!isConnected) {
|
|
734
|
+
logger.error('rabbit: isConnected - false');
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
const channel: any = await this.assertChannel();
|
|
738
|
+
try {
|
|
739
|
+
await Promise.all([
|
|
740
|
+
channel.waitForConnect(),
|
|
741
|
+
...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
|
742
|
+
]);
|
|
743
|
+
} catch (e) {
|
|
744
|
+
logger.error('rabbit: isConnected - false');
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
logger.info('rabbit: isConnected - true');
|
|
748
|
+
return true;
|
|
731
749
|
}
|
|
732
750
|
|
|
733
|
-
async gracefulShutdown(signal: string): Promise<void> {
|
|
751
|
+
async gracefulShutdown(signal: string) : Promise<void> {
|
|
734
752
|
const tagsNumber = this.consumersTags.length;
|
|
735
753
|
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
736
754
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
package/src/lib/consts.ts
CHANGED
|
@@ -3,6 +3,7 @@ export const DEFAULT_LOCK_TIMEOUT = 1000 * 5;
|
|
|
3
3
|
export const RETRY_HEADER = 'x-retry-count';
|
|
4
4
|
export const TRACING_HEADER = 'x-trace-id';
|
|
5
5
|
export const USER_TRACING_HEADER = 'x-af-user-id';
|
|
6
|
+
export const AUTOMATION_ID_HEADER = 'x-af-automation-id';
|
|
6
7
|
export const USER_OBJECT = 'userObject';
|
|
7
8
|
export const DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
8
9
|
export const CONNECTION_CREATED_CONST = 'connectionCreated';
|
package/src/lib/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AmqpConnectionManager } from 'amqp-connection-manager';
|
|
2
1
|
import { ConsumeMessage, Options, Replies } from 'amqplib';
|
|
3
2
|
|
|
4
3
|
export interface ExchangesCache {
|
|
@@ -13,6 +12,10 @@ export interface QueueSetupPromisesDictionary {
|
|
|
13
12
|
[key: string]: Promise<Replies.AssertQueue> | undefined
|
|
14
13
|
}
|
|
15
14
|
|
|
15
|
+
export interface AssertExchangePromisesDictionary {
|
|
16
|
+
[key: string]: Promise<Replies.AssertExchange> | undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
16
19
|
export type CustomMessageHeaders = {
|
|
17
20
|
redisTimestampValidationKey?: string;
|
|
18
21
|
}
|
|
@@ -56,13 +59,3 @@ export const CONSUMER_DEFAULT_OPTIONS: Options.Consume = {
|
|
|
56
59
|
[HA_PROMOTE_ON_SHUTDOWN]: 'always',
|
|
57
60
|
},
|
|
58
61
|
};
|
|
59
|
-
|
|
60
|
-
export type ConnectionData = {
|
|
61
|
-
connection: AmqpConnectionManager | null;
|
|
62
|
-
creatingConnection: boolean;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export enum ConnectionPurpose {
|
|
66
|
-
Consume = 'consume',
|
|
67
|
-
Publish = 'publish',
|
|
68
|
-
}
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { ChannelWrapper } from 'amqp-connection-manager';
|
|
2
|
-
import { ConfirmChannel } from 'amqplib';
|
|
2
|
+
import { ConfirmChannel, Replies } from 'amqplib';
|
|
3
|
+
|
|
4
|
+
export const assertExchangeFanout = async (
|
|
5
|
+
c: ChannelWrapper | ConfirmChannel,
|
|
6
|
+
exchangeName: string,
|
|
7
|
+
): Promise<Replies.AssertExchange> => c.assertExchange(exchangeName, 'fanout');
|
|
3
8
|
|
|
4
|
-
export const assertExchangeFanout = async (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
|
|
5
9
|
export const wrapSetImmediate = (callback: () => any) => new Promise<any>((resolve, reject) => {
|
|
6
10
|
setImmediate(async () => {
|
|
7
11
|
try {
|