Skip to the content

Capsid menu

app/routes/api.health.ts

118 lines and 5 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of api.health.ts
FunctionLinesComplexityCoveredCRAPMutation
healthJson26 to 312No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
loader37 to 1187No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:91>91 to 911No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:92>92 to 921No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
action125 to 1311No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code

Imports

It imports app/lib/backup/age.server.ts, app/lib/client-ip.ts, app/lib/context.ts, app/lib/error-message.mjs, app/lib/health/checks.server.ts, app/lib/health/snapshot.server.ts, app/lib/health/verdicts.mjs, app/lib/rate-limit.mjs. No module of this repo imports it.

Source

/**
 * `no-store` is load bearing: a health check served from cache is not a health check. Stated here
 * although workers/app.ts defaults it, because that default exists for forgotten headers.
 */

import { clientIp } from "~/lib/client-ip";
import { getEnv } from "~/lib/context";
import { limitHit } from "~/lib/rate-limit.mjs";
import { runHealthChecks } from "~/lib/health/checks.server";
import { publicHealthBody } from "~/lib/health/verdicts.mjs";
import { writeHealthSnapshot } from "~/lib/health/snapshot.server";
import { backupAge } from "~/lib/backup/age.server";

import type { Route } from "./+types/api.health";
import { errorMessage } from "~/lib/error-message.mjs";

const HEALTH_RATE_LIMIT = 20;
const HEALTH_RATE_PERIOD_SECONDS = 60;

/** A cacheable 503 would keep reporting a failure after the site recovered. */
const HEALTH_HEADERS = {
  "Cache-Control": "no-store",
  "Content-Type": "application/json; charset=utf-8",
};

function healthJson(body: unknown, status: number, extra: HeadersInit = {}): Response {
  /* `Headers` rather than a spread, so a caller cannot shadow `Cache-Control` with a different case. */
  const headers = new Headers(HEALTH_HEADERS);
  for (const [name, value] of new Headers(extra)) headers.set(name, value);
  return new Response(JSON.stringify(body), { status, headers });
}

/**
 * The status code is the alert: `curl --fail` reads a 200 carrying `{"ok": false}` as health. The
 * body carries names and booleans only: details include row counts and an R2 key, and this is public.
 */
export async function loader({ request, context }: Route.LoaderArgs) {
  const env = getEnv(context);

  /*
   * First because everything below costs: five checks against D1, R2 and AI Search, unauthenticated.
   * Without the limiter this does not serve.
   */
  /*
   * A limiter that threw is not a limiter that said yes, so 503 rather than fail open. Neither name is
   * in `REPAIRABLE`, so a broken limiter alerts rather than firing a rebuild.
   */
  let verdict: Awaited<ReturnType<typeof limitHit>>;
  try {
    verdict = await limitHit(
      env,
      `health:${clientIp(request)}`,
      HEALTH_RATE_LIMIT,
      HEALTH_RATE_PERIOD_SECONDS,
    );
  } catch (error) {
    // Logged by name: the wire body carries names only, so this line is the whole record of why.
    console.error(
      JSON.stringify({
        alert: "health-rate-limiter-threw",
        error: errorMessage(error),
      }),
    );
    return healthJson({ ok: false, checks: [{ name: "rate-limiter-failed", ok: false }] }, 503);
  }

  if (verdict.status === "unavailable") {
    return healthJson(
      { ok: false, checks: [{ name: "rate-limiter-unavailable", ok: false }] },
      503,
    );
  }
  if (verdict.status === "limited") {
    return healthJson(
      { ok: false, checks: [{ name: "rate-limited", ok: false }] },
      429,
      { "Retry-After": String(verdict.retryAfterSeconds) },
    );
  }

  try {
    const run = await runHealthChecks(env);
    const body = publicHealthBody(run);

    if (!body.ok) {
      // `invocation_logs: false` drops the automatic per-request record, not custom logs, so the detail
      // the wire omits is still recoverable.
      console.error(
        JSON.stringify({
          alert: "health-check-failed",
          failed: run.failed.map((c) => c.name),
          detail: run.failed.map((c) => c.detail),
        }),
      );
    }

    /*
     * Written for both verdicts, or the home tile would keep showing the last good answer while the site
     * failed. The failure path writes nothing, so the tile ages into `stale`.
     */
    await writeHealthSnapshot(env, body, new Date().toISOString());

    /*
     * Reported beside the checks and NOT one of them: a stale dump must not turn the verdict red,
     * because ship refuses unless this is ok and the first deploy that adds the dump has none yet.
     * Two fields and no message, since this is public; the watchdog throws when the dump fails.
     */
    return healthJson({ ...body, backup: await backupAge(env) }, body.ok ? 200 : 503);
  } catch (error) {
    console.error(
      JSON.stringify({
        alert: "health-run-threw",
        error: errorMessage(error),
      }),
    );
    return healthJson({ ok: false, checks: [{ name: "health-run", ok: false }] }, 503);
  }
}

/**
 * React Router's default 405 has no `Allow` header and a body that says the server broke. No rate
 * limit: metering costs more than the refusal. `Allow: GET` only, because the platform answers HEAD
 * by running the loader.
 */
export async function action() {
  return healthJson(
    { ok: false, checks: [{ name: "method-not-allowed", ok: false }] },
    405,
    { Allow: "GET" },
  );
}