@sebspark/health-check 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +20 -59
- package/dist/index.js.map +1 -0
- package/package.json +5 -5
- package/dist/index.d.mts +0 -378
- package/dist/index.mjs +0 -533
package/dist/index.js
CHANGED
|
@@ -1,44 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.ts
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
DependencyMonitor: () => DependencyMonitor,
|
|
34
|
-
HealthMonitor: () => HealthMonitor,
|
|
35
|
-
TimeoutError: () => TimeoutError,
|
|
36
|
-
UnknownError: () => UnknownError
|
|
37
|
-
});
|
|
38
|
-
module.exports = __toCommonJS(index_exports);
|
|
39
|
-
|
|
40
1
|
// src/dependency-monitor.ts
|
|
41
|
-
|
|
2
|
+
import { performance } from "perf_hooks";
|
|
42
3
|
|
|
43
4
|
// src/types.ts
|
|
44
5
|
var TimeoutError = class extends Error {
|
|
@@ -156,11 +117,11 @@ var DependencyMonitor = class {
|
|
|
156
117
|
* - Error if `"error"`, `Error`, or timeout exceeded
|
|
157
118
|
*/
|
|
158
119
|
async doSyncCheck() {
|
|
159
|
-
const start =
|
|
120
|
+
const start = performance.now();
|
|
160
121
|
const { syncCall, timeoutLimitMs } = this.config;
|
|
161
122
|
try {
|
|
162
123
|
const response = await runAgainstTimeout(syncCall(), timeoutLimitMs);
|
|
163
|
-
const duration =
|
|
124
|
+
const duration = performance.now() - start;
|
|
164
125
|
this.handleDependencyResponse(response, duration);
|
|
165
126
|
} catch (err) {
|
|
166
127
|
this.handleDependencyError(err);
|
|
@@ -174,7 +135,7 @@ var DependencyMonitor = class {
|
|
|
174
135
|
*/
|
|
175
136
|
async doAsyncCheck() {
|
|
176
137
|
const { asyncCall, healthyLimitMs, timeoutLimitMs } = this.config;
|
|
177
|
-
const start =
|
|
138
|
+
const start = performance.now();
|
|
178
139
|
let callActive = true;
|
|
179
140
|
let healthTimeout;
|
|
180
141
|
let serviceTimeout;
|
|
@@ -183,7 +144,7 @@ var DependencyMonitor = class {
|
|
|
183
144
|
callActive = false;
|
|
184
145
|
clearTimeout(healthTimeout);
|
|
185
146
|
clearTimeout(serviceTimeout);
|
|
186
|
-
const duration =
|
|
147
|
+
const duration = performance.now() - start;
|
|
187
148
|
this.handleDependencyResponse(response, duration);
|
|
188
149
|
});
|
|
189
150
|
healthTimeout = setTimeout(() => {
|
|
@@ -284,25 +245,25 @@ var DependencyMonitor = class {
|
|
|
284
245
|
};
|
|
285
246
|
|
|
286
247
|
// src/health-monitor.ts
|
|
287
|
-
|
|
248
|
+
import { Router } from "express";
|
|
288
249
|
|
|
289
250
|
// src/static-checks.ts
|
|
290
|
-
|
|
251
|
+
import os from "os";
|
|
291
252
|
var ping = () => ({ status: "ok" });
|
|
292
253
|
var liveness = () => {
|
|
293
|
-
const cpus =
|
|
294
|
-
const total =
|
|
295
|
-
const free =
|
|
254
|
+
const cpus = os.cpus();
|
|
255
|
+
const total = os.totalmem();
|
|
256
|
+
const free = os.freemem();
|
|
296
257
|
return {
|
|
297
258
|
status: "ok",
|
|
298
259
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
299
260
|
system: {
|
|
300
|
-
hostname:
|
|
301
|
-
platform:
|
|
302
|
-
release:
|
|
303
|
-
arch:
|
|
304
|
-
uptime:
|
|
305
|
-
loadavg:
|
|
261
|
+
hostname: os.hostname(),
|
|
262
|
+
platform: os.platform(),
|
|
263
|
+
release: os.release(),
|
|
264
|
+
arch: os.arch(),
|
|
265
|
+
uptime: os.uptime(),
|
|
266
|
+
loadavg: os.loadavg(),
|
|
306
267
|
totalmem: total,
|
|
307
268
|
freemem: free,
|
|
308
269
|
memUsedRatio: total > 0 ? (total - free) / total : 0,
|
|
@@ -362,7 +323,7 @@ var HealthMonitor = class {
|
|
|
362
323
|
* Internal: create the Express router with /health routes.
|
|
363
324
|
*/
|
|
364
325
|
createRouter() {
|
|
365
|
-
const router =
|
|
326
|
+
const router = Router();
|
|
366
327
|
router.get("/health", async (_req, res, next) => {
|
|
367
328
|
try {
|
|
368
329
|
const health = await this.health();
|
|
@@ -564,10 +525,10 @@ var HealthMonitor = class {
|
|
|
564
525
|
}
|
|
565
526
|
}
|
|
566
527
|
};
|
|
567
|
-
|
|
568
|
-
0 && (module.exports = {
|
|
528
|
+
export {
|
|
569
529
|
DependencyMonitor,
|
|
570
530
|
HealthMonitor,
|
|
571
531
|
TimeoutError,
|
|
572
532
|
UnknownError
|
|
573
|
-
}
|
|
533
|
+
};
|
|
534
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dependency-monitor.ts","../src/types.ts","../src/timing.ts","../src/health-monitor.ts","../src/static-checks.ts"],"sourcesContent":["import { performance } from 'node:perf_hooks'\nimport { runAgainstTimeout, singleFlight } from './timing'\nimport {\n type CheckError,\n type DependencyCheck,\n type DependencyStatusValue,\n type Freshness,\n type Impact,\n type Mode,\n type Observed,\n type StatusValue,\n TimeoutError,\n UnknownError,\n} from './types'\n\n/**\n * Base configuration shared by all dependency monitor modes.\n */\ntype BaseConfig = {\n /**\n * Importance of the dependency:\n * - `'critical'` → service cannot run without it\n * - `'non_critical'` → service is degraded but still functional if it fails\n */\n impact: Impact\n\n /**\n * Polling interval in milliseconds.\n * - Required for polled and async modes\n * - Must not be set for inline mode\n */\n pollRate?: number\n\n /**\n * Polling interval in milliseconds for failed calls.\n * - Defaults to pollRate for polled and async modes\n * - Must not be set for inline mode\n */\n retryRate?: number\n\n /**\n * Maximum response time in milliseconds considered healthy.\n * - A call resolving within this time window **and** returning `\"ok\"` → status `\"ok\"`\n * - A call resolving after this but **before** `timeoutLimitMs` → status `\"degraded\"`\n */\n healthyLimitMs: number\n\n /**\n * Absolute upper bound in milliseconds; beyond this a call is considered `\"error\"`.\n * - Inline: the awaited call is raced with a timeout and rejected past this limit\n * - Polled/Async: escalate `unknown` → `degraded` at `healthyLimitMs`, then `\"error\"` at `timeoutLimitMs`\n * - Any late `\"ok\"` result after this limit is ignored\n */\n timeoutLimitMs: number\n}\n\n/**\n * Inline mode configuration (no polling).\n * `syncCall` runs **only** when {@link DependencyMonitor.check} is invoked.\n */\nexport type SyncInlineConfig = BaseConfig & {\n /** Performs the check immediately when {@link DependencyMonitor.check} is called. */\n syncCall: () => Promise<DependencyStatusValue | Error>\n asyncCall?: never\n pollRate?: undefined\n retryRate?: undefined\n}\n\n/**\n * Polled mode configuration.\n * `syncCall` runs on an interval (`pollRate`) and results are cached.\n */\nexport type SyncPolledConfig = BaseConfig & {\n /** Performs the check at each poll interval. */\n syncCall: () => Promise<DependencyStatusValue | Error>\n /** Polling interval in milliseconds. */\n pollRate: number\n asyncCall?: never\n}\n\n/**\n * Async mode configuration.\n * `asyncCall` starts a check and must report via the provided callback.\n */\nexport type AsyncConfig = BaseConfig & {\n /**\n * Starts an asynchronous check. Must call `reportResponse` once a result is available.\n * The callback may be invoked with a `DependencyStatusValue` or an `Error`.\n */\n asyncCall: (\n reportResponse: (\n status: DependencyStatusValue | Error\n ) => void | Promise<void>\n ) => void | Promise<void>\n /** Polling interval in milliseconds. */\n pollRate: number\n syncCall?: never\n}\n\n/**\n * Discriminated configuration for a dependency monitor.\n * - {@link SyncInlineConfig} → inline checks on demand\n * - {@link SyncPolledConfig} → synchronous checks on a fixed interval\n * - {@link AsyncConfig} → asynchronous checks that report back via callback\n */\nexport type DependencyMonitorConfig =\n | SyncInlineConfig\n | SyncPolledConfig\n | AsyncConfig\n\n/**\n * Tracks the health of a single dependency in one of three modes:\n *\n * - **inline**: `syncCall` executed only when {@link check} is called\n * - **polled**: `syncCall` executed on `pollRate`; latest result cached\n * - **async**: `asyncCall` executed on `pollRate`, result returned via callback\n *\n * The monitor:\n * - classifies results using `healthyLimitMs` and `timeoutLimitMs`\n * - records freshness (`lastChecked`, `lastSuccess`)\n * - records observed latency (ms) when applicable\n *\n * Status interpretation:\n * - `\"ok\"` only if returned `\"ok\"` **and** latency ≤ `healthyLimitMs`\n * - `\"degraded\"` if returned `\"degraded\"` **or** latency > `healthyLimitMs` but ≤ `timeoutLimitMs`\n * - `\"error\"` if returned `\"error\"`, an `Error`, or exceeded `timeoutLimitMs`\n * - `\"unknown\"` is the initial state before first successful/failed result (polled/async)\n */\nexport class DependencyMonitor {\n /** Immutable configuration for this monitor. */\n private readonly config: DependencyMonitorConfig\n /** Operational mode determined from the configuration. */\n private readonly mode: Mode\n\n /** Last computed status (starts as `\"unknown\"` until first result). */\n private status: StatusValue\n /** Optional error details from the last failure. */\n private error?: CheckError\n /** Last observed metrics (e.g., latency). */\n private observed?: Observed\n /** Freshness timestamps for last check/success. */\n private freshness?: Freshness\n\n /** Indicates whether this monitor has been disposed. */\n private isDisposed = false\n /** Internal timer handle for polling/retry. */\n private timeout?: NodeJS.Timeout\n\n /**\n * Create a new {@link DependencyMonitor}.\n * @param config Monitor configuration (inline, polled, or async).\n */\n constructor(config: DependencyMonitorConfig) {\n this.config = config\n\n this.mode = config.asyncCall\n ? 'async'\n : config.pollRate\n ? 'polled'\n : 'inline'\n\n this.status = 'unknown'\n\n // Deduplicate concurrent inline checks (single-flight).\n if (this.mode === 'inline') {\n this.check = singleFlight(this.check.bind(this))\n }\n\n // Kick off the first cycle for polled/async.\n if (this.mode !== 'inline') {\n this.doCheck()\n }\n }\n\n /** Impact of this dependency (critical/non_critical). */\n public get impact() {\n return this.config.impact\n }\n\n /**\n * Run one check cycle depending on the configured mode.\n * - Inline: no-op here (runs in {@link check})\n * - Polled/Async: invoked automatically and re-scheduled as needed\n */\n private async doCheck() {\n if (this.timeout) {\n clearTimeout(this.timeout)\n }\n if (this.isDisposed) {\n return\n }\n\n if (this.config.syncCall) {\n await this.doSyncCheck()\n } else {\n this.doAsyncCheck()\n }\n }\n\n /**\n * Execute a synchronous (inline/polled) check and update internal state.\n * Classification:\n * - OK if `\"ok\"` and latency ≤ `healthyLimitMs`\n * - Degraded if `\"degraded\"` or latency between `(healthyLimitMs, timeoutLimitMs]`\n * - Error if `\"error\"`, `Error`, or timeout exceeded\n */\n private async doSyncCheck() {\n const start = performance.now()\n\n const { syncCall, timeoutLimitMs } = this.config as SyncInlineConfig\n\n try {\n const response = await runAgainstTimeout(syncCall(), timeoutLimitMs)\n const duration = performance.now() - start\n\n this.handleDependencyResponse(response, duration)\n } catch (err) {\n this.handleDependencyError(err as Error)\n }\n }\n\n /**\n * Execute an asynchronous check (async mode).\n * - Initializes timers to escalate `unknown` → `degraded` → `error`\n * if no callback result arrives within the thresholds.\n * - On callback, clears timers and classifies the result with latency.\n */\n private async doAsyncCheck() {\n const { asyncCall, healthyLimitMs, timeoutLimitMs } = this\n .config as AsyncConfig\n\n const start = performance.now()\n let callActive = true\n let healthTimeout: NodeJS.Timeout | undefined\n let serviceTimeout: NodeJS.Timeout | undefined\n\n asyncCall((response) => {\n if (!callActive) return\n callActive = false\n clearTimeout(healthTimeout)\n clearTimeout(serviceTimeout)\n\n const duration = performance.now() - start\n this.handleDependencyResponse(response, duration)\n })\n\n // Escalate to degraded after healthy limit if still waiting.\n healthTimeout = setTimeout(() => {\n if (callActive) this.status = 'degraded'\n }, healthyLimitMs)\n\n // Escalate to error after timeout limit if still waiting.\n serviceTimeout = setTimeout(() => {\n if (!callActive) return\n callActive = false\n clearTimeout(healthTimeout)\n this.handleDependencyError(new TimeoutError())\n }, timeoutLimitMs)\n }\n\n /**\n * Normalize a successful dependency response into status + metadata.\n * @param response Returned status value or Error from the dependency\n * @param duration Measured latency in milliseconds\n */\n private handleDependencyResponse(\n response: DependencyStatusValue | Error,\n duration: number\n ) {\n if (response instanceof Error) {\n return this.handleDependencyError(response)\n }\n if (response === 'error') {\n return this.handleDependencyError(new UnknownError())\n }\n\n this.error = undefined\n this.status =\n response === 'ok' && duration <= this.config.healthyLimitMs\n ? 'ok'\n : 'degraded'\n\n const end = new Date()\n this.freshness = {\n lastChecked: end.toISOString(),\n lastSuccess: end.toISOString(),\n }\n\n this.observed = { latencyMs: duration }\n\n // Schedule next poll if applicable.\n if (this.config.pollRate && !this.isDisposed) {\n const delay = this.config.pollRate\n this.timeout = setTimeout(() => this.doCheck(), delay)\n }\n }\n\n /**\n * Normalize a failed dependency result into `\"error\"` and update metadata.\n * @param error Error thrown/returned or synthesized (e.g., timeout)\n */\n private handleDependencyError(error: Error & { code?: string }) {\n this.status = 'error'\n this.observed = undefined\n this.error = {\n code: error.code || 'UNKNOWN',\n message: error.message,\n }\n\n const end = new Date()\n if (this.freshness) {\n this.freshness.lastChecked = end.toISOString()\n } else {\n this.freshness = { lastChecked: end.toISOString(), lastSuccess: null }\n }\n\n // Schedule retry/poll if configured.\n if ((this.config.retryRate || this.config.pollRate) && !this.isDisposed) {\n const delay = this.config.retryRate || this.config.pollRate\n this.timeout = setTimeout(() => this.doCheck(), delay)\n }\n }\n\n /**\n * Get a snapshot of the dependency’s current health.\n * - Inline mode: executes the `syncCall` (single-flight guarded)\n * - Polled/Async: returns the cached result\n * @throws if the monitor has been disposed\n */\n public async check(): Promise<DependencyCheck> {\n if (this.isDisposed) {\n throw new Error('DependencyMonitor has been disposed')\n }\n\n if (this.mode === 'inline') {\n await this.doCheck()\n }\n\n const result: DependencyCheck = {\n status: this.status as StatusValue,\n impact: this.config.impact,\n mode: this.mode,\n freshness: this.freshness as Freshness,\n observed: this.observed,\n error: this.error,\n }\n\n return result\n }\n\n /** Symbol-based disposer for use with `using`. */\n public [Symbol.dispose]() {\n this.dispose()\n }\n\n /**\n * Dispose the monitor:\n * - Clears any scheduled timers\n * - Prevents further checks from running\n */\n public dispose() {\n this.isDisposed = true\n if (this.timeout) {\n clearTimeout(this.timeout)\n }\n }\n}\n","export type DependencyStatusValue = 'ok' | 'degraded' | 'error'\nexport type StatusValue = DependencyStatusValue | 'unknown' | 'draining'\nexport type Impact = 'critical' | 'non_critical'\nexport type Mode = 'inline' | 'polled' | 'async'\n\nexport type Status = {\n status: StatusValue\n}\n\nexport type System = {\n hostname: string\n platform: NodeJS.Platform\n release: string\n arch: string\n uptime: number\n loadavg: [number, number, number] // 1, 5, 15 min\n totalmem: number\n freemem: number\n memUsedRatio: number // 0..1\n cpus: {\n count: number\n model?: string\n speedMHz?: number\n }\n}\n\nexport type Process = {\n pid: number\n node: string\n uptime: number\n memory: NodeJS.MemoryUsage\n}\n\nexport type Liveness = Status & {\n timestamp: string\n system: System\n process: Process\n}\n\nexport type Freshness = {\n lastChecked: string\n lastSuccess: string | null\n}\n\nexport type Observed = {\n latencyMs: number | null\n [k: string]: unknown\n}\n\nexport type CheckError = {\n code: string\n message: string\n}\n\nexport type DependencyCheck = Status & {\n impact: Impact\n mode: Mode\n freshness: Freshness\n observed?: Observed\n details?: Record<string, unknown>\n error?: CheckError | null\n since?: string | null\n}\n\nexport type ReadinessSummary = {\n critical: { ok: number; failing: number }\n nonCritical: { ok: number; degraded: number; failing: number }\n degradedReasons: string[]\n}\n\nexport type ReadinessPayload = Status & {\n timestamp: string\n service?: { name?: string; version?: string; instanceId?: string }\n summary: ReadinessSummary\n checks: Record<string, DependencyCheck>\n}\n\nexport type HealthSummary = Status &\n Pick<ReadinessPayload, 'summary' | 'checks'> &\n Pick<Liveness, 'system' | 'process'> & { timestamp: string }\n\nexport class TimeoutError extends Error {\n constructor() {\n super('timeout')\n this.name = 'TimeoutError'\n }\n}\n\nexport class UnknownError extends Error {\n constructor() {\n super('unknown')\n this.name = 'UnknownError'\n }\n}\n","import { TimeoutError } from './types'\n\nexport const wait = (ms: number) =>\n new Promise<void>((resolve) => setTimeout(resolve, ms))\n\nexport const runTimeoutTimer = async (ms: number) => {\n await wait(ms)\n throw new TimeoutError()\n}\n\nexport const runAgainstTimeout = <T>(\n promise: Promise<T>,\n ms?: number | undefined\n): Promise<T> => {\n if ((ms as number) > 0) {\n return Promise.race([promise, runTimeoutTimer(ms as number)])\n }\n\n return promise\n}\n\n/**\n * Throttles an async function:\n * - coalesces concurrent calls into a single in-flight Promise,\n * - after it settles, reuses that settled Promise for `ms` ms,\n * - then clears so the next call invokes `fn` again.\n *\n * Fully preserves the original function signature.\n */\nexport const throttle = <F extends (...args: unknown[]) => Promise<unknown>>(\n fn: F,\n ms: number\n) => {\n type R = ReturnType<F> // a Promise<T>\n\n let current: R | null = null\n let clearHandle: NodeJS.Timeout | null = null\n\n const wrapped = (...args: Parameters<F>): R => {\n if (!current) {\n current = fn(...args) as R\n\n if (clearHandle) {\n clearTimeout(clearHandle)\n clearHandle = null\n }\n\n current.finally(() => {\n if (ms > 0) {\n clearHandle = setTimeout(() => {\n current = null\n clearHandle = null\n }, ms)\n } else {\n current = null\n }\n })\n }\n\n return current\n }\n\n // Cast to F so the returned function has the *identical* callable type\n return wrapped as unknown as F\n}\n\n/**\n * Wraps an async function so that concurrent invocations share the same in-flight Promise.\n * After the Promise settles (resolve or reject), the next call will invoke the function again.\n * Essentially just throttled with timeout 0\n */\nexport const singleFlight = <\n F extends (...args: unknown[]) => Promise<unknown>,\n>(\n fn: F\n) => throttle<F>(fn, 0)\n","import { Router } from 'express'\nimport type { ErrorRequestHandler } from 'express-serve-static-core'\nimport type {\n DependencyMonitor,\n DependencyMonitorConfig,\n} from './dependency-monitor'\nimport { liveness, ping } from './static-checks'\nimport { throttle } from './timing'\nimport type {\n DependencyCheck,\n HealthSummary,\n Impact,\n ReadinessPayload,\n ReadinessSummary,\n} from './types'\n\nexport interface HealthMonitorConfig {\n throttle: number\n}\n\n/**\n * HealthMonitor wires up standard health check endpoints for an Express app.\n *\n * It provides three endpoints under `/health`:\n * - /ping → lightweight always-OK response (`{ status: \"ok\" }`)\n * - /live → liveness probe (`{ status, uptime, timestamp }`)\n * - /ready → readiness probe with dependency checks (`{ status, checks }`)\n *\n * Dependencies can be added via {@link addDependency} with a {@link DependencyMonitorConfig}.\n * Each dependency is wrapped in a {@link DependencyMonitor} and tracked for readiness.\n *\n * ### Example\n *\n * ```ts\n * import express from 'express'\n * import { HealthMonitor } from './health-monitor'\n *\n * const app = express()\n * const monitor = new HealthMonitor()\n *\n * monitor.addDependency('db', {\n * impact: 'critical',\n * syncCall: async () => {\n * await db.ping()\n * return 'ok'\n * }\n * })\n *\n * app.use(monitor.router)\n * app.listen(3000)\n * ```\n */\nexport class HealthMonitor {\n private readonly _router: Router\n private readonly dependencies: Map<string, DependencyMonitor>\n // private isDisposed = false\n\n /**\n * Create a new HealthMonitor instance with its own router and dependency map.\n */\n constructor(config?: HealthMonitorConfig) {\n this._router = this.createRouter()\n this.dependencies = new Map()\n\n const thr = config ? config.throttle : 10\n if (thr > 0) {\n this.ready = throttle(this.ready.bind(this), thr)\n }\n }\n\n /**\n * Register a named dependency to be checked as part of readiness.\n *\n * @param name - Identifier for the dependency (e.g. \"postgres\", \"redis\").\n * @param dependency - DependencyMonitor\n * @returns this for chaining\n *\n * @example\n * ```ts\n * monitor.addDependency('redis', new DependencyMonitor({\n * impact: 'critical',\n * pollRate: 5000,\n * syncCall: async () => redis.ping() ? 'ok' : 'error'\n * }))\n * ```\n */\n public addDependency(name: string, dependency: DependencyMonitor) {\n this.dependencies.set(name, dependency)\n return this\n }\n\n /**\n * Internal: create the Express router with /health routes.\n */\n private createRouter() {\n const router = Router()\n\n // Health\n router.get('/health', async (_req, res, next) => {\n try {\n const health = await this.health()\n res.status(200).json(health)\n } catch (err) {\n next?.(err)\n }\n })\n\n // Ping\n router.get('/health/ping', (_req, res, next) => {\n try {\n res.status(200).json(this.ping())\n } catch (err) {\n next?.(err)\n }\n })\n\n // Liveness\n router.get('/health/live', (_req, res, next) => {\n try {\n res.status(200).json(this.live())\n } catch (err) {\n next?.(err)\n }\n })\n\n // Readiness\n router.get('/health/ready', async (_req, res, next) => {\n try {\n const readiness = await this.ready()\n res.status(200).json(readiness)\n } catch (err) {\n next?.(err)\n }\n })\n\n // Error handler\n const errorHandler: ErrorRequestHandler = (err, _req, res, _next) => {\n const message = err instanceof Error ? err.message : String(err)\n res.status(500).json({\n status: 'error',\n error: { message },\n })\n }\n router.use(errorHandler)\n\n return router\n }\n\n /**\n * Static ping check — always returns `{ status: \"ok\" }`.\n */\n public ping() {\n return ping()\n }\n\n /**\n * Liveness probe: reports uptime and current timestamp.\n */\n public live() {\n return liveness()\n }\n\n /**\n * Readiness probe: runs all registered dependencies in parallel\n * and aggregates their results into an overall status.\n *\n * - Any **critical error** → `\"error\"`\n * - Else any **critical degraded** or any non-critical degraded/error → `\"degraded\"`\n * - Else → `\"ok\"`\n *\n * @returns Object with overall `status` and per-dependency `checks`\n *\n * @example\n * ```json\n * {\n * \"status\": \"ok\",\n * \"checks\": {\n * \"postgres\": {\n * \"status\": \"ok\",\n * \"impact\": \"critical\",\n * \"mode\": \"inline\",\n * \"freshness\": {\n * \"lastChecked\": \"2025-08-19T12:00:00Z\",\n * \"lastSuccess\": \"2025-08-19T12:00:00Z\"\n * },\n * \"observed\": { \"latencyMs\": 12 }\n * }\n * }\n * }\n * ```\n */\n public async ready(): Promise<ReadinessPayload> {\n const entries = Array.from(this.dependencies.entries())\n\n const settled = await Promise.allSettled(\n entries.map(async ([name, monitor]) => {\n const check = await monitor.check()\n return [name, check] as const\n })\n )\n\n const checks: Record<string, DependencyCheck> = {}\n\n for (let i = 0; i < settled.length; i++) {\n const s = settled[i]\n const [name] = entries[i]\n\n if (s.status === 'fulfilled') {\n const [, check] = s.value\n checks[name] = check\n } else {\n checks[name] = {\n status: 'error',\n impact: this.dependencies.get(name)?.impact as Impact,\n mode: 'polled',\n freshness: {\n lastChecked: new Date().toISOString(),\n lastSuccess: null,\n },\n observed: undefined,\n error: {\n code: 'CHECK_FAILED',\n message: String(s.reason ?? 'unknown error'),\n },\n }\n }\n }\n\n // ----- summary -------------------------------------------------------------\n let criticalOk = 0\n let criticalFailing = 0 // counts any non-ok critical (degraded OR error)\n\n let nonCritOk = 0\n let nonCritDegraded = 0\n let nonCritFailing = 0 // non-critical with status === 'error'\n\n const degradedReasons: string[] = []\n\n for (const [name, c] of Object.entries(checks)) {\n const isCritical = c.impact === 'critical'\n if (c.status === 'ok') {\n if (isCritical) criticalOk++\n else nonCritOk++\n } else if (c.status === 'degraded') {\n if (isCritical)\n criticalFailing++ // critical degraded counts as failing\n else nonCritDegraded++\n degradedReasons.push(`${name}:degraded`)\n } else {\n // 'error'\n if (isCritical) criticalFailing++\n else nonCritFailing++\n degradedReasons.push(`${name}:${c.error?.code ?? 'error'}`)\n }\n }\n\n const summary: ReadinessSummary = {\n critical: { ok: criticalOk, failing: criticalFailing },\n nonCritical: {\n ok: nonCritOk,\n degraded: nonCritDegraded,\n failing: nonCritFailing,\n },\n degradedReasons,\n }\n\n // ----- overall status (same rules as before) -------------------------------\n let status: import('./types').StatusValue = 'ok'\n const values = Object.values(checks)\n const anyCriticalError = values.some(\n (c) => c.impact === 'critical' && c.status === 'error'\n )\n const anyDegradedOrError = values.some(\n (c) => c.status === 'degraded' || c.status === 'error'\n )\n const anyCriticalDegraded = values.some(\n (c) => c.impact === 'critical' && c.status === 'degraded'\n )\n\n if (anyCriticalError) status = 'error'\n else if (anyCriticalDegraded || anyDegradedOrError) status = 'degraded'\n\n // ----- payload -------------------------------------------------------------\n const payload: ReadinessPayload = {\n status,\n timestamp: new Date().toISOString(),\n // service: { name, version, instanceId } // (optional; if you add config later)\n summary,\n checks,\n }\n\n return payload\n }\n\n public async health() {\n const { status, checks, summary } = await this.ready()\n const { timestamp, system, process } = this.live()\n\n return {\n status,\n timestamp,\n system,\n process,\n checks,\n summary,\n } as HealthSummary\n }\n\n /**\n * Accessor for the Express router that exposes /health endpoints.\n */\n public get router() {\n return this._router\n }\n\n /**\n * Symbol-based disposer for use with `using`.\n */\n public [Symbol.dispose]() {\n this.dispose()\n }\n\n /**\n * Dispose of the monitor:\n * - marks as disposed\n * - (future: could also stop all dependency monitors)\n */\n public dispose() {\n // this.isDisposed = true\n for (const dependency of this.dependencies.values()) {\n dependency.dispose()\n }\n }\n}\n","import os from 'node:os'\nimport type { Liveness, Status } from './types'\n\nexport const ping = (): Status => ({ status: 'ok' })\nexport const liveness = (): Liveness => {\n const cpus = os.cpus()\n const total = os.totalmem()\n const free = os.freemem()\n\n return {\n status: 'ok',\n timestamp: new Date().toISOString(),\n system: {\n hostname: os.hostname(),\n platform: os.platform(),\n release: os.release(),\n arch: os.arch(),\n uptime: os.uptime(),\n loadavg: os.loadavg() as [number, number, number],\n totalmem: total,\n freemem: free,\n memUsedRatio: total > 0 ? (total - free) / total : 0,\n cpus: {\n count: cpus.length,\n model: cpus[0]?.model,\n speedMHz: cpus[0]?.speed,\n },\n },\n process: {\n pid: process.pid,\n node: process.versions.node,\n uptime: process.uptime(),\n memory: process.memoryUsage(), // rss, heapTotal, heapUsed, external, arrayBuffers\n },\n }\n}\n"],"mappings":"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","names":["process"]}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sebspark/health-check",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsup
|
|
12
|
+
"build": "tsup src/index.ts --config ./tsup.config.ts",
|
|
13
13
|
"dev": "tsc --watch --noEmit",
|
|
14
14
|
"lint": "biome check .",
|
|
15
15
|
"test": "vitest run --passWithNoTests --coverage",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"typecheck": "vitest --typecheck.only --passWithNoTests"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"express": "5.1.0",
|
|
21
|
-
"express
|
|
20
|
+
"@types/express-serve-static-core": "5.1.0",
|
|
21
|
+
"express": "5.1.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@google-cloud/pubsub": "5.2.0",
|
package/dist/index.d.mts
DELETED
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
|
|
3
|
-
type DependencyStatusValue = 'ok' | 'degraded' | 'error';
|
|
4
|
-
type StatusValue = DependencyStatusValue | 'unknown' | 'draining';
|
|
5
|
-
type Impact = 'critical' | 'non_critical';
|
|
6
|
-
type Mode = 'inline' | 'polled' | 'async';
|
|
7
|
-
type Status = {
|
|
8
|
-
status: StatusValue;
|
|
9
|
-
};
|
|
10
|
-
type System = {
|
|
11
|
-
hostname: string;
|
|
12
|
-
platform: NodeJS.Platform;
|
|
13
|
-
release: string;
|
|
14
|
-
arch: string;
|
|
15
|
-
uptime: number;
|
|
16
|
-
loadavg: [number, number, number];
|
|
17
|
-
totalmem: number;
|
|
18
|
-
freemem: number;
|
|
19
|
-
memUsedRatio: number;
|
|
20
|
-
cpus: {
|
|
21
|
-
count: number;
|
|
22
|
-
model?: string;
|
|
23
|
-
speedMHz?: number;
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
type Process = {
|
|
27
|
-
pid: number;
|
|
28
|
-
node: string;
|
|
29
|
-
uptime: number;
|
|
30
|
-
memory: NodeJS.MemoryUsage;
|
|
31
|
-
};
|
|
32
|
-
type Liveness = Status & {
|
|
33
|
-
timestamp: string;
|
|
34
|
-
system: System;
|
|
35
|
-
process: Process;
|
|
36
|
-
};
|
|
37
|
-
type Freshness = {
|
|
38
|
-
lastChecked: string;
|
|
39
|
-
lastSuccess: string | null;
|
|
40
|
-
};
|
|
41
|
-
type Observed = {
|
|
42
|
-
latencyMs: number | null;
|
|
43
|
-
[k: string]: unknown;
|
|
44
|
-
};
|
|
45
|
-
type CheckError = {
|
|
46
|
-
code: string;
|
|
47
|
-
message: string;
|
|
48
|
-
};
|
|
49
|
-
type DependencyCheck = Status & {
|
|
50
|
-
impact: Impact;
|
|
51
|
-
mode: Mode;
|
|
52
|
-
freshness: Freshness;
|
|
53
|
-
observed?: Observed;
|
|
54
|
-
details?: Record<string, unknown>;
|
|
55
|
-
error?: CheckError | null;
|
|
56
|
-
since?: string | null;
|
|
57
|
-
};
|
|
58
|
-
type ReadinessSummary = {
|
|
59
|
-
critical: {
|
|
60
|
-
ok: number;
|
|
61
|
-
failing: number;
|
|
62
|
-
};
|
|
63
|
-
nonCritical: {
|
|
64
|
-
ok: number;
|
|
65
|
-
degraded: number;
|
|
66
|
-
failing: number;
|
|
67
|
-
};
|
|
68
|
-
degradedReasons: string[];
|
|
69
|
-
};
|
|
70
|
-
type ReadinessPayload = Status & {
|
|
71
|
-
timestamp: string;
|
|
72
|
-
service?: {
|
|
73
|
-
name?: string;
|
|
74
|
-
version?: string;
|
|
75
|
-
instanceId?: string;
|
|
76
|
-
};
|
|
77
|
-
summary: ReadinessSummary;
|
|
78
|
-
checks: Record<string, DependencyCheck>;
|
|
79
|
-
};
|
|
80
|
-
type HealthSummary = Status & Pick<ReadinessPayload, 'summary' | 'checks'> & Pick<Liveness, 'system' | 'process'> & {
|
|
81
|
-
timestamp: string;
|
|
82
|
-
};
|
|
83
|
-
declare class TimeoutError extends Error {
|
|
84
|
-
constructor();
|
|
85
|
-
}
|
|
86
|
-
declare class UnknownError extends Error {
|
|
87
|
-
constructor();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Base configuration shared by all dependency monitor modes.
|
|
92
|
-
*/
|
|
93
|
-
type BaseConfig = {
|
|
94
|
-
/**
|
|
95
|
-
* Importance of the dependency:
|
|
96
|
-
* - `'critical'` → service cannot run without it
|
|
97
|
-
* - `'non_critical'` → service is degraded but still functional if it fails
|
|
98
|
-
*/
|
|
99
|
-
impact: Impact;
|
|
100
|
-
/**
|
|
101
|
-
* Polling interval in milliseconds.
|
|
102
|
-
* - Required for polled and async modes
|
|
103
|
-
* - Must not be set for inline mode
|
|
104
|
-
*/
|
|
105
|
-
pollRate?: number;
|
|
106
|
-
/**
|
|
107
|
-
* Polling interval in milliseconds for failed calls.
|
|
108
|
-
* - Defaults to pollRate for polled and async modes
|
|
109
|
-
* - Must not be set for inline mode
|
|
110
|
-
*/
|
|
111
|
-
retryRate?: number;
|
|
112
|
-
/**
|
|
113
|
-
* Maximum response time in milliseconds considered healthy.
|
|
114
|
-
* - A call resolving within this time window **and** returning `"ok"` → status `"ok"`
|
|
115
|
-
* - A call resolving after this but **before** `timeoutLimitMs` → status `"degraded"`
|
|
116
|
-
*/
|
|
117
|
-
healthyLimitMs: number;
|
|
118
|
-
/**
|
|
119
|
-
* Absolute upper bound in milliseconds; beyond this a call is considered `"error"`.
|
|
120
|
-
* - Inline: the awaited call is raced with a timeout and rejected past this limit
|
|
121
|
-
* - Polled/Async: escalate `unknown` → `degraded` at `healthyLimitMs`, then `"error"` at `timeoutLimitMs`
|
|
122
|
-
* - Any late `"ok"` result after this limit is ignored
|
|
123
|
-
*/
|
|
124
|
-
timeoutLimitMs: number;
|
|
125
|
-
};
|
|
126
|
-
/**
|
|
127
|
-
* Inline mode configuration (no polling).
|
|
128
|
-
* `syncCall` runs **only** when {@link DependencyMonitor.check} is invoked.
|
|
129
|
-
*/
|
|
130
|
-
type SyncInlineConfig = BaseConfig & {
|
|
131
|
-
/** Performs the check immediately when {@link DependencyMonitor.check} is called. */
|
|
132
|
-
syncCall: () => Promise<DependencyStatusValue | Error>;
|
|
133
|
-
asyncCall?: never;
|
|
134
|
-
pollRate?: undefined;
|
|
135
|
-
retryRate?: undefined;
|
|
136
|
-
};
|
|
137
|
-
/**
|
|
138
|
-
* Polled mode configuration.
|
|
139
|
-
* `syncCall` runs on an interval (`pollRate`) and results are cached.
|
|
140
|
-
*/
|
|
141
|
-
type SyncPolledConfig = BaseConfig & {
|
|
142
|
-
/** Performs the check at each poll interval. */
|
|
143
|
-
syncCall: () => Promise<DependencyStatusValue | Error>;
|
|
144
|
-
/** Polling interval in milliseconds. */
|
|
145
|
-
pollRate: number;
|
|
146
|
-
asyncCall?: never;
|
|
147
|
-
};
|
|
148
|
-
/**
|
|
149
|
-
* Async mode configuration.
|
|
150
|
-
* `asyncCall` starts a check and must report via the provided callback.
|
|
151
|
-
*/
|
|
152
|
-
type AsyncConfig = BaseConfig & {
|
|
153
|
-
/**
|
|
154
|
-
* Starts an asynchronous check. Must call `reportResponse` once a result is available.
|
|
155
|
-
* The callback may be invoked with a `DependencyStatusValue` or an `Error`.
|
|
156
|
-
*/
|
|
157
|
-
asyncCall: (reportResponse: (status: DependencyStatusValue | Error) => void | Promise<void>) => void | Promise<void>;
|
|
158
|
-
/** Polling interval in milliseconds. */
|
|
159
|
-
pollRate: number;
|
|
160
|
-
syncCall?: never;
|
|
161
|
-
};
|
|
162
|
-
/**
|
|
163
|
-
* Discriminated configuration for a dependency monitor.
|
|
164
|
-
* - {@link SyncInlineConfig} → inline checks on demand
|
|
165
|
-
* - {@link SyncPolledConfig} → synchronous checks on a fixed interval
|
|
166
|
-
* - {@link AsyncConfig} → asynchronous checks that report back via callback
|
|
167
|
-
*/
|
|
168
|
-
type DependencyMonitorConfig = SyncInlineConfig | SyncPolledConfig | AsyncConfig;
|
|
169
|
-
/**
|
|
170
|
-
* Tracks the health of a single dependency in one of three modes:
|
|
171
|
-
*
|
|
172
|
-
* - **inline**: `syncCall` executed only when {@link check} is called
|
|
173
|
-
* - **polled**: `syncCall` executed on `pollRate`; latest result cached
|
|
174
|
-
* - **async**: `asyncCall` executed on `pollRate`, result returned via callback
|
|
175
|
-
*
|
|
176
|
-
* The monitor:
|
|
177
|
-
* - classifies results using `healthyLimitMs` and `timeoutLimitMs`
|
|
178
|
-
* - records freshness (`lastChecked`, `lastSuccess`)
|
|
179
|
-
* - records observed latency (ms) when applicable
|
|
180
|
-
*
|
|
181
|
-
* Status interpretation:
|
|
182
|
-
* - `"ok"` only if returned `"ok"` **and** latency ≤ `healthyLimitMs`
|
|
183
|
-
* - `"degraded"` if returned `"degraded"` **or** latency > `healthyLimitMs` but ≤ `timeoutLimitMs`
|
|
184
|
-
* - `"error"` if returned `"error"`, an `Error`, or exceeded `timeoutLimitMs`
|
|
185
|
-
* - `"unknown"` is the initial state before first successful/failed result (polled/async)
|
|
186
|
-
*/
|
|
187
|
-
declare class DependencyMonitor {
|
|
188
|
-
/** Immutable configuration for this monitor. */
|
|
189
|
-
private readonly config;
|
|
190
|
-
/** Operational mode determined from the configuration. */
|
|
191
|
-
private readonly mode;
|
|
192
|
-
/** Last computed status (starts as `"unknown"` until first result). */
|
|
193
|
-
private status;
|
|
194
|
-
/** Optional error details from the last failure. */
|
|
195
|
-
private error?;
|
|
196
|
-
/** Last observed metrics (e.g., latency). */
|
|
197
|
-
private observed?;
|
|
198
|
-
/** Freshness timestamps for last check/success. */
|
|
199
|
-
private freshness?;
|
|
200
|
-
/** Indicates whether this monitor has been disposed. */
|
|
201
|
-
private isDisposed;
|
|
202
|
-
/** Internal timer handle for polling/retry. */
|
|
203
|
-
private timeout?;
|
|
204
|
-
/**
|
|
205
|
-
* Create a new {@link DependencyMonitor}.
|
|
206
|
-
* @param config Monitor configuration (inline, polled, or async).
|
|
207
|
-
*/
|
|
208
|
-
constructor(config: DependencyMonitorConfig);
|
|
209
|
-
/** Impact of this dependency (critical/non_critical). */
|
|
210
|
-
get impact(): Impact;
|
|
211
|
-
/**
|
|
212
|
-
* Run one check cycle depending on the configured mode.
|
|
213
|
-
* - Inline: no-op here (runs in {@link check})
|
|
214
|
-
* - Polled/Async: invoked automatically and re-scheduled as needed
|
|
215
|
-
*/
|
|
216
|
-
private doCheck;
|
|
217
|
-
/**
|
|
218
|
-
* Execute a synchronous (inline/polled) check and update internal state.
|
|
219
|
-
* Classification:
|
|
220
|
-
* - OK if `"ok"` and latency ≤ `healthyLimitMs`
|
|
221
|
-
* - Degraded if `"degraded"` or latency between `(healthyLimitMs, timeoutLimitMs]`
|
|
222
|
-
* - Error if `"error"`, `Error`, or timeout exceeded
|
|
223
|
-
*/
|
|
224
|
-
private doSyncCheck;
|
|
225
|
-
/**
|
|
226
|
-
* Execute an asynchronous check (async mode).
|
|
227
|
-
* - Initializes timers to escalate `unknown` → `degraded` → `error`
|
|
228
|
-
* if no callback result arrives within the thresholds.
|
|
229
|
-
* - On callback, clears timers and classifies the result with latency.
|
|
230
|
-
*/
|
|
231
|
-
private doAsyncCheck;
|
|
232
|
-
/**
|
|
233
|
-
* Normalize a successful dependency response into status + metadata.
|
|
234
|
-
* @param response Returned status value or Error from the dependency
|
|
235
|
-
* @param duration Measured latency in milliseconds
|
|
236
|
-
*/
|
|
237
|
-
private handleDependencyResponse;
|
|
238
|
-
/**
|
|
239
|
-
* Normalize a failed dependency result into `"error"` and update metadata.
|
|
240
|
-
* @param error Error thrown/returned or synthesized (e.g., timeout)
|
|
241
|
-
*/
|
|
242
|
-
private handleDependencyError;
|
|
243
|
-
/**
|
|
244
|
-
* Get a snapshot of the dependency’s current health.
|
|
245
|
-
* - Inline mode: executes the `syncCall` (single-flight guarded)
|
|
246
|
-
* - Polled/Async: returns the cached result
|
|
247
|
-
* @throws if the monitor has been disposed
|
|
248
|
-
*/
|
|
249
|
-
check(): Promise<DependencyCheck>;
|
|
250
|
-
/** Symbol-based disposer for use with `using`. */
|
|
251
|
-
[Symbol.dispose](): void;
|
|
252
|
-
/**
|
|
253
|
-
* Dispose the monitor:
|
|
254
|
-
* - Clears any scheduled timers
|
|
255
|
-
* - Prevents further checks from running
|
|
256
|
-
*/
|
|
257
|
-
dispose(): void;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
interface HealthMonitorConfig {
|
|
261
|
-
throttle: number;
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* HealthMonitor wires up standard health check endpoints for an Express app.
|
|
265
|
-
*
|
|
266
|
-
* It provides three endpoints under `/health`:
|
|
267
|
-
* - /ping → lightweight always-OK response (`{ status: "ok" }`)
|
|
268
|
-
* - /live → liveness probe (`{ status, uptime, timestamp }`)
|
|
269
|
-
* - /ready → readiness probe with dependency checks (`{ status, checks }`)
|
|
270
|
-
*
|
|
271
|
-
* Dependencies can be added via {@link addDependency} with a {@link DependencyMonitorConfig}.
|
|
272
|
-
* Each dependency is wrapped in a {@link DependencyMonitor} and tracked for readiness.
|
|
273
|
-
*
|
|
274
|
-
* ### Example
|
|
275
|
-
*
|
|
276
|
-
* ```ts
|
|
277
|
-
* import express from 'express'
|
|
278
|
-
* import { HealthMonitor } from './health-monitor'
|
|
279
|
-
*
|
|
280
|
-
* const app = express()
|
|
281
|
-
* const monitor = new HealthMonitor()
|
|
282
|
-
*
|
|
283
|
-
* monitor.addDependency('db', {
|
|
284
|
-
* impact: 'critical',
|
|
285
|
-
* syncCall: async () => {
|
|
286
|
-
* await db.ping()
|
|
287
|
-
* return 'ok'
|
|
288
|
-
* }
|
|
289
|
-
* })
|
|
290
|
-
*
|
|
291
|
-
* app.use(monitor.router)
|
|
292
|
-
* app.listen(3000)
|
|
293
|
-
* ```
|
|
294
|
-
*/
|
|
295
|
-
declare class HealthMonitor {
|
|
296
|
-
private readonly _router;
|
|
297
|
-
private readonly dependencies;
|
|
298
|
-
/**
|
|
299
|
-
* Create a new HealthMonitor instance with its own router and dependency map.
|
|
300
|
-
*/
|
|
301
|
-
constructor(config?: HealthMonitorConfig);
|
|
302
|
-
/**
|
|
303
|
-
* Register a named dependency to be checked as part of readiness.
|
|
304
|
-
*
|
|
305
|
-
* @param name - Identifier for the dependency (e.g. "postgres", "redis").
|
|
306
|
-
* @param dependency - DependencyMonitor
|
|
307
|
-
* @returns this for chaining
|
|
308
|
-
*
|
|
309
|
-
* @example
|
|
310
|
-
* ```ts
|
|
311
|
-
* monitor.addDependency('redis', new DependencyMonitor({
|
|
312
|
-
* impact: 'critical',
|
|
313
|
-
* pollRate: 5000,
|
|
314
|
-
* syncCall: async () => redis.ping() ? 'ok' : 'error'
|
|
315
|
-
* }))
|
|
316
|
-
* ```
|
|
317
|
-
*/
|
|
318
|
-
addDependency(name: string, dependency: DependencyMonitor): this;
|
|
319
|
-
/**
|
|
320
|
-
* Internal: create the Express router with /health routes.
|
|
321
|
-
*/
|
|
322
|
-
private createRouter;
|
|
323
|
-
/**
|
|
324
|
-
* Static ping check — always returns `{ status: "ok" }`.
|
|
325
|
-
*/
|
|
326
|
-
ping(): Status;
|
|
327
|
-
/**
|
|
328
|
-
* Liveness probe: reports uptime and current timestamp.
|
|
329
|
-
*/
|
|
330
|
-
live(): Liveness;
|
|
331
|
-
/**
|
|
332
|
-
* Readiness probe: runs all registered dependencies in parallel
|
|
333
|
-
* and aggregates their results into an overall status.
|
|
334
|
-
*
|
|
335
|
-
* - Any **critical error** → `"error"`
|
|
336
|
-
* - Else any **critical degraded** or any non-critical degraded/error → `"degraded"`
|
|
337
|
-
* - Else → `"ok"`
|
|
338
|
-
*
|
|
339
|
-
* @returns Object with overall `status` and per-dependency `checks`
|
|
340
|
-
*
|
|
341
|
-
* @example
|
|
342
|
-
* ```json
|
|
343
|
-
* {
|
|
344
|
-
* "status": "ok",
|
|
345
|
-
* "checks": {
|
|
346
|
-
* "postgres": {
|
|
347
|
-
* "status": "ok",
|
|
348
|
-
* "impact": "critical",
|
|
349
|
-
* "mode": "inline",
|
|
350
|
-
* "freshness": {
|
|
351
|
-
* "lastChecked": "2025-08-19T12:00:00Z",
|
|
352
|
-
* "lastSuccess": "2025-08-19T12:00:00Z"
|
|
353
|
-
* },
|
|
354
|
-
* "observed": { "latencyMs": 12 }
|
|
355
|
-
* }
|
|
356
|
-
* }
|
|
357
|
-
* }
|
|
358
|
-
* ```
|
|
359
|
-
*/
|
|
360
|
-
ready(): Promise<ReadinessPayload>;
|
|
361
|
-
health(): Promise<HealthSummary>;
|
|
362
|
-
/**
|
|
363
|
-
* Accessor for the Express router that exposes /health endpoints.
|
|
364
|
-
*/
|
|
365
|
-
get router(): Router;
|
|
366
|
-
/**
|
|
367
|
-
* Symbol-based disposer for use with `using`.
|
|
368
|
-
*/
|
|
369
|
-
[Symbol.dispose](): void;
|
|
370
|
-
/**
|
|
371
|
-
* Dispose of the monitor:
|
|
372
|
-
* - marks as disposed
|
|
373
|
-
* - (future: could also stop all dependency monitors)
|
|
374
|
-
*/
|
|
375
|
-
dispose(): void;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export { type AsyncConfig, type CheckError, type DependencyCheck, DependencyMonitor, type DependencyMonitorConfig, type DependencyStatusValue, type Freshness, HealthMonitor, type HealthMonitorConfig, type HealthSummary, type Impact, type Liveness, type Mode, type Observed, type Process, type ReadinessPayload, type ReadinessSummary, type Status, type StatusValue, type SyncInlineConfig, type SyncPolledConfig, type System, TimeoutError, UnknownError };
|
package/dist/index.mjs
DELETED
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
// src/dependency-monitor.ts
|
|
2
|
-
import { performance } from "perf_hooks";
|
|
3
|
-
|
|
4
|
-
// src/types.ts
|
|
5
|
-
var TimeoutError = class extends Error {
|
|
6
|
-
constructor() {
|
|
7
|
-
super("timeout");
|
|
8
|
-
this.name = "TimeoutError";
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
var UnknownError = class extends Error {
|
|
12
|
-
constructor() {
|
|
13
|
-
super("unknown");
|
|
14
|
-
this.name = "UnknownError";
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// src/timing.ts
|
|
19
|
-
var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
-
var runTimeoutTimer = async (ms) => {
|
|
21
|
-
await wait(ms);
|
|
22
|
-
throw new TimeoutError();
|
|
23
|
-
};
|
|
24
|
-
var runAgainstTimeout = (promise, ms) => {
|
|
25
|
-
if (ms > 0) {
|
|
26
|
-
return Promise.race([promise, runTimeoutTimer(ms)]);
|
|
27
|
-
}
|
|
28
|
-
return promise;
|
|
29
|
-
};
|
|
30
|
-
var throttle = (fn, ms) => {
|
|
31
|
-
let current = null;
|
|
32
|
-
let clearHandle = null;
|
|
33
|
-
const wrapped = (...args) => {
|
|
34
|
-
if (!current) {
|
|
35
|
-
current = fn(...args);
|
|
36
|
-
if (clearHandle) {
|
|
37
|
-
clearTimeout(clearHandle);
|
|
38
|
-
clearHandle = null;
|
|
39
|
-
}
|
|
40
|
-
current.finally(() => {
|
|
41
|
-
if (ms > 0) {
|
|
42
|
-
clearHandle = setTimeout(() => {
|
|
43
|
-
current = null;
|
|
44
|
-
clearHandle = null;
|
|
45
|
-
}, ms);
|
|
46
|
-
} else {
|
|
47
|
-
current = null;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
return current;
|
|
52
|
-
};
|
|
53
|
-
return wrapped;
|
|
54
|
-
};
|
|
55
|
-
var singleFlight = (fn) => throttle(fn, 0);
|
|
56
|
-
|
|
57
|
-
// src/dependency-monitor.ts
|
|
58
|
-
var DependencyMonitor = class {
|
|
59
|
-
/** Immutable configuration for this monitor. */
|
|
60
|
-
config;
|
|
61
|
-
/** Operational mode determined from the configuration. */
|
|
62
|
-
mode;
|
|
63
|
-
/** Last computed status (starts as `"unknown"` until first result). */
|
|
64
|
-
status;
|
|
65
|
-
/** Optional error details from the last failure. */
|
|
66
|
-
error;
|
|
67
|
-
/** Last observed metrics (e.g., latency). */
|
|
68
|
-
observed;
|
|
69
|
-
/** Freshness timestamps for last check/success. */
|
|
70
|
-
freshness;
|
|
71
|
-
/** Indicates whether this monitor has been disposed. */
|
|
72
|
-
isDisposed = false;
|
|
73
|
-
/** Internal timer handle for polling/retry. */
|
|
74
|
-
timeout;
|
|
75
|
-
/**
|
|
76
|
-
* Create a new {@link DependencyMonitor}.
|
|
77
|
-
* @param config Monitor configuration (inline, polled, or async).
|
|
78
|
-
*/
|
|
79
|
-
constructor(config) {
|
|
80
|
-
this.config = config;
|
|
81
|
-
this.mode = config.asyncCall ? "async" : config.pollRate ? "polled" : "inline";
|
|
82
|
-
this.status = "unknown";
|
|
83
|
-
if (this.mode === "inline") {
|
|
84
|
-
this.check = singleFlight(this.check.bind(this));
|
|
85
|
-
}
|
|
86
|
-
if (this.mode !== "inline") {
|
|
87
|
-
this.doCheck();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
/** Impact of this dependency (critical/non_critical). */
|
|
91
|
-
get impact() {
|
|
92
|
-
return this.config.impact;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Run one check cycle depending on the configured mode.
|
|
96
|
-
* - Inline: no-op here (runs in {@link check})
|
|
97
|
-
* - Polled/Async: invoked automatically and re-scheduled as needed
|
|
98
|
-
*/
|
|
99
|
-
async doCheck() {
|
|
100
|
-
if (this.timeout) {
|
|
101
|
-
clearTimeout(this.timeout);
|
|
102
|
-
}
|
|
103
|
-
if (this.isDisposed) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (this.config.syncCall) {
|
|
107
|
-
await this.doSyncCheck();
|
|
108
|
-
} else {
|
|
109
|
-
this.doAsyncCheck();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Execute a synchronous (inline/polled) check and update internal state.
|
|
114
|
-
* Classification:
|
|
115
|
-
* - OK if `"ok"` and latency ≤ `healthyLimitMs`
|
|
116
|
-
* - Degraded if `"degraded"` or latency between `(healthyLimitMs, timeoutLimitMs]`
|
|
117
|
-
* - Error if `"error"`, `Error`, or timeout exceeded
|
|
118
|
-
*/
|
|
119
|
-
async doSyncCheck() {
|
|
120
|
-
const start = performance.now();
|
|
121
|
-
const { syncCall, timeoutLimitMs } = this.config;
|
|
122
|
-
try {
|
|
123
|
-
const response = await runAgainstTimeout(syncCall(), timeoutLimitMs);
|
|
124
|
-
const duration = performance.now() - start;
|
|
125
|
-
this.handleDependencyResponse(response, duration);
|
|
126
|
-
} catch (err) {
|
|
127
|
-
this.handleDependencyError(err);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Execute an asynchronous check (async mode).
|
|
132
|
-
* - Initializes timers to escalate `unknown` → `degraded` → `error`
|
|
133
|
-
* if no callback result arrives within the thresholds.
|
|
134
|
-
* - On callback, clears timers and classifies the result with latency.
|
|
135
|
-
*/
|
|
136
|
-
async doAsyncCheck() {
|
|
137
|
-
const { asyncCall, healthyLimitMs, timeoutLimitMs } = this.config;
|
|
138
|
-
const start = performance.now();
|
|
139
|
-
let callActive = true;
|
|
140
|
-
let healthTimeout;
|
|
141
|
-
let serviceTimeout;
|
|
142
|
-
asyncCall((response) => {
|
|
143
|
-
if (!callActive) return;
|
|
144
|
-
callActive = false;
|
|
145
|
-
clearTimeout(healthTimeout);
|
|
146
|
-
clearTimeout(serviceTimeout);
|
|
147
|
-
const duration = performance.now() - start;
|
|
148
|
-
this.handleDependencyResponse(response, duration);
|
|
149
|
-
});
|
|
150
|
-
healthTimeout = setTimeout(() => {
|
|
151
|
-
if (callActive) this.status = "degraded";
|
|
152
|
-
}, healthyLimitMs);
|
|
153
|
-
serviceTimeout = setTimeout(() => {
|
|
154
|
-
if (!callActive) return;
|
|
155
|
-
callActive = false;
|
|
156
|
-
clearTimeout(healthTimeout);
|
|
157
|
-
this.handleDependencyError(new TimeoutError());
|
|
158
|
-
}, timeoutLimitMs);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Normalize a successful dependency response into status + metadata.
|
|
162
|
-
* @param response Returned status value or Error from the dependency
|
|
163
|
-
* @param duration Measured latency in milliseconds
|
|
164
|
-
*/
|
|
165
|
-
handleDependencyResponse(response, duration) {
|
|
166
|
-
if (response instanceof Error) {
|
|
167
|
-
return this.handleDependencyError(response);
|
|
168
|
-
}
|
|
169
|
-
if (response === "error") {
|
|
170
|
-
return this.handleDependencyError(new UnknownError());
|
|
171
|
-
}
|
|
172
|
-
this.error = void 0;
|
|
173
|
-
this.status = response === "ok" && duration <= this.config.healthyLimitMs ? "ok" : "degraded";
|
|
174
|
-
const end = /* @__PURE__ */ new Date();
|
|
175
|
-
this.freshness = {
|
|
176
|
-
lastChecked: end.toISOString(),
|
|
177
|
-
lastSuccess: end.toISOString()
|
|
178
|
-
};
|
|
179
|
-
this.observed = { latencyMs: duration };
|
|
180
|
-
if (this.config.pollRate && !this.isDisposed) {
|
|
181
|
-
const delay = this.config.pollRate;
|
|
182
|
-
this.timeout = setTimeout(() => this.doCheck(), delay);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Normalize a failed dependency result into `"error"` and update metadata.
|
|
187
|
-
* @param error Error thrown/returned or synthesized (e.g., timeout)
|
|
188
|
-
*/
|
|
189
|
-
handleDependencyError(error) {
|
|
190
|
-
this.status = "error";
|
|
191
|
-
this.observed = void 0;
|
|
192
|
-
this.error = {
|
|
193
|
-
code: error.code || "UNKNOWN",
|
|
194
|
-
message: error.message
|
|
195
|
-
};
|
|
196
|
-
const end = /* @__PURE__ */ new Date();
|
|
197
|
-
if (this.freshness) {
|
|
198
|
-
this.freshness.lastChecked = end.toISOString();
|
|
199
|
-
} else {
|
|
200
|
-
this.freshness = { lastChecked: end.toISOString(), lastSuccess: null };
|
|
201
|
-
}
|
|
202
|
-
if ((this.config.retryRate || this.config.pollRate) && !this.isDisposed) {
|
|
203
|
-
const delay = this.config.retryRate || this.config.pollRate;
|
|
204
|
-
this.timeout = setTimeout(() => this.doCheck(), delay);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Get a snapshot of the dependency’s current health.
|
|
209
|
-
* - Inline mode: executes the `syncCall` (single-flight guarded)
|
|
210
|
-
* - Polled/Async: returns the cached result
|
|
211
|
-
* @throws if the monitor has been disposed
|
|
212
|
-
*/
|
|
213
|
-
async check() {
|
|
214
|
-
if (this.isDisposed) {
|
|
215
|
-
throw new Error("DependencyMonitor has been disposed");
|
|
216
|
-
}
|
|
217
|
-
if (this.mode === "inline") {
|
|
218
|
-
await this.doCheck();
|
|
219
|
-
}
|
|
220
|
-
const result = {
|
|
221
|
-
status: this.status,
|
|
222
|
-
impact: this.config.impact,
|
|
223
|
-
mode: this.mode,
|
|
224
|
-
freshness: this.freshness,
|
|
225
|
-
observed: this.observed,
|
|
226
|
-
error: this.error
|
|
227
|
-
};
|
|
228
|
-
return result;
|
|
229
|
-
}
|
|
230
|
-
/** Symbol-based disposer for use with `using`. */
|
|
231
|
-
[Symbol.dispose]() {
|
|
232
|
-
this.dispose();
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Dispose the monitor:
|
|
236
|
-
* - Clears any scheduled timers
|
|
237
|
-
* - Prevents further checks from running
|
|
238
|
-
*/
|
|
239
|
-
dispose() {
|
|
240
|
-
this.isDisposed = true;
|
|
241
|
-
if (this.timeout) {
|
|
242
|
-
clearTimeout(this.timeout);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// src/health-monitor.ts
|
|
248
|
-
import { Router } from "express";
|
|
249
|
-
|
|
250
|
-
// src/static-checks.ts
|
|
251
|
-
import os from "os";
|
|
252
|
-
var ping = () => ({ status: "ok" });
|
|
253
|
-
var liveness = () => {
|
|
254
|
-
const cpus = os.cpus();
|
|
255
|
-
const total = os.totalmem();
|
|
256
|
-
const free = os.freemem();
|
|
257
|
-
return {
|
|
258
|
-
status: "ok",
|
|
259
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
260
|
-
system: {
|
|
261
|
-
hostname: os.hostname(),
|
|
262
|
-
platform: os.platform(),
|
|
263
|
-
release: os.release(),
|
|
264
|
-
arch: os.arch(),
|
|
265
|
-
uptime: os.uptime(),
|
|
266
|
-
loadavg: os.loadavg(),
|
|
267
|
-
totalmem: total,
|
|
268
|
-
freemem: free,
|
|
269
|
-
memUsedRatio: total > 0 ? (total - free) / total : 0,
|
|
270
|
-
cpus: {
|
|
271
|
-
count: cpus.length,
|
|
272
|
-
model: cpus[0]?.model,
|
|
273
|
-
speedMHz: cpus[0]?.speed
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
process: {
|
|
277
|
-
pid: process.pid,
|
|
278
|
-
node: process.versions.node,
|
|
279
|
-
uptime: process.uptime(),
|
|
280
|
-
memory: process.memoryUsage()
|
|
281
|
-
// rss, heapTotal, heapUsed, external, arrayBuffers
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
// src/health-monitor.ts
|
|
287
|
-
var HealthMonitor = class {
|
|
288
|
-
_router;
|
|
289
|
-
dependencies;
|
|
290
|
-
// private isDisposed = false
|
|
291
|
-
/**
|
|
292
|
-
* Create a new HealthMonitor instance with its own router and dependency map.
|
|
293
|
-
*/
|
|
294
|
-
constructor(config) {
|
|
295
|
-
this._router = this.createRouter();
|
|
296
|
-
this.dependencies = /* @__PURE__ */ new Map();
|
|
297
|
-
const thr = config ? config.throttle : 10;
|
|
298
|
-
if (thr > 0) {
|
|
299
|
-
this.ready = throttle(this.ready.bind(this), thr);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Register a named dependency to be checked as part of readiness.
|
|
304
|
-
*
|
|
305
|
-
* @param name - Identifier for the dependency (e.g. "postgres", "redis").
|
|
306
|
-
* @param dependency - DependencyMonitor
|
|
307
|
-
* @returns this for chaining
|
|
308
|
-
*
|
|
309
|
-
* @example
|
|
310
|
-
* ```ts
|
|
311
|
-
* monitor.addDependency('redis', new DependencyMonitor({
|
|
312
|
-
* impact: 'critical',
|
|
313
|
-
* pollRate: 5000,
|
|
314
|
-
* syncCall: async () => redis.ping() ? 'ok' : 'error'
|
|
315
|
-
* }))
|
|
316
|
-
* ```
|
|
317
|
-
*/
|
|
318
|
-
addDependency(name, dependency) {
|
|
319
|
-
this.dependencies.set(name, dependency);
|
|
320
|
-
return this;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Internal: create the Express router with /health routes.
|
|
324
|
-
*/
|
|
325
|
-
createRouter() {
|
|
326
|
-
const router = Router();
|
|
327
|
-
router.get("/health", async (_req, res, next) => {
|
|
328
|
-
try {
|
|
329
|
-
const health = await this.health();
|
|
330
|
-
res.status(200).json(health);
|
|
331
|
-
} catch (err) {
|
|
332
|
-
next?.(err);
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
router.get("/health/ping", (_req, res, next) => {
|
|
336
|
-
try {
|
|
337
|
-
res.status(200).json(this.ping());
|
|
338
|
-
} catch (err) {
|
|
339
|
-
next?.(err);
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
router.get("/health/live", (_req, res, next) => {
|
|
343
|
-
try {
|
|
344
|
-
res.status(200).json(this.live());
|
|
345
|
-
} catch (err) {
|
|
346
|
-
next?.(err);
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
router.get("/health/ready", async (_req, res, next) => {
|
|
350
|
-
try {
|
|
351
|
-
const readiness = await this.ready();
|
|
352
|
-
res.status(200).json(readiness);
|
|
353
|
-
} catch (err) {
|
|
354
|
-
next?.(err);
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
const errorHandler = (err, _req, res, _next) => {
|
|
358
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
359
|
-
res.status(500).json({
|
|
360
|
-
status: "error",
|
|
361
|
-
error: { message }
|
|
362
|
-
});
|
|
363
|
-
};
|
|
364
|
-
router.use(errorHandler);
|
|
365
|
-
return router;
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Static ping check — always returns `{ status: "ok" }`.
|
|
369
|
-
*/
|
|
370
|
-
ping() {
|
|
371
|
-
return ping();
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Liveness probe: reports uptime and current timestamp.
|
|
375
|
-
*/
|
|
376
|
-
live() {
|
|
377
|
-
return liveness();
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Readiness probe: runs all registered dependencies in parallel
|
|
381
|
-
* and aggregates their results into an overall status.
|
|
382
|
-
*
|
|
383
|
-
* - Any **critical error** → `"error"`
|
|
384
|
-
* - Else any **critical degraded** or any non-critical degraded/error → `"degraded"`
|
|
385
|
-
* - Else → `"ok"`
|
|
386
|
-
*
|
|
387
|
-
* @returns Object with overall `status` and per-dependency `checks`
|
|
388
|
-
*
|
|
389
|
-
* @example
|
|
390
|
-
* ```json
|
|
391
|
-
* {
|
|
392
|
-
* "status": "ok",
|
|
393
|
-
* "checks": {
|
|
394
|
-
* "postgres": {
|
|
395
|
-
* "status": "ok",
|
|
396
|
-
* "impact": "critical",
|
|
397
|
-
* "mode": "inline",
|
|
398
|
-
* "freshness": {
|
|
399
|
-
* "lastChecked": "2025-08-19T12:00:00Z",
|
|
400
|
-
* "lastSuccess": "2025-08-19T12:00:00Z"
|
|
401
|
-
* },
|
|
402
|
-
* "observed": { "latencyMs": 12 }
|
|
403
|
-
* }
|
|
404
|
-
* }
|
|
405
|
-
* }
|
|
406
|
-
* ```
|
|
407
|
-
*/
|
|
408
|
-
async ready() {
|
|
409
|
-
const entries = Array.from(this.dependencies.entries());
|
|
410
|
-
const settled = await Promise.allSettled(
|
|
411
|
-
entries.map(async ([name, monitor]) => {
|
|
412
|
-
const check = await monitor.check();
|
|
413
|
-
return [name, check];
|
|
414
|
-
})
|
|
415
|
-
);
|
|
416
|
-
const checks = {};
|
|
417
|
-
for (let i = 0; i < settled.length; i++) {
|
|
418
|
-
const s = settled[i];
|
|
419
|
-
const [name] = entries[i];
|
|
420
|
-
if (s.status === "fulfilled") {
|
|
421
|
-
const [, check] = s.value;
|
|
422
|
-
checks[name] = check;
|
|
423
|
-
} else {
|
|
424
|
-
checks[name] = {
|
|
425
|
-
status: "error",
|
|
426
|
-
impact: this.dependencies.get(name)?.impact,
|
|
427
|
-
mode: "polled",
|
|
428
|
-
freshness: {
|
|
429
|
-
lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
|
|
430
|
-
lastSuccess: null
|
|
431
|
-
},
|
|
432
|
-
observed: void 0,
|
|
433
|
-
error: {
|
|
434
|
-
code: "CHECK_FAILED",
|
|
435
|
-
message: String(s.reason ?? "unknown error")
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
let criticalOk = 0;
|
|
441
|
-
let criticalFailing = 0;
|
|
442
|
-
let nonCritOk = 0;
|
|
443
|
-
let nonCritDegraded = 0;
|
|
444
|
-
let nonCritFailing = 0;
|
|
445
|
-
const degradedReasons = [];
|
|
446
|
-
for (const [name, c] of Object.entries(checks)) {
|
|
447
|
-
const isCritical = c.impact === "critical";
|
|
448
|
-
if (c.status === "ok") {
|
|
449
|
-
if (isCritical) criticalOk++;
|
|
450
|
-
else nonCritOk++;
|
|
451
|
-
} else if (c.status === "degraded") {
|
|
452
|
-
if (isCritical)
|
|
453
|
-
criticalFailing++;
|
|
454
|
-
else nonCritDegraded++;
|
|
455
|
-
degradedReasons.push(`${name}:degraded`);
|
|
456
|
-
} else {
|
|
457
|
-
if (isCritical) criticalFailing++;
|
|
458
|
-
else nonCritFailing++;
|
|
459
|
-
degradedReasons.push(`${name}:${c.error?.code ?? "error"}`);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
const summary = {
|
|
463
|
-
critical: { ok: criticalOk, failing: criticalFailing },
|
|
464
|
-
nonCritical: {
|
|
465
|
-
ok: nonCritOk,
|
|
466
|
-
degraded: nonCritDegraded,
|
|
467
|
-
failing: nonCritFailing
|
|
468
|
-
},
|
|
469
|
-
degradedReasons
|
|
470
|
-
};
|
|
471
|
-
let status = "ok";
|
|
472
|
-
const values = Object.values(checks);
|
|
473
|
-
const anyCriticalError = values.some(
|
|
474
|
-
(c) => c.impact === "critical" && c.status === "error"
|
|
475
|
-
);
|
|
476
|
-
const anyDegradedOrError = values.some(
|
|
477
|
-
(c) => c.status === "degraded" || c.status === "error"
|
|
478
|
-
);
|
|
479
|
-
const anyCriticalDegraded = values.some(
|
|
480
|
-
(c) => c.impact === "critical" && c.status === "degraded"
|
|
481
|
-
);
|
|
482
|
-
if (anyCriticalError) status = "error";
|
|
483
|
-
else if (anyCriticalDegraded || anyDegradedOrError) status = "degraded";
|
|
484
|
-
const payload = {
|
|
485
|
-
status,
|
|
486
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
487
|
-
// service: { name, version, instanceId } // (optional; if you add config later)
|
|
488
|
-
summary,
|
|
489
|
-
checks
|
|
490
|
-
};
|
|
491
|
-
return payload;
|
|
492
|
-
}
|
|
493
|
-
async health() {
|
|
494
|
-
const { status, checks, summary } = await this.ready();
|
|
495
|
-
const { timestamp, system, process: process2 } = this.live();
|
|
496
|
-
return {
|
|
497
|
-
status,
|
|
498
|
-
timestamp,
|
|
499
|
-
system,
|
|
500
|
-
process: process2,
|
|
501
|
-
checks,
|
|
502
|
-
summary
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Accessor for the Express router that exposes /health endpoints.
|
|
507
|
-
*/
|
|
508
|
-
get router() {
|
|
509
|
-
return this._router;
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Symbol-based disposer for use with `using`.
|
|
513
|
-
*/
|
|
514
|
-
[Symbol.dispose]() {
|
|
515
|
-
this.dispose();
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Dispose of the monitor:
|
|
519
|
-
* - marks as disposed
|
|
520
|
-
* - (future: could also stop all dependency monitors)
|
|
521
|
-
*/
|
|
522
|
-
dispose() {
|
|
523
|
-
for (const dependency of this.dependencies.values()) {
|
|
524
|
-
dependency.dispose();
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
export {
|
|
529
|
-
DependencyMonitor,
|
|
530
|
-
HealthMonitor,
|
|
531
|
-
TimeoutError,
|
|
532
|
-
UnknownError
|
|
533
|
-
};
|