@beignet/core
Version:
Core framework primitives for Beignet
133 lines • 4.65 kB
JavaScript
/**
* 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