@adalo/metrics 0.1.124 → 0.1.126

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.
@@ -0,0 +1,242 @@
1
+ # Health Check Client
2
+
3
+ Provides health check endpoints for external monitoring services like BetterStack.
4
+ Uses a background worker to periodically check database connections and cache results in Redis,
5
+ preventing HTTP requests from triggering database queries.
6
+
7
+ ## How It Works
8
+
9
+ 1. **Worker Process**: Runs as a separate process (defined in Procfile)
10
+ - Performs health checks every minute (configurable)
11
+ - Checks database connections (and optionally Redis)
12
+ - Stores results in Redis cache with 60-second TTL
13
+ - Creates its own Redis connection with name `{appName}:healthcheck-worker`
14
+
15
+ 2. **Web Processes**: Handle HTTP requests
16
+ - Read health check results from Redis cache only
17
+ - Never trigger database queries
18
+ - Return cached data or error if Redis unavailable
19
+ - Use existing system Redis client
20
+
21
+ ## Environment Variables
22
+
23
+ | Variable | Description | Default |
24
+ |----------|-------------|---------|
25
+ | `BUILD_APP_NAME` | Application name for logging and Redis client naming | `unknown-app` |
26
+ | `HOSTNAME` | Dyno/instance ID for logging | `unknown-dyno` |
27
+ | `BUILD_DYNO_PROCESS_TYPE` | Process type for logging | `health-check-worker` |
28
+ | `HEALTH_LOG_VALUES` | Enable logging of health check refresh status (`true`/`false`) | `false` |
29
+ | `REDIS_URL` | Redis connection URL for cache | - |
30
+ | `DATABASE_URL` | Main PostgreSQL connection URL (for backend worker) | - |
31
+ | `META_DB_URL` | Main PostgreSQL connection URL (for database worker) | - |
32
+
33
+ ## Usage
34
+
35
+ ### Backend Service
36
+
37
+ **Endpoint Client** (`lib/healthCheck.ts`):
38
+ ```typescript
39
+ import { createHealthCheckEndpointClient } from '@adalo/metrics'
40
+ import { redisClient } from './redis'
41
+
42
+ export const healthCheckClient = createHealthCheckEndpointClient({
43
+ redisClient,
44
+ includeRedisCheck: true,
45
+ })
46
+ ```
47
+
48
+ **Worker** (`health-check-worker.ts`):
49
+ ```typescript
50
+ import { createHealthCheckWorker } from '@adalo/metrics'
51
+ import { createNamedRedisClient } from './lib/redis'
52
+
53
+ const healthCheckRedisClient = createNamedRedisClient('healthcheck-worker')
54
+
55
+ const runHealthCheckWorker = createHealthCheckWorker({
56
+ databaseUrl: process.env.DATABASE_URL,
57
+ databaseName: 'backend',
58
+ redisClient: healthCheckRedisClient,
59
+ includeRedisCheck: true,
60
+ })
61
+
62
+ runHealthCheckWorker().catch(err => {
63
+ console.error('[HealthCheckWorker] Fatal error:', err)
64
+ process.exit(1)
65
+ })
66
+ ```
67
+
68
+ **Procfile**:
69
+ ```
70
+ health-check-worker: node dist/health-check-worker.js
71
+ ```
72
+
73
+ ### Database Service
74
+
75
+ **Endpoint Client** (`lib/healthCheck.ts`):
76
+ ```typescript
77
+ import { createHealthCheckEndpointClient } from '@adalo/metrics'
78
+ import { redisClient } from './cache/client'
79
+
80
+ export const healthCheckClient = createHealthCheckEndpointClient({
81
+ redisClient,
82
+ })
83
+ ```
84
+
85
+ **Worker** (`workers/health-check-worker.ts`):
86
+ ```typescript
87
+ import { createHealthCheckWorker } from '@adalo/metrics'
88
+ import { getClusterDatabaseUrls } from '../utils/clusterUrls'
89
+ import { createNamedRedisClient } from '../lib/cache/client'
90
+
91
+ const healthCheckRedisClient = createNamedRedisClient('healthcheck-worker')
92
+
93
+ const runHealthCheckWorker = createHealthCheckWorker({
94
+ databaseUrl: process.env.META_DB_URL,
95
+ databaseName: 'meta_db',
96
+ additionalDatabaseUrls: getClusterDatabaseUrls(),
97
+ redisClient: healthCheckRedisClient,
98
+ })
99
+
100
+ runHealthCheckWorker().catch(err => {
101
+ console.error('[HealthCheckWorker] Fatal error:', err)
102
+ process.exit(1)
103
+ })
104
+ ```
105
+
106
+ **Procfile**:
107
+ ```
108
+ health-check-worker: node --enable-source-maps dist/workers/health-check-worker.js
109
+ ```
110
+
111
+ ## Endpoints
112
+
113
+ ### Default Endpoint
114
+
115
+ By default, `registerHealthEndpoint()` registers at `/health`:
116
+
117
+ ```typescript
118
+ healthCheckClient.registerHealthEndpoint(app)
119
+ // Registers: GET /health
120
+ ```
121
+
122
+ ### Custom Endpoints
123
+
124
+ You can also register custom endpoints that use the same cached data:
125
+
126
+ ```typescript
127
+ // Backend: /healthcheck (old endpoint, still works)
128
+ app.get('/healthcheck', healthCheckClient.healthHandler())
129
+
130
+ // Database: /hc (old endpoint, still works)
131
+ app.get('/hc', healthCheckClient.healthHandler())
132
+ ```
133
+
134
+ ## Response Format
135
+
136
+ ### Healthy Response
137
+ ```json
138
+ {
139
+ "status": "healthy",
140
+ "timestamp": "2026-01-23T08:27:57.926Z",
141
+ "checks": {
142
+ "database": {
143
+ "status": "healthy",
144
+ "latencyMs": 52
145
+ },
146
+ "redis": {
147
+ "status": "healthy",
148
+ "latencyMs": 1
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### Unhealthy Response (Redis Cache Unavailable)
155
+ ```json
156
+ {
157
+ "status": "unhealthy",
158
+ "timestamp": "2026-01-23T08:27:57.926Z",
159
+ "checks": {
160
+ "redis": {
161
+ "status": "unhealthy",
162
+ "error": "Redis cache read failed: connection refused"
163
+ }
164
+ },
165
+ "errors": ["Redis cache read failed: connection refused"]
166
+ }
167
+ ```
168
+
169
+ ### Unhealthy Response (Database Issue)
170
+ ```json
171
+ {
172
+ "status": "unhealthy",
173
+ "timestamp": "2026-01-23T08:27:57.926Z",
174
+ "checks": {
175
+ "database": {
176
+ "status": "unhealthy",
177
+ "error": "connection timeout",
178
+ "latencyMs": 5000
179
+ }
180
+ },
181
+ "errors": ["DB backend: connection timeout"]
182
+ }
183
+ ```
184
+
185
+ ## Status Codes
186
+
187
+ - `200` - healthy or degraded
188
+ - `503` - unhealthy (at least one component failed)
189
+
190
+ ## Configuration Options
191
+
192
+ ### Worker Options
193
+
194
+ ```typescript
195
+ createHealthCheckWorker({
196
+ databaseUrl: string, // Required: Main database URL
197
+ databaseName: string, // Required: Database name
198
+ redisClient: any, // Required: Redis client instance (created with createNamedRedisClient('healthcheck-worker'))
199
+ additionalDatabaseUrls?: object, // Optional: Additional clusters (database service)
200
+ includeRedisCheck?: boolean, // Optional: Include Redis in health check (default: false)
201
+ refreshIntervalMs?: number, // Optional: Refresh interval (default: 60000ms)
202
+ cacheTtlMs?: number, // Optional: Cache TTL (default: 60000ms)
203
+ appName?: string, // Optional: App name (defaults to BUILD_APP_NAME)
204
+ })
205
+ ```
206
+
207
+ ### Endpoint Client Options
208
+
209
+ ```typescript
210
+ createHealthCheckEndpointClient({
211
+ redisClient: any, // Required: Existing Redis client from system
212
+ includeRedisCheck?: boolean, // Optional: Include Redis in response (default: false)
213
+ cacheTtlMs?: number, // Optional: Cache TTL (default: 60000ms)
214
+ })
215
+ ```
216
+
217
+ ## Logging Format
218
+
219
+ Worker logs follow the same format as metrics:
220
+
221
+ ```
222
+ [health-check-worker] [app-name] [dyno-id] [HealthCheck] Starting health check worker...
223
+ [health-check-worker] [app-name] [dyno-id] [HealthCheck] Refresh interval: 60000ms
224
+ ```
225
+
226
+ When `HEALTH_LOG_VALUES=true`, additional logs are printed:
227
+
228
+ ```
229
+ [health-check-worker] [app-name] [dyno-id] [HealthCheck] Initial health check completed
230
+ [health-check-worker] [app-name] [dyno-id] [HealthCheck] Health check refreshed at 2026-01-23T08:27:57.926Z, status: healthy
231
+ ```
232
+
233
+ Error logs are always printed regardless of `HEALTH_LOG_VALUES` setting.
234
+
235
+ ## Benefits
236
+
237
+ - **No Concurrent DB Queries**: Only worker queries database, endpoints read from cache
238
+ - **Same Data Per Minute**: All requests get the same cached result within the TTL period
239
+ - **Redis Optional**: Works even if Redis is disabled (falls back to in-memory cache per process)
240
+ - **Graceful Degradation**: Returns proper error format if Redis unavailable
241
+ - **Multi-Process Support**: Shared cache across all web process instances via Redis
242
+
@@ -1 +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"}
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;OAsBrC;IAnBC,iBAA8C;IAC9C,gBACgE;IAChE,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,CAwCzB;IAED;;;OAGG;IACH,SAFa,QAAQ,IAAI,CAAC,CAyBzB;IAED;;;OAGG;IACH,oBAFa,OAAO,CAInB;CACF"}
@@ -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 = 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":[]}
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 * 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(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 // 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(`[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":[]}
@@ -252,7 +252,7 @@ export class HealthCheckClient {
252
252
  * Register health check endpoint on an Express app.
253
253
  *
254
254
  * @param {import('express').Application} app - Express application
255
- * @param {string} [path='/health-status'] - Path for the health endpoint
255
+ * @param {string} [path='/health'] - Path for the health endpoint
256
256
  */
257
257
  registerHealthEndpoint(app: any, path?: string | undefined): void;
258
258
  /**
@@ -1 +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"}
1
+ {"version":3,"file":"healthCheckClient.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckClient.js"],"names":[],"mappings":"2BAoEa,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,0BAkCC;IAED;;;;;;;;;;OAUG;IACH,uBAFmB,GAAG,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,CAwDjD;IAED;;;;;OAKG;IACH,kEAGC;IAED;;;OAGG;IACH,WAFa,QAAQ,IAAI,CAAC,CAczB;CACF"}
@@ -600,9 +600,9 @@ class HealthCheckClient {
600
600
  * Register health check endpoint on an Express app.
601
601
  *
602
602
  * @param {import('express').Application} app - Express application
603
- * @param {string} [path='/health-status'] - Path for the health endpoint
603
+ * @param {string} [path='/health'] - Path for the health endpoint
604
604
  */
605
- registerHealthEndpoint(app, path = '/health-status') {
605
+ registerHealthEndpoint(app, path = '/health') {
606
606
  app.get(path, this.healthHandler());
607
607
  console.info(`${this.prefixLogs} Registered health endpoint at ${path}`);
608
608
  }
@@ -1 +1 @@
1
- {"version":3,"file":"healthCheckClient.js","names":["Pool","require","getRedisClientType","REDIS_V4","IOREDIS","REDIS_V3","HealthCheckCache","HEALTH_CHECK_CACHE_TTL_MS","SENSITIVE_PATTERNS","pattern","replacement","maskSensitiveData","text","masked","replace","HealthCheckClient","constructor","options","redisClient","includeRedisCheck","cacheTtlMs","appName","process","env","BUILD_APP_NAME","prefixLogs","_cachedResult","_refreshPromise","_databasePools","Map","_mainDatabaseConfig","_clusterConfigs","_initDatabases","_redisClientType","_cache","mainUrl","databaseUrl","DATABASE_URL","mainName","databaseName","name","url","additionalUrls","additionalDatabaseUrls","Object","entries","push","_getPool","config","has","set","connectionString","max","idleTimeoutMillis","connectionTimeoutMillis","get","_isCacheValid","Date","now","timestamp","_checkSingleDatabase","start","pool","query","status","latencyMs","err","error","message","_checkAllDatabases","length","mainHealth","clusters","clusterResults","Promise","all","map","health","allStatuses","values","c","overallStatus","some","s","result","_checkRedis","pong","resolve","reject","ping","performHealthCheck","cached","_performHealthCheckInternal","then","catch","dbHealth","redisHealth","checks","database","redis","statuses","toISOString","getCachedResult","console","errorMessage","errors","refreshCache","clearCache","_getErrorMessages","db","dbName","errorMsg","healthHandler","req","res","json","statusCode","response","registerHealthEndpoint","app","path","info","cleanup","end","clear","module","exports"],"sources":["../../src/health/healthCheckClient.js"],"sourcesContent":["const { Pool } = require('pg')\nconst {\n getRedisClientType,\n REDIS_V4,\n IOREDIS,\n REDIS_V3,\n} = require('../redisUtils')\nconst { HealthCheckCache } = require('./healthCheckCache')\n\nconst HEALTH_CHECK_CACHE_TTL_MS = 60 * 1000\n\n/**\n * Patterns to detect and mask sensitive information in error messages\n */\nconst SENSITIVE_PATTERNS = [\n // Database connection strings: postgres://user:password@host:port/database\n {\n pattern: /(postgres(?:ql)?|mysql|mongodb|redis|amqp):\\/\\/([^:]+):([^@]+)@([^:/]+)(:\\d+)?\\/([^\\s?]+)/gi,\n replacement: '$1://***:***@***$5/***',\n },\n // Generic URLs with credentials: protocol://user:password@host\n {\n pattern: /(\\w+):\\/\\/([^:]+):([^@]+)@([^\\s/]+)/gi,\n replacement: '$1://***:***@***',\n },\n // Password fields in JSON or key=value format\n {\n pattern: /(password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?token)[\"\\s]*[:=][\"\\s]*([^\\s,}\"]+)/gi,\n replacement: '$1=***',\n },\n // Database/table/schema/role/user names in error messages: database \"name\", table \"name\", etc.\n {\n pattern: /(database|table|schema|role|user|relation|column|index)\\s*[\"']([^\"']+)[\"']/gi,\n replacement: '$1 \"***\"',\n },\n // IP addresses (to hide internal network structure)\n {\n pattern: /\\b(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(:\\d+)?\\b/g,\n replacement: '***$2',\n },\n // Host names that might reveal internal infrastructure\n {\n pattern: /\\b(host|hostname|server)[\"\\s]*[:=][\"\\s]*([^\\s,}\"]+)/gi,\n replacement: '$1=***',\n },\n]\n\n/**\n * Masks sensitive information in a string\n * @param {string} text - Text that might contain sensitive data\n * @returns {string} - Text with sensitive data masked\n */\nfunction maskSensitiveData(text) {\n if (!text || typeof text !== 'string') {\n return text\n }\n\n let masked = text\n for (const { pattern, replacement } of SENSITIVE_PATTERNS) {\n masked = masked.replace(pattern, replacement)\n }\n return masked\n}\n\n/**\n * @typedef {'healthy' | 'unhealthy' | 'degraded'} HealthStatus\n */\n\n/**\n * @typedef {Object} ComponentHealth\n * @property {HealthStatus} status - Component health status\n * @property {string} [error] - Error message if status is unhealthy\n * @property {string} [message] - Optional status message (deprecated, use error)\n * @property {number} [latencyMs] - Connection latency in milliseconds\n */\n\n/**\n * @typedef {Object} DatabaseClusterHealth\n * @property {HealthStatus} status - Overall databases status\n * @property {Object<string, ComponentHealth>} clusters - Individual cluster health\n */\n\n/**\n * @typedef {Object} HealthCheckResult\n * @property {HealthStatus} status - Overall health status\n * @property {string} timestamp - ISO timestamp of the check\n * @property {Object<string, ComponentHealth | DatabaseClusterHealth>} checks - Individual service health checks\n * @property {string[]} [errors] - Top-level error messages (not related to specific services)\n */\n\n/**\n * @typedef {Object} CachedHealthResult\n * @property {HealthCheckResult} result - The cached health check result\n * @property {number} timestamp - Unix timestamp when cached\n */\n\n/**\n * @typedef {Object} DatabaseConfig\n * @property {string} name - Database/cluster name\n * @property {string} url - Connection URL\n */\n\n/**\n * HealthCheckClient provides a health check middleware for external monitoring services\n * like BetterStack. It validates database and Redis connections with rate limiting\n * to prevent excessive load on backend services.\n *\n * Features:\n * - Multi-cluster DB validation (PostgreSQL)\n * - Redis connection validation (supports ioredis, node-redis v3/v4)\n * - Result caching (default: 60 seconds) to prevent overloading services\n * - Express middleware support\n * - BetterStack-compatible JSON response format\n */\nclass HealthCheckClient {\n /**\n * @param {Object} options\n * @param {string} [options.databaseUrl] - Main PostgreSQL connection URL\n * @param {string} [options.databaseName='main'] - Name for the main database\n * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional DB clusters (name -> URL)\n * @param {any} [options.redisClient] - Redis client instance (ioredis or node-redis) - used for cache\n * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in results (for backend)\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds (default: 60s)\n * @param {string} [options.appName] - Application name for logging\n */\n constructor(options = {}) {\n this.redisClient = options.redisClient || null\n this.includeRedisCheck = options.includeRedisCheck || false\n this.cacheTtlMs = options.cacheTtlMs ?? HEALTH_CHECK_CACHE_TTL_MS\n this.appName =\n options.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n\n this.prefixLogs = `[${this.appName}] [HealthCheck]`\n\n /** @type {CachedHealthResult | null} */\n this._cachedResult = null\n\n /** @type {Promise<HealthCheckResult> | null} */\n this._refreshPromise = null\n\n /** @type {Map<string, Pool>} */\n this._databasePools = new Map()\n\n /** @type {DatabaseConfig | null} */\n this._mainDatabaseConfig = null\n\n /** @type {DatabaseConfig[]} */\n this._clusterConfigs = []\n\n this._initDatabases(options)\n\n if (this.redisClient) {\n this._redisClientType = getRedisClientType(this.redisClient)\n }\n\n this._cache = new HealthCheckCache({\n redisClient: this.redisClient,\n appName: this.appName,\n cacheTtlMs: this.cacheTtlMs,\n })\n }\n\n /**\n * Initialize database configurations from options.\n * @param {Object} options - Constructor options\n * @private\n */\n _initDatabases(options) {\n const mainUrl = options.databaseUrl || process.env.DATABASE_URL || ''\n const mainName = options.databaseName || `${this.appName}_db`\n\n if (mainUrl) {\n this._mainDatabaseConfig = { name: mainName, url: mainUrl }\n }\n\n const additionalUrls = options.additionalDatabaseUrls || {}\n for (const [name, url] of Object.entries(additionalUrls)) {\n if (url) {\n this._clusterConfigs.push({ name, url })\n }\n }\n }\n\n /**\n * Get or create a database pool for a given config.\n * @param {DatabaseConfig} config - Database configuration\n * @returns {Pool}\n * @private\n */\n _getPool(config) {\n if (!this._databasePools.has(config.name)) {\n this._databasePools.set(\n config.name,\n new Pool({\n connectionString: config.url,\n max: 1,\n idleTimeoutMillis: 30000,\n connectionTimeoutMillis: 5000,\n })\n )\n }\n return this._databasePools.get(config.name)\n }\n\n /**\n * Checks if cached result is still valid based on TTL.\n * @returns {boolean}\n * @private\n */\n _isCacheValid() {\n if (!this._cachedResult) return false\n return Date.now() - this._cachedResult.timestamp < this.cacheTtlMs\n }\n\n /**\n * Tests a single database cluster connectivity.\n * @param {DatabaseConfig} config - Database configuration\n * @returns {Promise<ComponentHealth>}\n * @private\n */\n async _checkSingleDatabase(config) {\n const start = Date.now()\n\n try {\n const pool = this._getPool(config)\n await pool.query('SELECT 1')\n return {\n status: 'healthy',\n latencyMs: Date.now() - start,\n }\n } catch (err) {\n return {\n status: 'unhealthy',\n error: maskSensitiveData(err.message),\n latencyMs: Date.now() - start,\n }\n }\n }\n\n /**\n * Tests all PostgreSQL databases (main + clusters) in parallel.\n * @returns {Promise<Object | null>} Database health with optional clusters\n * @private\n */\n async _checkAllDatabases() {\n if (!this._mainDatabaseConfig && this._clusterConfigs.length === 0) {\n return null\n }\n\n // Check main database\n let mainHealth = null\n if (this._mainDatabaseConfig) {\n mainHealth = await this._checkSingleDatabase(this._mainDatabaseConfig)\n }\n\n // Check clusters in parallel\n let clusters = null\n if (this._clusterConfigs.length > 0) {\n const clusterResults = await Promise.all(\n this._clusterConfigs.map(async config => ({\n name: config.name,\n health: await this._checkSingleDatabase(config),\n }))\n )\n\n clusters = {}\n for (const { name, health } of clusterResults) {\n clusters[name] = health\n }\n }\n\n // Calculate overall status\n const allStatuses = []\n if (mainHealth) allStatuses.push(mainHealth.status)\n if (clusters) {\n allStatuses.push(...Object.values(clusters).map(c => c.status))\n }\n\n let overallStatus = 'healthy'\n if (allStatuses.some(s => s === 'unhealthy')) {\n overallStatus = 'unhealthy'\n } else if (allStatuses.some(s => s === 'degraded')) {\n overallStatus = 'degraded'\n }\n\n // Build result\n const result = { status: overallStatus }\n if (mainHealth) {\n result.latencyMs = mainHealth.latencyMs\n if (mainHealth.error) result.error = mainHealth.error\n // Keep message for backward compatibility\n if (mainHealth.message) result.message = mainHealth.message\n }\n if (clusters) {\n result.clusters = clusters\n }\n\n return result\n }\n\n /**\n * Tests Redis connectivity using PING command.\n * @returns {Promise<ComponentHealth>}\n * @private\n */\n async _checkRedis() {\n if (!this.redisClient) {\n return { status: 'healthy', message: 'Not configured' }\n }\n\n const start = Date.now()\n\n try {\n let pong\n\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 { status: 'unhealthy', message: 'Unknown Redis client type' }\n }\n\n if (pong === 'PONG') {\n return {\n status: 'healthy',\n latencyMs: Date.now() - start,\n }\n }\n\n return {\n status: 'unhealthy',\n message: maskSensitiveData(`Unexpected PING response: ${pong}`),\n latencyMs: Date.now() - start,\n }\n } catch (err) {\n return {\n status: 'unhealthy',\n message: maskSensitiveData(err.message),\n latencyMs: Date.now() - start,\n }\n }\n }\n\n /**\n * Performs a full health check on all configured components.\n * Results are cached for the configured TTL to prevent excessive load.\n * Uses a mutex pattern to prevent concurrent health checks when cache expires.\n * If cache is expired but a refresh is in progress, returns stale cache (if available).\n *\n * @returns {Promise<HealthCheckResult>}\n */\n async performHealthCheck() {\n // If cache is valid, return immediately\n if (this._isCacheValid()) {\n return { ...this._cachedResult.result, cached: true }\n }\n\n // If a refresh is already in progress, return stale cache (if available) or wait for refresh\n if (this._refreshPromise) {\n // Return stale cache immediately if available, otherwise wait for refresh\n if (this._cachedResult) {\n return { ...this._cachedResult.result, cached: true }\n }\n // Wait for the in-progress refresh\n return this._refreshPromise\n }\n\n // Start a new refresh\n this._refreshPromise = this._performHealthCheckInternal()\n .then(result => {\n this._refreshPromise = null\n return result\n })\n .catch(err => {\n this._refreshPromise = null\n throw err\n })\n\n // Return stale cache if available while refresh is happening, otherwise wait\n if (this._cachedResult) {\n // Return stale cache immediately, refresh will happen in background\n return { ...this._cachedResult.result, cached: true }\n }\n\n // No stale cache available, wait for refresh\n return this._refreshPromise\n }\n\n /**\n * Internal method that actually performs the health check.\n * This is separated to allow the mutex pattern in performHealthCheck.\n * Only checks database - Redis is used only for cache, not health check.\n *\n * @returns {Promise<HealthCheckResult>}\n * @private\n */\n async _performHealthCheckInternal() {\n // Check database\n const dbHealth = await this._checkAllDatabases()\n\n // Optionally check Redis (for backend)\n let redisHealth = null\n if (this.includeRedisCheck && this.redisClient) {\n redisHealth = await this._checkRedis()\n }\n\n const checks = {}\n if (dbHealth) checks.database = dbHealth\n if (redisHealth) checks.redis = redisHealth\n\n const statuses = Object.values(checks).map(c => c.status)\n let overallStatus = 'healthy'\n\n if (statuses.some(s => s === 'unhealthy')) {\n overallStatus = 'unhealthy'\n } else if (statuses.some(s => s === 'degraded')) {\n overallStatus = 'degraded'\n }\n\n /** @type {HealthCheckResult} */\n const result = {\n status: overallStatus,\n timestamp: new Date().toISOString(),\n checks,\n }\n\n this._cachedResult = {\n result,\n timestamp: Date.now(),\n }\n\n return result\n }\n\n /**\n * Gets cached result from shared cache (Redis if available, otherwise in-memory).\n * Returns null if no cache is available. This is used by endpoints to read-only access.\n * If Redis fails, returns error result with proper format.\n *\n * @returns {Promise<HealthCheckResult | null>} Cached result, error result if Redis fails, or null if not available\n */\n async getCachedResult() {\n try {\n const cached = await this._cache.get()\n if (cached) {\n return cached\n }\n // No cache available - worker may not have run yet\n return null\n } catch (err) {\n // Redis read failed - return error result with proper format\n console.error(`${this.prefixLogs} Failed to read from cache:`, err)\n const errorMessage = maskSensitiveData(err.message || 'Cache read failed')\n return {\n status: 'unhealthy',\n timestamp: new Date().toISOString(),\n checks: {\n redis: {\n status: 'unhealthy',\n error: errorMessage,\n },\n },\n errors: [errorMessage],\n }\n }\n }\n\n /**\n * Forces a refresh of the health check cache.\n * This is used by background workers to periodically update the cache.\n * Updates shared cache (Redis if available, otherwise in-memory).\n *\n * @returns {Promise<HealthCheckResult>}\n */\n async refreshCache() {\n const result = await this._performHealthCheckInternal()\n await this._cache.set(result)\n\n return result\n }\n\n /**\n * Clears the cached health check result, forcing the next check to be fresh.\n */\n clearCache() {\n this._cachedResult = null\n this._refreshPromise = null\n }\n\n /**\n * Builds a list of error messages from health check result.\n * All error messages are sanitized to remove sensitive information.\n * @param {HealthCheckResult} result - Health check result\n * @returns {string[]} Array of sanitized error messages\n * @private\n */\n _getErrorMessages(result) {\n const errors = []\n\n if (result.checks.database) {\n const db = result.checks.database\n\n // Check main database status\n if (db.status === 'unhealthy' && (db.message || db.error)) {\n const dbName = this._mainDatabaseConfig?.name || 'main'\n const errorMsg = db.error || db.message\n errors.push(`DB ${dbName}: ${maskSensitiveData(errorMsg)}`)\n }\n\n // Check clusters\n if (db.clusters) {\n for (const [name, health] of Object.entries(db.clusters)) {\n if (health.status === 'unhealthy') {\n const message = health.error || health.message || 'connection failed'\n errors.push(`DB ${name}: ${maskSensitiveData(message)}`)\n }\n }\n }\n }\n\n if (result.checks.redis && result.checks.redis.status === 'unhealthy') {\n const message = result.checks.redis.error || result.checks.redis.message || 'connection failed'\n errors.push(`Redis: ${maskSensitiveData(message)}`)\n }\n\n return errors\n }\n\n /**\n * Express middleware handler for health check endpoint.\n * Returns 200 for healthy/degraded, 503 for unhealthy.\n * Response includes errors array when status is not healthy.\n * All sensitive data (passwords, connection strings, etc.) is masked.\n *\n * This handler only reads from cache and never triggers database queries.\n * Use a background worker to periodically refresh the cache.\n *\n * @returns {(req: any, res: any) => Promise<void>} Express request handler\n */\n healthHandler() {\n return async (req, res) => {\n try {\n // Only read from cache, never trigger DB queries\n const result = await this.getCachedResult()\n\n if (!result) {\n // No cache available - return unhealthy status with proper format\n const errorMsg = 'Health check cache not available. Worker may not be running.'\n res.status(503).json({\n status: 'unhealthy',\n timestamp: new Date().toISOString(),\n checks: {\n redis: {\n status: 'unhealthy',\n error: errorMsg,\n },\n },\n errors: [errorMsg],\n })\n return\n }\n\n const statusCode = result.status === 'unhealthy' ? 503 : 200\n\n // Build response - errors are already in result if present\n const response = { ...result }\n \n // Add top-level errors array if not healthy and errors not already present\n if (result.status !== 'healthy' && !result.errors) {\n const errors = this._getErrorMessages(result)\n if (errors.length > 0) {\n response.errors = errors\n }\n }\n\n res.status(statusCode).json(response)\n } catch (err) {\n console.error(`${this.prefixLogs} Health check failed:`, err)\n const errorMsg = maskSensitiveData(err.message)\n res.status(503).json({\n status: 'unhealthy',\n timestamp: new Date().toISOString(),\n checks: {\n redis: {\n status: 'unhealthy',\n error: errorMsg,\n },\n },\n errors: [errorMsg],\n })\n }\n }\n }\n\n /**\n * Register health check endpoint on an Express app.\n *\n * @param {import('express').Application} app - Express application\n * @param {string} [path='/health-status'] - Path for the health endpoint\n */\n registerHealthEndpoint(app, path = '/health-status') {\n app.get(path, this.healthHandler())\n console.info(`${this.prefixLogs} Registered health endpoint at ${path}`)\n }\n\n /**\n * Cleanup resources (database pools).\n * @returns {Promise<void>}\n */\n async cleanup() {\n for (const [name, pool] of this._databasePools) {\n try {\n await pool.end()\n } catch (err) {\n console.error(`${this.prefixLogs} Error closing database pool ${name}:`, err)\n }\n }\n this._databasePools.clear()\n }\n}\n\nmodule.exports = { HealthCheckClient }\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"healthCheckClient.js","names":["Pool","require","getRedisClientType","REDIS_V4","IOREDIS","REDIS_V3","HealthCheckCache","HEALTH_CHECK_CACHE_TTL_MS","SENSITIVE_PATTERNS","pattern","replacement","maskSensitiveData","text","masked","replace","HealthCheckClient","constructor","options","redisClient","includeRedisCheck","cacheTtlMs","appName","process","env","BUILD_APP_NAME","prefixLogs","_cachedResult","_refreshPromise","_databasePools","Map","_mainDatabaseConfig","_clusterConfigs","_initDatabases","_redisClientType","_cache","mainUrl","databaseUrl","DATABASE_URL","mainName","databaseName","name","url","additionalUrls","additionalDatabaseUrls","Object","entries","push","_getPool","config","has","set","connectionString","max","idleTimeoutMillis","connectionTimeoutMillis","get","_isCacheValid","Date","now","timestamp","_checkSingleDatabase","start","pool","query","status","latencyMs","err","error","message","_checkAllDatabases","length","mainHealth","clusters","clusterResults","Promise","all","map","health","allStatuses","values","c","overallStatus","some","s","result","_checkRedis","pong","resolve","reject","ping","performHealthCheck","cached","_performHealthCheckInternal","then","catch","dbHealth","redisHealth","checks","database","redis","statuses","toISOString","getCachedResult","console","errorMessage","errors","refreshCache","clearCache","_getErrorMessages","db","dbName","errorMsg","healthHandler","req","res","json","statusCode","response","registerHealthEndpoint","app","path","info","cleanup","end","clear","module","exports"],"sources":["../../src/health/healthCheckClient.js"],"sourcesContent":["const { Pool } = require('pg')\nconst {\n getRedisClientType,\n REDIS_V4,\n IOREDIS,\n REDIS_V3,\n} = require('../redisUtils')\nconst { HealthCheckCache } = require('./healthCheckCache')\n\nconst HEALTH_CHECK_CACHE_TTL_MS = 60 * 1000\n\n/**\n * Patterns to detect and mask sensitive information in error messages\n */\nconst SENSITIVE_PATTERNS = [\n // Database connection strings: postgres://user:password@host:port/database\n {\n pattern:\n /(postgres(?:ql)?|mysql|mongodb|redis|amqp):\\/\\/([^:]+):([^@]+)@([^:/]+)(:\\d+)?\\/([^\\s?]+)/gi,\n replacement: '$1://***:***@***$5/***',\n },\n // Generic URLs with credentials: protocol://user:password@host\n {\n pattern: /(\\w+):\\/\\/([^:]+):([^@]+)@([^\\s/]+)/gi,\n replacement: '$1://***:***@***',\n },\n // Password fields in JSON or key=value format\n {\n pattern:\n /(password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?token)[\"\\s]*[:=][\"\\s]*([^\\s,}\"]+)/gi,\n replacement: '$1=***',\n },\n // Database/table/schema/role/user names in error messages: database \"name\", table \"name\", etc.\n {\n pattern:\n /(database|table|schema|role|user|relation|column|index)\\s*[\"']([^\"']+)[\"']/gi,\n replacement: '$1 \"***\"',\n },\n // IP addresses (to hide internal network structure)\n {\n pattern: /\\b(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(:\\d+)?\\b/g,\n replacement: '***$2',\n },\n // Host names that might reveal internal infrastructure\n {\n pattern: /\\b(host|hostname|server)[\"\\s]*[:=][\"\\s]*([^\\s,}\"]+)/gi,\n replacement: '$1=***',\n },\n]\n\n/**\n * Masks sensitive information in a string\n * @param {string} text - Text that might contain sensitive data\n * @returns {string} - Text with sensitive data masked\n */\nfunction maskSensitiveData(text) {\n if (!text || typeof text !== 'string') {\n return text\n }\n\n let masked = text\n for (const { pattern, replacement } of SENSITIVE_PATTERNS) {\n masked = masked.replace(pattern, replacement)\n }\n return masked\n}\n\n/**\n * @typedef {'healthy' | 'unhealthy' | 'degraded'} HealthStatus\n */\n\n/**\n * @typedef {Object} ComponentHealth\n * @property {HealthStatus} status - Component health status\n * @property {string} [error] - Error message if status is unhealthy\n * @property {string} [message] - Optional status message (deprecated, use error)\n * @property {number} [latencyMs] - Connection latency in milliseconds\n */\n\n/**\n * @typedef {Object} DatabaseClusterHealth\n * @property {HealthStatus} status - Overall databases status\n * @property {Object<string, ComponentHealth>} clusters - Individual cluster health\n */\n\n/**\n * @typedef {Object} HealthCheckResult\n * @property {HealthStatus} status - Overall health status\n * @property {string} timestamp - ISO timestamp of the check\n * @property {Object<string, ComponentHealth | DatabaseClusterHealth>} checks - Individual service health checks\n * @property {string[]} [errors] - Top-level error messages (not related to specific services)\n */\n\n/**\n * @typedef {Object} CachedHealthResult\n * @property {HealthCheckResult} result - The cached health check result\n * @property {number} timestamp - Unix timestamp when cached\n */\n\n/**\n * @typedef {Object} DatabaseConfig\n * @property {string} name - Database/cluster name\n * @property {string} url - Connection URL\n */\n\n/**\n * HealthCheckClient provides a health check middleware for external monitoring services\n * like BetterStack. It validates database and Redis connections with rate limiting\n * to prevent excessive load on backend services.\n *\n * Features:\n * - Multi-cluster DB validation (PostgreSQL)\n * - Redis connection validation (supports ioredis, node-redis v3/v4)\n * - Result caching (default: 60 seconds) to prevent overloading services\n * - Express middleware support\n * - BetterStack-compatible JSON response format\n */\nclass HealthCheckClient {\n /**\n * @param {Object} options\n * @param {string} [options.databaseUrl] - Main PostgreSQL connection URL\n * @param {string} [options.databaseName='main'] - Name for the main database\n * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional DB clusters (name -> URL)\n * @param {any} [options.redisClient] - Redis client instance (ioredis or node-redis) - used for cache\n * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in results (for backend)\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds (default: 60s)\n * @param {string} [options.appName] - Application name for logging\n */\n constructor(options = {}) {\n this.redisClient = options.redisClient || null\n this.includeRedisCheck = options.includeRedisCheck || false\n this.cacheTtlMs = options.cacheTtlMs ?? HEALTH_CHECK_CACHE_TTL_MS\n this.appName =\n options.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n\n this.prefixLogs = `[${this.appName}] [HealthCheck]`\n\n /** @type {CachedHealthResult | null} */\n this._cachedResult = null\n\n /** @type {Promise<HealthCheckResult> | null} */\n this._refreshPromise = null\n\n /** @type {Map<string, Pool>} */\n this._databasePools = new Map()\n\n /** @type {DatabaseConfig | null} */\n this._mainDatabaseConfig = null\n\n /** @type {DatabaseConfig[]} */\n this._clusterConfigs = []\n\n this._initDatabases(options)\n\n if (this.redisClient) {\n this._redisClientType = getRedisClientType(this.redisClient)\n }\n\n this._cache = new HealthCheckCache({\n redisClient: this.redisClient,\n appName: this.appName,\n cacheTtlMs: this.cacheTtlMs,\n })\n }\n\n /**\n * Initialize database configurations from options.\n * @param {Object} options - Constructor options\n * @private\n */\n _initDatabases(options) {\n const mainUrl = options.databaseUrl || process.env.DATABASE_URL || ''\n const mainName = options.databaseName || `${this.appName}_db`\n\n if (mainUrl) {\n this._mainDatabaseConfig = { name: mainName, url: mainUrl }\n }\n\n const additionalUrls = options.additionalDatabaseUrls || {}\n for (const [name, url] of Object.entries(additionalUrls)) {\n if (url) {\n this._clusterConfigs.push({ name, url })\n }\n }\n }\n\n /**\n * Get or create a database pool for a given config.\n * @param {DatabaseConfig} config - Database configuration\n * @returns {Pool}\n * @private\n */\n _getPool(config) {\n if (!this._databasePools.has(config.name)) {\n this._databasePools.set(\n config.name,\n new Pool({\n connectionString: config.url,\n max: 1,\n idleTimeoutMillis: 30000,\n connectionTimeoutMillis: 5000,\n })\n )\n }\n return this._databasePools.get(config.name)\n }\n\n /**\n * Checks if cached result is still valid based on TTL.\n * @returns {boolean}\n * @private\n */\n _isCacheValid() {\n if (!this._cachedResult) return false\n return Date.now() - this._cachedResult.timestamp < this.cacheTtlMs\n }\n\n /**\n * Tests a single database cluster connectivity.\n * @param {DatabaseConfig} config - Database configuration\n * @returns {Promise<ComponentHealth>}\n * @private\n */\n async _checkSingleDatabase(config) {\n const start = Date.now()\n\n try {\n const pool = this._getPool(config)\n await pool.query('SELECT 1')\n return {\n status: 'healthy',\n latencyMs: Date.now() - start,\n }\n } catch (err) {\n return {\n status: 'unhealthy',\n error: maskSensitiveData(err.message),\n latencyMs: Date.now() - start,\n }\n }\n }\n\n /**\n * Tests all PostgreSQL databases (main + clusters) in parallel.\n * @returns {Promise<Object | null>} Database health with optional clusters\n * @private\n */\n async _checkAllDatabases() {\n if (!this._mainDatabaseConfig && this._clusterConfigs.length === 0) {\n return null\n }\n\n // Check main database\n let mainHealth = null\n if (this._mainDatabaseConfig) {\n mainHealth = await this._checkSingleDatabase(this._mainDatabaseConfig)\n }\n\n // Check clusters in parallel\n let clusters = null\n if (this._clusterConfigs.length > 0) {\n const clusterResults = await Promise.all(\n this._clusterConfigs.map(async config => ({\n name: config.name,\n health: await this._checkSingleDatabase(config),\n }))\n )\n\n clusters = {}\n for (const { name, health } of clusterResults) {\n clusters[name] = health\n }\n }\n\n // Calculate overall status\n const allStatuses = []\n if (mainHealth) allStatuses.push(mainHealth.status)\n if (clusters) {\n allStatuses.push(...Object.values(clusters).map(c => c.status))\n }\n\n let overallStatus = 'healthy'\n if (allStatuses.some(s => s === 'unhealthy')) {\n overallStatus = 'unhealthy'\n } else if (allStatuses.some(s => s === 'degraded')) {\n overallStatus = 'degraded'\n }\n\n // Build result\n const result = { status: overallStatus }\n if (mainHealth) {\n result.latencyMs = mainHealth.latencyMs\n if (mainHealth.error) result.error = mainHealth.error\n // Keep message for backward compatibility\n if (mainHealth.message) result.message = mainHealth.message\n }\n if (clusters) {\n result.clusters = clusters\n }\n\n return result\n }\n\n /**\n * Tests Redis connectivity using PING command.\n * @returns {Promise<ComponentHealth>}\n * @private\n */\n async _checkRedis() {\n if (!this.redisClient) {\n return { status: 'healthy', message: 'Not configured' }\n }\n\n const start = Date.now()\n\n try {\n let pong\n\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 { status: 'unhealthy', message: 'Unknown Redis client type' }\n }\n\n if (pong === 'PONG') {\n return {\n status: 'healthy',\n latencyMs: Date.now() - start,\n }\n }\n\n return {\n status: 'unhealthy',\n message: maskSensitiveData(`Unexpected PING response: ${pong}`),\n latencyMs: Date.now() - start,\n }\n } catch (err) {\n return {\n status: 'unhealthy',\n message: maskSensitiveData(err.message),\n latencyMs: Date.now() - start,\n }\n }\n }\n\n /**\n * Performs a full health check on all configured components.\n * Results are cached for the configured TTL to prevent excessive load.\n * Uses a mutex pattern to prevent concurrent health checks when cache expires.\n * If cache is expired but a refresh is in progress, returns stale cache (if available).\n *\n * @returns {Promise<HealthCheckResult>}\n */\n async performHealthCheck() {\n // If cache is valid, return immediately\n if (this._isCacheValid()) {\n return { ...this._cachedResult.result, cached: true }\n }\n\n // If a refresh is already in progress, return stale cache (if available) or wait for refresh\n if (this._refreshPromise) {\n // Return stale cache immediately if available, otherwise wait for refresh\n if (this._cachedResult) {\n return { ...this._cachedResult.result, cached: true }\n }\n // Wait for the in-progress refresh\n return this._refreshPromise\n }\n\n // Start a new refresh\n this._refreshPromise = this._performHealthCheckInternal()\n .then(result => {\n this._refreshPromise = null\n return result\n })\n .catch(err => {\n this._refreshPromise = null\n throw err\n })\n\n // Return stale cache if available while refresh is happening, otherwise wait\n if (this._cachedResult) {\n // Return stale cache immediately, refresh will happen in background\n return { ...this._cachedResult.result, cached: true }\n }\n\n // No stale cache available, wait for refresh\n return this._refreshPromise\n }\n\n /**\n * Internal method that actually performs the health check.\n * This is separated to allow the mutex pattern in performHealthCheck.\n * Only checks database - Redis is used only for cache, not health check.\n *\n * @returns {Promise<HealthCheckResult>}\n * @private\n */\n async _performHealthCheckInternal() {\n // Check database\n const dbHealth = await this._checkAllDatabases()\n\n // Optionally check Redis (for backend)\n let redisHealth = null\n if (this.includeRedisCheck && this.redisClient) {\n redisHealth = await this._checkRedis()\n }\n\n const checks = {}\n if (dbHealth) checks.database = dbHealth\n if (redisHealth) checks.redis = redisHealth\n\n const statuses = Object.values(checks).map(c => c.status)\n let overallStatus = 'healthy'\n\n if (statuses.some(s => s === 'unhealthy')) {\n overallStatus = 'unhealthy'\n } else if (statuses.some(s => s === 'degraded')) {\n overallStatus = 'degraded'\n }\n\n /** @type {HealthCheckResult} */\n const result = {\n status: overallStatus,\n timestamp: new Date().toISOString(),\n checks,\n }\n\n this._cachedResult = {\n result,\n timestamp: Date.now(),\n }\n\n return result\n }\n\n /**\n * Gets cached result from shared cache (Redis if available, otherwise in-memory).\n * Returns null if no cache is available. This is used by endpoints to read-only access.\n * If Redis fails, returns error result with proper format.\n *\n * @returns {Promise<HealthCheckResult | null>} Cached result, error result if Redis fails, or null if not available\n */\n async getCachedResult() {\n try {\n const cached = await this._cache.get()\n if (cached) {\n return cached\n }\n // No cache available - worker may not have run yet\n return null\n } catch (err) {\n // Redis read failed - return error result with proper format\n console.error(`${this.prefixLogs} Failed to read from cache:`, err)\n const errorMessage = maskSensitiveData(err.message || 'Cache read failed')\n return {\n status: 'unhealthy',\n timestamp: new Date().toISOString(),\n checks: {\n redis: {\n status: 'unhealthy',\n error: errorMessage,\n },\n },\n errors: [errorMessage],\n }\n }\n }\n\n /**\n * Forces a refresh of the health check cache.\n * This is used by background workers to periodically update the cache.\n * Updates shared cache (Redis if available, otherwise in-memory).\n *\n * @returns {Promise<HealthCheckResult>}\n */\n async refreshCache() {\n const result = await this._performHealthCheckInternal()\n await this._cache.set(result)\n\n return result\n }\n\n /**\n * Clears the cached health check result, forcing the next check to be fresh.\n */\n clearCache() {\n this._cachedResult = null\n this._refreshPromise = null\n }\n\n /**\n * Builds a list of error messages from health check result.\n * All error messages are sanitized to remove sensitive information.\n * @param {HealthCheckResult} result - Health check result\n * @returns {string[]} Array of sanitized error messages\n * @private\n */\n _getErrorMessages(result) {\n const errors = []\n\n if (result.checks.database) {\n const db = result.checks.database\n\n // Check main database status\n if (db.status === 'unhealthy' && (db.message || db.error)) {\n const dbName = this._mainDatabaseConfig?.name || 'main'\n const errorMsg = db.error || db.message\n errors.push(`DB ${dbName}: ${maskSensitiveData(errorMsg)}`)\n }\n\n // Check clusters\n if (db.clusters) {\n for (const [name, health] of Object.entries(db.clusters)) {\n if (health.status === 'unhealthy') {\n const message =\n health.error || health.message || 'connection failed'\n errors.push(`DB ${name}: ${maskSensitiveData(message)}`)\n }\n }\n }\n }\n\n if (result.checks.redis && result.checks.redis.status === 'unhealthy') {\n const message =\n result.checks.redis.error ||\n result.checks.redis.message ||\n 'connection failed'\n errors.push(`Redis: ${maskSensitiveData(message)}`)\n }\n\n return errors\n }\n\n /**\n * Express middleware handler for health check endpoint.\n * Returns 200 for healthy/degraded, 503 for unhealthy.\n * Response includes errors array when status is not healthy.\n * All sensitive data (passwords, connection strings, etc.) is masked.\n *\n * This handler only reads from cache and never triggers database queries.\n * Use a background worker to periodically refresh the cache.\n *\n * @returns {(req: any, res: any) => Promise<void>} Express request handler\n */\n healthHandler() {\n return async (req, res) => {\n try {\n // Only read from cache, never trigger DB queries\n const result = await this.getCachedResult()\n\n if (!result) {\n // No cache available - return unhealthy status with proper format\n const errorMsg =\n 'Health check cache not available. Worker may not be running.'\n res.status(503).json({\n status: 'unhealthy',\n timestamp: new Date().toISOString(),\n checks: {\n redis: {\n status: 'unhealthy',\n error: errorMsg,\n },\n },\n errors: [errorMsg],\n })\n return\n }\n\n const statusCode = result.status === 'unhealthy' ? 503 : 200\n\n // Build response - errors are already in result if present\n const response = { ...result }\n\n // Add top-level errors array if not healthy and errors not already present\n if (result.status !== 'healthy' && !result.errors) {\n const errors = this._getErrorMessages(result)\n if (errors.length > 0) {\n response.errors = errors\n }\n }\n\n res.status(statusCode).json(response)\n } catch (err) {\n console.error(`${this.prefixLogs} Health check failed:`, err)\n const errorMsg = maskSensitiveData(err.message)\n res.status(503).json({\n status: 'unhealthy',\n timestamp: new Date().toISOString(),\n checks: {\n redis: {\n status: 'unhealthy',\n error: errorMsg,\n },\n },\n errors: [errorMsg],\n })\n }\n }\n }\n\n /**\n * Register health check endpoint on an Express app.\n *\n * @param {import('express').Application} app - Express application\n * @param {string} [path='/health'] - Path for the health endpoint\n */\n registerHealthEndpoint(app, path = '/health') {\n app.get(path, this.healthHandler())\n console.info(`${this.prefixLogs} Registered health endpoint at ${path}`)\n }\n\n /**\n * Cleanup resources (database pools).\n * @returns {Promise<void>}\n */\n async cleanup() {\n for (const [name, pool] of this._databasePools) {\n try {\n await pool.end()\n } catch (err) {\n console.error(\n `${this.prefixLogs} Error closing database pool ${name}:`,\n err\n )\n }\n }\n this._databasePools.clear()\n }\n}\n\nmodule.exports = { HealthCheckClient }\n"],"mappings":"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","ignoreList":[]}
@@ -1,18 +1,8 @@
1
- /**
2
- * Creates a Redis client for health check cache with a specific client name.
3
- * This allows the cache to be shared across worker and web processes.
4
- *
5
- * @param {Object} options
6
- * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)
7
- * @param {string} [options.appName] - Application name for client name
8
- * @param {string} [options.clientName='healthcheck'] - Client name suffix
9
- * @returns {any | null} Redis client instance or null if REDIS_URL not available
10
- */
11
- export function createHealthCheckRedisClient(options?: {
12
- redisUrl?: string | undefined;
13
- appName?: string | undefined;
14
- clientName?: string | undefined;
15
- }): any | null;
1
+ declare namespace _default {
2
+ export { createHealthCheckWorkerClient };
3
+ export { createHealthCheckEndpointClient };
4
+ }
5
+ export default _default;
16
6
  /**
17
7
  * Creates a health check client for worker process.
18
8
  * Worker needs database config to perform health checks and store results in Redis.
@@ -20,22 +10,24 @@ export function createHealthCheckRedisClient(options?: {
20
10
  * @param {Object} options
21
11
  * @param {string} options.databaseUrl - Main database URL
22
12
  * @param {string} options.databaseName - Database name
13
+ * @param {any} options.redisClient - Redis client instance (from service config with appropriate name)
23
14
  * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)
24
15
  * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)
25
- * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)
16
+ * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response
26
17
  * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
27
18
  * @returns {HealthCheckClient} Configured health check client instance for worker
28
19
  */
29
- export function createHealthCheckWorkerClient(options: {
20
+ declare function createHealthCheckWorkerClient(options: {
30
21
  databaseUrl: string;
31
22
  databaseName: string;
23
+ redisClient: any;
32
24
  appName?: string | undefined;
33
25
  additionalDatabaseUrls?: {
34
26
  [x: string]: string;
35
27
  } | undefined;
36
- redisUrl?: string | undefined;
28
+ includeRedisCheck?: boolean | undefined;
37
29
  cacheTtlMs?: number | undefined;
38
- }): import("./healthCheckClient").HealthCheckClient;
30
+ }): HealthCheckClient;
39
31
  /**
40
32
  * Creates a health check client for endpoint handlers.
41
33
  * Endpoints only read from cache, so they only need Redis client (no database config).
@@ -46,9 +38,10 @@ export function createHealthCheckWorkerClient(options: {
46
38
  * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
47
39
  * @returns {HealthCheckClient} Configured health check client instance for endpoints
48
40
  */
49
- export function createHealthCheckEndpointClient(options: {
41
+ declare function createHealthCheckEndpointClient(options: {
50
42
  redisClient: any;
51
43
  includeRedisCheck?: boolean | undefined;
52
44
  cacheTtlMs?: number | undefined;
53
- }): import("./healthCheckClient").HealthCheckClient;
45
+ }): HealthCheckClient;
46
+ import { HealthCheckClient } from "./healthCheckClient";
54
47
  //# sourceMappingURL=healthCheckUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"healthCheckUtils.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckUtils.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH;IAL4B,QAAQ;IACR,OAAO;IACP,UAAU;IACzB,GAAG,GAAG,IAAI,CAmDtB;AAED;;;;;;;;;;;;GAYG;AACH;IAR2B,WAAW,EAA3B,MAAM;IACU,YAAY,EAA5B,MAAM;IACW,OAAO;IACS,sBAAsB;;;IACtC,QAAQ;IACR,UAAU;oDAmCrC;AAED;;;;;;;;;GASG;AACH;IALwB,WAAW,EAAxB,GAAG;IACe,iBAAiB;IAClB,UAAU;oDAiBrC"}
1
+ {"version":3,"file":"healthCheckUtils.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckUtils.js"],"names":[],"mappings":";;;;;AAEA;;;;;;;;;;;;;GAaG;AACH;IAT2B,WAAW,EAA3B,MAAM;IACU,YAAY,EAA5B,MAAM;IACO,WAAW,EAAxB,GAAG;IACc,OAAO;IACS,sBAAsB;;;IACrC,iBAAiB;IAClB,UAAU;IACzB,iBAAiB,CA+B7B;AAED;;;;;;;;;GASG;AACH;IALwB,WAAW,EAAxB,GAAG;IACe,iBAAiB;IAClB,UAAU;IACzB,iBAAiB,CAiB7B"}
@@ -1,65 +1,10 @@
1
1
  "use strict";
2
2
 
3
- const redis = require('redis');
4
- const {
5
- getRedisClientType,
6
- REDIS_V3,
7
- REDIS_V4
8
- } = require('../redisUtils');
9
-
10
- /**
11
- * Creates a Redis client for health check cache with a specific client name.
12
- * This allows the cache to be shared across worker and web processes.
13
- *
14
- * @param {Object} options
15
- * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)
16
- * @param {string} [options.appName] - Application name for client name
17
- * @param {string} [options.clientName='healthcheck'] - Client name suffix
18
- * @returns {any | null} Redis client instance or null if REDIS_URL not available
19
- */
20
- function createHealthCheckRedisClient(options = {}) {
21
- const redisUrl = options.redisUrl || process.env.REDIS_URL;
22
- const appName = options.appName || process.env.METRICS_APP_NAME || process.env.BUILD_APP_NAME || 'unknown-app';
23
- const clientName = options.clientName || 'healthcheck';
24
- if (!redisUrl) {
25
- console.warn(`[HealthCheck] REDIS_URL not configured for ${appName}, cache will be in-memory only (not shared across processes)`);
26
- return null;
27
- }
28
- try {
29
- const client = redis.createClient({
30
- url: redisUrl,
31
- retry_strategy: retryOptions => {
32
- if (retryOptions.attempt > 3) {
33
- console.warn(`[HealthCheck] Redis client connection failed after max retries for ${appName}`);
34
- return undefined;
35
- }
36
- return 2000;
37
- }
38
- });
39
-
40
- // Set client name for identification
41
- if (process.env.NODE_ENV !== 'test') {
42
- client.on('connect', () => {
43
- const fullClientName = `${appName}:${clientName}`;
44
- client.send_command('CLIENT', ['SETNAME', fullClientName], err => {
45
- if (err) {
46
- console.error(`[HealthCheck] Failed to set client name for ${fullClientName}:`, err);
47
- } else {
48
- console.log(`[HealthCheck] Connected to Redis for pid:${process.pid} (${fullClientName})`);
49
- }
50
- });
51
- });
52
- }
53
- client.on('error', err => {
54
- console.warn(`[HealthCheck] Redis client error for ${appName}:`, err.message);
55
- });
56
- return client;
57
- } catch (err) {
58
- console.warn(`[HealthCheck] Failed to create Redis client for ${appName}:`, err.message);
59
- return null;
60
- }
61
- }
62
-
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _healthCheckClient = require("./healthCheckClient");
63
8
  /**
64
9
  * Creates a health check client for worker process.
65
10
  * Worker needs database config to perform health checks and store results in Redis.
@@ -67,30 +12,26 @@ function createHealthCheckRedisClient(options = {}) {
67
12
  * @param {Object} options
68
13
  * @param {string} options.databaseUrl - Main database URL
69
14
  * @param {string} options.databaseName - Database name
15
+ * @param {any} options.redisClient - Redis client instance (from service config with appropriate name)
70
16
  * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)
71
17
  * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)
72
- * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)
18
+ * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response
73
19
  * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
74
20
  * @returns {HealthCheckClient} Configured health check client instance for worker
75
21
  */
76
22
  function createHealthCheckWorkerClient(options) {
77
- const {
78
- HealthCheckClient
79
- } = require('./healthCheckClient');
80
23
  const {
81
24
  databaseUrl,
82
25
  databaseName,
26
+ redisClient,
83
27
  appName,
84
28
  additionalDatabaseUrls,
85
- redisUrl,
86
29
  includeRedisCheck = false,
87
30
  cacheTtlMs = 60000
88
31
  } = options;
89
- const redisClient = createHealthCheckRedisClient({
90
- redisUrl,
91
- appName,
92
- clientName: 'healthcheck-worker'
93
- });
32
+ if (!redisClient) {
33
+ throw new Error('redisClient is required for createHealthCheckWorkerClient');
34
+ }
94
35
  const clientOptions = {
95
36
  databaseUrl,
96
37
  databaseName,
@@ -102,7 +43,7 @@ function createHealthCheckWorkerClient(options) {
102
43
  if (additionalDatabaseUrls) {
103
44
  clientOptions.additionalDatabaseUrls = additionalDatabaseUrls;
104
45
  }
105
- return new HealthCheckClient(clientOptions);
46
+ return new _healthCheckClient.HealthCheckClient(clientOptions);
106
47
  }
107
48
 
108
49
  /**
@@ -116,9 +57,6 @@ function createHealthCheckWorkerClient(options) {
116
57
  * @returns {HealthCheckClient} Configured health check client instance for endpoints
117
58
  */
118
59
  function createHealthCheckEndpointClient(options) {
119
- const {
120
- HealthCheckClient
121
- } = require('./healthCheckClient');
122
60
  const {
123
61
  redisClient,
124
62
  includeRedisCheck = false,
@@ -127,15 +65,14 @@ function createHealthCheckEndpointClient(options) {
127
65
  if (!redisClient) {
128
66
  throw new Error('redisClient is required for createHealthCheckEndpointClient');
129
67
  }
130
- return new HealthCheckClient({
68
+ return new _healthCheckClient.HealthCheckClient({
131
69
  appName: process.env.BUILD_APP_NAME || 'unknown-app',
132
70
  redisClient,
133
71
  cacheTtlMs,
134
72
  includeRedisCheck
135
73
  });
136
74
  }
137
- module.exports = {
138
- createHealthCheckRedisClient,
75
+ var _default = exports.default = {
139
76
  createHealthCheckWorkerClient,
140
77
  createHealthCheckEndpointClient
141
78
  };
@@ -1 +1 @@
1
- {"version":3,"file":"healthCheckUtils.js","names":["redis","require","getRedisClientType","REDIS_V3","REDIS_V4","createHealthCheckRedisClient","options","redisUrl","process","env","REDIS_URL","appName","METRICS_APP_NAME","BUILD_APP_NAME","clientName","console","warn","client","createClient","url","retry_strategy","retryOptions","attempt","undefined","NODE_ENV","on","fullClientName","send_command","err","error","log","pid","message","createHealthCheckWorkerClient","HealthCheckClient","databaseUrl","databaseName","additionalDatabaseUrls","includeRedisCheck","cacheTtlMs","redisClient","clientOptions","createHealthCheckEndpointClient","Error","module","exports"],"sources":["../../src/health/healthCheckUtils.js"],"sourcesContent":["const redis = require('redis')\nconst { getRedisClientType, REDIS_V3, REDIS_V4 } = require('../redisUtils')\n\n/**\n * Creates a Redis client for health check cache with a specific client name.\n * This allows the cache to be shared across worker and web processes.\n *\n * @param {Object} options\n * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)\n * @param {string} [options.appName] - Application name for client name\n * @param {string} [options.clientName='healthcheck'] - Client name suffix\n * @returns {any | null} Redis client instance or null if REDIS_URL not available\n */\nfunction createHealthCheckRedisClient(options = {}) {\n const redisUrl = options.redisUrl || process.env.REDIS_URL\n const appName = options.appName || process.env.METRICS_APP_NAME || process.env.BUILD_APP_NAME || 'unknown-app'\n const clientName = options.clientName || 'healthcheck'\n\n if (!redisUrl) {\n console.warn(`[HealthCheck] REDIS_URL not configured for ${appName}, cache will be in-memory only (not shared across processes)`)\n return null\n }\n\n try {\n const client = redis.createClient({\n url: redisUrl,\n retry_strategy: retryOptions => {\n if (retryOptions.attempt > 3) {\n console.warn(`[HealthCheck] Redis client connection failed after max retries for ${appName}`)\n return undefined\n }\n return 2000\n },\n })\n\n // Set client name for identification\n if (process.env.NODE_ENV !== 'test') {\n client.on('connect', () => {\n const fullClientName = `${appName}:${clientName}`\n client.send_command(\n 'CLIENT',\n ['SETNAME', fullClientName],\n err => {\n if (err) {\n console.error(`[HealthCheck] Failed to set client name for ${fullClientName}:`, err)\n } else {\n console.log(`[HealthCheck] Connected to Redis for pid:${process.pid} (${fullClientName})`)\n }\n }\n )\n })\n }\n\n client.on('error', err => {\n console.warn(`[HealthCheck] Redis client error for ${appName}:`, err.message)\n })\n\n return client\n } catch (err) {\n console.warn(`[HealthCheck] Failed to create Redis client for ${appName}:`, err.message)\n return null\n }\n}\n\n/**\n * Creates a health check client for worker process.\n * Worker needs database config to perform health checks and store results in Redis.\n *\n * @param {Object} options\n * @param {string} options.databaseUrl - Main database URL\n * @param {string} options.databaseName - Database name\n * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)\n * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)\n * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds\n * @returns {HealthCheckClient} Configured health check client instance for worker\n */\nfunction createHealthCheckWorkerClient(options) {\n const { HealthCheckClient } = require('./healthCheckClient')\n const {\n databaseUrl,\n databaseName,\n appName,\n additionalDatabaseUrls,\n redisUrl,\n includeRedisCheck = false,\n cacheTtlMs = 60000,\n } = options\n\n const redisClient = createHealthCheckRedisClient({\n redisUrl,\n appName,\n clientName: 'healthcheck-worker',\n })\n\n const clientOptions = {\n databaseUrl,\n databaseName,\n appName: appName || process.env.BUILD_APP_NAME || 'unknown-app',\n redisClient,\n includeRedisCheck,\n cacheTtlMs,\n }\n\n if (additionalDatabaseUrls) {\n clientOptions.additionalDatabaseUrls = additionalDatabaseUrls\n }\n\n return new HealthCheckClient(clientOptions)\n}\n\n/**\n * Creates a health check client for endpoint handlers.\n * Endpoints only read from cache, so they only need Redis client (no database config).\n *\n * @param {Object} options\n * @param {any} options.redisClient - Existing Redis client instance (from system) - REQUIRED\n * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response (for backend)\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds\n * @returns {HealthCheckClient} Configured health check client instance for endpoints\n */\nfunction createHealthCheckEndpointClient(options) {\n const { HealthCheckClient } = require('./healthCheckClient')\n const { redisClient, includeRedisCheck = false, cacheTtlMs = 60000 } = options\n\n if (!redisClient) {\n throw new Error('redisClient is required for createHealthCheckEndpointClient')\n }\n\n return new HealthCheckClient({\n appName: process.env.BUILD_APP_NAME || 'unknown-app',\n redisClient,\n cacheTtlMs,\n includeRedisCheck,\n })\n}\n\nmodule.exports = {\n createHealthCheckRedisClient,\n createHealthCheckWorkerClient,\n createHealthCheckEndpointClient,\n}\n\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"healthCheckUtils.js","names":["_healthCheckClient","require","createHealthCheckWorkerClient","options","databaseUrl","databaseName","redisClient","appName","additionalDatabaseUrls","includeRedisCheck","cacheTtlMs","Error","clientOptions","process","env","BUILD_APP_NAME","HealthCheckClient","createHealthCheckEndpointClient","_default","exports","default"],"sources":["../../src/health/healthCheckUtils.js"],"sourcesContent":["import { HealthCheckClient } from './healthCheckClient'\n\n/**\n * Creates a health check client for worker process.\n * Worker needs database config to perform health checks and store results in Redis.\n *\n * @param {Object} options\n * @param {string} options.databaseUrl - Main database URL\n * @param {string} options.databaseName - Database name\n * @param {any} options.redisClient - Redis client instance (from service config with appropriate name)\n * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)\n * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)\n * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds\n * @returns {HealthCheckClient} Configured health check client instance for worker\n */\nfunction createHealthCheckWorkerClient(options) {\n const {\n databaseUrl,\n databaseName,\n redisClient,\n appName,\n additionalDatabaseUrls,\n includeRedisCheck = false,\n cacheTtlMs = 60000,\n } = options\n\n if (!redisClient) {\n throw new Error('redisClient is required for createHealthCheckWorkerClient')\n }\n\n const clientOptions = {\n databaseUrl,\n databaseName,\n appName: appName || process.env.BUILD_APP_NAME || 'unknown-app',\n redisClient,\n includeRedisCheck,\n cacheTtlMs,\n }\n\n if (additionalDatabaseUrls) {\n clientOptions.additionalDatabaseUrls = additionalDatabaseUrls\n }\n\n return new HealthCheckClient(clientOptions)\n}\n\n/**\n * Creates a health check client for endpoint handlers.\n * Endpoints only read from cache, so they only need Redis client (no database config).\n *\n * @param {Object} options\n * @param {any} options.redisClient - Existing Redis client instance (from system) - REQUIRED\n * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response (for backend)\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds\n * @returns {HealthCheckClient} Configured health check client instance for endpoints\n */\nfunction createHealthCheckEndpointClient(options) {\n const { redisClient, includeRedisCheck = false, cacheTtlMs = 60000 } = options\n\n if (!redisClient) {\n throw new Error(\n 'redisClient is required for createHealthCheckEndpointClient'\n )\n }\n\n return new HealthCheckClient({\n appName: process.env.BUILD_APP_NAME || 'unknown-app',\n redisClient,\n cacheTtlMs,\n includeRedisCheck,\n })\n}\n\nexport default {\n createHealthCheckWorkerClient,\n createHealthCheckEndpointClient,\n}\n"],"mappings":";;;;;;AAAA,IAAAA,kBAAA,GAAAC,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,6BAA6BA,CAACC,OAAO,EAAE;EAC9C,MAAM;IACJC,WAAW;IACXC,YAAY;IACZC,WAAW;IACXC,OAAO;IACPC,sBAAsB;IACtBC,iBAAiB,GAAG,KAAK;IACzBC,UAAU,GAAG;EACf,CAAC,GAAGP,OAAO;EAEX,IAAI,CAACG,WAAW,EAAE;IAChB,MAAM,IAAIK,KAAK,CAAC,2DAA2D,CAAC;EAC9E;EAEA,MAAMC,aAAa,GAAG;IACpBR,WAAW;IACXC,YAAY;IACZE,OAAO,EAAEA,OAAO,IAAIM,OAAO,CAACC,GAAG,CAACC,cAAc,IAAI,aAAa;IAC/DT,WAAW;IACXG,iBAAiB;IACjBC;EACF,CAAC;EAED,IAAIF,sBAAsB,EAAE;IAC1BI,aAAa,CAACJ,sBAAsB,GAAGA,sBAAsB;EAC/D;EAEA,OAAO,IAAIQ,oCAAiB,CAACJ,aAAa,CAAC;AAC7C;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASK,+BAA+BA,CAACd,OAAO,EAAE;EAChD,MAAM;IAAEG,WAAW;IAAEG,iBAAiB,GAAG,KAAK;IAAEC,UAAU,GAAG;EAAM,CAAC,GAAGP,OAAO;EAE9E,IAAI,CAACG,WAAW,EAAE;IAChB,MAAM,IAAIK,KAAK,CACb,6DACF,CAAC;EACH;EAEA,OAAO,IAAIK,oCAAiB,CAAC;IAC3BT,OAAO,EAAEM,OAAO,CAACC,GAAG,CAACC,cAAc,IAAI,aAAa;IACpDT,WAAW;IACXI,UAAU;IACVD;EACF,CAAC,CAAC;AACJ;AAAC,IAAAS,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAEc;EACblB,6BAA6B;EAC7Be;AACF,CAAC","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"healthCheckWorker.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckWorker.js"],"names":[],"mappings":"AAiBA,2EA2CC"}
1
+ {"version":3,"file":"healthCheckWorker.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckWorker.js"],"names":[],"mappings":"AAkBA,2EAuDC"}
@@ -8,52 +8,62 @@ exports.createHealthCheckWorker = createHealthCheckWorker;
8
8
  * Shared health check worker utility.
9
9
  * This runs as a separate process and periodically refreshes the health check cache,
10
10
  * preventing HTTP requests from triggering database queries.
11
- * Creates its own Redis connection and health check client internally.
11
+ * Uses Redis client from service config (should be created with name 'healthcheck-worker').
12
12
  *
13
13
  * @param {Object} options
14
14
  * @param {string} options.databaseUrl - Main database URL
15
15
  * @param {string} options.databaseName - Database name
16
+ * @param {any} options.redisClient - Redis client instance (from service config with name 'healthcheck-worker')
16
17
  * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)
17
18
  * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)
18
- * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)
19
+ * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response
19
20
  * @param {number} [options.refreshIntervalMs=60000] - Refresh interval in milliseconds (default: 1 minute)
20
21
  * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
21
22
  */
22
23
  const {
23
24
  createHealthCheckWorkerClient
24
- } = require('./healthCheckUtils');
25
+ } = require('./healthCheckUtils').default;
25
26
  function createHealthCheckWorker(options) {
26
27
  const {
27
28
  refreshIntervalMs = 60000,
28
29
  ...workerClientOptions
29
30
  } = options;
31
+ const appName = workerClientOptions.appName || process.env.BUILD_APP_NAME || 'unknown-app';
32
+ const dynoId = process.env.HOSTNAME || 'unknown-dyno';
33
+ const processType = process.env.BUILD_DYNO_PROCESS_TYPE || 'health-check-worker';
34
+ const logValues = process.env.HEALTH_LOG_VALUES === 'true';
35
+ const prefixLogs = `[${processType}] [${appName}] [${dynoId}] [HealthCheck]`;
30
36
  const healthCheckClient = createHealthCheckWorkerClient(workerClientOptions);
31
37
  return async function runHealthCheckWorker() {
32
- console.log('[HealthCheckWorker] Starting health check worker...');
33
- console.log(`[HealthCheckWorker] Refresh interval: ${refreshIntervalMs}ms`);
38
+ console.log(`${prefixLogs} Starting health check worker...`);
39
+ console.log(`${prefixLogs} Refresh interval: ${refreshIntervalMs}ms`);
34
40
  try {
35
41
  await healthCheckClient.refreshCache();
36
- console.log('[HealthCheckWorker] Initial health check completed');
42
+ if (logValues) {
43
+ console.log(`${prefixLogs} Initial health check completed`);
44
+ }
37
45
  } catch (err) {
38
- console.error('[HealthCheckWorker] Initial health check failed:', err);
46
+ console.error(`${prefixLogs} Initial health check failed:`, err);
39
47
  }
40
48
  const interval = setInterval(async () => {
41
49
  try {
42
50
  const result = await healthCheckClient.refreshCache();
43
- console.log(`[HealthCheckWorker] Health check refreshed at ${result.timestamp}, status: ${result.status}`);
51
+ if (logValues) {
52
+ console.log(`${prefixLogs} Health check refreshed at ${result.timestamp}, status: ${result.status}`);
53
+ }
44
54
  } catch (err) {
45
- console.error('[HealthCheckWorker] Health check refresh failed:', err);
55
+ console.error(`${prefixLogs} Health check refresh failed:`, err);
46
56
  }
47
57
  }, refreshIntervalMs);
48
58
  process.on('SIGTERM', () => {
49
- console.log('[HealthCheckWorker] Received SIGTERM, shutting down...');
59
+ console.log(`${prefixLogs} Received SIGTERM, shutting down...`);
50
60
  clearInterval(interval);
51
61
  healthCheckClient.cleanup().finally(() => {
52
62
  process.exit(0);
53
63
  });
54
64
  });
55
65
  process.on('SIGINT', () => {
56
- console.log('[HealthCheckWorker] Received SIGINT, shutting down...');
66
+ console.log(`${prefixLogs} Received SIGINT, shutting down...`);
57
67
  clearInterval(interval);
58
68
  healthCheckClient.cleanup().finally(() => {
59
69
  process.exit(0);
@@ -1 +1 @@
1
- {"version":3,"file":"healthCheckWorker.js","names":["createHealthCheckWorkerClient","require","createHealthCheckWorker","options","refreshIntervalMs","workerClientOptions","healthCheckClient","runHealthCheckWorker","console","log","refreshCache","err","error","interval","setInterval","result","timestamp","status","process","on","clearInterval","cleanup","finally","exit"],"sources":["../../src/health/healthCheckWorker.js"],"sourcesContent":["/**\n * Shared health check worker utility.\n * This runs as a separate process and periodically refreshes the health check cache,\n * preventing HTTP requests from triggering database queries.\n * Creates its own Redis connection and health check client internally.\n *\n * @param {Object} options\n * @param {string} options.databaseUrl - Main database URL\n * @param {string} options.databaseName - Database name\n * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)\n * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)\n * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)\n * @param {number} [options.refreshIntervalMs=60000] - Refresh interval in milliseconds (default: 1 minute)\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds\n */\nconst { createHealthCheckWorkerClient } = require('./healthCheckUtils')\n\nexport function createHealthCheckWorker(options) {\n const { refreshIntervalMs = 60000, ...workerClientOptions } = options\n\n const healthCheckClient = createHealthCheckWorkerClient(workerClientOptions)\n\n return async function runHealthCheckWorker() {\n console.log('[HealthCheckWorker] Starting health check worker...')\n console.log(`[HealthCheckWorker] Refresh interval: ${refreshIntervalMs}ms`)\n\n try {\n await healthCheckClient.refreshCache()\n console.log('[HealthCheckWorker] Initial health check completed')\n } catch (err) {\n console.error('[HealthCheckWorker] Initial health check failed:', err)\n }\n\n const interval = setInterval(async () => {\n try {\n const result = await healthCheckClient.refreshCache()\n console.log(\n `[HealthCheckWorker] Health check refreshed at ${result.timestamp}, status: ${result.status}`\n )\n } catch (err) {\n console.error('[HealthCheckWorker] Health check refresh failed:', err)\n }\n }, refreshIntervalMs)\n\n process.on('SIGTERM', () => {\n console.log('[HealthCheckWorker] Received SIGTERM, shutting down...')\n clearInterval(interval)\n healthCheckClient.cleanup().finally(() => {\n process.exit(0)\n })\n })\n\n process.on('SIGINT', () => {\n console.log('[HealthCheckWorker] Received SIGINT, shutting down...')\n clearInterval(interval)\n healthCheckClient.cleanup().finally(() => {\n process.exit(0)\n })\n })\n }\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;EAAEA;AAA8B,CAAC,GAAGC,OAAO,CAAC,oBAAoB,CAAC;AAEhE,SAASC,uBAAuBA,CAACC,OAAO,EAAE;EAC/C,MAAM;IAAEC,iBAAiB,GAAG,KAAK;IAAE,GAAGC;EAAoB,CAAC,GAAGF,OAAO;EAErE,MAAMG,iBAAiB,GAAGN,6BAA6B,CAACK,mBAAmB,CAAC;EAE5E,OAAO,eAAeE,oBAAoBA,CAAA,EAAG;IAC3CC,OAAO,CAACC,GAAG,CAAC,qDAAqD,CAAC;IAClED,OAAO,CAACC,GAAG,CAAC,yCAAyCL,iBAAiB,IAAI,CAAC;IAE3E,IAAI;MACF,MAAME,iBAAiB,CAACI,YAAY,CAAC,CAAC;MACtCF,OAAO,CAACC,GAAG,CAAC,oDAAoD,CAAC;IACnE,CAAC,CAAC,OAAOE,GAAG,EAAE;MACZH,OAAO,CAACI,KAAK,CAAC,kDAAkD,EAAED,GAAG,CAAC;IACxE;IAEA,MAAME,QAAQ,GAAGC,WAAW,CAAC,YAAY;MACvC,IAAI;QACF,MAAMC,MAAM,GAAG,MAAMT,iBAAiB,CAACI,YAAY,CAAC,CAAC;QACrDF,OAAO,CAACC,GAAG,CACT,iDAAiDM,MAAM,CAACC,SAAS,aAAaD,MAAM,CAACE,MAAM,EAC7F,CAAC;MACH,CAAC,CAAC,OAAON,GAAG,EAAE;QACZH,OAAO,CAACI,KAAK,CAAC,kDAAkD,EAAED,GAAG,CAAC;MACxE;IACF,CAAC,EAAEP,iBAAiB,CAAC;IAErBc,OAAO,CAACC,EAAE,CAAC,SAAS,EAAE,MAAM;MAC1BX,OAAO,CAACC,GAAG,CAAC,wDAAwD,CAAC;MACrEW,aAAa,CAACP,QAAQ,CAAC;MACvBP,iBAAiB,CAACe,OAAO,CAAC,CAAC,CAACC,OAAO,CAAC,MAAM;QACxCJ,OAAO,CAACK,IAAI,CAAC,CAAC,CAAC;MACjB,CAAC,CAAC;IACJ,CAAC,CAAC;IAEFL,OAAO,CAACC,EAAE,CAAC,QAAQ,EAAE,MAAM;MACzBX,OAAO,CAACC,GAAG,CAAC,uDAAuD,CAAC;MACpEW,aAAa,CAACP,QAAQ,CAAC;MACvBP,iBAAiB,CAACe,OAAO,CAAC,CAAC,CAACC,OAAO,CAAC,MAAM;QACxCJ,OAAO,CAACK,IAAI,CAAC,CAAC,CAAC;MACjB,CAAC,CAAC;IACJ,CAAC,CAAC;EACJ,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"file":"healthCheckWorker.js","names":["createHealthCheckWorkerClient","require","default","createHealthCheckWorker","options","refreshIntervalMs","workerClientOptions","appName","process","env","BUILD_APP_NAME","dynoId","HOSTNAME","processType","BUILD_DYNO_PROCESS_TYPE","logValues","HEALTH_LOG_VALUES","prefixLogs","healthCheckClient","runHealthCheckWorker","console","log","refreshCache","err","error","interval","setInterval","result","timestamp","status","on","clearInterval","cleanup","finally","exit"],"sources":["../../src/health/healthCheckWorker.js"],"sourcesContent":["/**\n * Shared health check worker utility.\n * This runs as a separate process and periodically refreshes the health check cache,\n * preventing HTTP requests from triggering database queries.\n * Uses Redis client from service config (should be created with name 'healthcheck-worker').\n *\n * @param {Object} options\n * @param {string} options.databaseUrl - Main database URL\n * @param {string} options.databaseName - Database name\n * @param {any} options.redisClient - Redis client instance (from service config with name 'healthcheck-worker')\n * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)\n * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)\n * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response\n * @param {number} [options.refreshIntervalMs=60000] - Refresh interval in milliseconds (default: 1 minute)\n * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds\n */\nconst { createHealthCheckWorkerClient } = require('./healthCheckUtils').default\n\nexport function createHealthCheckWorker(options) {\n const { refreshIntervalMs = 60000, ...workerClientOptions } = options\n\n const appName =\n workerClientOptions.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n const dynoId = process.env.HOSTNAME || 'unknown-dyno'\n const processType =\n process.env.BUILD_DYNO_PROCESS_TYPE || 'health-check-worker'\n const logValues = process.env.HEALTH_LOG_VALUES === 'true'\n const prefixLogs = `[${processType}] [${appName}] [${dynoId}] [HealthCheck]`\n\n const healthCheckClient = createHealthCheckWorkerClient(workerClientOptions)\n\n return async function runHealthCheckWorker() {\n console.log(`${prefixLogs} Starting health check worker...`)\n console.log(`${prefixLogs} Refresh interval: ${refreshIntervalMs}ms`)\n\n try {\n await healthCheckClient.refreshCache()\n if (logValues) {\n console.log(`${prefixLogs} Initial health check completed`)\n }\n } catch (err) {\n console.error(`${prefixLogs} Initial health check failed:`, err)\n }\n\n const interval = setInterval(async () => {\n try {\n const result = await healthCheckClient.refreshCache()\n if (logValues) {\n console.log(\n `${prefixLogs} Health check refreshed at ${result.timestamp}, status: ${result.status}`\n )\n }\n } catch (err) {\n console.error(`${prefixLogs} Health check refresh failed:`, err)\n }\n }, refreshIntervalMs)\n\n process.on('SIGTERM', () => {\n console.log(`${prefixLogs} Received SIGTERM, shutting down...`)\n clearInterval(interval)\n healthCheckClient.cleanup().finally(() => {\n process.exit(0)\n })\n })\n\n process.on('SIGINT', () => {\n console.log(`${prefixLogs} Received SIGINT, shutting down...`)\n clearInterval(interval)\n healthCheckClient.cleanup().finally(() => {\n process.exit(0)\n })\n })\n }\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;EAAEA;AAA8B,CAAC,GAAGC,OAAO,CAAC,oBAAoB,CAAC,CAACC,OAAO;AAExE,SAASC,uBAAuBA,CAACC,OAAO,EAAE;EAC/C,MAAM;IAAEC,iBAAiB,GAAG,KAAK;IAAE,GAAGC;EAAoB,CAAC,GAAGF,OAAO;EAErE,MAAMG,OAAO,GACXD,mBAAmB,CAACC,OAAO,IAAIC,OAAO,CAACC,GAAG,CAACC,cAAc,IAAI,aAAa;EAC5E,MAAMC,MAAM,GAAGH,OAAO,CAACC,GAAG,CAACG,QAAQ,IAAI,cAAc;EACrD,MAAMC,WAAW,GACfL,OAAO,CAACC,GAAG,CAACK,uBAAuB,IAAI,qBAAqB;EAC9D,MAAMC,SAAS,GAAGP,OAAO,CAACC,GAAG,CAACO,iBAAiB,KAAK,MAAM;EAC1D,MAAMC,UAAU,GAAG,IAAIJ,WAAW,MAAMN,OAAO,MAAMI,MAAM,iBAAiB;EAE5E,MAAMO,iBAAiB,GAAGlB,6BAA6B,CAACM,mBAAmB,CAAC;EAE5E,OAAO,eAAea,oBAAoBA,CAAA,EAAG;IAC3CC,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,kCAAkC,CAAC;IAC5DG,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,sBAAsBZ,iBAAiB,IAAI,CAAC;IAErE,IAAI;MACF,MAAMa,iBAAiB,CAACI,YAAY,CAAC,CAAC;MACtC,IAAIP,SAAS,EAAE;QACbK,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,iCAAiC,CAAC;MAC7D;IACF,CAAC,CAAC,OAAOM,GAAG,EAAE;MACZH,OAAO,CAACI,KAAK,CAAC,GAAGP,UAAU,+BAA+B,EAAEM,GAAG,CAAC;IAClE;IAEA,MAAME,QAAQ,GAAGC,WAAW,CAAC,YAAY;MACvC,IAAI;QACF,MAAMC,MAAM,GAAG,MAAMT,iBAAiB,CAACI,YAAY,CAAC,CAAC;QACrD,IAAIP,SAAS,EAAE;UACbK,OAAO,CAACC,GAAG,CACT,GAAGJ,UAAU,8BAA8BU,MAAM,CAACC,SAAS,aAAaD,MAAM,CAACE,MAAM,EACvF,CAAC;QACH;MACF,CAAC,CAAC,OAAON,GAAG,EAAE;QACZH,OAAO,CAACI,KAAK,CAAC,GAAGP,UAAU,+BAA+B,EAAEM,GAAG,CAAC;MAClE;IACF,CAAC,EAAElB,iBAAiB,CAAC;IAErBG,OAAO,CAACsB,EAAE,CAAC,SAAS,EAAE,MAAM;MAC1BV,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,qCAAqC,CAAC;MAC/Dc,aAAa,CAACN,QAAQ,CAAC;MACvBP,iBAAiB,CAACc,OAAO,CAAC,CAAC,CAACC,OAAO,CAAC,MAAM;QACxCzB,OAAO,CAAC0B,IAAI,CAAC,CAAC,CAAC;MACjB,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF1B,OAAO,CAACsB,EAAE,CAAC,QAAQ,EAAE,MAAM;MACzBV,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,oCAAoC,CAAC;MAC9Dc,aAAa,CAACN,QAAQ,CAAC;MACvBP,iBAAiB,CAACc,OAAO,CAAC,CAAC,CAACC,OAAO,CAAC,MAAM;QACxCzB,OAAO,CAAC0B,IAAI,CAAC,CAAC,CAAC;MACjB,CAAC,CAAC;IACJ,CAAC,CAAC;EACJ,CAAC;AACH","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adalo/metrics",
3
- "version": "0.1.124",
3
+ "version": "0.1.126",
4
4
  "description": "Reusable metrics utilities for Node.js and Laravel apps",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -19,10 +19,11 @@ class HealthCheckCache {
19
19
  */
20
20
  constructor(options = {}) {
21
21
  this.redisClient = options.redisClient || null
22
- this.appName = options.appName || process.env.BUILD_APP_NAME || 'unknown-app'
22
+ this.appName =
23
+ options.appName || process.env.BUILD_APP_NAME || 'unknown-app'
23
24
  this.cacheTtlMs = options.cacheTtlMs ?? 60 * 1000
24
25
  this.cacheKey = `healthcheck:${this.appName}`
25
-
26
+
26
27
  /** In-memory fallback cache */
27
28
  this._memoryCache = null
28
29
  this._memoryCacheTimestamp = null
@@ -165,15 +166,10 @@ class HealthCheckCache {
165
166
 
166
167
  if (this._redisClientType === REDIS_V3) {
167
168
  await new Promise((resolve, reject) => {
168
- this.redisClient.setex(
169
- this.cacheKey,
170
- ttlSeconds,
171
- cacheStr,
172
- (err) => {
173
- if (err) reject(err)
174
- else resolve()
175
- }
176
- )
169
+ this.redisClient.setex(this.cacheKey, ttlSeconds, cacheStr, err => {
170
+ if (err) reject(err)
171
+ else resolve()
172
+ })
177
173
  })
178
174
  } else if (
179
175
  this._redisClientType === REDIS_V4 ||
@@ -204,7 +200,7 @@ class HealthCheckCache {
204
200
  try {
205
201
  if (this._redisClientType === REDIS_V3) {
206
202
  await new Promise((resolve, reject) => {
207
- this.redisClient.del(this.cacheKey, (err) => {
203
+ this.redisClient.del(this.cacheKey, err => {
208
204
  if (err) reject(err)
209
205
  else resolve()
210
206
  })
@@ -216,10 +212,7 @@ class HealthCheckCache {
216
212
  await this.redisClient.del(this.cacheKey)
217
213
  }
218
214
  } catch (redisErr) {
219
- console.warn(
220
- `[HealthCheckCache] Redis clear failed:`,
221
- redisErr.message
222
- )
215
+ console.warn(`[HealthCheckCache] Redis clear failed:`, redisErr.message)
223
216
  }
224
217
  }
225
218
  }
@@ -234,4 +227,3 @@ class HealthCheckCache {
234
227
  }
235
228
 
236
229
  module.exports = { HealthCheckCache }
237
-
@@ -15,7 +15,8 @@ const HEALTH_CHECK_CACHE_TTL_MS = 60 * 1000
15
15
  const SENSITIVE_PATTERNS = [
16
16
  // Database connection strings: postgres://user:password@host:port/database
17
17
  {
18
- pattern: /(postgres(?:ql)?|mysql|mongodb|redis|amqp):\/\/([^:]+):([^@]+)@([^:/]+)(:\d+)?\/([^\s?]+)/gi,
18
+ pattern:
19
+ /(postgres(?:ql)?|mysql|mongodb|redis|amqp):\/\/([^:]+):([^@]+)@([^:/]+)(:\d+)?\/([^\s?]+)/gi,
19
20
  replacement: '$1://***:***@***$5/***',
20
21
  },
21
22
  // Generic URLs with credentials: protocol://user:password@host
@@ -25,12 +26,14 @@ const SENSITIVE_PATTERNS = [
25
26
  },
26
27
  // Password fields in JSON or key=value format
27
28
  {
28
- pattern: /(password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?token)["\s]*[:=]["\s]*([^\s,}"]+)/gi,
29
+ pattern:
30
+ /(password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?token)["\s]*[:=]["\s]*([^\s,}"]+)/gi,
29
31
  replacement: '$1=***',
30
32
  },
31
33
  // Database/table/schema/role/user names in error messages: database "name", table "name", etc.
32
34
  {
33
- pattern: /(database|table|schema|role|user|relation|column|index)\s*["']([^"']+)["']/gi,
35
+ pattern:
36
+ /(database|table|schema|role|user|relation|column|index)\s*["']([^"']+)["']/gi,
34
37
  replacement: '$1 "***"',
35
38
  },
36
39
  // IP addresses (to hide internal network structure)
@@ -520,7 +523,8 @@ class HealthCheckClient {
520
523
  if (db.clusters) {
521
524
  for (const [name, health] of Object.entries(db.clusters)) {
522
525
  if (health.status === 'unhealthy') {
523
- const message = health.error || health.message || 'connection failed'
526
+ const message =
527
+ health.error || health.message || 'connection failed'
524
528
  errors.push(`DB ${name}: ${maskSensitiveData(message)}`)
525
529
  }
526
530
  }
@@ -528,7 +532,10 @@ class HealthCheckClient {
528
532
  }
529
533
 
530
534
  if (result.checks.redis && result.checks.redis.status === 'unhealthy') {
531
- const message = result.checks.redis.error || result.checks.redis.message || 'connection failed'
535
+ const message =
536
+ result.checks.redis.error ||
537
+ result.checks.redis.message ||
538
+ 'connection failed'
532
539
  errors.push(`Redis: ${maskSensitiveData(message)}`)
533
540
  }
534
541
 
@@ -554,7 +561,8 @@ class HealthCheckClient {
554
561
 
555
562
  if (!result) {
556
563
  // No cache available - return unhealthy status with proper format
557
- const errorMsg = 'Health check cache not available. Worker may not be running.'
564
+ const errorMsg =
565
+ 'Health check cache not available. Worker may not be running.'
558
566
  res.status(503).json({
559
567
  status: 'unhealthy',
560
568
  timestamp: new Date().toISOString(),
@@ -573,7 +581,7 @@ class HealthCheckClient {
573
581
 
574
582
  // Build response - errors are already in result if present
575
583
  const response = { ...result }
576
-
584
+
577
585
  // Add top-level errors array if not healthy and errors not already present
578
586
  if (result.status !== 'healthy' && !result.errors) {
579
587
  const errors = this._getErrorMessages(result)
@@ -605,9 +613,9 @@ class HealthCheckClient {
605
613
  * Register health check endpoint on an Express app.
606
614
  *
607
615
  * @param {import('express').Application} app - Express application
608
- * @param {string} [path='/health-status'] - Path for the health endpoint
616
+ * @param {string} [path='/health'] - Path for the health endpoint
609
617
  */
610
- registerHealthEndpoint(app, path = '/health-status') {
618
+ registerHealthEndpoint(app, path = '/health') {
611
619
  app.get(path, this.healthHandler())
612
620
  console.info(`${this.prefixLogs} Registered health endpoint at ${path}`)
613
621
  }
@@ -621,7 +629,10 @@ class HealthCheckClient {
621
629
  try {
622
630
  await pool.end()
623
631
  } catch (err) {
624
- console.error(`${this.prefixLogs} Error closing database pool ${name}:`, err)
632
+ console.error(
633
+ `${this.prefixLogs} Error closing database pool ${name}:`,
634
+ err
635
+ )
625
636
  }
626
637
  }
627
638
  this._databasePools.clear()
@@ -1,66 +1,4 @@
1
- const redis = require('redis')
2
- const { getRedisClientType, REDIS_V3, REDIS_V4 } = require('../redisUtils')
3
-
4
- /**
5
- * Creates a Redis client for health check cache with a specific client name.
6
- * This allows the cache to be shared across worker and web processes.
7
- *
8
- * @param {Object} options
9
- * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)
10
- * @param {string} [options.appName] - Application name for client name
11
- * @param {string} [options.clientName='healthcheck'] - Client name suffix
12
- * @returns {any | null} Redis client instance or null if REDIS_URL not available
13
- */
14
- function createHealthCheckRedisClient(options = {}) {
15
- const redisUrl = options.redisUrl || process.env.REDIS_URL
16
- const appName = options.appName || process.env.METRICS_APP_NAME || process.env.BUILD_APP_NAME || 'unknown-app'
17
- const clientName = options.clientName || 'healthcheck'
18
-
19
- if (!redisUrl) {
20
- console.warn(`[HealthCheck] REDIS_URL not configured for ${appName}, cache will be in-memory only (not shared across processes)`)
21
- return null
22
- }
23
-
24
- try {
25
- const client = redis.createClient({
26
- url: redisUrl,
27
- retry_strategy: retryOptions => {
28
- if (retryOptions.attempt > 3) {
29
- console.warn(`[HealthCheck] Redis client connection failed after max retries for ${appName}`)
30
- return undefined
31
- }
32
- return 2000
33
- },
34
- })
35
-
36
- // Set client name for identification
37
- if (process.env.NODE_ENV !== 'test') {
38
- client.on('connect', () => {
39
- const fullClientName = `${appName}:${clientName}`
40
- client.send_command(
41
- 'CLIENT',
42
- ['SETNAME', fullClientName],
43
- err => {
44
- if (err) {
45
- console.error(`[HealthCheck] Failed to set client name for ${fullClientName}:`, err)
46
- } else {
47
- console.log(`[HealthCheck] Connected to Redis for pid:${process.pid} (${fullClientName})`)
48
- }
49
- }
50
- )
51
- })
52
- }
53
-
54
- client.on('error', err => {
55
- console.warn(`[HealthCheck] Redis client error for ${appName}:`, err.message)
56
- })
57
-
58
- return client
59
- } catch (err) {
60
- console.warn(`[HealthCheck] Failed to create Redis client for ${appName}:`, err.message)
61
- return null
62
- }
63
- }
1
+ import { HealthCheckClient } from './healthCheckClient'
64
2
 
65
3
  /**
66
4
  * Creates a health check client for worker process.
@@ -69,29 +7,27 @@ function createHealthCheckRedisClient(options = {}) {
69
7
  * @param {Object} options
70
8
  * @param {string} options.databaseUrl - Main database URL
71
9
  * @param {string} options.databaseName - Database name
10
+ * @param {any} options.redisClient - Redis client instance (from service config with appropriate name)
72
11
  * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)
73
12
  * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)
74
- * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)
13
+ * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response
75
14
  * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
76
15
  * @returns {HealthCheckClient} Configured health check client instance for worker
77
16
  */
78
17
  function createHealthCheckWorkerClient(options) {
79
- const { HealthCheckClient } = require('./healthCheckClient')
80
18
  const {
81
19
  databaseUrl,
82
20
  databaseName,
21
+ redisClient,
83
22
  appName,
84
23
  additionalDatabaseUrls,
85
- redisUrl,
86
24
  includeRedisCheck = false,
87
25
  cacheTtlMs = 60000,
88
26
  } = options
89
27
 
90
- const redisClient = createHealthCheckRedisClient({
91
- redisUrl,
92
- appName,
93
- clientName: 'healthcheck-worker',
94
- })
28
+ if (!redisClient) {
29
+ throw new Error('redisClient is required for createHealthCheckWorkerClient')
30
+ }
95
31
 
96
32
  const clientOptions = {
97
33
  databaseUrl,
@@ -120,11 +56,12 @@ function createHealthCheckWorkerClient(options) {
120
56
  * @returns {HealthCheckClient} Configured health check client instance for endpoints
121
57
  */
122
58
  function createHealthCheckEndpointClient(options) {
123
- const { HealthCheckClient } = require('./healthCheckClient')
124
59
  const { redisClient, includeRedisCheck = false, cacheTtlMs = 60000 } = options
125
60
 
126
61
  if (!redisClient) {
127
- throw new Error('redisClient is required for createHealthCheckEndpointClient')
62
+ throw new Error(
63
+ 'redisClient is required for createHealthCheckEndpointClient'
64
+ )
128
65
  }
129
66
 
130
67
  return new HealthCheckClient({
@@ -135,9 +72,7 @@ function createHealthCheckEndpointClient(options) {
135
72
  })
136
73
  }
137
74
 
138
- module.exports = {
139
- createHealthCheckRedisClient,
75
+ export default {
140
76
  createHealthCheckWorkerClient,
141
77
  createHealthCheckEndpointClient,
142
78
  }
143
-
@@ -2,48 +2,61 @@
2
2
  * Shared health check worker utility.
3
3
  * This runs as a separate process and periodically refreshes the health check cache,
4
4
  * preventing HTTP requests from triggering database queries.
5
- * Creates its own Redis connection and health check client internally.
5
+ * Uses Redis client from service config (should be created with name 'healthcheck-worker').
6
6
  *
7
7
  * @param {Object} options
8
8
  * @param {string} options.databaseUrl - Main database URL
9
9
  * @param {string} options.databaseName - Database name
10
+ * @param {any} options.redisClient - Redis client instance (from service config with name 'healthcheck-worker')
10
11
  * @param {string} [options.appName] - Application name (defaults to BUILD_APP_NAME)
11
12
  * @param {Object<string, string>} [options.additionalDatabaseUrls] - Additional database clusters (for database service)
12
- * @param {string} [options.redisUrl] - Redis URL (defaults to process.env.REDIS_URL)
13
+ * @param {boolean} [options.includeRedisCheck=false] - Include Redis health check in response
13
14
  * @param {number} [options.refreshIntervalMs=60000] - Refresh interval in milliseconds (default: 1 minute)
14
15
  * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
15
16
  */
16
- const { createHealthCheckWorkerClient } = require('./healthCheckUtils')
17
+ const { createHealthCheckWorkerClient } = require('./healthCheckUtils').default
17
18
 
18
19
  export function createHealthCheckWorker(options) {
19
20
  const { refreshIntervalMs = 60000, ...workerClientOptions } = options
20
21
 
22
+ const appName =
23
+ workerClientOptions.appName || process.env.BUILD_APP_NAME || 'unknown-app'
24
+ const dynoId = process.env.HOSTNAME || 'unknown-dyno'
25
+ const processType =
26
+ process.env.BUILD_DYNO_PROCESS_TYPE || 'health-check-worker'
27
+ const logValues = process.env.HEALTH_LOG_VALUES === 'true'
28
+ const prefixLogs = `[${processType}] [${appName}] [${dynoId}] [HealthCheck]`
29
+
21
30
  const healthCheckClient = createHealthCheckWorkerClient(workerClientOptions)
22
31
 
23
32
  return async function runHealthCheckWorker() {
24
- console.log('[HealthCheckWorker] Starting health check worker...')
25
- console.log(`[HealthCheckWorker] Refresh interval: ${refreshIntervalMs}ms`)
33
+ console.log(`${prefixLogs} Starting health check worker...`)
34
+ console.log(`${prefixLogs} Refresh interval: ${refreshIntervalMs}ms`)
26
35
 
27
36
  try {
28
37
  await healthCheckClient.refreshCache()
29
- console.log('[HealthCheckWorker] Initial health check completed')
38
+ if (logValues) {
39
+ console.log(`${prefixLogs} Initial health check completed`)
40
+ }
30
41
  } catch (err) {
31
- console.error('[HealthCheckWorker] Initial health check failed:', err)
42
+ console.error(`${prefixLogs} Initial health check failed:`, err)
32
43
  }
33
44
 
34
45
  const interval = setInterval(async () => {
35
46
  try {
36
47
  const result = await healthCheckClient.refreshCache()
37
- console.log(
38
- `[HealthCheckWorker] Health check refreshed at ${result.timestamp}, status: ${result.status}`
39
- )
48
+ if (logValues) {
49
+ console.log(
50
+ `${prefixLogs} Health check refreshed at ${result.timestamp}, status: ${result.status}`
51
+ )
52
+ }
40
53
  } catch (err) {
41
- console.error('[HealthCheckWorker] Health check refresh failed:', err)
54
+ console.error(`${prefixLogs} Health check refresh failed:`, err)
42
55
  }
43
56
  }, refreshIntervalMs)
44
57
 
45
58
  process.on('SIGTERM', () => {
46
- console.log('[HealthCheckWorker] Received SIGTERM, shutting down...')
59
+ console.log(`${prefixLogs} Received SIGTERM, shutting down...`)
47
60
  clearInterval(interval)
48
61
  healthCheckClient.cleanup().finally(() => {
49
62
  process.exit(0)
@@ -51,7 +64,7 @@ export function createHealthCheckWorker(options) {
51
64
  })
52
65
 
53
66
  process.on('SIGINT', () => {
54
- console.log('[HealthCheckWorker] Received SIGINT, shutting down...')
67
+ console.log(`${prefixLogs} Received SIGINT, shutting down...`)
55
68
  clearInterval(interval)
56
69
  healthCheckClient.cleanup().finally(() => {
57
70
  process.exit(0)