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