elysia-openapi-codegen 0.1.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.
Files changed (3) hide show
  1. package/README.md +357 -0
  2. package/index.ts +393 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,357 @@
1
+ # Elysia OpenAPI Code Generator
2
+
3
+ Generate fully-typed React Query hooks and TypeScript interfaces from OpenAPI specifications. Perfect for Elysia.js APIs and any OpenAPI 3.x compliant backend.
4
+
5
+ ## Features
6
+
7
+ - **Type-Safe Hooks**: Automatically generates React Query hooks with full TypeScript support
8
+ - **OpenAPI 3.x Compatible**: Works with any valid OpenAPI specification
9
+ - **Multiple Input Sources**: Fetch specs from URLs or local files
10
+ - **Zero Configuration**: Simple CLI with sensible defaults
11
+ - **React Query Integration**: Generates `useQuery` and `useMutation` hooks ready to use
12
+ - **Flexible Arguments**: Supports both flag-based and positional arguments
13
+
14
+ ## Installation
15
+
16
+ ### Using Bun (Recommended)
17
+
18
+ ```bash
19
+ bun add -d elysia-openapi-codegen
20
+ ```
21
+
22
+ ### Using npm
23
+
24
+ ```bash
25
+ npm install --save-dev elysia-openapi-codegen
26
+ ```
27
+
28
+ ### Using yarn
29
+
30
+ ```bash
31
+ yarn add -D elysia-openapi-codegen
32
+ ```
33
+
34
+ ### Global Installation
35
+
36
+ ```bash
37
+ # Bun
38
+ bun add -g elysia-openapi-codegen
39
+
40
+ # npm
41
+ npm install -g elysia-openapi-codegen
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### CLI Flags
47
+
48
+ ```bash
49
+ elysia-codegen -i <source> -o <output>
50
+ ```
51
+
52
+ **Arguments:**
53
+ - `-i, --input <source>` - OpenAPI spec source (URL or file path)
54
+ - `-o, --output <output>` - Output directory for generated files
55
+ - `-h, --help` - Show help message
56
+
57
+ ### Examples
58
+
59
+ #### Using Flag Arguments
60
+
61
+ ```bash
62
+ # From a URL
63
+ elysia-codegen -i https://api.example.com/openapi.json -o ./src/api
64
+
65
+ # From a local file
66
+ elysia-codegen -i ./openapi.json -o ./generated
67
+
68
+ # Using long-form flags
69
+ elysia-codegen --input https://api.example.com/openapi.json --output ./src/api
70
+ ```
71
+
72
+ #### Using Positional Arguments
73
+
74
+ ```bash
75
+ # From a URL
76
+ elysia-codegen https://api.example.com/openapi.json ./src/api
77
+
78
+ # From a local file
79
+ elysia-codegen ./openapi.json ./generated
80
+ ```
81
+
82
+ #### With Bun
83
+
84
+ ```bash
85
+ # Run directly with bun
86
+ bun index.ts -i https://api.example.com/openapi.json -o ./src/api
87
+
88
+ # Or using positional arguments
89
+ bun index.ts https://api.example.com/openapi.json ./src/api
90
+ ```
91
+
92
+ ## Generated Code Usage
93
+
94
+ The generator creates a single `generated.ts` file containing all types and hooks.
95
+
96
+ ### TypeScript Types
97
+
98
+ All request/response types are automatically generated:
99
+
100
+ ```typescript
101
+ import type { User, CreateUserBody, GetUsersResponse } from './generated';
102
+
103
+ // Use types in your components
104
+ const user: User = {
105
+ id: 1,
106
+ name: 'John Doe',
107
+ email: 'john@example.com'
108
+ };
109
+ ```
110
+
111
+ ### React Query Hooks
112
+
113
+ #### Query Hooks (GET requests)
114
+
115
+ ```typescript
116
+ import { useGetUsers, useGetUserById } from './api/generated';
117
+
118
+ function UsersList() {
119
+ // Simple query with no parameters
120
+ const { data, isLoading, error } = useGetUsers();
121
+
122
+ if (isLoading) return <div>Loading...</div>;
123
+ if (error) return <div>Error: {error.message}</div>;
124
+
125
+ return (
126
+ <ul>
127
+ {data?.users.map(user => (
128
+ <li key={user.id}>{user.name}</li>
129
+ ))}
130
+ </ul>
131
+ );
132
+ }
133
+
134
+ function UserProfile({ userId }: { userId: number }) {
135
+ // Query with parameters
136
+ const { data: user } = useGetUserById(
137
+ { id: userId },
138
+ {
139
+ enabled: !!userId, // React Query options
140
+ staleTime: 5000,
141
+ }
142
+ );
143
+
144
+ return <div>{user?.name}</div>;
145
+ }
146
+ ```
147
+
148
+ #### Mutation Hooks (POST, PUT, PATCH, DELETE)
149
+
150
+ ```typescript
151
+ import { useCreateUser, useUpdateUser, useDeleteUser } from './api/generated';
152
+ import { useQueryClient } from '@tanstack/react-query';
153
+
154
+ function CreateUserForm() {
155
+ const queryClient = useQueryClient();
156
+
157
+ const { mutate, isPending } = useCreateUser({
158
+ onSuccess: () => {
159
+ // Invalidate and refetch
160
+ queryClient.invalidateQueries({ queryKey: ['getUsers'] });
161
+ },
162
+ });
163
+
164
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
165
+ e.preventDefault();
166
+ const formData = new FormData(e.currentTarget);
167
+
168
+ mutate({
169
+ name: formData.get('name') as string,
170
+ email: formData.get('email') as string,
171
+ });
172
+ };
173
+
174
+ return (
175
+ <form onSubmit={handleSubmit}>
176
+ <input name="name" required />
177
+ <input name="email" type="email" required />
178
+ <button type="submit" disabled={isPending}>
179
+ {isPending ? 'Creating...' : 'Create User'}
180
+ </button>
181
+ </form>
182
+ );
183
+ }
184
+
185
+ function UserActions({ userId }: { userId: number }) {
186
+ const queryClient = useQueryClient();
187
+
188
+ const { mutate: updateUser } = useUpdateUser({
189
+ onSuccess: () => {
190
+ queryClient.invalidateQueries({ queryKey: ['getUserById', { id: userId }] });
191
+ },
192
+ });
193
+
194
+ const { mutate: deleteUser } = useDeleteUser({
195
+ onSuccess: () => {
196
+ queryClient.invalidateQueries({ queryKey: ['getUsers'] });
197
+ },
198
+ });
199
+
200
+ return (
201
+ <div>
202
+ <button onClick={() => updateUser({ id: userId, name: 'Updated Name' })}>
203
+ Update
204
+ </button>
205
+ <button onClick={() => deleteUser({ id: userId })}>
206
+ Delete
207
+ </button>
208
+ </div>
209
+ );
210
+ }
211
+ ```
212
+
213
+ ### Advanced Usage
214
+
215
+ #### Custom Query Keys
216
+
217
+ ```typescript
218
+ import { useGetUsers } from './api/generated';
219
+
220
+ function FilteredUsers({ status }: { status: string }) {
221
+ const { data } = useGetUsers(
222
+ { status },
223
+ {
224
+ queryKey: ['users', status], // Custom query key
225
+ staleTime: 60000,
226
+ refetchOnWindowFocus: false,
227
+ }
228
+ );
229
+
230
+ return <div>{/* Render users */}</div>;
231
+ }
232
+ ```
233
+
234
+ #### Error Handling
235
+
236
+ ```typescript
237
+ import { useCreateUser } from './api/generated';
238
+
239
+ function CreateUserForm() {
240
+ const { mutate, error, isError } = useCreateUser({
241
+ onError: (error) => {
242
+ console.error('Failed to create user:', error);
243
+ // Show toast notification, etc.
244
+ },
245
+ });
246
+
247
+ return (
248
+ <div>
249
+ {isError && <div className="error">{error.message}</div>}
250
+ {/* Form fields */}
251
+ </div>
252
+ );
253
+ }
254
+ ```
255
+
256
+ #### Optimistic Updates
257
+
258
+ ```typescript
259
+ import { useUpdateUser } from './api/generated';
260
+ import { useQueryClient } from '@tanstack/react-query';
261
+
262
+ function UserEditor({ userId }: { userId: number }) {
263
+ const queryClient = useQueryClient();
264
+
265
+ const { mutate } = useUpdateUser({
266
+ onMutate: async (newUser) => {
267
+ // Cancel outgoing refetches
268
+ await queryClient.cancelQueries({ queryKey: ['getUserById', { id: userId }] });
269
+
270
+ // Snapshot the previous value
271
+ const previousUser = queryClient.getQueryData(['getUserById', { id: userId }]);
272
+
273
+ // Optimistically update
274
+ queryClient.setQueryData(['getUserById', { id: userId }], newUser);
275
+
276
+ return { previousUser };
277
+ },
278
+ onError: (err, newUser, context) => {
279
+ // Rollback on error
280
+ queryClient.setQueryData(
281
+ ['getUserById', { id: userId }],
282
+ context?.previousUser
283
+ );
284
+ },
285
+ onSettled: () => {
286
+ queryClient.invalidateQueries({ queryKey: ['getUserById', { id: userId }] });
287
+ },
288
+ });
289
+
290
+ return <div>{/* Editor UI */}</div>;
291
+ }
292
+ ```
293
+
294
+ ## Requirements
295
+
296
+ - **TypeScript**: ^5.0.0
297
+ - **@tanstack/react-query**: ^5.0.0 (peer dependency for generated code)
298
+ - **React**: ^18.0.0 (peer dependency for generated code)
299
+
300
+ ## Project Structure
301
+
302
+ ```
303
+ your-project/
304
+ ├── src/
305
+ │ ├── api/
306
+ │ │ └── generated.ts # Generated by this tool
307
+ │ ├── components/
308
+ │ │ └── Users.tsx # Your components using the hooks
309
+ │ └── App.tsx
310
+ ├── openapi.json # Your OpenAPI spec
311
+ └── package.json
312
+ ```
313
+
314
+ ## Development
315
+
316
+ ### Setup
317
+
318
+ ```bash
319
+ # Install dependencies
320
+ bun install
321
+
322
+ # Run the generator locally
323
+ bun index.ts -i ./example/openapi.json -o ./output
324
+ ```
325
+
326
+ ### Building
327
+
328
+ This project uses Bun as the runtime. No build step is necessary for development.
329
+
330
+ ## How It Works
331
+
332
+ 1. **Fetches OpenAPI Spec**: Reads from a URL or local file
333
+ 2. **Generates TypeScript Types**: Creates interfaces from schema definitions
334
+ 3. **Creates React Query Hooks**: Generates typed hooks for each endpoint
335
+ - GET requests → `useQuery` hooks
336
+ - POST/PUT/PATCH/DELETE → `useMutation` hooks
337
+ 4. **Outputs Single File**: All types and hooks in one `generated.ts` file
338
+
339
+ ## Limitations
340
+
341
+ - Only supports `application/json` content types
342
+ - Uses the first server URL as the base URL
343
+ - Assumes standard REST conventions for operations
344
+
345
+ ## Contributing
346
+
347
+ Contributions are welcome! Please feel free to submit a Pull Request.
348
+
349
+ ## License
350
+
351
+ MIT
352
+
353
+ ## Related Projects
354
+
355
+ - [Elysia](https://elysiajs.com/) - Fast and friendly Bun web framework
356
+ - [TanStack Query](https://tanstack.com/query) - Powerful data synchronization for React
357
+ - [OpenAPI](https://www.openapis.org/) - API specification standard
package/index.ts ADDED
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Elysia OpenAPI Code Generator
5
+ * Generates typed React Query hooks from OpenAPI specifications.
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import https from 'https';
11
+ import http from 'http';
12
+
13
+ interface OpenAPISpec {
14
+ openapi: string;
15
+ info: {
16
+ title: string;
17
+ version: string;
18
+ [key: string]: unknown;
19
+ };
20
+ servers?: Array<{
21
+ url: string;
22
+ description?: string;
23
+ }>;
24
+ paths: Record<string, OpenAPIPathItem>;
25
+ components?: {
26
+ schemas?: Record<string, OpenAPISchema>;
27
+ [key: string]: unknown;
28
+ };
29
+ [key: string]: unknown;
30
+ }
31
+
32
+ interface OpenAPIPathItem {
33
+ get?: OpenAPIOperation;
34
+ post?: OpenAPIOperation;
35
+ put?: OpenAPIOperation;
36
+ delete?: OpenAPIOperation;
37
+ patch?: OpenAPIOperation;
38
+ [key: string]: unknown;
39
+ }
40
+
41
+ interface OpenAPIOperation {
42
+ operationId?: string;
43
+ summary?: string;
44
+ description?: string;
45
+ parameters?: OpenAPIParameter[];
46
+ responses?: Record<string, OpenAPIResponse>;
47
+ requestBody?: {
48
+ content: {
49
+ [contentType: string]: {
50
+ schema: OpenAPISchema;
51
+ };
52
+ };
53
+ };
54
+ [key: string]: unknown;
55
+ }
56
+
57
+ interface OpenAPIParameter {
58
+ name: string;
59
+ in: 'query' | 'header' | 'path' | 'cookie';
60
+ description?: string;
61
+ required?: boolean;
62
+ schema?: OpenAPISchema;
63
+ [key: string]: unknown;
64
+ }
65
+
66
+ interface OpenAPIResponse {
67
+ description: string;
68
+ content?: {
69
+ [contentType: string]: {
70
+ schema: OpenAPISchema;
71
+ };
72
+ };
73
+ [key: string]: unknown;
74
+ }
75
+
76
+ interface OpenAPISchema {
77
+ type?: string;
78
+ items?: OpenAPISchema;
79
+ properties?: Record<string, OpenAPISchema>;
80
+ required?: string[];
81
+ $ref?: string;
82
+ nullable?: boolean;
83
+ anyOf?: OpenAPISchema[];
84
+ oneOf?: OpenAPISchema[];
85
+ allOf?: OpenAPISchema[];
86
+ [key: string]: unknown;
87
+ }
88
+
89
+ async function fetchSpec(source: string): Promise<OpenAPISpec> {
90
+ if (source.startsWith('http://') || source.startsWith('https://')) {
91
+ return new Promise((resolve, reject) => {
92
+ const client = source.startsWith('https://') ? https : http;
93
+ client.get(source, (res) => {
94
+ let data = '';
95
+ res.on('data', (chunk) => data += chunk);
96
+ res.on('end', () => {
97
+ try {
98
+ resolve(JSON.parse(data) as OpenAPISpec);
99
+ } catch (e) {
100
+ reject(new Error(`Failed to parse OpenAPI spec: ${(e as Error).message}`));
101
+ }
102
+ });
103
+ res.on('error', reject);
104
+ });
105
+ });
106
+ }
107
+
108
+ return JSON.parse(fs.readFileSync(source, 'utf8')) as OpenAPISpec;
109
+ }
110
+
111
+ function resolveType(schema?: OpenAPISchema): string {
112
+ if (!schema) return 'any';
113
+
114
+ if (schema.$ref) {
115
+ return schema.$ref.split('/').pop() || 'any';
116
+ }
117
+
118
+ if (schema.type === 'array' && schema.items) {
119
+ return `Array<${resolveType(schema.items)}>`;
120
+ }
121
+
122
+ if (schema.type === 'object') {
123
+ if (!schema.properties) return 'Record<string, any>';
124
+
125
+ const props = Object.entries(schema.properties).map(([key, prop]) => {
126
+ const isRequired = schema.required?.includes(key);
127
+ const optional = isRequired ? '' : '?';
128
+ return ` ${key}${optional}: ${resolveType(prop)};`;
129
+ });
130
+
131
+ return `{\n${props.join('\n')}\n}`;
132
+ }
133
+
134
+ if (schema.anyOf || schema.oneOf) {
135
+ return (schema.anyOf || schema.oneOf || []).map(resolveType).join(' | ');
136
+ }
137
+
138
+ if (schema.allOf) {
139
+ return schema.allOf.map(resolveType).join(' & ');
140
+ }
141
+
142
+ if (schema.nullable) {
143
+ return `${resolveType({ ...schema, nullable: false })} | null`;
144
+ }
145
+
146
+ const typeMap: Record<string, string> = {
147
+ string: 'string',
148
+ number: 'number',
149
+ integer: 'number',
150
+ boolean: 'boolean',
151
+ null: 'null',
152
+ };
153
+
154
+ return (schema.type && typeMap[schema.type]) || 'any';
155
+ }
156
+
157
+ function generateTypes(spec: OpenAPISpec): string {
158
+ const definitions: string[] = [];
159
+
160
+ if (spec.components?.schemas) {
161
+ for (const [name, schema] of Object.entries(spec.components.schemas)) {
162
+ definitions.push(`export type ${name} = ${resolveType(schema)};`);
163
+ }
164
+ }
165
+
166
+ for (const [pathUrl, pathItem] of Object.entries(spec.paths)) {
167
+ for (const [method, operation] of Object.entries(pathItem)) {
168
+ if (!['get', 'post', 'put', 'patch', 'delete'].includes(method)) continue;
169
+
170
+ const op = operation as OpenAPIOperation;
171
+ const opId = op.operationId || `${method}${pathUrl.replace(/[^a-zA-Z0-9]/g, '')}`;
172
+ const response = op.responses?.['200'];
173
+
174
+ if (response?.content?.['application/json']?.schema) {
175
+ const responseType = resolveType(response.content['application/json'].schema);
176
+ definitions.push(`export type ${capitalize(opId)}Response = ${responseType};`);
177
+ }
178
+
179
+ const params = op.parameters || [];
180
+ if (params.length > 0) {
181
+ const paramProps = params.map(param => {
182
+ const required = param.required ? '' : '?';
183
+ return ` ${param.name}${required}: ${resolveType(param.schema)};`;
184
+ });
185
+ definitions.push(`export type ${capitalize(opId)}Params = {\n${paramProps.join('\n')}\n};`);
186
+ }
187
+
188
+ if (op.requestBody?.content?.['application/json']?.schema) {
189
+ const bodyType = resolveType(op.requestBody.content['application/json'].schema);
190
+ definitions.push(`export type ${capitalize(opId)}Body = ${bodyType};`);
191
+ }
192
+ }
193
+ }
194
+
195
+ return definitions.join('\n\n');
196
+ }
197
+
198
+ function generateHooks(spec: OpenAPISpec): string {
199
+ const hooks: string[] = [];
200
+ const baseUrl = spec.servers?.[0]?.url || '';
201
+
202
+ for (const [pathUrl, pathItem] of Object.entries(spec.paths)) {
203
+ for (const [method, operation] of Object.entries(pathItem)) {
204
+ if (!['get', 'post', 'put', 'patch', 'delete'].includes(method)) continue;
205
+
206
+ const op = operation as OpenAPIOperation;
207
+ const opId = op.operationId || `${method}${pathUrl.replace(/[^a-zA-Z0-9]/g, '')}`;
208
+
209
+ const hasResponse = !!op.responses?.['200']?.content?.['application/json']?.schema;
210
+ const responseType = hasResponse ? `${capitalize(opId)}Response` : 'any';
211
+
212
+ const params = op.parameters || [];
213
+ const hasParams = params.length > 0;
214
+ const paramsType = hasParams ? `${capitalize(opId)}Params` : 'void';
215
+
216
+ const hasBody = !!op.requestBody?.content?.['application/json']?.schema;
217
+ const bodyType = hasBody ? `${capitalize(opId)}Body` : 'void';
218
+
219
+ if (method === 'get') {
220
+ const queryParams = params.map(p => p.name);
221
+ const queryString = queryParams.length > 0
222
+ ? `?${queryParams.map((p: string) => `\${params?.${p} !== undefined ? '${p}=' + params.${p} : ''}`).join('&')}`
223
+ : '';
224
+
225
+ hooks.push(`
226
+ export const use${capitalize(opId)} = (
227
+ params${hasParams ? '' : '?'}: ${paramsType},
228
+ options?: Omit<UseQueryOptions<${responseType}>, 'queryKey' | 'queryFn'>
229
+ ) => {
230
+ return useQuery<${responseType}>({
231
+ queryKey: ['${opId}', params],
232
+ queryFn: async () => {
233
+ const res = await fetch(\`\${baseUrl}${pathUrl}${queryString}\`);
234
+ if (!res.ok) throw new Error('API Error');
235
+ return res.json();
236
+ },
237
+ ...options,
238
+ });
239
+ };`);
240
+ } else {
241
+ let inputType = 'void';
242
+ let inputArg = '';
243
+
244
+ if (hasBody) {
245
+ inputType = bodyType;
246
+ inputArg = 'body';
247
+ } else if (hasParams) {
248
+ inputType = paramsType;
249
+ inputArg = 'params';
250
+ }
251
+
252
+ const hasInput = inputType !== 'void';
253
+
254
+ hooks.push(`
255
+ export const use${capitalize(opId)} = (
256
+ options?: UseMutationOptions<${responseType}, Error, ${inputType}>
257
+ ) => {
258
+ return useMutation<${responseType}, Error, ${inputType}>({
259
+ mutationFn: async (${hasInput ? inputArg : ''}) => {
260
+ const res = await fetch(\`\${baseUrl}${pathUrl}\`, {
261
+ method: '${method.toUpperCase()}',
262
+ headers: { 'Content-Type': 'application/json' },
263
+ body: JSON.stringify(${hasInput ? inputArg : '{}'}),
264
+ });
265
+ if (!res.ok) throw new Error('API Error');
266
+ return res.json();
267
+ },
268
+ ...options,
269
+ });
270
+ };`);
271
+ }
272
+ }
273
+ }
274
+
275
+ return hooks.join('\n');
276
+ }
277
+
278
+ function capitalize(str: string): string {
279
+ return str.charAt(0).toUpperCase() + str.slice(1);
280
+ }
281
+
282
+ async function parseArgs() {
283
+ const args = process.argv.slice(2);
284
+ const config = {
285
+ input: '',
286
+ output: '',
287
+ };
288
+
289
+ for (let i = 0; i < args.length; i++) {
290
+ const arg = args[i];
291
+ if (!arg) continue;
292
+
293
+ if (arg === '-i' || arg === '--input') {
294
+ const value = args[++i];
295
+ if (!value) {
296
+ console.error(`Error: ${arg} flag requires a value`);
297
+ process.exit(1);
298
+ }
299
+ config.input = value;
300
+ } else if (arg === '-o' || arg === '--output') {
301
+ const value = args[++i];
302
+ if (!value) {
303
+ console.error(`Error: ${arg} flag requires a value`);
304
+ process.exit(1);
305
+ }
306
+ config.output = value;
307
+ } else if (!arg.startsWith('-')) {
308
+ if (!config.input) config.input = arg;
309
+ else if (!config.output) config.output = arg;
310
+ }
311
+ }
312
+
313
+ return config;
314
+ }
315
+
316
+ function showHelp() {
317
+ console.log(`
318
+ Elysia OpenAPI Code Generator
319
+ Generate React Query hooks and TypeScript types from OpenAPI specifications.
320
+
321
+ Usage:
322
+ elysia-codegen -i <source> -o <output>
323
+ elysia-codegen --input <source> --output <output>
324
+ elysia-codegen <source> <output>
325
+
326
+ Arguments:
327
+ -i, --input <source> OpenAPI spec source (URL or file path)
328
+ -o, --output <output> Output directory for generated files
329
+
330
+ Examples:
331
+ # Using flags
332
+ elysia-codegen -i https://api.example.com/openapi.json -o ./src/api
333
+ elysia-codegen --input ./openapi.json --output ./generated
334
+
335
+ # Using positional arguments
336
+ elysia-codegen https://api.example.com/openapi.json ./src/api
337
+ elysia-codegen ./openapi.json ./generated
338
+ `);
339
+ }
340
+
341
+ async function main() {
342
+ const args = process.argv.slice(2);
343
+
344
+ if (args.includes('-h') || args.includes('--help') || args.length === 0) {
345
+ showHelp();
346
+ process.exit(0);
347
+ }
348
+
349
+ const { input, output } = await parseArgs();
350
+
351
+ if (!input || !output) {
352
+ console.error('Error: Both input source and output directory are required.\n');
353
+ showHelp();
354
+ process.exit(1);
355
+ }
356
+
357
+ console.log('Fetching OpenAPI spec...');
358
+
359
+ try {
360
+ const spec = await fetchSpec(input);
361
+ const types = generateTypes(spec);
362
+ const hooks = generateHooks(spec);
363
+ const baseUrl = spec.servers?.[0]?.url || '';
364
+
365
+ const fileContent = `/* eslint-disable */
366
+ /**
367
+ * Auto-generated by Elysia OpenAPI Codegen
368
+ */
369
+
370
+ import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
371
+
372
+ const baseUrl = '${baseUrl}';
373
+
374
+ ${types}
375
+
376
+ ${hooks}
377
+ `;
378
+
379
+ if (!fs.existsSync(output)) {
380
+ fs.mkdirSync(output, { recursive: true });
381
+ }
382
+
383
+ const outputPath = path.join(output, 'generated.ts');
384
+ fs.writeFileSync(outputPath, fileContent);
385
+
386
+ console.log(`Successfully generated hooks at ${outputPath}`);
387
+ } catch (err) {
388
+ console.error('Generation failed:', err instanceof Error ? err.message : String(err));
389
+ process.exit(1);
390
+ }
391
+ }
392
+
393
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "elysia-openapi-codegen",
3
+ "version": "0.1.0",
4
+ "description": "Generate React Query hooks and fully typed TypeScript interfaces from Elysia OpenAPI specs.",
5
+ "module": "index.ts",
6
+ "type": "module",
7
+ "bin": {
8
+ "elysia-codegen": "./index.ts"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "README.md"
13
+ ],
14
+ "author": "Khantamir mkhantamir77@gmail.com",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/mkhantamir/elysia-openapi-codegen.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/mkhantamir/elysia-openapi-codegen/issues"
22
+ },
23
+ "homepage": "https://github.com/mkhantamir/elysia-openapi-codegen#readme",
24
+ "keywords": [
25
+ "elysia",
26
+ "openapi",
27
+ "codegen",
28
+ "typescript",
29
+ "react-query",
30
+ "tanstack-query"
31
+ ],
32
+ "devDependencies": {
33
+ "@types/bun": "latest"
34
+ },
35
+ "peerDependencies": {
36
+ "typescript": "^5"
37
+ }
38
+ }