# Bring Your Own Backend (https://v2.s3.dimah.dev/docs/react/custom-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`](https://v2.s3.dimah.dev/docs/server). Mount the
result with `S3Provider` as shown in [React Setup](https://v2.s3.dimah.dev/docs/react/setup).

## Contract [#contract]

- **Every method takes `(payload, options?)`.** `options` is `{ signal, headers }`. Forward `signal` to 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 a `key`: your backend picks it and returns it.
- **Issue a ticket.** `upload` and `multipart.init` return a `ticket`. `confirm` and 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 `APIError`** with a [catalog code](https://v2.s3.dimah.dev/docs/server/errors). The hooks retry `NETWORK_ERROR` and `S3_NETWORK_ERROR`, and show codes with `useFormatDimahError`. Anything else becomes `UNKNOWN_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 [#flows]

| Flow           | Methods                                                             | Doc                                               |
| -------------- | ------------------------------------------------------------------- | ------------------------------------------------- |
| Route policies | `catalog`                                                           | [Route catalog](#route-catalog)                   |
| Simple upload  | `upload` → browser uploads to S3 → `confirm`                        | [Upload](https://v2.s3.dimah.dev/docs/react/custom-backend/upload)       |
| Download       | `download`                                                          | [Download](https://v2.s3.dimah.dev/docs/react/custom-backend/download)   |
| Delete         | `delete` or `deleteMany`                                            | [Delete](https://v2.s3.dimah.dev/docs/react/custom-backend/delete)       |
| Multipart      | `init` → `signPart` → `complete` or `abort` · `listParts` to resume | [Multipart](https://v2.s3.dimah.dev/docs/react/custom-backend/multipart) |

## Skeleton [#skeleton]

One fetcher serves every method. `call` forwards the per-call signal and
headers:

```ts title="lib/s3-api/fetch.ts"
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:

```ts title="lib/s3-api/index.ts"
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](https://v2.s3.dimah.dev/docs/core).

## Route catalog [#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.

<AutoTypeTable
  path="packages/core/src/schema/catalog.ts"
  name="RouteCatalogResponse"
/>

<AutoTypeTable
  path="packages/core/src/schema/catalog.ts"
  name="RouteCatalogEntry"
/>

```ts title="lib/s3-api/catalog.ts"
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 [#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`.

<AutoTypeTable
  path="packages/react/src/types/upload.ts"
  name="S3ApiUploadTransport"
/>
