Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/),
и этот проект придерживается [Semantic Versioning](https://semver.org/lang/ru/).

## [3.1.0] - 2025-11-06

### Добавлено

- Добавлена утилита `parseApiRequest` для упрощения создания запросов к API на основе спецификации OpenAPI и эндпоинтов

### Исправлено

- Исправлена генерация типов в корневой папке проекта при использовании команды генерации типов

## [3.0.0] - 2025-10-30

### Изменено
Expand Down
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,72 @@ async getEncryptedData(): Promise<ApiResponseCrypted> {
}
```

## Утилита: parseApiRequest

`parseApiRequest` помогает собрать корректный `Request` для `fetch` из структурированного описания запроса: базовый URL, путь с параметрами, метод, заголовки, тело и query-параметры.

Импорт:

```ts
import { parseApiRequest } from '@gratio/api';
```

### Пример: GET с path и query параметрами

```ts
const request = parseApiRequest({
baseUrl: 'https://api.example.com',
path: '/users/{id}',
method: 'get',
status: '200',
pathParams: { id: '123' },
queryParams: { search: 'john', limit: '10' },
headers: { Authorization: `Bearer ${token}` },
});

const response = await fetch(request);
const data = await response.json();
```

### Пример: POST с JSON-телом

```ts
type CreateUserDto = { name: string; email: string };

const request = parseApiRequest({
baseUrl: 'https://api.example.com',
path: '/users',
method: 'post',
status: '201',
body: { name: 'John', email: 'john@example.com' } satisfies CreateUserDto,
});

const response = await fetch(request);
```

### Пример: POST с FormData

```ts
const form = new FormData();
form.append('avatar', file);

const request = parseApiRequest({
baseUrl: 'https://api.example.com',
path: '/users/{id}/avatar',
method: 'post',
status: '200',
pathParams: { id: '123' },
body: form, // Content-Type проставлять не нужно — браузер выставит boundary автоматически
});

await fetch(request);
```

Примечания:

- Если указан `FormData`, заголовок `Content-Type` не добавляется вручную.
- Путь может включать параметры вида `{id}` — они будут подставлены из `pathParams` с URL-экранированием. Query-параметры добавляются из `queryParams`.

## `@gratio/api gen types --help`

```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gratio/api",
"version": "3.0.0",
"version": "3.1.0",
"description": "TypeScript common types for Gratio projects API responses and requests",
"homepage": "https://github.com/Gratio-tech/Api#readme",
"packageManager": "bun@1.2.17",
Expand Down
4 changes: 3 additions & 1 deletion src/commands/gen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ export default defineCommand({
}

if (typeof outputPath === 'string') {
mkdirSync(dirname(outputPath), { recursive: true });
const dirName = dirname(outputPath);
if (dirName !== '.') mkdirSync(dirName, { recursive: true });

await writeFile(outputPath, contents);
console.info(`✓ ${outputPath}`);
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from '~/utils/parseApiRequest';
export * from '~/types/api';
41 changes: 41 additions & 0 deletions src/utils/parseApiRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
interface ApiResponse {
path: string;
method: string;
status: string;
}

interface ApiRequest extends ApiResponse {
baseUrl: string;
headers?: Record<string, string>;
body?: Record<string, unknown> | FormData;
queryParams?: Record<string, string>;
pathParams?: Record<string, string>;
}

/** Parse ApiRequest to a Request object. */
export function parseApiRequest(request: ApiRequest): Request {
// Parse query parameters
const url = new URL(request.path, request.baseUrl);
if (request.queryParams) {
for (const [key, value] of Object.entries(request.queryParams)) {
url.searchParams.append(key, String(value));
}
}

// Parse path parameters
for (const [key, value] of Object.entries(request.pathParams || {})) {
url.pathname = url.pathname.replace(`{${key}}`, encodeURIComponent(String(value)));
}

// Parse headers
const headers = new Headers(request.headers || {});
if (request.body && !(request.body instanceof FormData)) {
headers.append('Content-Type', 'application/json');
}

return new Request(url.toString(), {
method: request.method.toUpperCase(),
headers,
body: request.body ? (request.body instanceof FormData ? request.body : JSON.stringify(request.body)) : undefined,
});
}
4 changes: 3 additions & 1 deletion src/utils/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export function createTemplate(path: string, isRaw = false) {

const write = (outputPath: string): void => {
try {
mkdirSync(dirname(outputPath), { recursive: true });
const dirName = dirname(outputPath);
if (dirName !== '.') mkdirSync(dirName, { recursive: true });

writeFileSync(outputPath, template);
console.info(`✓ ${outputPath}`);
} catch {
Expand Down