// What one chart read carries for the original view it is rendered in: the view header, and a binder that receives the // exact answer so a server receipt can be checked against the bytes it names. Passed per call, never set globally. /** One served answer as it arrived: its own response headers and the exact body text before parsing. */ export interface ServedAnswer { header: (name: string) => string | null; text: string; } export interface ReadOptions { headers?: Record; /** Called once with the answer of a successful read; anything it throws is kept from the read. */ onResponse?: (answer: ServedAnswer) => void | Promise; } const headersOf = (headers: unknown): Record => { if (!headers) return {}; if (headers instanceof Headers) return Object.fromEntries(headers.entries()); if (Array.isArray(headers)) return Object.fromEntries(headers as Array<[string, string]>); return headers as Record; }; /** Joins a read's own request options with the view's, keeping both sets of headers. */ export function withRead(base: O, read?: ReadOptions): O & ReadOptions { if (!read) return base as O & ReadOptions; const own = headersOf((base as { headers?: unknown }).headers); return { ...base, headers: { ...own, ...(read.headers ?? {}) }, ...(read.onResponse ? { onResponse: read.onResponse } : {}) } as O & ReadOptions; }