Skip to content

health

import {
summarizeHealth,
writeHealthSummary,
readHealthSummary,
healthIssueCount,
} from "louise-toolkit/health";

The site-health co-pilot’s data layer: one owner-facing snapshot composed from primitives the toolkit already has. Binding: a KV namespace. No required peers.

The three inputs have very different costs, and the split follows from that:

  • Broken links come from a crawl—network, seconds, driven by a Cron Trigger. Far too slow for a dashboard request.
  • Missing alt and SEO gaps are cheap COUNTs the site computes at scan time.

So a scheduled scan assembles everything and writes it; the dashboard only ever reads. That is why the module is a summarize/write/read triple rather than a “get health” call.

function summarizeHealth(input: HealthInput): HealthSummary;
function writeHealthSummary(kv, summary, opts?: { key?; ttlSeconds? }): Promise<void>;
function readHealthSummary(kv, key?): Promise<HealthSummary | null>;
// in a scheduled handler
const broken = await checkLinks({ base, paths });
await writeHealthSummary(
env.HEALTH_KV,
summarizeHealth({ brokenLinks: broken, missingAlt, seoGaps }),
);

Counts are guarded to non-negative integers, so a bad input can’t skew the traffic light. now is injectable for deterministic tests.

brokenLinkDetails is capped at MAX_BROKEN_LINK_DETAILS (50)—the counts stay exact, the details are a sample for a list view, so one badly broken deploy can’t bloat the stored blob.

Omit ttlSeconds to keep the summary until the next scan overwrites it. A stale snapshot is more useful than none, and checkedAt tells the dashboard how old it is.

readHealthSummary returns null for both “nothing stored” and “stored blob is unparseable”—a corrupt value degrades to no-data rather than throwing inside a dashboard request.

HealthSummary.cwv holds a CwvSummary once a scan adds one. Absent means “not measured yet” and the panel says so—distinct from measured-and-poor, which is a real result.

function healthIssueCount(summary: HealthSummary): number;

The “N things need attention” number: broken links + missing alt + SEO gaps. CWV is deliberately not in it—a slow LCP is not a countable defect the way a 404 is, and adding it would make the number jump for something you can’t fix by editing one page.

HealthSummary is shape-compatible with overview.health (the extra detail field is ignored there), so the overview route can return a stored summary directly rather than re-mapping it.

HealthSummary, HealthInput, HealthKV. Constants: HEALTH_KV_KEY ("louise:health:summary"), MAX_BROKEN_LINK_DETAILS.

HealthKV is structural—get/put—so a real KVNamespace satisfies it without this module importing Workers types.