Upload and download files
Use FormData for multipart uploads and an explicit responseType for downloads. These functions are intended for a browser app; the upload endpoint returns JSON { "id": "..." }.
import { Http, rest } from "fetchzen";import type { HttpProgressHandler } from "fetchzen";
const api = Http.create("https://api.example.com", { preset: rest, cache: false,});
export async function uploadFile(file: File, signal?: AbortSignal) { const form = new FormData(); form.append("file", file, file.name); const { data } = await api.post<FormData, { id: string }>("/files", form, { signal, }); return data;}
export async function downloadFile( id: string, onProgress: HttpProgressHandler, signal?: AbortSignal,): Promise<Blob> { const { data } = await api.get<Blob>(`/files/${encodeURIComponent(id)}`, { responseType: "blob", onDownloadProgress: onProgress, signal, }); return data;}Do not set a multipart Content-Type manually. The client passes FormData through and removes that header so fetch can generate the boundary. Raw Blob/ArrayBuffer uploads are also passed through, but do not automatically replace the client’s default JSON content type; set an appropriate request headers value for those uploads.
Progress and memory
Section titled “Progress and memory”ReadableStream request bodies are runtime-dependent. The client supplies
duplex: "half" when omitted and forwards it to native or custom Fetch; you may
also pass it explicitly. This does not enable streamed uploads in browsers that
do not support them. It provides neither upload progress nor resumable uploads.
onDownloadProgressreceives{ loaded, total?, percent?, done }.loadedcounts bytes;totalandpercentdepend on a usableContent-Length. Non-identityContent-Encodingleaves both undefined because compression can make the declared length differ from bytes read. A CORS response without a visible encoding header also has an unknown total; an uncompressed cross-origin server can send and exposeContent-Encoding: identity. Handle missing totals with an indeterminate indicator.- Native Fetch upload-transfer callbacks are not supported. Ordinary raw/multipart uploads remain indeterminate until their response. For server-confirmed progress, use
uploadConfirmedwith a cooperating chunk endpoint; this is not transport progress or automatic file splitting for an ordinary endpoint. responseType: "blob"buffers the download. To use bytes instead, pairget<ArrayBuffer>withresponseType: "arrayBuffer".- For large downloads, use
get<ReadableStream<Uint8Array> | null>withresponseType: "stream"and consume or cancel the returned stream. The request resolves before consumption finishes; handle read errors too. Progress follows consumption, not just the initial response. - Binary and stream responses are not cloned by default. Use the returned
data, rather than trying to read the response body again after a buffered download. - Progress wrapping and cloning preserve
Response.url,redirected, andtype; they do not manufacture a known total for compressed responses.
To display a downloaded blob, create an object URL with URL.createObjectURL(blob) and revoke it with URL.revokeObjectURL(url) when it is no longer in use.
A forced binary response type applies to error responses too. rest still rejects failing HTTP statuses, but a JSON error body is not parsed as JSON when responseType: "blob" is selected.
Related: cancelling stale requests. Try file upload, download, or streaming.