Appearance
Edge internals
Chapter 14. Chapter 8 introduced the edge from outside. This is its inside — tenant resolution, the read-survival cache flow, and the circuit breaker, in code.
The line-level mechanics of the edge. Two layers: packages/edge-core is the portable, platform-agnostic library (nothing Cloudflare-specific — adapters inject KV, cache, fetch, dataset); apps/edge/src/worker.ts is the Cloudflare adapter that wires Workers KV, caches.default, neon(), and Analytics Engine into it.
Request orchestration — worker.ts
One Hono app. app.onError(() => storeUnavailable()) turns any unhandled throw into a branded 503 (never a raw 500). A use('*') middleware wraps every request:
- reqId —
sanitizeReqId(header('x-request-id')) ?? crypto.randomUUID()(adopt a validated client id, else mint). - finally-log — logs in a
finally(so a downstream throw is still recorded):{ tenant, status, stale, ms, path }wherestatus = threw ? 503 : res.statusandpathis pathname only. Then one Analytics Engine metric point.
The main all('*') route: block /__* (404) → resolve tenant → 404 if none → 503 if no ORIGIN_URL → fetchViaOrigin → publicHeaders. The edge never renders — no origin ⇒ 503.
Tenant resolve — tenant-resolve.ts
lookupTenant(host, kv, dbQuery) is KV-first, and "not found" is two different states:
| KV read | Meaning | Action |
|---|---|---|
JSON {"t":"t_x"} | found | use the id, no DB |
JSON {"t":null} | known-bad | return null, no DB (neg cache) |
kv.get() returns null | absent | DB lookup once, then cache back |
- DB lookup is wrapped in
withTimeout(dbQuery(host), 800ms); the verified-claim SQL isSELECT tenant_id FROM domains WHERE host=$1 AND verified=true—verified=trueis load-bearing (an unverified squat must not serve, and stays reclaimable). - On success it writes KV: positive TTL 3600s / negative TTL 60s.
- A timed-out lookup is deliberately NOT cached — caching a negative on a transient DB blip would 404 a real store for 60s. (Caveat: the pending Neon query can't be cancelled; it leaks until GC — correctness rests only on not persisting its result.)
The adapter also honors ?store=<id> before KV/DB — but only when storeOverrideAllowed(host) (localhost-gated).
Serve-origin — the read-survival flow — serve-origin.ts
- Reads get a 1.5s budget; writes get 10s and never serve stale (
canServeStale = isRead && !!cache). A dead-origin write becomes a 503, not a faked page — a durable mutation can't come from cache. - Freshness, not mere presence, short-circuits the origin —
isFreshchecks(now - x-ratio-cached-at) < ttl. A stale copy does not short-circuit; it's only served if a revalidation then 5xxs/times-out. - Cacheability (
isCacheable): aset-cookieresponse is never cached (per-user);no-store/private→ not cached; TTL comes froms-maxage(preferred) elsemax-age, must be> 0. A page that forgetss-maxageis silently never cached. - On failure the breaker is fed
onFailure(); on successonSuccess().
Circuit breaker — circuit-breaker.ts
createCircuitBreaker(5, 10_000) — a closure over failures + openedAt (no explicit half-open flag):
It's module-scoped → per-isolate: each isolate learns independently, no fleet consensus, a cold isolate starts closed. Net effect: a sustained outage costs an isolate ~5 timeouts total, not one per request.
Headers & the edge secret — headers.ts, edge-secret.ts
proxyInitinjects fresh trusted headersx-edge-auth+x-ratio-tenant(never forwards a client copy), forwards only an allowlist (content-type, accept, accept-language, cookie), and setsredirect: 'manual'— a cart write answers303 → /cartwith aSet-Cookie; if the edge followed it, the cookie would be swallowed and the followed GET would be cookieless.'manual'returns the 3xx with the cookie so the browser follows it.publicHeadersstrips internal headers by prefix allowlist — onlyx-content-type-options+x-request-idsurvive; every otherx-*is dropped. (This is an allowlist because the old denylist drifted and leakedx-theme-render/x-theme-version.)storeOverrideAllowedgates?store=tolocalhost/*.localhostonly — it must never loosen to*.workers.dev(live in prod) or a visitor could render an arbitrary tenant and poison the 1-year CDN cache.resolveEdgeSecretreturnsEDGE_SECRET, else the dev literal only whenRATIO_LOCAL === 'true', else throws (fail closed). The gate isRATIO_LOCAL, notNODE_ENV(the oldNODE_ENVcheck wrongly handed the literal to staging).- The constant-time compare lives at the origin (
edgeAuthOk→timingSafeEqual), which gates every request. The edge sends the secret; the origin verifies it and takes the tenant from the header only.
Logs, metric, 503
- Access log — one line per request, a fixed field allowlist (
tenant, method, path, status, stale, ms), path is pathname only (never the query string, which can carry tokens). - Metric — one Analytics Engine point with exactly one index = tenant (bounded cardinality; AE samples the long tail).
storeUnavailable()— a self-contained 503 (inline CSS,retry-after: 30,no-store, the storefront CSP), generated entirely at the edge so it always renders.
Portability (Akamai)
There's no EdgeProvider interface — the seam is plain dependency injection: lookupTenant(kv, dbQuery), fetchViaOrigin(cache, doFetch, breaker), createCircuitBreaker(…, now) all take their platform bindings as arguments and never reach for globals. TenantKV/EdgeCache/AnalyticsEngineDataset are minimal local interfaces. An Akamai adapter would inject EdgeKV, DataStream 2, and back the breaker with EdgeKV (per-isolate state is unreliable there). No Akamai adapter exists in-tree yet.
Gotchas
?store=bypasses KV/DB — safe only because it's localhost-gated; loosening it to a public host would allow arbitrary-tenant rendering + 1-year cache poisoning.- Query string is in the cache key but not in logs —
?utm_*fragments the CDN cache per distinct query, and you can't see it in the pathname-only access log. - Negative cache 404s a store for up to 60s unless a control-plane write-through updates the KV key.
- A forgotten
s-maxage= silently never cached (no error) → every view hits the origin. - Stale is served only on an actual origin failure, never proactively; a stale-but-present copy still revalidates.
- Writes never serve stale and use a 10s budget — a dead-origin write is a 503, not a page.
- Breaker is per-isolate, not global; cold isolates start closed.
- Any new client-facing
x-*header must be added to thepublicHeadersallowlist or it's stripped. - The edge sends
EDGE_SECRET ?? ''— fail-closed lives at the origin verifier, not the edge.