export class HttpError extends Error { readonly status: number; readonly code: string; readonly detail?: Record; constructor(status: number, code: string, message: string, detail?: Record) { super(message); this.name = "HttpError"; this.status = status; this.code = code; this.detail = detail; } toResponse(): Response { return new Response(JSON.stringify({ error: this.code, message: this.message, ...(this.detail ?? {}) }), { status: this.status, headers: { "content-type": "application/json" }, }); } } // JSON response carrying a one-time secret (invite URL, OAuth client // secret, rotated client secret). POST responses are not cached by // default, but explicit `no-store` removes the chance that a browser // extension, dev tool, intermediary, or edge cache retains the value // after the user closes the tab. `private` is defense-in-depth for // caches that ignore `no-store` but honor `private`. Pragma is // duplicated for HTTP/1.0 and strict legacy proxies. export const secretJsonResponse = (payload: unknown, status = 200): Response => new Response(JSON.stringify(payload), { status, headers: { "content-type": "application/json", "cache-control": "no-store, private, max-age=0", pragma: "no-cache", }, });