UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

133 lines 4.65 kB
/** * Health check handler * Health check handler for Beignet server adapters. */ const DEFAULT_HEALTH_TIMEOUT_MS = 2000; function errorMessage(error) { return error instanceof Error ? error.message : String(error); } function assertTimeout(timeoutMs) { if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) { throw new Error("Health check timeoutMs must be a positive integer."); } } function normalizeHealthDetail(result) { if (typeof result === "boolean") return { ok: result }; return result ?? { ok: true }; } function redactHealthDetail(detail, includeErrorDetails) { if (includeErrorDetails || detail.ok || detail.message === undefined) { return detail; } return { ...detail, message: "Health check failed", }; } function withTimeout(promise, timeoutMs, name) { let timeout; const timeoutPromise = new Promise((_, reject) => { timeout = setTimeout(() => { reject(new Error(`Health check "${name}" did not complete within ${timeoutMs}ms.`)); }, timeoutMs); }); return Promise.race([promise, timeoutPromise]).finally(() => { if (timeout) clearTimeout(timeout); }); } /** * Run named dependency health checks in parallel and aggregate the result. * * This is intended for app-owned readiness endpoints. Checks should be cheap, * bounded, non-mutating probes such as `SELECT 1`, Redis `PING`, or provider * health endpoints. Do not start workers, drains, migrations, or polling loops * from readiness checks. */ export async function runHealthChecks(ports, checks, options = {}) { const timeoutMs = options.timeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS; assertTimeout(timeoutMs); const includeErrorDetails = options.includeErrorDetails ?? true; const entries = await Promise.all(Object.entries(checks).map(async ([name, check]) => { const startedAt = Date.now(); try { const detail = redactHealthDetail(normalizeHealthDetail(await withTimeout(Promise.resolve(check(ports)), timeoutMs, name)), includeErrorDetails); return [ name, { ...detail, durationMs: Date.now() - startedAt, }, ]; } catch (error) { return [ name, { ok: false, message: includeErrorDetails ? errorMessage(error) : "Health check failed", durationMs: Date.now() - startedAt, }, ]; } })); const details = Object.fromEntries(entries); return { ok: Object.values(details).every((detail) => detail.ok), details, }; } /** * Create a framework-neutral health check handler. * * The returned handler reports 200 when healthy and 503 when unhealthy. Thrown * health check errors include details in development/test and use a generic * message in production. */ export function createHealthHandler(ports, healthConfig, env) { return async (_req) => { let result; if (healthConfig?.check) { try { result = await healthConfig.check(ports); } catch (error) { // Health check function threw - treat as unhealthy // Only include error details in development/test to avoid leaking sensitive info const includeErrorDetails = env === "development" || env === "test"; result = { ok: false, details: { error: { ok: false, message: includeErrorDetails ? error instanceof Error ? error.message : String(error) : "Health check failed", }, }, }; } } else if (healthConfig?.checks) { result = await runHealthChecks(ports, healthConfig.checks, { timeoutMs: healthConfig.timeoutMs, includeErrorDetails: env !== "production", }); } else { result = { ok: true }; } const status = result.ok ? 200 : 503; return { status, body: result, headers: { "Content-Type": "application/json" }, }; }; } //# sourceMappingURL=health.js.map