runmq 1.1.1 → 1.2.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 CHANGED
@@ -15,7 +15,6 @@ It combines RabbitMQ’s proven reliability with a modern developer experience
15
15
 
16
16
  Whether you’re running <b>background jobs</b>, designing an <b>event-driven architecture</b>, or managing a <b>pub/sub event bus</b>, RunMQ provides everything you need — all in a <b>lightweight package</b> with a <b>simple DX</b>, <b>without the hassle of managing everything on your own</b>.
17
17
 
18
-
19
18
  ## Features
20
19
 
21
20
  - **Reliable Message Processing with Retries**: Automatically retries failed messages with configurable delays and retry limits.
package/dist/index.cjs CHANGED
@@ -60,8 +60,8 @@ var RunMQException = class extends Error {
60
60
  }
61
61
  };
62
62
 
63
- // src/core/clients/AmqplibClient.ts
64
- var amqp = __toESM(require("amqplib"), 1);
63
+ // src/core/clients/RabbitMQClientAdapter.ts
64
+ var import_rabbitmq_client = require("rabbitmq-client");
65
65
 
66
66
  // src/core/exceptions/Exceptions.ts
67
67
  var Exceptions = class {
@@ -73,31 +73,228 @@ Exceptions.INVALID_MESSAGE_FORMAT = "MESSAGE_SHOULD_BE_VALID_RECORD";
73
73
  Exceptions.UNSUPPORTED_SCHEMA = "UNSUPPORTED_SCHEMA";
74
74
  Exceptions.FAILURE_TO_DEFINE_TTL_POLICY = "FAILURE_TO_DEFINE_TTL_POLICY";
75
75
 
76
- // src/core/clients/AmqplibClient.ts
77
- var AmqplibClient = class {
76
+ // src/core/clients/RabbitMQClientChannel.ts
77
+ var RabbitMQClientChannel = class {
78
+ constructor(channel) {
79
+ this.channel = channel;
80
+ }
81
+ async assertQueue(queue, options) {
82
+ const args = {};
83
+ if (options == null ? void 0 : options.deadLetterExchange) args["x-dead-letter-exchange"] = options.deadLetterExchange;
84
+ if (options == null ? void 0 : options.deadLetterRoutingKey) args["x-dead-letter-routing-key"] = options.deadLetterRoutingKey;
85
+ if (options == null ? void 0 : options.messageTtl) args["x-message-ttl"] = options.messageTtl;
86
+ if (options == null ? void 0 : options.arguments) Object.assign(args, options.arguments);
87
+ const result = await this.channel.queueDeclare({
88
+ queue,
89
+ durable: options == null ? void 0 : options.durable,
90
+ exclusive: options == null ? void 0 : options.exclusive,
91
+ autoDelete: options == null ? void 0 : options.autoDelete,
92
+ arguments: Object.keys(args).length > 0 ? args : void 0
93
+ });
94
+ return {
95
+ queue: result.queue,
96
+ messageCount: result.messageCount,
97
+ consumerCount: result.consumerCount
98
+ };
99
+ }
100
+ async checkQueue(queue) {
101
+ const result = await this.channel.queueDeclare({
102
+ queue,
103
+ passive: true
104
+ });
105
+ return {
106
+ queue: result.queue,
107
+ messageCount: result.messageCount,
108
+ consumerCount: result.consumerCount
109
+ };
110
+ }
111
+ async deleteQueue(queue, options) {
112
+ const result = await this.channel.queueDelete({
113
+ queue,
114
+ ifUnused: options == null ? void 0 : options.ifUnused,
115
+ ifEmpty: options == null ? void 0 : options.ifEmpty
116
+ });
117
+ return {
118
+ messageCount: result.messageCount
119
+ };
120
+ }
121
+ async assertExchange(exchange, type, options) {
122
+ const args = {};
123
+ if (options == null ? void 0 : options.alternateExchange) args["alternate-exchange"] = options.alternateExchange;
124
+ if (options == null ? void 0 : options.arguments) Object.assign(args, options.arguments);
125
+ await this.channel.exchangeDeclare({
126
+ exchange,
127
+ type,
128
+ durable: options == null ? void 0 : options.durable,
129
+ internal: options == null ? void 0 : options.internal,
130
+ autoDelete: options == null ? void 0 : options.autoDelete,
131
+ arguments: Object.keys(args).length > 0 ? args : void 0
132
+ });
133
+ return {
134
+ exchange
135
+ };
136
+ }
137
+ async checkExchange(exchange) {
138
+ await this.channel.exchangeDeclare({
139
+ exchange,
140
+ passive: true
141
+ });
142
+ return {
143
+ exchange
144
+ };
145
+ }
146
+ async deleteExchange(exchange, options) {
147
+ await this.channel.exchangeDelete({
148
+ exchange,
149
+ ifUnused: options == null ? void 0 : options.ifUnused
150
+ });
151
+ }
152
+ async bindQueue(queue, source, pattern, args) {
153
+ await this.channel.queueBind({
154
+ queue,
155
+ exchange: source,
156
+ routingKey: pattern,
157
+ arguments: args
158
+ });
159
+ }
160
+ publish(exchange, routingKey, content, options) {
161
+ var _a2;
162
+ this.channel.basicPublish({
163
+ exchange,
164
+ routingKey,
165
+ correlationId: options == null ? void 0 : options.correlationId,
166
+ messageId: options == null ? void 0 : options.messageId,
167
+ headers: options == null ? void 0 : options.headers,
168
+ durable: options == null ? void 0 : options.persistent,
169
+ expiration: (_a2 = options == null ? void 0 : options.expiration) == null ? void 0 : _a2.toString(),
170
+ contentType: options == null ? void 0 : options.contentType,
171
+ contentEncoding: options == null ? void 0 : options.contentEncoding,
172
+ priority: options == null ? void 0 : options.priority,
173
+ replyTo: options == null ? void 0 : options.replyTo,
174
+ timestamp: options == null ? void 0 : options.timestamp,
175
+ type: options == null ? void 0 : options.type,
176
+ userId: options == null ? void 0 : options.userId,
177
+ appId: options == null ? void 0 : options.appId
178
+ }, content);
179
+ return true;
180
+ }
181
+ async consume(queue, onMessage, options) {
182
+ const result = await this.channel.basicConsume({
183
+ queue,
184
+ consumerTag: options == null ? void 0 : options.consumerTag,
185
+ noLocal: options == null ? void 0 : options.noLocal,
186
+ noAck: options == null ? void 0 : options.noAck,
187
+ exclusive: options == null ? void 0 : options.exclusive,
188
+ arguments: options == null ? void 0 : options.arguments
189
+ }, (msg) => {
190
+ const body = msg.body;
191
+ const content = Buffer.isBuffer(body) ? body : typeof body === "string" ? Buffer.from(body) : Buffer.from(JSON.stringify(body));
192
+ const consumeMessage = {
193
+ content,
194
+ fields: {
195
+ consumerTag: msg.consumerTag,
196
+ deliveryTag: msg.deliveryTag,
197
+ redelivered: msg.redelivered,
198
+ exchange: msg.exchange,
199
+ routingKey: msg.routingKey
200
+ },
201
+ properties: {
202
+ contentType: msg.contentType,
203
+ contentEncoding: msg.contentEncoding,
204
+ headers: msg.headers || {},
205
+ deliveryMode: msg.durable ? 2 : 1,
206
+ priority: msg.priority,
207
+ correlationId: msg.correlationId,
208
+ replyTo: msg.replyTo,
209
+ expiration: msg.expiration,
210
+ messageId: msg.messageId,
211
+ timestamp: msg.timestamp,
212
+ type: msg.type,
213
+ userId: msg.userId,
214
+ appId: msg.appId
215
+ }
216
+ };
217
+ onMessage(consumeMessage);
218
+ });
219
+ return {
220
+ consumerTag: result.consumerTag
221
+ };
222
+ }
223
+ ack(message, allUpTo) {
224
+ this.channel.basicAck({
225
+ deliveryTag: message.fields.deliveryTag,
226
+ multiple: allUpTo
227
+ });
228
+ }
229
+ nack(message, allUpTo, requeue) {
230
+ this.channel.basicNack({
231
+ deliveryTag: message.fields.deliveryTag,
232
+ multiple: allUpTo,
233
+ requeue
234
+ });
235
+ }
236
+ async prefetch(count, global) {
237
+ await this.channel.basicQos({
238
+ prefetchCount: count,
239
+ global
240
+ });
241
+ }
242
+ async close() {
243
+ await this.channel.close();
244
+ }
245
+ };
246
+
247
+ // src/core/clients/RabbitMQClientAdapter.ts
248
+ var RabbitMQClientAdapter = class {
78
249
  constructor(config) {
79
250
  this.config = config;
80
251
  this.isConnected = false;
81
- this.config = config;
252
+ this.acquiredChannels = [];
82
253
  }
83
254
  async connect() {
84
255
  try {
85
- if (this.isConnected && this.channelModel) {
86
- return this.channelModel;
256
+ if (this.connection && this.isConnected) {
257
+ return this.connection;
87
258
  }
88
- this.channelModel = await amqp.connect(this.config.url);
89
- this.isConnected = true;
90
- if (this.isConnected) {
91
- this.channelModel.on("error", () => {
92
- this.isConnected = false;
93
- });
94
- this.channelModel.on("close", () => {
95
- this.isConnected = false;
96
- });
259
+ if (this.connection) {
260
+ try {
261
+ await this.connection.close();
262
+ } catch (e) {
263
+ }
264
+ this.connection = void 0;
97
265
  }
98
- return this.channelModel;
266
+ this.connection = new import_rabbitmq_client.Connection({
267
+ url: this.config.url,
268
+ // Disable automatic retries - we handle retries at RunMQ level
269
+ retryLow: 100,
270
+ retryHigh: 200,
271
+ connectionTimeout: 5e3
272
+ });
273
+ this.connection.on("error", (err) => {
274
+ console.error("RabbitMQ connection error:", err);
275
+ this.isConnected = false;
276
+ });
277
+ this.connection.on("connection", () => {
278
+ this.isConnected = true;
279
+ });
280
+ this.connection.on("connection.blocked", (reason) => {
281
+ console.warn("RabbitMQ connection blocked:", reason);
282
+ });
283
+ this.connection.on("connection.unblocked", () => {
284
+ console.info("RabbitMQ connection unblocked");
285
+ });
286
+ await this.connection.onConnect(5e3, true);
287
+ this.isConnected = true;
288
+ return this.connection;
99
289
  } catch (error) {
100
290
  this.isConnected = false;
291
+ if (this.connection) {
292
+ try {
293
+ this.connection.close();
294
+ } catch (e) {
295
+ }
296
+ this.connection = void 0;
297
+ }
101
298
  throw new RunMQException(
102
299
  Exceptions.CONNECTION_NOT_ESTABLISHED,
103
300
  {
@@ -107,25 +304,41 @@ var AmqplibClient = class {
107
304
  }
108
305
  }
109
306
  async getChannel() {
110
- return await (await this.connect()).createChannel();
307
+ const connection = await this.connect();
308
+ const rawChannel = await connection.acquire();
309
+ this.acquiredChannels.push(rawChannel);
310
+ return new RabbitMQClientChannel(rawChannel);
311
+ }
312
+ async getDefaultChannel() {
313
+ if (!this.defaultChannel) {
314
+ this.defaultChannel = await this.getChannel();
315
+ }
316
+ return this.defaultChannel;
111
317
  }
112
318
  async disconnect() {
113
- try {
114
- if (this.channelModel && this.isConnected) {
115
- await this.channelModel.close();
116
- this.isConnected = false;
117
- }
118
- } catch (error) {
119
- throw new RunMQException(
120
- Exceptions.CONNECTION_NOT_ESTABLISHED,
121
- {
122
- error: error instanceof Error ? error.message : String(error)
319
+ const conn = this.connection;
320
+ const channels = this.acquiredChannels;
321
+ this.connection = void 0;
322
+ this.defaultChannel = void 0;
323
+ this.isConnected = false;
324
+ this.acquiredChannels = [];
325
+ for (const channel of channels) {
326
+ try {
327
+ if (channel.active) {
328
+ await channel.close();
123
329
  }
124
- );
330
+ } catch (e) {
331
+ }
332
+ }
333
+ if (conn) {
334
+ try {
335
+ await conn.close();
336
+ } catch (e) {
337
+ }
125
338
  }
126
339
  }
127
340
  isActive() {
128
- return this.isConnected && this.channelModel !== void 0;
341
+ return this.connection !== void 0 && this.isConnected;
129
342
  }
130
343
  };
131
344
 
@@ -175,6 +388,23 @@ var RabbitMQMessage = class _RabbitMQMessage {
175
388
  this.amqpMessage = amqpMessage;
176
389
  this.headers = headers;
177
390
  }
391
+ /**
392
+ * Acknowledges the message.
393
+ */
394
+ ack() {
395
+ if (this.amqpMessage) {
396
+ this.channel.ack(this.amqpMessage);
397
+ }
398
+ }
399
+ /**
400
+ * Negatively acknowledges the message.
401
+ * @param requeue - Whether to requeue the message (default: false)
402
+ */
403
+ nack(requeue = false) {
404
+ if (this.amqpMessage) {
405
+ this.channel.nack(this.amqpMessage, false, requeue);
406
+ }
407
+ }
178
408
  static from(messageData, channel, props, amqpMessage = null) {
179
409
  return new _RabbitMQMessage(
180
410
  messageData,
@@ -195,7 +425,7 @@ var RunMQSucceededMessageAcknowledgerProcessor = class {
195
425
  async consume(message) {
196
426
  const result = await this.consumer.consume(message);
197
427
  if (result) {
198
- message.channel.ack(message.amqpMessage);
428
+ message.ack();
199
429
  }
200
430
  return result;
201
431
  }
@@ -210,7 +440,7 @@ var RunMQFailedMessageRejecterProcessor = class {
210
440
  try {
211
441
  return await this.consumer.consume(message);
212
442
  } catch (e) {
213
- message.channel.nack(message.amqpMessage, false, false);
443
+ message.nack(false);
214
444
  return false;
215
445
  }
216
446
  }
@@ -271,7 +501,7 @@ var RunMQRetriesCheckerProcessor = class {
271
501
  }
272
502
  acknowledgeMessage(message) {
273
503
  try {
274
- message.channel.ack(message.amqpMessage, false);
504
+ message.ack();
275
505
  } catch (e) {
276
506
  const error = new Error("A message acknowledge failed after publishing to final dead letter");
277
507
  this.logger.error(error.message, { cause: e instanceof Error ? e.message : String(e) });
@@ -703,8 +933,7 @@ var RunMQTTLPolicyManager = class {
703
933
 
704
934
  // src/core/consumer/RunMQConsumerCreator.ts
705
935
  var RunMQConsumerCreator = class {
706
- constructor(defaultChannel, client, logger, managementConfig) {
707
- this.defaultChannel = defaultChannel;
936
+ constructor(client, logger, managementConfig) {
708
937
  this.client = client;
709
938
  this.logger = logger;
710
939
  this.ttlPolicyManager = new RunMQTTLPolicyManager(logger, managementConfig);
@@ -756,12 +985,13 @@ var RunMQConsumerCreator = class {
756
985
  }
757
986
  async assertQueues(consumerConfiguration) {
758
987
  var _a2, _b;
759
- await this.defaultChannel.assertQueue(consumerConfiguration.processorConfig.name, {
988
+ const defaultChannel = await this.client.getDefaultChannel();
989
+ await defaultChannel.assertQueue(consumerConfiguration.processorConfig.name, {
760
990
  durable: true,
761
991
  deadLetterExchange: Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME,
762
992
  deadLetterRoutingKey: consumerConfiguration.processorConfig.name
763
993
  });
764
- await this.defaultChannel.assertQueue(ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name), {
994
+ await defaultChannel.assertQueue(ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name), {
765
995
  durable: true,
766
996
  deadLetterExchange: Constants.ROUTER_EXCHANGE_NAME,
767
997
  deadLetterRoutingKey: consumerConfiguration.processorConfig.name
@@ -770,7 +1000,7 @@ var RunMQConsumerCreator = class {
770
1000
  const messageDelay = (_a2 = consumerConfiguration.processorConfig.attemptsDelay) != null ? _a2 : DEFAULTS.PROCESSING_RETRY_DELAY;
771
1001
  const policiesForTTL = (_b = consumerConfiguration.processorConfig.usePoliciesForDelay) != null ? _b : false;
772
1002
  if (!policiesForTTL) {
773
- await this.defaultChannel.assertQueue(retryDelayQueueName, {
1003
+ await defaultChannel.assertQueue(retryDelayQueueName, {
774
1004
  durable: true,
775
1005
  deadLetterExchange: Constants.ROUTER_EXCHANGE_NAME,
776
1006
  messageTtl: messageDelay
@@ -782,7 +1012,7 @@ var RunMQConsumerCreator = class {
782
1012
  messageDelay
783
1013
  );
784
1014
  if (result) {
785
- await this.defaultChannel.assertQueue(retryDelayQueueName, {
1015
+ await defaultChannel.assertQueue(retryDelayQueueName, {
786
1016
  durable: true,
787
1017
  deadLetterExchange: Constants.ROUTER_EXCHANGE_NAME
788
1018
  });
@@ -796,22 +1026,23 @@ var RunMQConsumerCreator = class {
796
1026
  );
797
1027
  }
798
1028
  async bindQueues(consumerConfiguration) {
799
- await this.defaultChannel.bindQueue(
1029
+ const defaultChannel = await this.client.getDefaultChannel();
1030
+ await defaultChannel.bindQueue(
800
1031
  consumerConfiguration.processorConfig.name,
801
1032
  Constants.ROUTER_EXCHANGE_NAME,
802
1033
  consumerConfiguration.topic
803
1034
  );
804
- await this.defaultChannel.bindQueue(
1035
+ await defaultChannel.bindQueue(
805
1036
  consumerConfiguration.processorConfig.name,
806
1037
  Constants.ROUTER_EXCHANGE_NAME,
807
1038
  consumerConfiguration.processorConfig.name
808
1039
  );
809
- await this.defaultChannel.bindQueue(
1040
+ await defaultChannel.bindQueue(
810
1041
  ConsumerCreatorUtils.getRetryDelayTopicName(consumerConfiguration.processorConfig.name),
811
1042
  Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME,
812
1043
  consumerConfiguration.processorConfig.name
813
1044
  );
814
- await this.defaultChannel.bindQueue(
1045
+ await defaultChannel.bindQueue(
815
1046
  ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name),
816
1047
  Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME,
817
1048
  ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name)
@@ -877,7 +1108,7 @@ var RunMQ = class _RunMQ {
877
1108
  reconnectDelay: (_a2 = config.reconnectDelay) != null ? _a2 : DEFAULTS.RECONNECT_DELAY,
878
1109
  maxReconnectAttempts: (_b = config.maxReconnectAttempts) != null ? _b : DEFAULTS.MAX_RECONNECT_ATTEMPTS
879
1110
  });
880
- this.amqplibClient = new AmqplibClient(this.config);
1111
+ this.client = new RabbitMQClientAdapter(this.config);
881
1112
  }
882
1113
  /**
883
1114
  * Starts the RunMQ instance by establishing a connection to RabbitMQ and initializing necessary components.
@@ -898,7 +1129,7 @@ var RunMQ = class _RunMQ {
898
1129
  * @param processor The function that will process the incoming messages
899
1130
  */
900
1131
  async process(topic, config, processor) {
901
- const consumer = new RunMQConsumerCreator(this.defaultChannel, this.amqplibClient, this.logger, this.config.management);
1132
+ const consumer = new RunMQConsumerCreator(this.client, this.logger, this.config.management);
902
1133
  await consumer.createConsumer(new ConsumerConfiguration(topic, config, processor));
903
1134
  }
904
1135
  /**
@@ -908,7 +1139,7 @@ var RunMQ = class _RunMQ {
908
1139
  * @param correlationId (Optional) A unique identifier for correlating messages; if not provided, a new UUID will be generated
909
1140
  */
910
1141
  publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
911
- if (!this.publisher) {
1142
+ if (!this.publisher || !this.defaultChannel) {
912
1143
  throw new RunMQException(Exceptions.NOT_INITIALIZED, {});
913
1144
  }
914
1145
  RunMQUtils.assertRecord(message);
@@ -931,7 +1162,7 @@ var RunMQ = class _RunMQ {
931
1162
  */
932
1163
  async disconnect() {
933
1164
  try {
934
- await this.amqplibClient.disconnect();
1165
+ await this.client.disconnect();
935
1166
  } catch (error) {
936
1167
  throw new RunMQException(
937
1168
  Exceptions.CONNECTION_NOT_ESTABLISHED,
@@ -945,19 +1176,20 @@ var RunMQ = class _RunMQ {
945
1176
  * Checks if the connection is currently active.
946
1177
  */
947
1178
  isActive() {
948
- return this.amqplibClient.isActive();
1179
+ return this.client.isActive();
949
1180
  }
950
1181
  async connectWithRetry() {
951
1182
  const maxAttempts = this.config.maxReconnectAttempts;
952
1183
  const delay = this.config.reconnectDelay;
953
1184
  while (this.retryAttempts < maxAttempts) {
954
1185
  try {
955
- await this.amqplibClient.connect();
1186
+ await this.client.connect();
956
1187
  this.logger.log("Successfully connected to RabbitMQ");
957
1188
  this.retryAttempts = 0;
958
1189
  return;
959
1190
  } catch (error) {
960
1191
  this.retryAttempts++;
1192
+ console.log(this.logger);
961
1193
  this.logger.error(`Connection attempt ${this.retryAttempts}/${maxAttempts} failed:`, error);
962
1194
  if (this.retryAttempts >= maxAttempts) {
963
1195
  throw new RunMQException(
@@ -974,7 +1206,7 @@ var RunMQ = class _RunMQ {
974
1206
  }
975
1207
  }
976
1208
  async initialize() {
977
- this.defaultChannel = await this.amqplibClient.getChannel();
1209
+ this.defaultChannel = await this.client.getDefaultChannel();
978
1210
  await this.defaultChannel.assertExchange(Constants.ROUTER_EXCHANGE_NAME, "direct", { durable: true });
979
1211
  await this.defaultChannel.assertExchange(Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME, "direct", { durable: true });
980
1212
  this.publisher = new RunMQPublisherCreator(this.logger).createPublisher();