feat: сделать split-клиент режимом по умолчанию
- добавлен operationsTree для сборки полного клиента - удален режим генерации both - обновлена документация под npm SDK workflow - поднята версия пакета до 4.0.0
This commit is contained in:
306
README.md
306
README.md
@@ -1,49 +1,36 @@
|
||||
# @gromlab/api-codegen
|
||||
|
||||
CLI утилита для генерации TypeScript REST-клиента из OpenAPI спецификации.
|
||||
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 <INPUT> -o <OUTPUT> [-n <NAME>] [--mode single|split|both]
|
||||
npx @gromlab/api-codegen -i ./openapi.json -o ./src/generated
|
||||
```
|
||||
|
||||
**Аргументы:**
|
||||
- `-i, --input <path>` - путь к OpenAPI файлу или URL
|
||||
- `-o, --output <path>` - директория для сохранения файлов
|
||||
- `-n, --name <name>` - имя монолитного файла без `.ts`
|
||||
- `--mode <mode>` - режим генерации: `single`, `split`, `both` (по умолчанию `single`)
|
||||
- `--single-file` - устаревший алиас для `--mode single`
|
||||
|
||||
## Примеры
|
||||
Для генерации из URL:
|
||||
|
||||
```bash
|
||||
# Локальный файл
|
||||
npx @gromlab/api-codegen -i ./openapi.json -o ./src/api
|
||||
|
||||
# URL на спецификацию
|
||||
npx @gromlab/api-codegen -i https://httpbin.org/spec.json -o ./src/api
|
||||
|
||||
# Монолитный файл с кастомным именем
|
||||
npx @gromlab/api-codegen -i ./openapi.json -o ./src/api -n MyApi
|
||||
|
||||
# Разложенный tree-shaking friendly клиент
|
||||
npx @gromlab/api-codegen -i ./openapi.json -o ./src/api --mode split
|
||||
|
||||
# Монолит и разложенный клиент одновременно
|
||||
npx @gromlab/api-codegen -i ./openapi.json -o ./src/api -n MyApi --mode both
|
||||
npx @gromlab/api-codegen -i https://api.example.com/openapi.json -o ./src/generated
|
||||
```
|
||||
|
||||
## Структура вывода
|
||||
По умолчанию будет создан split-клиент.
|
||||
|
||||
По умолчанию генерируется legacy монолитный клиент для обратной совместимости:
|
||||
|
||||
```text
|
||||
generated/
|
||||
└── MyApi.ts
|
||||
```
|
||||
|
||||
В режиме `split` генерируется tree-shaking friendly структура:
|
||||
## Что Генерируется
|
||||
|
||||
```text
|
||||
generated/
|
||||
@@ -51,39 +38,122 @@ generated/
|
||||
├── data-contracts.ts
|
||||
├── http-client.ts
|
||||
├── index.ts
|
||||
├── operations-tree.ts
|
||||
└── operations/
|
||||
├── index.ts
|
||||
├── get-users.ts
|
||||
└── create-user.ts
|
||||
```
|
||||
|
||||
Каждый endpoint лежит в отдельном файле внутри `operations/`.
|
||||
Основные части:
|
||||
|
||||
В режиме `both` генерируются оба варианта рядом.
|
||||
- `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.
|
||||
|
||||
## Пример Использования
|
||||
## Полный Клиент
|
||||
|
||||
Для `single` режима:
|
||||
Если приложению нужен весь API, используйте `operationsTree`.
|
||||
|
||||
```typescript
|
||||
import { Api, HttpClient } from './generated/MyApi';
|
||||
import { createApiClient, HttpClient, operationsTree } from './generated';
|
||||
|
||||
const http = new HttpClient({ baseUrl: 'https://api.example.com' });
|
||||
const api = new Api(http);
|
||||
// 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 });
|
||||
```
|
||||
|
||||
Для `split` режима:
|
||||
`operationsTree` намеренно лежит в отдельном файле и экспортируется отдельно. Импортируя его, вы явно выбираете полный клиент со всеми операциями.
|
||||
|
||||
## Частичный Клиент
|
||||
|
||||
Если приложению нужна только часть API, соберите клиент вручную.
|
||||
|
||||
```typescript
|
||||
import { createApiClient, HttpClient } from './generated';
|
||||
import { getAll } from './generated/operations/get-all';
|
||||
import { create } from './generated/operations/create';
|
||||
import {
|
||||
v1AdminPharmaciesCreate,
|
||||
v1AdminPharmaciesList,
|
||||
v1AdminPharmaciesProfileUpdate,
|
||||
} from './generated/operations';
|
||||
|
||||
const http = new HttpClient({
|
||||
baseUrl: 'https://api.example.com',
|
||||
securityWorker: (securityData: { token: string } | null) => {
|
||||
});
|
||||
|
||||
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<SecurityData>({
|
||||
baseUrl: 'https://api.example.com',
|
||||
baseApiParams: {
|
||||
headers: {
|
||||
'X-App-Version': '1.0.0',
|
||||
},
|
||||
},
|
||||
securityWorker: (securityData) => {
|
||||
if (!securityData?.token) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -96,44 +166,144 @@ const http = new HttpClient({
|
||||
},
|
||||
});
|
||||
|
||||
const api = createApiClient(http, {
|
||||
users: {
|
||||
getAll,
|
||||
create,
|
||||
},
|
||||
});
|
||||
|
||||
const users = await api.users.getAll({});
|
||||
const createdUser = await api.users.create({ email, password });
|
||||
http.setSecurityData({ token: 'jwt-token' });
|
||||
```
|
||||
|
||||
Если нужен максимально точечный импорт, operation можно вызвать напрямую:
|
||||
Для отдельного вызова можно передать дополнительные request params последним аргументом operation.
|
||||
|
||||
```typescript
|
||||
import { HttpClient } from './generated/http-client';
|
||||
import { getAll } from './generated/operations/get-all';
|
||||
|
||||
const http = new HttpClient();
|
||||
const users = await getAll(http, {});
|
||||
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
|
||||
bun run build
|
||||
api-codegen -i ./openapi.json -o ./src/generated
|
||||
```
|
||||
|
||||
### Тестирование
|
||||
Эквивалентно:
|
||||
|
||||
```bash
|
||||
bun test
|
||||
bun run test:unit
|
||||
bun run test:integration
|
||||
api-codegen -i ./openapi.json -o ./src/generated --mode split
|
||||
```
|
||||
|
||||
Подробная документация по тестированию в [`tests/README.md`](tests/README.md).
|
||||
Используйте этот режим для новых проектов, 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 <input> -o <output> [--mode split|single] [-n <name>]
|
||||
```
|
||||
|
||||
Аргументы:
|
||||
|
||||
- `-i, --input <path>` - путь к OpenAPI файлу или URL.
|
||||
- `-o, --output <path>` - директория для generated файлов.
|
||||
- `--mode <mode>` - режим генерации: `split` или `single`. По умолчанию `split`.
|
||||
- `-n, --name <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
|
||||
```
|
||||
|
||||
## Лицензия
|
||||
|
||||
|
||||
Reference in New Issue
Block a user