{"version":3,"file":"body.mjs","names":[],"sources":["../../../../../../../http/src/utils/body.ts"],"sourcesContent":["import type { DownloadProgressEvent, ResponseType, UploadProgressEvent } from \"../Http.types\";\n\ntype BodyResult = {\n  body: BodyInit | undefined;\n  contentType: string | undefined;\n};\n\n/**\n * Convert HttpData to a BodyInit suitable for fetch().\n *\n * - FormData / HTMLFormElement → passed through; let the browser set Content-Type\n *   (so the boundary is included automatically — never override it manually).\n * - string → passed through; caller is responsible for Content-Type.\n * - object → JSON.stringify(); Content-Type: application/json.\n */\nexport function prepareBody(data: unknown): BodyResult {\n  if (data === undefined || data === null) {\n    return { body: undefined, contentType: undefined };\n  }\n\n  if (typeof data === \"string\") {\n    return { body: data, contentType: undefined };\n  }\n\n  if (data instanceof FormData) {\n    return { body: data, contentType: undefined };\n  }\n\n  if (\n    typeof HTMLFormElement !== \"undefined\" &&\n    data instanceof HTMLFormElement\n  ) {\n    return { body: new FormData(data as HTMLFormElement), contentType: undefined };\n  }\n\n  // Plain object / array → JSON\n  return {\n    body: JSON.stringify(data),\n    contentType: \"application/json\",\n  };\n}\n\n/**\n * Parse a Response body according to the requested responseType.\n * When responseType is omitted, auto-detects from Content-Type.\n * Never throws — returns null on parse failure.\n */\nexport async function parseBody(\n  response: Response,\n  responseType?: ResponseType,\n): Promise<unknown> {\n  try {\n    switch (responseType) {\n      case \"blob\":\n        return await response.blob();\n      case \"arrayBuffer\":\n        return await response.arrayBuffer();\n      case \"text\":\n        return await response.text();\n      case \"json\":\n        return await response.json();\n      default: {\n        const ct = response.headers.get(\"content-type\") ?? \"\";\n        if (ct.includes(\"application/json\")) {\n          return await response.json();\n        }\n        if (\n          /^(image|video|audio|font)\\//i.test(ct) ||\n          ct.includes(\"application/octet-stream\") ||\n          ct.includes(\"application/pdf\") ||\n          ct.includes(\"application/zip\")\n        ) {\n          return await response.blob();\n        }\n        return await response.text();\n      }\n    }\n  } catch {\n    return null;\n  }\n}\n\n// ─── Upload progress ─────────────────────────────────────────────────────────\n\nconst UPLOAD_CHUNK_SIZE = 65536; // 64 KiB\n\n/**\n * Wrap a request body in a ReadableStream that fires progress events as chunks\n * pass through. Returns the original body unchanged for types that cannot be\n * streamed (FormData, Blob), with no events fired for those types.\n *\n * When the body is wrapped as a ReadableStream, the caller must add\n * `duplex: \"half\"` to the fetch() options (required by the Fetch spec for\n * half-duplex streaming — Chrome 105+, Node.js 18+, Safari 17.4+).\n */\nexport function wrapBodyWithProgress(\n  body: BodyInit | undefined,\n  onProgress: (event: UploadProgressEvent) => void,\n): { body: BodyInit | undefined; duplex?: \"half\" } {\n  if (!body || typeof ReadableStream === \"undefined\") {\n    return { body };\n  }\n\n  // Convert to Uint8Array for types where we know the full size.\n  let bytes: Uint8Array | null = null;\n\n  if (typeof body === \"string\") {\n    bytes = new TextEncoder().encode(body);\n  } else if (body instanceof ArrayBuffer) {\n    bytes = new Uint8Array(body);\n  } else if (ArrayBuffer.isView(body) && !(body instanceof DataView)) {\n    bytes = new Uint8Array(body.buffer, body.byteOffset, body.byteLength);\n  }\n\n  // FormData / Blob: can't determine size; skip progress (documented limitation).\n  if (!bytes) {\n    return { body };\n  }\n\n  const total = bytes.byteLength;\n  let offset = 0;\n\n  const stream = new ReadableStream<Uint8Array>({\n    pull(controller): void {\n      if (offset >= total) {\n        controller.close();\n        return;\n      }\n      const end = Math.min(offset + UPLOAD_CHUNK_SIZE, total);\n      const chunk = bytes!.subarray(offset, end);\n      offset += chunk.byteLength;\n      controller.enqueue(chunk);\n      onProgress({\n        loaded: offset,\n        total,\n        percent: Math.round((offset / total) * 100),\n      });\n    },\n  });\n\n  return { body: stream, duplex: \"half\" };\n}\n\n/**\n * Read a Response body chunk-by-chunk, firing onProgress after each chunk.\n * Falls back to parseBody when response.body is unavailable.\n */\nexport async function readBodyWithProgress(\n  response: Response,\n  onProgress: (event: DownloadProgressEvent) => void,\n  responseType?: ResponseType,\n): Promise<unknown> {\n  const contentLength = response.headers.get(\"content-length\");\n  const total = contentLength ? parseInt(contentLength, 10) : null;\n\n  const reader = response.body?.getReader();\n  if (!reader) {\n    return parseBody(response, responseType);\n  }\n\n  const chunks: Uint8Array[] = [];\n  let loaded = 0;\n\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n\n      chunks.push(value);\n      loaded += value.byteLength;\n\n      onProgress({\n        loaded,\n        total,\n        percent: total !== null ? Math.round((loaded / total) * 100) : null,\n      });\n    }\n  } finally {\n    reader.releaseLock();\n  }\n\n  // Combine all chunks into one buffer\n  const combined = new Uint8Array(loaded);\n  let offset = 0;\n  for (const chunk of chunks) {\n    combined.set(chunk, offset);\n    offset += chunk.byteLength;\n  }\n\n  switch (responseType) {\n    case \"blob\":\n      return new Blob([combined], {\n        type: response.headers.get(\"content-type\") ?? undefined,\n      });\n    case \"arrayBuffer\":\n      return combined.buffer;\n    case \"text\":\n      return new TextDecoder().decode(combined);\n    default: {\n      const ct = response.headers.get(\"content-type\") ?? \"\";\n      if (ct.includes(\"application/json\")) {\n        const text = new TextDecoder().decode(combined);\n        try {\n          return JSON.parse(text);\n        } catch {\n          return text;\n        }\n      }\n      if (\n        /^(image|video|audio|font)\\//i.test(ct) ||\n        ct.includes(\"application/octet-stream\") ||\n        ct.includes(\"application/pdf\") ||\n        ct.includes(\"application/zip\")\n      ) {\n        return new Blob([combined], { type: ct || undefined });\n      }\n      return new TextDecoder().decode(combined);\n    }\n  }\n}\n"],"mappings":"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"}