@adalo/metrics 0.1.123 → 0.1.125

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.
Files changed (63) hide show
  1. package/lib/health/healthCheckCache.d.ts +57 -0
  2. package/lib/health/healthCheckCache.d.ts.map +1 -0
  3. package/lib/health/healthCheckCache.js +200 -0
  4. package/lib/health/healthCheckCache.js.map +1 -0
  5. package/lib/{healthCheckClient.d.ts → health/healthCheckClient.d.ts} +54 -12
  6. package/lib/health/healthCheckClient.d.ts.map +1 -0
  7. package/lib/{healthCheckClient.js → health/healthCheckClient.js} +173 -27
  8. package/lib/health/healthCheckClient.js.map +1 -0
  9. package/lib/health/healthCheckUtils.d.ts +54 -0
  10. package/lib/health/healthCheckUtils.d.ts.map +1 -0
  11. package/lib/health/healthCheckUtils.js +142 -0
  12. package/lib/health/healthCheckUtils.js.map +1 -0
  13. package/lib/health/healthCheckWorker.d.ts +2 -0
  14. package/lib/health/healthCheckWorker.d.ts.map +1 -0
  15. package/lib/health/healthCheckWorker.js +64 -0
  16. package/lib/health/healthCheckWorker.js.map +1 -0
  17. package/lib/index.d.ts +8 -6
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +28 -6
  20. package/lib/index.js.map +1 -1
  21. package/lib/metrics/baseMetricsClient.d.ts.map +1 -0
  22. package/lib/metrics/baseMetricsClient.js.map +1 -0
  23. package/lib/metrics/metricsClient.d.ts.map +1 -0
  24. package/lib/metrics/metricsClient.js.map +1 -0
  25. package/lib/metrics/metricsDatabaseClient.d.ts.map +1 -0
  26. package/lib/metrics/metricsDatabaseClient.js.map +1 -0
  27. package/lib/metrics/metricsQueueRedisClient.d.ts.map +1 -0
  28. package/lib/{metricsQueueRedisClient.js → metrics/metricsQueueRedisClient.js} +2 -2
  29. package/lib/metrics/metricsQueueRedisClient.js.map +1 -0
  30. package/lib/metrics/metricsRedisClient.d.ts.map +1 -0
  31. package/lib/{metricsRedisClient.js → metrics/metricsRedisClient.js} +1 -1
  32. package/lib/metrics/metricsRedisClient.js.map +1 -0
  33. package/package.json +1 -1
  34. package/src/health/healthCheckCache.js +237 -0
  35. package/src/{healthCheckClient.js → health/healthCheckClient.js} +171 -31
  36. package/src/health/healthCheckUtils.js +143 -0
  37. package/src/health/healthCheckWorker.js +61 -0
  38. package/src/index.ts +8 -6
  39. package/src/{metricsQueueRedisClient.js → metrics/metricsQueueRedisClient.js} +2 -2
  40. package/src/{metricsRedisClient.js → metrics/metricsRedisClient.js} +1 -1
  41. package/lib/baseMetricsClient.d.ts.map +0 -1
  42. package/lib/baseMetricsClient.js.map +0 -1
  43. package/lib/healthCheckClient.d.ts.map +0 -1
  44. package/lib/healthCheckClient.js.map +0 -1
  45. package/lib/metricsClient.d.ts.map +0 -1
  46. package/lib/metricsClient.js.map +0 -1
  47. package/lib/metricsDatabaseClient.d.ts.map +0 -1
  48. package/lib/metricsDatabaseClient.js.map +0 -1
  49. package/lib/metricsQueueRedisClient.d.ts.map +0 -1
  50. package/lib/metricsQueueRedisClient.js.map +0 -1
  51. package/lib/metricsRedisClient.d.ts.map +0 -1
  52. package/lib/metricsRedisClient.js.map +0 -1
  53. /package/lib/{baseMetricsClient.d.ts → metrics/baseMetricsClient.d.ts} +0 -0
  54. /package/lib/{baseMetricsClient.js → metrics/baseMetricsClient.js} +0 -0
  55. /package/lib/{metricsClient.d.ts → metrics/metricsClient.d.ts} +0 -0
  56. /package/lib/{metricsClient.js → metrics/metricsClient.js} +0 -0
  57. /package/lib/{metricsDatabaseClient.d.ts → metrics/metricsDatabaseClient.d.ts} +0 -0
  58. /package/lib/{metricsDatabaseClient.js → metrics/metricsDatabaseClient.js} +0 -0
  59. /package/lib/{metricsQueueRedisClient.d.ts → metrics/metricsQueueRedisClient.d.ts} +0 -0
  60. /package/lib/{metricsRedisClient.d.ts → metrics/metricsRedisClient.d.ts} +0 -0
  61. /package/src/{baseMetricsClient.js → metrics/baseMetricsClient.js} +0 -0
  62. /package/src/{metricsClient.js → metrics/metricsClient.js} +0 -0
  63. /package/src/{metricsDatabaseClient.js → metrics/metricsDatabaseClient.js} +0 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * HealthCheckCache provides a shared cache layer for health check results.
3
+ * It uses Redis if available for cross-process sharing, with graceful fallback
4
+ * to in-memory cache if Redis is not configured or unavailable.
5
+ */
6
+ export class HealthCheckCache {
7
+ /**
8
+ * @param {Object} options
9
+ * @param {any} [options.redisClient] - Redis client instance (optional)
10
+ * @param {string} [options.appName] - Application name for cache key
11
+ * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
12
+ */
13
+ constructor(options?: {
14
+ redisClient?: any;
15
+ appName?: string | undefined;
16
+ cacheTtlMs?: number | undefined;
17
+ });
18
+ redisClient: any;
19
+ appName: string;
20
+ cacheTtlMs: number;
21
+ cacheKey: string;
22
+ /** In-memory fallback cache */
23
+ _memoryCache: any;
24
+ _memoryCacheTimestamp: any;
25
+ _redisClientType: string | undefined;
26
+ _redisAvailable: boolean;
27
+ /**
28
+ * Checks if Redis is available and working.
29
+ * @returns {Promise<boolean>}
30
+ * @private
31
+ */
32
+ private _checkRedisAvailable;
33
+ /**
34
+ * Gets cached health check result from Redis (if available) or in-memory cache.
35
+ * Throws error if Redis is configured but read fails (so caller can return proper error format).
36
+ * @returns {Promise<Object | null>} Cached result or null
37
+ * @throws {Error} If Redis is configured but read fails
38
+ */
39
+ get(): Promise<Object | null>;
40
+ /**
41
+ * Sets cached health check result in Redis (if available) and in-memory.
42
+ * @param {Object} result - Health check result to cache
43
+ * @returns {Promise<void>}
44
+ */
45
+ set(result: Object): Promise<void>;
46
+ /**
47
+ * Clears the cache (both Redis and in-memory).
48
+ * @returns {Promise<void>}
49
+ */
50
+ clear(): Promise<void>;
51
+ /**
52
+ * Checks if Redis is configured and available.
53
+ * @returns {boolean}
54
+ */
55
+ isRedisAvailable(): boolean;
56
+ }
57
+ //# sourceMappingURL=healthCheckCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healthCheckCache.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckCache.js"],"names":[],"mappings":"AAOA;;;;GAIG;AACH;IACE;;;;;OAKG;IACH;QAJyB,WAAW,GAAzB,GAAG;QACc,OAAO;QACP,UAAU;OAqBrC;IAlBC,iBAA8C;IAC9C,gBAA6E;IAC7E,mBAAiD;IACjD,iBAA6C;IAE7C,+BAA+B;IAC/B,kBAAwB;IACxB,2BAAiC;IAG/B,qCAA4D;IAC5D,yBAA2B;IAS/B;;;;OAIG;IACH,6BAiCC;IAED;;;;;OAKG;IACH,OAHa,QAAQ,MAAM,GAAG,IAAI,CAAC,CA2DlC;IAED;;;;OAIG;IACH,YAHW,MAAM,GACJ,QAAQ,IAAI,CAAC,CA6CzB;IAED;;;OAGG;IACH,SAFa,QAAQ,IAAI,CAAC,CA4BzB;IAED;;;OAGG;IACH,oBAFa,OAAO,CAInB;CACF"}
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+
3
+ const {
4
+ getRedisClientType,
5
+ REDIS_V4,
6
+ IOREDIS,
7
+ REDIS_V3
8
+ } = require('../redisUtils');
9
+
10
+ /**
11
+ * HealthCheckCache provides a shared cache layer for health check results.
12
+ * It uses Redis if available for cross-process sharing, with graceful fallback
13
+ * to in-memory cache if Redis is not configured or unavailable.
14
+ */
15
+ class HealthCheckCache {
16
+ /**
17
+ * @param {Object} options
18
+ * @param {any} [options.redisClient] - Redis client instance (optional)
19
+ * @param {string} [options.appName] - Application name for cache key
20
+ * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
21
+ */
22
+ constructor(options = {}) {
23
+ this.redisClient = options.redisClient || null;
24
+ this.appName = options.appName || process.env.BUILD_APP_NAME || 'unknown-app';
25
+ this.cacheTtlMs = options.cacheTtlMs ?? 60 * 1000;
26
+ this.cacheKey = `healthcheck:${this.appName}`;
27
+
28
+ /** In-memory fallback cache */
29
+ this._memoryCache = null;
30
+ this._memoryCacheTimestamp = null;
31
+ if (this.redisClient) {
32
+ this._redisClientType = getRedisClientType(this.redisClient);
33
+ this._redisAvailable = true;
34
+ } else {
35
+ this._redisAvailable = false;
36
+ console.warn(`[HealthCheckCache] Redis not configured for ${this.appName}, using in-memory cache only (not shared across processes)`);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Checks if Redis is available and working.
42
+ * @returns {Promise<boolean>}
43
+ * @private
44
+ */
45
+ async _checkRedisAvailable() {
46
+ if (!this.redisClient || !this._redisAvailable) {
47
+ return false;
48
+ }
49
+ try {
50
+ let pong;
51
+ if (this._redisClientType === REDIS_V3) {
52
+ pong = await new Promise((resolve, reject) => {
53
+ this.redisClient.ping((err, result) => {
54
+ if (err) reject(err);else resolve(result);
55
+ });
56
+ });
57
+ } else if (this._redisClientType === REDIS_V4 || this._redisClientType === IOREDIS) {
58
+ pong = await this.redisClient.ping();
59
+ } else {
60
+ return false;
61
+ }
62
+ return pong === 'PONG';
63
+ } catch (err) {
64
+ // Redis not available
65
+ if (this._redisAvailable) {
66
+ console.warn(`[HealthCheckCache] Redis became unavailable: ${err.message}`);
67
+ this._redisAvailable = false;
68
+ }
69
+ return false;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Gets cached health check result from Redis (if available) or in-memory cache.
75
+ * Throws error if Redis is configured but read fails (so caller can return proper error format).
76
+ * @returns {Promise<Object | null>} Cached result or null
77
+ * @throws {Error} If Redis is configured but read fails
78
+ */
79
+ async get() {
80
+ // If Redis is configured, we MUST read from it (don't fall back to memory)
81
+ if (this.redisClient) {
82
+ try {
83
+ let cachedStr;
84
+ if (this._redisClientType === REDIS_V3) {
85
+ cachedStr = await new Promise((resolve, reject) => {
86
+ this.redisClient.get(this.cacheKey, (err, result) => {
87
+ if (err) reject(err);else resolve(result);
88
+ });
89
+ });
90
+ } else if (this._redisClientType === REDIS_V4 || this._redisClientType === IOREDIS) {
91
+ cachedStr = await this.redisClient.get(this.cacheKey);
92
+ }
93
+ if (cachedStr) {
94
+ try {
95
+ const cached = JSON.parse(cachedStr);
96
+ if (cached.result && cached.timestamp) {
97
+ const age = Date.now() - cached.timestamp;
98
+ if (age < this.cacheTtlMs) {
99
+ // Also update in-memory cache as backup
100
+ this._memoryCache = cached.result;
101
+ this._memoryCacheTimestamp = cached.timestamp;
102
+ return cached.result;
103
+ }
104
+ }
105
+ } catch (parseErr) {
106
+ console.warn(`[HealthCheckCache] Failed to parse Redis cache:`, parseErr.message);
107
+ }
108
+ }
109
+ // No cache in Redis - return null (worker may not have run yet)
110
+ return null;
111
+ } catch (redisErr) {
112
+ // Redis read failed - throw error so caller can return proper error format
113
+ this._redisAvailable = false;
114
+ throw new Error(`Redis cache read failed: ${redisErr.message}`);
115
+ }
116
+ }
117
+
118
+ // No Redis configured - fall back to in-memory cache
119
+ if (this._memoryCache && this._memoryCacheTimestamp) {
120
+ const age = Date.now() - this._memoryCacheTimestamp;
121
+ if (age < this.cacheTtlMs) {
122
+ return this._memoryCache;
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+
128
+ /**
129
+ * Sets cached health check result in Redis (if available) and in-memory.
130
+ * @param {Object} result - Health check result to cache
131
+ * @returns {Promise<void>}
132
+ */
133
+ async set(result) {
134
+ const cacheData = {
135
+ result,
136
+ timestamp: Date.now()
137
+ };
138
+
139
+ // Update in-memory cache
140
+ this._memoryCache = result;
141
+ this._memoryCacheTimestamp = cacheData.timestamp;
142
+
143
+ // Try to update Redis if available
144
+ if (await this._checkRedisAvailable()) {
145
+ try {
146
+ const cacheStr = JSON.stringify(cacheData);
147
+ const ttlSeconds = Math.ceil(this.cacheTtlMs / 1000) + 10;
148
+ if (this._redisClientType === REDIS_V3) {
149
+ await new Promise((resolve, reject) => {
150
+ this.redisClient.setex(this.cacheKey, ttlSeconds, cacheStr, err => {
151
+ if (err) reject(err);else resolve();
152
+ });
153
+ });
154
+ } else if (this._redisClientType === REDIS_V4 || this._redisClientType === IOREDIS) {
155
+ await this.redisClient.setex(this.cacheKey, ttlSeconds, cacheStr);
156
+ }
157
+ } catch (redisErr) {
158
+ // Redis write failed, but in-memory cache is updated
159
+ console.warn(`[HealthCheckCache] Redis write failed (in-memory cache updated):`, redisErr.message);
160
+ this._redisAvailable = false;
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Clears the cache (both Redis and in-memory).
167
+ * @returns {Promise<void>}
168
+ */
169
+ async clear() {
170
+ this._memoryCache = null;
171
+ this._memoryCacheTimestamp = null;
172
+ if (await this._checkRedisAvailable()) {
173
+ try {
174
+ if (this._redisClientType === REDIS_V3) {
175
+ await new Promise((resolve, reject) => {
176
+ this.redisClient.del(this.cacheKey, err => {
177
+ if (err) reject(err);else resolve();
178
+ });
179
+ });
180
+ } else if (this._redisClientType === REDIS_V4 || this._redisClientType === IOREDIS) {
181
+ await this.redisClient.del(this.cacheKey);
182
+ }
183
+ } catch (redisErr) {
184
+ console.warn(`[HealthCheckCache] Redis clear failed:`, redisErr.message);
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Checks if Redis is configured and available.
191
+ * @returns {boolean}
192
+ */
193
+ isRedisAvailable() {
194
+ return this._redisAvailable && this.redisClient !== null;
195
+ }
196
+ }
197
+ module.exports = {
198
+ HealthCheckCache
199
+ };
200
+ //# sourceMappingURL=healthCheckCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healthCheckCache.js","names":["getRedisClientType","REDIS_V4","IOREDIS","REDIS_V3","require","HealthCheckCache","constructor","options","redisClient","appName","process","env","BUILD_APP_NAME","cacheTtlMs","cacheKey","_memoryCache","_memoryCacheTimestamp","_redisClientType","_redisAvailable","console","warn","_checkRedisAvailable","pong","Promise","resolve","reject","ping","err","result","message","get","cachedStr","cached","JSON","parse","timestamp","age","Date","now","parseErr","redisErr","Error","set","cacheData","cacheStr","stringify","ttlSeconds","Math","ceil","setex","clear","del","isRedisAvailable","module","exports"],"sources":["../../src/health/healthCheckCache.js"],"sourcesContent":["const {\n getRedisClientType,\n REDIS_V4,\n IOREDIS,\n REDIS_V3,\n} = require('../redisUtils')\n\n/**\n * HealthCheckCache provides a shared cache layer for health check results.\n * It uses Redis if available for cross-process sharing, with graceful fallback\n * to in-memory cache if Redis is not configured or unavailable.\n */\nclass HealthCheckCache {\n /**\n * @param {Object} options\n * @param {any} [options.redisClient] - Redis client instance (optional)\n * @param {string} [options.appName] - Application name for cache key\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds\n */\n constructor(options = {}) {\n this.redisClient = options.redisClient || null\n this.appName = options.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.cacheTtlMs = options.cacheTtlMs ?? 60 * 1000\n this.cacheKey = `healthcheck:${this.appName}`\n \n /** In-memory fallback cache */\n this._memoryCache = null\n this._memoryCacheTimestamp = null\n\n if (this.redisClient) {\n this._redisClientType = getRedisClientType(this.redisClient)\n this._redisAvailable = true\n } else {\n this._redisAvailable = false\n console.warn(\n `[HealthCheckCache] Redis not configured for ${this.appName}, using in-memory cache only (not shared across processes)`\n )\n }\n }\n\n /**\n * Checks if Redis is available and working.\n * @returns {Promise<boolean>}\n * @private\n */\n async _checkRedisAvailable() {\n if (!this.redisClient || !this._redisAvailable) {\n return false\n }\n\n try {\n let pong\n if (this._redisClientType === REDIS_V3) {\n pong = await new Promise((resolve, reject) => {\n this.redisClient.ping((err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n } else if (\n this._redisClientType === REDIS_V4 ||\n this._redisClientType === IOREDIS\n ) {\n pong = await this.redisClient.ping()\n } else {\n return false\n }\n return pong === 'PONG'\n } catch (err) {\n // Redis not available\n if (this._redisAvailable) {\n console.warn(\n `[HealthCheckCache] Redis became unavailable: ${err.message}`\n )\n this._redisAvailable = false\n }\n return false\n }\n }\n\n /**\n * Gets cached health check result from Redis (if available) or in-memory cache.\n * Throws error if Redis is configured but read fails (so caller can return proper error format).\n * @returns {Promise<Object | null>} Cached result or null\n * @throws {Error} If Redis is configured but read fails\n */\n async get() {\n // If Redis is configured, we MUST read from it (don't fall back to memory)\n if (this.redisClient) {\n try {\n let cachedStr\n if (this._redisClientType === REDIS_V3) {\n cachedStr = await new Promise((resolve, reject) => {\n this.redisClient.get(this.cacheKey, (err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n } else if (\n this._redisClientType === REDIS_V4 ||\n this._redisClientType === IOREDIS\n ) {\n cachedStr = await this.redisClient.get(this.cacheKey)\n }\n\n if (cachedStr) {\n try {\n const cached = JSON.parse(cachedStr)\n if (cached.result && cached.timestamp) {\n const age = Date.now() - cached.timestamp\n if (age < this.cacheTtlMs) {\n // Also update in-memory cache as backup\n this._memoryCache = cached.result\n this._memoryCacheTimestamp = cached.timestamp\n return cached.result\n }\n }\n } catch (parseErr) {\n console.warn(\n `[HealthCheckCache] Failed to parse Redis cache:`,\n parseErr.message\n )\n }\n }\n // No cache in Redis - return null (worker may not have run yet)\n return null\n } catch (redisErr) {\n // Redis read failed - throw error so caller can return proper error format\n this._redisAvailable = false\n throw new Error(`Redis cache read failed: ${redisErr.message}`)\n }\n }\n\n // No Redis configured - fall back to in-memory cache\n if (this._memoryCache && this._memoryCacheTimestamp) {\n const age = Date.now() - this._memoryCacheTimestamp\n if (age < this.cacheTtlMs) {\n return this._memoryCache\n }\n }\n\n return null\n }\n\n /**\n * Sets cached health check result in Redis (if available) and in-memory.\n * @param {Object} result - Health check result to cache\n * @returns {Promise<void>}\n */\n async set(result) {\n const cacheData = {\n result,\n timestamp: Date.now(),\n }\n\n // Update in-memory cache\n this._memoryCache = result\n this._memoryCacheTimestamp = cacheData.timestamp\n\n // Try to update Redis if available\n if (await this._checkRedisAvailable()) {\n try {\n const cacheStr = JSON.stringify(cacheData)\n const ttlSeconds = Math.ceil(this.cacheTtlMs / 1000) + 10\n\n if (this._redisClientType === REDIS_V3) {\n await new Promise((resolve, reject) => {\n this.redisClient.setex(\n this.cacheKey,\n ttlSeconds,\n cacheStr,\n (err) => {\n if (err) reject(err)\n else resolve()\n }\n )\n })\n } else if (\n this._redisClientType === REDIS_V4 ||\n this._redisClientType === IOREDIS\n ) {\n await this.redisClient.setex(this.cacheKey, ttlSeconds, cacheStr)\n }\n } catch (redisErr) {\n // Redis write failed, but in-memory cache is updated\n console.warn(\n `[HealthCheckCache] Redis write failed (in-memory cache updated):`,\n redisErr.message\n )\n this._redisAvailable = false\n }\n }\n }\n\n /**\n * Clears the cache (both Redis and in-memory).\n * @returns {Promise<void>}\n */\n async clear() {\n this._memoryCache = null\n this._memoryCacheTimestamp = null\n\n if (await this._checkRedisAvailable()) {\n try {\n if (this._redisClientType === REDIS_V3) {\n await new Promise((resolve, reject) => {\n this.redisClient.del(this.cacheKey, (err) => {\n if (err) reject(err)\n else resolve()\n })\n })\n } else if (\n this._redisClientType === REDIS_V4 ||\n this._redisClientType === IOREDIS\n ) {\n await this.redisClient.del(this.cacheKey)\n }\n } catch (redisErr) {\n console.warn(\n `[HealthCheckCache] Redis clear failed:`,\n redisErr.message\n )\n }\n }\n }\n\n /**\n * Checks if Redis is configured and available.\n * @returns {boolean}\n */\n isRedisAvailable() {\n return this._redisAvailable && this.redisClient !== null\n }\n}\n\nmodule.exports = { HealthCheckCache }\n\n"],"mappings":"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","ignoreList":[]}
@@ -5,7 +5,11 @@ export type ComponentHealth = {
5
5
  */
6
6
  status: HealthStatus;
7
7
  /**
8
- * - Optional status message
8
+ * - Error message if status is unhealthy
9
+ */
10
+ error?: string | undefined;
11
+ /**
12
+ * - Optional status message (deprecated, use error)
9
13
  */
10
14
  message?: string | undefined;
11
15
  /**
@@ -35,15 +39,15 @@ export type HealthCheckResult = {
35
39
  */
36
40
  timestamp: string;
37
41
  /**
38
- * - Whether this result is from cache
42
+ * - Individual service health checks
39
43
  */
40
- cached: boolean;
41
- /**
42
- * - Individual component health
43
- */
44
- components: {
44
+ checks: {
45
45
  [x: string]: ComponentHealth | DatabaseClusterHealth;
46
46
  };
47
+ /**
48
+ * - Top-level error messages (not related to specific services)
49
+ */
50
+ errors?: string[] | undefined;
47
51
  };
48
52
  export type CachedHealthResult = {
49
53
  /**
@@ -71,7 +75,8 @@ export type DatabaseConfig = {
71
75
  /**
72
76
  * @typedef {Object} ComponentHealth
73
77
  * @property {HealthStatus} status - Component health status
74
- * @property {string} [message] - Optional status message
78
+ * @property {string} [error] - Error message if status is unhealthy
79
+ * @property {string} [message] - Optional status message (deprecated, use error)
75
80
  * @property {number} [latencyMs] - Connection latency in milliseconds
76
81
  */
77
82
  /**
@@ -83,8 +88,8 @@ export type DatabaseConfig = {
83
88
  * @typedef {Object} HealthCheckResult
84
89
  * @property {HealthStatus} status - Overall health status
85
90
  * @property {string} timestamp - ISO timestamp of the check
86
- * @property {boolean} cached - Whether this result is from cache
87
- * @property {Object<string, ComponentHealth | DatabaseClusterHealth>} components - Individual component health
91
+ * @property {Object<string, ComponentHealth | DatabaseClusterHealth>} checks - Individual service health checks
92
+ * @property {string[]} [errors] - Top-level error messages (not related to specific services)
88
93
  */
89
94
  /**
90
95
  * @typedef {Object} CachedHealthResult
@@ -114,7 +119,8 @@ export class HealthCheckClient {
114
119
  * @param {string} [options.databaseUrl] - Main PostgreSQL connection URL
115
120
  * @param {string} [options.databaseName='main'] - Name for the main database
116
121
  * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional DB clusters (name -> URL)
117
- * @param {any} [options.redisClient] - Redis client instance (ioredis or node-redis)
122
+ * @param {any} [options.redisClient] - Redis client instance (ioredis or node-redis) - used for cache
123
+ * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in results (for backend)
118
124
  * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds (default: 60s)
119
125
  * @param {string} [options.appName] - Application name for logging
120
126
  */
@@ -125,15 +131,19 @@ export class HealthCheckClient {
125
131
  [x: string]: string;
126
132
  } | undefined;
127
133
  redisClient?: any;
134
+ includeRedisCheck?: boolean | undefined;
128
135
  cacheTtlMs?: number | undefined;
129
136
  appName?: string | undefined;
130
137
  });
131
138
  redisClient: any;
139
+ includeRedisCheck: boolean;
132
140
  cacheTtlMs: number;
133
141
  appName: string;
134
142
  prefixLogs: string;
135
143
  /** @type {CachedHealthResult | null} */
136
144
  _cachedResult: CachedHealthResult | null;
145
+ /** @type {Promise<HealthCheckResult> | null} */
146
+ _refreshPromise: Promise<HealthCheckResult> | null;
137
147
  /** @type {Map<string, Pool>} */
138
148
  _databasePools: Map<string, Pool>;
139
149
  /** @type {DatabaseConfig | null} */
@@ -141,6 +151,7 @@ export class HealthCheckClient {
141
151
  /** @type {DatabaseConfig[]} */
142
152
  _clusterConfigs: DatabaseConfig[];
143
153
  _redisClientType: string | undefined;
154
+ _cache: HealthCheckCache;
144
155
  /**
145
156
  * Initialize database configurations from options.
146
157
  * @param {Object} options - Constructor options
@@ -182,10 +193,37 @@ export class HealthCheckClient {
182
193
  /**
183
194
  * Performs a full health check on all configured components.
184
195
  * Results are cached for the configured TTL to prevent excessive load.
196
+ * Uses a mutex pattern to prevent concurrent health checks when cache expires.
197
+ * If cache is expired but a refresh is in progress, returns stale cache (if available).
185
198
  *
186
199
  * @returns {Promise<HealthCheckResult>}
187
200
  */
188
201
  performHealthCheck(): Promise<HealthCheckResult>;
202
+ /**
203
+ * Internal method that actually performs the health check.
204
+ * This is separated to allow the mutex pattern in performHealthCheck.
205
+ * Only checks database - Redis is used only for cache, not health check.
206
+ *
207
+ * @returns {Promise<HealthCheckResult>}
208
+ * @private
209
+ */
210
+ private _performHealthCheckInternal;
211
+ /**
212
+ * Gets cached result from shared cache (Redis if available, otherwise in-memory).
213
+ * Returns null if no cache is available. This is used by endpoints to read-only access.
214
+ * If Redis fails, returns error result with proper format.
215
+ *
216
+ * @returns {Promise<HealthCheckResult | null>} Cached result, error result if Redis fails, or null if not available
217
+ */
218
+ getCachedResult(): Promise<HealthCheckResult | null>;
219
+ /**
220
+ * Forces a refresh of the health check cache.
221
+ * This is used by background workers to periodically update the cache.
222
+ * Updates shared cache (Redis if available, otherwise in-memory).
223
+ *
224
+ * @returns {Promise<HealthCheckResult>}
225
+ */
226
+ refreshCache(): Promise<HealthCheckResult>;
189
227
  /**
190
228
  * Clears the cached health check result, forcing the next check to be fresh.
191
229
  */
@@ -204,6 +242,9 @@ export class HealthCheckClient {
204
242
  * Response includes errors array when status is not healthy.
205
243
  * All sensitive data (passwords, connection strings, etc.) is masked.
206
244
  *
245
+ * This handler only reads from cache and never triggers database queries.
246
+ * Use a background worker to periodically refresh the cache.
247
+ *
207
248
  * @returns {(req: any, res: any) => Promise<void>} Express request handler
208
249
  */
209
250
  healthHandler(): (req: any, res: any) => Promise<void>;
@@ -211,7 +252,7 @@ export class HealthCheckClient {
211
252
  * Register health check endpoint on an Express app.
212
253
  *
213
254
  * @param {import('express').Application} app - Express application
214
- * @param {string} [path='/health-status'] - Path for the health endpoint
255
+ * @param {string} [path='/health'] - Path for the health endpoint
215
256
  */
216
257
  registerHealthEndpoint(app: any, path?: string | undefined): void;
217
258
  /**
@@ -221,4 +262,5 @@ export class HealthCheckClient {
221
262
  cleanup(): Promise<void>;
222
263
  }
223
264
  import { Pool } from "pg";
265
+ import { HealthCheckCache } from "./healthCheckCache";
224
266
  //# sourceMappingURL=healthCheckClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healthCheckClient.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckClient.js"],"names":[],"mappings":"2BAiEa,SAAS,GAAG,WAAW,GAAG,UAAU;;;;;YAKnC,YAAY;;;;;;;;;;;;;;;;;;YAQZ,YAAY;;;;;YACL,MAAM,GAAE,eAAe;;;;;;;YAK9B,YAAY;;;;eACZ,MAAM;;;;;YACC,MAAM,GAAE,eAAe,GAAG,qBAAqB;;;;;;;;;;;YAMtD,iBAAiB;;;;eACjB,MAAM;;;;;;UAKN,MAAM;;;;SACN,MAAM;;AAnCpB;;GAEG;AAEH;;;;;;GAMG;AAEH;;;;GAIG;AAEH;;;;;;GAMG;AAEH;;;;GAIG;AAEH;;;;GAIG;AAEH;;;;;;;;;;;GAWG;AACH;IACE;;;;;;;;;OASG;IACH;QAR4B,WAAW;QACX,YAAY;QACI,sBAAsB;;;QACzC,WAAW,GAAzB,GAAG;QACe,iBAAiB;QAClB,UAAU;QACV,OAAO;OAqClC;IAlCC,iBAA8C;IAC9C,2BAA2D;IAC3D,mBAAiE;IACjE,gBACgE;IAEhE,mBAAmD;IAEnD,wCAAwC;IACxC,eADW,kBAAkB,GAAG,IAAI,CACX;IAEzB,gDAAgD;IAChD,iBADW,QAAQ,iBAAiB,CAAC,GAAG,IAAI,CACjB;IAE3B,gCAAgC;IAChC,gBADW,IAAI,MAAM,EAAE,IAAI,CAAC,CACG;IAE/B,oCAAoC;IACpC,qBADW,cAAc,GAAG,IAAI,CACD;IAE/B,+BAA+B;IAC/B,iBADW,cAAc,EAAE,CACF;IAKvB,qCAA4D;IAG9D,yBAIE;IAGJ;;;;OAIG;IACH,uBAcC;IAED;;;;;OAKG;IACH,iBAaC;IAED;;;;OAIG;IACH,sBAGC;IAED;;;;;OAKG;IACH,6BAiBC;IAED;;;;OAIG;IACH,2BAsDC;IAED;;;;OAIG;IACH,oBA6CC;IAED;;;;;;;OAOG;IACH,sBAFa,QAAQ,iBAAiB,CAAC,CAqCtC;IAED;;;;;;;OAOG;IACH,oCAoCC;IAED;;;;;;OAMG;IACH,mBAFa,QAAQ,iBAAiB,GAAG,IAAI,CAAC,CA0B7C;IAED;;;;;;OAMG;IACH,gBAFa,QAAQ,iBAAiB,CAAC,CAOtC;IAED;;OAEG;IACH,mBAGC;IAED;;;;;;OAMG;IACH,0BA8BC;IAED;;;;;;;;;;OAUG;IACH,uBAFmB,GAAG,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,CAuDjD;IAED;;;;;OAKG;IACH,kEAGC;IAED;;;OAGG;IACH,WAFa,QAAQ,IAAI,CAAC,CAWzB;CACF"}