@cushin/api-codegen 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +435 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +756 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +79 -0
- package/dist/config/index.js +63 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +43 -0
- package/dist/config/schema.js +14 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +875 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/client.d.ts +42 -0
- package/dist/runtime/client.js +265 -0
- package/dist/runtime/client.js.map +1 -0
- package/package.json +100 -0
package/LICENSE
ADDED
|
File without changes
|
package/README.md
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# @cushin/api-codegen
|
|
2
|
+
|
|
3
|
+
Type-safe API client generator for React/Next.js applications with automatic hooks and server actions generation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Type-Safe**: Full TypeScript support with Zod schema validation
|
|
8
|
+
- 🔄 **Auto-Generated**: Generate React Query hooks, Server Actions, and Server Queries
|
|
9
|
+
- 🚀 **Framework Agnostic**: Works with Vite, Next.js, and more
|
|
10
|
+
- 🔐 **Auth Built-in**: Token refresh, automatic retry with customizable callbacks
|
|
11
|
+
- 📦 **Zero Config**: Simple configuration with sensible defaults
|
|
12
|
+
- 🎨 **Customizable**: Custom templates and generation options
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @cushin/api-codegen ky zod
|
|
18
|
+
# or
|
|
19
|
+
pnpm add @cushin/api-codegen ky zod
|
|
20
|
+
# or
|
|
21
|
+
yarn add @cushin/api-codegen ky zod
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For React Query support (client-side):
|
|
25
|
+
```bash
|
|
26
|
+
npm install @tanstack/react-query
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Initialize Configuration
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx api-codegen init --provider vite
|
|
35
|
+
# or for Next.js
|
|
36
|
+
npx api-codegen init --provider nextjs
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This creates `api-codegen.config.js`:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
/** @type {import('@cushin/api-codegen').UserConfig} */
|
|
43
|
+
export default {
|
|
44
|
+
provider: 'vite',
|
|
45
|
+
endpoints: './lib/api/config/endpoints.ts',
|
|
46
|
+
output: './lib/api/generated',
|
|
47
|
+
baseUrl: process.env.VITE_API_URL,
|
|
48
|
+
generateHooks: true,
|
|
49
|
+
generateClient: true,
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Define Your API Endpoints
|
|
54
|
+
|
|
55
|
+
Create your endpoints configuration file:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// lib/api/config/endpoints.ts
|
|
59
|
+
import { z } from 'zod';
|
|
60
|
+
import { defineConfig, defineEndpoint } from '@cushin/api-codegen';
|
|
61
|
+
|
|
62
|
+
// Define your schemas
|
|
63
|
+
const UserSchema = z.object({
|
|
64
|
+
id: z.string(),
|
|
65
|
+
name: z.string(),
|
|
66
|
+
email: z.string().email(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const CreateUserSchema = z.object({
|
|
70
|
+
name: z.string(),
|
|
71
|
+
email: z.string().email(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Define your endpoints
|
|
75
|
+
export const apiConfig = defineConfig({
|
|
76
|
+
baseUrl: 'https://api.example.com',
|
|
77
|
+
endpoints: {
|
|
78
|
+
// GET request
|
|
79
|
+
getUser: defineEndpoint({
|
|
80
|
+
path: '/users/:id',
|
|
81
|
+
method: 'GET',
|
|
82
|
+
params: z.object({ id: z.string() }),
|
|
83
|
+
response: UserSchema,
|
|
84
|
+
tags: ['users', 'query'],
|
|
85
|
+
description: 'Get user by ID',
|
|
86
|
+
}),
|
|
87
|
+
|
|
88
|
+
// GET with query params
|
|
89
|
+
listUsers: defineEndpoint({
|
|
90
|
+
path: '/users',
|
|
91
|
+
method: 'GET',
|
|
92
|
+
query: z.object({
|
|
93
|
+
page: z.number().optional(),
|
|
94
|
+
limit: z.number().optional(),
|
|
95
|
+
}),
|
|
96
|
+
response: z.array(UserSchema),
|
|
97
|
+
tags: ['users', 'query'],
|
|
98
|
+
}),
|
|
99
|
+
|
|
100
|
+
// POST request
|
|
101
|
+
createUser: defineEndpoint({
|
|
102
|
+
path: '/users',
|
|
103
|
+
method: 'POST',
|
|
104
|
+
body: CreateUserSchema,
|
|
105
|
+
response: UserSchema,
|
|
106
|
+
tags: ['users', 'mutation'],
|
|
107
|
+
}),
|
|
108
|
+
|
|
109
|
+
// PUT request with params
|
|
110
|
+
updateUser: defineEndpoint({
|
|
111
|
+
path: '/users/:id',
|
|
112
|
+
method: 'PUT',
|
|
113
|
+
params: z.object({ id: z.string() }),
|
|
114
|
+
body: CreateUserSchema,
|
|
115
|
+
response: UserSchema,
|
|
116
|
+
tags: ['users', 'mutation'],
|
|
117
|
+
}),
|
|
118
|
+
|
|
119
|
+
// DELETE request
|
|
120
|
+
deleteUser: defineEndpoint({
|
|
121
|
+
path: '/users/:id',
|
|
122
|
+
method: 'DELETE',
|
|
123
|
+
params: z.object({ id: z.string() }),
|
|
124
|
+
response: z.object({ success: z.boolean() }),
|
|
125
|
+
tags: ['users', 'mutation'],
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 3. Generate Code
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx api-codegen generate
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This generates:
|
|
138
|
+
- `generated/types.ts` - Type definitions
|
|
139
|
+
- `generated/client.ts` - API client
|
|
140
|
+
- `generated/hooks.ts` - React Query hooks
|
|
141
|
+
- `generated/actions.ts` - Server Actions (Next.js only)
|
|
142
|
+
- `generated/queries.ts` - Server Queries (Next.js only)
|
|
143
|
+
|
|
144
|
+
### 4. Initialize Client (Vite)
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// lib/auth/provider.tsx
|
|
148
|
+
import { initializeAPIClient } from '@/lib/api/generated/client';
|
|
149
|
+
|
|
150
|
+
export function AuthProvider({ children }) {
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
initializeAPIClient({
|
|
153
|
+
getTokens: () => {
|
|
154
|
+
const token = localStorage.getItem('access_token');
|
|
155
|
+
return token ? { accessToken: token } : null;
|
|
156
|
+
},
|
|
157
|
+
setTokens: (tokens) => {
|
|
158
|
+
localStorage.setItem('access_token', tokens.accessToken);
|
|
159
|
+
},
|
|
160
|
+
clearTokens: () => {
|
|
161
|
+
localStorage.removeItem('access_token');
|
|
162
|
+
},
|
|
163
|
+
onAuthError: () => {
|
|
164
|
+
router.push('/login');
|
|
165
|
+
},
|
|
166
|
+
onRefreshToken: async () => {
|
|
167
|
+
const response = await fetch('/api/auth/refresh', {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
credentials: 'include',
|
|
170
|
+
});
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
return data.accessToken;
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
return <>{children}</>;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 5. Use Generated Hooks
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// components/UserList.tsx
|
|
185
|
+
import { useListUsers, useCreateUser, useDeleteUser } from '@/lib/api/generated/hooks';
|
|
186
|
+
|
|
187
|
+
export function UserList() {
|
|
188
|
+
// Query hook
|
|
189
|
+
const { data: users, isLoading } = useListUsers({
|
|
190
|
+
page: 1,
|
|
191
|
+
limit: 10,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Mutation hooks
|
|
195
|
+
const createUser = useCreateUser({
|
|
196
|
+
onSuccess: () => {
|
|
197
|
+
console.log('User created!');
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const deleteUser = useDeleteUser();
|
|
202
|
+
|
|
203
|
+
const handleCreate = () => {
|
|
204
|
+
createUser.mutate({
|
|
205
|
+
name: 'John Doe',
|
|
206
|
+
email: 'john@example.com',
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const handleDelete = (id: string) => {
|
|
211
|
+
deleteUser.mutate({ id });
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (isLoading) return <div>Loading...</div>;
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div>
|
|
218
|
+
<button onClick={handleCreate}>Create User</button>
|
|
219
|
+
{users?.map((user) => (
|
|
220
|
+
<div key={user.id}>
|
|
221
|
+
{user.name}
|
|
222
|
+
<button onClick={() => handleDelete(user.id)}>Delete</button>
|
|
223
|
+
</div>
|
|
224
|
+
))}
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Next.js Usage
|
|
231
|
+
|
|
232
|
+
### Server Components
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// app/users/page.tsx
|
|
236
|
+
import { listUsersQuery } from '@/lib/api/generated/queries';
|
|
237
|
+
|
|
238
|
+
export default async function UsersPage() {
|
|
239
|
+
const users = await listUsersQuery({ page: 1, limit: 10 });
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<div>
|
|
243
|
+
{users.map((user) => (
|
|
244
|
+
<div key={user.id}>{user.name}</div>
|
|
245
|
+
))}
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Server Actions
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// app/users/actions.ts
|
|
255
|
+
'use client';
|
|
256
|
+
|
|
257
|
+
import { createUserAction, deleteUserAction } from '@/lib/api/generated/actions';
|
|
258
|
+
import { useTransition } from 'react';
|
|
259
|
+
|
|
260
|
+
export function UserForm() {
|
|
261
|
+
const [isPending, startTransition] = useTransition();
|
|
262
|
+
|
|
263
|
+
const handleSubmit = async (formData: FormData) => {
|
|
264
|
+
startTransition(async () => {
|
|
265
|
+
const result = await createUserAction({
|
|
266
|
+
name: formData.get('name') as string,
|
|
267
|
+
email: formData.get('email') as string,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (result.success) {
|
|
271
|
+
console.log('User created:', result.data);
|
|
272
|
+
} else {
|
|
273
|
+
console.error('Error:', result.error);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
return <form action={handleSubmit}>...</form>;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Configuration
|
|
283
|
+
|
|
284
|
+
### Full Configuration Options
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
/** @type {import('@cushin/api-codegen').UserConfig} */
|
|
288
|
+
export default {
|
|
289
|
+
// Required: Provider type
|
|
290
|
+
provider: 'vite' | 'nextjs',
|
|
291
|
+
|
|
292
|
+
// Required: Path to endpoints configuration
|
|
293
|
+
endpoints: './lib/api/config/endpoints.ts',
|
|
294
|
+
|
|
295
|
+
// Required: Output directory
|
|
296
|
+
output: './lib/api/generated',
|
|
297
|
+
|
|
298
|
+
// Optional: Base URL (can also be set at runtime)
|
|
299
|
+
baseUrl: process.env.VITE_API_URL,
|
|
300
|
+
|
|
301
|
+
// Optional: Generation flags
|
|
302
|
+
generateHooks: true, // Generate React Query hooks
|
|
303
|
+
generateClient: true, // Generate API client
|
|
304
|
+
generateServerActions: true, // Next.js only
|
|
305
|
+
generateServerQueries: true, // Next.js only
|
|
306
|
+
|
|
307
|
+
// Optional: Advanced options
|
|
308
|
+
options: {
|
|
309
|
+
useClientDirective: true, // Add 'use client' to generated files
|
|
310
|
+
hookPrefix: 'use', // Prefix for hook names (e.g., useGetUser)
|
|
311
|
+
actionSuffix: 'Action', // Suffix for action names (e.g., createUserAction)
|
|
312
|
+
customImports: {
|
|
313
|
+
// Add custom imports to generated files
|
|
314
|
+
hooks: ['import { customHook } from "./custom"'],
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## CLI Commands
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
# Generate code from config
|
|
324
|
+
npx api-codegen generate
|
|
325
|
+
|
|
326
|
+
# Generate with specific config file
|
|
327
|
+
npx api-codegen generate --config ./custom.config.js
|
|
328
|
+
|
|
329
|
+
# Initialize new config
|
|
330
|
+
npx api-codegen init --provider nextjs
|
|
331
|
+
|
|
332
|
+
# Validate configuration
|
|
333
|
+
npx api-codegen validate
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Advanced Usage
|
|
337
|
+
|
|
338
|
+
### Custom Base URL per Endpoint
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
defineEndpoint({
|
|
342
|
+
path: '/auth/login',
|
|
343
|
+
method: 'POST',
|
|
344
|
+
baseUrl: 'https://auth.example.com', // Override base URL
|
|
345
|
+
body: LoginSchema,
|
|
346
|
+
response: TokenSchema,
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Multiple Endpoints Files
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// lib/api/config/modules/users.ts
|
|
354
|
+
export const userEndpoints = {
|
|
355
|
+
getUser: defineEndpoint({ ... }),
|
|
356
|
+
createUser: defineEndpoint({ ... }),
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// lib/api/config/endpoints.ts
|
|
360
|
+
import { userEndpoints } from './modules/users';
|
|
361
|
+
import { productEndpoints } from './modules/products';
|
|
362
|
+
|
|
363
|
+
export const apiConfig = defineConfig({
|
|
364
|
+
baseUrl: 'https://api.example.com',
|
|
365
|
+
endpoints: {
|
|
366
|
+
...userEndpoints,
|
|
367
|
+
...productEndpoints,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Custom Auth Logic
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
initializeAPIClient({
|
|
376
|
+
getTokens: () => {
|
|
377
|
+
// Custom token retrieval
|
|
378
|
+
return yourAuthStore.getTokens();
|
|
379
|
+
},
|
|
380
|
+
setTokens: (tokens) => {
|
|
381
|
+
// Custom token storage
|
|
382
|
+
yourAuthStore.setTokens(tokens);
|
|
383
|
+
},
|
|
384
|
+
clearTokens: () => {
|
|
385
|
+
// Custom cleanup
|
|
386
|
+
yourAuthStore.clearTokens();
|
|
387
|
+
},
|
|
388
|
+
onRefreshToken: async () => {
|
|
389
|
+
// Custom refresh logic
|
|
390
|
+
const newToken = await yourRefreshFunction();
|
|
391
|
+
return newToken;
|
|
392
|
+
},
|
|
393
|
+
onAuthError: () => {
|
|
394
|
+
// Custom error handling
|
|
395
|
+
yourRouter.push('/login');
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Type Safety
|
|
401
|
+
|
|
402
|
+
All generated code is fully typed:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// IntelliSense knows the exact shape
|
|
406
|
+
const { data } = useGetUser({ id: '123' });
|
|
407
|
+
// ^? { id: string; name: string; email: string; }
|
|
408
|
+
|
|
409
|
+
// TypeScript will error on invalid params
|
|
410
|
+
const { data } = useGetUser({ id: 123 }); // ❌ Type error
|
|
411
|
+
const { data } = useGetUser({ wrongParam: '123' }); // ❌ Type error
|
|
412
|
+
|
|
413
|
+
// Mutation inputs are also typed
|
|
414
|
+
createUser.mutate({
|
|
415
|
+
name: 'John',
|
|
416
|
+
email: 'invalid', // ❌ Type error: invalid email format
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Best Practices
|
|
421
|
+
|
|
422
|
+
1. **Organize endpoints by feature/module**
|
|
423
|
+
2. **Use descriptive endpoint names**
|
|
424
|
+
3. **Add descriptions to endpoints for better documentation**
|
|
425
|
+
4. **Use tags for query invalidation**
|
|
426
|
+
5. **Define reusable schemas**
|
|
427
|
+
6. **Keep baseUrl in environment variables**
|
|
428
|
+
|
|
429
|
+
## Contributing
|
|
430
|
+
|
|
431
|
+
Contributions are welcome! Please read our contributing guide.
|
|
432
|
+
|
|
433
|
+
## License
|
|
434
|
+
|
|
435
|
+
MIT © Le Viet Hoang
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|