{"version":3,"file":"io.mjs","names":[],"sources":["../src/io/browser.ts"],"sourcesContent":["// Browser-target I/O helpers. Phase-1 §1: synchronous in-memory paths.\n// Response / streaming-WritableStream bridges land alongside the\n// streaming ZIP work in §2.\n//\n// Node 18+ provides Blob / File / FormData / fetch / Web Streams natively,\n// so this module is also exercised by the Node-hosted vitest runner.\n\nimport { OpenXmlIoError } from '../utils/exceptions';\nimport type { BufferedSinkWriter, XlsxSink } from './sink';\nimport type { XlsxSource } from './source';\n\n/**\n * Wrap a Blob (or File, since File extends Blob) as an XlsxSource. The\n * source materialises bytes lazily on the first {@link XlsxSource.toBytes}\n * call.\n */\nexport function fromBlob(blob: Blob): XlsxSource {\n  if (!(blob instanceof Blob)) {\n    throw new OpenXmlIoError('fromBlob expects a Blob (or File)');\n  }\n  return {\n    async toBytes() {\n      try {\n        const ab = await blob.arrayBuffer();\n        return new Uint8Array(ab);\n      } catch (cause) {\n        throw new OpenXmlIoError('fromBlob: failed to read blob contents', { cause });\n      }\n    },\n    toStream() {\n      return blob.stream();\n    },\n  };\n}\n\n/**\n * Wrap a fetch {@link Response} as an XlsxSource. `toBytes` collects\n * the entire response body via `Response.arrayBuffer()`; `toStream`\n * returns `response.body` directly so the ZIP reader can pull chunks\n * lazily from the network. The Response can have been fetched with\n * any method and content-type; this helper does no validation.\n */\nexport function fromResponse(response: Response): XlsxSource {\n  if (!(response instanceof Response)) {\n    throw new OpenXmlIoError('fromResponse expects a fetch Response');\n  }\n  let bytes: Promise<Uint8Array> | undefined;\n  return {\n    async toBytes() {\n      if (bytes) return bytes;\n      bytes = (async () => {\n        try {\n          return new Uint8Array(await response.arrayBuffer());\n        } catch (cause) {\n          throw new OpenXmlIoError('fromResponse: failed to read response body', { cause });\n        }\n      })();\n      return bytes;\n    },\n    toStream() {\n      const body = response.body;\n      if (!body) {\n        // Bodyless responses (HEAD / 204 / 304) — return an empty stream.\n        return new ReadableStream<Uint8Array>({\n          start(controller) {\n            controller.close();\n          },\n        });\n      }\n      return body;\n    },\n  };\n}\n\n/**\n * Wrap a Web {@link ReadableStream} of bytes as an XlsxSource. `toStream`\n * returns the stream directly; `toBytes` drains it once and caches the\n * bytes. Streams can only be consumed once — calling `toBytes` after\n * `toStream` (or vice versa) on the same source throws.\n */\nexport function fromStream(stream: ReadableStream<Uint8Array>): XlsxSource {\n  if (!(stream instanceof ReadableStream)) {\n    throw new OpenXmlIoError('fromStream expects a ReadableStream<Uint8Array>');\n  }\n  let consumed = false;\n  let bytes: Promise<Uint8Array> | undefined;\n  return {\n    async toBytes() {\n      if (bytes) return bytes;\n      if (consumed) {\n        throw new OpenXmlIoError('fromStream: stream already consumed via toStream()');\n      }\n      consumed = true;\n      bytes = (async () => {\n        const chunks: Uint8Array[] = [];\n        const reader = stream.getReader();\n        try {\n          while (true) {\n            const { done, value } = await reader.read();\n            if (done) break;\n            if (value) chunks.push(value);\n          }\n        } finally {\n          reader.releaseLock();\n        }\n        let total = 0;\n        for (const c of chunks) total += c.byteLength;\n        const out = new Uint8Array(total);\n        let off = 0;\n        for (const c of chunks) {\n          out.set(c, off);\n          off += c.byteLength;\n        }\n        return out;\n      })();\n      return bytes;\n    },\n    toStream() {\n      if (bytes) {\n        throw new OpenXmlIoError('fromStream: bytes already consumed via toBytes()');\n      }\n      if (consumed) {\n        throw new OpenXmlIoError('fromStream: stream already consumed');\n      }\n      consumed = true;\n      return stream;\n    },\n  };\n}\n\n/**\n * Wrap an ArrayBuffer or Uint8Array. The bytes are referenced (Uint8Array\n * input) or wrapped without copy (ArrayBuffer input).\n */\nexport function fromArrayBuffer(buf: ArrayBuffer | Uint8Array): XlsxSource {\n  let bytes: Uint8Array;\n  if (buf instanceof Uint8Array) {\n    bytes = buf;\n  } else if (buf instanceof ArrayBuffer) {\n    bytes = new Uint8Array(buf);\n  } else {\n    throw new OpenXmlIoError('fromArrayBuffer expects an ArrayBuffer or Uint8Array');\n  }\n  return {\n    async toBytes() {\n      return bytes;\n    },\n    toStream() {\n      return new ReadableStream<Uint8Array>({\n        start(controller) {\n          controller.enqueue(bytes);\n          controller.close();\n        },\n      });\n    },\n  };\n}\n\nconst collectChunks = (chunks: Uint8Array[]): Uint8Array => {\n  let total = 0;\n  for (const c of chunks) total += c.byteLength;\n  const out = new Uint8Array(total);\n  let off = 0;\n  for (const c of chunks) {\n    out.set(c, off);\n    off += c.byteLength;\n  }\n  return out;\n};\n\n/**\n * In-memory Blob sink. Convenience `result()` returns the accumulated\n * payload as a Blob with the supplied MIME type.\n */\nexport function toBlob(\n  mime: string = 'application/octet-stream',\n): XlsxSink & { toBytes(): BufferedSinkWriter; result(): Blob } {\n  const chunks: Uint8Array[] = [];\n  let finalised: Uint8Array | undefined;\n\n  const finalise = (): Uint8Array => {\n    if (finalised !== undefined) return finalised;\n    finalised = collectChunks(chunks);\n    chunks.length = 0;\n    return finalised;\n  };\n\n  return {\n    toBytes(): BufferedSinkWriter {\n      return {\n        write(chunk: Uint8Array): void {\n          if (finalised !== undefined) throw new OpenXmlIoError('toBlob sink: write after finish');\n          if (!(chunk instanceof Uint8Array)) throw new OpenXmlIoError('toBlob sink: chunk is not a Uint8Array');\n          chunks.push(chunk);\n        },\n        async finish(): Promise<Uint8Array> {\n          return finalise();\n        },\n        abort(): void {\n          if (finalised !== undefined) return;\n          finalised = new Uint8Array(0);\n          chunks.length = 0;\n        },\n      };\n    },\n    result(): Blob {\n      // Copy the bytes' typed-array view into a fresh Blob to detach from\n      // the writer's internal buffer.\n      const bytes = finalise();\n      return new Blob([bytes.slice()], { type: mime });\n    },\n  };\n}\n\n/** In-memory ArrayBuffer sink. */\nexport function toArrayBuffer(): XlsxSink & { toBytes(): BufferedSinkWriter; result(): ArrayBuffer } {\n  const chunks: Uint8Array[] = [];\n  let finalised: Uint8Array | undefined;\n\n  const finalise = (): Uint8Array => {\n    if (finalised !== undefined) return finalised;\n    finalised = collectChunks(chunks);\n    chunks.length = 0;\n    return finalised;\n  };\n\n  return {\n    toBytes(): BufferedSinkWriter {\n      return {\n        write(chunk: Uint8Array): void {\n          if (finalised !== undefined) throw new OpenXmlIoError('toArrayBuffer sink: write after finish');\n          if (!(chunk instanceof Uint8Array)) throw new OpenXmlIoError('toArrayBuffer sink: chunk is not a Uint8Array');\n          chunks.push(chunk);\n        },\n        async finish(): Promise<Uint8Array> {\n          return finalise();\n        },\n        abort(): void {\n          if (finalised !== undefined) return;\n          finalised = new Uint8Array(0);\n          chunks.length = 0;\n        },\n      };\n    },\n    result(): ArrayBuffer {\n      const bytes = finalise();\n      // bytes' underlying buffer may be larger than its byteLength (slice\n      // semantics on Uint8Array). Materialise an exactly-sized copy.\n      const out = new ArrayBuffer(bytes.byteLength);\n      new Uint8Array(out).set(bytes);\n      return out;\n    },\n  };\n}\n"],"mappings":"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"}