@aichatwar/shared 1.0.102 → 1.0.104

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.
@@ -7,6 +7,10 @@ export declare abstract class Listener<T extends BaseEvent> {
7
7
  protected consumer: Consumer;
8
8
  protected ackDeadline: number;
9
9
  private currentPayload?;
10
+ private retryCount;
11
+ private readonly maxInitialRetries;
12
+ private readonly maxRetryDelay;
13
+ private isListening;
10
14
  constructor(consumer: Consumer);
11
15
  ack(): Promise<void>;
12
16
  listen(): Promise<void>;
@@ -13,6 +13,10 @@ exports.Listener = void 0;
13
13
  class Listener {
14
14
  constructor(consumer) {
15
15
  this.ackDeadline = 5 * 1000; // 5 seconds
16
+ this.retryCount = 0;
17
+ this.maxInitialRetries = 5; // Show detailed retry logs for first 5 attempts
18
+ this.maxRetryDelay = 60000; // Cap delay at 60 seconds
19
+ this.isListening = false; // Track if listener is active
16
20
  this.consumer = consumer;
17
21
  }
18
22
  // Manual acknowledgment method
@@ -31,36 +35,88 @@ class Listener {
31
35
  }
32
36
  listen() {
33
37
  return __awaiter(this, void 0, void 0, function* () {
34
- yield this.consumer.connect();
35
- yield this.consumer.subscribe({
36
- topic: this.topic,
37
- fromBeginning: false,
38
- });
39
- console.log(`Listening to topic: ${this.topic} with groupId: ${this.groupId}`);
40
- yield this.consumer.run({
41
- eachMessage: (payload) => __awaiter(this, void 0, void 0, function* () {
42
- if (!payload.message.value)
38
+ var _a;
39
+ // Prevent multiple simultaneous listen attempts
40
+ if (this.isListening) {
41
+ return;
42
+ }
43
+ try {
44
+ yield this.consumer.connect();
45
+ // Handle consumer errors (including partition errors)
46
+ this.consumer.on('consumer.crash', (event) => {
47
+ var _a;
48
+ const error = event.payload.error;
49
+ if ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes('does not host this topic-partition')) {
50
+ console.warn(`[${this.topic}] Consumer partition error (non-fatal):`, error.message);
51
+ // Don't crash - this is often a transient error
43
52
  return;
44
- console.log(`Message received -> topic: ${this.topic}, groupId: ${this.groupId}`);
45
- // Store current payload for manual ack
46
- this.currentPayload = payload;
47
- try {
48
- const data = JSON.parse(payload.message.value.toString());
49
- yield this.onMessage(data, payload);
50
- // Note: Child listeners must call this.ack() manually after successful processing
51
- // If they don't call ack(), the message will be redelivered
52
53
  }
53
- catch (error) {
54
- console.error(`Error processing message for topic: ${this.topic}`, error);
55
- // In case of error, we don't commit the offset, so the message will be redelivered
56
- throw error;
54
+ console.error(`[${this.topic}] Consumer crashed:`, error);
55
+ });
56
+ yield this.consumer.subscribe({
57
+ topic: this.topic,
58
+ fromBeginning: false,
59
+ });
60
+ // Reset retry count on successful connection
61
+ this.retryCount = 0;
62
+ console.log(`[${this.topic}] Listening to topic with groupId: ${this.groupId}`);
63
+ yield this.consumer.run({
64
+ eachMessage: (payload) => __awaiter(this, void 0, void 0, function* () {
65
+ if (!payload.message.value)
66
+ return;
67
+ console.log(`Message received -> topic: ${this.topic}, groupId: ${this.groupId}`);
68
+ // Store current payload for manual ack
69
+ this.currentPayload = payload;
70
+ try {
71
+ const data = JSON.parse(payload.message.value.toString());
72
+ yield this.onMessage(data, payload);
73
+ // Note: Child listeners must call this.ack() manually after successful processing
74
+ // If they don't call ack(), the message will be redelivered
75
+ }
76
+ catch (error) {
77
+ console.error(`Error processing message for topic: ${this.topic}`, error);
78
+ // In case of error, we don't commit the offset, so the message will be redelivered
79
+ throw error;
80
+ }
81
+ finally {
82
+ // Clear the current payload
83
+ this.currentPayload = undefined;
84
+ }
85
+ })
86
+ });
87
+ // Mark as successfully listening only after subscription and run are successful
88
+ this.isListening = true;
89
+ }
90
+ catch (error) {
91
+ // Reset listening flag on error so we can retry
92
+ this.isListening = false;
93
+ // Handle connection/subscription errors gracefully
94
+ // The "This server does not host this topic-partition" error is often harmless
95
+ // and occurs when KafkaJS tries to list offsets for a topic that doesn't exist yet or has no messages
96
+ if ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes('does not host this topic-partition')) {
97
+ this.retryCount++;
98
+ // Calculate exponential backoff delay (cap at maxRetryDelay)
99
+ const delay = Math.min(5000 * Math.pow(2, Math.min(this.retryCount - 1, 6)), this.maxRetryDelay);
100
+ // Show detailed logs for first few attempts, then less frequently
101
+ if (this.retryCount <= this.maxInitialRetries) {
102
+ console.warn(`[${this.topic}] Topic partition error (attempt ${this.retryCount}/${this.maxInitialRetries}): ${error.message}. Retrying in ${delay}ms...`);
57
103
  }
58
- finally {
59
- // Clear the current payload
60
- this.currentPayload = undefined;
104
+ else if (this.retryCount % 10 === 0) {
105
+ // Log every 10th retry after initial attempts to avoid log spam
106
+ console.warn(`[${this.topic}] Still retrying subscription (attempt ${this.retryCount}). Topic may not exist yet or has no data. Retrying in ${delay}ms...`);
61
107
  }
62
- })
63
- });
108
+ // Retry indefinitely with exponential backoff
109
+ // This allows the listener to connect once the topic becomes available
110
+ setTimeout(() => {
111
+ if (!this.isListening) {
112
+ this.listen();
113
+ }
114
+ }, delay);
115
+ return;
116
+ }
117
+ console.error(`[${this.topic}] Error setting up listener:`, error);
118
+ throw error;
119
+ }
64
120
  });
65
121
  }
66
122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aichatwar/shared",
3
- "version": "1.0.102",
3
+ "version": "1.0.104",
4
4
  "main": "./build/index.js",
5
5
  "typs": "./build/index.d.ts",
6
6
  "files": [