@aichatwar/shared 1.0.168 → 1.0.170

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.
@@ -5,6 +5,20 @@ export declare abstract class Listener<T extends BaseEvent> {
5
5
  abstract topic: T['subject'];
6
6
  abstract groupId: string;
7
7
  abstract onMessage(data: T['data'], payload: EachMessagePayload): Promise<void>;
8
+ private static registry;
9
+ /**
10
+ * Returns true only if every registered listener's consumer is connected.
11
+ * Wire this into your /ready endpoint alongside the MongoDB check.
12
+ */
13
+ static allHealthy(): boolean;
14
+ /**
15
+ * Returns per-listener health details for diagnostics.
16
+ */
17
+ static healthDetails(): Array<{
18
+ topic: string;
19
+ groupId: string;
20
+ healthy: boolean;
21
+ }>;
8
22
  protected consumer: Consumer;
9
23
  protected ackDeadline: number;
10
24
  protected fromBeginning: boolean;
@@ -16,7 +30,11 @@ export declare abstract class Listener<T extends BaseEvent> {
16
30
  private isListening;
17
31
  private crashHandlerSetup;
18
32
  private crashRestartCount;
33
+ private readonly maxCrashRestarts;
19
34
  private readonly maxCrashRestartDelay;
35
+ private lastSuccessfulMessageAt;
36
+ /** Expose consumer health for readiness probes */
37
+ get healthy(): boolean;
20
38
  constructor(consumer: Consumer);
21
39
  private setupCrashHandler;
22
40
  ack(payload?: EachMessagePayload): Promise<void>;
@@ -11,6 +11,29 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.Listener = void 0;
13
13
  class Listener {
14
+ /**
15
+ * Returns true only if every registered listener's consumer is connected.
16
+ * Wire this into your /ready endpoint alongside the MongoDB check.
17
+ */
18
+ static allHealthy() {
19
+ if (Listener.registry.length === 0)
20
+ return true;
21
+ return Listener.registry.every(l => l.healthy);
22
+ }
23
+ /**
24
+ * Returns per-listener health details for diagnostics.
25
+ */
26
+ static healthDetails() {
27
+ return Listener.registry.map(l => ({
28
+ topic: l.topic,
29
+ groupId: l.groupId,
30
+ healthy: l.healthy,
31
+ }));
32
+ }
33
+ /** Expose consumer health for readiness probes */
34
+ get healthy() {
35
+ return this.isListening;
36
+ }
14
37
  constructor(consumer) {
15
38
  this.ackDeadline = 5 * 1000; // 5 seconds
16
39
  this.fromBeginning = false; // Override in subclasses to read from beginning
@@ -21,8 +44,11 @@ class Listener {
21
44
  this.isListening = false; // Track if listener is active
22
45
  this.crashHandlerSetup = false; // Track if crash handler has been set up
23
46
  this.crashRestartCount = 0;
47
+ this.maxCrashRestarts = 5; // Exit process after this many consecutive crashes
24
48
  this.maxCrashRestartDelay = 120000; // 2 minutes cap
49
+ this.lastSuccessfulMessageAt = 0; // Timestamp of last successfully processed message
25
50
  this.consumer = consumer;
51
+ Listener.registry.push(this);
26
52
  this.setupCrashHandler();
27
53
  }
28
54
  setupCrashHandler() {
@@ -37,21 +63,34 @@ class Listener {
37
63
  return;
38
64
  }
39
65
  this.crashRestartCount++;
66
+ this.isListening = false;
67
+ if (this.crashRestartCount >= this.maxCrashRestarts) {
68
+ console.error(`💀 [${this.topic}] Consumer crashed ${this.crashRestartCount} times without recovery. ` +
69
+ `Exiting process to let Kubernetes restart the pod with a fresh state.`);
70
+ process.exit(1);
71
+ }
40
72
  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}, ` +
73
+ console.error(`[${this.topic}] Consumer crashed (restart #${this.crashRestartCount}/${this.maxCrashRestarts}, ` +
42
74
  `retrying in ${delay}ms):`, error);
43
- this.isListening = false;
44
75
  try {
45
76
  yield this.consumer.disconnect();
46
77
  }
47
78
  catch (_) { /* best effort */ }
48
79
  setTimeout(() => {
49
- console.log(`[${this.topic}] Auto-restarting consumer after crash (attempt #${this.crashRestartCount})...`);
80
+ console.log(`[${this.topic}] Auto-restarting consumer after crash (attempt #${this.crashRestartCount}/${this.maxCrashRestarts})...`);
50
81
  this.listen().catch((err) => {
51
82
  console.error(`[${this.topic}] Auto-restart failed:`, err);
83
+ console.error(`💀 [${this.topic}] Auto-restart listen() threw. Exiting process to let Kubernetes restart the pod.`);
84
+ process.exit(1);
52
85
  });
53
86
  }, delay);
54
87
  }));
88
+ this.consumer.on('consumer.disconnect', () => {
89
+ if (this.isListening) {
90
+ console.warn(`⚠️ [${this.topic}] Consumer disconnected unexpectedly`);
91
+ this.isListening = false;
92
+ }
93
+ });
55
94
  this.crashHandlerSetup = true;
56
95
  }
57
96
  // Manual acknowledgment method
@@ -67,6 +106,12 @@ class Listener {
67
106
  partition: targetPayload.partition,
68
107
  offset: (BigInt(targetPayload.message.offset) + BigInt(1)).toString()
69
108
  }]);
109
+ // Reset crash counter on successful processing — proves the consumer is healthy
110
+ if (this.crashRestartCount > 0) {
111
+ console.log(`[${this.topic}] Crash counter reset (was ${this.crashRestartCount}) after successful message processing`);
112
+ this.crashRestartCount = 0;
113
+ }
114
+ this.lastSuccessfulMessageAt = Date.now();
70
115
  console.log(`Message manually acknowledged for topic: ${this.topic}`);
71
116
  });
72
117
  }
@@ -198,3 +243,4 @@ class Listener {
198
243
  }
199
244
  }
200
245
  exports.Listener = Listener;
246
+ Listener.registry = [];
@@ -0,0 +1,7 @@
1
+ export declare enum SubscriptionTier {
2
+ Free = "free",
3
+ Basic = "basic",
4
+ Pro = "pro",
5
+ Enterprise = "enterprise"
6
+ }
7
+ export declare const AGENT_LIMITS_BY_TIER: Record<SubscriptionTier, number>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_LIMITS_BY_TIER = exports.SubscriptionTier = void 0;
4
+ var SubscriptionTier;
5
+ (function (SubscriptionTier) {
6
+ SubscriptionTier["Free"] = "free";
7
+ SubscriptionTier["Basic"] = "basic";
8
+ SubscriptionTier["Pro"] = "pro";
9
+ SubscriptionTier["Enterprise"] = "enterprise";
10
+ })(SubscriptionTier || (exports.SubscriptionTier = SubscriptionTier = {}));
11
+ exports.AGENT_LIMITS_BY_TIER = {
12
+ [SubscriptionTier.Free]: 2,
13
+ [SubscriptionTier.Basic]: 3,
14
+ [SubscriptionTier.Pro]: 5,
15
+ [SubscriptionTier.Enterprise]: 50,
16
+ };
@@ -1,6 +1,7 @@
1
1
  import { Subjects } from "./subjects";
2
2
  import { BaseEvent } from "./baseEvent";
3
3
  import { UserStatus } from "./types/userStatus";
4
+ import { SubscriptionTier } from "./types/subscriptionTier";
4
5
  interface UserCreatedEvent extends BaseEvent {
5
6
  subject: Subjects.UserCreated;
6
7
  data: {
@@ -11,6 +12,7 @@ interface UserCreatedEvent extends BaseEvent {
11
12
  isAgent?: boolean;
12
13
  ownerUserId?: string;
13
14
  role?: 'user' | 'admin';
15
+ subscriptionTier?: SubscriptionTier;
14
16
  };
15
17
  }
16
18
  interface UserUpdatedEvent extends BaseEvent {
@@ -21,6 +23,7 @@ interface UserUpdatedEvent extends BaseEvent {
21
23
  status: UserStatus;
22
24
  version: number;
23
25
  role?: 'user' | 'admin';
26
+ subscriptionTier?: SubscriptionTier;
24
27
  };
25
28
  }
26
29
  interface UserSingedInEvent extends BaseEvent {
package/build/index.d.ts CHANGED
@@ -36,6 +36,7 @@ export * from "./events/types/friendshipStatus";
36
36
  export * from "./events/types/userStatus";
37
37
  export * from "./events/types/visibility";
38
38
  export * from "./events/types/postStatus";
39
+ export * from "./events/types/subscriptionTier";
39
40
  export * from "./events/userEvents";
40
41
  export * from "./events/mediaEvents";
41
42
  export * from "./events/agentManagerEvents";
package/build/index.js CHANGED
@@ -52,6 +52,7 @@ __exportStar(require("./events/types/friendshipStatus"), exports);
52
52
  __exportStar(require("./events/types/userStatus"), exports);
53
53
  __exportStar(require("./events/types/visibility"), exports);
54
54
  __exportStar(require("./events/types/postStatus"), exports);
55
+ __exportStar(require("./events/types/subscriptionTier"), exports);
55
56
  __exportStar(require("./events/userEvents"), exports);
56
57
  __exportStar(require("./events/mediaEvents"), exports);
57
58
  __exportStar(require("./events/agentManagerEvents"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aichatwar/shared",
3
- "version": "1.0.168",
3
+ "version": "1.0.170",
4
4
  "main": "./build/index.js",
5
5
  "typs": "./build/index.d.ts",
6
6
  "files": [