# @gromlab/api-codegen CLI для генерации typed TypeScript REST SDK из OpenAPI спецификации. Генератор рассчитан на сценарий, где REST API можно вынести в отдельный npm-пакет, а приложения будут собирать из него свои API-клиенты: полный клиент, частичный клиент или точечные вызовы отдельных операций. ## Главная Идея По умолчанию `@gromlab/api-codegen` генерирует split-клиент. Каждый endpoint становится отдельной typed operation-функцией. Это позволяет импортировать только нужные методы и не тащить весь API в bundle приложения. Если нужен полный клиент, используйте `operationsTree`. Если нужен клиент только под конкретный экран, модуль или приложение, соберите дерево операций вручную. Если нужен максимально точечный вызов, импортируйте одну operation напрямую. ## Быстрый Старт ```bash npx @gromlab/api-codegen -i ./openapi.json -o ./src/generated ``` Для генерации из URL: ```bash npx @gromlab/api-codegen -i https://api.example.com/openapi.json -o ./src/generated ``` По умолчанию будет создан split-клиент. ## Что Генерируется ```text generated/ ├── create-api-client.ts ├── data-contracts.ts ├── http-client.ts ├── index.ts ├── operations-tree.ts └── operations/ ├── index.ts ├── get-users.ts └── create-user.ts ``` Основные части: - `http-client.ts` - fetch-based HTTP клиент, настройки авторизации, headers, baseUrl и transport customization. - `data-contracts.ts` - TypeScript типы из OpenAPI schemas. - `operations/*.ts` - отдельная typed function на каждый endpoint. - `operations/index.ts` - barrel export всех операций. - `operations-tree.ts` - дерево всех операций для сборки полного API клиента. - `create-api-client.ts` - helper, который привязывает дерево операций к конкретному HTTP клиенту. - `index.ts` - основная входная точка generated SDK. ## Полный Клиент Если приложению нужен весь API, используйте `operationsTree`. ```typescript import { createApiClient, HttpClient, operationsTree } from './generated'; // 1. Инициализация HTTP-клиента: baseUrl, headers, авторизация и transport-настройки. const http = new HttpClient({ baseUrl: 'https://api.example.com', }); // 2. Создание API-клиента: привязываем все сгенерированные операции к HTTP-клиенту. const api = createApiClient(http, operationsTree); // 3. Использование API-клиента: вызываем методы из дерева операций. const users = await api.users.getAll({}); const createdUser = await api.users.create({ email, password }); ``` `operationsTree` намеренно лежит в отдельном файле и экспортируется отдельно. Импортируя его, вы явно выбираете полный клиент со всеми операциями. ## Частичный Клиент Если приложению нужна только часть API, соберите клиент вручную. ```typescript import { createApiClient, HttpClient } from './generated'; import { v1AdminPharmaciesCreate, v1AdminPharmaciesList, v1AdminPharmaciesProfileUpdate, } from './generated/operations'; const http = new HttpClient({ baseUrl: 'https://api.example.com', }); const api = createApiClient(http, { pharmaciesAdmin: { list: v1AdminPharmaciesList, create: v1AdminPharmaciesCreate, updateProfile: v1AdminPharmaciesProfileUpdate, }, }); const pharmacies = await api.pharmaciesAdmin.list({}); const createdPharmacy = await api.pharmaciesAdmin.create(payload); await api.pharmaciesAdmin.updateProfile({ id }, payload); ``` Так можно собрать отдельный клиент для админки, публичного сайта, мобильного приложения или отдельного feature-модуля. `operations/index.ts` реэкспортит все operation-функции, поэтому можно делать named imports из `./generated/operations`. В ESM-сборке такой импорт остается tree-shaking friendly: в клиенте используются только явно выбранные операции. Если OpenAPI большой и полный SDK занимает десятки тысяч строк, приложение не обязано тянуть весь набор методов в свой чанк. Можно собрать минимально рабочий API-клиент под конкретный сценарий. Если нужен максимально строгий контроль над тем, какой файл попадет в bundle, импортируйте operation напрямую из ее файла: ```typescript import { v1AdminPharmaciesList } from './generated/operations/v1-admin-pharmacies-list'; ``` ## Точечный Вызов Operation Если по какой-то причине нужно вызвать только одну операцию, можно не собирать API-клиент через `createApiClient` и вызвать operation напрямую. ```typescript import { HttpClient } from './generated/http-client'; import { v1AdminPharmaciesList } from './generated/operations/v1-admin-pharmacies-list'; const http = new HttpClient({ baseUrl: 'https://api.example.com', }); const pharmacies = await v1AdminPharmaciesList(http, {}); ``` Это полезно для разовых сценариев: health-check, bootstrap-запроса, утилитного скрипта или кода, где полноценный объект `api` не нужен. ## Кастомный HTTP Клиент `HttpClient` создается отдельно и передается в generated operations. Это позволяет централизованно настраивать авторизацию, headers, baseUrl и transport behavior. ```typescript import { HttpClient } from './generated'; type SecurityData = { token: string; }; const http = new HttpClient({ baseUrl: 'https://api.example.com', baseApiParams: { headers: { 'X-App-Version': '1.0.0', }, }, securityWorker: (securityData) => { if (!securityData?.token) { return undefined; } return { headers: { Authorization: `Bearer ${securityData.token}`, }, }; }, }); http.setSecurityData({ token: 'jwt-token' }); ``` Для отдельного вызова можно передать дополнительные request params последним аргументом operation. ```typescript await api.users.getAll( {}, { headers: { 'X-Request-Id': requestId, }, }, ); ``` ## API Как npm-Пакет Типичный workflow: 1. Backend публикует OpenAPI спецификацию. 2. Отдельный пакет генерирует typed REST SDK в `src/generated`. 3. Пакет экспортирует generated entrypoint. 4. Приложения импортируют SDK и собирают нужные API-клиенты. Пример структуры пакета: ```text packages/my-api/ ├── package.json └── src/ ├── generated/ └── index.ts ``` `src/index.ts`: ```typescript export * from './generated'; ``` Использование в приложении: ```typescript import { createApiClient, HttpClient, operationsTree } from '@company/my-api'; const http = new HttpClient({ baseUrl: 'https://api.example.com', }); export const api = createApiClient(http, operationsTree); ``` Если приложение хочет выбрать только часть API: ```typescript import { createApiClient, HttpClient } from '@company/my-api'; import { v1AdminPharmaciesList, v1AdminPharmaciesProfileUpdate, } from '@company/my-api/operations'; export const api = createApiClient(new HttpClient(), { pharmaciesAdmin: { list: v1AdminPharmaciesList, updateProfile: v1AdminPharmaciesProfileUpdate, }, }); ``` ## Режимы Генерации ### split Режим по умолчанию. ```bash api-codegen -i ./openapi.json -o ./src/generated ``` Эквивалентно: ```bash api-codegen -i ./openapi.json -o ./src/generated --mode split ``` Используйте этот режим для новых проектов, npm SDK пакетов и приложений, где важны модульность и tree-shaking. ### single Legacy-режим, который генерирует один монолитный TypeScript файл. ```bash api-codegen -i ./openapi.json -o ./src/generated --mode single -n MyApi ``` Результат: ```text generated/ └── MyApi.ts ``` Используйте `single`, если проект уже завязан на старый монолитный generated-клиент. ## CLI ```bash api-codegen -i -o [--mode split|single] [-n ] ``` Аргументы: - `-i, --input ` - путь к OpenAPI файлу или URL. - `-o, --output ` - директория для generated файлов. - `--mode ` - режим генерации: `split` или `single`. По умолчанию `split`. - `-n, --name ` - имя файла для `single` режима без расширения `.ts`. - `--single-file` - устаревший алиас для `--mode single`. ## Примеры Команд Split из локального файла: ```bash api-codegen -i ./openapi.json -o ./src/generated ``` Split из URL: ```bash api-codegen -i https://api.example.com/openapi.json -o ./src/generated ``` Legacy single файл: ```bash api-codegen -i ./openapi.json -o ./src/generated --mode single -n MyApi ``` ## Лицензия MIT