2.0 beta — new releases can include breaking changes. Stable 1.x docs
dimah-s3v2.0.0-beta.0
Bring Your Own Backend

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/*.

lib/s3-api/multipart.ts
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.

On this page