@seidor-cloud-produtos/orbit-backend-lib 2.0.125 → 2.0.128

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.
@@ -31,7 +31,8 @@ export default class AmqpQueue implements QueueConnection {
31
31
  private static instance;
32
32
  private vHost;
33
33
  private connection;
34
- private isReconnecting;
34
+ private isConnected;
35
+ private reconnectionPromise?;
35
36
  private messagesInMemory;
36
37
  private channels;
37
38
  protected socketOptions?: SocketOptions;
@@ -118,6 +119,8 @@ export default class AmqpQueue implements QueueConnection {
118
119
  * Quando a conexão fecha, a lib tenta reconectar em loop e, ao
119
120
  * reconectar, reativa consumidores e reenvia mensagens em memória.
120
121
  */
122
+ private getActiveConnection;
123
+ private scheduleReconnection;
121
124
  private treatReconnection;
122
125
  /**
123
126
  * Reinscreve todos os consumidores registrados anteriormente.
@@ -20,7 +20,8 @@ class AmqpQueue {
20
20
  static instance;
21
21
  vHost;
22
22
  connection;
23
- isReconnecting = false;
23
+ isConnected = false;
24
+ reconnectionPromise;
24
25
  messagesInMemory = [];
25
26
  channels = new Map();
26
27
  socketOptions;
@@ -58,11 +59,12 @@ class AmqpQueue {
58
59
  const urlConnection = `${this.uri}${buildedVhost}`;
59
60
  this.connection = await amqplib_1.default.connect(urlConnection, this.socketOptions);
60
61
  this.vHost = vHost;
61
- this.connection.on('error', async (e) => {
62
- return await this.treatReconnection(vHost);
62
+ this.isConnected = true;
63
+ this.connection.on('error', async () => {
64
+ return await this.scheduleReconnection(vHost);
63
65
  });
64
66
  this.connection.on('close', async () => {
65
- return await this.treatReconnection(vHost);
67
+ return await this.scheduleReconnection(vHost);
66
68
  });
67
69
  if (process.env.NODE_ENV !== 'test') {
68
70
  this.logger.info(`⚙️ PROCESS ${this.vHost} PID: `, process.pid);
@@ -75,6 +77,9 @@ class AmqpQueue {
75
77
  retryCount >= this.socketOptions?.retry?.maxCount) {
76
78
  throw e;
77
79
  }
80
+ if (retryCount === 0) {
81
+ await this.close().catch(() => null);
82
+ }
78
83
  this.logger.info(`Retrying connection in... ${this.socketOptions.retry.intervalMs || 0}ms`);
79
84
  await (0, timeout_1.sleep)(this.socketOptions.retry.intervalMs);
80
85
  return await this.connect(vHost, retryCount + 1);
@@ -84,6 +89,7 @@ class AmqpQueue {
84
89
  * Fecha a conexão atual com o broker.
85
90
  */
86
91
  async close() {
92
+ this.isConnected = false;
87
93
  this.connection.removeAllListeners('close');
88
94
  await this.connection.close();
89
95
  if (process.env.NODE_ENV !== 'test') {
@@ -92,7 +98,8 @@ class AmqpQueue {
92
98
  }
93
99
  async get(queueName, options) {
94
100
  try {
95
- const channel = await this.connection.createChannel();
101
+ const connection = await this.getActiveConnection();
102
+ const channel = await connection.createChannel();
96
103
  const rawMessage = await channel.get(queueName, options);
97
104
  if (!rawMessage) {
98
105
  await channel.close();
@@ -142,7 +149,8 @@ class AmqpQueue {
142
149
  */
143
150
  async on(queueName, callback) {
144
151
  try {
145
- const channel = await this.connection.createChannel();
152
+ const connection = await this.getActiveConnection();
153
+ const channel = await connection.createChannel();
146
154
  this.channels.set(queueName, callback);
147
155
  await channel.prefetch(callback.getSimultaneity());
148
156
  await channel.consume(queueName, async (message) => {
@@ -251,7 +259,8 @@ class AmqpQueue {
251
259
  }
252
260
  }
253
261
  async publishToServerWaitForConfirms(exchangeName, domainEvent, buildedConfigs) {
254
- const channel = await this.connection.createConfirmChannel();
262
+ const connection = await this.getActiveConnection();
263
+ const channel = await connection.createConfirmChannel();
255
264
  channel.publish(exchangeName, domainEvent.name || '', Buffer.from(JSON.stringify(domainEvent)), {
256
265
  persistent: true,
257
266
  ...buildedConfigs,
@@ -260,7 +269,8 @@ class AmqpQueue {
260
269
  await channel.close();
261
270
  }
262
271
  async publishToServer(exchangeName, domainEvent, buildedConfigs) {
263
- const channel = await this.connection.createChannel();
272
+ const connection = await this.getActiveConnection();
273
+ const channel = await connection.createChannel();
264
274
  channel.publish(exchangeName, domainEvent.name || '', Buffer.from(JSON.stringify(domainEvent)), {
265
275
  persistent: true,
266
276
  ...buildedConfigs,
@@ -278,7 +288,8 @@ class AmqpQueue {
278
288
  */
279
289
  async createConsumers(queueName, configs) {
280
290
  try {
281
- const channel = await this.connection.createChannel();
291
+ const connection = await this.getActiveConnection();
292
+ const channel = await connection.createChannel();
282
293
  await channel.assertExchange(configs.exchangeName, configs.exchangeType, {
283
294
  durable: true,
284
295
  });
@@ -347,14 +358,29 @@ class AmqpQueue {
347
358
  * Quando a conexão fecha, a lib tenta reconectar em loop e, ao
348
359
  * reconectar, reativa consumidores e reenvia mensagens em memória.
349
360
  */
350
- async treatReconnection(vHost) {
351
- if (this.isReconnecting) {
352
- return;
361
+ async getActiveConnection() {
362
+ if (!this.isConnected && this.reconnectionPromise) {
363
+ await this.reconnectionPromise;
364
+ }
365
+ if (!this.connection) {
366
+ throw new infra_error_1.default('RabbitMQ connection is not available!');
367
+ }
368
+ return this.connection;
369
+ }
370
+ async scheduleReconnection(vHost) {
371
+ this.isConnected = false;
372
+ if (!this.reconnectionPromise) {
373
+ this.reconnectionPromise = this.treatReconnection(vHost).finally(() => {
374
+ this.reconnectionPromise = undefined;
375
+ });
353
376
  }
354
- this.isReconnecting = true;
377
+ return await this.reconnectionPromise;
378
+ }
379
+ async treatReconnection(vHost) {
380
+ this.isConnected = false;
355
381
  this.logger.info('⛔ Connection Error!!');
356
382
  this.logger.info(`🔄 Try connection to the vHost ${vHost}`);
357
- await this.connect(vHost);
383
+ AmqpQueue.instance = await this.connect(vHost);
358
384
  await this.reconnectionChannels();
359
385
  await this.publishMessagesOfMemory();
360
386
  }
@@ -333,6 +333,36 @@ const createLogger = () => ({
333
333
  },
334
334
  ]);
335
335
  });
336
+ (0, vitest_1.it)('should wait for an in-flight reconnection before publishing', async () => {
337
+ let finishReconnection;
338
+ const oldConnection = createConnection();
339
+ const channel = createChannel();
340
+ const newConnection = createConnection(channel);
341
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
342
+ const event = { name: 'domain.event' };
343
+ queue['connection'] = oldConnection;
344
+ queue['vHost'] = 'jobs';
345
+ queue['isConnected'] = false;
346
+ queue['reconnectionPromise'] = new Promise(resolve => {
347
+ finishReconnection = () => {
348
+ queue['connection'] = newConnection;
349
+ queue['isConnected'] = true;
350
+ resolve();
351
+ };
352
+ });
353
+ const publishPromise = queue.publish('exchange', event);
354
+ await Promise.resolve();
355
+ (0, vitest_1.expect)(oldConnection.createChannel).not.toHaveBeenCalled();
356
+ (0, vitest_1.expect)(newConnection.createChannel).not.toHaveBeenCalled();
357
+ finishReconnection();
358
+ await publishPromise;
359
+ (0, vitest_1.expect)(oldConnection.createChannel).not.toHaveBeenCalled();
360
+ (0, vitest_1.expect)(newConnection.createChannel).toHaveBeenCalledTimes(1);
361
+ (0, vitest_1.expect)(channel.publish).toHaveBeenCalledWith('exchange', 'domain.event', Buffer.from(JSON.stringify(event)), {
362
+ persistent: true,
363
+ expiration: FOURTEEN_DAYS_IN_MS,
364
+ });
365
+ });
336
366
  (0, vitest_1.it)('should not store messages when storeMessageOnError is false', async () => {
337
367
  const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
338
368
  queue['publishToServer'] = vitest_1.vi.fn().mockRejectedValue(new Error('fail'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seidor-cloud-produtos/orbit-backend-lib",
3
- "version": "2.0.125",
3
+ "version": "2.0.128",
4
4
  "description": "Internal lib for backend components",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",