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/dist/index.js CHANGED
@@ -27,8 +27,8 @@ var RunMQException = class extends Error {
27
27
  }
28
28
  };
29
29
 
30
- // src/core/clients/AmqplibClient.ts
31
- import * as amqp from "amqplib";
30
+ // src/core/clients/RabbitMQClientAdapter.ts
31
+ import { Connection } from "rabbitmq-client";
32
32
 
33
33
  // src/core/exceptions/Exceptions.ts
34
34
  var Exceptions = class {
@@ -40,31 +40,228 @@ Exceptions.INVALID_MESSAGE_FORMAT = "MESSAGE_SHOULD_BE_VALID_RECORD";
40
40
  Exceptions.UNSUPPORTED_SCHEMA = "UNSUPPORTED_SCHEMA";
41
41
  Exceptions.FAILURE_TO_DEFINE_TTL_POLICY = "FAILURE_TO_DEFINE_TTL_POLICY";
42
42
 
43
- // src/core/clients/AmqplibClient.ts
44
- var AmqplibClient = class {
43
+ // src/core/clients/RabbitMQClientChannel.ts
44
+ var RabbitMQClientChannel = class {
45
+ constructor(channel) {
46
+ this.channel = channel;
47
+ }
48
+ async assertQueue(queue, options) {
49
+ const args = {};
50
+ if (options == null ? void 0 : options.deadLetterExchange) args["x-dead-letter-exchange"] = options.deadLetterExchange;
51
+ if (options == null ? void 0 : options.deadLetterRoutingKey) args["x-dead-letter-routing-key"] = options.deadLetterRoutingKey;
52
+ if (options == null ? void 0 : options.messageTtl) args["x-message-ttl"] = options.messageTtl;
53
+ if (options == null ? void 0 : options.arguments) Object.assign(args, options.arguments);
54
+ const result = await this.channel.queueDeclare({
55
+ queue,
56
+ durable: options == null ? void 0 : options.durable,
57
+ exclusive: options == null ? void 0 : options.exclusive,
58
+ autoDelete: options == null ? void 0 : options.autoDelete,
59
+ arguments: Object.keys(args).length > 0 ? args : void 0
60
+ });
61
+ return {
62
+ queue: result.queue,
63
+ messageCount: result.messageCount,
64
+ consumerCount: result.consumerCount
65
+ };
66
+ }
67
+ async checkQueue(queue) {
68
+ const result = await this.channel.queueDeclare({
69
+ queue,
70
+ passive: true
71
+ });
72
+ return {
73
+ queue: result.queue,
74
+ messageCount: result.messageCount,
75
+ consumerCount: result.consumerCount
76
+ };
77
+ }
78
+ async deleteQueue(queue, options) {
79
+ const result = await this.channel.queueDelete({
80
+ queue,
81
+ ifUnused: options == null ? void 0 : options.ifUnused,
82
+ ifEmpty: options == null ? void 0 : options.ifEmpty
83
+ });
84
+ return {
85
+ messageCount: result.messageCount
86
+ };
87
+ }
88
+ async assertExchange(exchange, type, options) {
89
+ const args = {};
90
+ if (options == null ? void 0 : options.alternateExchange) args["alternate-exchange"] = options.alternateExchange;
91
+ if (options == null ? void 0 : options.arguments) Object.assign(args, options.arguments);
92
+ await this.channel.exchangeDeclare({
93
+ exchange,
94
+ type,
95
+ durable: options == null ? void 0 : options.durable,
96
+ internal: options == null ? void 0 : options.internal,
97
+ autoDelete: options == null ? void 0 : options.autoDelete,
98
+ arguments: Object.keys(args).length > 0 ? args : void 0
99
+ });
100
+ return {
101
+ exchange
102
+ };
103
+ }
104
+ async checkExchange(exchange) {
105
+ await this.channel.exchangeDeclare({
106
+ exchange,
107
+ passive: true
108
+ });
109
+ return {
110
+ exchange
111
+ };
112
+ }
113
+ async deleteExchange(exchange, options) {
114
+ await this.channel.exchangeDelete({
115
+ exchange,
116
+ ifUnused: options == null ? void 0 : options.ifUnused
117
+ });
118
+ }
119
+ async bindQueue(queue, source, pattern, args) {
120
+ await this.channel.queueBind({
121
+ queue,
122
+ exchange: source,
123
+ routingKey: pattern,
124
+ arguments: args
125
+ });
126
+ }
127
+ publish(exchange, routingKey, content, options) {
128
+ var _a2;
129
+ this.channel.basicPublish({
130
+ exchange,
131
+ routingKey,
132
+ correlationId: options == null ? void 0 : options.correlationId,
133
+ messageId: options == null ? void 0 : options.messageId,
134
+ headers: options == null ? void 0 : options.headers,
135
+ durable: options == null ? void 0 : options.persistent,
136
+ expiration: (_a2 = options == null ? void 0 : options.expiration) == null ? void 0 : _a2.toString(),
137
+ contentType: options == null ? void 0 : options.contentType,
138
+ contentEncoding: options == null ? void 0 : options.contentEncoding,
139
+ priority: options == null ? void 0 : options.priority,
140
+ replyTo: options == null ? void 0 : options.replyTo,
141
+ timestamp: options == null ? void 0 : options.timestamp,
142
+ type: options == null ? void 0 : options.type,
143
+ userId: options == null ? void 0 : options.userId,
144
+ appId: options == null ? void 0 : options.appId
145
+ }, content);
146
+ return true;
147
+ }
148
+ async consume(queue, onMessage, options) {
149
+ const result = await this.channel.basicConsume({
150
+ queue,
151
+ consumerTag: options == null ? void 0 : options.consumerTag,
152
+ noLocal: options == null ? void 0 : options.noLocal,
153
+ noAck: options == null ? void 0 : options.noAck,
154
+ exclusive: options == null ? void 0 : options.exclusive,
155
+ arguments: options == null ? void 0 : options.arguments
156
+ }, (msg) => {
157
+ const body = msg.body;
158
+ const content = Buffer.isBuffer(body) ? body : typeof body === "string" ? Buffer.from(body) : Buffer.from(JSON.stringify(body));
159
+ const consumeMessage = {
160
+ content,
161
+ fields: {
162
+ consumerTag: msg.consumerTag,
163
+ deliveryTag: msg.deliveryTag,
164
+ redelivered: msg.redelivered,
165
+ exchange: msg.exchange,
166
+ routingKey: msg.routingKey
167
+ },
168
+ properties: {
169
+ contentType: msg.contentType,
170
+ contentEncoding: msg.contentEncoding,
171
+ headers: msg.headers || {},
172
+ deliveryMode: msg.durable ? 2 : 1,
173
+ priority: msg.priority,
174
+ correlationId: msg.correlationId,
175
+ replyTo: msg.replyTo,
176
+ expiration: msg.expiration,
177
+ messageId: msg.messageId,
178
+ timestamp: msg.timestamp,
179
+ type: msg.type,
180
+ userId: msg.userId,
181
+ appId: msg.appId
182
+ }
183
+ };
184
+ onMessage(consumeMessage);
185
+ });
186
+ return {
187
+ consumerTag: result.consumerTag
188
+ };
189
+ }
190
+ ack(message, allUpTo) {
191
+ this.channel.basicAck({
192
+ deliveryTag: message.fields.deliveryTag,
193
+ multiple: allUpTo
194
+ });
195
+ }
196
+ nack(message, allUpTo, requeue) {
197
+ this.channel.basicNack({
198
+ deliveryTag: message.fields.deliveryTag,
199
+ multiple: allUpTo,
200
+ requeue
201
+ });
202
+ }
203
+ async prefetch(count, global) {
204
+ await this.channel.basicQos({
205
+ prefetchCount: count,
206
+ global
207
+ });
208
+ }
209
+ async close() {
210
+ await this.channel.close();
211
+ }
212
+ };
213
+
214
+ // src/core/clients/RabbitMQClientAdapter.ts
215
+ var RabbitMQClientAdapter = class {
45
216
  constructor(config) {
46
217
  this.config = config;
47
218
  this.isConnected = false;
48
- this.config = config;
219
+ this.acquiredChannels = [];
49
220
  }
50
221
  async connect() {
51
222
  try {
52
- if (this.isConnected && this.channelModel) {
53
- return this.channelModel;
223
+ if (this.connection && this.isConnected) {
224
+ return this.connection;
54
225
  }
55
- this.channelModel = await amqp.connect(this.config.url);
56
- this.isConnected = true;
57
- if (this.isConnected) {
58
- this.channelModel.on("error", () => {
59
- this.isConnected = false;
60
- });
61
- this.channelModel.on("close", () => {
62
- this.isConnected = false;
63
- });
226
+ if (this.connection) {
227
+ try {
228
+ await this.connection.close();
229
+ } catch (e) {
230
+ }
231
+ this.connection = void 0;
64
232
  }
65
- return this.channelModel;
233
+ this.connection = new Connection({
234
+ url: this.config.url,
235
+ // Disable automatic retries - we handle retries at RunMQ level
236
+ retryLow: 100,
237
+ retryHigh: 200,
238
+ connectionTimeout: 5e3
239
+ });
240
+ this.connection.on("error", (err) => {
241
+ console.error("RabbitMQ connection error:", err);
242
+ this.isConnected = false;
243
+ });
244
+ this.connection.on("connection", () => {
245
+ this.isConnected = true;
246
+ });
247
+ this.connection.on("connection.blocked", (reason) => {
248
+ console.warn("RabbitMQ connection blocked:", reason);
249
+ });
250
+ this.connection.on("connection.unblocked", () => {
251
+ console.info("RabbitMQ connection unblocked");
252
+ });
253
+ await this.connection.onConnect(5e3, true);
254
+ this.isConnected = true;
255
+ return this.connection;
66
256
  } catch (error) {
67
257
  this.isConnected = false;
258
+ if (this.connection) {
259
+ try {
260
+ this.connection.close();
261
+ } catch (e) {
262
+ }
263
+ this.connection = void 0;
264
+ }
68
265
  throw new RunMQException(
69
266
  Exceptions.CONNECTION_NOT_ESTABLISHED,
70
267
  {
@@ -74,25 +271,41 @@ var AmqplibClient = class {
74
271
  }
75
272
  }
76
273
  async getChannel() {
77
- return await (await this.connect()).createChannel();
274
+ const connection = await this.connect();
275
+ const rawChannel = await connection.acquire();
276
+ this.acquiredChannels.push(rawChannel);
277
+ return new RabbitMQClientChannel(rawChannel);
278
+ }
279
+ async getDefaultChannel() {
280
+ if (!this.defaultChannel) {
281
+ this.defaultChannel = await this.getChannel();
282
+ }
283
+ return this.defaultChannel;
78
284
  }
79
285
  async disconnect() {
80
- try {
81
- if (this.channelModel && this.isConnected) {
82
- await this.channelModel.close();
83
- this.isConnected = false;
84
- }
85
- } catch (error) {
86
- throw new RunMQException(
87
- Exceptions.CONNECTION_NOT_ESTABLISHED,
88
- {
89
- error: error instanceof Error ? error.message : String(error)
286
+ const conn = this.connection;
287
+ const channels = this.acquiredChannels;
288
+ this.connection = void 0;
289
+ this.defaultChannel = void 0;
290
+ this.isConnected = false;
291
+ this.acquiredChannels = [];
292
+ for (const channel of channels) {
293
+ try {
294
+ if (channel.active) {
295
+ await channel.close();
90
296
  }
91
- );
297
+ } catch (e) {
298
+ }
299
+ }
300
+ if (conn) {
301
+ try {
302
+ await conn.close();
303
+ } catch (e) {
304
+ }
92
305
  }
93
306
  }
94
307
  isActive() {
95
- return this.isConnected && this.channelModel !== void 0;
308
+ return this.connection !== void 0 && this.isConnected;
96
309
  }
97
310
  };
98
311
 
@@ -142,6 +355,23 @@ var RabbitMQMessage = class _RabbitMQMessage {
142
355
  this.amqpMessage = amqpMessage;
143
356
  this.headers = headers;
144
357
  }
358
+ /**
359
+ * Acknowledges the message.
360
+ */
361
+ ack() {
362
+ if (this.amqpMessage) {
363
+ this.channel.ack(this.amqpMessage);
364
+ }
365
+ }
366
+ /**
367
+ * Negatively acknowledges the message.
368
+ * @param requeue - Whether to requeue the message (default: false)
369
+ */
370
+ nack(requeue = false) {
371
+ if (this.amqpMessage) {
372
+ this.channel.nack(this.amqpMessage, false, requeue);
373
+ }
374
+ }
145
375
  static from(messageData, channel, props, amqpMessage = null) {
146
376
  return new _RabbitMQMessage(
147
377
  messageData,
@@ -162,7 +392,7 @@ var RunMQSucceededMessageAcknowledgerProcessor = class {
162
392
  async consume(message) {
163
393
  const result = await this.consumer.consume(message);
164
394
  if (result) {
165
- message.channel.ack(message.amqpMessage);
395
+ message.ack();
166
396
  }
167
397
  return result;
168
398
  }
@@ -177,7 +407,7 @@ var RunMQFailedMessageRejecterProcessor = class {
177
407
  try {
178
408
  return await this.consumer.consume(message);
179
409
  } catch (e) {
180
- message.channel.nack(message.amqpMessage, false, false);
410
+ message.nack(false);
181
411
  return false;
182
412
  }
183
413
  }
@@ -238,7 +468,7 @@ var RunMQRetriesCheckerProcessor = class {
238
468
  }
239
469
  acknowledgeMessage(message) {
240
470
  try {
241
- message.channel.ack(message.amqpMessage, false);
471
+ message.ack();
242
472
  } catch (e) {
243
473
  const error = new Error("A message acknowledge failed after publishing to final dead letter");
244
474
  this.logger.error(error.message, { cause: e instanceof Error ? e.message : String(e) });
@@ -670,8 +900,7 @@ var RunMQTTLPolicyManager = class {
670
900
 
671
901
  // src/core/consumer/RunMQConsumerCreator.ts
672
902
  var RunMQConsumerCreator = class {
673
- constructor(defaultChannel, client, logger, managementConfig) {
674
- this.defaultChannel = defaultChannel;
903
+ constructor(client, logger, managementConfig) {
675
904
  this.client = client;
676
905
  this.logger = logger;
677
906
  this.ttlPolicyManager = new RunMQTTLPolicyManager(logger, managementConfig);
@@ -723,12 +952,13 @@ var RunMQConsumerCreator = class {
723
952
  }
724
953
  async assertQueues(consumerConfiguration) {
725
954
  var _a2, _b;
726
- await this.defaultChannel.assertQueue(consumerConfiguration.processorConfig.name, {
955
+ const defaultChannel = await this.client.getDefaultChannel();
956
+ await defaultChannel.assertQueue(consumerConfiguration.processorConfig.name, {
727
957
  durable: true,
728
958
  deadLetterExchange: Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME,
729
959
  deadLetterRoutingKey: consumerConfiguration.processorConfig.name
730
960
  });
731
- await this.defaultChannel.assertQueue(ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name), {
961
+ await defaultChannel.assertQueue(ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name), {
732
962
  durable: true,
733
963
  deadLetterExchange: Constants.ROUTER_EXCHANGE_NAME,
734
964
  deadLetterRoutingKey: consumerConfiguration.processorConfig.name
@@ -737,7 +967,7 @@ var RunMQConsumerCreator = class {
737
967
  const messageDelay = (_a2 = consumerConfiguration.processorConfig.attemptsDelay) != null ? _a2 : DEFAULTS.PROCESSING_RETRY_DELAY;
738
968
  const policiesForTTL = (_b = consumerConfiguration.processorConfig.usePoliciesForDelay) != null ? _b : false;
739
969
  if (!policiesForTTL) {
740
- await this.defaultChannel.assertQueue(retryDelayQueueName, {
970
+ await defaultChannel.assertQueue(retryDelayQueueName, {
741
971
  durable: true,
742
972
  deadLetterExchange: Constants.ROUTER_EXCHANGE_NAME,
743
973
  messageTtl: messageDelay
@@ -749,7 +979,7 @@ var RunMQConsumerCreator = class {
749
979
  messageDelay
750
980
  );
751
981
  if (result) {
752
- await this.defaultChannel.assertQueue(retryDelayQueueName, {
982
+ await defaultChannel.assertQueue(retryDelayQueueName, {
753
983
  durable: true,
754
984
  deadLetterExchange: Constants.ROUTER_EXCHANGE_NAME
755
985
  });
@@ -763,22 +993,23 @@ var RunMQConsumerCreator = class {
763
993
  );
764
994
  }
765
995
  async bindQueues(consumerConfiguration) {
766
- await this.defaultChannel.bindQueue(
996
+ const defaultChannel = await this.client.getDefaultChannel();
997
+ await defaultChannel.bindQueue(
767
998
  consumerConfiguration.processorConfig.name,
768
999
  Constants.ROUTER_EXCHANGE_NAME,
769
1000
  consumerConfiguration.topic
770
1001
  );
771
- await this.defaultChannel.bindQueue(
1002
+ await defaultChannel.bindQueue(
772
1003
  consumerConfiguration.processorConfig.name,
773
1004
  Constants.ROUTER_EXCHANGE_NAME,
774
1005
  consumerConfiguration.processorConfig.name
775
1006
  );
776
- await this.defaultChannel.bindQueue(
1007
+ await defaultChannel.bindQueue(
777
1008
  ConsumerCreatorUtils.getRetryDelayTopicName(consumerConfiguration.processorConfig.name),
778
1009
  Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME,
779
1010
  consumerConfiguration.processorConfig.name
780
1011
  );
781
- await this.defaultChannel.bindQueue(
1012
+ await defaultChannel.bindQueue(
782
1013
  ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name),
783
1014
  Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME,
784
1015
  ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name)
@@ -844,7 +1075,7 @@ var RunMQ = class _RunMQ {
844
1075
  reconnectDelay: (_a2 = config.reconnectDelay) != null ? _a2 : DEFAULTS.RECONNECT_DELAY,
845
1076
  maxReconnectAttempts: (_b = config.maxReconnectAttempts) != null ? _b : DEFAULTS.MAX_RECONNECT_ATTEMPTS
846
1077
  });
847
- this.amqplibClient = new AmqplibClient(this.config);
1078
+ this.client = new RabbitMQClientAdapter(this.config);
848
1079
  }
849
1080
  /**
850
1081
  * Starts the RunMQ instance by establishing a connection to RabbitMQ and initializing necessary components.
@@ -865,7 +1096,7 @@ var RunMQ = class _RunMQ {
865
1096
  * @param processor The function that will process the incoming messages
866
1097
  */
867
1098
  async process(topic, config, processor) {
868
- const consumer = new RunMQConsumerCreator(this.defaultChannel, this.amqplibClient, this.logger, this.config.management);
1099
+ const consumer = new RunMQConsumerCreator(this.client, this.logger, this.config.management);
869
1100
  await consumer.createConsumer(new ConsumerConfiguration(topic, config, processor));
870
1101
  }
871
1102
  /**
@@ -875,7 +1106,7 @@ var RunMQ = class _RunMQ {
875
1106
  * @param correlationId (Optional) A unique identifier for correlating messages; if not provided, a new UUID will be generated
876
1107
  */
877
1108
  publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
878
- if (!this.publisher) {
1109
+ if (!this.publisher || !this.defaultChannel) {
879
1110
  throw new RunMQException(Exceptions.NOT_INITIALIZED, {});
880
1111
  }
881
1112
  RunMQUtils.assertRecord(message);
@@ -898,7 +1129,7 @@ var RunMQ = class _RunMQ {
898
1129
  */
899
1130
  async disconnect() {
900
1131
  try {
901
- await this.amqplibClient.disconnect();
1132
+ await this.client.disconnect();
902
1133
  } catch (error) {
903
1134
  throw new RunMQException(
904
1135
  Exceptions.CONNECTION_NOT_ESTABLISHED,
@@ -912,19 +1143,20 @@ var RunMQ = class _RunMQ {
912
1143
  * Checks if the connection is currently active.
913
1144
  */
914
1145
  isActive() {
915
- return this.amqplibClient.isActive();
1146
+ return this.client.isActive();
916
1147
  }
917
1148
  async connectWithRetry() {
918
1149
  const maxAttempts = this.config.maxReconnectAttempts;
919
1150
  const delay = this.config.reconnectDelay;
920
1151
  while (this.retryAttempts < maxAttempts) {
921
1152
  try {
922
- await this.amqplibClient.connect();
1153
+ await this.client.connect();
923
1154
  this.logger.log("Successfully connected to RabbitMQ");
924
1155
  this.retryAttempts = 0;
925
1156
  return;
926
1157
  } catch (error) {
927
1158
  this.retryAttempts++;
1159
+ console.log(this.logger);
928
1160
  this.logger.error(`Connection attempt ${this.retryAttempts}/${maxAttempts} failed:`, error);
929
1161
  if (this.retryAttempts >= maxAttempts) {
930
1162
  throw new RunMQException(
@@ -941,7 +1173,7 @@ var RunMQ = class _RunMQ {
941
1173
  }
942
1174
  }
943
1175
  async initialize() {
944
- this.defaultChannel = await this.amqplibClient.getChannel();
1176
+ this.defaultChannel = await this.client.getDefaultChannel();
945
1177
  await this.defaultChannel.assertExchange(Constants.ROUTER_EXCHANGE_NAME, "direct", { durable: true });
946
1178
  await this.defaultChannel.assertExchange(Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME, "direct", { durable: true });
947
1179
  this.publisher = new RunMQPublisherCreator(this.logger).createPublisher();