@aichatwar/shared 1.0.166 → 1.0.168

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.
@@ -8,12 +8,15 @@ export declare abstract class Listener<T extends BaseEvent> {
8
8
  protected consumer: Consumer;
9
9
  protected ackDeadline: number;
10
10
  protected fromBeginning: boolean;
11
+ protected maxEventAgeMs: number;
11
12
  private currentPayload?;
12
13
  private retryCount;
13
14
  private readonly maxInitialRetries;
14
15
  private readonly maxRetryDelay;
15
16
  private isListening;
16
17
  private crashHandlerSetup;
18
+ private crashRestartCount;
19
+ private readonly maxCrashRestartDelay;
17
20
  constructor(consumer: Consumer);
18
21
  private setupCrashHandler;
19
22
  ack(payload?: EachMessagePayload): Promise<void>;
@@ -14,29 +14,44 @@ class Listener {
14
14
  constructor(consumer) {
15
15
  this.ackDeadline = 5 * 1000; // 5 seconds
16
16
  this.fromBeginning = false; // Override in subclasses to read from beginning
17
+ this.maxEventAgeMs = 0; // 0 = disabled. When set, messages older than this are auto-acked and skipped.
17
18
  this.retryCount = 0;
18
19
  this.maxInitialRetries = 3; // Show detailed retry logs for first 3 attempts
19
20
  this.maxRetryDelay = 60000; // Cap delay at 60 seconds
20
21
  this.isListening = false; // Track if listener is active
21
22
  this.crashHandlerSetup = false; // Track if crash handler has been set up
23
+ this.crashRestartCount = 0;
24
+ this.maxCrashRestartDelay = 120000; // 2 minutes cap
22
25
  this.consumer = consumer;
23
26
  this.setupCrashHandler();
24
27
  }
25
28
  setupCrashHandler() {
26
- // Only set up crash handler once to avoid memory leaks
27
29
  if (this.crashHandlerSetup) {
28
30
  return;
29
31
  }
30
- this.consumer.on('consumer.crash', (event) => {
32
+ this.consumer.on('consumer.crash', (event) => __awaiter(this, void 0, void 0, function* () {
31
33
  var _a;
32
34
  const error = event.payload.error;
33
35
  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')) {
34
36
  console.warn(`[${this.topic}] Consumer partition error (non-fatal):`, error.message);
35
- // Don't crash - this is often a transient error
36
37
  return;
37
38
  }
38
- console.error(`[${this.topic}] Consumer crashed:`, error);
39
- });
39
+ this.crashRestartCount++;
40
+ const delay = Math.min(5000 * Math.pow(2, Math.min(this.crashRestartCount - 1, 5)), this.maxCrashRestartDelay);
41
+ console.error(`[${this.topic}] Consumer crashed (restart #${this.crashRestartCount}, ` +
42
+ `retrying in ${delay}ms):`, error);
43
+ this.isListening = false;
44
+ try {
45
+ yield this.consumer.disconnect();
46
+ }
47
+ catch (_) { /* best effort */ }
48
+ setTimeout(() => {
49
+ console.log(`[${this.topic}] Auto-restarting consumer after crash (attempt #${this.crashRestartCount})...`);
50
+ this.listen().catch((err) => {
51
+ console.error(`[${this.topic}] Auto-restart failed:`, err);
52
+ });
53
+ }, delay);
54
+ }));
40
55
  this.crashHandlerSetup = true;
41
56
  }
42
57
  // Manual acknowledgment method
@@ -107,6 +122,18 @@ class Listener {
107
122
  // Commented out key logging as requested
108
123
  // console.log(`📨 [${this.topic}] Message received -> groupId: ${this.groupId}, partition: ${payload.partition}, offset: ${payload.message.offset}, key: ${payload.message.key?.toString() || 'none'}`);
109
124
  console.log(`📨 [${this.topic}] Message received -> groupId: ${this.groupId}, partition: ${payload.partition}, offset: ${payload.message.offset}, value length: ${payload.message.value.toString().length}`);
125
+ // Skip stale messages when maxEventAgeMs is configured
126
+ if (this.maxEventAgeMs > 0 && payload.message.timestamp) {
127
+ const messageAge = Date.now() - Number(payload.message.timestamp);
128
+ if (messageAge > this.maxEventAgeMs) {
129
+ yield this.consumer.commitOffsets([{
130
+ topic: payload.topic,
131
+ partition: payload.partition,
132
+ offset: (BigInt(payload.message.offset) + BigInt(1)).toString()
133
+ }]);
134
+ return;
135
+ }
136
+ }
110
137
  // Store current payload for manual ack
111
138
  this.currentPayload = payload;
112
139
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aichatwar/shared",
3
- "version": "1.0.166",
3
+ "version": "1.0.168",
4
4
  "main": "./build/index.js",
5
5
  "typs": "./build/index.d.ts",
6
6
  "files": [