@jsnw/nestjs-rabbitmq 1.0.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/README.md +194 -0
- package/dist/index.js +23 -0
- package/dist/rabbitmq/index.js +9 -0
- package/dist/rabbitmq/rabbitmq-exchange.js +20 -0
- package/dist/rabbitmq/rabbitmq-queue.js +36 -0
- package/dist/rabbitmq/rabbitmq.js +235 -0
- package/dist/rabbitmq-core.module.js +116 -0
- package/dist/rabbitmq-explorer.service.js +143 -0
- package/dist/rabbitmq-instances-manager.js +100 -0
- package/dist/rabbitmq-metadata-storage.js +38 -0
- package/dist/rabbitmq.consts.js +6 -0
- package/dist/rabbitmq.decorators.js +38 -0
- package/dist/rabbitmq.helpers.js +27 -0
- package/dist/rabbitmq.module.js +93 -0
- package/dist/rabbitmq.types.js +2 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/rabbitmq/index.d.ts +3 -0
- package/dist/types/rabbitmq/rabbitmq-exchange.d.ts +18 -0
- package/dist/types/rabbitmq/rabbitmq-queue.d.ts +49 -0
- package/dist/types/rabbitmq/rabbitmq.d.ts +123 -0
- package/dist/types/rabbitmq-core.module.d.ts +15 -0
- package/dist/types/rabbitmq-explorer.service.d.ts +32 -0
- package/dist/types/rabbitmq-instances-manager.d.ts +23 -0
- package/dist/types/rabbitmq-metadata-storage.d.ts +21 -0
- package/dist/types/rabbitmq.consts.d.ts +3 -0
- package/dist/types/rabbitmq.decorators.d.ts +12 -0
- package/dist/types/rabbitmq.helpers.d.ts +5 -0
- package/dist/types/rabbitmq.module.d.ts +14 -0
- package/dist/types/rabbitmq.types.d.ts +17 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# @jsnw/nestjs-rabbitmq
|
|
2
|
+
|
|
3
|
+
NestJS module for RabbitMQ integration using [rabbitmq-client](https://www.npmjs.com/package/rabbitmq-client).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -s @jsnw/nestjs-rabbitmq
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Additionally, you can install `zod` and `rabbitmq-client`
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Module } from '@nestjs/common';
|
|
17
|
+
import { RabbitmqModule, RabbitmqExchange, RabbitmqQueue } from '@jsnw/nestjs-rabbitmq';
|
|
18
|
+
|
|
19
|
+
@Module({
|
|
20
|
+
imports: [
|
|
21
|
+
RabbitmqModule.forRoot({
|
|
22
|
+
name: 'main',
|
|
23
|
+
hostname: 'localhost',
|
|
24
|
+
port: 5672,
|
|
25
|
+
username: 'guest',
|
|
26
|
+
password: 'guest',
|
|
27
|
+
isDefault: true
|
|
28
|
+
})
|
|
29
|
+
]
|
|
30
|
+
})
|
|
31
|
+
export class AppModule {}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Declaring Exchanges and Queues
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const ordersExchange = new RabbitmqExchange({
|
|
38
|
+
name: 'orders',
|
|
39
|
+
type: 'topic',
|
|
40
|
+
durable: true
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const ordersQueue = new RabbitmqQueue({
|
|
44
|
+
name: 'order.created',
|
|
45
|
+
durable: true,
|
|
46
|
+
bindings: [
|
|
47
|
+
{ exchange: ordersExchange, routingKeys: ['order.created'] }
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
RabbitmqModule.forRoot({
|
|
52
|
+
name: 'main',
|
|
53
|
+
hostname: 'localhost',
|
|
54
|
+
port: 5672,
|
|
55
|
+
username: 'guest',
|
|
56
|
+
password: 'guest',
|
|
57
|
+
exchanges: [ordersExchange],
|
|
58
|
+
queues: [ordersQueue]
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Publishing Messages
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Injectable } from '@nestjs/common';
|
|
66
|
+
import { InjectRabbitmq, Rabbitmq } from '@jsnw/nestjs-rabbitmq';
|
|
67
|
+
|
|
68
|
+
@Injectable()
|
|
69
|
+
export class OrderService {
|
|
70
|
+
constructor(
|
|
71
|
+
@InjectRabbitmq() // injects default instance
|
|
72
|
+
private readonly rabbitmq: Rabbitmq
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
async createOrder(data: any) {
|
|
76
|
+
await this.rabbitmq.publish({
|
|
77
|
+
type: 'json',
|
|
78
|
+
message: data,
|
|
79
|
+
exchange: ordersExchange,
|
|
80
|
+
routingKey: 'order.created'
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
To inject a specific instance by name:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
@InjectRabbitmq('main')
|
|
90
|
+
private readonly rabbitmq: Rabbitmq
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Subscribing to Queues
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { Injectable } from '@nestjs/common';
|
|
97
|
+
import { RabbitmqSubscribe, type AsyncMessage } from '@jsnw/nestjs-rabbitmq';
|
|
98
|
+
|
|
99
|
+
@Injectable()
|
|
100
|
+
export class OrderConsumer {
|
|
101
|
+
@RabbitmqSubscribe({
|
|
102
|
+
queue: ordersQueue,
|
|
103
|
+
concurrency: 5
|
|
104
|
+
})
|
|
105
|
+
async handleOrderCreated(data: any, message: AsyncMessage) {
|
|
106
|
+
console.log('Order created:', data);
|
|
107
|
+
return 'ack'; // or 'drop', 'requeue'
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Subscriber Options
|
|
113
|
+
|
|
114
|
+
- `instanceName`: RabbitMQ instance name
|
|
115
|
+
- `queue` (required): Queue to subscribe to
|
|
116
|
+
- `concurrency`: Number of concurrent message handlers (default: 1)
|
|
117
|
+
- `prefetchCount`: Number of messages to prefetch (default: 1)
|
|
118
|
+
- `prefetchSize`: Size of messages to prefetch in bytes (default: 0)
|
|
119
|
+
- `requeue`: Whether to requeue failed messages (default: false)
|
|
120
|
+
- `autoStart`: Auto-start consumer on boot (default: true)
|
|
121
|
+
- `id`: Unique identifier for manual start control
|
|
122
|
+
|
|
123
|
+
## Message Validation
|
|
124
|
+
|
|
125
|
+
Use Zod schemas to validate incoming messages:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { z } from 'zod';
|
|
129
|
+
|
|
130
|
+
const orderSchema = z.object({
|
|
131
|
+
orderId: z.string(),
|
|
132
|
+
amount: z.number()
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
@RabbitmqSubscribe({
|
|
136
|
+
instanceName: 'main',
|
|
137
|
+
queue: ordersQueue,
|
|
138
|
+
validation: {
|
|
139
|
+
schema: orderSchema,
|
|
140
|
+
onFail: 'drop' // or 'ack', 'requeue'
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
async handleOrderCreated(data: z.infer<typeof orderSchema>) {
|
|
144
|
+
// data is typed and validated
|
|
145
|
+
return 'ack';
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Dead Letter Exchanges
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const dlxExchange = new RabbitmqExchange({
|
|
153
|
+
name: 'orders.dlx',
|
|
154
|
+
type: 'topic',
|
|
155
|
+
durable: true
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const queueWithDlx = new RabbitmqQueue({
|
|
159
|
+
name: 'order.created',
|
|
160
|
+
durable: true,
|
|
161
|
+
deadLetterExchange: dlxExchange,
|
|
162
|
+
deadLetterRoutingKey: 'order.failed',
|
|
163
|
+
bindings: [{ exchange: ordersExchange, routingKeys: ['order.created'] }]
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Multiple Instances
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
RabbitmqModule.forRoot({
|
|
171
|
+
name: 'instance1',
|
|
172
|
+
hostname: 'localhost',
|
|
173
|
+
port: 5672,
|
|
174
|
+
username: 'guest',
|
|
175
|
+
password: 'guest',
|
|
176
|
+
isDefault: true
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
RabbitmqModule.forRoot({
|
|
180
|
+
name: 'instance2',
|
|
181
|
+
hostname: 'other-host',
|
|
182
|
+
port: 5672,
|
|
183
|
+
username: 'guest',
|
|
184
|
+
password: 'guest'
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT
|
|
191
|
+
|
|
192
|
+
## Author
|
|
193
|
+
|
|
194
|
+
Pavlo Baliuk (jsnow0177@gmail.com)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.InjectRabbitmq = exports.RabbitmqSubscribe = exports.RabbitmqModule = void 0;
|
|
18
|
+
__exportStar(require("./rabbitmq"), exports);
|
|
19
|
+
var rabbitmq_module_1 = require("./rabbitmq.module");
|
|
20
|
+
Object.defineProperty(exports, "RabbitmqModule", { enumerable: true, get: function () { return rabbitmq_module_1.RabbitmqModule; } });
|
|
21
|
+
var rabbitmq_decorators_1 = require("./rabbitmq.decorators");
|
|
22
|
+
Object.defineProperty(exports, "RabbitmqSubscribe", { enumerable: true, get: function () { return rabbitmq_decorators_1.RabbitmqSubscribe; } });
|
|
23
|
+
Object.defineProperty(exports, "InjectRabbitmq", { enumerable: true, get: function () { return rabbitmq_decorators_1.InjectRabbitmq; } });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RabbitmqQueue = exports.RabbitmqExchange = exports.Rabbitmq = void 0;
|
|
4
|
+
var rabbitmq_1 = require("./rabbitmq");
|
|
5
|
+
Object.defineProperty(exports, "Rabbitmq", { enumerable: true, get: function () { return rabbitmq_1.Rabbitmq; } });
|
|
6
|
+
var rabbitmq_exchange_1 = require("./rabbitmq-exchange");
|
|
7
|
+
Object.defineProperty(exports, "RabbitmqExchange", { enumerable: true, get: function () { return rabbitmq_exchange_1.RabbitmqExchange; } });
|
|
8
|
+
var rabbitmq_queue_1 = require("./rabbitmq-queue");
|
|
9
|
+
Object.defineProperty(exports, "RabbitmqQueue", { enumerable: true, get: function () { return rabbitmq_queue_1.RabbitmqQueue; } });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RabbitmqExchange = void 0;
|
|
4
|
+
class RabbitmqExchange {
|
|
5
|
+
_$type = 'exchange';
|
|
6
|
+
name;
|
|
7
|
+
type;
|
|
8
|
+
durable;
|
|
9
|
+
autoDelete;
|
|
10
|
+
/**
|
|
11
|
+
* @param {RabbitmqExchangeDeclaration} declaration
|
|
12
|
+
*/
|
|
13
|
+
constructor(declaration) {
|
|
14
|
+
this.name = declaration.name;
|
|
15
|
+
this.type = declaration.type;
|
|
16
|
+
this.durable = !!declaration.durable;
|
|
17
|
+
this.autoDelete = !!declaration.autoDelete;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.RabbitmqExchange = RabbitmqExchange;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RabbitmqQueue = void 0;
|
|
4
|
+
class RabbitmqQueue {
|
|
5
|
+
_$type = 'queue';
|
|
6
|
+
name;
|
|
7
|
+
arguments;
|
|
8
|
+
durable;
|
|
9
|
+
autoDelete;
|
|
10
|
+
exclusive;
|
|
11
|
+
/** https://www.rabbitmq.com/dlx.html */
|
|
12
|
+
deadLetterExchange;
|
|
13
|
+
/** https://www.rabbitmq.com/dlx.html */
|
|
14
|
+
deadLetterRoutingKey;
|
|
15
|
+
_bindings;
|
|
16
|
+
get bindings() {
|
|
17
|
+
return this._bindings.map(binding => ({
|
|
18
|
+
exchange: binding.exchange,
|
|
19
|
+
routingKeys: (binding.routingKeys ?? []).map(key => key)
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @param {RabbitmqQueueDeclaration} declaration
|
|
24
|
+
*/
|
|
25
|
+
constructor(declaration) {
|
|
26
|
+
this.name = declaration.name;
|
|
27
|
+
this.arguments = Object.assign({}, declaration.arguments ?? {});
|
|
28
|
+
this.durable = !!declaration.durable;
|
|
29
|
+
this.autoDelete = !!declaration.autoDelete;
|
|
30
|
+
this.exclusive = !!declaration.exclusive;
|
|
31
|
+
this.deadLetterExchange = declaration.deadLetterExchange ?? null;
|
|
32
|
+
this.deadLetterRoutingKey = declaration.deadLetterRoutingKey ?? null;
|
|
33
|
+
this._bindings = declaration.bindings ?? [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.RabbitmqQueue = RabbitmqQueue;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Rabbitmq = void 0;
|
|
4
|
+
const rabbitmq_client_1 = require("rabbitmq-client");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
class Rabbitmq {
|
|
7
|
+
name;
|
|
8
|
+
logger;
|
|
9
|
+
connection;
|
|
10
|
+
exchanges = new Map();
|
|
11
|
+
queues = new Map();
|
|
12
|
+
consumers = new Set;
|
|
13
|
+
consumersById = new Map();
|
|
14
|
+
publisher = null;
|
|
15
|
+
/**
|
|
16
|
+
* @param {string | RabbitmqConstructorParams} arg1
|
|
17
|
+
* @param {Connection} arg2
|
|
18
|
+
*/
|
|
19
|
+
constructor(arg1, arg2) {
|
|
20
|
+
if (typeof arg1 === 'string' && !arg2)
|
|
21
|
+
throw new TypeError(`Invalid second argument passed to the constructor of ${Rabbitmq.name}`);
|
|
22
|
+
this.name = typeof arg1 === 'string' ? arg1 : arg1.name;
|
|
23
|
+
this.logger = new common_1.Logger(Rabbitmq.name + `(${this.name})`);
|
|
24
|
+
this.connection = typeof arg1 === 'string'
|
|
25
|
+
? arg2
|
|
26
|
+
: new rabbitmq_client_1.Connection({
|
|
27
|
+
connectionName: arg1.connectionName ?? arg1.name,
|
|
28
|
+
hostname: arg1.hostname,
|
|
29
|
+
port: arg1.port,
|
|
30
|
+
username: arg1.username,
|
|
31
|
+
password: arg1.password,
|
|
32
|
+
vhost: arg1.vhost ?? '/',
|
|
33
|
+
connectionTimeout: arg1.connectionTimeout ?? 10_000
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* @param {RabbitmqExchange} exchange
|
|
38
|
+
* @returns {Promise<boolean>}
|
|
39
|
+
*/
|
|
40
|
+
async declareExchange(exchange) {
|
|
41
|
+
if (this.exchanges.has(exchange.name))
|
|
42
|
+
return false;
|
|
43
|
+
this.exchanges.set(exchange.name, exchange);
|
|
44
|
+
await this.connection.exchangeDeclare({
|
|
45
|
+
exchange: exchange.name,
|
|
46
|
+
type: exchange.type,
|
|
47
|
+
durable: exchange.durable,
|
|
48
|
+
autoDelete: exchange.autoDelete
|
|
49
|
+
});
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @param {RabbitmqQueue} queue
|
|
54
|
+
* @returns {Promise<boolean>}
|
|
55
|
+
*/
|
|
56
|
+
async declareQueue(queue) {
|
|
57
|
+
if (this.queues.has(queue.name))
|
|
58
|
+
return false;
|
|
59
|
+
// Auto-declare dead-letter exchange if any
|
|
60
|
+
if (queue.deadLetterExchange)
|
|
61
|
+
await this.declareExchange(queue.deadLetterExchange);
|
|
62
|
+
this.queues.set(queue.name, queue);
|
|
63
|
+
await this.connection.queueDeclare({
|
|
64
|
+
queue: queue.name,
|
|
65
|
+
durable: queue.durable,
|
|
66
|
+
autoDelete: queue.autoDelete,
|
|
67
|
+
arguments: {
|
|
68
|
+
...queue.arguments,
|
|
69
|
+
'x-dead-letter-exchange': queue.deadLetterExchange?.name ?? undefined,
|
|
70
|
+
'x-dead-letter-routing-key': queue.deadLetterRoutingKey ?? undefined
|
|
71
|
+
},
|
|
72
|
+
exclusive: queue.exclusive
|
|
73
|
+
});
|
|
74
|
+
// Auto-declare bound exchanges
|
|
75
|
+
await this.setupQueueBindings(queue);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* @param {RabbitmqQueue} queue
|
|
80
|
+
* @returns {Promise<void>}
|
|
81
|
+
* @protected
|
|
82
|
+
*/
|
|
83
|
+
async setupQueueBindings(queue) {
|
|
84
|
+
const bindings = queue.bindings;
|
|
85
|
+
for (const binding of bindings) {
|
|
86
|
+
// Try to declare (or re-declare) an exchange to make sure that it exists before binding a queue to it
|
|
87
|
+
await this.declareExchange(binding.exchange);
|
|
88
|
+
if (binding.routingKeys.length === 0 || binding.routingKeys.length === 1) {
|
|
89
|
+
await this.connection.queueBind({
|
|
90
|
+
queue: queue.name,
|
|
91
|
+
exchange: binding.exchange.name,
|
|
92
|
+
routingKey: binding.routingKeys.length === 0 ? undefined : binding.routingKeys[0]
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
await Promise.all(binding.routingKeys.map(routingKey => this.connection.queueBind({
|
|
97
|
+
queue: queue.name,
|
|
98
|
+
exchange: binding.exchange.name,
|
|
99
|
+
routingKey: routingKey
|
|
100
|
+
})));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* @param {RabbitmqSubscribeParams} params
|
|
106
|
+
* @param {RabbitmqSubscriber} subscriber
|
|
107
|
+
* @returns {Promise<void>}
|
|
108
|
+
*/
|
|
109
|
+
async subscribe(params, subscriber) {
|
|
110
|
+
await this.declareQueue(params.queue);
|
|
111
|
+
const consumer = this.connection.createConsumer({
|
|
112
|
+
queue: params.queue.name,
|
|
113
|
+
requeue: !!params.requeue,
|
|
114
|
+
qos: {
|
|
115
|
+
prefetchSize: params.prefetchSize ?? 0,
|
|
116
|
+
prefetchCount: params.prefetchCount ?? 1
|
|
117
|
+
},
|
|
118
|
+
concurrency: params.concurrency ?? 1,
|
|
119
|
+
lazy: !!params.autoStart
|
|
120
|
+
}, this.internalConsumer.bind(this, params, subscriber));
|
|
121
|
+
this.consumers.add(consumer);
|
|
122
|
+
if (params.id)
|
|
123
|
+
this.consumersById.set(params.id, { isStarted: !!params.autoStart, consumer: consumer });
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* @param {string} id
|
|
127
|
+
* @returns {void}
|
|
128
|
+
*/
|
|
129
|
+
startSubscriber(id) {
|
|
130
|
+
const consumer = this.consumersById.get(id);
|
|
131
|
+
if (!consumer)
|
|
132
|
+
throw new Error(`Subscriber ${id} not found for connection ${this.name}`);
|
|
133
|
+
if (consumer.isStarted)
|
|
134
|
+
return;
|
|
135
|
+
consumer.isStarted = true;
|
|
136
|
+
consumer.consumer.start();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* @returns {void}
|
|
140
|
+
*/
|
|
141
|
+
startPendingSubscribers() {
|
|
142
|
+
for (const consumer of this.consumersById.values()) {
|
|
143
|
+
if (consumer.isStarted)
|
|
144
|
+
continue;
|
|
145
|
+
consumer.isStarted = true;
|
|
146
|
+
consumer.consumer.start();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @param {RabbitmqSubscribeParams} params
|
|
151
|
+
* @param {RabbitmqSubscriber} subscriber
|
|
152
|
+
* @param {AsyncMessage} message
|
|
153
|
+
* @returns {Promise<ConsumerStatus>}
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
async internalConsumer(params, subscriber, message) {
|
|
157
|
+
if (!message.body || (typeof message.body !== 'string' && !Buffer.isBuffer(message.body)))
|
|
158
|
+
return rabbitmq_client_1.ConsumerStatus.DROP;
|
|
159
|
+
const messageBody = Buffer.isBuffer(message.body)
|
|
160
|
+
? message.body.toString('utf-8')
|
|
161
|
+
: typeof message.body === 'string'
|
|
162
|
+
? message.body
|
|
163
|
+
: null;
|
|
164
|
+
if (!messageBody)
|
|
165
|
+
return rabbitmq_client_1.ConsumerStatus.DROP;
|
|
166
|
+
const isJSON = message.contentType === 'application/json';
|
|
167
|
+
let data;
|
|
168
|
+
try {
|
|
169
|
+
data = isJSON ? JSON.parse(messageBody) : messageBody;
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
this.logger.error(`Failed to parse message body as JSON`);
|
|
173
|
+
return rabbitmq_client_1.ConsumerStatus.DROP;
|
|
174
|
+
}
|
|
175
|
+
if (!!params.validation?.schema) {
|
|
176
|
+
const { data: parsed, error, success } = params.validation.schema.safeParse(data);
|
|
177
|
+
if (error || !success)
|
|
178
|
+
return this.mapRabbitmqResponseToConsumerStatus(params.validation.onFail ?? 'drop');
|
|
179
|
+
data = parsed;
|
|
180
|
+
}
|
|
181
|
+
const subscriberName = typeof subscriber === 'function'
|
|
182
|
+
? subscriber.name ?? 'anonymous function'
|
|
183
|
+
: `${subscriber.instance.constructor.name}.${subscriber.methodName}`;
|
|
184
|
+
try {
|
|
185
|
+
const response = await (typeof subscriber === 'function'
|
|
186
|
+
? subscriber(data, message)
|
|
187
|
+
: subscriber.instance[subscriber.methodName](data, message));
|
|
188
|
+
return this.mapRabbitmqResponseToConsumerStatus(response);
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
this.logger.error(`Error in subscriber ${subscriberName} (connection: ${this.name}). Error: ${e.constructor.name}(${e.message ?? ''})`);
|
|
192
|
+
return !!params.requeue ? rabbitmq_client_1.ConsumerStatus.REQUEUE : rabbitmq_client_1.ConsumerStatus.DROP;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* @param {RabbitmqPublishOptions} options
|
|
197
|
+
* @return {Promise<void>}
|
|
198
|
+
*/
|
|
199
|
+
publish(options) {
|
|
200
|
+
if (!this.publisher)
|
|
201
|
+
this.publisher = this.connection.createPublisher();
|
|
202
|
+
return this.publisher.send({
|
|
203
|
+
exchange: options.exchange.name,
|
|
204
|
+
routingKey: options.routingKey,
|
|
205
|
+
durable: options.durable !== undefined ? options.durable : options.exchange.durable,
|
|
206
|
+
contentType: options.type === 'json' ? 'application/json' : undefined,
|
|
207
|
+
expiration: options.ttlMs?.toString() ?? undefined
|
|
208
|
+
}, options.type === 'json' ? JSON.stringify(options.message) : options.message);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* @param {RabbitmqResponse | string} response
|
|
212
|
+
* @return {ConsumerStatus}
|
|
213
|
+
* @protected
|
|
214
|
+
*/
|
|
215
|
+
mapRabbitmqResponseToConsumerStatus(response) {
|
|
216
|
+
response = response.toLowerCase();
|
|
217
|
+
switch (response) {
|
|
218
|
+
case 'ack': return rabbitmq_client_1.ConsumerStatus.ACK;
|
|
219
|
+
case 'requeue': return rabbitmq_client_1.ConsumerStatus.REQUEUE;
|
|
220
|
+
case 'drop': return rabbitmq_client_1.ConsumerStatus.DROP;
|
|
221
|
+
}
|
|
222
|
+
return rabbitmq_client_1.ConsumerStatus.DROP;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* @return {Promise<void>}
|
|
226
|
+
*/
|
|
227
|
+
async close() {
|
|
228
|
+
const promises = [];
|
|
229
|
+
for (const consumer of this.consumers.values())
|
|
230
|
+
promises.push(consumer.close());
|
|
231
|
+
promises.push(this.connection.close());
|
|
232
|
+
await Promise.allSettled(promises);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.Rabbitmq = Rabbitmq;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
3
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
4
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
5
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
6
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
7
|
+
var _, done = false;
|
|
8
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
9
|
+
var context = {};
|
|
10
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
11
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
12
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
13
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
14
|
+
if (kind === "accessor") {
|
|
15
|
+
if (result === void 0) continue;
|
|
16
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
17
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
18
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
19
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
20
|
+
}
|
|
21
|
+
else if (_ = accept(result)) {
|
|
22
|
+
if (kind === "field") initializers.unshift(_);
|
|
23
|
+
else descriptor[key] = _;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
27
|
+
done = true;
|
|
28
|
+
};
|
|
29
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
30
|
+
var useValue = arguments.length > 2;
|
|
31
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
32
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
33
|
+
}
|
|
34
|
+
return useValue ? value : void 0;
|
|
35
|
+
};
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.RabbitmqCoreModule = void 0;
|
|
38
|
+
const common_1 = require("@nestjs/common");
|
|
39
|
+
const rabbitmq_consts_1 = require("./rabbitmq.consts");
|
|
40
|
+
const rabbitmq_helpers_1 = require("./rabbitmq.helpers");
|
|
41
|
+
const rabbitmq_instances_manager_1 = require("./rabbitmq-instances-manager");
|
|
42
|
+
const rabbitmq_explorer_service_1 = require("./rabbitmq-explorer.service");
|
|
43
|
+
const rabbitmq_1 = require("./rabbitmq");
|
|
44
|
+
let RabbitmqCoreModule = (() => {
|
|
45
|
+
let _classDecorators = [(0, common_1.Global)(), (0, common_1.Module)({
|
|
46
|
+
providers: [
|
|
47
|
+
rabbitmq_instances_manager_1.RabbitmqInstancesManager,
|
|
48
|
+
rabbitmq_explorer_service_1.RabbitmqExplorerService
|
|
49
|
+
]
|
|
50
|
+
})];
|
|
51
|
+
let _classDescriptor;
|
|
52
|
+
let _classExtraInitializers = [];
|
|
53
|
+
let _classThis;
|
|
54
|
+
var RabbitmqCoreModule = class {
|
|
55
|
+
static { _classThis = this; }
|
|
56
|
+
static {
|
|
57
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
58
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
59
|
+
RabbitmqCoreModule = _classThis = _classDescriptor.value;
|
|
60
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
61
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @param {RabbitmqForRootParams} params
|
|
65
|
+
* @return {DynamicModule}
|
|
66
|
+
*/
|
|
67
|
+
static forRoot(params) {
|
|
68
|
+
const providers = [];
|
|
69
|
+
const instanceProvider = this.createInstanceProvider(params);
|
|
70
|
+
providers.push(instanceProvider);
|
|
71
|
+
if (params.isDefault)
|
|
72
|
+
providers.push({
|
|
73
|
+
provide: rabbitmq_consts_1.RABBITMQ_INSTANCE_DEFAULT_NAME,
|
|
74
|
+
useExisting: (0, rabbitmq_helpers_1.getInstanceToken)(params)
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
module: RabbitmqCoreModule,
|
|
78
|
+
providers: providers,
|
|
79
|
+
exports: providers
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* @param {RabbitmqForRootParams} params
|
|
84
|
+
* @return {FactoryProvider}
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
static createInstanceProvider(params) {
|
|
88
|
+
const instanceToken = (0, rabbitmq_helpers_1.getInstanceToken)(params);
|
|
89
|
+
return {
|
|
90
|
+
provide: instanceToken,
|
|
91
|
+
useFactory: (im) => {
|
|
92
|
+
const existingInstance = im.getInstance(instanceToken);
|
|
93
|
+
if (existingInstance)
|
|
94
|
+
return existingInstance;
|
|
95
|
+
const instance = new rabbitmq_1.Rabbitmq({
|
|
96
|
+
name: params.name,
|
|
97
|
+
connectionName: params.connectionName,
|
|
98
|
+
hostname: params.hostname,
|
|
99
|
+
port: params.port,
|
|
100
|
+
username: params.username,
|
|
101
|
+
password: params.password,
|
|
102
|
+
vhost: params.vhost,
|
|
103
|
+
connectionTimeout: params.connectionTimeout
|
|
104
|
+
});
|
|
105
|
+
im.addInstance(instanceToken, instance);
|
|
106
|
+
return instance;
|
|
107
|
+
},
|
|
108
|
+
inject: [
|
|
109
|
+
rabbitmq_instances_manager_1.RabbitmqInstancesManager
|
|
110
|
+
]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
return RabbitmqCoreModule = _classThis;
|
|
115
|
+
})();
|
|
116
|
+
exports.RabbitmqCoreModule = RabbitmqCoreModule;
|