Skip to content

Commit 6cbda0b

Browse files
committed
feat: React Query 제거 및 API 정의 시스템 도입
React Query 기반 훅 생성 시스템을 제거하고, 타입 안전한 API 정의 메타데이터 생성 시스템으로 전환하여 API 클라이언트 생성 파이프라인을 단순화합니다.
1 parent 8ee5eb8 commit 6cbda0b

21 files changed

Lines changed: 830 additions & 2327 deletions

API_DEFINITIONS_SPECIFICATION.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# API Definitions Specification
2+
3+
## Overview
4+
5+
This document describes how Bruno files are transformed into typed API definitions and factory functions for type-safe API consumption in TypeScript applications.
6+
7+
## Architecture
8+
9+
### 1. Generated File Structure
10+
11+
For each domain (based on Bruno folder structure), the following files are generated:
12+
13+
```
14+
src/apis/
15+
├── users/
16+
│ ├── api.ts # API factory with all endpoints
17+
│ ├── apiDefinitions.ts # Type metadata for each endpoint
18+
│ └── index.ts # Exports
19+
├── products/
20+
│ ├── api.ts
21+
│ ├── apiDefinitions.ts
22+
│ └── index.ts
23+
```
24+
25+
### 2. API Factory (api.ts)
26+
27+
**Purpose**: Provides typed API client functions grouped by domain.
28+
29+
**Example**:
30+
```typescript
31+
export const usersApi = {
32+
getProfile: async (params: { params?: Record<string, unknown> }): Promise<GetProfileResponse> => {
33+
const res = await axiosInstance.get<GetProfileResponse>(`/users/profile`, { params: params?.params });
34+
return res.data;
35+
},
36+
37+
updateProfile: async (params: { data: UpdateProfileRequest }): Promise<UpdateProfileResponse> => {
38+
const res = await axiosInstance.put<UpdateProfileResponse>(`/users/profile`, params.data);
39+
return res.data;
40+
},
41+
};
42+
```
43+
44+
**Type Safety Features**:
45+
- Full TypeScript type inference
46+
- Compile-time parameter validation
47+
- Response type checking
48+
49+
### 3. API Definitions (apiDefinitions.ts)
50+
51+
**Purpose**: Provides typed metadata about each API endpoint.
52+
53+
**Example**:
54+
```typescript
55+
import type { GetProfileResponse, UpdateProfileRequest, UpdateProfileResponse } from './api';
56+
57+
export const usersApiDefinitions = {
58+
getProfile: {
59+
method: 'GET' as const,
60+
path: '/users/profile' as const,
61+
pathParams: {} as Record<string, never>,
62+
queryParams: {} as Record<string, unknown>,
63+
body: {} as Record<string, never>,
64+
response: {} as GetProfileResponse,
65+
},
66+
67+
updateProfile: {
68+
method: 'PUT' as const,
69+
path: '/users/profile' as const,
70+
pathParams: {} as Record<string, never>,
71+
queryParams: {} as Record<string, never>,
72+
body: {} as UpdateProfileRequest,
73+
response: {} as UpdateProfileResponse,
74+
},
75+
} as const;
76+
77+
export type UsersApiDefinitions = typeof usersApiDefinitions;
78+
```
79+
80+
**Metadata Fields**:
81+
82+
| Field | Type | Description |
83+
|-------|------|-------------|
84+
| `method` | `'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'` | HTTP method |
85+
| `path` | `string` | URL path template |
86+
| `pathParams` | Type object | Path parameter types |
87+
| `queryParams` | Type object | Query parameter types |
88+
| `body` | Type object | Request body type |
89+
| `response` | Type object | Response type |
90+
91+
## Type Generation Rules
92+
93+
### 1. Path Parameters
94+
95+
**Bruno File**: `GET /users/:userId/posts/:postId`
96+
97+
**Generated Type**:
98+
```typescript
99+
pathParams: {} as { userId: string | number; postId: string | number }
100+
```
101+
102+
### 2. Query Parameters
103+
104+
**For GET requests**:
105+
```typescript
106+
queryParams: {} as Record<string, unknown>
107+
```
108+
109+
**For non-GET requests**:
110+
```typescript
111+
queryParams: {} as Record<string, never>
112+
```
113+
114+
### 3. Request Body
115+
116+
**For POST/PUT/PATCH with body**:
117+
```typescript
118+
body: {} as CreateUserRequest
119+
```
120+
121+
**For GET/DELETE or no body**:
122+
```typescript
123+
body: {} as Record<string, never>
124+
```
125+
126+
### 4. Response Types
127+
128+
**Generated from Bruno docs block**:
129+
```typescript
130+
response: {} as GetUserResponse
131+
```
132+
133+
**If no docs block**:
134+
```typescript
135+
response: {} as void
136+
```
137+
138+
## Usage Examples
139+
140+
### Direct API Calls
141+
142+
```typescript
143+
import { usersApi } from '@/apis/users';
144+
145+
// GET request
146+
const profile = await usersApi.getProfile({
147+
params: { includeDetails: true }
148+
});
149+
150+
// POST request
151+
const newUser = await usersApi.createUser({
152+
data: { name: 'John', email: 'john@example.com' }
153+
});
154+
155+
// PUT request with path params
156+
const updated = await usersApi.updatePost({
157+
postId: 123,
158+
data: { title: 'New Title' }
159+
});
160+
```
161+
162+
### With Custom Hooks
163+
164+
```typescript
165+
import { usersApi } from '@/apis/users';
166+
167+
export async function fetchProfile() {
168+
return usersApi.getProfile({});
169+
}
170+
```
171+
172+
### Using Type Metadata
173+
174+
```typescript
175+
import { usersApiDefinitions } from '@/apis/users';
176+
177+
// Extract types
178+
type ProfileResponse = typeof usersApiDefinitions.getProfile.response;
179+
type UpdateRequest = typeof usersApiDefinitions.updateProfile.body;
180+
181+
// Runtime metadata
182+
console.log(usersApiDefinitions.getProfile.method); // 'GET'
183+
console.log(usersApiDefinitions.getProfile.path); // '/users/profile'
184+
```
185+
186+
## File Generation Process
187+
188+
1. **Parse Bruno Files**: Extract API definitions from `.bru` files
189+
2. **Extract Metadata**: Determine method, path, parameters, body, response
190+
3. **Infer Types**: Generate TypeScript types from docs JSON examples
191+
4. **Generate Factory**: Create typed API client functions
192+
5. **Generate Definitions**: Create type metadata exports
193+
6. **Generate Index**: Export all APIs from domain
194+
195+
## Type Safety Guarantees
196+
197+
### Compile-Time Validation
198+
199+
```typescript
200+
// ✅ Correct usage
201+
await usersApi.getProfile({ params: { page: 1 } });
202+
203+
// ❌ Type error: missing data parameter
204+
await usersApi.createUser({});
205+
206+
// ❌ Type error: wrong parameter type
207+
await usersApi.getProfile({ data: {} });
208+
```
209+
210+
### Response Type Inference
211+
212+
```typescript
213+
const profile = await usersApi.getProfile({});
214+
// profile is automatically typed as GetProfileResponse
215+
216+
profile.id; // ✅ OK
217+
profile.username; // ✅ OK
218+
profile.invalid; // ❌ Type error
219+
```
220+
221+
## Best Practices
222+
223+
### 1. Do Not Modify Generated Files
224+
225+
Generated files (`api.ts`, `apiDefinitions.ts`) are overwritten on each generation. Do not add custom logic to these files.
226+
227+
### 2. Keep Custom Logic Separate
228+
229+
```
230+
src/
231+
├── apis/ # Generated (do not modify)
232+
│ └── users/
233+
│ ├── api.ts
234+
│ └── apiDefinitions.ts
235+
└── hooks/ # Custom hooks (safe to modify)
236+
└── useAuth.ts
237+
```
238+
239+
### 3. Use Type Metadata for Generic Functions
240+
241+
```typescript
242+
import { usersApiDefinitions } from '@/apis/users';
243+
244+
function getEndpointPath<T extends keyof typeof usersApiDefinitions>(
245+
endpoint: T
246+
) {
247+
return usersApiDefinitions[endpoint].path;
248+
}
249+
```
250+
251+
## Migration from Query Keys
252+
253+
Previous version generated hooks and query keys. The new approach:
254+
255+
**Before**:
256+
```typescript
257+
import { useGetProfile } from '@/apis/users';
258+
const { data } = useGetProfile();
259+
```
260+
261+
**After**:
262+
```typescript
263+
import { usersApi } from '@/apis/users';
264+
const data = await usersApi.getProfile({});
265+
```
266+
267+
**Benefits**:
268+
- Framework-agnostic API clients
269+
- Easier testing (mock API functions directly)
270+
- Clear separation between data fetching and business logic
271+
272+
## Related Files
273+
274+
- `src/generator/apiFactoryGenerator.ts` - Generates API factory
275+
- `src/generator/apiDefinitionGenerator.ts` - Generates type definitions
276+
- `src/generator/typeGenerator.ts` - Infers TypeScript types
277+
- `src/parser/bruParser.ts` - Parses Bruno files

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ return folderName; // 대괄호가 없으면 폴더명 그대로 반환
100100
- CLI tool
101101
- TypeScript type generation
102102
- API client generation
103-
- React Query hooks generation
103+
- Typed API client generation
104104

105105
---
106106

@@ -128,7 +128,7 @@ bruno/
128128
생성되는 파일명은 동일합니다: `applications/useGetApplicationsList.ts`
129129

130130
#### 2. MSW 핸들러 생성 (선택사항)
131-
기존 hooks 생성에 `--msw-output` 옵션을 추가하면 됩니다.
131+
기존 API 클라이언트 생성에 `--msw-output` 옵션을 추가하면 됩니다.
132132

133133
**package.json 업데이트:**
134134
```json

0 commit comments

Comments
 (0)