@autofleet/rabbit 3.2.23-beta.1.0 → 3.2.24-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +16 -12
- package/dist/index.js +78 -67
- package/dist/lib/consts.d.ts +4 -0
- package/dist/lib/consts.js +6 -1
- package/dist/lib/types.d.ts +5 -0
- package/package.json +2 -2
- package/src/index.ts +101 -84
- package/src/lib/celery.ts +39 -39
- package/src/lib/consts.ts +5 -0
- package/src/lib/types.ts +6 -0
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +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 {
|
|
7
|
-
import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, AssertExchangePromisesDictionary } from './lib/types';
|
|
6
|
+
import { ConnectionPurpose } from './lib/consts';
|
|
7
|
+
import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, AssertExchangePromisesDictionary, ConnectionData } from './lib/types';
|
|
8
8
|
export interface IAfRabbitMq {
|
|
9
9
|
ack: any;
|
|
10
10
|
nack: any;
|
|
@@ -38,10 +38,12 @@ type newChannelOpts = {
|
|
|
38
38
|
name?: string;
|
|
39
39
|
onClose?: null | ((args: any | null) => void);
|
|
40
40
|
options?: CreateChannelOpts | undefined;
|
|
41
|
+
connectionPurpose?: ConnectionPurpose;
|
|
41
42
|
};
|
|
42
43
|
type assertChannelOpts = {
|
|
43
44
|
channelName?: string;
|
|
44
45
|
force?: boolean;
|
|
46
|
+
connectionPurpose?: ConnectionPurpose;
|
|
45
47
|
};
|
|
46
48
|
declare class RabbitMq implements IAfRabbitMq {
|
|
47
49
|
static parseMsg(msg: any): any;
|
|
@@ -61,9 +63,11 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
61
63
|
channel: ChannelWrapper | null;
|
|
62
64
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
63
65
|
blockReconnect: boolean | null | undefined;
|
|
64
|
-
|
|
66
|
+
connectionsMap: {
|
|
67
|
+
[ConnectionPurpose.Consume]: ConnectionData;
|
|
68
|
+
[ConnectionPurpose.Publish]: ConnectionData;
|
|
69
|
+
};
|
|
65
70
|
em: EventEmitter;
|
|
66
|
-
creatingConnection: boolean;
|
|
67
71
|
exchanges: ExchangesCache;
|
|
68
72
|
queues: QueuesCache;
|
|
69
73
|
queueSetupPromises: QueueSetupPromisesDictionary;
|
|
@@ -78,16 +82,16 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
78
82
|
private shouldConsumeMessageByTimestamp;
|
|
79
83
|
ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
|
|
80
84
|
nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
|
|
81
|
-
getConnection(): Promise<AmqpConnectionManager>;
|
|
82
|
-
getNewChannel({ name, onClose, options }
|
|
83
|
-
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>;
|
|
84
88
|
assertExchange(exchangeName: string, options?: any): Promise<any>;
|
|
85
|
-
getQueueLength(queue: string): Promise<Replies.AssertQueue>;
|
|
89
|
+
getQueueLength(queue: string, connectionPurpose?: ConnectionPurpose): Promise<Replies.AssertQueue>;
|
|
86
90
|
private deleteQueue;
|
|
87
|
-
bindQueue(queue: string, exchange: string): Promise<void>;
|
|
88
|
-
setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
91
|
+
bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose): Promise<void>;
|
|
92
|
+
setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
89
93
|
static shouldUseQuorum(queueName: string): boolean;
|
|
90
|
-
assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
|
|
94
|
+
assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<any>;
|
|
91
95
|
private saveConsumer;
|
|
92
96
|
consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
|
|
93
97
|
private lockRedisIfNeeded;
|
|
@@ -100,4 +104,4 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
100
104
|
gracefulShutdown(signal: string): Promise<void>;
|
|
101
105
|
}
|
|
102
106
|
export default RabbitMq;
|
|
103
|
-
export { sendCeleryTaskViaHttp, };
|
|
107
|
+
export { sendCeleryTaskViaHttp, } from './lib/celery';
|
package/dist/index.js
CHANGED
|
@@ -16,8 +16,6 @@ const logger_1 = __importDefault(require("./logger"));
|
|
|
16
16
|
const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
|
|
17
17
|
const redis_1 = __importDefault(require("./lib/redis"));
|
|
18
18
|
const utils_1 = require("./lib/utils");
|
|
19
|
-
const celery_1 = require("./lib/celery");
|
|
20
|
-
Object.defineProperty(exports, "sendCeleryTaskViaHttp", { enumerable: true, get: function () { return celery_1.sendCeleryTaskViaHttp; } });
|
|
21
19
|
const consts_1 = require("./lib/consts");
|
|
22
20
|
const types_1 = require("./lib/types");
|
|
23
21
|
// const debug = nodeDebug('af-rabbitmq')
|
|
@@ -121,8 +119,10 @@ class RabbitMq {
|
|
|
121
119
|
this.em = new events_1.EventEmitter();
|
|
122
120
|
this.channel = null;
|
|
123
121
|
this.publishChannelSetupPromise = null;
|
|
124
|
-
this.
|
|
125
|
-
|
|
122
|
+
this.connectionsMap = {
|
|
123
|
+
[consts_1.ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
|
|
124
|
+
[consts_1.ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
|
|
125
|
+
};
|
|
126
126
|
this.exchanges = {};
|
|
127
127
|
this.queues = {};
|
|
128
128
|
this.queueSetupPromises = {};
|
|
@@ -144,30 +144,31 @@ class RabbitMq {
|
|
|
144
144
|
});
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
async getConnection() {
|
|
147
|
+
async getConnection(connectionPurpose) {
|
|
148
148
|
return new Promise(async (resolve, reject) => {
|
|
149
|
+
const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
|
|
149
150
|
if (this.blockReconnect) {
|
|
150
151
|
debug('rabbit: block reconnect');
|
|
151
152
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
152
153
|
// @ts-ignore
|
|
153
154
|
return resolve();
|
|
154
155
|
}
|
|
155
|
-
if (
|
|
156
|
-
if (this.options?.disableReconnect ||
|
|
156
|
+
if (connection !== null) {
|
|
157
|
+
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
157
158
|
debug('rabbit: connection - is connected');
|
|
158
159
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
159
160
|
// @ts-ignore
|
|
160
|
-
return resolve(
|
|
161
|
+
return resolve(connection);
|
|
161
162
|
}
|
|
162
163
|
debug('rabbit: connection - reconnecting');
|
|
163
164
|
}
|
|
164
|
-
if (
|
|
165
|
+
if (creatingConnection) {
|
|
165
166
|
debug('rabbit: creating connection emi');
|
|
166
167
|
this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
|
|
167
168
|
this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
|
-
this.creatingConnection = true;
|
|
171
|
+
this.connectionsMap[connectionPurpose].creatingConnection = true;
|
|
171
172
|
let isResolved = false;
|
|
172
173
|
// It is import to use it as a function and not as a variable
|
|
173
174
|
// because of k8s changes the env variables
|
|
@@ -180,11 +181,11 @@ class RabbitMq {
|
|
|
180
181
|
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
181
182
|
};
|
|
182
183
|
const defaultUrls = findServers();
|
|
183
|
-
const
|
|
184
|
+
const newConnection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
184
185
|
findServers,
|
|
185
186
|
});
|
|
186
|
-
this.connection =
|
|
187
|
-
|
|
187
|
+
this.connectionsMap[connectionPurpose].connection = newConnection;
|
|
188
|
+
newConnection.on('error', (err) => {
|
|
188
189
|
logger_1.default.error('rabbit: connection error', { err });
|
|
189
190
|
if (!isResolved) {
|
|
190
191
|
isResolved = true;
|
|
@@ -192,7 +193,7 @@ class RabbitMq {
|
|
|
192
193
|
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
193
194
|
}
|
|
194
195
|
});
|
|
195
|
-
|
|
196
|
+
newConnection.on('connectFailed', (err) => {
|
|
196
197
|
this.consumersTags = [];
|
|
197
198
|
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
198
199
|
if (!isResolved) {
|
|
@@ -201,8 +202,7 @@ class RabbitMq {
|
|
|
201
202
|
this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
202
203
|
}
|
|
203
204
|
});
|
|
204
|
-
|
|
205
|
-
// this.channel = null;
|
|
205
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
206
206
|
this.consumersTags = [];
|
|
207
207
|
debug('rabbit: connection closed');
|
|
208
208
|
if (this.options?.disableReconnect) {
|
|
@@ -213,24 +213,26 @@ class RabbitMq {
|
|
|
213
213
|
logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
214
214
|
}
|
|
215
215
|
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
this.
|
|
219
|
-
this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
|
|
216
|
+
newConnection.once('connect', async () => {
|
|
217
|
+
this.connectionsMap[connectionPurpose].creatingConnection = false;
|
|
218
|
+
this.em.emit(consts_1.CONNECTION_CREATED_CONST, newConnection);
|
|
220
219
|
isResolved = true;
|
|
221
|
-
resolve(
|
|
220
|
+
resolve(newConnection);
|
|
222
221
|
});
|
|
223
222
|
});
|
|
224
223
|
}
|
|
225
|
-
async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}
|
|
224
|
+
async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connectionPurpose = consts_1.ConnectionPurpose.Consume, }) {
|
|
226
225
|
let connection;
|
|
227
226
|
try {
|
|
228
|
-
connection = await this.getConnection();
|
|
227
|
+
connection = await this.getConnection(connectionPurpose);
|
|
229
228
|
}
|
|
230
229
|
catch (e) {
|
|
231
230
|
logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
232
231
|
throw e;
|
|
233
232
|
}
|
|
233
|
+
if (!connection) {
|
|
234
|
+
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
235
|
+
}
|
|
234
236
|
const channel = connection.createChannel({ ...options });
|
|
235
237
|
(0, events_1.once)(channel, 'close').then((args) => {
|
|
236
238
|
logger_1.default.error(`rabbit: channel ${name} closed`);
|
|
@@ -246,14 +248,15 @@ class RabbitMq {
|
|
|
246
248
|
throw err;
|
|
247
249
|
}
|
|
248
250
|
}
|
|
249
|
-
async assertChannel({ force = false
|
|
251
|
+
async assertChannel({ force = false, connectionPurpose = consts_1.ConnectionPurpose.Consume }) {
|
|
250
252
|
if (!this.publishChannelSetupPromise) {
|
|
251
253
|
this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
|
|
252
254
|
if (this.channel && !force) {
|
|
253
255
|
return resolve(this.channel);
|
|
254
256
|
}
|
|
255
257
|
try {
|
|
256
|
-
const channel = await this.getNewChannel({});
|
|
258
|
+
const channel = await this.getNewChannel({ connectionPurpose });
|
|
259
|
+
debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
|
|
257
260
|
channel.on('error', (err) => {
|
|
258
261
|
logger_1.default.error('rabbit: channel error', { err });
|
|
259
262
|
});
|
|
@@ -267,8 +270,8 @@ class RabbitMq {
|
|
|
267
270
|
}
|
|
268
271
|
return this.publishChannelSetupPromise;
|
|
269
272
|
}
|
|
270
|
-
async assertExchange(exchangeName, options) {
|
|
271
|
-
const channel = await this.assertChannel();
|
|
273
|
+
async assertExchange(exchangeName, options = { connectionPurpose: consts_1.ConnectionPurpose.Consume }) {
|
|
274
|
+
const channel = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
|
|
272
275
|
if (this.exchanges[exchangeName]) {
|
|
273
276
|
delete this.assertExchangePromises[exchangeName];
|
|
274
277
|
return this.exchanges[exchangeName];
|
|
@@ -280,29 +283,30 @@ class RabbitMq {
|
|
|
280
283
|
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
281
284
|
return this.exchanges[exchangeName];
|
|
282
285
|
}
|
|
283
|
-
async getQueueLength(queue) {
|
|
286
|
+
async getQueueLength(queue, connectionPurpose = consts_1.ConnectionPurpose.Consume) {
|
|
284
287
|
RabbitMq.validateName('queue', queue);
|
|
288
|
+
const { connection } = this.connectionsMap[connectionPurpose];
|
|
285
289
|
const { channel } = this;
|
|
286
290
|
if (!channel) {
|
|
287
291
|
throw new Error('channel is not defined');
|
|
288
292
|
}
|
|
289
|
-
debug('rabbit: getting queue length', { queue, connected:
|
|
293
|
+
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
290
294
|
return channel?.checkQueue(queue);
|
|
291
295
|
}
|
|
292
|
-
async deleteQueue(queue) {
|
|
296
|
+
async deleteQueue(queue, connectionPurpose) {
|
|
293
297
|
RabbitMq.validateName('queue', queue);
|
|
294
|
-
const channel = await this.assertChannel();
|
|
298
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
295
299
|
logger_1.default.info('rabbit: deleting queue', { queue });
|
|
296
300
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
297
301
|
debug('queue deleted', deleteQueueRes);
|
|
298
302
|
return deleteQueueRes;
|
|
299
303
|
}
|
|
300
|
-
async bindQueue(queue, exchange) {
|
|
301
|
-
const channel = await this.assertChannel();
|
|
304
|
+
async bindQueue(queue, exchange, connectionPurpose) {
|
|
305
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
302
306
|
await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
303
307
|
return channel.bindQueue(queue, exchange, '');
|
|
304
308
|
}
|
|
305
|
-
async setupQueue(queueName, options) {
|
|
309
|
+
async setupQueue(queueName, connectionPurpose, options) {
|
|
306
310
|
let queue;
|
|
307
311
|
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
308
312
|
const localeOptions = {
|
|
@@ -315,7 +319,7 @@ class RabbitMq {
|
|
|
315
319
|
},
|
|
316
320
|
};
|
|
317
321
|
try {
|
|
318
|
-
const channel = await this.assertChannel();
|
|
322
|
+
const channel = await this.assertChannel({ connectionPurpose });
|
|
319
323
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
320
324
|
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
321
325
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
@@ -325,8 +329,8 @@ class RabbitMq {
|
|
|
325
329
|
logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
326
330
|
if (!this.options?.dontRetryAssert) {
|
|
327
331
|
debug('retrying assertQueue', { queueName });
|
|
328
|
-
const channel = await this.assertChannel({ force: true });
|
|
329
|
-
await this.deleteQueue(queueName);
|
|
332
|
+
const channel = await this.assertChannel({ force: true, connectionPurpose });
|
|
333
|
+
await this.deleteQueue(queueName, connectionPurpose);
|
|
330
334
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
331
335
|
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
332
336
|
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
@@ -350,7 +354,7 @@ class RabbitMq {
|
|
|
350
354
|
}
|
|
351
355
|
return false;
|
|
352
356
|
}
|
|
353
|
-
async assertQueue(queueName, options) {
|
|
357
|
+
async assertQueue(queueName, connectionPurpose, options) {
|
|
354
358
|
RabbitMq.validateName('queue', queueName);
|
|
355
359
|
if (this.queues[queueName]) {
|
|
356
360
|
delete this.queueSetupPromises[queueName];
|
|
@@ -359,7 +363,7 @@ class RabbitMq {
|
|
|
359
363
|
if (this.queueSetupPromises[queueName]) {
|
|
360
364
|
return this.queueSetupPromises[queueName];
|
|
361
365
|
}
|
|
362
|
-
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
366
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
|
|
363
367
|
return this.queueSetupPromises[queueName];
|
|
364
368
|
}
|
|
365
369
|
saveConsumer(queue, callback, options) {
|
|
@@ -402,9 +406,9 @@ class RabbitMq {
|
|
|
402
406
|
}
|
|
403
407
|
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
404
408
|
}
|
|
405
|
-
const channel = await this.getNewChannel({});
|
|
409
|
+
const channel = await this.getNewChannel({ connectionPurpose: consts_1.ConnectionPurpose.Consume });
|
|
406
410
|
return channel.addSetup(async (confirmChannel) => {
|
|
407
|
-
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
411
|
+
const q = await this.assertQueue(queue, consts_1.ConnectionPurpose.Consume, optionsWithDefaults);
|
|
408
412
|
await confirmChannel.prefetch(limit, false);
|
|
409
413
|
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
410
414
|
if (!msg) {
|
|
@@ -486,7 +490,7 @@ class RabbitMq {
|
|
|
486
490
|
RabbitMq.validateName('queue', queue);
|
|
487
491
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
488
492
|
await this.saveConsumer(queue, callback, options);
|
|
489
|
-
const channel = await this.getNewChannel({ name: `consume-exchange
|
|
493
|
+
const channel = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
|
|
490
494
|
return channel.addSetup(async (c) => {
|
|
491
495
|
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
492
496
|
await c.assertQueue(queue);
|
|
@@ -501,14 +505,14 @@ class RabbitMq {
|
|
|
501
505
|
async publish(exchange, content, customHeaders) {
|
|
502
506
|
return (0, utils_1.wrapSetImmediate)(async () => {
|
|
503
507
|
RabbitMq.validateName('exchange', exchange);
|
|
504
|
-
const channel = await this.assertChannel();
|
|
505
|
-
await this.assertExchange(exchange);
|
|
508
|
+
const channel = await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
|
|
509
|
+
await this.assertExchange(exchange, { connectionPurpose: consts_1.ConnectionPurpose.Publish });
|
|
506
510
|
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
507
511
|
});
|
|
508
512
|
}
|
|
509
513
|
async sendToQueue(queue, content, options, customHeaders) {
|
|
510
514
|
try {
|
|
511
|
-
await this.assertChannel();
|
|
515
|
+
await this.assertChannel({ connectionPurpose: consts_1.ConnectionPurpose.Publish });
|
|
512
516
|
}
|
|
513
517
|
catch (e) {
|
|
514
518
|
logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
@@ -516,7 +520,7 @@ class RabbitMq {
|
|
|
516
520
|
}
|
|
517
521
|
try {
|
|
518
522
|
RabbitMq.validateName('queue', queue);
|
|
519
|
-
await this.assertQueue(queue, options);
|
|
523
|
+
await this.assertQueue(queue, consts_1.ConnectionPurpose.Publish, options);
|
|
520
524
|
}
|
|
521
525
|
catch (e) {
|
|
522
526
|
logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
@@ -528,31 +532,36 @@ class RabbitMq {
|
|
|
528
532
|
return res;
|
|
529
533
|
}
|
|
530
534
|
catch (e) {
|
|
531
|
-
|
|
532
|
-
logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
535
|
+
logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
|
|
533
536
|
throw e;
|
|
534
537
|
}
|
|
535
538
|
}
|
|
536
539
|
async isConnected() {
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
540
|
+
const isEachConnectionConnected = await Promise.all(Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
|
|
541
|
+
const { connection } = connectionData;
|
|
542
|
+
if (connectionPurpose === consts_1.ConnectionPurpose.Publish && !connection) {
|
|
543
|
+
return true; // The connection hasn't been initialized yet, as no messages have been sent through it
|
|
544
|
+
}
|
|
545
|
+
const isConnected = connection?.isConnected();
|
|
546
|
+
if (!isConnected) {
|
|
547
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
const channel = await this.assertChannel({ connectionPurpose: connectionPurpose });
|
|
551
|
+
try {
|
|
552
|
+
await Promise.all([
|
|
553
|
+
channel.waitForConnect(),
|
|
554
|
+
...this.consumers.map((c) => channel.checkQueue(c.queue)),
|
|
555
|
+
]);
|
|
556
|
+
}
|
|
557
|
+
catch (e) {
|
|
558
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
logger_1.default.info('rabbit: isConnected - true');
|
|
562
|
+
return true;
|
|
563
|
+
}));
|
|
564
|
+
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
556
565
|
}
|
|
557
566
|
async gracefulShutdown(signal) {
|
|
558
567
|
const tagsNumber = this.consumersTags.length;
|
|
@@ -571,3 +580,5 @@ class RabbitMq {
|
|
|
571
580
|
}
|
|
572
581
|
}
|
|
573
582
|
exports.default = RabbitMq;
|
|
583
|
+
var celery_1 = require("./lib/celery");
|
|
584
|
+
Object.defineProperty(exports, "sendCeleryTaskViaHttp", { enumerable: true, get: function () { return celery_1.sendCeleryTaskViaHttp; } });
|
package/dist/lib/consts.d.ts
CHANGED
package/dist/lib/consts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.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;
|
|
3
|
+
exports.ConnectionPurpose = exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.AUTOMATION_ID_HEADER = exports.USER_TRACING_HEADER = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
|
|
4
4
|
exports.DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 12;
|
|
5
5
|
exports.DEFAULT_LOCK_TIMEOUT = 1000 * 5;
|
|
6
6
|
exports.RETRY_HEADER = 'x-retry-count';
|
|
@@ -20,3 +20,8 @@ exports.DEFAULT_OPTIONS = {
|
|
|
20
20
|
auditContext: null,
|
|
21
21
|
enableRabbitTrace: false,
|
|
22
22
|
};
|
|
23
|
+
var ConnectionPurpose;
|
|
24
|
+
(function (ConnectionPurpose) {
|
|
25
|
+
ConnectionPurpose["Consume"] = "consume";
|
|
26
|
+
ConnectionPurpose["Publish"] = "publish";
|
|
27
|
+
})(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,7 @@ 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
|
+
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/rabbit",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.24-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">=16.7.0"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"postinstall": "echo '\\033[0;31m\nWARNING: in case of migrating to new version of @autofleet/rabbit \nyou might have to delete existing queues or the deployment will fail.\\033[0m\n'",
|
package/src/index.ts
CHANGED
|
@@ -18,11 +18,11 @@ import logger from './logger';
|
|
|
18
18
|
import RabbitError from './lib/rabbitError';
|
|
19
19
|
import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
20
20
|
import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
|
|
21
|
-
import { sendCeleryTaskViaHttp } from './lib/celery';
|
|
22
21
|
import {
|
|
23
22
|
AUTOMATION_ID_HEADER,
|
|
24
23
|
CONNECTION_CREATED_CONST,
|
|
25
24
|
CONNECTION_FAILED_CONST,
|
|
25
|
+
ConnectionPurpose,
|
|
26
26
|
DEFAULT_LOCK_TIMEOUT,
|
|
27
27
|
DEFAULT_OPTIONS,
|
|
28
28
|
RETRY_HEADER,
|
|
@@ -41,13 +41,13 @@ import {
|
|
|
41
41
|
CONSUMER_DEFAULT_OPTIONS,
|
|
42
42
|
QueueSetupPromisesDictionary,
|
|
43
43
|
AssertExchangePromisesDictionary,
|
|
44
|
+
ConnectionData,
|
|
44
45
|
} from './lib/types';
|
|
45
46
|
|
|
46
47
|
// const debug = nodeDebug('af-rabbitmq')
|
|
47
48
|
const debug = logger.debug.bind(logger);
|
|
48
49
|
|
|
49
50
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
50
|
-
|
|
51
51
|
export interface IAfRabbitMq {
|
|
52
52
|
ack: any;
|
|
53
53
|
nack: any;
|
|
@@ -87,11 +87,13 @@ type newChannelOpts = {
|
|
|
87
87
|
name?: string;
|
|
88
88
|
onClose?: null | ((args: any | null) => void);
|
|
89
89
|
options?: CreateChannelOpts | undefined;
|
|
90
|
+
connectionPurpose?: ConnectionPurpose,
|
|
90
91
|
};
|
|
91
92
|
|
|
92
93
|
type assertChannelOpts = {
|
|
93
94
|
channelName?: string;
|
|
94
95
|
force?: boolean;
|
|
96
|
+
connectionPurpose?: ConnectionPurpose;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
type AfConsumer = {
|
|
@@ -103,7 +105,7 @@ type AfConsumer = {
|
|
|
103
105
|
const HEARTBEAT = '60';
|
|
104
106
|
|
|
105
107
|
class RabbitMq implements IAfRabbitMq {
|
|
106
|
-
static parseMsg(msg: any)
|
|
108
|
+
static parseMsg(msg: any): any {
|
|
107
109
|
let { content } = msg;
|
|
108
110
|
content = content.toString();
|
|
109
111
|
|
|
@@ -148,14 +150,15 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
148
150
|
|
|
149
151
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
150
152
|
|
|
151
|
-
blockReconnect: boolean | null | undefined
|
|
153
|
+
blockReconnect: boolean | null | undefined;
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
connectionsMap: {
|
|
156
|
+
[ConnectionPurpose.Consume]: ConnectionData;
|
|
157
|
+
[ConnectionPurpose.Publish]: ConnectionData;
|
|
158
|
+
};
|
|
154
159
|
|
|
155
160
|
em: EventEmitter;
|
|
156
161
|
|
|
157
|
-
creatingConnection: boolean;
|
|
158
|
-
|
|
159
162
|
exchanges: ExchangesCache;
|
|
160
163
|
|
|
161
164
|
queues: QueuesCache;
|
|
@@ -179,8 +182,10 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
179
182
|
this.em = new EventEmitter();
|
|
180
183
|
this.channel = null;
|
|
181
184
|
this.publishChannelSetupPromise = null;
|
|
182
|
-
this.
|
|
183
|
-
|
|
185
|
+
this.connectionsMap = {
|
|
186
|
+
[ConnectionPurpose.Consume]: { connection: null, creatingConnection: false },
|
|
187
|
+
[ConnectionPurpose.Publish]: { connection: null, creatingConnection: false },
|
|
188
|
+
};
|
|
184
189
|
this.exchanges = {};
|
|
185
190
|
this.queues = {};
|
|
186
191
|
this.queueSetupPromises = {};
|
|
@@ -217,7 +222,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
217
222
|
return false;
|
|
218
223
|
}
|
|
219
224
|
|
|
220
|
-
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage)
|
|
225
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
221
226
|
if (msg) {
|
|
222
227
|
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
223
228
|
await channel.ack(msg);
|
|
@@ -243,8 +248,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
243
248
|
userMsg: ConsumeMessageOrNull,
|
|
244
249
|
{
|
|
245
250
|
skipRetry = false,
|
|
246
|
-
}: NackOptions = {
|
|
247
|
-
)
|
|
251
|
+
}: NackOptions = {},
|
|
252
|
+
): Promise<any> => {
|
|
248
253
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
249
254
|
if (channel && msg) {
|
|
250
255
|
if (
|
|
@@ -278,30 +283,31 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
278
283
|
}
|
|
279
284
|
}
|
|
280
285
|
|
|
281
|
-
async getConnection() {
|
|
282
|
-
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
286
|
+
async getConnection(connectionPurpose: ConnectionPurpose) {
|
|
287
|
+
return new Promise<AmqpConnectionManager | undefined | null>(async (resolve, reject) => {
|
|
288
|
+
const { connection, creatingConnection } = this.connectionsMap[connectionPurpose];
|
|
283
289
|
if (this.blockReconnect) {
|
|
284
290
|
debug('rabbit: block reconnect');
|
|
285
291
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
286
292
|
// @ts-ignore
|
|
287
293
|
return resolve();
|
|
288
294
|
}
|
|
289
|
-
if (
|
|
290
|
-
if (this.options?.disableReconnect ||
|
|
295
|
+
if (connection !== null) {
|
|
296
|
+
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
291
297
|
debug('rabbit: connection - is connected');
|
|
292
298
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
293
299
|
// @ts-ignore
|
|
294
|
-
return resolve(
|
|
300
|
+
return resolve(connection);
|
|
295
301
|
}
|
|
296
302
|
debug('rabbit: connection - reconnecting');
|
|
297
303
|
}
|
|
298
|
-
if (
|
|
304
|
+
if (creatingConnection) {
|
|
299
305
|
debug('rabbit: creating connection emi');
|
|
300
306
|
this.em.once(CONNECTION_CREATED_CONST, resolve);
|
|
301
307
|
this.em.once(CONNECTION_FAILED_CONST, reject);
|
|
302
308
|
return;
|
|
303
309
|
}
|
|
304
|
-
this.creatingConnection = true;
|
|
310
|
+
this.connectionsMap[connectionPurpose].creatingConnection = true;
|
|
305
311
|
let isResolved = false;
|
|
306
312
|
|
|
307
313
|
// It is import to use it as a function and not as a variable
|
|
@@ -318,12 +324,13 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
318
324
|
};
|
|
319
325
|
|
|
320
326
|
const defaultUrls = findServers();
|
|
321
|
-
const
|
|
327
|
+
const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
322
328
|
findServers,
|
|
323
329
|
});
|
|
324
330
|
|
|
325
|
-
this.connection =
|
|
326
|
-
|
|
331
|
+
this.connectionsMap[connectionPurpose].connection = newConnection;
|
|
332
|
+
|
|
333
|
+
newConnection.on('error', (err) => {
|
|
327
334
|
logger.error('rabbit: connection error', { err });
|
|
328
335
|
if (!isResolved) {
|
|
329
336
|
isResolved = true;
|
|
@@ -332,7 +339,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
332
339
|
}
|
|
333
340
|
});
|
|
334
341
|
|
|
335
|
-
|
|
342
|
+
newConnection.on('connectFailed', (err) => {
|
|
336
343
|
this.consumersTags = [];
|
|
337
344
|
logger.error('rabbit: connection connectFailed', { err });
|
|
338
345
|
if (!isResolved) {
|
|
@@ -342,8 +349,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
342
349
|
}
|
|
343
350
|
});
|
|
344
351
|
|
|
345
|
-
|
|
346
|
-
// this.channel = null;
|
|
352
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
347
353
|
this.consumersTags = [];
|
|
348
354
|
debug('rabbit: connection closed');
|
|
349
355
|
if (this.options?.disableReconnect) {
|
|
@@ -353,25 +359,28 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
353
359
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
354
360
|
}
|
|
355
361
|
});
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.creatingConnection = false;
|
|
360
|
-
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
362
|
+
newConnection.once('connect', async () => {
|
|
363
|
+
this.connectionsMap[connectionPurpose].creatingConnection = false;
|
|
364
|
+
this.em.emit(CONNECTION_CREATED_CONST, newConnection);
|
|
361
365
|
isResolved = true;
|
|
362
|
-
resolve(
|
|
366
|
+
resolve(newConnection);
|
|
363
367
|
});
|
|
364
368
|
});
|
|
365
369
|
}
|
|
366
370
|
|
|
367
|
-
async getNewChannel({
|
|
368
|
-
|
|
371
|
+
async getNewChannel({
|
|
372
|
+
name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
|
|
373
|
+
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
374
|
+
let connection!: AmqpConnectionManager | undefined | null;
|
|
369
375
|
try {
|
|
370
|
-
connection = await this.getConnection();
|
|
376
|
+
connection = await this.getConnection(connectionPurpose);
|
|
371
377
|
} catch (e) {
|
|
372
378
|
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
373
379
|
throw e;
|
|
374
380
|
}
|
|
381
|
+
if (!connection) {
|
|
382
|
+
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
383
|
+
}
|
|
375
384
|
const channel = connection.createChannel({ ...options });
|
|
376
385
|
once(channel, 'close').then((args) => {
|
|
377
386
|
logger.error(`rabbit: channel ${name} closed`);
|
|
@@ -387,7 +396,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
387
396
|
}
|
|
388
397
|
}
|
|
389
398
|
|
|
390
|
-
async assertChannel({ force = false }
|
|
399
|
+
async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
|
|
391
400
|
if (!this.publishChannelSetupPromise) {
|
|
392
401
|
this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
393
402
|
if (this.channel && !force) {
|
|
@@ -395,7 +404,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
395
404
|
}
|
|
396
405
|
|
|
397
406
|
try {
|
|
398
|
-
const channel = await this.getNewChannel({});
|
|
407
|
+
const channel = await this.getNewChannel({ connectionPurpose });
|
|
408
|
+
debug('rabbit: new channel got', { connectionPurpose, channel: this.channel });
|
|
399
409
|
channel.on('error', (err) => {
|
|
400
410
|
logger.error('rabbit: channel error', { err });
|
|
401
411
|
});
|
|
@@ -409,9 +419,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
409
419
|
return this.publishChannelSetupPromise;
|
|
410
420
|
}
|
|
411
421
|
|
|
412
|
-
async assertExchange(exchangeName: string, options
|
|
413
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
414
|
-
|
|
422
|
+
async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
|
|
423
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
|
|
415
424
|
if (this.exchanges[exchangeName]) {
|
|
416
425
|
delete this.assertExchangePromises[exchangeName];
|
|
417
426
|
return this.exchanges[exchangeName];
|
|
@@ -426,32 +435,33 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
426
435
|
return this.exchanges[exchangeName];
|
|
427
436
|
}
|
|
428
437
|
|
|
429
|
-
async getQueueLength(queue: string) {
|
|
438
|
+
async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
|
|
430
439
|
RabbitMq.validateName('queue', queue);
|
|
440
|
+
const { connection } = this.connectionsMap[connectionPurpose];
|
|
431
441
|
const { channel } = this;
|
|
432
442
|
if (!channel) {
|
|
433
443
|
throw new Error('channel is not defined');
|
|
434
444
|
}
|
|
435
|
-
debug('rabbit: getting queue length', { queue, connected:
|
|
445
|
+
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
436
446
|
return channel?.checkQueue(queue);
|
|
437
447
|
}
|
|
438
448
|
|
|
439
|
-
private async deleteQueue(queue: string) {
|
|
449
|
+
private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
|
|
440
450
|
RabbitMq.validateName('queue', queue);
|
|
441
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
451
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
442
452
|
logger.info('rabbit: deleting queue', { queue });
|
|
443
453
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
444
454
|
debug('queue deleted', deleteQueueRes);
|
|
445
455
|
return deleteQueueRes;
|
|
446
456
|
}
|
|
447
457
|
|
|
448
|
-
async bindQueue(queue: string, exchange: string) {
|
|
449
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
458
|
+
async bindQueue(queue: string, exchange: string, connectionPurpose: ConnectionPurpose) {
|
|
459
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
450
460
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
451
461
|
return channel.bindQueue(queue, exchange, '');
|
|
452
462
|
}
|
|
453
463
|
|
|
454
|
-
async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
464
|
+
async setupQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
455
465
|
let queue: Replies.AssertQueue;
|
|
456
466
|
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
457
467
|
const localeOptions = {
|
|
@@ -464,7 +474,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
464
474
|
},
|
|
465
475
|
};
|
|
466
476
|
try {
|
|
467
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
477
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
468
478
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
469
479
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
470
480
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
@@ -473,8 +483,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
473
483
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
474
484
|
if (!this.options?.dontRetryAssert) {
|
|
475
485
|
debug('retrying assertQueue', { queueName });
|
|
476
|
-
const channel = await this.assertChannel({ force: true });
|
|
477
|
-
await this.deleteQueue(queueName);
|
|
486
|
+
const channel = await this.assertChannel({ force: true, connectionPurpose });
|
|
487
|
+
await this.deleteQueue(queueName, connectionPurpose);
|
|
478
488
|
|
|
479
489
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
480
490
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
@@ -504,7 +514,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
504
514
|
return false;
|
|
505
515
|
}
|
|
506
516
|
|
|
507
|
-
async assertQueue(queueName: string, options?: Options.AssertQueue)
|
|
517
|
+
async assertQueue(queueName: string, connectionPurpose: ConnectionPurpose, options?: Options.AssertQueue) {
|
|
508
518
|
RabbitMq.validateName('queue', queueName);
|
|
509
519
|
if (this.queues[queueName]) {
|
|
510
520
|
delete this.queueSetupPromises[queueName];
|
|
@@ -515,12 +525,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
515
525
|
return this.queueSetupPromises[queueName];
|
|
516
526
|
}
|
|
517
527
|
|
|
518
|
-
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
528
|
+
this.queueSetupPromises[queueName] = this.setupQueue(queueName, connectionPurpose, options);
|
|
519
529
|
return this.queueSetupPromises[queueName];
|
|
520
530
|
}
|
|
521
531
|
|
|
522
532
|
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
523
|
-
const isConsumerExist
|
|
533
|
+
const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
|
|
524
534
|
if (!isConsumerExist) {
|
|
525
535
|
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
526
536
|
this.consumers.push({
|
|
@@ -569,9 +579,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
569
579
|
}
|
|
570
580
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
571
581
|
}
|
|
572
|
-
const channel = await this.getNewChannel({});
|
|
582
|
+
const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
|
|
573
583
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
574
|
-
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
584
|
+
const q = await this.assertQueue(queue, ConnectionPurpose.Consume, optionsWithDefaults);
|
|
575
585
|
await confirmChannel.prefetch(limit, false);
|
|
576
586
|
const { consumerTag } = await confirmChannel.consume(
|
|
577
587
|
queue,
|
|
@@ -659,13 +669,13 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
659
669
|
});
|
|
660
670
|
}
|
|
661
671
|
|
|
662
|
-
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions)
|
|
672
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
663
673
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
664
674
|
RabbitMq.validateName('exchange', exchange);
|
|
665
675
|
RabbitMq.validateName('queue', queue);
|
|
666
676
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
667
677
|
await this.saveConsumer(queue, callback, options);
|
|
668
|
-
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange
|
|
678
|
+
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume - exchange - ${exchange} -queue - ${queue}` });
|
|
669
679
|
|
|
670
680
|
return channel.addSetup(async (c: ConfirmChannel) => {
|
|
671
681
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
@@ -683,11 +693,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
683
693
|
});
|
|
684
694
|
}
|
|
685
695
|
|
|
686
|
-
async publish(exchange: string, content: any, customHeaders?: any)
|
|
696
|
+
async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
|
|
687
697
|
return wrapSetImmediate(async () => {
|
|
688
698
|
RabbitMq.validateName('exchange', exchange);
|
|
689
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
690
|
-
await this.assertExchange(exchange);
|
|
699
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
700
|
+
await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
|
|
691
701
|
await channel.publish(exchange, '',
|
|
692
702
|
Buffer.from(JSON.stringify(content)),
|
|
693
703
|
RabbitMq.getPublishOptions(customHeaders));
|
|
@@ -701,7 +711,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
701
711
|
customHeaders?: any,
|
|
702
712
|
): Promise<boolean | undefined> {
|
|
703
713
|
try {
|
|
704
|
-
await this.assertChannel();
|
|
714
|
+
await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
705
715
|
} catch (e) {
|
|
706
716
|
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
707
717
|
throw e;
|
|
@@ -709,7 +719,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
709
719
|
|
|
710
720
|
try {
|
|
711
721
|
RabbitMq.validateName('queue', queue);
|
|
712
|
-
await this.assertQueue(queue, options);
|
|
722
|
+
await this.assertQueue(queue, ConnectionPurpose.Publish, options);
|
|
713
723
|
} catch (e) {
|
|
714
724
|
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
715
725
|
throw e;
|
|
@@ -722,34 +732,41 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
722
732
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
723
733
|
return res;
|
|
724
734
|
} catch (e) {
|
|
725
|
-
|
|
726
|
-
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
735
|
+
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
|
|
727
736
|
throw e;
|
|
728
737
|
}
|
|
729
738
|
}
|
|
730
739
|
|
|
731
|
-
async isConnected()
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
740
|
+
async isConnected(): Promise<boolean> {
|
|
741
|
+
const isEachConnectionConnected = await Promise.all(
|
|
742
|
+
Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
|
|
743
|
+
const { connection } = connectionData;
|
|
744
|
+
if (connectionPurpose === ConnectionPurpose.Publish && !connection) {
|
|
745
|
+
return true; // The connection hasn't been initialized yet, as no messages have been sent through it
|
|
746
|
+
}
|
|
747
|
+
const isConnected = connection?.isConnected();
|
|
748
|
+
if (!isConnected) {
|
|
749
|
+
logger.error('rabbit: isConnected - false');
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
|
|
753
|
+
try {
|
|
754
|
+
await Promise.all([
|
|
755
|
+
channel.waitForConnect(),
|
|
756
|
+
...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
|
757
|
+
]);
|
|
758
|
+
} catch (e) {
|
|
759
|
+
logger.error('rabbit: isConnected - false');
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
logger.info('rabbit: isConnected - true');
|
|
763
|
+
return true;
|
|
764
|
+
}),
|
|
765
|
+
);
|
|
766
|
+
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
750
767
|
}
|
|
751
768
|
|
|
752
|
-
async gracefulShutdown(signal: string)
|
|
769
|
+
async gracefulShutdown(signal: string): Promise<void> {
|
|
753
770
|
const tagsNumber = this.consumersTags.length;
|
|
754
771
|
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
755
772
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
@@ -769,4 +786,4 @@ export default RabbitMq;
|
|
|
769
786
|
|
|
770
787
|
export {
|
|
771
788
|
sendCeleryTaskViaHttp,
|
|
772
|
-
};
|
|
789
|
+
} from './lib/celery';
|
package/src/lib/celery.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import logger from '../logger';
|
|
3
3
|
// Environment configuration
|
|
4
4
|
const config = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
host: process.env.RABBITMQ_SERVICE_HOST || 'localhost',
|
|
6
|
+
username: process.env.RABBITMQ_USERNAME || 'guest',
|
|
7
|
+
password: process.env.RABBITMQ_PASSWORD || 'guest',
|
|
8
8
|
} as const;
|
|
9
9
|
|
|
10
10
|
// Type definitions
|
|
@@ -42,48 +42,48 @@ interface SendTaskOptions {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
async function sendCeleryTaskViaHttp(
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
data: TaskData,
|
|
46
|
+
{ taskName, queueName }: SendTaskOptions,
|
|
47
47
|
): Promise<void> {
|
|
48
|
-
|
|
48
|
+
const apiUrl = `http://${config.host}:15672/api/exchanges/%2f/amq.default/publish`;
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
const message: TaskMessage = {
|
|
51
|
+
task: taskName,
|
|
52
|
+
id: randomUUID(),
|
|
53
|
+
args: [data],
|
|
54
|
+
};
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
const payload: PublishPayload = {
|
|
57
|
+
properties: {
|
|
58
|
+
delivery_mode: 2,
|
|
59
|
+
content_type: 'application/json',
|
|
60
|
+
},
|
|
61
|
+
routing_key: queueName,
|
|
62
|
+
payload: JSON.stringify(message),
|
|
63
|
+
payload_encoding: 'string',
|
|
64
|
+
};
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(apiUrl, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
Authorization: `Basic ${Buffer.from(`${config.username}:${config.password}`).toString('base64')}`,
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify(payload),
|
|
74
|
+
});
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
} catch (error) {
|
|
84
|
-
logger.error('Error sending request:', error instanceof Error ? error.message : String(error));
|
|
85
|
-
throw error;
|
|
76
|
+
if (response.ok) {
|
|
77
|
+
const result: PublishResponse = await response.json();
|
|
78
|
+
logger.info('Successfully published message:', result);
|
|
79
|
+
} else {
|
|
80
|
+
logger.error(`Failed to publish message. Status code: ${response.status}`);
|
|
81
|
+
logger.error(`Response: ${await response.text()}`);
|
|
86
82
|
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
logger.error('Error sending request:', error instanceof Error ? error.message : String(error));
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export { sendCeleryTaskViaHttp, TaskData, SendTaskOptions };
|
package/src/lib/consts.ts
CHANGED
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,8 @@ 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
|
+
};
|