poly-bus-rabbitmq 0.4.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 +209 -0
- package/dist/__tests__/mocks/poly-bus.d.ts +60 -0
- package/dist/__tests__/mocks/poly-bus.js +31 -0
- package/dist/__tests__/transport/rabbitmq/rabbitmq-transport.integration.test.d.ts +1 -0
- package/dist/__tests__/transport/rabbitmq/rabbitmq-transport.integration.test.js +230 -0
- package/dist/__tests__/transport/rabbitmq/rabbitmq-transport.test.d.ts +1 -0
- package/dist/__tests__/transport/rabbitmq/rabbitmq-transport.test.js +257 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/index.mjs +254 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.js +260 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/package.json +1 -0
- package/dist/transport/rabbitmq/rabbitmq-config.d.ts +19 -0
- package/dist/transport/rabbitmq/rabbitmq-config.js +31 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +27 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.js +230 -0
- package/package.json +81 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('poly-bus'), require('amqplib')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'poly-bus', 'amqplib'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.PolyBusRabbitMq = {}, global.PolyBus, global.amqplib));
|
|
5
|
+
})(this, (function (exports, polyBus, amqp) { 'use strict';
|
|
6
|
+
|
|
7
|
+
class RabbitMqTransport {
|
|
8
|
+
constructor(config, bus) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.bus = bus;
|
|
11
|
+
this.channel = null;
|
|
12
|
+
this.connection = null;
|
|
13
|
+
this.supportsCommandMessages = true;
|
|
14
|
+
this.supportsDelayedCommands = true;
|
|
15
|
+
this.supportsSubscriptions = true;
|
|
16
|
+
this.deadLetterEndpoint = config.deadLetterQueueName ?? `${bus.name}.dead.letters`;
|
|
17
|
+
this.queueName = config.queueName ?? bus.name;
|
|
18
|
+
}
|
|
19
|
+
async handleMessage(message) {
|
|
20
|
+
if (!message || !this.channel) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const headers = this.readHeaders(message.properties.headers);
|
|
25
|
+
const header = headers.get(polyBus.Headers.MessageType) ?? '';
|
|
26
|
+
const messageInfo = polyBus.MessageInfo.getAttributeFromHeader(header);
|
|
27
|
+
if (!messageInfo) {
|
|
28
|
+
throw new Error(`Missing or invalid message header for message on queue: ${this.queueName}`);
|
|
29
|
+
}
|
|
30
|
+
const incomingMessage = new polyBus.IncomingMessage(this.bus, message.content.toString('utf8'), messageInfo);
|
|
31
|
+
incomingMessage.headers = headers;
|
|
32
|
+
const transaction = await this.bus.createIncomingTransaction(incomingMessage);
|
|
33
|
+
await transaction.commit();
|
|
34
|
+
this.channel.ack(message);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
this.config.log.error(error.message, error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async handle(transaction) {
|
|
41
|
+
const channel = this.channel;
|
|
42
|
+
if (!channel) {
|
|
43
|
+
this.config.log.error(`Cannot send message from: ${this.queueName}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const messages = [];
|
|
47
|
+
for (const outgoingMessage of transaction.outgoingMessages) {
|
|
48
|
+
try {
|
|
49
|
+
const header = this.bus.messages.getHeaderByMessageInfo(outgoingMessage.messageInfo);
|
|
50
|
+
if (header) {
|
|
51
|
+
outgoingMessage.headers.set(polyBus.Headers.MessageType, header);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Ignore missing header registration to mirror .NET null-check semantics.
|
|
56
|
+
}
|
|
57
|
+
let exchange = this.config.directExchangeName;
|
|
58
|
+
if (!outgoingMessage.endpoint) {
|
|
59
|
+
exchange = outgoingMessage.messageInfo.type === polyBus.MessageType.Command
|
|
60
|
+
? this.config.commandExchangeName
|
|
61
|
+
: this.config.eventExchangeName;
|
|
62
|
+
}
|
|
63
|
+
let routingKey = outgoingMessage.endpoint ?? this.getRoutingKey(false, outgoingMessage.messageInfo);
|
|
64
|
+
const deliverAt = outgoingMessage.deliverAt;
|
|
65
|
+
if (deliverAt) {
|
|
66
|
+
const delayInSeconds = Math.floor((deliverAt.getTime() - Date.now()) / 1000);
|
|
67
|
+
if (delayInSeconds > 0) {
|
|
68
|
+
let startingDelayQueue = 0;
|
|
69
|
+
const parts = [];
|
|
70
|
+
for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 0; n -= 1) {
|
|
71
|
+
const enabled = ((delayInSeconds >> n) & 1) === 1;
|
|
72
|
+
if (startingDelayQueue === 0 && enabled) {
|
|
73
|
+
startingDelayQueue = n;
|
|
74
|
+
}
|
|
75
|
+
parts.push(enabled ? '1' : '0');
|
|
76
|
+
}
|
|
77
|
+
parts.push(outgoingMessage.endpoint ?? outgoingMessage.messageInfo.endpoint);
|
|
78
|
+
routingKey = parts.join('.');
|
|
79
|
+
exchange = this.delayQueueName(startingDelayQueue);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
messages.push({
|
|
83
|
+
exchange,
|
|
84
|
+
routingKey,
|
|
85
|
+
body: Buffer.from(outgoingMessage.body, 'utf8'),
|
|
86
|
+
options: {
|
|
87
|
+
headers: Object.fromEntries(outgoingMessage.headers)
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
for (const message of messages) {
|
|
92
|
+
this.config.log.info(`Publishing to ${message.exchange} using routing key ${message.routingKey}`);
|
|
93
|
+
channel.publish(message.exchange, message.routingKey, message.body, message.options);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async start() {
|
|
97
|
+
if (this.channel) {
|
|
98
|
+
this.config.log.error(`RabbitMQ channel is already started for: ${this.queueName}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.config.log.info(`Starting RabbitMQ channel for: ${this.queueName}`);
|
|
102
|
+
const connectOptions = {};
|
|
103
|
+
this.connection = await amqp.connect(this.config.connectionString, connectOptions);
|
|
104
|
+
this.channel = await this.connection.createChannel();
|
|
105
|
+
const channel = this.channel;
|
|
106
|
+
await channel.assertQueue(this.queueName, {
|
|
107
|
+
durable: true,
|
|
108
|
+
exclusive: false,
|
|
109
|
+
autoDelete: false,
|
|
110
|
+
arguments: this.config.queueArguments
|
|
111
|
+
});
|
|
112
|
+
await channel.assertExchange(this.config.commandExchangeName, 'topic', { durable: true });
|
|
113
|
+
await channel.bindQueue(this.queueName, this.config.commandExchangeName, `${this.messageTypeSegment(polyBus.MessageType.Command)}.${this.queueName}.#`);
|
|
114
|
+
await channel.assertExchange(this.config.directExchangeName, 'topic', { durable: true });
|
|
115
|
+
await channel.bindQueue(this.queueName, this.config.directExchangeName, this.queueName);
|
|
116
|
+
await channel.assertExchange(this.config.eventExchangeName, 'topic', { durable: true });
|
|
117
|
+
await channel.assertQueue(this.deadLetterEndpoint, {
|
|
118
|
+
durable: true,
|
|
119
|
+
exclusive: false,
|
|
120
|
+
autoDelete: false,
|
|
121
|
+
arguments: this.config.queueArguments
|
|
122
|
+
});
|
|
123
|
+
await channel.bindQueue(this.deadLetterEndpoint, this.config.directExchangeName, this.deadLetterEndpoint);
|
|
124
|
+
await this.setupDelayQueues();
|
|
125
|
+
await channel.consume(this.queueName, async (message) => {
|
|
126
|
+
await this.handleMessage(message);
|
|
127
|
+
}, { noAck: false });
|
|
128
|
+
}
|
|
129
|
+
async stop() {
|
|
130
|
+
if (this.channel) {
|
|
131
|
+
try {
|
|
132
|
+
await this.channel.close();
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Ignore shutdown errors.
|
|
136
|
+
}
|
|
137
|
+
this.channel = null;
|
|
138
|
+
}
|
|
139
|
+
if (this.connection) {
|
|
140
|
+
try {
|
|
141
|
+
await this.connection.close();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Ignore shutdown errors.
|
|
145
|
+
}
|
|
146
|
+
this.connection = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async subscribe(messageInfo) {
|
|
150
|
+
const channel = this.channel;
|
|
151
|
+
if (!channel) {
|
|
152
|
+
throw new Error('RabbitMQ channel is not initialized');
|
|
153
|
+
}
|
|
154
|
+
await channel.bindQueue(this.queueName, this.config.eventExchangeName, this.getRoutingKey(true, messageInfo));
|
|
155
|
+
}
|
|
156
|
+
async setupDelayQueues() {
|
|
157
|
+
const channel = this.channel;
|
|
158
|
+
if (!channel) {
|
|
159
|
+
throw new Error('RabbitMQ channel is not initialized');
|
|
160
|
+
}
|
|
161
|
+
let routingKey = '1.#';
|
|
162
|
+
for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 0; n -= 1) {
|
|
163
|
+
const queue = this.delayQueueName(n);
|
|
164
|
+
const nextQueue = this.delayQueueName(n - 1);
|
|
165
|
+
await channel.assertExchange(queue, 'topic', { durable: true });
|
|
166
|
+
await channel.assertQueue(queue, {
|
|
167
|
+
durable: true,
|
|
168
|
+
exclusive: false,
|
|
169
|
+
autoDelete: false,
|
|
170
|
+
arguments: {
|
|
171
|
+
'x-queue-type': 'quorum',
|
|
172
|
+
'x-dead-letter-strategy': 'at-least-once',
|
|
173
|
+
'x-overflow': 'reject-publish',
|
|
174
|
+
'x-message-ttl': Math.trunc(Math.pow(2, n) * 1000),
|
|
175
|
+
'x-dead-letter-exchange': n > 0 ? nextQueue : this.config.delayDelivery
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
await channel.bindQueue(queue, queue, routingKey);
|
|
179
|
+
routingKey = `*.${routingKey}`;
|
|
180
|
+
}
|
|
181
|
+
routingKey = '0.#';
|
|
182
|
+
for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 1; n -= 1) {
|
|
183
|
+
const queue = this.delayQueueName(n);
|
|
184
|
+
const nextQueue = this.delayQueueName(n - 1);
|
|
185
|
+
await channel.bindExchange(nextQueue, queue, routingKey);
|
|
186
|
+
routingKey = `*.${routingKey}`;
|
|
187
|
+
}
|
|
188
|
+
await channel.assertExchange(this.config.delayDelivery, 'topic', { durable: true });
|
|
189
|
+
await channel.bindExchange(this.config.delayDelivery, this.delayQueueName(0), routingKey);
|
|
190
|
+
await channel.bindQueue(this.queueName, this.config.delayDelivery, `#.${this.queueName}`);
|
|
191
|
+
}
|
|
192
|
+
getRoutingKey(subscribe, messageInfo) {
|
|
193
|
+
const messageType = this.messageTypeSegment(messageInfo.type);
|
|
194
|
+
if (subscribe) {
|
|
195
|
+
return `${messageType}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.#`;
|
|
196
|
+
}
|
|
197
|
+
return `${messageType}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.${messageInfo.minor}.${messageInfo.patch}`;
|
|
198
|
+
}
|
|
199
|
+
delayQueueName(n) {
|
|
200
|
+
return `${this.config.delayDelivery}.${n.toString().padStart(2, '0')}`;
|
|
201
|
+
}
|
|
202
|
+
readHeaders(headers) {
|
|
203
|
+
const normalized = new Map();
|
|
204
|
+
if (!headers) {
|
|
205
|
+
return normalized;
|
|
206
|
+
}
|
|
207
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
208
|
+
if (typeof value === 'string') {
|
|
209
|
+
normalized.set(key, value);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
213
|
+
normalized.set(key, String(value));
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (value instanceof Buffer) {
|
|
217
|
+
normalized.set(key, value.toString('utf8'));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return normalized;
|
|
221
|
+
}
|
|
222
|
+
messageTypeSegment(messageType) {
|
|
223
|
+
return messageType === polyBus.MessageType.Command ? 'Command' : 'Event';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
RabbitMqTransport.MAX_NUMBER_OF_BITS_TO_USE = 28;
|
|
227
|
+
RabbitMqTransport.DELAY_QUEUE_COUNT = RabbitMqTransport.MAX_NUMBER_OF_BITS_TO_USE - 1;
|
|
228
|
+
|
|
229
|
+
class NoOpLogger {
|
|
230
|
+
info(_message, ..._meta) {
|
|
231
|
+
// Intentionally empty.
|
|
232
|
+
}
|
|
233
|
+
error(_message, ..._meta) {
|
|
234
|
+
// Intentionally empty.
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
class RabbitMqConfig {
|
|
238
|
+
constructor() {
|
|
239
|
+
this.queueArguments = {
|
|
240
|
+
'x-queue-type': 'quorum'
|
|
241
|
+
};
|
|
242
|
+
this.log = new NoOpLogger();
|
|
243
|
+
this.networkRecoveryIntervalMs = 10000;
|
|
244
|
+
this.commandExchangeName = 'polybus.commands';
|
|
245
|
+
this.delayDelivery = 'polybus.delay';
|
|
246
|
+
this.directExchangeName = 'polybus.direct';
|
|
247
|
+
this.eventExchangeName = 'polybus.events';
|
|
248
|
+
this.connectionString = '';
|
|
249
|
+
}
|
|
250
|
+
async create(_builder, bus) {
|
|
251
|
+
this.log.info(`Creating RabbitMQ transport for: ${this.queueName ?? bus.name}`);
|
|
252
|
+
return new RabbitMqTransport(this, bus);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
exports.RabbitMqConfig = RabbitMqConfig;
|
|
257
|
+
exports.RabbitMqTransport = RabbitMqTransport;
|
|
258
|
+
|
|
259
|
+
}));
|
|
260
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/transport/rabbitmq/rabbitmq-transport.ts","../src/transport/rabbitmq/rabbitmq-config.ts"],"sourcesContent":["import {\n Headers,\n IncomingMessage,\n type IPolyBus,\n type ITransport,\n MessageInfo,\n MessageType,\n type Transaction\n} from 'poly-bus';\nimport amqp, { type Channel, type ChannelModel, type ConsumeMessage, type Message } from 'amqplib';\nimport type { Options } from 'amqplib';\nimport type { RabbitMqConfig } from './rabbitmq-config';\n\ninterface PendingPublish {\n exchange: string;\n routingKey: string;\n body: Buffer;\n options: Options.Publish;\n}\n\nexport class RabbitMqTransport implements ITransport {\n static readonly MAX_NUMBER_OF_BITS_TO_USE = 28;\n static readonly DELAY_QUEUE_COUNT = RabbitMqTransport.MAX_NUMBER_OF_BITS_TO_USE - 1;\n\n channel: Channel | null = null;\n connection: ChannelModel | null = null;\n readonly deadLetterEndpoint: string;\n readonly queueName: string;\n readonly supportsCommandMessages = true;\n readonly supportsDelayedCommands = true;\n readonly supportsSubscriptions = true;\n\n constructor(\n readonly config: RabbitMqConfig,\n private readonly bus: IPolyBus\n ) {\n this.deadLetterEndpoint = config.deadLetterQueueName ?? `${bus.name}.dead.letters`;\n this.queueName = config.queueName ?? bus.name;\n }\n\n async handleMessage(message: ConsumeMessage | null): Promise<void> {\n if (!message || !this.channel) {\n return;\n }\n\n try {\n const headers = this.readHeaders(message.properties.headers);\n const header = headers.get(Headers.MessageType) ?? '';\n const messageInfo = MessageInfo.getAttributeFromHeader(header);\n\n if (!messageInfo) {\n throw new Error(`Missing or invalid message header for message on queue: ${this.queueName}`);\n }\n\n const incomingMessage = new IncomingMessage(this.bus, message.content.toString('utf8'), messageInfo);\n incomingMessage.headers = headers;\n\n const transaction = await this.bus.createIncomingTransaction(incomingMessage);\n await transaction.commit();\n this.channel.ack(message);\n } catch (error) {\n this.config.log.error((error as Error).message, error);\n }\n }\n\n async handle(transaction: Transaction): Promise<void> {\n const channel = this.channel;\n if (!channel) {\n this.config.log.error(`Cannot send message from: ${this.queueName}`);\n return;\n }\n\n const messages: PendingPublish[] = [];\n\n for (const outgoingMessage of transaction.outgoingMessages) {\n try {\n const header = this.bus.messages.getHeaderByMessageInfo(outgoingMessage.messageInfo);\n if (header) {\n outgoingMessage.headers.set(Headers.MessageType, header);\n }\n } catch {\n // Ignore missing header registration to mirror .NET null-check semantics.\n }\n\n let exchange = this.config.directExchangeName;\n if (!outgoingMessage.endpoint) {\n exchange = outgoingMessage.messageInfo.type === MessageType.Command\n ? this.config.commandExchangeName\n : this.config.eventExchangeName;\n }\n\n let routingKey = outgoingMessage.endpoint ?? this.getRoutingKey(false, outgoingMessage.messageInfo);\n const deliverAt = outgoingMessage.deliverAt;\n\n if (deliverAt) {\n const delayInSeconds = Math.floor((deliverAt.getTime() - Date.now()) / 1000);\n if (delayInSeconds > 0) {\n let startingDelayQueue = 0;\n const parts: string[] = [];\n\n for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 0; n -= 1) {\n const enabled = ((delayInSeconds >> n) & 1) === 1;\n if (startingDelayQueue === 0 && enabled) {\n startingDelayQueue = n;\n }\n\n parts.push(enabled ? '1' : '0');\n }\n\n parts.push(outgoingMessage.endpoint ?? outgoingMessage.messageInfo.endpoint);\n routingKey = parts.join('.');\n exchange = this.delayQueueName(startingDelayQueue);\n }\n }\n\n messages.push({\n exchange,\n routingKey,\n body: Buffer.from(outgoingMessage.body, 'utf8'),\n options: {\n headers: Object.fromEntries(outgoingMessage.headers)\n }\n });\n }\n\n for (const message of messages) {\n this.config.log.info(`Publishing to ${message.exchange} using routing key ${message.routingKey}`);\n channel.publish(message.exchange, message.routingKey, message.body, message.options);\n }\n }\n\n async start(): Promise<void> {\n if (this.channel) {\n this.config.log.error(`RabbitMQ channel is already started for: ${this.queueName}`);\n return;\n }\n\n this.config.log.info(`Starting RabbitMQ channel for: ${this.queueName}`);\n\n const connectOptions: Options.Connect = {};\n this.connection = await amqp.connect(this.config.connectionString, connectOptions);\n this.channel = await this.connection.createChannel();\n\n const channel = this.channel;\n\n await channel.assertQueue(this.queueName, {\n durable: true,\n exclusive: false,\n autoDelete: false,\n arguments: this.config.queueArguments\n });\n\n await channel.assertExchange(this.config.commandExchangeName, 'topic', { durable: true });\n await channel.bindQueue(this.queueName, this.config.commandExchangeName, `${this.messageTypeSegment(MessageType.Command)}.${this.queueName}.#`);\n\n await channel.assertExchange(this.config.directExchangeName, 'topic', { durable: true });\n await channel.bindQueue(this.queueName, this.config.directExchangeName, this.queueName);\n\n await channel.assertExchange(this.config.eventExchangeName, 'topic', { durable: true });\n\n await channel.assertQueue(this.deadLetterEndpoint, {\n durable: true,\n exclusive: false,\n autoDelete: false,\n arguments: this.config.queueArguments\n });\n await channel.bindQueue(this.deadLetterEndpoint, this.config.directExchangeName, this.deadLetterEndpoint);\n\n await this.setupDelayQueues();\n\n await channel.consume(this.queueName, async (message) => {\n await this.handleMessage(message);\n }, { noAck: false });\n }\n\n async stop(): Promise<void> {\n if (this.channel) {\n try {\n await this.channel.close();\n } catch {\n // Ignore shutdown errors.\n }\n this.channel = null;\n }\n\n if (this.connection) {\n try {\n await this.connection.close();\n } catch {\n // Ignore shutdown errors.\n }\n this.connection = null;\n }\n }\n\n async subscribe(messageInfo: MessageInfo): Promise<void> {\n const channel = this.channel;\n if (!channel) {\n throw new Error('RabbitMQ channel is not initialized');\n }\n\n await channel.bindQueue(this.queueName, this.config.eventExchangeName, this.getRoutingKey(true, messageInfo));\n }\n\n async setupDelayQueues(): Promise<void> {\n const channel = this.channel;\n if (!channel) {\n throw new Error('RabbitMQ channel is not initialized');\n }\n\n let routingKey = '1.#';\n\n for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 0; n -= 1) {\n const queue = this.delayQueueName(n);\n const nextQueue = this.delayQueueName(n - 1);\n\n await channel.assertExchange(queue, 'topic', { durable: true });\n await channel.assertQueue(queue, {\n durable: true,\n exclusive: false,\n autoDelete: false,\n arguments: {\n 'x-queue-type': 'quorum',\n 'x-dead-letter-strategy': 'at-least-once',\n 'x-overflow': 'reject-publish',\n 'x-message-ttl': Math.trunc(Math.pow(2, n) * 1000),\n 'x-dead-letter-exchange': n > 0 ? nextQueue : this.config.delayDelivery\n }\n });\n await channel.bindQueue(queue, queue, routingKey);\n\n routingKey = `*.${routingKey}`;\n }\n\n routingKey = '0.#';\n\n for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 1; n -= 1) {\n const queue = this.delayQueueName(n);\n const nextQueue = this.delayQueueName(n - 1);\n await channel.bindExchange(nextQueue, queue, routingKey);\n\n routingKey = `*.${routingKey}`;\n }\n\n await channel.assertExchange(this.config.delayDelivery, 'topic', { durable: true });\n await channel.bindExchange(this.config.delayDelivery, this.delayQueueName(0), routingKey);\n await channel.bindQueue(this.queueName, this.config.delayDelivery, `#.${this.queueName}`);\n }\n\n getRoutingKey(subscribe: boolean, messageInfo: MessageInfo): string {\n const messageType = this.messageTypeSegment(messageInfo.type);\n\n if (subscribe) {\n return `${messageType}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.#`;\n }\n\n return `${messageType}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.${messageInfo.minor}.${messageInfo.patch}`;\n }\n\n delayQueueName(n: number): string {\n return `${this.config.delayDelivery}.${n.toString().padStart(2, '0')}`;\n }\n\n private readHeaders(headers?: Message['properties']['headers']): Map<string, string> {\n const normalized = new Map<string, string>();\n\n if (!headers) {\n return normalized;\n }\n\n for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {\n if (typeof value === 'string') {\n normalized.set(key, value);\n continue;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n normalized.set(key, String(value));\n continue;\n }\n\n if (value instanceof Buffer) {\n normalized.set(key, value.toString('utf8'));\n }\n }\n\n return normalized;\n }\n\n private messageTypeSegment(messageType: MessageType): string {\n return messageType === MessageType.Command ? 'Command' : 'Event';\n }\n}\n","import type { IPolyBus, ITransport, PolyBusBuilder } from 'poly-bus';\nimport { RabbitMqTransport } from './rabbitmq-transport';\n\nexport interface RabbitMqLogger {\n info(message: string, ...meta: unknown[]): void;\n error(message: string, ...meta: unknown[]): void;\n}\n\nclass NoOpLogger implements RabbitMqLogger {\n info(_message: string, ..._meta: unknown[]): void {\n // Intentionally empty.\n }\n\n error(_message: string, ..._meta: unknown[]): void {\n // Intentionally empty.\n }\n}\n\nexport class RabbitMqConfig {\n readonly queueArguments: Record<string, unknown> = {\n 'x-queue-type': 'quorum'\n };\n\n log: RabbitMqLogger = new NoOpLogger();\n networkRecoveryIntervalMs = 10_000;\n commandExchangeName = 'polybus.commands';\n delayDelivery = 'polybus.delay';\n directExchangeName = 'polybus.direct';\n eventExchangeName = 'polybus.events';\n clientProvidedName?: string;\n connectionString = '';\n deadLetterQueueName?: string;\n queueName?: string;\n\n async create(_builder: PolyBusBuilder, bus: IPolyBus): Promise<ITransport> {\n this.log.info(`Creating RabbitMQ transport for: ${this.queueName ?? bus.name}`);\n return new RabbitMqTransport(this, bus);\n }\n}\n"],"names":["Headers","MessageInfo","IncomingMessage","MessageType"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IPolyBus, ITransport, PolyBusBuilder } from 'poly-bus';
|
|
2
|
+
export interface RabbitMqLogger {
|
|
3
|
+
info(message: string, ...meta: unknown[]): void;
|
|
4
|
+
error(message: string, ...meta: unknown[]): void;
|
|
5
|
+
}
|
|
6
|
+
export declare class RabbitMqConfig {
|
|
7
|
+
readonly queueArguments: Record<string, unknown>;
|
|
8
|
+
log: RabbitMqLogger;
|
|
9
|
+
networkRecoveryIntervalMs: number;
|
|
10
|
+
commandExchangeName: string;
|
|
11
|
+
delayDelivery: string;
|
|
12
|
+
directExchangeName: string;
|
|
13
|
+
eventExchangeName: string;
|
|
14
|
+
clientProvidedName?: string;
|
|
15
|
+
connectionString: string;
|
|
16
|
+
deadLetterQueueName?: string;
|
|
17
|
+
queueName?: string;
|
|
18
|
+
create(_builder: PolyBusBuilder, bus: IPolyBus): Promise<ITransport>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RabbitMqConfig = void 0;
|
|
4
|
+
const rabbitmq_transport_1 = require("./rabbitmq-transport");
|
|
5
|
+
class NoOpLogger {
|
|
6
|
+
info(_message, ..._meta) {
|
|
7
|
+
// Intentionally empty.
|
|
8
|
+
}
|
|
9
|
+
error(_message, ..._meta) {
|
|
10
|
+
// Intentionally empty.
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
class RabbitMqConfig {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.queueArguments = {
|
|
16
|
+
'x-queue-type': 'quorum'
|
|
17
|
+
};
|
|
18
|
+
this.log = new NoOpLogger();
|
|
19
|
+
this.networkRecoveryIntervalMs = 10000;
|
|
20
|
+
this.commandExchangeName = 'polybus.commands';
|
|
21
|
+
this.delayDelivery = 'polybus.delay';
|
|
22
|
+
this.directExchangeName = 'polybus.direct';
|
|
23
|
+
this.eventExchangeName = 'polybus.events';
|
|
24
|
+
this.connectionString = '';
|
|
25
|
+
}
|
|
26
|
+
async create(_builder, bus) {
|
|
27
|
+
this.log.info(`Creating RabbitMQ transport for: ${this.queueName ?? bus.name}`);
|
|
28
|
+
return new rabbitmq_transport_1.RabbitMqTransport(this, bus);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.RabbitMqConfig = RabbitMqConfig;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type IPolyBus, type ITransport, MessageInfo, type Transaction } from 'poly-bus';
|
|
2
|
+
import { type Channel, type ChannelModel, type ConsumeMessage } from 'amqplib';
|
|
3
|
+
import type { RabbitMqConfig } from './rabbitmq-config';
|
|
4
|
+
export declare class RabbitMqTransport implements ITransport {
|
|
5
|
+
readonly config: RabbitMqConfig;
|
|
6
|
+
private readonly bus;
|
|
7
|
+
static readonly MAX_NUMBER_OF_BITS_TO_USE = 28;
|
|
8
|
+
static readonly DELAY_QUEUE_COUNT: number;
|
|
9
|
+
channel: Channel | null;
|
|
10
|
+
connection: ChannelModel | null;
|
|
11
|
+
readonly deadLetterEndpoint: string;
|
|
12
|
+
readonly queueName: string;
|
|
13
|
+
readonly supportsCommandMessages = true;
|
|
14
|
+
readonly supportsDelayedCommands = true;
|
|
15
|
+
readonly supportsSubscriptions = true;
|
|
16
|
+
constructor(config: RabbitMqConfig, bus: IPolyBus);
|
|
17
|
+
handleMessage(message: ConsumeMessage | null): Promise<void>;
|
|
18
|
+
handle(transaction: Transaction): Promise<void>;
|
|
19
|
+
start(): Promise<void>;
|
|
20
|
+
stop(): Promise<void>;
|
|
21
|
+
subscribe(messageInfo: MessageInfo): Promise<void>;
|
|
22
|
+
setupDelayQueues(): Promise<void>;
|
|
23
|
+
getRoutingKey(subscribe: boolean, messageInfo: MessageInfo): string;
|
|
24
|
+
delayQueueName(n: number): string;
|
|
25
|
+
private readHeaders;
|
|
26
|
+
private messageTypeSegment;
|
|
27
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RabbitMqTransport = void 0;
|
|
7
|
+
const poly_bus_1 = require("poly-bus");
|
|
8
|
+
const amqplib_1 = __importDefault(require("amqplib"));
|
|
9
|
+
class RabbitMqTransport {
|
|
10
|
+
constructor(config, bus) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.bus = bus;
|
|
13
|
+
this.channel = null;
|
|
14
|
+
this.connection = null;
|
|
15
|
+
this.supportsCommandMessages = true;
|
|
16
|
+
this.supportsDelayedCommands = true;
|
|
17
|
+
this.supportsSubscriptions = true;
|
|
18
|
+
this.deadLetterEndpoint = config.deadLetterQueueName ?? `${bus.name}.dead.letters`;
|
|
19
|
+
this.queueName = config.queueName ?? bus.name;
|
|
20
|
+
}
|
|
21
|
+
async handleMessage(message) {
|
|
22
|
+
if (!message || !this.channel) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const headers = this.readHeaders(message.properties.headers);
|
|
27
|
+
const header = headers.get(poly_bus_1.Headers.MessageType) ?? '';
|
|
28
|
+
const messageInfo = poly_bus_1.MessageInfo.getAttributeFromHeader(header);
|
|
29
|
+
if (!messageInfo) {
|
|
30
|
+
throw new Error(`Missing or invalid message header for message on queue: ${this.queueName}`);
|
|
31
|
+
}
|
|
32
|
+
const incomingMessage = new poly_bus_1.IncomingMessage(this.bus, message.content.toString('utf8'), messageInfo);
|
|
33
|
+
incomingMessage.headers = headers;
|
|
34
|
+
const transaction = await this.bus.createIncomingTransaction(incomingMessage);
|
|
35
|
+
await transaction.commit();
|
|
36
|
+
this.channel.ack(message);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
this.config.log.error(error.message, error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async handle(transaction) {
|
|
43
|
+
const channel = this.channel;
|
|
44
|
+
if (!channel) {
|
|
45
|
+
this.config.log.error(`Cannot send message from: ${this.queueName}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const messages = [];
|
|
49
|
+
for (const outgoingMessage of transaction.outgoingMessages) {
|
|
50
|
+
try {
|
|
51
|
+
const header = this.bus.messages.getHeaderByMessageInfo(outgoingMessage.messageInfo);
|
|
52
|
+
if (header) {
|
|
53
|
+
outgoingMessage.headers.set(poly_bus_1.Headers.MessageType, header);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Ignore missing header registration to mirror .NET null-check semantics.
|
|
58
|
+
}
|
|
59
|
+
let exchange = this.config.directExchangeName;
|
|
60
|
+
if (!outgoingMessage.endpoint) {
|
|
61
|
+
exchange = outgoingMessage.messageInfo.type === poly_bus_1.MessageType.Command
|
|
62
|
+
? this.config.commandExchangeName
|
|
63
|
+
: this.config.eventExchangeName;
|
|
64
|
+
}
|
|
65
|
+
let routingKey = outgoingMessage.endpoint ?? this.getRoutingKey(false, outgoingMessage.messageInfo);
|
|
66
|
+
const deliverAt = outgoingMessage.deliverAt;
|
|
67
|
+
if (deliverAt) {
|
|
68
|
+
const delayInSeconds = Math.floor((deliverAt.getTime() - Date.now()) / 1000);
|
|
69
|
+
if (delayInSeconds > 0) {
|
|
70
|
+
let startingDelayQueue = 0;
|
|
71
|
+
const parts = [];
|
|
72
|
+
for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 0; n -= 1) {
|
|
73
|
+
const enabled = ((delayInSeconds >> n) & 1) === 1;
|
|
74
|
+
if (startingDelayQueue === 0 && enabled) {
|
|
75
|
+
startingDelayQueue = n;
|
|
76
|
+
}
|
|
77
|
+
parts.push(enabled ? '1' : '0');
|
|
78
|
+
}
|
|
79
|
+
parts.push(outgoingMessage.endpoint ?? outgoingMessage.messageInfo.endpoint);
|
|
80
|
+
routingKey = parts.join('.');
|
|
81
|
+
exchange = this.delayQueueName(startingDelayQueue);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
messages.push({
|
|
85
|
+
exchange,
|
|
86
|
+
routingKey,
|
|
87
|
+
body: Buffer.from(outgoingMessage.body, 'utf8'),
|
|
88
|
+
options: {
|
|
89
|
+
headers: Object.fromEntries(outgoingMessage.headers)
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
for (const message of messages) {
|
|
94
|
+
this.config.log.info(`Publishing to ${message.exchange} using routing key ${message.routingKey}`);
|
|
95
|
+
channel.publish(message.exchange, message.routingKey, message.body, message.options);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async start() {
|
|
99
|
+
if (this.channel) {
|
|
100
|
+
this.config.log.error(`RabbitMQ channel is already started for: ${this.queueName}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.config.log.info(`Starting RabbitMQ channel for: ${this.queueName}`);
|
|
104
|
+
const connectOptions = {};
|
|
105
|
+
this.connection = await amqplib_1.default.connect(this.config.connectionString, connectOptions);
|
|
106
|
+
this.channel = await this.connection.createChannel();
|
|
107
|
+
const channel = this.channel;
|
|
108
|
+
await channel.assertQueue(this.queueName, {
|
|
109
|
+
durable: true,
|
|
110
|
+
exclusive: false,
|
|
111
|
+
autoDelete: false,
|
|
112
|
+
arguments: this.config.queueArguments
|
|
113
|
+
});
|
|
114
|
+
await channel.assertExchange(this.config.commandExchangeName, 'topic', { durable: true });
|
|
115
|
+
await channel.bindQueue(this.queueName, this.config.commandExchangeName, `${this.messageTypeSegment(poly_bus_1.MessageType.Command)}.${this.queueName}.#`);
|
|
116
|
+
await channel.assertExchange(this.config.directExchangeName, 'topic', { durable: true });
|
|
117
|
+
await channel.bindQueue(this.queueName, this.config.directExchangeName, this.queueName);
|
|
118
|
+
await channel.assertExchange(this.config.eventExchangeName, 'topic', { durable: true });
|
|
119
|
+
await channel.assertQueue(this.deadLetterEndpoint, {
|
|
120
|
+
durable: true,
|
|
121
|
+
exclusive: false,
|
|
122
|
+
autoDelete: false,
|
|
123
|
+
arguments: this.config.queueArguments
|
|
124
|
+
});
|
|
125
|
+
await channel.bindQueue(this.deadLetterEndpoint, this.config.directExchangeName, this.deadLetterEndpoint);
|
|
126
|
+
await this.setupDelayQueues();
|
|
127
|
+
await channel.consume(this.queueName, async (message) => {
|
|
128
|
+
await this.handleMessage(message);
|
|
129
|
+
}, { noAck: false });
|
|
130
|
+
}
|
|
131
|
+
async stop() {
|
|
132
|
+
if (this.channel) {
|
|
133
|
+
try {
|
|
134
|
+
await this.channel.close();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Ignore shutdown errors.
|
|
138
|
+
}
|
|
139
|
+
this.channel = null;
|
|
140
|
+
}
|
|
141
|
+
if (this.connection) {
|
|
142
|
+
try {
|
|
143
|
+
await this.connection.close();
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Ignore shutdown errors.
|
|
147
|
+
}
|
|
148
|
+
this.connection = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async subscribe(messageInfo) {
|
|
152
|
+
const channel = this.channel;
|
|
153
|
+
if (!channel) {
|
|
154
|
+
throw new Error('RabbitMQ channel is not initialized');
|
|
155
|
+
}
|
|
156
|
+
await channel.bindQueue(this.queueName, this.config.eventExchangeName, this.getRoutingKey(true, messageInfo));
|
|
157
|
+
}
|
|
158
|
+
async setupDelayQueues() {
|
|
159
|
+
const channel = this.channel;
|
|
160
|
+
if (!channel) {
|
|
161
|
+
throw new Error('RabbitMQ channel is not initialized');
|
|
162
|
+
}
|
|
163
|
+
let routingKey = '1.#';
|
|
164
|
+
for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 0; n -= 1) {
|
|
165
|
+
const queue = this.delayQueueName(n);
|
|
166
|
+
const nextQueue = this.delayQueueName(n - 1);
|
|
167
|
+
await channel.assertExchange(queue, 'topic', { durable: true });
|
|
168
|
+
await channel.assertQueue(queue, {
|
|
169
|
+
durable: true,
|
|
170
|
+
exclusive: false,
|
|
171
|
+
autoDelete: false,
|
|
172
|
+
arguments: {
|
|
173
|
+
'x-queue-type': 'quorum',
|
|
174
|
+
'x-dead-letter-strategy': 'at-least-once',
|
|
175
|
+
'x-overflow': 'reject-publish',
|
|
176
|
+
'x-message-ttl': Math.trunc(Math.pow(2, n) * 1000),
|
|
177
|
+
'x-dead-letter-exchange': n > 0 ? nextQueue : this.config.delayDelivery
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
await channel.bindQueue(queue, queue, routingKey);
|
|
181
|
+
routingKey = `*.${routingKey}`;
|
|
182
|
+
}
|
|
183
|
+
routingKey = '0.#';
|
|
184
|
+
for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 1; n -= 1) {
|
|
185
|
+
const queue = this.delayQueueName(n);
|
|
186
|
+
const nextQueue = this.delayQueueName(n - 1);
|
|
187
|
+
await channel.bindExchange(nextQueue, queue, routingKey);
|
|
188
|
+
routingKey = `*.${routingKey}`;
|
|
189
|
+
}
|
|
190
|
+
await channel.assertExchange(this.config.delayDelivery, 'topic', { durable: true });
|
|
191
|
+
await channel.bindExchange(this.config.delayDelivery, this.delayQueueName(0), routingKey);
|
|
192
|
+
await channel.bindQueue(this.queueName, this.config.delayDelivery, `#.${this.queueName}`);
|
|
193
|
+
}
|
|
194
|
+
getRoutingKey(subscribe, messageInfo) {
|
|
195
|
+
const messageType = this.messageTypeSegment(messageInfo.type);
|
|
196
|
+
if (subscribe) {
|
|
197
|
+
return `${messageType}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.#`;
|
|
198
|
+
}
|
|
199
|
+
return `${messageType}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.${messageInfo.minor}.${messageInfo.patch}`;
|
|
200
|
+
}
|
|
201
|
+
delayQueueName(n) {
|
|
202
|
+
return `${this.config.delayDelivery}.${n.toString().padStart(2, '0')}`;
|
|
203
|
+
}
|
|
204
|
+
readHeaders(headers) {
|
|
205
|
+
const normalized = new Map();
|
|
206
|
+
if (!headers) {
|
|
207
|
+
return normalized;
|
|
208
|
+
}
|
|
209
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
210
|
+
if (typeof value === 'string') {
|
|
211
|
+
normalized.set(key, value);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
215
|
+
normalized.set(key, String(value));
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (value instanceof Buffer) {
|
|
219
|
+
normalized.set(key, value.toString('utf8'));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return normalized;
|
|
223
|
+
}
|
|
224
|
+
messageTypeSegment(messageType) {
|
|
225
|
+
return messageType === poly_bus_1.MessageType.Command ? 'Command' : 'Event';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
exports.RabbitMqTransport = RabbitMqTransport;
|
|
229
|
+
RabbitMqTransport.MAX_NUMBER_OF_BITS_TO_USE = 28;
|
|
230
|
+
RabbitMqTransport.DELAY_QUEUE_COUNT = RabbitMqTransport.MAX_NUMBER_OF_BITS_TO_USE - 1;
|