@jetit/publisher 5.4.0 → 5.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetit/publisher",
3
- "version": "5.4.0",
3
+ "version": "5.5.1",
4
4
  "type": "commonjs",
5
5
  "dependencies": {
6
6
  "@jetit/id": "^0.0.13",
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MetricsCollector = void 0;
4
+ const logger_1 = require("../redis/logger");
4
5
  class MetricsCollector {
5
6
  constructor(config, dlq) {
6
7
  this.metrics = [];
@@ -116,19 +117,34 @@ class MetricsCollector {
116
117
  if (keys.length > 0) {
117
118
  for (const streamKey of keys) {
118
119
  try {
120
+ // Verify if the key is a stream before proceeding
121
+ const keyType = await this.redisClient.type(streamKey);
122
+ if (keyType !== 'stream') {
123
+ continue; // Skip non-stream keys
124
+ }
119
125
  // Get stream length and pending info
120
126
  const streamLength = await this.redisClient.xlen(streamKey);
121
127
  // Extract consumer group name from stream key (format: eventName:cg-serviceName)
122
- const consumerGroup = streamKey.split(':')[1];
128
+ const [eventName, consumerGroup] = streamKey.split(':');
129
+ if (!consumerGroup?.startsWith('cg-')) {
130
+ continue; // Skip if key format doesn't match expected pattern
131
+ }
123
132
  // XPENDING returns [count, min-id, max-id, consumer-list]
124
- const pendingInfo = await this.redisClient.xpending(streamKey, consumerGroup);
125
- const totalPending = pendingInfo ? Number(pendingInfo[0]) : 0;
133
+ let totalPending = 0;
134
+ try {
135
+ const pendingInfo = await this.redisClient.xpending(streamKey, consumerGroup);
136
+ totalPending = pendingInfo ? Number(pendingInfo[0]) : 0;
137
+ }
138
+ catch (error) {
139
+ logger_1.PUBLISHER_LOGGER.error(`Error getting pending info for stream ${streamKey}:`, error);
140
+ // Continue with totalPending as 0 if XPENDING fails
141
+ }
126
142
  const queueDepth = Math.max(0, streamLength - totalPending);
127
143
  totalDepth += queueDepth;
128
144
  individualDepths[streamKey] = queueDepth;
129
145
  }
130
146
  catch (error) {
131
- console.error(`Error processing key ${streamKey}:`, error);
147
+ logger_1.PUBLISHER_LOGGER.error(`Error processing key ${streamKey}:`, error);
132
148
  continue;
133
149
  }
134
150
  }
@@ -18,7 +18,11 @@ class DeadLetterQueue {
18
18
  return;
19
19
  }
20
20
  const eventJson = JSON.stringify(event);
21
- await this.redisClient.multi().hset(DLQ_HASH_KEY, event.eventId, eventJson).zadd(DLQ_ZSET_KEY, event.timestamp, event.eventId).exec();
21
+ // Execute commands separately to avoid CROSSSLOT error in cluster mode
22
+ await Promise.all([
23
+ this.redisClient.hset(DLQ_HASH_KEY, event.eventId, eventJson),
24
+ this.redisClient.zadd(DLQ_ZSET_KEY, event.timestamp, event.eventId),
25
+ ]);
22
26
  await this.incrementRateLimit();
23
27
  logger_1.PUBLISHER_LOGGER.log(`DLQ: Added event ${event.eventId} to Dead Letter Queue`);
24
28
  }
@@ -69,8 +73,12 @@ class DeadLetterQueue {
69
73
  }
70
74
  async removeFromDLQ(eventId) {
71
75
  try {
72
- const removed = await this.redisClient.multi().hdel(DLQ_HASH_KEY, eventId).zrem(DLQ_ZSET_KEY, eventId).exec();
73
- if (removed && removed[0][1] === 1 && removed[1][1] === 1) {
76
+ // Execute commands separately to avoid CROSSSLOT error in cluster mode
77
+ const [hdelResult, zremResult] = await Promise.all([
78
+ this.redisClient.hdel(DLQ_HASH_KEY, eventId),
79
+ this.redisClient.zrem(DLQ_ZSET_KEY, eventId),
80
+ ]);
81
+ if (hdelResult === 1 && zremResult === 1) {
74
82
  logger_1.PUBLISHER_LOGGER.log(`DLQ: Successfully removed event ${eventId} from Dead Letter Queue`);
75
83
  return true;
76
84
  }
@@ -90,11 +98,11 @@ class DeadLetterQueue {
90
98
  try {
91
99
  const expiredEventIds = await this.redisClient.zrangebyscore(DLQ_ZSET_KEY, 0, cutoffTime);
92
100
  if (expiredEventIds.length > 0) {
93
- await this.redisClient
94
- .multi()
95
- .hdel(DLQ_HASH_KEY, ...expiredEventIds)
96
- .zremrangebyscore(DLQ_ZSET_KEY, 0, cutoffTime)
97
- .exec();
101
+ // Execute commands separately to avoid CROSSSLOT error in cluster mode
102
+ await Promise.all([
103
+ this.redisClient.hdel(DLQ_HASH_KEY, ...expiredEventIds),
104
+ this.redisClient.zremrangebyscore(DLQ_ZSET_KEY, 0, cutoffTime),
105
+ ]);
98
106
  }
99
107
  logger_1.PUBLISHER_LOGGER.log(`DLQ: Cleaned up ${expiredEventIds.length} expired events from Dead Letter Queue`);
100
108
  }
@@ -104,16 +112,9 @@ class DeadLetterQueue {
104
112
  }
105
113
  async getDLQStats() {
106
114
  try {
107
- const results = await this.redisClient.multi().zcard(DLQ_ZSET_KEY).get(DLQ_RATE_LIMIT_KEY).exec();
108
- if (!results) {
109
- throw new Error('Failed to execute Redis commands');
110
- }
111
- const [sizeResult, additionRateResult] = results;
112
- if (sizeResult[0] || additionRateResult[0]) {
113
- throw new Error('Error executing Redis commands');
114
- }
115
- const size = sizeResult[1];
116
- const additionRate = parseInt(additionRateResult[1] || '0', 10);
115
+ // Execute commands separately to avoid CROSSSLOT error in cluster mode
116
+ const [size, additionRateStr] = await Promise.all([this.redisClient.zcard(DLQ_ZSET_KEY), this.redisClient.get(DLQ_RATE_LIMIT_KEY)]);
117
+ const additionRate = parseInt(additionRateStr || '0', 10);
117
118
  return { size, additionRate };
118
119
  }
119
120
  catch (error) {
@@ -126,8 +127,12 @@ class DeadLetterQueue {
126
127
  return parseInt(currentRate, 10) >= this.maxEventsThreshold;
127
128
  }
128
129
  async incrementRateLimit() {
129
- await this.redisClient.incr(DLQ_RATE_LIMIT_KEY);
130
- await this.redisClient.expire(DLQ_RATE_LIMIT_KEY, 60); // Reset rate limit after 1 minute
130
+ // These commands operate on the same key so they can be combined in cluster mode
131
+ await this.redisClient
132
+ .multi()
133
+ .incr(DLQ_RATE_LIMIT_KEY)
134
+ .expire(DLQ_RATE_LIMIT_KEY, 60) // Reset rate limit after 1 minute
135
+ .exec();
131
136
  }
132
137
  }
133
138
  exports.DeadLetterQueue = DeadLetterQueue;
@@ -301,12 +301,12 @@ class Streams {
301
301
  }
302
302
  const bs = new rxjs_1.BehaviorSubject(null);
303
303
  // Making the subscription Immutable
304
- const subscription = Object.freeze({
304
+ const subscription = {
305
305
  subject: bs,
306
306
  filter: eventFilter,
307
307
  lastMatchTime: Date.now(),
308
308
  keepAlive: filterKeepAlive,
309
- });
309
+ };
310
310
  eventSubscriptions.set(subscriptionId, subscription);
311
311
  // Return early if not first subscription
312
312
  if (!isNewSubscription) {
@@ -718,11 +718,15 @@ class Streams {
718
718
  * This is used to track cleanup progress and ensure we don't delete unprocessed messages.
719
719
  */
720
720
  async acknowledgeMessage(ackKey) {
721
- const { streamName, messageId } = this.demergeMessageKey(ackKey);
721
+ let { streamName, messageId } = this.demergeMessageKey(ackKey);
722
722
  const lastAckKey = `last_ack:${streamName}`;
723
+ messageId == '0' ? '0-0' : messageId;
723
724
  try {
724
725
  // Update last acknowledged ID and acknowledge message atomically
725
- await Promise.all([this.redisGroups.xack(streamName, this.consumerGroupName, messageId), this.redisGroups.set(lastAckKey, messageId)]);
726
+ await Promise.all([
727
+ this.redisGroups.xack(streamName, this.consumerGroupName, messageId),
728
+ this.redisGroups.set(lastAckKey, messageId.toString()),
729
+ ]);
726
730
  }
727
731
  catch (error) {
728
732
  logger_1.PUBLISHER_LOGGER.error(`Error acknowledging message ${messageId} for ${streamName}:`, error);