File Uploads and Artifacts
Use file uploads and artifacts with the current published Askr packages.
@askrjs/askr0.0.59
How to use file uploads and artifacts
Stream multipart uploads at the server boundary, enforce size and content constraints before storage, and return an opaque artifact identifier.
- Import the published @askrjs/askr entrypoint.
- Keep file uploads and artifacts configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
<form method="post" action="/artifacts" enctype="multipart/form-data">
<label for="artifact">Build artifact</label>
<input id="artifact" name="artifact" type="file" required />
<button type="submit">Upload</button>
</form>
// Server: bind the multipart input, validate it, stream to storage, then return 201.Goal and architecture
Uploads in Askr go through the same typed-client machinery as any other request: `@askrjs/fetch` ships a `multipart()` codec that types request bodies as `FormData`, so a file upload endpoint is defined the same way as a JSON one, just with a different codec on the request side. There's no separate 'upload manager' abstraction — you build a `FormData`, attach it to a `post()` endpoint typed with `multipart()`, and get back the same discriminated `ClientResult` as any other call.
Implementation
Define the endpoint with `.body(multipart())` (or whatever your API descriptor's body method is named) and construct the `FormData` on the caller side with `append('file', fileInput.files[0])` plus any accompanying fields. Because responses go through the codec system too, a large download can use `blob()` or `arrayBuffer()` instead of `json()`, and a chunked upload-progress or download stream can use `stream()` to get a raw `ReadableStream<Uint8Array>` rather than buffering the whole payload in memory. On the server side, read the incoming `multipart/form-data` body off the standard `Request` object inside your `Handler`.
Failure states
Enforce size and type limits before the request leaves the browser where you can — the client doesn't reject oversized files for you, so check `file.size` and `file.type` against your limits and surface a specific message rather than letting a large upload fail with a generic transport error partway through. On the server, a request that exceeds your platform's body-size limit or has an unexpected content type should return a typed error response (`context.badRequest(...)` or a codec-declared `413`/`415` in `.errors({...})`) that the client can decode, not a raw 500. Uploads with bodies are excluded from the client's automatic retry middleware by design, so a failed upload needs its own explicit retry action rather than silently resubmitting.
Verification
Test the multipart codec path with a small in-memory `File`/`Blob` and assert the server handler receives the expected fields and file bytes, rather than mocking `FormData` construction away. Cover the size-limit and wrong-content-type rejection paths explicitly, since those are the failure modes users hit most often in practice. For large-file or streaming paths, verify backpressure behavior with a stream that yields chunks slowly, confirming the upload doesn't buffer the entire file into memory before sending.