# @dimah-s3/core (https://v1.s3.dimah.dev/docs/core)

`@dimah-s3/core` is the protocol source of truth shared by
`@dimah-s3/server` and `@dimah-s3/react`.

## What lives here [#what-lives-here]

- **`S3_API_ROUTES` / `S3_API_BASE_PATH`** — canonical route paths
- **`createS3Client`** — browser/isomorphic client implementing `S3Api` (+ client plugins). Exposes `$ERROR_CODES`, `$fetch`, `$Infer`. `baseURL` wins over `basePath`. Upload and download `expiresIn` default to **600** seconds (`S3_DEFAULT_EXPIRES_IN`) on the server.
- **`APIError`** — error class with `status` and `body`. Serialized as JSON `{ message, code?, params? }`. Use `APIError.from(status, S3_ERROR_CODES.FORBIDDEN)` or `isAPIError`. Catalog: [Errors](https://v1.s3.dimah.dev/docs/server/errors).
- **`s3FetchErrorSchema`** — Zod schema for that JSON error body
- **Client plugin helpers** — `defineClientPlugin`, `createS3Fetch`, `pluginPath`
- **Pure file helpers** — `validateFile`, `formatFileSize`, `buildContentDisposition`, …

The browser client (`createS3Client`) uses nested calls
(`api.download({ route, key })`, `api.multipart.init`). The server `s3.api`
uses query-based handlers (`s3.api.download({ query })`) and the same nested
aliases (`s3.api.multipart.init`).
The protocol uses these paths under `basePath` (default `/api/s3`):

| Constant             | Method | Path                          |
| -------------------- | ------ | ----------------------------- |
| `upload`             | POST   | `/presign/upload`             |
| `uploadConfirm`      | POST   | `/presign/upload/confirm`     |
| `download`           | GET    | `/presign/download`           |
| `file`               | GET    | `/file`                       |
| `catalog`            | GET    | `/routes`                     |
| `delete`             | DELETE | `/delete`                     |
| `deleteBatch`        | POST   | `/delete/batch`               |
| `multipartInit`      | POST   | `/presign/multipart/init`     |
| `multipartPart`      | POST   | `/presign/multipart/part`     |
| `multipartListParts` | GET    | `/presign/multipart/parts`    |
| `multipartComplete`  | POST   | `/presign/multipart/complete` |
| `multipartAbort`     | POST   | `/presign/multipart/abort`    |

`file` is the server-side proxy target returned by `download` when its mode is
`"proxy"`; it is not a separate `S3Api` client method.

## Quick start [#quick-start]

```ts
import { createS3Client } from "@dimah-s3/core";
import { dbClient } from "@dimah-s3/db/client";

export const api = createS3Client({
  basePath: "/api/s3",
  credentials: "include", // [!code ++]
  plugins: [dbClient()], // [!code ++]
});

await api.upload({
  route: "avatar",
  fileName: "avatar.png",
  contentType: "image/png",
  fileSize: 1024,
});
await api.db.listObjects({ limit: 20, offset: 0 });
```

For React apps prefer `createS3Client` from `@dimah-s3/react` — same options, plus a bound `Provider` / typed `useApi` on the client object.

## Client plugins [#client-plugins]

```ts
import { defineClientPlugin, pluginPath } from "@dimah-s3/core";

export function myClient() {
  return defineClientPlugin({
    id: "my",
    getActions: ($fetch) => ({
      ping: () => $fetch(pluginPath("my", "ping"), { method: "GET" }),
    }),
  });
}
```

Paths must match the server plugin endpoint (e.g. `createS3Endpoint("/my/ping", …)`).

## Validation [#validation]

`validateFile` returns `{ code, message, params? }` (or `null`). On the client, `useFormatValidateFileError` maps `code` to a UI string.
