Multipart
Implement multipart init, part signing, resume, completion, and abort for a custom backend.
Large files upload in parts. Each part is a presigned PUT, and your backend runs the session: init, signing, complete, and abort.
Flow: init → signPart (per part) → complete or abort · listParts
to resume.
The browser uses multipart for files of at least 50 MiB when the route's
catalog entry has multipart: true.
Init
Same file fields as a simple upload,
without checksum.
Prop
Type
Your backend picks the part layout. Return partSize (every part but the
last) and partCount; the browser slices the file exactly that way. Set
checksum: true to make every part send a SHA-256. The ticket goes with
every later call, so it must identify the route, key, and upload id.
Prop
Type
Sign part
Prop
Type
Sign the part's exact byte size into the URL, and the checksum when there is
one. The browser sends exactly headers on the PUT. When a part URL expires
mid-upload, the browser calls signPart again rather than reusing it.
Prop
Type
List parts, complete, and abort
These three take only the ticket.
Prop
Type
listParts returns the parts S3 already has, so a resumed upload sends only
the rest. Read every page of S3's ListParts.
Prop
Type
complete lists the parts on the server, completes the upload, and returns
the object HeadObject reports, plus the upload id.
Prop
Type
abort ends the session. The browser calls it when the user cancels.
Prop
Type
When a resumed upload's ticket has expired, or the upload is gone, throw
UPLOAD_TICKET_EXPIRED, UPLOAD_TICKET_INVALID, or OBJECT_NOT_FOUND. The
browser then aborts, forgets the stored upload, and starts over.
Example
@dimah-s3/server mounts these under
/api/s3/presign/multipart/*.
import type {
MultipartAbortResponse,
MultipartCompleteResponse,
MultipartInitPayload,
MultipartInitResponse,
MultipartListPartsResponse,
MultipartPartResponse,
MultipartSignPartPayload,
TicketPayload,
S3RequestOptions,
} from "@dimah-s3/core";
import { $fetch, call } from "./fetch";
const post = <T>(path: string, body: object, options?: S3RequestOptions) =>
$fetch<T>(`/multipart${path}`, { method: "POST", body, ...call(options) });
export const init = (
payload: MultipartInitPayload,
options?: S3RequestOptions,
) => post<MultipartInitResponse>("/init", payload, options);
export const signPart = (
payload: MultipartSignPartPayload,
options?: S3RequestOptions,
) => post<MultipartPartResponse>("/part", payload, options);
export const listParts = (payload: TicketPayload, options?: S3RequestOptions) =>
post<MultipartListPartsResponse>("/parts", payload, options);
export const complete = (payload: TicketPayload, options?: S3RequestOptions) =>
post<MultipartCompleteResponse>("/complete", payload, options);
export const abort = (payload: TicketPayload, options?: S3RequestOptions) =>
post<MultipartAbortResponse>("/abort", payload, options);$fetch and call come from the
skeleton.