{"version":3,"file":"decode-response.cjs","names":[],"sources":["../../src/http/decode-response.ts"],"sourcesContent":["import { isDevBuild } from \"../utils/dev-mode\";\n\n/**\n * Media-type fragments that name something safe to read as text, for the types\n * that do not open with `text/` — `application/xml`, `application/javascript`,\n * `application/x-www-form-urlencoded`.\n */\nconst TEXTUAL_TOKENS: readonly string[] = [\"xml\", \"javascript\", \"ecmascript\", \"urlencoded\", \"csv\"];\n\n/** How a {@link createApiClient} response body becomes the value a caller awaited. */\nexport type ResponseDecoder<T> = (response: Response) => Promise<T>;\n\n/**\n * Whether a `Content-Type` names a JSON dialect.\n *\n * Matches the suffix forms as well as the plain one, so `application/json`,\n * `application/problem+json` (RFC 9457, what a FastAPI error handler sends) and\n * `application/vnd.tempest.v2+json` all decode as JSON instead of falling\n * through to text.\n *\n * @param contentType - The raw header value, possibly with parameters.\n * @returns Whether the body should be read with `response.json()`.\n */\nexport function isJsonContentType(contentType: string): boolean {\n    return contentType.toLowerCase().includes(\"json\");\n}\n\n/**\n * Whether a `Content-Type` names something that survives being read as text.\n *\n * An absent header counts as textual: a backend that answers without one is\n * almost always sending text, and treating it as binary would change what\n * existing callers get back for no measured gain.\n *\n * @param contentType - The raw header value, possibly with parameters.\n * @returns Whether the body should be read with `response.text()`.\n */\nexport function isTextualContentType(contentType: string): boolean {\n    const type = contentType.trim().toLowerCase();\n    if (type === \"\") return true;\n    if (type.startsWith(\"text/\")) return true;\n    return TEXTUAL_TOKENS.some((token) => type.includes(token));\n}\n\n/**\n * Warn, in a development build, that a binary body reached the JSON/text path.\n *\n * @param contentType - The `Content-Type` that was decoded as a `Blob`.\n */\nfunction warnBinaryBody(contentType: string): void {\n    if (!isDevBuild()) return;\n    console.warn(\n        `[tempest] <ApiClient> received a binary response (content-type: ${contentType}) and returned a Blob. ` +\n            \"Call `client.blob()` or `client.arrayBuffer()` so the type matches what arrives.\",\n    );\n}\n\n/**\n * Decode a response the way its `Content-Type` asks to be decoded.\n *\n * JSON is parsed, text is read as text, and **everything else comes back as a\n * `Blob`**. That last branch is the fix for a silent data defect: the client\n * used to fall back to `response.text()` for any non-JSON body, so an\n * `image/jpeg` was decoded as UTF-8 and the bytes were destroyed on the way in.\n * Measured on a bare JPEG header — `ff d8 ff e0 00 10 4a 46 49 46` — the\n * round-trip through text returns 18 bytes for the 10 that arrived, 4 of them\n * replaced by `U+FFFD`. No caller can undo that, which is why the download had\n * to leave the client entirely.\n *\n * A `204` has no body at all and resolves to `undefined`, whatever the headers\n * claim.\n *\n * @typeParam T - What the caller expects back.\n * @param response - A response already known to be successful.\n * @returns The decoded body.\n */\nexport async function decodeByContentType<T>(response: Response): Promise<T> {\n    if (response.status === 204) {\n        return undefined as T;\n    }\n\n    const contentType = response.headers.get(\"content-type\") ?? \"\";\n    if (isJsonContentType(contentType)) {\n        return (await response.json()) as T;\n    }\n    if (isTextualContentType(contentType)) {\n        return (await response.text()) as unknown as T;\n    }\n\n    warnBinaryBody(contentType);\n    return (await response.blob()) as unknown as T;\n}\n"],"mappings":"yCAOA,IAAM,EAAoC,CAAC,MAAO,aAAc,aAAc,aAAc,KAAK,EAgBjG,SAAgB,EAAkB,EAA8B,CAC5D,OAAO,EAAY,YAAY,CAAC,CAAC,SAAS,MAAM,CACpD,CAYA,SAAgB,EAAqB,EAA8B,CAC/D,IAAM,EAAO,EAAY,KAAK,CAAC,CAAC,YAAY,EAG5C,OAFI,IAAS,IACT,EAAK,WAAW,OAAO,EAAU,GAC9B,EAAe,KAAM,GAAU,EAAK,SAAS,CAAK,CAAC,CAC9D,CAOA,SAAS,EAAe,EAA2B,CAC1C,EAAA,WAAW,GAChB,QAAQ,KACJ,mEAAmE,EAAY,4GAEnF,CACJ,CAqBA,eAAsB,EAAuB,EAAgC,CACzE,GAAI,EAAS,SAAW,IACpB,OAGJ,IAAM,EAAc,EAAS,QAAQ,IAAI,cAAc,GAAK,GAS5D,OARI,EAAkB,CAAW,EACrB,MAAM,EAAS,KAAK,EAE5B,EAAqB,CAAW,EACxB,MAAM,EAAS,KAAK,GAGhC,EAAe,CAAW,EAClB,MAAM,EAAS,KAAK,EAChC"}