@seidor-cloud-produtos/orbit-backend-lib 1.101.28 → 1.101.30

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.
@@ -3,4 +3,5 @@ import Handler from './handler';
3
3
  export default interface Queue {
4
4
  on(queueName: string, callback: Handler): Promise<void>;
5
5
  publish(exchangeName: string, domainEvent: DomainEvent, configs?: Record<string, any>): Promise<void>;
6
+ get?(queueName: string, options?: any): Promise<unknown>;
6
7
  }
@@ -6,6 +6,6 @@ export declare enum ENVIRONMENT_TYPES {
6
6
  TEST = "test"
7
7
  }
8
8
  export declare enum LOG_LEVEL {
9
- debug = "DEBUG",
10
- info = "INFO"
9
+ debug = "debug",
10
+ info = "info"
11
11
  }
@@ -11,6 +11,6 @@ var ENVIRONMENT_TYPES;
11
11
  })(ENVIRONMENT_TYPES || (exports.ENVIRONMENT_TYPES = ENVIRONMENT_TYPES = {}));
12
12
  var LOG_LEVEL;
13
13
  (function (LOG_LEVEL) {
14
- LOG_LEVEL["debug"] = "DEBUG";
15
- LOG_LEVEL["info"] = "INFO";
14
+ LOG_LEVEL["debug"] = "debug";
15
+ LOG_LEVEL["info"] = "info";
16
16
  })(LOG_LEVEL || (exports.LOG_LEVEL = LOG_LEVEL = {}));
@@ -1,7 +1,9 @@
1
+ import amqp from 'amqplib';
1
2
  import Handler from '../../../application/queue/handler';
2
3
  import QueueConnection from '../../../application/queue/queue-connection';
3
4
  import DomainEvent from '../../../domain/events/domain-event';
4
5
  import QueueController from '../queue-controller';
6
+ import { AmqpQueueGetResult } from './types';
5
7
  export default class AmqpQueue implements QueueConnection {
6
8
  private uri;
7
9
  private static instance;
@@ -10,9 +12,12 @@ export default class AmqpQueue implements QueueConnection {
10
12
  private messagesInMemory;
11
13
  private controller;
12
14
  private channels;
13
- constructor();
15
+ constructor(uri?: string);
14
16
  connect(vHost: string): Promise<AmqpQueue>;
15
17
  close(): Promise<void>;
18
+ get(queueName: string, options?: amqp.Options.Get | undefined): Promise<AmqpQueueGetResult | null>;
19
+ ack(channel: amqp.Channel, msg: any): Promise<void>;
20
+ nack(channel: amqp.Channel, msg: any, requeue?: boolean): Promise<void>;
16
21
  on(queueName: string, callback: Handler): Promise<void>;
17
22
  publish(exchangeName: string, domainEvent: DomainEvent, configs?: Record<string, any>): Promise<void>;
18
23
  createConsumers(queueName: string, configs: CreateConsumers): Promise<void>;
@@ -10,8 +10,8 @@ class AmqpQueue {
10
10
  messagesInMemory = [];
11
11
  controller;
12
12
  channels = new Map();
13
- constructor() {
14
- this.uri = process.env.BROKER_AMQP;
13
+ constructor(uri = process.env.BROKER_AMQP) {
14
+ this.uri = uri;
15
15
  }
16
16
  async connect(vHost) {
17
17
  const urlConnection = `${this.uri}/${vHost}`;
@@ -30,6 +30,39 @@ class AmqpQueue {
30
30
  console.log(`⛔ Queue closed on vHost ${this.vHost}`);
31
31
  }
32
32
  }
33
+ async get(queueName, options) {
34
+ try {
35
+ const channel = await this.connection.createChannel();
36
+ const rawMessage = await channel.get(queueName, options);
37
+ if (!rawMessage) {
38
+ await channel.close();
39
+ return null;
40
+ }
41
+ return { rawMessage, channel };
42
+ }
43
+ catch (err) {
44
+ console.log(`⛔ Erro ao obter mensagem de ${queueName}:`, err);
45
+ return null;
46
+ }
47
+ }
48
+ async ack(channel, msg) {
49
+ try {
50
+ channel.ack(msg);
51
+ await channel.close();
52
+ }
53
+ catch (err) {
54
+ console.log("⛔ Erro ao dar ack:", err);
55
+ }
56
+ }
57
+ async nack(channel, msg, requeue = false) {
58
+ try {
59
+ channel.nack(msg, false, requeue);
60
+ await channel.close();
61
+ }
62
+ catch (err) {
63
+ console.log("⛔ Erro ao dar nack:", err);
64
+ }
65
+ }
33
66
  async on(queueName, callback) {
34
67
  try {
35
68
  const channel = await this.connection.createChannel();
@@ -26,10 +26,80 @@ describe('AmqpQueue', () => {
26
26
  ];
27
27
  const mockPublish = (mq['publish'] = vi.fn());
28
28
  await mq['publishMessagesOfMemory']();
29
- console.log(mockPublish.mock.calls);
30
29
  expect(mockPublish.mock.calls).toEqual([
31
30
  ['exchangeName_1', 'domainEvent_1', 'my_cfg_1'],
32
31
  ['exchangeName_2', 'domainEvent_2', 'my_cfg_2'],
33
32
  ]);
34
33
  });
34
+ describe('get', () => {
35
+ it('should return null if no message is found', async () => {
36
+ const mq = new amqp_lib_1.default();
37
+ mq['connection'] = {
38
+ createChannel: vi.fn().mockResolvedValue({
39
+ get: vi.fn().mockResolvedValue(null),
40
+ close: vi.fn(),
41
+ }),
42
+ };
43
+ const result = await mq.get('queueName');
44
+ expect(result).toBeNull();
45
+ });
46
+ it('should return rawMessage and channel if message is found', async () => {
47
+ const fakeMsg = { content: Buffer.from('msg') };
48
+ const fakeChannel = {
49
+ get: vi.fn().mockResolvedValue(fakeMsg),
50
+ close: vi.fn(),
51
+ };
52
+ const mq = new amqp_lib_1.default();
53
+ mq['connection'] = {
54
+ createChannel: vi.fn().mockResolvedValue(fakeChannel),
55
+ };
56
+ const result = await mq.get('queueName');
57
+ expect(result).toEqual({ rawMessage: fakeMsg, channel: fakeChannel });
58
+ expect(fakeChannel.get).toHaveBeenCalledWith('queueName', undefined);
59
+ });
60
+ it('should handle errors and return null', async () => {
61
+ const mq = new amqp_lib_1.default();
62
+ mq['connection'] = {
63
+ createChannel: vi.fn().mockRejectedValue(new Error('fail')),
64
+ };
65
+ const result = await mq.get('queueName');
66
+ expect(result).toBeNull();
67
+ });
68
+ });
69
+ describe('ack', () => {
70
+ it('should call channel.ack and close', async () => {
71
+ const ack = vi.fn();
72
+ const close = vi.fn();
73
+ const channel = { ack, close };
74
+ const mq = new amqp_lib_1.default();
75
+ await mq.ack(channel, 'msg');
76
+ expect(ack).toHaveBeenCalledWith('msg');
77
+ expect(close).toHaveBeenCalled();
78
+ });
79
+ it('should handle errors gracefully', async () => {
80
+ const ack = vi.fn(() => { throw new Error('fail'); });
81
+ const close = vi.fn();
82
+ const channel = { ack, close };
83
+ const mq = new amqp_lib_1.default();
84
+ await expect(mq.ack(channel, 'msg')).resolves.toBeUndefined();
85
+ });
86
+ });
87
+ describe('nack', () => {
88
+ it('should call channel.nack and close', async () => {
89
+ const nack = vi.fn();
90
+ const close = vi.fn();
91
+ const channel = { nack, close };
92
+ const mq = new amqp_lib_1.default();
93
+ await mq.nack(channel, 'msg', true);
94
+ expect(nack).toHaveBeenCalledWith('msg', false, true);
95
+ expect(close).toHaveBeenCalled();
96
+ });
97
+ it('should handle errors gracefully', async () => {
98
+ const nack = vi.fn(() => { throw new Error('fail'); });
99
+ const close = vi.fn();
100
+ const channel = { nack, close };
101
+ const mq = new amqp_lib_1.default();
102
+ await expect(mq.nack(channel, 'msg')).resolves.toBeUndefined();
103
+ });
104
+ });
35
105
  });
@@ -0,0 +1,5 @@
1
+ import amqp from "amqplib";
2
+ export type AmqpQueueGetResult = {
3
+ rawMessage: any;
4
+ channel: amqp.Channel;
5
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ import Handler from '../../../clean-arch/application/queue/handler';
2
+ import AmqpQueue from '../queue/rabbitmq/amqp-lib';
3
+ import { AmqpQueueGetResult } from '../queue/rabbitmq/types';
4
+ import { RabbitMQScaledJobOptions } from './types';
5
+ export declare class RabbitMQScaledJobRunner<T = any> {
6
+ private readonly amqpQueue;
7
+ private readonly queueName;
8
+ private readonly vHost;
9
+ private readonly handler;
10
+ private readonly options;
11
+ constructor(amqpQueue: AmqpQueue, queueName: string, vHost: string, handler: Handler, options: RabbitMQScaledJobOptions);
12
+ private handleError;
13
+ protected errorStrategy(getResult: AmqpQueueGetResult): Promise<void>;
14
+ private rejectTimeout;
15
+ private finishProcess;
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RabbitMQScaledJobRunner = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const logger_1 = tslib_1.__importDefault(require("../../application/logger"));
6
+ class RabbitMQScaledJobRunner {
7
+ amqpQueue;
8
+ queueName;
9
+ vHost;
10
+ handler;
11
+ options;
12
+ constructor(amqpQueue, queueName, vHost, handler, options) {
13
+ this.amqpQueue = amqpQueue;
14
+ this.queueName = queueName;
15
+ this.vHost = vHost;
16
+ this.handler = handler;
17
+ this.options = options;
18
+ }
19
+ async handleError(error, getResult) {
20
+ logger_1.default.error(`[RabbitMQScaledJobRunner] Erro: ${error.message}`);
21
+ if (!getResult) {
22
+ return;
23
+ }
24
+ return await this.errorStrategy(getResult);
25
+ }
26
+ async errorStrategy(getResult) {
27
+ const { channel, rawMessage } = getResult;
28
+ try {
29
+ if (this.options.errorStrategy === 'nack-requeue') {
30
+ return await this.amqpQueue.nack(channel, rawMessage, true);
31
+ }
32
+ return await this.amqpQueue.nack(channel, rawMessage, false);
33
+ }
34
+ catch {
35
+ logger_1.default?.error('[RabbitMQScaledJobRunner] Falha ao aplicar estratégia de erro');
36
+ }
37
+ }
38
+ rejectTimeout() {
39
+ return new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), this.options.timeoutMs));
40
+ }
41
+ async finishProcess() {
42
+ logger_1.default.debug()?.info('[RabbitMQScaledJobRunner] Finished process');
43
+ }
44
+ async run() {
45
+ let getResult = null;
46
+ try {
47
+ await this.amqpQueue.connect(this.vHost);
48
+ getResult = await this.amqpQueue.get(this.queueName, {
49
+ noAck: !this.options.manualAck,
50
+ });
51
+ if (!getResult) {
52
+ logger_1.default
53
+ .debug()
54
+ ?.warning('[RabbitMQScaledJobRunner] Nenhuma mensagem disponível.');
55
+ return;
56
+ }
57
+ const { channel, rawMessage } = getResult;
58
+ const msg = this.options.parse(rawMessage);
59
+ logger_1.default
60
+ .debug()
61
+ ?.info(`[RabbitMQScaledJobRunner] Mensagem recebida ${JSON.stringify(msg, null, 2)}`);
62
+ await Promise.race([this.handler.handle(msg), this.rejectTimeout()]);
63
+ if (this.options.manualAck) {
64
+ await this.amqpQueue.ack(channel, rawMessage);
65
+ }
66
+ }
67
+ catch (err) {
68
+ logger_1.default.error(`[RabbitMQScaledJobRunner] Erro: ${err.message}`);
69
+ if (getResult && this.options.manualAck) {
70
+ await this.handleError(err, getResult);
71
+ }
72
+ }
73
+ finally {
74
+ try {
75
+ await this.amqpQueue.close();
76
+ }
77
+ catch {
78
+ logger_1.default.error('[RabbitMQScaledJobRunner] Erro ao fechar conexão');
79
+ await this.finishProcess();
80
+ }
81
+ }
82
+ }
83
+ }
84
+ exports.RabbitMQScaledJobRunner = RabbitMQScaledJobRunner;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const runner_1 = require("./runner");
5
+ (0, vitest_1.describe)('RabbitMQScaledJobRunner', () => {
6
+ let amqpQueue;
7
+ let handler;
8
+ let options;
9
+ let onFinally;
10
+ let runner;
11
+ (0, vitest_1.beforeEach)(() => {
12
+ amqpQueue = {
13
+ connect: vitest_1.vi.fn().mockResolvedValue(undefined),
14
+ get: vitest_1.vi.fn().mockResolvedValue(null),
15
+ ack: vitest_1.vi.fn().mockResolvedValue(undefined),
16
+ nack: vitest_1.vi.fn().mockResolvedValue(undefined),
17
+ close: vitest_1.vi.fn().mockResolvedValue(undefined),
18
+ };
19
+ handler = { handle: vitest_1.vi.fn().mockResolvedValue(undefined) };
20
+ options = {
21
+ manualAck: true,
22
+ timeoutMs: 1000,
23
+ errorStrategy: 'nack-dlq',
24
+ parse: vitest_1.vi.fn((raw) => ({ ok: true, msg: 'parsed' })),
25
+ };
26
+ onFinally = vitest_1.vi.fn();
27
+ runner = new runner_1.RabbitMQScaledJobRunner(amqpQueue, 'queue', 'vhost', handler, options);
28
+ });
29
+ (0, vitest_1.it)('should connect, get, handle, ack and close (happy path)', async () => {
30
+ const rawMessage = { content: Buffer.from('msg') };
31
+ const channel = {};
32
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
33
+ await runner.run();
34
+ (0, vitest_1.expect)(amqpQueue.connect).toHaveBeenCalledWith('vhost');
35
+ (0, vitest_1.expect)(amqpQueue.get).toHaveBeenCalledWith('queue', { noAck: false });
36
+ (0, vitest_1.expect)(options.parse).toHaveBeenCalledWith(rawMessage);
37
+ (0, vitest_1.expect)(handler.handle).toHaveBeenCalledWith({ ok: true, msg: 'parsed' });
38
+ (0, vitest_1.expect)(amqpQueue.ack).toHaveBeenCalledWith(channel, rawMessage);
39
+ (0, vitest_1.expect)(amqpQueue.close).toHaveBeenCalled();
40
+ });
41
+ (0, vitest_1.it)('should not ack if manualAck is false', async () => {
42
+ options.manualAck = false;
43
+ const rawMessage = { content: Buffer.from('msg') };
44
+ const channel = {};
45
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
46
+ await runner.run();
47
+ (0, vitest_1.expect)(amqpQueue.ack).not.toHaveBeenCalled();
48
+ });
49
+ (0, vitest_1.it)('should nack with correct strategy', async () => {
50
+ options.errorStrategy = 'nack-requeue';
51
+ const rawMessage = { content: Buffer.from('msg') };
52
+ const channel = {};
53
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
54
+ handler.handle = vitest_1.vi.fn(() => { throw new Error('fail'); });
55
+ await runner.run();
56
+ (0, vitest_1.expect)(amqpQueue.nack).toHaveBeenCalledWith(channel, rawMessage, true);
57
+ });
58
+ (0, vitest_1.it)('should nack with default strategy', async () => {
59
+ options.errorStrategy = 'nack-dlq';
60
+ const rawMessage = { content: Buffer.from('msg') };
61
+ const channel = {};
62
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
63
+ handler.handle = vitest_1.vi.fn(() => { throw new Error('fail'); });
64
+ await runner.run();
65
+ (0, vitest_1.expect)(amqpQueue.nack).toHaveBeenCalledWith(channel, rawMessage, false);
66
+ });
67
+ (0, vitest_1.it)('should log and return if no message is available', async () => {
68
+ amqpQueue.get.mockResolvedValue(null);
69
+ await runner.run();
70
+ (0, vitest_1.expect)(handler.handle).not.toHaveBeenCalled();
71
+ });
72
+ (0, vitest_1.it)('should log error if amqpQueue.connect throws', async () => {
73
+ amqpQueue.connect.mockRejectedValue(new Error('fail'));
74
+ await runner.run();
75
+ (0, vitest_1.expect)(amqpQueue.connect).toHaveBeenCalledWith('vhost');
76
+ });
77
+ (0, vitest_1.it)('should log error if amqpQueue.close throws', async () => {
78
+ amqpQueue.close.mockRejectedValue(new Error('fail'));
79
+ const rawMessage = { content: Buffer.from('msg') };
80
+ const channel = {};
81
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
82
+ await runner.run();
83
+ (0, vitest_1.expect)(amqpQueue.close).toHaveBeenCalled();
84
+ });
85
+ (0, vitest_1.it)('should call onFinally if close fails', async () => {
86
+ amqpQueue.close.mockRejectedValue(new Error('fail'));
87
+ const rawMessage = { content: Buffer.from('msg') };
88
+ const channel = {};
89
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
90
+ await runner.run();
91
+ // onFinally is called inside finishProcess
92
+ // No assertion needed for logger, just ensure no throw
93
+ });
94
+ (0, vitest_1.it)('should handle timeout and call nack', async () => {
95
+ options.timeoutMs = 10;
96
+ handler.handle = vitest_1.vi.fn(() => new Promise(() => { }));
97
+ const rawMessage = { content: Buffer.from('msg') };
98
+ const channel = {};
99
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
100
+ await runner.run();
101
+ (0, vitest_1.expect)(amqpQueue.nack).toHaveBeenCalledWith(channel, rawMessage, false);
102
+ });
103
+ (0, vitest_1.it)('should handle error in nack gracefully', async () => {
104
+ amqpQueue.nack.mockRejectedValue(new Error('fail'));
105
+ options.errorStrategy = 'nack-dlq';
106
+ const rawMessage = { content: Buffer.from('msg') };
107
+ const channel = {};
108
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
109
+ handler.handle = vitest_1.vi.fn(() => { throw new Error('fail'); });
110
+ await runner.run();
111
+ (0, vitest_1.expect)(amqpQueue.nack).toHaveBeenCalledWith(channel, rawMessage, false);
112
+ });
113
+ (0, vitest_1.it)('should handle error in ack gracefully', async () => {
114
+ amqpQueue.ack.mockRejectedValue(new Error('fail'));
115
+ const rawMessage = { content: Buffer.from('msg') };
116
+ const channel = {};
117
+ amqpQueue.get.mockResolvedValue({ channel, rawMessage });
118
+ await runner.run();
119
+ (0, vitest_1.expect)(amqpQueue.ack).toHaveBeenCalledWith(channel, rawMessage);
120
+ });
121
+ });
@@ -0,0 +1,7 @@
1
+ export type ErrorStrategy = "nack-dlq" | "nack-requeue";
2
+ export type RabbitMQScaledJobOptions = {
3
+ parse: (raw: any) => any;
4
+ manualAck: boolean;
5
+ errorStrategy: ErrorStrategy;
6
+ timeoutMs: number;
7
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seidor-cloud-produtos/orbit-backend-lib",
3
- "version": "1.101.28",
3
+ "version": "1.101.30",
4
4
  "description": "Internal lib for backend components",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",