Bring Your Own Backend
Implement the complete S3Api contract for an existing backend.
defineApi from @dimah-s3/react/engine type-checks a complete S3Api and
returns it unchanged. Your routes, transport, and server framework are up to
you; they do not need to match @dimah-s3/server. Mount the
result with S3Provider as shown in React Setup.
Contract
- Every method takes
(payload, options?).optionsis{ signal, headers }. Forwardsignalto your request so cancel reaches it. - Return every required field in the response tables. Optional fields may be left out.
- Every object call names a
route.catalog()is the exception. Upload and multipart init do not send akey: your backend picks it and returns it. - Issue a ticket.
uploadandmultipart.initreturn aticket.confirmand every later multipart call send only{ ticket }(plus the part number when signing), so the ticket must tell your backend the route, key, and upload id. Sign it, or store it server side. It may be up to 8192 characters. - Throw an
APIErrorwith a catalog code. The hooks retryNETWORK_ERRORandS3_NETWORK_ERROR, and show codes withuseFormatDimahError. Anything else becomesUNKNOWN_ERROR, which is not retried. - Trust
HeadObject, not the client. Confirm and complete return the size and type S3 reports, not what the browser declared.
createS3Fetch from @dimah-s3/core gives you a fetcher with these error
rules: a { code, message, params? } body becomes an APIError with that
code and status, a failed request becomes NETWORK_ERROR, and an aborted
signal rethrows its reason. The examples on these pages use it.
Payload types are exported from @dimah-s3/core, and so is every response
type.
Flows
| Flow | Methods | Doc |
|---|---|---|
| Route policies | catalog | Route catalog |
| Simple upload | upload → browser uploads to S3 → confirm | Upload |
| Download | download | Download |
| Delete | delete or deleteMany | Delete |
| Multipart | init → signPart → complete or abort · listParts to resume | Multipart |
Skeleton
One fetcher serves every method. call forwards the per-call signal and
headers:
import { createS3Fetch, type S3RequestOptions } from "@dimah-s3/core";
export const $fetch = createS3Fetch("/api/files");
/** Per-call signal and headers for `$fetch`. */
export const call = (options?: S3RequestOptions) => ({
signal: options?.signal,
headers: options?.headers ? new Headers(options.headers) : undefined,
});Combine the methods from each page in one defineApi call:
import { defineApi } from "@dimah-s3/react/engine";
import { catalog } from "./catalog";
import { deleteMany, deleteObject } from "./delete";
import { download } from "./download";
import { abort, complete, init, listParts, signPart } from "./multipart";
import { confirm, upload } from "./upload";
export const api = defineApi({
upload,
confirm,
download,
catalog,
delete: deleteObject,
deleteMany,
multipart: { init, signPart, listParts, complete, abort },
});Every method is required. Response shapes must match the
core S3Api protocol.
Route catalog
catalog() returns, for every named route, which operations are on and the
upload policy. useUpload loads it before every upload and checks files
against fileTypes and maxFileSize. multipart turns on multipart for large
files, and checksum makes single-shot uploads send a SHA-256.
catalog is required. If it fails, every file of the upload fails with its
error.
Prop
Type
Prop
Type
import type { RouteCatalogResponse, S3RequestOptions } from "@dimah-s3/core";
import { $fetch, call } from "./fetch";
export const catalog = (options?: S3RequestOptions) =>
$fetch<RouteCatalogResponse>("/routes", { method: "GET", ...call(options) });Upload transport
The browser sends single-shot uploads to presign.url itself. To replace that
transfer, for example with an in-memory backend in tests, add
uploadTransport to the object. It must send presign.fields or
presign.headers exactly, and reject with an APIError so the engine can
retry: UPLOAD_FAILED with S3's status, CHECKSUM_MISMATCH when S3 rejects
the bytes' checksum (never retried), or NETWORK_ERROR.
Prop
Type