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

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?). 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. 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

FlowMethodsDoc
Route policiescatalogRoute catalog
Simple uploadupload → browser uploads to S3 → confirmUpload
DownloaddownloadDownload
Deletedelete or deleteManyDelete
Multipartinit → signPart → complete or abort · listParts to resumeMultipart

Skeleton

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

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:

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.

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

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

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

On this page