elysia-openapi-codegen 0.1.8 → 0.2.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/README.md +10 -3
- package/dist/index.js +36 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,12 @@ Generate fully-typed React Query hooks and TypeScript interfaces from OpenAPI sp
|
|
|
11
11
|
- **React Query Integration**: Generates `useQuery` and `useMutation` hooks ready to use
|
|
12
12
|
- **Flexible Arguments**: Supports both flag-based and positional arguments
|
|
13
13
|
|
|
14
|
+
### Using Bunx (Recommended)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bunx elysia-openapi-codegen -i https://api.example.com/openapi.json -o ./src/api
|
|
18
|
+
```
|
|
19
|
+
|
|
14
20
|
## Installation
|
|
15
21
|
|
|
16
22
|
### Using Bun (Recommended)
|
|
@@ -50,6 +56,7 @@ elysia-codegen -i <source> -o <output>
|
|
|
50
56
|
```
|
|
51
57
|
|
|
52
58
|
**Arguments:**
|
|
59
|
+
|
|
53
60
|
- `-i, --input <source>` - OpenAPI spec source (URL or file path)
|
|
54
61
|
- `-o, --output <output>` - Output directory for generated files
|
|
55
62
|
- `-h, --help` - Show help message
|
|
@@ -98,13 +105,13 @@ The generator creates a single `generated.ts` file containing all types and hook
|
|
|
98
105
|
All request/response types are automatically generated:
|
|
99
106
|
|
|
100
107
|
```typescript
|
|
101
|
-
import type { User, CreateUserBody, GetUsersResponse } from
|
|
108
|
+
import type { User, CreateUserBody, GetUsersResponse } from "./generated";
|
|
102
109
|
|
|
103
110
|
// Use types in your components
|
|
104
111
|
const user: User = {
|
|
105
112
|
id: 1,
|
|
106
|
-
name:
|
|
107
|
-
email:
|
|
113
|
+
name: "John Doe",
|
|
114
|
+
email: "john@example.com",
|
|
108
115
|
};
|
|
109
116
|
```
|
|
110
117
|
|
package/dist/index.js
CHANGED
|
@@ -43,7 +43,8 @@ ${props.join(`
|
|
|
43
43
|
}`;
|
|
44
44
|
}
|
|
45
45
|
if (schema.anyOf || schema.oneOf) {
|
|
46
|
-
|
|
46
|
+
const members = [...new Set((schema.anyOf || schema.oneOf || []).map(resolveType))];
|
|
47
|
+
return members.join(" | ");
|
|
47
48
|
}
|
|
48
49
|
if (schema.allOf) {
|
|
49
50
|
return schema.allOf.map(resolveType).join(" & ");
|
|
@@ -77,6 +78,8 @@ function generateGroups(spec) {
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
for (const [pathUrl, pathItem] of Object.entries(spec.paths)) {
|
|
81
|
+
if (pathUrl.includes("*"))
|
|
82
|
+
continue;
|
|
80
83
|
const g = getGroup(getPathGroup(pathUrl));
|
|
81
84
|
for (const [method, operation] of Object.entries(pathItem)) {
|
|
82
85
|
if (!["get", "post", "put", "patch", "delete"].includes(method))
|
|
@@ -100,6 +103,8 @@ ${paramProps.join(`
|
|
|
100
103
|
}
|
|
101
104
|
const isMultipart = !op.requestBody?.content?.["application/json"] && !!op.requestBody?.content?.["multipart/form-data"];
|
|
102
105
|
const bodySchema = op.requestBody?.content?.["application/json"]?.schema ?? op.requestBody?.content?.["multipart/form-data"]?.schema;
|
|
106
|
+
const hasBinaryField = (s) => !!s && (s.format === "binary" || Object.values(s.properties ?? {}).some((p) => p.format === "binary"));
|
|
107
|
+
const useFormData = isMultipart || hasBinaryField(bodySchema);
|
|
103
108
|
if (bodySchema) {
|
|
104
109
|
g.types.push(`export type ${capitalize(opId)}Body = ${resolveType(bodySchema)};`);
|
|
105
110
|
}
|
|
@@ -116,6 +121,7 @@ ${paramProps.join(`
|
|
|
116
121
|
for (const p of pathParamsList) {
|
|
117
122
|
urlPath = urlPath.replace(`{${p.name}}`, `\${params.${p.name}}`);
|
|
118
123
|
}
|
|
124
|
+
urlPath = urlPath.replace(/\/+$/, "");
|
|
119
125
|
const qsLines = (indent) => queryParamsList.map((p) => `${indent}if (params?.${p.name} !== undefined) _qs.set('${p.name}', String(params.${p.name}));`).join(`
|
|
120
126
|
`);
|
|
121
127
|
if (!hasResponse) {
|
|
@@ -133,8 +139,12 @@ ${body}
|
|
|
133
139
|
${qsLines(" ")}
|
|
134
140
|
const _qstr = _qs.toString();
|
|
135
141
|
const res = await fetch(\`\${baseUrl}${urlPath}\${_qstr ? '?' + _qstr : ''}\`);` : ` const res = await fetch(\`\${baseUrl}${urlPath}\`);`;
|
|
136
|
-
const
|
|
137
|
-
|
|
142
|
+
const guardParams = [
|
|
143
|
+
...pathParamsList,
|
|
144
|
+
...queryParamsList.filter((p) => p.required)
|
|
145
|
+
];
|
|
146
|
+
const enabledGuard = guardParams.length > 0 ? `
|
|
147
|
+
enabled: ${guardParams.map((p) => `params.${p.name} != null`).join(" && ")},` : "";
|
|
138
148
|
g.hooks.push(`
|
|
139
149
|
export const use${capitalize(opId)} = (
|
|
140
150
|
params${hasParams ? "" : "?"}: ${paramsType},
|
|
@@ -152,27 +162,41 @@ ${fetchStatement}
|
|
|
152
162
|
};`);
|
|
153
163
|
} else {
|
|
154
164
|
const pathParamsList = params.filter((p) => p.in === "path");
|
|
165
|
+
const queryParamsList = params.filter((p) => p.in === "query");
|
|
155
166
|
const hasPathParams = pathParamsList.length > 0;
|
|
167
|
+
const hasQueryParams = queryParamsList.length > 0;
|
|
168
|
+
const hasAnyParams = hasPathParams || hasQueryParams;
|
|
156
169
|
let urlPath = pathUrl;
|
|
157
170
|
for (const p of pathParamsList) {
|
|
158
171
|
urlPath = urlPath.replace(`{${p.name}}`, `\${${p.name}}`);
|
|
159
172
|
}
|
|
173
|
+
urlPath = urlPath.replace(/\/+$/, "");
|
|
174
|
+
const mutQsCode = hasQueryParams ? `
|
|
175
|
+
const _qs = new URLSearchParams();
|
|
176
|
+
${queryParamsList.map((p) => ` if (${p.name} !== undefined) _qs.set('${p.name}', String(${p.name}));`).join(`
|
|
177
|
+
`)}
|
|
178
|
+
const _qstr = _qs.toString();` : "";
|
|
179
|
+
const fetchUrlExpr = hasQueryParams ? `\`\${baseUrl}${urlPath}\${_qstr ? '?' + _qstr : ''}\`` : `\`\${baseUrl}${urlPath}\``;
|
|
180
|
+
const allParamNames = [
|
|
181
|
+
...pathParamsList.map((p) => p.name),
|
|
182
|
+
...queryParamsList.map((p) => p.name)
|
|
183
|
+
];
|
|
160
184
|
let inputType;
|
|
161
185
|
let mutationArg;
|
|
162
|
-
if (hasBody &&
|
|
186
|
+
if (hasBody && hasAnyParams) {
|
|
163
187
|
inputType = `${capitalize(opId)}Params & ${capitalize(opId)}Body`;
|
|
164
|
-
mutationArg = `{ ${
|
|
188
|
+
mutationArg = `{ ${allParamNames.join(", ")}, ...body }`;
|
|
165
189
|
} else if (hasBody) {
|
|
166
190
|
inputType = bodyType;
|
|
167
191
|
mutationArg = "body";
|
|
168
|
-
} else if (
|
|
192
|
+
} else if (hasAnyParams) {
|
|
169
193
|
inputType = paramsType;
|
|
170
|
-
mutationArg = `{ ${
|
|
194
|
+
mutationArg = `{ ${allParamNames.join(", ")} }`;
|
|
171
195
|
} else {
|
|
172
196
|
inputType = "void";
|
|
173
197
|
mutationArg = "";
|
|
174
198
|
}
|
|
175
|
-
const fetchBodyLines = hasBody ?
|
|
199
|
+
const fetchBodyLines = hasBody ? useFormData ? `
|
|
176
200
|
body: Object.entries(body as Record<string, unknown>).reduce((f, [k, v]) => { f.append(k, v as any); return f; }, new FormData()),` : `
|
|
177
201
|
headers: { 'Content-Type': 'application/json' },
|
|
178
202
|
body: JSON.stringify(body),` : "";
|
|
@@ -181,8 +205,8 @@ export const use${capitalize(opId)} = (
|
|
|
181
205
|
options?: UseMutationOptions<${responseType}, Error, ${inputType}>
|
|
182
206
|
) => {
|
|
183
207
|
return useMutation<${responseType}, Error, ${inputType}>({
|
|
184
|
-
mutationFn: async (${mutationArg}) => {
|
|
185
|
-
const res = await fetch(
|
|
208
|
+
mutationFn: async (${mutationArg}) => {${mutQsCode}
|
|
209
|
+
const res = await fetch(${fetchUrlExpr}, {
|
|
186
210
|
method: '${method.toUpperCase()}',${fetchBodyLines}
|
|
187
211
|
});
|
|
188
212
|
if (!res.ok) throw new Error(\`\${res.status}: \${await res.text()}\`);
|
|
@@ -268,7 +292,8 @@ async function main() {
|
|
|
268
292
|
fs.mkdirSync(output, { recursive: true });
|
|
269
293
|
}
|
|
270
294
|
fs.writeFileSync(path.join(output, "base.ts"), `/* eslint-disable */
|
|
271
|
-
|
|
295
|
+
/** Override this before making any API calls if your base URL changes at runtime */
|
|
296
|
+
export let baseUrl = '${baseUrl}';
|
|
272
297
|
`);
|
|
273
298
|
const groupNames = [];
|
|
274
299
|
for (const [groupName, { types, hooks }] of Object.entries(groups)) {
|