Files
2026-08-01 09:31:08 +03:00

343 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Автогенерация REST-клиента
description: Генерация split REST-клиента из OpenAPI-спецификации.
keywords: [rest, openapi, api-codegen, автогенерация, generated, split, operations, npx]
---
# Автогенерация REST-клиента
Генерация REST-клиента из OpenAPI-спецификации.
## Когда использовать
Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт split-клиент: HTTP-клиент, типы и отдельную operation-функцию на каждый endpoint. Разработчик вручную добавляет транспорт в `client.ts`, полный API в `rest-api.ts` и GET-хуки.
По умолчанию генерация идёт внутрь infra-модуля приложения в `{name}-rest-api/generated` — этот сценарий описан ниже. Если клиент нужен нескольким приложениям, generated-код выносится в отдельный пакет `{name}-rest-api-sdk`: [SDK-пакет REST-клиента](./sdk.md).
## Пример API
В примерах используется Swagger Petstore:
```text
https://petstore3.swagger.io/api/v3/openapi.json
```
Имена модуля:
```text
src/infra/pet-store-rest-api/
petStoreRestApi
```
## Скрипт генерации
`@gromlab/api-codegen` не устанавливается в `devDependencies`. Используем `npx @gromlab/api-codegen@latest`, чтобы запускать свежую версию.
```json
{
"scripts": {
"codegen:pet-store-rest-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infra/pet-store-rest-api/generated"
}
}
```
Параметры:
- `-i` — путь к OpenAPI-спецификации: URL или локальный файл.
- `-o` — директория для сгенерированного split-клиента.
По умолчанию генератор работает в режиме `split`. Legacy-режим `--mode single -n <имя>` генерирует один монолитный файл и используется только в старых проектах, которые уже завязаны на монолитный generated-клиент. В новых проектах single-режим не используется.
Генератор не создаёт SWR-хуки. GET-хуки REST-клиента пишутся вручную, чтобы сохранить проектный контракт: один GET-хук = одна GET operation, без бизнес-логики и композиции.
## Генерация
```bash
npm run codegen:pet-store-rest-api
```
Ожидаемый результат:
```text
src/infra/pet-store-rest-api/generated/
├── create-api-client.ts
├── data-contracts.ts
├── http-client.ts
├── index.ts
├── operations-tree.ts
└── operations/
├── index.ts
├── get-pet-by-id.ts
└── find-pets-by-status.ts
```
Основные части:
- `http-client.ts` — fetch-based `HttpClient`: `baseUrl`, заголовки, авторизация и хуки транспорта.
- `data-contracts.ts` — TypeScript-типы из OpenAPI schemas, включая `*Params`-типы операций.
- `operations/*.ts` — отдельная typed operation-функция на каждый endpoint.
- `operations-tree.ts` — дерево всех операций для сборки полного клиента.
- `create-api-client.ts``createApiClient`, который привязывает дерево операций к `HttpClient`.
- `index.ts` — входная точка generated-кода, реэкспортирует всё перечисленное.
Файлы в `generated/` не правятся руками и коммитятся в репозиторий.
Enum-значения в split-режиме генерируются как union-типы строковых литералов. Отдельных runtime enum в `generated/` нет: в коде используются строковые литералы вроде `'available'`, а тип проверяет их допустимость.
## Проверка операций
После генерации откройте `generated/operations-tree.ts` и проверьте фактические имена operation-функций и структуру дерева.
Для Petstore нужны GET-операции вида:
```ts
import { findPetsByStatus } from './generated/operations/find-pets-by-status'
import { getPetById } from './generated/operations/get-pet-by-id'
```
Точечная operation вызывается через настроенный `HttpClient`:
```ts
getPetById(petStoreHttpClient, { petId: 10 })
findPetsByStatus(petStoreHttpClient, { status: 'available' })
```
После сборки полного клиента в `rest-api.ts` те же операции доступны как bound API:
```ts
petStoreRestApi.pet.findPetsByStatus({ status: 'available' })
petStoreRestApi.pet.getPetById({ petId: 10 })
```
Имена операций и группировка дерева зависят от `operationId` и тегов OpenAPI-схемы. В рабочих задачах всегда сверяйтесь с `generated/operations-tree.ts` и `generated/data-contracts.ts`.
## Источник Imports
Если generated-код лежит внутри infra-модуля, импортируйте operation из локального `generated/`:
```ts
import { getPetById } from '../generated/operations/get-pet-by-id'
```
Если generated-код вынесен в SDK-пакет, импортируйте operation через subpath export пакета:
```ts
import { getPetById } from '@company/pet-store-rest-api-sdk/operations/get-pet-by-id'
```
Не импортируйте весь `operations` namespace ради одной операции.
## Алгоритм для агента
После генерации агент должен действовать по шагам:
1. Открыть `generated/operations-tree.ts` и найти фактические имена нужных operation-функций.
2. Для каждой нужной операции найти тип параметров и тип ответа в `generated/data-contracts.ts`.
3. Создать или обновить `client.ts`: настроить и экспортировать только `*HttpClient`.
4. Создать или обновить `rest-api.ts`: собрать полный `*RestApi` через `createApiClient(*HttpClient, operationsTree)`.
5. Создать GET-хуки только для реально нужных GET-операций, не для всех операций API на всякий случай.
6. В каждом GET-хуке импортировать точечную operation из `operations/<operation-file>`.
7. Для каждого GET-хука создать key-функцию формата `[serviceName, endpoint]`.
8. В key-функции вернуть `null`, если обязательные параметры не готовы.
9. В хуке принять `params?: GeneratedParams | null` и `config?: SWRConfiguration<Data>`.
10. В fetcher вызвать operation через общий `*HttpClient`: `operation(nameHttpClient, params as GeneratedParams, requestParams?)`.
11. Экспортировать хук и key-функцию из `hooks/index.ts`; если это первый hook, добавить в `hooks/index.ts` директиву `'use client'`.
12. Экспортировать наружу только нужные generated-типы, DTO, `*HttpClient`, `*RestApi` и `hooks` через корневой `index.ts`.
Что агент не должен делать:
- Не править файлы в `generated/` руками.
- Не импортировать операции и `HttpClient` из `generated/` или SDK в UI/components.
- Не импортировать `operationsTree` или полный `*RestApi` в GET-хуки.
- Не добавлять GET-хуки для POST, PUT, PATCH, DELETE.
- Не добавлять бизнес-флаги, тосты, редиректы и UI-состояние в GET-хук.
- Не создавать словари enum-маппинга внутри GET-хука.
- Не объявлять DTO и response-типы в файле хука.
- Не вызывать `useSWR` условно.
- Не добавлять `throw` в fetcher для неготовых params.
## `client.ts`
`client.ts` содержит только настроенный транспортный `HttpClient`.
```ts
// src/infra/pet-store-rest-api/client.ts
import { HttpClient } from './generated'
export const petStoreHttpClient = new HttpClient({
baseUrl: 'https://example.com/api',
})
```
`client.ts` не содержит расширения типов, `declare module`, `Extended`-типы, GET-хуки, `operationsTree`, `createApiClient` и бизнес-логику.
Авторизация, refresh token, логирование и другие настройки транспорта задаются опциями и хуками `HttpClient` в этом же файле: [Кастомизация HTTP-клиента](./http-client.md).
## `rest-api.ts`
`rest-api.ts` собирает полный bound API-клиент из всего generated дерева операций.
```ts
// src/infra/pet-store-rest-api/rest-api.ts
import { createApiClient, operationsTree } from './generated'
import { petStoreHttpClient } from './client'
export const petStoreRestApi = createApiClient(petStoreHttpClient, operationsTree)
```
Импорт `operationsTree` означает полный клиент: в bound API доступны все операции API.
Если generated-код вынесен в SDK-пакет, используйте subpath exports:
```ts
// src/infra/pet-store-rest-api/rest-api.ts
import { createApiClient } from '@company/pet-store-rest-api-sdk/create-api-client'
import { operationsTree } from '@company/pet-store-rest-api-sdk/operations-tree'
import { petStoreHttpClient } from './client'
export const petStoreRestApi = createApiClient(petStoreHttpClient, operationsTree)
```
## GET-хуки
GET-хуки пишутся вручную после проверки generated-операций.
Пример для операции `getPetById`:
```ts
// src/infra/pet-store-rest-api/hooks/use-get-pet-detail.hook.ts
import { getPetById } from '../generated/operations/get-pet-by-id'
import type { SWRConfiguration } from 'swr'
import useSWR from 'swr'
import { petStoreHttpClient } from '../client'
import type { GetPetByIdParams, Pet } from '../generated'
export const getPetDetailKey = (params?: GetPetByIdParams | null) => {
if (!params?.petId) {
return null
}
return ['pet-store-rest-api', `/pet/${params.petId}`] as const
}
/**
* Получает детальную карточку питомца с кешированием результата.
*/
export const useGetPetDetail = (
params?: GetPetByIdParams | null,
config?: SWRConfiguration<Pet>,
) => {
const key = getPetDetailKey(params)
const fetcher = () => getPetById(petStoreHttpClient, params as GetPetByIdParams)
return useSWR<Pet>(key, fetcher, config)
}
```
Типы импортируются как `import type` из `./generated`: generated `index.ts` реэкспортирует все типы из `data-contracts.ts`.
Подробный контракт key-функций, `params`, `config` и запретов описан в разделе [GET-хуки REST-клиента](../../SKILL.md#get-хуки-rest-клиента).
## Расширение сгенерированных типов
Файлы в `generated/` не правятся руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
```text
src/infra/biocad-less-rest-api/
├── generated/
│ ├── data-contracts.ts
│ ├── operations/
│ └── index.ts
├── types/
│ ├── term.ts
│ └── index.ts
├── client.ts
├── rest-api.ts
└── index.ts
```
Пример расширения generated-типа:
```ts
// src/infra/biocad-less-rest-api/types/term.ts
import type { TermRecordItem } from '../generated/data-contracts'
declare module '../generated/data-contracts' {
interface TermRecordItem {
media?: {
file?: string
title?: string
url?: string
}
}
}
export type TermRecordItemExtended = Omit<
TermRecordItem,
'categories' | 'tags' | 'fields'
> & {
categories?: Array<{
_id?: string
id?: string
slug?: string
name?: string
}>
tags?: Array<{
_id?: string
id?: string
slug?: string
name?: string
}>
fields?: Record<string, unknown>
}
```
```ts
// src/infra/biocad-less-rest-api/types/index.ts
export type { TermRecordItemExtended } from './term'
```
`declare module` нацеливается на `../generated/data-contracts` — именно там живут generated-интерфейсы. Он используется для добавления отсутствующих полей. `Extended`-тип используется, когда нужно переопределить неточные поля, не трогая generated-файлы.
## Публичный API
```ts
// src/infra/pet-store-rest-api/index.ts
export { petStoreHttpClient } from './client'
export { petStoreRestApi } from './rest-api'
export type {
FindPetsByStatusParams,
GetPetByIdParams,
Pet,
} from './generated'
export * from './hooks'
```
Наружу импортируют только из `infra/pet-store-rest-api`, не из `generated/`.
Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`:
```ts
// src/infra/biocad-less-rest-api/index.ts
export type { TermRecordItemExtended } from './types'
```
## Регенерация
При изменении OpenAPI-схемы:
```bash
npm run codegen:pet-store-rest-api
```
Что меняется:
- Папка `generated/` — перезаписывается генератором целиком.
- `client.ts`, `rest-api.ts`, `hooks/`, `types/`, `index.ts` — не трогаются автоматически.
Если после регенерации поменялись имена операций, сигнатуры или типы, это исправляется в ручном коде модуля: `rest-api.ts`, GET-хуки, minimal clients и реэкспорты.
## Следующий шаг
После генерации настройте транспорт в `client.ts` по разделу [Кастомизация HTTP-клиента](./http-client.md), соберите полный API в `rest-api.ts`, затем проверьте [использование REST-клиента](../../SKILL.md#использование-rest-клиента) или добавьте [GET-хук REST-клиента](../../SKILL.md#get-хуки-rest-клиента) для Client Components.