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