@adalo/metrics 0.1.122 → 0.1.124
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/lib/health/healthCheckCache.d.ts +57 -0
- package/lib/health/healthCheckCache.d.ts.map +1 -0
- package/lib/health/healthCheckCache.js +200 -0
- package/lib/health/healthCheckCache.js.map +1 -0
- package/lib/{healthCheckClient.d.ts → health/healthCheckClient.d.ts} +59 -15
- package/lib/health/healthCheckClient.d.ts.map +1 -0
- package/lib/{healthCheckClient.js → health/healthCheckClient.js} +232 -49
- package/lib/health/healthCheckClient.js.map +1 -0
- package/lib/health/healthCheckUtils.d.ts +54 -0
- package/lib/health/healthCheckUtils.d.ts.map +1 -0
- package/lib/health/healthCheckUtils.js +142 -0
- package/lib/health/healthCheckUtils.js.map +1 -0
- package/lib/health/healthCheckWorker.d.ts +2 -0
- package/lib/health/healthCheckWorker.d.ts.map +1 -0
- package/lib/health/healthCheckWorker.js +64 -0
- package/lib/health/healthCheckWorker.js.map +1 -0
- package/lib/index.d.ts +8 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +28 -6
- package/lib/index.js.map +1 -1
- package/lib/metrics/baseMetricsClient.d.ts.map +1 -0
- package/lib/metrics/baseMetricsClient.js.map +1 -0
- package/lib/metrics/metricsClient.d.ts.map +1 -0
- package/lib/metrics/metricsClient.js.map +1 -0
- package/lib/metrics/metricsDatabaseClient.d.ts.map +1 -0
- package/lib/metrics/metricsDatabaseClient.js.map +1 -0
- package/lib/metrics/metricsQueueRedisClient.d.ts.map +1 -0
- package/lib/{metricsQueueRedisClient.js → metrics/metricsQueueRedisClient.js} +2 -2
- package/lib/metrics/metricsQueueRedisClient.js.map +1 -0
- package/lib/metrics/metricsRedisClient.d.ts.map +1 -0
- package/lib/{metricsRedisClient.js → metrics/metricsRedisClient.js} +1 -1
- package/lib/metrics/metricsRedisClient.js.map +1 -0
- package/package.json +1 -1
- package/src/health/healthCheckCache.js +237 -0
- package/src/{healthCheckClient.js → health/healthCheckClient.js} +226 -49
- package/src/health/healthCheckUtils.js +143 -0
- package/src/health/healthCheckWorker.js +61 -0
- package/src/index.ts +8 -6
- package/src/{metricsQueueRedisClient.js → metrics/metricsQueueRedisClient.js} +2 -2
- package/src/{metricsRedisClient.js → metrics/metricsRedisClient.js} +1 -1
- package/lib/baseMetricsClient.d.ts.map +0 -1
- package/lib/baseMetricsClient.js.map +0 -1
- package/lib/healthCheckClient.d.ts.map +0 -1
- package/lib/healthCheckClient.js.map +0 -1
- package/lib/metricsClient.d.ts.map +0 -1
- package/lib/metricsClient.js.map +0 -1
- package/lib/metricsDatabaseClient.d.ts.map +0 -1
- package/lib/metricsDatabaseClient.js.map +0 -1
- package/lib/metricsQueueRedisClient.d.ts.map +0 -1
- package/lib/metricsQueueRedisClient.js.map +0 -1
- package/lib/metricsRedisClient.d.ts.map +0 -1
- package/lib/metricsRedisClient.js.map +0 -1
- /package/lib/{baseMetricsClient.d.ts → metrics/baseMetricsClient.d.ts} +0 -0
- /package/lib/{baseMetricsClient.js → metrics/baseMetricsClient.js} +0 -0
- /package/lib/{metricsClient.d.ts → metrics/metricsClient.d.ts} +0 -0
- /package/lib/{metricsClient.js → metrics/metricsClient.js} +0 -0
- /package/lib/{metricsDatabaseClient.d.ts → metrics/metricsDatabaseClient.d.ts} +0 -0
- /package/lib/{metricsDatabaseClient.js → metrics/metricsDatabaseClient.js} +0 -0
- /package/lib/{metricsQueueRedisClient.d.ts → metrics/metricsQueueRedisClient.d.ts} +0 -0
- /package/lib/{metricsRedisClient.d.ts → metrics/metricsRedisClient.d.ts} +0 -0
- /package/src/{baseMetricsClient.js → metrics/baseMetricsClient.js} +0 -0
- /package/src/{metricsClient.js → metrics/metricsClient.js} +0 -0
- /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
|
-
* -
|
|
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
|
-
* -
|
|
42
|
+
* - Individual service health checks
|
|
39
43
|
*/
|
|
40
|
-
|
|
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} [
|
|
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 {
|
|
87
|
-
* @property {
|
|
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,20 +131,27 @@ 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>;
|
|
149
|
+
/** @type {DatabaseConfig | null} */
|
|
150
|
+
_mainDatabaseConfig: DatabaseConfig | null;
|
|
139
151
|
/** @type {DatabaseConfig[]} */
|
|
140
|
-
|
|
152
|
+
_clusterConfigs: DatabaseConfig[];
|
|
141
153
|
_redisClientType: string | undefined;
|
|
154
|
+
_cache: HealthCheckCache;
|
|
142
155
|
/**
|
|
143
156
|
* Initialize database configurations from options.
|
|
144
157
|
* @param {Object} options - Constructor options
|
|
@@ -166,8 +179,8 @@ export class HealthCheckClient {
|
|
|
166
179
|
*/
|
|
167
180
|
private _checkSingleDatabase;
|
|
168
181
|
/**
|
|
169
|
-
* Tests all PostgreSQL
|
|
170
|
-
* @returns {Promise<
|
|
182
|
+
* Tests all PostgreSQL databases (main + clusters) in parallel.
|
|
183
|
+
* @returns {Promise<Object | null>} Database health with optional clusters
|
|
171
184
|
* @private
|
|
172
185
|
*/
|
|
173
186
|
private _checkAllDatabases;
|
|
@@ -180,10 +193,37 @@ export class HealthCheckClient {
|
|
|
180
193
|
/**
|
|
181
194
|
* Performs a full health check on all configured components.
|
|
182
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).
|
|
183
198
|
*
|
|
184
199
|
* @returns {Promise<HealthCheckResult>}
|
|
185
200
|
*/
|
|
186
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>;
|
|
187
227
|
/**
|
|
188
228
|
* Clears the cached health check result, forcing the next check to be fresh.
|
|
189
229
|
*/
|
|
@@ -202,6 +242,9 @@ export class HealthCheckClient {
|
|
|
202
242
|
* Response includes errors array when status is not healthy.
|
|
203
243
|
* All sensitive data (passwords, connection strings, etc.) is masked.
|
|
204
244
|
*
|
|
245
|
+
* This handler only reads from cache and never triggers database queries.
|
|
246
|
+
* Use a background worker to periodically refresh the cache.
|
|
247
|
+
*
|
|
205
248
|
* @returns {(req: any, res: any) => Promise<void>} Express request handler
|
|
206
249
|
*/
|
|
207
250
|
healthHandler(): (req: any, res: any) => Promise<void>;
|
|
@@ -209,7 +252,7 @@ export class HealthCheckClient {
|
|
|
209
252
|
* Register health check endpoint on an Express app.
|
|
210
253
|
*
|
|
211
254
|
* @param {import('express').Application} app - Express application
|
|
212
|
-
* @param {string} [path='/health'] - Path for the health endpoint
|
|
255
|
+
* @param {string} [path='/health-status'] - Path for the health endpoint
|
|
213
256
|
*/
|
|
214
257
|
registerHealthEndpoint(app: any, path?: string | undefined): void;
|
|
215
258
|
/**
|
|
@@ -219,4 +262,5 @@ export class HealthCheckClient {
|
|
|
219
262
|
cleanup(): Promise<void>;
|
|
220
263
|
}
|
|
221
264
|
import { Pool } from "pg";
|
|
265
|
+
import { HealthCheckCache } from "./healthCheckCache";
|
|
222
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"}
|