# TypeScript (/docs/typescript)



The package is written in TypeScript and ships its own types. Every type is exported from `@mrzr/api-client`.

## Typing responses [#typing-responses]

The generic on each method types `res.data`:

```ts
const { data } = await api.get<User[]>("/users");
//      ^? User[] | undefined
```

`data` is optional because a `204`, an empty body or a failed parse legitimately has none. With the default `throwError: true`, a failure has already thrown by the time you read it, so `data!` is safe after a successful `await`.

The generic also types the transforms: `afterFunc` and `beforeSelectOptions` receive the payload as `R`.

## Lists and ordering [#lists-and-ordering]

`ListResponse<T>` is the common paginated shape (Django REST Framework's), and `ordering` in `params` is typed against the **item**, not the wrapper:

```ts
import type { ListResponse } from "@mrzr/api-client";

const { data } = await api.get<ListResponse<User>>("/users", {
  params: { page: 2, ordering: { createdAt: "desc" } },   // keys come from User
});
data?.results;   // User[]
data?.count;     // number
data?.next;      // string | null
```

```ts
interface ListResponse<T> { count: number; next: string | null; previous: string | null; results: T[] }
type Ordering<T> = { [K in keyof T]?: "asc" | "desc" };
```

Other params stay untyped (`[key: string]: unknown`), so any query parameter is allowed.

## A typed API layer [#a-typed-api-layer]

Wrap each resource once, and components never see URLs:

```ts title="api/users.ts"
import { api } from "@/lib/api";
import type { ListResponse } from "@mrzr/api-client";

export interface User { id: number; name: string; email: string }
export type NewUser = Omit<User, "id">;

export const users = {
  list: (page = 1) => api.get<ListResponse<User>>("/users", { params: { page } }).then((r) => r.data!),
  byId: (id: number) => api.get<User>("/users/{id}", { addTemplateToUrl: { id } }).then((r) => r.data!),
  create: (input: NewUser) => api.post<User>("/users", input).then((r) => r.data!),
  update: (id: number, patch: Partial<NewUser>) =>
    api.patch<User>("/users/{id}", patch, { addTemplateToUrl: { id } }).then((r) => r.data!),
  remove: (id: number) => api.delete("/users/{id}", { addTemplateToUrl: { id } }),
};
```

## Typing the user [#typing-the-user]

`AuthState.user` is `unknown`, because it's whatever your server returns. Narrow it in one place:

```ts
import type { AuthState } from "@mrzr/api-client";

interface AppUser { id: number; email: string; role: "admin" | "member" }
export type AppAuthState = Omit<AuthState, "user"> & { user?: AppUser };

api.onAuthStateChange((s) => setAuth(s as AppAuthState));
```

## Narrowing errors [#narrowing-errors]

```ts
import { ApiError } from "@mrzr/api-client";

export const isStatus = (e: unknown, code: number): e is ApiError =>
  e instanceof ApiError && e.statusCode === code;

if (isStatus(err, 409)) showConflictDialog(err.message);
```

`ApiError` is a real subclass of `Error` with `name === "ApiError"`, so the name check also works across bundles. Its `errors` are typed `Record<string, string[]>`, and `data` is `unknown`.

## Plugin methods [#plugin-methods]

Methods a plugin adds are typed on the client automatically. For the `services` plugin, a misspelled service name is a compile error:

```ts
const api = createClient({ plugins: [services({ files: "https://files.example.com" })] });
api.service("files");   // ok
api.service("fiels");   // error
```

To type your own, see [adding typed methods](/docs/plugins#adding-typed-methods). `PluginExtensions<typeof plugins>` is the type of the methods a plugin list adds.

## Exported types [#exported-types]

| Type                                                                              | Is                                         |
| --------------------------------------------------------------------------------- | ------------------------------------------ |
| `ApiClient`                                                                       | The client `createClient` returns          |
| `ClientOptions`, `RequestConfig<T>`                                               | Client and per-request options             |
| `IRes<R>`                                                                         | The response envelope                      |
| `ApiError`                                                                        | The error class (a value, not just a type) |
| `HttpMethod`, `ResponseFormat`, `AuthMode`, `StorageKind`                         | String unions                              |
| `Params<T>`, `Ordering<T>`, `ListResponse<T>`                                     | Query and list helpers                     |
| `AuthState`, `TokenPair`, `TokenStorage`                                          | Auth state, tokens and storage adapters    |
| `TokenExtractor`, `TokenFieldMap`, `RefreshBodyConfig`                            | Custom token shapes                        |
| `CancelOptions`, `CancelSelector`, `CancelMatch`, `CancelScope`, `PendingRequest` | Cancellation                               |
| `SocketTokenOptions`                                                              | Options for `getSocketToken`               |
| `LogEntry`                                                                        | What `onLog` receives                      |
| `ApiPlugin<E>`, `PluginRequest`, `PluginExtensions<P>`                            | Plugins                                    |
| `ServiceOptions`, `ServiceDefinition`, `ServiceClient`                            | From `@mrzr/api-client/services`           |
