/** * EdgeHostAdapter -- canonical host-facing edge resolution path. * * Resolves client hints, tiering, optional theme compilation, and optional * boundary compilation cache lookups in a single host-level operation. * * @module */ import type { ContentAddress } from '@czap/core'; import type { ExtendedDeviceCapabilities } from '@czap/detect'; import type { ClientHintsHeaders } from './client-hints.js'; import type { EdgeTierResult } from './edge-tier.js'; import type { CompiledOutputs, KVNamespace } from './kv-cache.js'; import type { TierKey } from './manifest.js'; import type { ThemeCompileConfig, ThemeCompileResult } from './theme-compiler.js'; /** * Detected device context available to host callbacks before compile. * * Pairs the parsed {@link ExtendedDeviceCapabilities} with the resolved * {@link EdgeTierResult} so a host can derive a theme config or compile * decision without re-parsing headers. */ export interface EdgeHostContext { /** Capabilities parsed from Client Hints. */ readonly capabilities: ExtendedDeviceCapabilities; /** Derived tier triple (cap, motion, design). */ readonly tier: EdgeTierResult; } /** * Compile-time context passed to {@link EdgeHostCacheConfig.compile}. * * Extends {@link EdgeHostContext} with the already-resolved theme result * (if any) so host compile callbacks can inject theme tokens into the * compiled per-state outputs without recomputation. Carries the identity * of the boundary being compiled so a callback shared across multiple * boundaries can branch -- without it, one compile result would be cached * under every boundary's content address. */ export interface EdgeHostCompileContext extends EdgeHostContext { /** Pre-compiled theme output, if the adapter resolved one for this request. */ readonly theme?: ThemeCompileResult; /** Content address of the boundary this compile call is for. */ readonly boundaryId: ContentAddress; /** Boundary name, when configured via {@link EdgeHostCacheConfig.boundaries}. */ readonly boundaryName?: string; } /** Tags attached to a boundary cache write for active invalidation. */ export type EdgeHostCacheTags = readonly string[] | ((context: EdgeHostCompileContext) => readonly string[] | null | undefined); /** * Outputs source for one boundary -- the per-boundary slice of * {@link EdgeHostCacheConfig}. Resolution order per boundary is * `precompiled`, then KV keyed by `(boundaryId, tier)`, then `compile` * (written back to KV). At least one of `precompiled` or `compile` is * required. */ export interface EdgeHostBoundaryConfig { /** Content address of the boundary being compiled (`Boundary.make`'s `id`). */ readonly boundaryId: ContentAddress; /** * Build-derived outputs keyed by {@link TierKey} -- the `outputsByTier` * field of a boundary manifest entry. Checked before KV. */ readonly precompiled?: Readonly>>; /** * Immutable static-asset URL keyed by {@link TierKey}, derived from a * manifest entry's optional `assetUrls`. Metadata only: it never changes * the cache key or lookup order. */ readonly assetUrlsByTier?: Readonly>>; /** Compile function invoked when neither `precompiled` nor KV has the tier. */ readonly compile?: (context: EdgeHostCompileContext) => Promise | CompiledOutputs; /** * Tags written into the boundary cache index when `compile` fills a KV miss. * Use the same values as Astro `routeRules.tags` when `cache.invalidate({ tags })` * should purge the corresponding CZAP boundary CSS variants. */ readonly tags?: EdgeHostCacheTags; } /** * Cache configuration for the edge host adapter. * * Two forms, mutually exclusive. Single boundary: `boundaryId` plus * `precompiled`/`compile` at the top level. Multiple boundaries (real * pages render several): `boundaries`, a name-keyed record of * {@link EdgeHostBoundaryConfig}. Either way, outputs per boundary are * resolved in order: `precompiled` (build-derived manifest entry, no KV * round-trip), then the KV cache keyed by `(boundaryId, tier)` -- the key * carries the boundary's content address, so boundaries can never read * each other's cached CSS -- then `compile` on a miss (result written * back to KV with the configured `ttl`). */ export interface EdgeHostCacheConfig { /** KV namespace backing the boundary cache. */ readonly kv: KVNamespace; /** * Content address of the boundary being compiled (`Boundary.make`'s * `id`). Single-boundary form; exclusive with `boundaries`. */ readonly boundaryId?: ContentAddress; /** * Build-derived outputs keyed by {@link TierKey} * (`":"`) -- a manifest entry inflated via * `resolveOutputsByTier(manifestEntry)`. Checked before KV; a covered * tier never touches the network. */ readonly precompiled?: Readonly>>; /** Immutable static-asset URL keyed by {@link TierKey} for the single-boundary form. */ readonly assetUrlsByTier?: Readonly>>; /** Compile function invoked when neither `precompiled` nor KV has the tier. */ readonly compile?: (context: EdgeHostCompileContext) => Promise | CompiledOutputs; /** Tags for the single-boundary form, passed through to the normalized boundary source. */ readonly tags?: EdgeHostCacheTags; /** * Multi-boundary form: outputs sources keyed by boundary name (the * manifest export name). Exclusive with the top-level * `boundaryId`/`precompiled`/`compile` fields. */ readonly boundaries?: Readonly>; /** * Cache entry TTL in seconds — an eviction/cost knob, not a freshness * knob. An entry is keyed by boundary content address, tier, name, and * resolved-theme fingerprint, so it never goes stale for a change in any of * those. (A `compile` whose output also depends on build-time inputs the * boundary id does not cover must vary `prefix` per deploy — see `prefix`.) * Deploys that change boundary content mint a new `ContentAddress` and * orphan the old keys, which KV stores (and bills) forever unless a TTL * reclaims them. Omit to cache indefinitely. */ readonly ttl?: number; /** * Optional KV key prefix. Doubles as the per-deploy content version for a * bundled `compile`: set it to a hash of compile's output (e.g. * `layout-${fnv1a(compileLayoutCss())}`) when that output depends on * build-time content outside the boundary's own address. */ readonly prefix?: string; } /** * Optional Workers background hook for deferring KV write-back off the request path (#122). */ export interface EdgeHostBackground { readonly waitUntil: (promise: Promise) => void; } /** * Configuration for {@link createEdgeHostAdapter}. * * `theme` may be a static {@link ThemeCompileConfig}, a per-request * resolver function, or absent. `cache` enables a KV-backed boundary * compile cache keyed by content address + tier. When `background` is * present, boundary-cache write-back on a compile miss is scheduled via * `waitUntil` instead of blocking the response (#122). */ export interface EdgeHostAdapterConfig { /** Static theme config, or a resolver invoked with each request's context. */ readonly theme?: ThemeCompileConfig | ((context: EdgeHostContext) => ThemeCompileConfig | null | undefined); /** KV-backed boundary output cache; omit to disable caching. */ readonly cache?: EdgeHostCacheConfig; /** * When present, boundary-cache write-back on a compile miss is scheduled via * `waitUntil` instead of blocking the response (#122). */ readonly background?: EdgeHostBackground; } /** * Cache lookup outcome reported in {@link EdgeHostResolution}. * `'precompiled'` means the outputs came from the build-derived manifest * without touching KV. */ export type EdgeHostCacheStatus = 'disabled' | 'precompiled' | 'hit' | 'miss'; /** * Per-boundary resolution outcome, reported in * {@link EdgeHostResolution.boundaries} when the cache is configured with * the multi-boundary form. */ export interface EdgeHostBoundaryResolution { /** Content address the outputs were resolved (and cached) under. */ readonly boundaryId: ContentAddress; /** Compiled per-state outputs; absent on an uncovered tier with no `compile`. */ readonly compiledOutputs?: CompiledOutputs; /** Immutable static-asset URL for this request's resolved tier, when emitted by the build. */ readonly assetUrl?: string; /** Where this boundary's outputs came from (`'disabled'` cannot occur per boundary). */ readonly cacheStatus: Exclude; } /** * Full per-request resolution output from {@link EdgeHostAdapter.resolve}. * * Carries the device context, optional theme and compiled outputs, the * `data-czap-*` attribute string for the root HTML element, and the * `Accept-CH`/`Critical-CH` headers the response should send back. */ export interface EdgeHostResolution extends EdgeHostContext { /** Compiled theme result, if a theme config was resolved for this request. */ readonly theme?: ThemeCompileResult; /** * Compiled per-state outputs when exactly one boundary is configured * (either form). Undefined with multiple boundaries -- read * {@link boundaries} instead. */ readonly compiledOutputs?: CompiledOutputs; /** Immutable static-asset URL when exactly one boundary is configured and emitted one. */ readonly assetUrl?: string; /** Per-boundary outcomes, keyed by name; present with the `boundaries` cache form. */ readonly boundaries?: Readonly>; /** `data-czap-tier`/`data-czap-motion`/`data-czap-design` string for `` (one per `CAP_AXES`). */ readonly htmlAttributes: string; /** * Spreadable map form of {@link htmlAttributes}, keyed by full attribute name * (`data-czap-`) and built from the canonical `CAP_AXES` registry, so a * new axis appears automatically. Astro: `` — a * consumer that spreads it can never silently miss an axis (vs hand-writing). */ readonly htmlAttributesMap: Readonly>; /** Response headers to send back so the browser will supply hints next time. */ readonly responseHeaders: { /** `Accept-CH` header value. */ readonly acceptCH: string; /** `Critical-CH` header value. */ readonly criticalCH: string; }; /** * Whether boundary outputs came from cache, were computed and stored, * or caching is off. With multiple boundaries this is the worst case * across them (worst-to-best: `miss`, `hit`, `precompiled`); * per-boundary statuses live in {@link boundaries}. */ readonly cacheStatus: EdgeHostCacheStatus; } /** * Opaque host-facing adapter returned by {@link createEdgeHostAdapter}. * * Call `resolve(headers)` per request; the adapter drives tier detection, * theme compilation, and boundary caching in a single pass. */ export interface EdgeHostAdapter { /** Resolve a request's device context, theme, and compiled outputs. */ resolve(headers: Headers | ClientHintsHeaders): Promise; } /** * Create an {@link EdgeHostAdapter} with optional theme and boundary cache. * * The returned adapter is designed to be instantiated once per worker and * reused across requests; it caches a compiled static theme eagerly and * only invokes the compile callback on cache miss when caching is enabled. */ export declare function createEdgeHostAdapter(config?: EdgeHostAdapterConfig): EdgeHostAdapter; /** * Edge host adapter namespace. * * `EdgeHostAdapter.create(config)` builds a reusable adapter that resolves * Client Hints, tiers, theme compilation, and KV-backed boundary caching * in a single per-request pass. */ export declare const EdgeHostAdapter: { /** Alias for {@link createEdgeHostAdapter}. */ readonly create: typeof createEdgeHostAdapter; }; export declare namespace EdgeHostAdapter { /** Alias for {@link EdgeHostAdapterConfig}. */ type Config = EdgeHostAdapterConfig; /** Alias for {@link EdgeHostResolution}. */ type Resolution = EdgeHostResolution; /** Alias for {@link EdgeHostCacheStatus}. */ type CacheStatus = EdgeHostCacheStatus; /** Alias for {@link EdgeHostContext}. */ type Context = EdgeHostContext; /** Alias for {@link EdgeHostCompileContext}. */ type CompileContext = EdgeHostCompileContext; /** Alias for {@link EdgeHostBoundaryConfig}. */ type BoundaryConfig = EdgeHostBoundaryConfig; /** Alias for {@link EdgeHostCacheTags}. */ type CacheTags = EdgeHostCacheTags; /** Alias for {@link EdgeHostBoundaryResolution}. */ type BoundaryResolution = EdgeHostBoundaryResolution; } //# sourceMappingURL=host-adapter.d.ts.map