@adalo/metrics 0.1.131 → 0.1.133
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/databaseChecker.d.ts +41 -0
- package/lib/health/databaseChecker.d.ts.map +1 -0
- package/lib/health/databaseChecker.js +121 -0
- package/lib/health/databaseChecker.js.map +1 -0
- package/lib/health/healthCheckCache.d.ts +7 -5
- package/lib/health/healthCheckCache.d.ts.map +1 -1
- package/lib/health/healthCheckCache.js +11 -16
- package/lib/health/healthCheckCache.js.map +1 -1
- package/lib/health/healthCheckClient.d.ts +17 -8
- package/lib/health/healthCheckClient.d.ts.map +1 -1
- package/lib/health/healthCheckClient.js +52 -59
- package/lib/health/healthCheckClient.js.map +1 -1
- package/lib/health/healthCheckUtils.d.ts +24 -8
- package/lib/health/healthCheckUtils.d.ts.map +1 -1
- package/lib/health/healthCheckUtils.js +46 -13
- package/lib/health/healthCheckUtils.js.map +1 -1
- package/package.json +2 -1
- package/src/health/databaseChecker.js +114 -0
- package/src/health/healthCheckCache.js +12 -16
- package/src/health/healthCheckClient.js +49 -52
- package/src/health/healthCheckUtils.js +43 -12
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} url
|
|
3
|
+
* @returns {string} postgres | mysql
|
|
4
|
+
*/
|
|
5
|
+
export function getDatabaseType(url: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} url
|
|
8
|
+
* @param {string} [type]
|
|
9
|
+
* @returns {{ host: string, port: number, user: string, password: string, database: string } | null}
|
|
10
|
+
*/
|
|
11
|
+
export function parseConnectionUrl(url: string, type?: string | undefined): {
|
|
12
|
+
host: string;
|
|
13
|
+
port: number;
|
|
14
|
+
user: string;
|
|
15
|
+
password: string;
|
|
16
|
+
database: string;
|
|
17
|
+
} | null;
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} env
|
|
20
|
+
* @param {string} url
|
|
21
|
+
* @param {number} connectionTimeoutMs
|
|
22
|
+
* @returns {{ pool: any, type: string }}
|
|
23
|
+
*/
|
|
24
|
+
export function createDatabasePool(env: string, url: string, connectionTimeoutMs: number): {
|
|
25
|
+
pool: any;
|
|
26
|
+
type: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* @param {any} pool
|
|
30
|
+
* @param {string} type
|
|
31
|
+
* @returns {Promise<void>}
|
|
32
|
+
*/
|
|
33
|
+
export function runHealthCheck(pool: any, type: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* @param {any} pool
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
export function closePool(pool: any): Promise<void>;
|
|
39
|
+
export const DB_TYPE_POSTGRES: "postgres";
|
|
40
|
+
export const DB_TYPE_MYSQL: "mysql";
|
|
41
|
+
//# sourceMappingURL=databaseChecker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"databaseChecker.d.ts","sourceRoot":"","sources":["../../src/health/databaseChecker.js"],"names":[],"mappings":"AAKA;;;GAGG;AACH,qCAHW,MAAM,GACJ,MAAM,CAYlB;AAED;;;;GAIG;AACH,wCAJW,MAAM,8BAEJ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiBnG;AAED;;;;;GAKG;AACH,wCALW,MAAM,OACN,MAAM,uBACN,MAAM;UACI,GAAG;UAAQ,MAAM;EA+BrC;AAED;;;;GAIG;AACH,qCAJW,GAAG,QACH,MAAM,GACJ,QAAQ,IAAI,CAAC,CAQzB;AAED;;;GAGG;AACH,gCAHW,GAAG,GACD,QAAQ,IAAI,CAAC,CAQzB;AArGD,0CAAmC;AACnC,oCAA6B"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
Pool
|
|
5
|
+
} = require('pg');
|
|
6
|
+
const DB_TYPE_POSTGRES = 'postgres';
|
|
7
|
+
const DB_TYPE_MYSQL = 'mysql';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} url
|
|
11
|
+
* @returns {string} postgres | mysql
|
|
12
|
+
*/
|
|
13
|
+
function getDatabaseType(url) {
|
|
14
|
+
if (!url || typeof url !== 'string') return DB_TYPE_POSTGRES;
|
|
15
|
+
const lower = url.toLowerCase();
|
|
16
|
+
if (lower.startsWith('mysql://') || lower.startsWith('mysql2://')) {
|
|
17
|
+
return DB_TYPE_MYSQL;
|
|
18
|
+
}
|
|
19
|
+
if (lower.startsWith('mariadb://')) {
|
|
20
|
+
return DB_TYPE_MYSQL;
|
|
21
|
+
}
|
|
22
|
+
return DB_TYPE_POSTGRES;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} url
|
|
27
|
+
* @param {string} [type]
|
|
28
|
+
* @returns {{ host: string, port: number, user: string, password: string, database: string } | null}
|
|
29
|
+
*/
|
|
30
|
+
function parseConnectionUrl(url, type) {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = new URL(url);
|
|
33
|
+
const defaultPort = type === DB_TYPE_MYSQL ? 3306 : 5432;
|
|
34
|
+
const defaultDb = type === DB_TYPE_MYSQL ? 'mysql' : 'postgres';
|
|
35
|
+
return {
|
|
36
|
+
host: parsed.hostname || 'localhost',
|
|
37
|
+
port: parseInt(parsed.port || String(defaultPort), 10),
|
|
38
|
+
user: parsed.username || '',
|
|
39
|
+
password: parsed.password || '',
|
|
40
|
+
database: (parsed.pathname || '/').replace(/^\//, '') || defaultDb
|
|
41
|
+
};
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} env
|
|
49
|
+
* @param {string} url
|
|
50
|
+
* @param {number} connectionTimeoutMs
|
|
51
|
+
* @returns {{ pool: any, type: string }}
|
|
52
|
+
*/
|
|
53
|
+
function createDatabasePool(env, url, connectionTimeoutMs) {
|
|
54
|
+
const type = getDatabaseType(url);
|
|
55
|
+
if (type === DB_TYPE_MYSQL) {
|
|
56
|
+
const mysql = require('mysql2/promise');
|
|
57
|
+
const config = parseConnectionUrl(url, DB_TYPE_MYSQL);
|
|
58
|
+
if (!config) {
|
|
59
|
+
throw new Error(`Invalid MySQL URL for ${env}`);
|
|
60
|
+
}
|
|
61
|
+
const pool = mysql.createPool({
|
|
62
|
+
host: config.host,
|
|
63
|
+
port: config.port,
|
|
64
|
+
user: config.user,
|
|
65
|
+
password: config.password,
|
|
66
|
+
database: config.database,
|
|
67
|
+
connectionLimit: 1,
|
|
68
|
+
connectTimeout: connectionTimeoutMs,
|
|
69
|
+
waitForConnections: false
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
pool,
|
|
73
|
+
type: DB_TYPE_MYSQL
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const pool = new Pool({
|
|
77
|
+
connectionString: url,
|
|
78
|
+
max: 1,
|
|
79
|
+
idleTimeoutMillis: 30000,
|
|
80
|
+
connectionTimeoutMillis: connectionTimeoutMs
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
pool,
|
|
84
|
+
type: DB_TYPE_POSTGRES
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {any} pool
|
|
90
|
+
* @param {string} type
|
|
91
|
+
* @returns {Promise<void>}
|
|
92
|
+
*/
|
|
93
|
+
async function runHealthCheck(pool, type) {
|
|
94
|
+
if (type === DB_TYPE_MYSQL) {
|
|
95
|
+
const [rows] = await pool.execute('SELECT 1');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await pool.query('SELECT 1');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {any} pool
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
*/
|
|
105
|
+
async function closePool(pool) {
|
|
106
|
+
try {
|
|
107
|
+
await pool.end();
|
|
108
|
+
} catch {
|
|
109
|
+
// ignore
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
module.exports = {
|
|
113
|
+
getDatabaseType,
|
|
114
|
+
parseConnectionUrl,
|
|
115
|
+
createDatabasePool,
|
|
116
|
+
runHealthCheck,
|
|
117
|
+
closePool,
|
|
118
|
+
DB_TYPE_POSTGRES,
|
|
119
|
+
DB_TYPE_MYSQL
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=databaseChecker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"databaseChecker.js","names":["Pool","require","DB_TYPE_POSTGRES","DB_TYPE_MYSQL","getDatabaseType","url","lower","toLowerCase","startsWith","parseConnectionUrl","type","parsed","URL","defaultPort","defaultDb","host","hostname","port","parseInt","String","user","username","password","database","pathname","replace","createDatabasePool","env","connectionTimeoutMs","mysql","config","Error","pool","createPool","connectionLimit","connectTimeout","waitForConnections","connectionString","max","idleTimeoutMillis","connectionTimeoutMillis","runHealthCheck","rows","execute","query","closePool","end","module","exports"],"sources":["../../src/health/databaseChecker.js"],"sourcesContent":["const { Pool } = require('pg')\n\nconst DB_TYPE_POSTGRES = 'postgres'\nconst DB_TYPE_MYSQL = 'mysql'\n\n/**\n * @param {string} url\n * @returns {string} postgres | mysql\n */\nfunction getDatabaseType(url) {\n if (!url || typeof url !== 'string') return DB_TYPE_POSTGRES\n const lower = url.toLowerCase()\n if (lower.startsWith('mysql://') || lower.startsWith('mysql2://')) {\n return DB_TYPE_MYSQL\n }\n if (lower.startsWith('mariadb://')) {\n return DB_TYPE_MYSQL\n }\n return DB_TYPE_POSTGRES\n}\n\n/**\n * @param {string} url\n * @param {string} [type]\n * @returns {{ host: string, port: number, user: string, password: string, database: string } | null}\n */\nfunction parseConnectionUrl(url, type) {\n try {\n const parsed = new URL(url)\n const defaultPort = type === DB_TYPE_MYSQL ? 3306 : 5432\n const defaultDb = type === DB_TYPE_MYSQL ? 'mysql' : 'postgres'\n return {\n host: parsed.hostname || 'localhost',\n port: parseInt(parsed.port || String(defaultPort), 10),\n user: parsed.username || '',\n password: parsed.password || '',\n database: (parsed.pathname || '/').replace(/^\\//, '') || defaultDb,\n }\n } catch {\n return null\n }\n}\n\n/**\n * @param {string} env\n * @param {string} url\n * @param {number} connectionTimeoutMs\n * @returns {{ pool: any, type: string }}\n */\nfunction createDatabasePool(env, url, connectionTimeoutMs) {\n const type = getDatabaseType(url)\n\n if (type === DB_TYPE_MYSQL) {\n const mysql = require('mysql2/promise')\n const config = parseConnectionUrl(url, DB_TYPE_MYSQL)\n if (!config) {\n throw new Error(`Invalid MySQL URL for ${env}`)\n }\n const pool = mysql.createPool({\n host: config.host,\n port: config.port,\n user: config.user,\n password: config.password,\n database: config.database,\n connectionLimit: 1,\n connectTimeout: connectionTimeoutMs,\n waitForConnections: false,\n })\n return { pool, type: DB_TYPE_MYSQL }\n }\n\n const pool = new Pool({\n connectionString: url,\n max: 1,\n idleTimeoutMillis: 30000,\n connectionTimeoutMillis: connectionTimeoutMs,\n })\n return { pool, type: DB_TYPE_POSTGRES }\n}\n\n/**\n * @param {any} pool\n * @param {string} type\n * @returns {Promise<void>}\n */\nasync function runHealthCheck(pool, type) {\n if (type === DB_TYPE_MYSQL) {\n const [rows] = await pool.execute('SELECT 1')\n return\n }\n await pool.query('SELECT 1')\n}\n\n/**\n * @param {any} pool\n * @returns {Promise<void>}\n */\nasync function closePool(pool) {\n try {\n await pool.end()\n } catch {\n // ignore\n }\n}\n\nmodule.exports = {\n getDatabaseType,\n parseConnectionUrl,\n createDatabasePool,\n runHealthCheck,\n closePool,\n DB_TYPE_POSTGRES,\n DB_TYPE_MYSQL,\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -6,18 +6,20 @@
|
|
|
6
6
|
export class HealthCheckCache {
|
|
7
7
|
/**
|
|
8
8
|
* @param {Object} options
|
|
9
|
-
* @param {any} [options.redisClient]
|
|
10
|
-
* @param {string} [options.
|
|
11
|
-
* @param {
|
|
9
|
+
* @param {any} [options.redisClient]
|
|
10
|
+
* @param {string} [options.cacheKey] - Redis key (e.g. 'health:database:status')
|
|
11
|
+
* @param {string} [options.appName] - Used when cacheKey not set: healthcheck:${appName}
|
|
12
|
+
* @param {number} [options.staleThresholdMs]
|
|
12
13
|
*/
|
|
13
14
|
constructor(options?: {
|
|
14
15
|
redisClient?: any;
|
|
16
|
+
cacheKey?: string | undefined;
|
|
15
17
|
appName?: string | undefined;
|
|
16
|
-
|
|
18
|
+
staleThresholdMs?: number | undefined;
|
|
17
19
|
});
|
|
18
20
|
redisClient: any;
|
|
19
21
|
appName: string;
|
|
20
|
-
|
|
22
|
+
staleThresholdMs: number;
|
|
21
23
|
cacheKey: string;
|
|
22
24
|
/** In-memory fallback cache */
|
|
23
25
|
_memoryCache: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"healthCheckCache.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckCache.js"],"names":[],"mappings":"AAOA;;;;GAIG;AACH;IACE
|
|
1
|
+
{"version":3,"file":"healthCheckCache.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckCache.js"],"names":[],"mappings":"AAOA;;;;GAIG;AACH;IACE;;;;;;OAMG;IACH;QALyB,WAAW,GAAzB,GAAG;QACc,QAAQ;QACR,OAAO;QACP,gBAAgB;OAuB3C;IApBC,iBAA8C;IAC9C,gBACgE;IAChE,yBAA2D;IAC3D,iBACmD;IAEnD,+BAA+B;IAC/B,kBAAwB;IACxB,2BAAiC;IAG/B,qCAA4D;IAC5D,yBAA2B;IAS/B;;;;OAIG;IACH,6BAgCC;IAED;;;;;OAKG;IACH,OAHa,QAAQ,MAAM,GAAG,IAAI,CAAC,CAgDlC;IAED;;;;OAIG;IACH,YAHW,MAAM,GACJ,QAAQ,IAAI,CAAC,CAqCzB;IAED;;;OAGG;IACH,SAFa,QAAQ,IAAI,CAAC,CAyBzB;IAED;;;OAGG;IACH,oBAFa,OAAO,CAInB;CACF"}
|
|
@@ -15,15 +15,16 @@ const {
|
|
|
15
15
|
class HealthCheckCache {
|
|
16
16
|
/**
|
|
17
17
|
* @param {Object} options
|
|
18
|
-
* @param {any} [options.redisClient]
|
|
19
|
-
* @param {string} [options.
|
|
20
|
-
* @param {
|
|
18
|
+
* @param {any} [options.redisClient]
|
|
19
|
+
* @param {string} [options.cacheKey] - Redis key (e.g. 'health:database:status')
|
|
20
|
+
* @param {string} [options.appName] - Used when cacheKey not set: healthcheck:${appName}
|
|
21
|
+
* @param {number} [options.staleThresholdMs]
|
|
21
22
|
*/
|
|
22
23
|
constructor(options = {}) {
|
|
23
24
|
this.redisClient = options.redisClient || null;
|
|
24
25
|
this.appName = options.appName || process.env.BUILD_APP_NAME || 'unknown-app';
|
|
25
|
-
this.
|
|
26
|
-
this.cacheKey = `healthcheck:${this.appName}`;
|
|
26
|
+
this.staleThresholdMs = options.staleThresholdMs ?? 180_000;
|
|
27
|
+
this.cacheKey = options.cacheKey || `healthcheck:${this.appName}`;
|
|
27
28
|
|
|
28
29
|
/** In-memory fallback cache */
|
|
29
30
|
this._memoryCache = null;
|
|
@@ -92,12 +93,9 @@ class HealthCheckCache {
|
|
|
92
93
|
try {
|
|
93
94
|
const cached = JSON.parse(cachedStr);
|
|
94
95
|
if (cached.result && cached.timestamp) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this._memoryCacheTimestamp = cached.timestamp;
|
|
99
|
-
return cached.result;
|
|
100
|
-
}
|
|
96
|
+
this._memoryCache = cached.result;
|
|
97
|
+
this._memoryCacheTimestamp = cached.timestamp;
|
|
98
|
+
return cached.result;
|
|
101
99
|
}
|
|
102
100
|
} catch (parseErr) {
|
|
103
101
|
console.warn(`[HealthCheckCache] Failed to parse Redis cache:`, parseErr.message);
|
|
@@ -110,10 +108,7 @@ class HealthCheckCache {
|
|
|
110
108
|
}
|
|
111
109
|
}
|
|
112
110
|
if (this._memoryCache && this._memoryCacheTimestamp) {
|
|
113
|
-
|
|
114
|
-
if (age < this.cacheTtlMs) {
|
|
115
|
-
return this._memoryCache;
|
|
116
|
-
}
|
|
111
|
+
return this._memoryCache;
|
|
117
112
|
}
|
|
118
113
|
return null;
|
|
119
114
|
}
|
|
@@ -133,7 +128,7 @@ class HealthCheckCache {
|
|
|
133
128
|
if (await this._checkRedisAvailable()) {
|
|
134
129
|
try {
|
|
135
130
|
const cacheStr = JSON.stringify(cacheData);
|
|
136
|
-
const ttlSeconds = Math.ceil(this.
|
|
131
|
+
const ttlSeconds = Math.ceil(this.staleThresholdMs / 1000) + 60;
|
|
137
132
|
if (this._redisClientType === REDIS_V3) {
|
|
138
133
|
await new Promise((resolve, reject) => {
|
|
139
134
|
this.redisClient.setex(this.cacheKey, ttlSeconds, cacheStr, err => {
|
|
@@ -1 +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 =\n options.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.cacheTtlMs = options.cacheTtlMs ?? 60_000\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 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 (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 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 return null\n } catch (redisErr) {\n this._redisAvailable = false\n throw new Error(`Redis cache read failed: ${redisErr.message}`)\n }\n }\n\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 this._memoryCache = result\n this._memoryCacheTimestamp = cacheData.timestamp\n\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(this.cacheKey, ttlSeconds, cacheStr, 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.setex(this.cacheKey, ttlSeconds, cacheStr)\n }\n } catch (redisErr) {\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(`[HealthCheckCache] Redis clear failed:`, redisErr.message)\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"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"healthCheckCache.js","names":["getRedisClientType","REDIS_V4","IOREDIS","REDIS_V3","require","HealthCheckCache","constructor","options","redisClient","appName","process","env","BUILD_APP_NAME","staleThresholdMs","cacheKey","_memoryCache","_memoryCacheTimestamp","_redisClientType","_redisAvailable","console","warn","_checkRedisAvailable","pong","Promise","resolve","reject","ping","err","result","message","get","cachedStr","cached","JSON","parse","timestamp","parseErr","redisErr","Error","set","cacheData","Date","now","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]\n * @param {string} [options.cacheKey] - Redis key (e.g. 'health:database:status')\n * @param {string} [options.appName] - Used when cacheKey not set: healthcheck:${appName}\n * @param {number} [options.staleThresholdMs]\n */\n constructor(options = {}) {\n this.redisClient = options.redisClient || null\n this.appName =\n options.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.staleThresholdMs = options.staleThresholdMs ?? 180_000\n this.cacheKey =\n options.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 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 (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 this._memoryCache = cached.result\n this._memoryCacheTimestamp = cached.timestamp\n return cached.result\n }\n } catch (parseErr) {\n console.warn(\n `[HealthCheckCache] Failed to parse Redis cache:`,\n parseErr.message\n )\n }\n }\n return null\n } catch (redisErr) {\n this._redisAvailable = false\n throw new Error(`Redis cache read failed: ${redisErr.message}`)\n }\n }\n\n if (this._memoryCache && this._memoryCacheTimestamp) {\n return this._memoryCache\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 this._memoryCache = result\n this._memoryCacheTimestamp = cacheData.timestamp\n\n if (await this._checkRedisAvailable()) {\n try {\n const cacheStr = JSON.stringify(cacheData)\n const ttlSeconds = Math.ceil(this.staleThresholdMs / 1000) + 60\n\n if (this._redisClientType === REDIS_V3) {\n await new Promise((resolve, reject) => {\n this.redisClient.setex(this.cacheKey, ttlSeconds, cacheStr, 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.setex(this.cacheKey, ttlSeconds, cacheStr)\n }\n } catch (redisErr) {\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(`[HealthCheckCache] Redis clear failed:`, redisErr.message)\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"],"mappings":";;AAAA,MAAM;EACJA,kBAAkB;EAClBC,QAAQ;EACRC,OAAO;EACPC;AACF,CAAC,GAAGC,OAAO,CAAC,eAAe,CAAC;;AAE5B;AACA;AACA;AACA;AACA;AACA,MAAMC,gBAAgB,CAAC;EACrB;AACF;AACA;AACA;AACA;AACA;AACA;EACEC,WAAWA,CAACC,OAAO,GAAG,CAAC,CAAC,EAAE;IACxB,IAAI,CAACC,WAAW,GAAGD,OAAO,CAACC,WAAW,IAAI,IAAI;IAC9C,IAAI,CAACC,OAAO,GACVF,OAAO,CAACE,OAAO,IAAIC,OAAO,CAACC,GAAG,CAACC,cAAc,IAAI,aAAa;IAChE,IAAI,CAACC,gBAAgB,GAAGN,OAAO,CAACM,gBAAgB,IAAI,OAAO;IAC3D,IAAI,CAACC,QAAQ,GACXP,OAAO,CAACO,QAAQ,IAAI,eAAe,IAAI,CAACL,OAAO,EAAE;;IAEnD;IACA,IAAI,CAACM,YAAY,GAAG,IAAI;IACxB,IAAI,CAACC,qBAAqB,GAAG,IAAI;IAEjC,IAAI,IAAI,CAACR,WAAW,EAAE;MACpB,IAAI,CAACS,gBAAgB,GAAGjB,kBAAkB,CAAC,IAAI,CAACQ,WAAW,CAAC;MAC5D,IAAI,CAACU,eAAe,GAAG,IAAI;IAC7B,CAAC,MAAM;MACL,IAAI,CAACA,eAAe,GAAG,KAAK;MAC5BC,OAAO,CAACC,IAAI,CACV,+CAA+C,IAAI,CAACX,OAAO,4DAC7D,CAAC;IACH;EACF;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAMY,oBAAoBA,CAAA,EAAG;IAC3B,IAAI,CAAC,IAAI,CAACb,WAAW,IAAI,CAAC,IAAI,CAACU,eAAe,EAAE;MAC9C,OAAO,KAAK;IACd;IAEA,IAAI;MACF,IAAII,IAAI;MACR,IAAI,IAAI,CAACL,gBAAgB,KAAKd,QAAQ,EAAE;QACtCmB,IAAI,GAAG,MAAM,IAAIC,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;UAC5C,IAAI,CAACjB,WAAW,CAACkB,IAAI,CAAC,CAACC,GAAG,EAAEC,MAAM,KAAK;YACrC,IAAID,GAAG,EAAEF,MAAM,CAACE,GAAG,CAAC,MACfH,OAAO,CAACI,MAAM,CAAC;UACtB,CAAC,CAAC;QACJ,CAAC,CAAC;MACJ,CAAC,MAAM,IACL,IAAI,CAACX,gBAAgB,KAAKhB,QAAQ,IAClC,IAAI,CAACgB,gBAAgB,KAAKf,OAAO,EACjC;QACAoB,IAAI,GAAG,MAAM,IAAI,CAACd,WAAW,CAACkB,IAAI,CAAC,CAAC;MACtC,CAAC,MAAM;QACL,OAAO,KAAK;MACd;MACA,OAAOJ,IAAI,KAAK,MAAM;IACxB,CAAC,CAAC,OAAOK,GAAG,EAAE;MACZ,IAAI,IAAI,CAACT,eAAe,EAAE;QACxBC,OAAO,CAACC,IAAI,CACV,gDAAgDO,GAAG,CAACE,OAAO,EAC7D,CAAC;QACD,IAAI,CAACX,eAAe,GAAG,KAAK;MAC9B;MACA,OAAO,KAAK;IACd;EACF;;EAEA;AACF;AACA;AACA;AACA;AACA;EACE,MAAMY,GAAGA,CAAA,EAAG;IACV,IAAI,IAAI,CAACtB,WAAW,EAAE;MACpB,IAAI;QACF,IAAIuB,SAAS;QACb,IAAI,IAAI,CAACd,gBAAgB,KAAKd,QAAQ,EAAE;UACtC4B,SAAS,GAAG,MAAM,IAAIR,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;YACjD,IAAI,CAACjB,WAAW,CAACsB,GAAG,CAAC,IAAI,CAAChB,QAAQ,EAAE,CAACa,GAAG,EAAEC,MAAM,KAAK;cACnD,IAAID,GAAG,EAAEF,MAAM,CAACE,GAAG,CAAC,MACfH,OAAO,CAACI,MAAM,CAAC;YACtB,CAAC,CAAC;UACJ,CAAC,CAAC;QACJ,CAAC,MAAM,IACL,IAAI,CAACX,gBAAgB,KAAKhB,QAAQ,IAClC,IAAI,CAACgB,gBAAgB,KAAKf,OAAO,EACjC;UACA6B,SAAS,GAAG,MAAM,IAAI,CAACvB,WAAW,CAACsB,GAAG,CAAC,IAAI,CAAChB,QAAQ,CAAC;QACvD;QAEA,IAAIiB,SAAS,EAAE;UACb,IAAI;YACF,MAAMC,MAAM,GAAGC,IAAI,CAACC,KAAK,CAACH,SAAS,CAAC;YACpC,IAAIC,MAAM,CAACJ,MAAM,IAAII,MAAM,CAACG,SAAS,EAAE;cACrC,IAAI,CAACpB,YAAY,GAAGiB,MAAM,CAACJ,MAAM;cACjC,IAAI,CAACZ,qBAAqB,GAAGgB,MAAM,CAACG,SAAS;cAC7C,OAAOH,MAAM,CAACJ,MAAM;YACtB;UACF,CAAC,CAAC,OAAOQ,QAAQ,EAAE;YACjBjB,OAAO,CAACC,IAAI,CACV,iDAAiD,EACjDgB,QAAQ,CAACP,OACX,CAAC;UACH;QACF;QACA,OAAO,IAAI;MACb,CAAC,CAAC,OAAOQ,QAAQ,EAAE;QACjB,IAAI,CAACnB,eAAe,GAAG,KAAK;QAC5B,MAAM,IAAIoB,KAAK,CAAC,4BAA4BD,QAAQ,CAACR,OAAO,EAAE,CAAC;MACjE;IACF;IAEA,IAAI,IAAI,CAACd,YAAY,IAAI,IAAI,CAACC,qBAAqB,EAAE;MACnD,OAAO,IAAI,CAACD,YAAY;IAC1B;IAEA,OAAO,IAAI;EACb;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAMwB,GAAGA,CAACX,MAAM,EAAE;IAChB,MAAMY,SAAS,GAAG;MAChBZ,MAAM;MACNO,SAAS,EAAEM,IAAI,CAACC,GAAG,CAAC;IACtB,CAAC;IAED,IAAI,CAAC3B,YAAY,GAAGa,MAAM;IAC1B,IAAI,CAACZ,qBAAqB,GAAGwB,SAAS,CAACL,SAAS;IAEhD,IAAI,MAAM,IAAI,CAACd,oBAAoB,CAAC,CAAC,EAAE;MACrC,IAAI;QACF,MAAMsB,QAAQ,GAAGV,IAAI,CAACW,SAAS,CAACJ,SAAS,CAAC;QAC1C,MAAMK,UAAU,GAAGC,IAAI,CAACC,IAAI,CAAC,IAAI,CAAClC,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE;QAE/D,IAAI,IAAI,CAACI,gBAAgB,KAAKd,QAAQ,EAAE;UACtC,MAAM,IAAIoB,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;YACrC,IAAI,CAACjB,WAAW,CAACwC,KAAK,CAAC,IAAI,CAAClC,QAAQ,EAAE+B,UAAU,EAAEF,QAAQ,EAAEhB,GAAG,IAAI;cACjE,IAAIA,GAAG,EAAEF,MAAM,CAACE,GAAG,CAAC,MACfH,OAAO,CAAC,CAAC;YAChB,CAAC,CAAC;UACJ,CAAC,CAAC;QACJ,CAAC,MAAM,IACL,IAAI,CAACP,gBAAgB,KAAKhB,QAAQ,IAClC,IAAI,CAACgB,gBAAgB,KAAKf,OAAO,EACjC;UACA,MAAM,IAAI,CAACM,WAAW,CAACwC,KAAK,CAAC,IAAI,CAAClC,QAAQ,EAAE+B,UAAU,EAAEF,QAAQ,CAAC;QACnE;MACF,CAAC,CAAC,OAAON,QAAQ,EAAE;QACjBlB,OAAO,CAACC,IAAI,CACV,kEAAkE,EAClEiB,QAAQ,CAACR,OACX,CAAC;QACD,IAAI,CAACX,eAAe,GAAG,KAAK;MAC9B;IACF;EACF;;EAEA;AACF;AACA;AACA;EACE,MAAM+B,KAAKA,CAAA,EAAG;IACZ,IAAI,CAAClC,YAAY,GAAG,IAAI;IACxB,IAAI,CAACC,qBAAqB,GAAG,IAAI;IAEjC,IAAI,MAAM,IAAI,CAACK,oBAAoB,CAAC,CAAC,EAAE;MACrC,IAAI;QACF,IAAI,IAAI,CAACJ,gBAAgB,KAAKd,QAAQ,EAAE;UACtC,MAAM,IAAIoB,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;YACrC,IAAI,CAACjB,WAAW,CAAC0C,GAAG,CAAC,IAAI,CAACpC,QAAQ,EAAEa,GAAG,IAAI;cACzC,IAAIA,GAAG,EAAEF,MAAM,CAACE,GAAG,CAAC,MACfH,OAAO,CAAC,CAAC;YAChB,CAAC,CAAC;UACJ,CAAC,CAAC;QACJ,CAAC,MAAM,IACL,IAAI,CAACP,gBAAgB,KAAKhB,QAAQ,IAClC,IAAI,CAACgB,gBAAgB,KAAKf,OAAO,EACjC;UACA,MAAM,IAAI,CAACM,WAAW,CAAC0C,GAAG,CAAC,IAAI,CAACpC,QAAQ,CAAC;QAC3C;MACF,CAAC,CAAC,OAAOuB,QAAQ,EAAE;QACjBlB,OAAO,CAACC,IAAI,CAAC,wCAAwC,EAAEiB,QAAQ,CAACR,OAAO,CAAC;MAC1E;IACF;EACF;;EAEA;AACF;AACA;AACA;EACEsB,gBAAgBA,CAAA,EAAG;IACjB,OAAO,IAAI,CAACjC,eAAe,IAAI,IAAI,CAACV,WAAW,KAAK,IAAI;EAC1D;AACF;AAEA4C,MAAM,CAACC,OAAO,GAAG;EAAEhD;AAAiB,CAAC","ignoreList":[]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export type HealthResource = {
|
|
2
|
-
|
|
2
|
+
env: string;
|
|
3
3
|
url?: string;
|
|
4
4
|
} | {
|
|
5
|
-
|
|
6
|
-
client
|
|
5
|
+
env: string;
|
|
6
|
+
client?: any;
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
|
-
* @typedef {{
|
|
9
|
+
* @typedef {{ env: string, url?: string } | { env: string, client?: any }} HealthResource
|
|
10
10
|
*/
|
|
11
11
|
export class HealthCheckClient {
|
|
12
12
|
/**
|
|
@@ -14,13 +14,15 @@ export class HealthCheckClient {
|
|
|
14
14
|
* @param {HealthResource[]} options.resources - Must include Redis resource with client
|
|
15
15
|
* @param {number} [options.cacheTtlMs]
|
|
16
16
|
* @param {Object} [options.config]
|
|
17
|
-
* @param {string} [options.appName]
|
|
17
|
+
* @param {string} [options.appName] - For cache key: healthcheck:${appName}
|
|
18
|
+
* @param {string} [options.cacheKey] - Redis key (overrides appName)
|
|
18
19
|
*/
|
|
19
20
|
constructor(options?: {
|
|
20
21
|
resources: HealthResource[];
|
|
21
22
|
cacheTtlMs?: number | undefined;
|
|
22
23
|
config?: Object | undefined;
|
|
23
24
|
appName?: string | undefined;
|
|
25
|
+
cacheKey?: string | undefined;
|
|
24
26
|
});
|
|
25
27
|
healthConfig: {
|
|
26
28
|
constructor?: Function | undefined;
|
|
@@ -61,13 +63,21 @@ export class HealthCheckClient {
|
|
|
61
63
|
timestamp: number;
|
|
62
64
|
} | null;
|
|
63
65
|
_refreshPromise: any;
|
|
64
|
-
|
|
66
|
+
/** @type {Map<string, { pool: any, type: string }>} */
|
|
67
|
+
_databasePools: Map<string, {
|
|
68
|
+
pool: any;
|
|
69
|
+
type: string;
|
|
70
|
+
}>;
|
|
65
71
|
/** @type {HealthResource[]} */
|
|
66
72
|
_resources: HealthResource[];
|
|
67
73
|
_redisClientType: string | undefined;
|
|
68
74
|
_cache: HealthCheckCache;
|
|
75
|
+
_getEnv(resource: any): any;
|
|
69
76
|
_getRedisClientForCache(): any;
|
|
70
|
-
_getPool(
|
|
77
|
+
_getPool(env: any, url: any): {
|
|
78
|
+
pool: any;
|
|
79
|
+
type: string;
|
|
80
|
+
} | undefined;
|
|
71
81
|
_isCacheValid(): boolean;
|
|
72
82
|
_checkDatabase(resource: any): Promise<{
|
|
73
83
|
status: string;
|
|
@@ -126,7 +136,6 @@ export class HealthCheckClient {
|
|
|
126
136
|
}>;
|
|
127
137
|
clearCache(): void;
|
|
128
138
|
healthHandler(): (req: any, res: any) => Promise<void>;
|
|
129
|
-
registerHealthEndpoint(app: any, path?: string): void;
|
|
130
139
|
cleanup(): Promise<void>;
|
|
131
140
|
}
|
|
132
141
|
/** @type {{ cacheTtlMs: number, checkIntervalMs: number, staleThresholdMs: number, checkTimeoutMs: number }} */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"healthCheckClient.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckClient.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"healthCheckClient.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckClient.js"],"names":[],"mappings":"6BAiEa;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAA;CAAE;AAD1E;;GAEG;AACH;IACE;;;;;;;OAOG;IACH;QANqC,SAAS,EAAnC,cAAc,EAAE;QACC,UAAU;QACV,MAAM;QACN,OAAO;QACP,QAAQ;OAgCnC;IA7BC;;;;;;;;;;;;MAAmE;IAInE,mBAA8C;IAC9C,gBACgE;IAEhE,mBAAmD;IAEnD;;;;;;;;;;;;;;;;;;;;;aAAyB;IACzB,qBAA2B;IAC3B,uDAAuD;IACvD;cAD+B,GAAG;cAAQ,MAAM;OACjB;IAE/B,+BAA+B;IAC/B,YADW,cAAc,EAAE,CACc;IAIvC,qCAAuD;IAGzD,yBAKE;IAGJ,4BAEC;IAED,+BAGC;IAED;cA5BiC,GAAG;cAAQ,MAAM;kBAsCjD;IAED,yBAGC;IAED;;;;;;OAiBC;IAED;;;;;;OA0BC;IAED;;;;;;;;;;;;;;;;;;OAiCC;IAED,kDAeC;IAED,mCA2BC;IAED,gCAiBC;IAED;;;;;;;;;;;;;;;;;;OAIC;IAED,mBAGC;IAED,uDAiCC;IAED,yBASC;CACF;AApUD,gHAAgH;AAChH,oCADW;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAM3G"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
createDatabasePool,
|
|
5
|
+
runHealthCheck,
|
|
6
|
+
closePool
|
|
7
|
+
} = require('./databaseChecker');
|
|
6
8
|
const {
|
|
7
9
|
getRedisClientType,
|
|
8
10
|
REDIS_V4,
|
|
@@ -57,7 +59,7 @@ function maskSensitiveData(text) {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
|
-
* @typedef {{
|
|
62
|
+
* @typedef {{ env: string, url?: string } | { env: string, client?: any }} HealthResource
|
|
61
63
|
*/
|
|
62
64
|
class HealthCheckClient {
|
|
63
65
|
/**
|
|
@@ -65,7 +67,8 @@ class HealthCheckClient {
|
|
|
65
67
|
* @param {HealthResource[]} options.resources - Must include Redis resource with client
|
|
66
68
|
* @param {number} [options.cacheTtlMs]
|
|
67
69
|
* @param {Object} [options.config]
|
|
68
|
-
* @param {string} [options.appName]
|
|
70
|
+
* @param {string} [options.appName] - For cache key: healthcheck:${appName}
|
|
71
|
+
* @param {string} [options.cacheKey] - Redis key (overrides appName)
|
|
69
72
|
*/
|
|
70
73
|
constructor(options = {}) {
|
|
71
74
|
this.healthConfig = {
|
|
@@ -80,6 +83,7 @@ class HealthCheckClient {
|
|
|
80
83
|
this.prefixLogs = `[${this.appName}] [HealthCheck]`;
|
|
81
84
|
this._cachedResult = null;
|
|
82
85
|
this._refreshPromise = null;
|
|
86
|
+
/** @type {Map<string, { pool: any, type: string }>} */
|
|
83
87
|
this._databasePools = new Map();
|
|
84
88
|
|
|
85
89
|
/** @type {HealthResource[]} */
|
|
@@ -90,43 +94,50 @@ class HealthCheckClient {
|
|
|
90
94
|
}
|
|
91
95
|
this._cache = new HealthCheckCache({
|
|
92
96
|
redisClient: redisClient || null,
|
|
97
|
+
cacheKey: options.cacheKey,
|
|
93
98
|
appName: this.appName,
|
|
94
|
-
|
|
99
|
+
staleThresholdMs: this.healthConfig.staleThresholdMs
|
|
95
100
|
});
|
|
96
101
|
}
|
|
102
|
+
_getEnv(resource) {
|
|
103
|
+
return resource.env ?? resource.name;
|
|
104
|
+
}
|
|
97
105
|
_getRedisClientForCache() {
|
|
98
106
|
const redisResource = this._resources.find(r => 'client' in r && r.client);
|
|
99
107
|
return redisResource?.client || null;
|
|
100
108
|
}
|
|
101
|
-
_getPool(
|
|
102
|
-
if (!this._databasePools.has(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
_getPool(env, url) {
|
|
110
|
+
if (!this._databasePools.has(env)) {
|
|
111
|
+
const {
|
|
112
|
+
pool,
|
|
113
|
+
type
|
|
114
|
+
} = createDatabasePool(env, url, this.healthConfig.checkTimeoutMs);
|
|
115
|
+
this._databasePools.set(env, {
|
|
116
|
+
pool,
|
|
117
|
+
type
|
|
118
|
+
});
|
|
109
119
|
}
|
|
110
|
-
return this._databasePools.get(
|
|
120
|
+
return this._databasePools.get(env);
|
|
111
121
|
}
|
|
112
122
|
_isCacheValid() {
|
|
113
123
|
if (!this._cachedResult) return false;
|
|
114
124
|
return Date.now() - this._cachedResult.timestamp < this.cacheTtlMs;
|
|
115
125
|
}
|
|
116
126
|
async _checkDatabase(resource) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
} = resource;
|
|
120
|
-
const url = 'url' in resource ? resource.url : process.env[name];
|
|
127
|
+
const env = this._getEnv(resource);
|
|
128
|
+
const url = 'url' in resource ? resource.url : process.env[env];
|
|
121
129
|
if (!url) {
|
|
122
130
|
return {
|
|
123
131
|
status: 'error',
|
|
124
|
-
error: `Env ${
|
|
132
|
+
error: `Env ${env} not set`
|
|
125
133
|
};
|
|
126
134
|
}
|
|
127
135
|
try {
|
|
128
|
-
const
|
|
129
|
-
|
|
136
|
+
const {
|
|
137
|
+
pool,
|
|
138
|
+
type
|
|
139
|
+
} = this._getPool(env, url);
|
|
140
|
+
await runHealthCheck(pool, type);
|
|
130
141
|
return {
|
|
131
142
|
status: 'ok'
|
|
132
143
|
};
|
|
@@ -176,13 +187,11 @@ class HealthCheckClient {
|
|
|
176
187
|
async _performHealthCheckInternal() {
|
|
177
188
|
const resources = {};
|
|
178
189
|
for (const resource of this._resources) {
|
|
179
|
-
const
|
|
180
|
-
name
|
|
181
|
-
} = resource;
|
|
190
|
+
const env = this._getEnv(resource);
|
|
182
191
|
if ('client' in resource && resource.client) {
|
|
183
|
-
resources[
|
|
192
|
+
resources[env] = await this._checkRedis(resource);
|
|
184
193
|
} else {
|
|
185
|
-
resources[
|
|
194
|
+
resources[env] = await this._checkDatabase(resource);
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
197
|
const sortedResources = Object.keys(resources).sort().reduce((acc, key) => {
|
|
@@ -210,6 +219,9 @@ class HealthCheckClient {
|
|
|
210
219
|
...result,
|
|
211
220
|
isStale,
|
|
212
221
|
status: isStale ? 'stale' : result.status,
|
|
222
|
+
...(isStale && {
|
|
223
|
+
error: 'Health check data is stale, health-check worker may not be running. Resource statuses are unknown.'
|
|
224
|
+
}),
|
|
213
225
|
...(cached && {
|
|
214
226
|
cached: true
|
|
215
227
|
})
|
|
@@ -244,19 +256,12 @@ class HealthCheckClient {
|
|
|
244
256
|
return null;
|
|
245
257
|
} catch (err) {
|
|
246
258
|
console.error(`${this.prefixLogs} Failed to read from cache:`, err);
|
|
247
|
-
const errorMessage = maskSensitiveData(err.message || 'Cache read failed');
|
|
248
|
-
const redisResource = this._resources.find(r => 'client' in r);
|
|
249
|
-
const redisName = redisResource?.name || 'REDIS_URL';
|
|
250
259
|
return {
|
|
251
260
|
status: 'error',
|
|
252
|
-
lastCheckAt:
|
|
253
|
-
resources: {
|
|
254
|
-
[redisName]: {
|
|
255
|
-
status: 'error',
|
|
256
|
-
error: errorMessage
|
|
257
|
-
}
|
|
258
|
-
},
|
|
261
|
+
lastCheckAt: null,
|
|
262
|
+
resources: {},
|
|
259
263
|
isStale: true,
|
|
264
|
+
error: 'Redis unavailable, unable to read health status of other resources',
|
|
260
265
|
config: this.healthConfig
|
|
261
266
|
};
|
|
262
267
|
}
|
|
@@ -275,50 +280,38 @@ class HealthCheckClient {
|
|
|
275
280
|
try {
|
|
276
281
|
const result = await this.getCachedResult();
|
|
277
282
|
if (!result) {
|
|
278
|
-
const redisResource = this._resources.find(r => 'client' in r);
|
|
279
|
-
const redisName = redisResource?.name || 'REDIS_URL';
|
|
280
283
|
res.status(503).json({
|
|
281
284
|
status: 'error',
|
|
282
285
|
lastCheckAt: null,
|
|
283
|
-
resources: {
|
|
284
|
-
[redisName]: {
|
|
285
|
-
status: 'error',
|
|
286
|
-
error: 'Health check cache not available. Worker may not be running.'
|
|
287
|
-
}
|
|
288
|
-
},
|
|
286
|
+
resources: {},
|
|
289
287
|
isStale: true,
|
|
288
|
+
error: 'No health check data yet, health-check worker may not be running',
|
|
290
289
|
config: this.healthConfig
|
|
291
290
|
});
|
|
292
291
|
return;
|
|
293
292
|
}
|
|
294
|
-
|
|
293
|
+
const statusCode = result.status === 'ok' ? 200 : 503;
|
|
294
|
+
res.status(statusCode).json(result);
|
|
295
295
|
} catch (err) {
|
|
296
296
|
console.error(`${this.prefixLogs} Health check failed:`, err);
|
|
297
|
-
const redisResource = this._resources.find(r => 'client' in r);
|
|
298
|
-
const redisName = redisResource?.name || 'REDIS_URL';
|
|
299
297
|
res.status(503).json({
|
|
300
298
|
status: 'error',
|
|
301
299
|
lastCheckAt: null,
|
|
302
|
-
resources: {
|
|
303
|
-
[redisName]: {
|
|
304
|
-
status: 'error',
|
|
305
|
-
error: maskSensitiveData(err.message)
|
|
306
|
-
}
|
|
307
|
-
},
|
|
300
|
+
resources: {},
|
|
308
301
|
isStale: true,
|
|
302
|
+
error: 'Redis unavailable, unable to read health status of other resources',
|
|
309
303
|
config: this.healthConfig
|
|
310
304
|
});
|
|
311
305
|
}
|
|
312
306
|
};
|
|
313
307
|
}
|
|
314
|
-
registerHealthEndpoint(app, path = '/health') {
|
|
315
|
-
app.get(path, this.healthHandler());
|
|
316
|
-
console.info(`${this.prefixLogs} Registered health endpoint at ${path}`);
|
|
317
|
-
}
|
|
318
308
|
async cleanup() {
|
|
319
|
-
for (const [,
|
|
309
|
+
for (const [, {
|
|
310
|
+
pool,
|
|
311
|
+
type
|
|
312
|
+
}] of this._databasePools) {
|
|
320
313
|
try {
|
|
321
|
-
await pool
|
|
314
|
+
await closePool(pool);
|
|
322
315
|
} catch (err) {
|
|
323
316
|
console.error(`${this.prefixLogs} Error closing database pool:`, err);
|
|
324
317
|
}
|