# Multipart (https://v2.s3.dimah.dev/docs/react/custom-backend/multipart)

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 [#init]

Same file fields as a [simple upload](https://v2.s3.dimah.dev/docs/react/custom-backend/upload),
without `checksum`.

<AutoTypeTable
  path="packages/core/src/types/requests.ts"
  name="MultipartInitPayload"
/>

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.

<AutoTypeTable
  path="packages/core/src/types/responses.ts"
  name="MultipartInitResponse"
/>

## Sign part [#sign-part]

<AutoTypeTable
  path="packages/core/src/types/requests.ts"
  name="MultipartSignPartPayload"
/>

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.

<AutoTypeTable
  path="packages/core/src/types/responses.ts"
  name="MultipartPartResponse"
/>

## List parts, complete, and abort [#list-parts-complete-and-abort]

These three take only the ticket.

<AutoTypeTable
  path="packages/core/src/types/requests.ts"
  name="TicketPayload"
/>

`listParts` returns the parts S3 already has, so a resumed upload sends only
the rest. Read every page of S3's `ListParts`.

<AutoTypeTable
  path="packages/core/src/types/responses.ts"
  name="MultipartListPartsResponse"
/>

`complete` lists the parts on the server, completes the upload, and returns
the object `HeadObject` reports, plus the upload id.

<AutoTypeTable
  path="packages/core/src/types/responses.ts"
  name="MultipartCompleteResponse"
/>

`abort` ends the session. The browser calls it when the user cancels.

<AutoTypeTable
  path="packages/core/src/types/responses.ts"
  name="MultipartAbortResponse"
/>

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 [#example]

[`@dimah-s3/server`](https://v2.s3.dimah.dev/docs/server) mounts these under
`/api/s3/presign/multipart/*`.

```ts title="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](https://v2.s3.dimah.dev/docs/react/custom-backend#skeleton).
