10 KiB
title, description, keywords
| title | description | keywords | ||||||
|---|---|---|---|---|---|---|---|---|
| Автогенерация REST-клиента | Генерация REST-клиента из OpenAPI-спецификации. |
|
Автогенерация REST-клиента
Генерация REST-клиента из OpenAPI-спецификации.
Когда использовать
Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт TypeScript-клиент, типы и методы API, а разработчик вручную добавляет настройку клиента и GET-хуки.
Пример API
В примерах используется Swagger Petstore:
https://petstore3.swagger.io/api/v3/openapi.json
Имена модуля:
src/infra/pet-store-api/
petStoreApi
pet-store-api.generated.ts
Скрипт генерации
@gromlab/api-codegen не устанавливается в devDependencies. Используем npx @gromlab/api-codegen@latest, чтобы запускать свежую версию.
{
"scripts": {
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infra/pet-store-api/generated -n pet-store-api.generated"
}
}
Параметры:
-i— путь к OpenAPI-спецификации: URL или локальный файл.-o— директория для сгенерированного файла.-n— имя сгенерированного файла без.ts.
Ключ --swr не используется. GET-хуки REST-клиента пишутся вручную, чтобы сохранить проектный контракт: один GET-хук = один GET-метод, без бизнес-логики и композиции.
Генерация
npm run codegen:pet-store-api
Ожидаемый результат:
src/infra/pet-store-api/generated/
└── pet-store-api.generated.ts
Сгенерированный файл не правится руками и коммитится в репозиторий.
Проверка методов
После генерации откройте generated/pet-store-api.generated.ts и проверьте фактические имена методов.
Для Petstore нужны GET-операции вида:
petStoreApi.pet.findPetsByStatus({ status: StatusEnum.Available })
petStoreApi.pet.getPetById({ petId: 10 })
Точные сигнатуры зависят от OpenAPI-схемы и версии генератора. В рабочих задачах всегда сверяйтесь с generated-файлом.
Алгоритм для агента
После генерации агент должен действовать по шагам:
- Открыть
generated/{service-name}.generated.ts. - Найти фактические имена GET-методов клиента.
- Для каждого нужного GET-метода найти generated-тип параметров и тип ответа.
- Создать или обновить
client.tsтолько для настройки транспорта и экспорта инстанса клиента. - Создать GET-хуки только для реально нужных GET-методов, не для всех методов API на всякий случай.
- Для каждого GET-хука создать key-функцию формата
[serviceName, endpoint]. - В key-функции вернуть
null, если обязательные параметры не готовы. - В хуке принять
params?: GeneratedParams | nullиconfig?: SWRConfiguration<Data>. - В fetcher вызвать generated-метод клиента с
params as GeneratedParams. - Экспортировать хук и key-функцию из
hooks/index.ts. - Экспортировать наружу только нужные generated-типы, generated enum, DTO и
hooksчерез корневойindex.ts.
Что агент не должен делать:
- Не использовать ключ
--swrгенератора. - Не править
generated/*.generated.tsруками. - Не добавлять GET-хуки для POST, PUT, PATCH, DELETE.
- Не добавлять бизнес-флаги, тосты, редиректы и UI-состояние в GET-хук.
- Не создавать словари enum-маппинга внутри GET-хука.
- Не объявлять DTO и response-типы в файле хука.
- Не вызывать
useSWRусловно. - Не добавлять
throwв fetcher для неготовых params.
client.ts
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
// src/infra/pet-store-api/client.ts
import { Api, HttpClient } from './generated/pet-store-api.generated'
const baseUrl = process.env.NEXT_PUBLIC_PET_STORE_API_BASE_URL
if (!baseUrl) {
throw new Error('NEXT_PUBLIC_PET_STORE_API_BASE_URL is required')
}
const httpClient = new HttpClient({
baseUrl,
baseApiParams: {
secure: false,
headers: {
'Content-Type': 'application/json',
},
},
})
export const petStoreApi = new Api(httpClient)
Локальное значение NEXT_PUBLIC_PET_STORE_API_BASE_URL задаётся в .env.local. Не добавляйте fallback вроде ?? 'http://localhost:8080/api/v3' или ?? '': если env-переменная не задана, клиент должен падать с явной ошибкой конфигурации.
client.ts не содержит расширения типов, declare module и Extended-типы. Он только настраивает транспорт и экспортирует инстанс клиента.
GET-хуки
GET-хуки пишутся вручную после проверки generated-методов.
Пример для generated-метода petStoreApi.pet.getPetById({ petId }):
// src/infra/pet-store-api/hooks/use-get-pet-detail.hook.ts
import type { SWRConfiguration } from 'swr'
import useSWR from 'swr'
import { petStoreApi } from '../client'
import type { GetPetByIdParams, Pet } from '../generated/pet-store-api.generated'
export const getPetDetailKey = (params?: GetPetByIdParams | null) => {
if (!params?.petId) {
return null
}
return ['pet-store-api', `/pet/${params.petId}`] as const
}
/**
* Получает детальную карточку питомца с кешированием результата.
*/
export const useGetPetDetail = (
params?: GetPetByIdParams | null,
config?: SWRConfiguration<Pet>,
) => {
const key = getPetDetailKey(params)
const fetcher = () => petStoreApi.pet.getPetById(params as GetPetByIdParams)
return useSWR<Pet>(key, fetcher, config)
}
Подробный контракт key-функций, params, config и запретов описан в разделе GET-хуки REST-клиента.
Расширение сгенерированных типов
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (object, unknown, отсутствующее поле), расширения живут в types/.
src/infra/biocad-less-api/
├── generated/
│ └── biocad-less-api.generated.ts
├── types/
│ ├── term.ts
│ └── index.ts
├── client.ts
└── index.ts
Пример расширения generated-типа:
// src/infra/biocad-less-api/types/term.ts
import type { TermRecordItem } from '../generated/biocad-less-api.generated'
declare module '../generated/biocad-less-api.generated' {
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>
}
// src/infra/biocad-less-api/types/index.ts
export type { TermRecordItemExtended } from './term'
declare module используется для добавления отсутствующих полей в generated-интерфейс. Extended-тип используется, когда нужно переопределить неточные поля, не трогая generated-файл.
Публичный API
// src/infra/pet-store-api/index.ts
export { petStoreApi } from './client'
export type {
FindPetsByStatusParams,
GetPetByIdParams,
Pet,
} from './generated/pet-store-api.generated'
export { PetStatusEnum, StatusEnum } from './generated/pet-store-api.generated'
export * from './hooks'
Наружу импортируют только из infra/pet-store-api, не из generated/.
Если у модуля есть расширенные типы, они тоже реэкспортируются через index.ts:
// src/infra/biocad-less-api/index.ts
export type { TermRecordItemExtended } from './types'
Регенерация
При изменении OpenAPI-схемы:
npm run codegen:pet-store-api
Что меняется:
generated/pet-store-api.generated.ts— перезаписывается генератором.client.ts,hooks/,types/,index.ts— не трогаются автоматически.
Если после регенерации поменялись сигнатуры методов или типы, это исправляется в ручном коде модуля.
Следующий шаг
После генерации и настройки client.ts проверьте использование REST-клиента или добавьте GET-хук REST-клиента для Client Components.