Askr
Guides

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.

  1. Import the published @askrjs/askr entrypoint.
  2. Keep file uploads and artifacts configuration next to the component, route, or server composition root that owns it.
  3. 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.