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.
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const poly_bus_1 = require("poly-bus");
5
+ const rabbitmq_config_1 = require("../../../transport/rabbitmq/rabbitmq-config");
6
+ const rabbitmq_transport_1 = require("../../../transport/rabbitmq/rabbitmq-transport");
7
+ function createBusStub() {
8
+ return {
9
+ name: 'test-service',
10
+ properties: new Map(),
11
+ transport: {},
12
+ incomingPipeline: [],
13
+ outgoingPipeline: [],
14
+ messages: {
15
+ getHeaderByMessageInfo: () => 'Command.orders.created.1.0.0',
16
+ getTypeByMessageInfo: () => class TestMessage {
17
+ },
18
+ getMessageInfo: () => new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, 'orders', 'created', 1, 0, 0),
19
+ add: () => new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, 'orders', 'created', 1, 0, 0)
20
+ },
21
+ createIncomingTransaction: async (_message) => ({
22
+ commit: async () => undefined
23
+ }),
24
+ createOutgoingTransaction: async () => ({
25
+ outgoingMessages: []
26
+ }),
27
+ send: async (_transaction) => undefined,
28
+ start: async () => undefined,
29
+ stop: async () => undefined
30
+ };
31
+ }
32
+ (0, globals_1.describe)('RabbitMqTransport', () => {
33
+ (0, globals_1.it)('formats publish routing keys like the .NET transport', () => {
34
+ const config = new rabbitmq_config_1.RabbitMqConfig();
35
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
36
+ const info = new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, 'orders', 'created', 1, 2, 3);
37
+ (0, globals_1.expect)(transport.getRoutingKey(false, info)).toBe('Command.orders.created.1.2.3');
38
+ });
39
+ (0, globals_1.it)('formats subscribe routing keys with wildcard suffix', () => {
40
+ const config = new rabbitmq_config_1.RabbitMqConfig();
41
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
42
+ const info = new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Event, 'billing', 'paid', 2, 0, 0);
43
+ (0, globals_1.expect)(transport.getRoutingKey(true, info)).toBe('Event.billing.paid.2.#');
44
+ });
45
+ (0, globals_1.it)('formats delay queue names with 2-digit suffix', () => {
46
+ const config = new rabbitmq_config_1.RabbitMqConfig();
47
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
48
+ (0, globals_1.expect)(transport.delayQueueName(0)).toBe('polybus.delay.00');
49
+ (0, globals_1.expect)(transport.delayQueueName(7)).toBe('polybus.delay.07');
50
+ (0, globals_1.expect)(transport.delayQueueName(27)).toBe('polybus.delay.27');
51
+ });
52
+ (0, globals_1.it)('publishes delayed messages to delay exchange chain', async () => {
53
+ const config = new rabbitmq_config_1.RabbitMqConfig();
54
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
55
+ const publish = globals_1.jest.fn(() => true);
56
+ transport.channel = { publish };
57
+ const outgoing = {
58
+ messageInfo: new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, 'orders', 'created', 1, 0, 0),
59
+ headers: new Map(),
60
+ endpoint: undefined,
61
+ body: JSON.stringify({ id: 'abc' }),
62
+ deliverAt: new Date(Date.now() + 5000)
63
+ };
64
+ const transaction = {
65
+ outgoingMessages: [outgoing]
66
+ };
67
+ await transport.handle(transaction);
68
+ (0, globals_1.expect)(publish).toHaveBeenCalledTimes(1);
69
+ const [exchange, routingKey] = publish.mock.calls[0];
70
+ (0, globals_1.expect)(exchange).toBe('polybus.delay.02');
71
+ (0, globals_1.expect)(String(routingKey).endsWith('.orders')).toBe(true);
72
+ });
73
+ (0, globals_1.it)('logs and returns when handling outgoing messages without a channel', async () => {
74
+ const config = new rabbitmq_config_1.RabbitMqConfig();
75
+ const error = globals_1.jest.fn();
76
+ config.log = { info: globals_1.jest.fn(), error };
77
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
78
+ await transport.handle({ outgoingMessages: [] });
79
+ (0, globals_1.expect)(error).toHaveBeenCalledWith('Cannot send message from: test-service');
80
+ });
81
+ (0, globals_1.it)('uses direct exchange when endpoint override is provided', async () => {
82
+ const config = new rabbitmq_config_1.RabbitMqConfig();
83
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
84
+ const publish = globals_1.jest.fn(() => true);
85
+ transport.channel = { publish };
86
+ const transaction = {
87
+ outgoingMessages: [
88
+ {
89
+ messageInfo: new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Event, 'billing', 'paid', 1, 0, 0),
90
+ headers: new Map(),
91
+ endpoint: 'billing-service',
92
+ body: '{}',
93
+ deliverAt: undefined
94
+ }
95
+ ]
96
+ };
97
+ await transport.handle(transaction);
98
+ const [exchange, routingKey] = publish.mock.calls[0];
99
+ (0, globals_1.expect)(exchange).toBe('polybus.direct');
100
+ (0, globals_1.expect)(routingKey).toBe('billing-service');
101
+ });
102
+ (0, globals_1.it)('falls back to command and event exchanges when endpoint is not provided', async () => {
103
+ const config = new rabbitmq_config_1.RabbitMqConfig();
104
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
105
+ const publish = globals_1.jest.fn(() => true);
106
+ transport.channel = { publish };
107
+ const transaction = {
108
+ outgoingMessages: [
109
+ {
110
+ messageInfo: new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, 'orders', 'created', 1, 0, 0),
111
+ headers: new Map(),
112
+ endpoint: undefined,
113
+ body: '{}',
114
+ deliverAt: undefined
115
+ },
116
+ {
117
+ messageInfo: new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Event, 'billing', 'paid', 1, 0, 0),
118
+ headers: new Map(),
119
+ endpoint: undefined,
120
+ body: '{}',
121
+ deliverAt: undefined
122
+ }
123
+ ]
124
+ };
125
+ await transport.handle(transaction);
126
+ const firstCall = publish.mock.calls[0];
127
+ const secondCall = publish.mock.calls[1];
128
+ const firstExchange = firstCall[0];
129
+ const secondExchange = secondCall[0];
130
+ (0, globals_1.expect)(firstExchange).toBe('polybus.commands');
131
+ (0, globals_1.expect)(secondExchange).toBe('polybus.events');
132
+ });
133
+ (0, globals_1.it)('continues publishing when header lookup throws', async () => {
134
+ const config = new rabbitmq_config_1.RabbitMqConfig();
135
+ const bus = createBusStub();
136
+ bus.messages.getHeaderByMessageInfo = () => {
137
+ throw new Error('missing');
138
+ };
139
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, bus);
140
+ const publish = globals_1.jest.fn(() => true);
141
+ transport.channel = { publish };
142
+ const transaction = {
143
+ outgoingMessages: [
144
+ {
145
+ messageInfo: new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, 'orders', 'created', 1, 0, 0),
146
+ headers: new Map(),
147
+ endpoint: undefined,
148
+ body: '{}',
149
+ deliverAt: undefined
150
+ }
151
+ ]
152
+ };
153
+ await transport.handle(transaction);
154
+ (0, globals_1.expect)(publish).toHaveBeenCalledTimes(1);
155
+ const publishCall = publish.mock.calls[0];
156
+ const options = publishCall[3];
157
+ (0, globals_1.expect)(options.headers[poly_bus_1.Headers.MessageType]).toBeUndefined();
158
+ });
159
+ (0, globals_1.it)('acks incoming messages after successful commit and normalizes supported header types', async () => {
160
+ const config = new rabbitmq_config_1.RabbitMqConfig();
161
+ const bus = createBusStub();
162
+ const createIncomingTransaction = globals_1.jest.fn(async (_message) => ({
163
+ commit: async () => undefined
164
+ }));
165
+ bus.createIncomingTransaction = createIncomingTransaction;
166
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, bus);
167
+ const ack = globals_1.jest.fn();
168
+ transport.channel = { ack };
169
+ const messageInfo = new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, 'orders', 'created', 1, 0, 0);
170
+ const getAttributeFromHeaderSpy = globals_1.jest
171
+ .spyOn(poly_bus_1.MessageInfo, 'getAttributeFromHeader')
172
+ .mockReturnValue(messageInfo);
173
+ const message = {
174
+ content: Buffer.from('{"id":"abc"}', 'utf8'),
175
+ properties: {
176
+ headers: {
177
+ [poly_bus_1.Headers.MessageType]: bus.messages.getHeaderByMessageInfo(messageInfo),
178
+ numberHeader: 7,
179
+ booleanHeader: true,
180
+ bufferHeader: Buffer.from('buf', 'utf8'),
181
+ unsupported: { nested: true }
182
+ }
183
+ }
184
+ };
185
+ await transport.handleMessage(message);
186
+ (0, globals_1.expect)(createIncomingTransaction).toHaveBeenCalledTimes(1);
187
+ const incoming = createIncomingTransaction.mock.calls[0]?.[0];
188
+ (0, globals_1.expect)(incoming).toBeInstanceOf(poly_bus_1.IncomingMessage);
189
+ (0, globals_1.expect)(incoming.headers.get('numberHeader')).toBe('7');
190
+ (0, globals_1.expect)(incoming.headers.get('booleanHeader')).toBe('true');
191
+ (0, globals_1.expect)(incoming.headers.get('bufferHeader')).toBe('buf');
192
+ (0, globals_1.expect)(incoming.headers.has('unsupported')).toBe(false);
193
+ (0, globals_1.expect)(ack).toHaveBeenCalledTimes(1);
194
+ getAttributeFromHeaderSpy.mockRestore();
195
+ });
196
+ (0, globals_1.it)('logs an error for incoming messages with missing or invalid message type header', async () => {
197
+ const config = new rabbitmq_config_1.RabbitMqConfig();
198
+ const error = globals_1.jest.fn();
199
+ config.log = { info: globals_1.jest.fn(), error };
200
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
201
+ transport.channel = { ack: globals_1.jest.fn() };
202
+ const message = {
203
+ content: Buffer.from('{}', 'utf8'),
204
+ properties: {
205
+ headers: {}
206
+ }
207
+ };
208
+ await transport.handleMessage(message);
209
+ (0, globals_1.expect)(error).toHaveBeenCalledTimes(1);
210
+ const [loggedMessage] = error.mock.calls[0];
211
+ (0, globals_1.expect)(loggedMessage).toContain('Missing or invalid message header');
212
+ });
213
+ (0, globals_1.it)('skips incoming handling when message is null', async () => {
214
+ const config = new rabbitmq_config_1.RabbitMqConfig();
215
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
216
+ transport.channel = { ack: globals_1.jest.fn() };
217
+ await transport.handleMessage(null);
218
+ (0, globals_1.expect)(transport.channel.ack).not.toHaveBeenCalled();
219
+ });
220
+ (0, globals_1.it)('throws when subscribing without an initialized channel', async () => {
221
+ const config = new rabbitmq_config_1.RabbitMqConfig();
222
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
223
+ const info = new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Event, 'billing', 'paid', 1, 0, 0);
224
+ await (0, globals_1.expect)(transport.subscribe(info)).rejects.toThrow('RabbitMQ channel is not initialized');
225
+ });
226
+ (0, globals_1.it)('binds the queue to the event exchange when subscribing', async () => {
227
+ const config = new rabbitmq_config_1.RabbitMqConfig();
228
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
229
+ const bindQueue = globals_1.jest.fn(async () => undefined);
230
+ transport.channel = { bindQueue };
231
+ const info = new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Event, 'billing', 'paid', 1, 0, 0);
232
+ await transport.subscribe(info);
233
+ (0, globals_1.expect)(bindQueue).toHaveBeenCalledWith('test-service', 'polybus.events', 'Event.billing.paid.1.#');
234
+ });
235
+ (0, globals_1.it)('throws when setting up delay queues without an initialized channel', async () => {
236
+ const config = new rabbitmq_config_1.RabbitMqConfig();
237
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
238
+ await (0, globals_1.expect)(transport.setupDelayQueues()).rejects.toThrow('RabbitMQ channel is not initialized');
239
+ });
240
+ (0, globals_1.it)('clears channel and connection even when close throws', async () => {
241
+ const config = new rabbitmq_config_1.RabbitMqConfig();
242
+ const transport = new rabbitmq_transport_1.RabbitMqTransport(config, createBusStub());
243
+ transport.channel = {
244
+ close: globals_1.jest.fn(async () => {
245
+ throw new Error('close channel failure');
246
+ })
247
+ };
248
+ transport.connection = {
249
+ close: globals_1.jest.fn(async () => {
250
+ throw new Error('close connection failure');
251
+ })
252
+ };
253
+ await transport.stop();
254
+ (0, globals_1.expect)(transport.channel).toBeNull();
255
+ (0, globals_1.expect)(transport.connection).toBeNull();
256
+ });
257
+ });
@@ -0,0 +1,2 @@
1
+ export { RabbitMqConfig, type RabbitMqLogger } from './transport/rabbitmq/rabbitmq-config';
2
+ export { RabbitMqTransport } from './transport/rabbitmq/rabbitmq-transport';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RabbitMqTransport = exports.RabbitMqConfig = void 0;
4
+ var rabbitmq_config_1 = require("./transport/rabbitmq/rabbitmq-config");
5
+ Object.defineProperty(exports, "RabbitMqConfig", { enumerable: true, get: function () { return rabbitmq_config_1.RabbitMqConfig; } });
6
+ var rabbitmq_transport_1 = require("./transport/rabbitmq/rabbitmq-transport");
7
+ Object.defineProperty(exports, "RabbitMqTransport", { enumerable: true, get: function () { return rabbitmq_transport_1.RabbitMqTransport; } });
package/dist/index.mjs ADDED
@@ -0,0 +1,254 @@
1
+ import { Headers, MessageInfo, IncomingMessage, MessageType } from 'poly-bus';
2
+ import amqp from 'amqplib';
3
+
4
+ class RabbitMqTransport {
5
+ constructor(config, bus) {
6
+ this.config = config;
7
+ this.bus = bus;
8
+ this.channel = null;
9
+ this.connection = null;
10
+ this.supportsCommandMessages = true;
11
+ this.supportsDelayedCommands = true;
12
+ this.supportsSubscriptions = true;
13
+ this.deadLetterEndpoint = config.deadLetterQueueName ?? `${bus.name}.dead.letters`;
14
+ this.queueName = config.queueName ?? bus.name;
15
+ }
16
+ async handleMessage(message) {
17
+ if (!message || !this.channel) {
18
+ return;
19
+ }
20
+ try {
21
+ const headers = this.readHeaders(message.properties.headers);
22
+ const header = headers.get(Headers.MessageType) ?? '';
23
+ const messageInfo = MessageInfo.getAttributeFromHeader(header);
24
+ if (!messageInfo) {
25
+ throw new Error(`Missing or invalid message header for message on queue: ${this.queueName}`);
26
+ }
27
+ const incomingMessage = new IncomingMessage(this.bus, message.content.toString('utf8'), messageInfo);
28
+ incomingMessage.headers = headers;
29
+ const transaction = await this.bus.createIncomingTransaction(incomingMessage);
30
+ await transaction.commit();
31
+ this.channel.ack(message);
32
+ }
33
+ catch (error) {
34
+ this.config.log.error(error.message, error);
35
+ }
36
+ }
37
+ async handle(transaction) {
38
+ const channel = this.channel;
39
+ if (!channel) {
40
+ this.config.log.error(`Cannot send message from: ${this.queueName}`);
41
+ return;
42
+ }
43
+ const messages = [];
44
+ for (const outgoingMessage of transaction.outgoingMessages) {
45
+ try {
46
+ const header = this.bus.messages.getHeaderByMessageInfo(outgoingMessage.messageInfo);
47
+ if (header) {
48
+ outgoingMessage.headers.set(Headers.MessageType, header);
49
+ }
50
+ }
51
+ catch {
52
+ // Ignore missing header registration to mirror .NET null-check semantics.
53
+ }
54
+ let exchange = this.config.directExchangeName;
55
+ if (!outgoingMessage.endpoint) {
56
+ exchange = outgoingMessage.messageInfo.type === MessageType.Command
57
+ ? this.config.commandExchangeName
58
+ : this.config.eventExchangeName;
59
+ }
60
+ let routingKey = outgoingMessage.endpoint ?? this.getRoutingKey(false, outgoingMessage.messageInfo);
61
+ const deliverAt = outgoingMessage.deliverAt;
62
+ if (deliverAt) {
63
+ const delayInSeconds = Math.floor((deliverAt.getTime() - Date.now()) / 1000);
64
+ if (delayInSeconds > 0) {
65
+ let startingDelayQueue = 0;
66
+ const parts = [];
67
+ for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 0; n -= 1) {
68
+ const enabled = ((delayInSeconds >> n) & 1) === 1;
69
+ if (startingDelayQueue === 0 && enabled) {
70
+ startingDelayQueue = n;
71
+ }
72
+ parts.push(enabled ? '1' : '0');
73
+ }
74
+ parts.push(outgoingMessage.endpoint ?? outgoingMessage.messageInfo.endpoint);
75
+ routingKey = parts.join('.');
76
+ exchange = this.delayQueueName(startingDelayQueue);
77
+ }
78
+ }
79
+ messages.push({
80
+ exchange,
81
+ routingKey,
82
+ body: Buffer.from(outgoingMessage.body, 'utf8'),
83
+ options: {
84
+ headers: Object.fromEntries(outgoingMessage.headers)
85
+ }
86
+ });
87
+ }
88
+ for (const message of messages) {
89
+ this.config.log.info(`Publishing to ${message.exchange} using routing key ${message.routingKey}`);
90
+ channel.publish(message.exchange, message.routingKey, message.body, message.options);
91
+ }
92
+ }
93
+ async start() {
94
+ if (this.channel) {
95
+ this.config.log.error(`RabbitMQ channel is already started for: ${this.queueName}`);
96
+ return;
97
+ }
98
+ this.config.log.info(`Starting RabbitMQ channel for: ${this.queueName}`);
99
+ const connectOptions = {};
100
+ this.connection = await amqp.connect(this.config.connectionString, connectOptions);
101
+ this.channel = await this.connection.createChannel();
102
+ const channel = this.channel;
103
+ await channel.assertQueue(this.queueName, {
104
+ durable: true,
105
+ exclusive: false,
106
+ autoDelete: false,
107
+ arguments: this.config.queueArguments
108
+ });
109
+ await channel.assertExchange(this.config.commandExchangeName, 'topic', { durable: true });
110
+ await channel.bindQueue(this.queueName, this.config.commandExchangeName, `${this.messageTypeSegment(MessageType.Command)}.${this.queueName}.#`);
111
+ await channel.assertExchange(this.config.directExchangeName, 'topic', { durable: true });
112
+ await channel.bindQueue(this.queueName, this.config.directExchangeName, this.queueName);
113
+ await channel.assertExchange(this.config.eventExchangeName, 'topic', { durable: true });
114
+ await channel.assertQueue(this.deadLetterEndpoint, {
115
+ durable: true,
116
+ exclusive: false,
117
+ autoDelete: false,
118
+ arguments: this.config.queueArguments
119
+ });
120
+ await channel.bindQueue(this.deadLetterEndpoint, this.config.directExchangeName, this.deadLetterEndpoint);
121
+ await this.setupDelayQueues();
122
+ await channel.consume(this.queueName, async (message) => {
123
+ await this.handleMessage(message);
124
+ }, { noAck: false });
125
+ }
126
+ async stop() {
127
+ if (this.channel) {
128
+ try {
129
+ await this.channel.close();
130
+ }
131
+ catch {
132
+ // Ignore shutdown errors.
133
+ }
134
+ this.channel = null;
135
+ }
136
+ if (this.connection) {
137
+ try {
138
+ await this.connection.close();
139
+ }
140
+ catch {
141
+ // Ignore shutdown errors.
142
+ }
143
+ this.connection = null;
144
+ }
145
+ }
146
+ async subscribe(messageInfo) {
147
+ const channel = this.channel;
148
+ if (!channel) {
149
+ throw new Error('RabbitMQ channel is not initialized');
150
+ }
151
+ await channel.bindQueue(this.queueName, this.config.eventExchangeName, this.getRoutingKey(true, messageInfo));
152
+ }
153
+ async setupDelayQueues() {
154
+ const channel = this.channel;
155
+ if (!channel) {
156
+ throw new Error('RabbitMQ channel is not initialized');
157
+ }
158
+ let routingKey = '1.#';
159
+ for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 0; n -= 1) {
160
+ const queue = this.delayQueueName(n);
161
+ const nextQueue = this.delayQueueName(n - 1);
162
+ await channel.assertExchange(queue, 'topic', { durable: true });
163
+ await channel.assertQueue(queue, {
164
+ durable: true,
165
+ exclusive: false,
166
+ autoDelete: false,
167
+ arguments: {
168
+ 'x-queue-type': 'quorum',
169
+ 'x-dead-letter-strategy': 'at-least-once',
170
+ 'x-overflow': 'reject-publish',
171
+ 'x-message-ttl': Math.trunc(Math.pow(2, n) * 1000),
172
+ 'x-dead-letter-exchange': n > 0 ? nextQueue : this.config.delayDelivery
173
+ }
174
+ });
175
+ await channel.bindQueue(queue, queue, routingKey);
176
+ routingKey = `*.${routingKey}`;
177
+ }
178
+ routingKey = '0.#';
179
+ for (let n = RabbitMqTransport.DELAY_QUEUE_COUNT; n >= 1; n -= 1) {
180
+ const queue = this.delayQueueName(n);
181
+ const nextQueue = this.delayQueueName(n - 1);
182
+ await channel.bindExchange(nextQueue, queue, routingKey);
183
+ routingKey = `*.${routingKey}`;
184
+ }
185
+ await channel.assertExchange(this.config.delayDelivery, 'topic', { durable: true });
186
+ await channel.bindExchange(this.config.delayDelivery, this.delayQueueName(0), routingKey);
187
+ await channel.bindQueue(this.queueName, this.config.delayDelivery, `#.${this.queueName}`);
188
+ }
189
+ getRoutingKey(subscribe, messageInfo) {
190
+ const messageType = this.messageTypeSegment(messageInfo.type);
191
+ if (subscribe) {
192
+ return `${messageType}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.#`;
193
+ }
194
+ return `${messageType}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.${messageInfo.minor}.${messageInfo.patch}`;
195
+ }
196
+ delayQueueName(n) {
197
+ return `${this.config.delayDelivery}.${n.toString().padStart(2, '0')}`;
198
+ }
199
+ readHeaders(headers) {
200
+ const normalized = new Map();
201
+ if (!headers) {
202
+ return normalized;
203
+ }
204
+ for (const [key, value] of Object.entries(headers)) {
205
+ if (typeof value === 'string') {
206
+ normalized.set(key, value);
207
+ continue;
208
+ }
209
+ if (typeof value === 'number' || typeof value === 'boolean') {
210
+ normalized.set(key, String(value));
211
+ continue;
212
+ }
213
+ if (value instanceof Buffer) {
214
+ normalized.set(key, value.toString('utf8'));
215
+ }
216
+ }
217
+ return normalized;
218
+ }
219
+ messageTypeSegment(messageType) {
220
+ return messageType === MessageType.Command ? 'Command' : 'Event';
221
+ }
222
+ }
223
+ RabbitMqTransport.MAX_NUMBER_OF_BITS_TO_USE = 28;
224
+ RabbitMqTransport.DELAY_QUEUE_COUNT = RabbitMqTransport.MAX_NUMBER_OF_BITS_TO_USE - 1;
225
+
226
+ class NoOpLogger {
227
+ info(_message, ..._meta) {
228
+ // Intentionally empty.
229
+ }
230
+ error(_message, ..._meta) {
231
+ // Intentionally empty.
232
+ }
233
+ }
234
+ class RabbitMqConfig {
235
+ constructor() {
236
+ this.queueArguments = {
237
+ 'x-queue-type': 'quorum'
238
+ };
239
+ this.log = new NoOpLogger();
240
+ this.networkRecoveryIntervalMs = 10000;
241
+ this.commandExchangeName = 'polybus.commands';
242
+ this.delayDelivery = 'polybus.delay';
243
+ this.directExchangeName = 'polybus.direct';
244
+ this.eventExchangeName = 'polybus.events';
245
+ this.connectionString = '';
246
+ }
247
+ async create(_builder, bus) {
248
+ this.log.info(`Creating RabbitMQ transport for: ${this.queueName ?? bus.name}`);
249
+ return new RabbitMqTransport(this, bus);
250
+ }
251
+ }
252
+
253
+ export { RabbitMqConfig, RabbitMqTransport };
254
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","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":[],"mappings":"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"}