amqp-suite 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # amqp-suite
2
2
 
3
- [![NPM version](https://img.shields.io/npm/v/amqp-suite)](https://www.npmjs.com/package/amqp-suite)
4
- [![NPM downloads](https://img.shields.io/npm/dw/amqp-suite.svg)](https://www.npmjs.com/package/amqp-suite)
3
+ [![NPM version](https://img.shields.io/npm/v/amqp-suite?color=blue)](https://www.npmjs.com/package/amqp-suite)
5
4
  [![MIT license](https://img.shields.io/badge/License-MIT-bridhtgreen)](https://opensource.org/licenses/MIT)
5
+ [![NPM downloads](https://img.shields.io/npm/dw/amqp-suite?color=bridhtgreen)](https://www.npmjs.com/package/amqp-suite)
6
6
  [![stars](https://img.shields.io/github/stars/iamcarlosdaniel/amqp-suite)](https://github.com/iamcarlosdaniel/amqp-suite)
7
7
 
8
- ![](docs/assets/repository_banner.png)
8
+ ![](https://raw.githubusercontent.com/iamcarlosdaniel/amqp-suite/main/docs/assets/repository_banner.png)
9
9
 
10
10
  `amqp-suite` is a simple and efficient AMQP (Advanced Message Queuing Protocol) client wrapper for Node.js that handles connection management, message publishing, and consuming messages from queues with a topic exchange. This package abstracts complex connection handling and simplifies AMQP usage in applications by providing easy-to-use methods for connecting, publishing, consuming, and gracefully shutting down the connection.
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amqp-suite",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A simple wrapper for AMQP 0-9-1 messaging.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -8,6 +8,10 @@
8
8
  "email": "contact@iamcarlosdaniel.com",
9
9
  "url": "http://iamcarlosdaniel.com"
10
10
  },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/iamcarlosdaniel/amqp-suite.git"
14
+ },
11
15
  "type": "module",
12
16
  "main": "src/index.js",
13
17
  "exports": {
@@ -1,4 +1,4 @@
1
- declare module "amqp-wrapper" {
1
+ declare module "amqp-suite" {
2
2
  export class AmqpClient {
3
3
  constructor(amqpUrl: string, exchange: string);
4
4
  connect(retries?: number, delay?: number): Promise<void>;
@@ -23,6 +23,8 @@ class AmqpClient {
23
23
  this.channel = null;
24
24
  /** @type {boolean} State flag to prevent multiple simultaneous connection attempts. */
25
25
  this.isConnecting = false;
26
+ /** @type {boolean} State flag to indicate if the client is closing. */
27
+ this.isClosing = false;
26
28
  }
27
29
 
28
30
  /**
@@ -51,11 +53,14 @@ class AmqpClient {
51
53
  });
52
54
 
53
55
  this.connection.on("close", () => {
54
- this.isConnecting = false;
55
56
  this.connection = null;
56
57
  this.channel = null;
57
- console.warn(`AMQP connection lost. Retrying in ${delay}ms...`);
58
- setTimeout(() => this.connect(), delay);
58
+
59
+ if (!this.isClosing) {
60
+ this.isConnecting = false;
61
+ console.warn(`AMQP connection lost. Retrying in ${delay}ms...`);
62
+ setTimeout(() => this.connect(), delay);
63
+ }
59
64
  });
60
65
 
61
66
  console.log("AMQP: Connection established successfully");
@@ -171,10 +176,12 @@ class AmqpClient {
171
176
  */
172
177
  async close() {
173
178
  try {
179
+ this.isClosing = true;
174
180
  await this.channel?.close();
175
181
  await this.connection?.close();
176
182
  console.log("AMQP: Connection closed cleanly.");
177
183
  } catch (err) {
184
+ this.isClosing = false;
178
185
  console.error("AMQP: Error during shutdown:", err);
179
186
  }
180
187
  }
package/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { AmqpClient } from "./amqp-client";
1
+ import { AmqpClient } from "./amqp-client.js";
2
2
 
3
3
  export { AmqpClient };
package/vitest.config.js CHANGED
@@ -2,7 +2,7 @@ import { defineConfig } from "vitest/config";
2
2
 
3
3
  export default defineConfig({
4
4
  test: {
5
- globals: true, // Permite usar 'describe', 'it', 'expect' sin importarlos
5
+ globals: true,
6
6
  environment: "node",
7
7
  },
8
8
  });
Binary file
@@ -1,172 +0,0 @@
1
- /**
2
- * @fileoverview Unit tests for the AMQP class using Vitest and amqplib mocks.
3
- */
4
-
5
- import { describe, it, expect, vi, beforeEach } from "vitest";
6
- import amqp from "amqplib";
7
- import { AmqpClient } from "amqp-suite";
8
-
9
- vi.mock("amqplib");
10
-
11
- /**
12
- * Test suite for AMQP integration and message handling.
13
- */
14
- describe("AMQP Class", () => {
15
- /** @type {AmqpClient} */
16
- let amqpInstance;
17
-
18
- /** @type {string} */
19
- const mockUrl = "amqp://localhost";
20
-
21
- /** @type {string} */
22
- const mockExchange = "test_exchange";
23
-
24
- /**
25
- * Mocked AMQP Channel object with Vitest spy functions.
26
- * @type {Object}
27
- */
28
- const mockChannel = {
29
- assertExchange: vi.fn().mockResolvedValue(undefined),
30
- assertQueue: vi.fn().mockResolvedValue({}),
31
- bindQueue: vi.fn().mockResolvedValue(undefined),
32
- prefetch: vi.fn().mockResolvedValue(undefined),
33
- publish: vi.fn().mockReturnValue(true),
34
- consume: vi.fn().mockResolvedValue({ consumerTag: "abc" }),
35
- ack: vi.fn(),
36
- nack: vi.fn(),
37
- close: vi.fn().mockResolvedValue(undefined),
38
- };
39
-
40
- /**
41
- * Mocked AMQP Connection object.
42
- * @type {Object}
43
- */
44
- const mockConnection = {
45
- createChannel: vi.fn().mockResolvedValue(mockChannel),
46
- on: vi.fn(),
47
- close: vi.fn().mockResolvedValue(undefined),
48
- };
49
-
50
- /**
51
- * Setup hook to reset mocks and re-initialize the AMQP instance before each test.
52
- */
53
- beforeEach(() => {
54
- vi.clearAllMocks();
55
- amqpInstance = new AmqpClient(mockUrl, mockExchange);
56
- amqp.connect.mockResolvedValue(mockConnection);
57
- });
58
-
59
- /**
60
- * Test case: Constructor property assignment.
61
- */
62
- it("should correctly initialize values in the constructor", () => {
63
- expect(amqpInstance.amqpUrl).toBe(mockUrl);
64
- expect(amqpInstance.exchange).toBe(mockExchange);
65
- });
66
-
67
- /**
68
- * Test case: Connection workflow and exchange assertion.
69
- */
70
- it("should connect and create a channel successfully", async () => {
71
- await amqpInstance.connect();
72
-
73
- expect(amqp.connect).toHaveBeenCalledWith(mockUrl);
74
- expect(mockConnection.createChannel).toHaveBeenCalled();
75
- expect(mockChannel.assertExchange).toHaveBeenCalledWith(
76
- mockExchange,
77
- "topic",
78
- { durable: true }
79
- );
80
- expect(amqpInstance.connection).not.toBeNull();
81
- });
82
-
83
- /**
84
- * Test case: Message publishing logic and buffer conversion.
85
- */
86
- it("should publish a message correctly", async () => {
87
- await amqpInstance.connect();
88
- const routingKey = "user.created";
89
- const message = { id: 1, name: "Test" };
90
-
91
- await amqpInstance.publish(routingKey, message);
92
-
93
- expect(mockChannel.publish).toHaveBeenCalledWith(
94
- mockExchange,
95
- routingKey,
96
- expect.any(Buffer),
97
- expect.objectContaining({ persistent: true })
98
- );
99
- });
100
-
101
- /**
102
- * Test case: Consumer setup and successful message processing.
103
- */
104
- it("should setup consumer and acknowledge messages on success", async () => {
105
- await amqpInstance.connect();
106
- const queueName = "test_queue";
107
- const bindingKey = "test.key";
108
- const mockPayload = { data: "hello world" };
109
- const mockOnMessage = vi.fn().mockResolvedValue(undefined);
110
-
111
- await amqpInstance.consume(
112
- queueName,
113
- mockOnMessage,
114
- { prefetch: 5 },
115
- bindingKey
116
- );
117
-
118
- expect(mockChannel.assertQueue).toHaveBeenCalledWith(
119
- queueName,
120
- expect.objectContaining({ durable: true })
121
- );
122
- expect(mockChannel.bindQueue).toHaveBeenCalledWith(
123
- queueName,
124
- mockExchange,
125
- bindingKey
126
- );
127
- expect(mockChannel.prefetch).toHaveBeenCalledWith(5);
128
-
129
- const consumerCallback = mockChannel.consume.mock.calls[0][1];
130
- const fakeMsg = {
131
- content: Buffer.from(JSON.stringify(mockPayload)),
132
- };
133
-
134
- await consumerCallback(fakeMsg);
135
-
136
- expect(mockOnMessage).toHaveBeenCalledWith(mockPayload, fakeMsg);
137
- expect(mockChannel.ack).toHaveBeenCalledWith(fakeMsg);
138
- });
139
-
140
- /**
141
- * Test case: Consumer error handling (nack).
142
- */
143
- it("should nack messages if the processing fails", async () => {
144
- await amqpInstance.connect();
145
- const mockOnMessage = vi
146
- .fn()
147
- .mockRejectedValue(new Error("Processing failed"));
148
-
149
- await amqpInstance.consume("error_queue", mockOnMessage);
150
-
151
- const consumerCallback = mockChannel.consume.mock.calls[0][1];
152
- const fakeMsg = {
153
- content: Buffer.from(JSON.stringify({ some: "data" })),
154
- };
155
-
156
- await consumerCallback(fakeMsg);
157
-
158
- expect(mockChannel.nack).toHaveBeenCalledWith(fakeMsg, false, false);
159
- expect(mockChannel.ack).not.toHaveBeenCalled();
160
- });
161
-
162
- /**
163
- * Test case: Graceful shutdown of channel and connection.
164
- */
165
- it("should close the connection cleanly", async () => {
166
- await amqpInstance.connect();
167
- await amqpInstance.close();
168
-
169
- expect(mockChannel.close).toHaveBeenCalled();
170
- expect(mockConnection.close).toHaveBeenCalled();
171
- });
172
- });