react-query-lightbase-codegen 2.5.11 → 3.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/dist/cli.d.ts +2 -0
- package/dist/cli.js +180 -0
- package/dist/generateHooks.js +14 -57
- package/dist/generateHooks.js.map +1 -1
- package/dist/generator/clientGenerator.d.ts +0 -10
- package/dist/generator/clientGenerator.js +18 -72
- package/dist/generator/reactQueryGenerator.js +4 -40
- package/dist/generator/schemaGenerator.js +37 -51
- package/dist/utils.d.ts +34 -1
- package/dist/utils.js +112 -10
- package/dist/utils.js.map +1 -1
- package/package.json +4 -1
- package/src/cli.ts +212 -0
- package/src/generator/clientGenerator.ts +28 -98
- package/src/generator/reactQueryGenerator.ts +11 -46
- package/src/generator/schemaGenerator.ts +48 -62
- package/src/utils.ts +137 -11
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
requestBody?: OpenAPIV3.RequestBodyObject;
|
|
12
|
-
responses: OpenAPIV3.ResponsesObject;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function resolveSchema(
|
|
16
|
-
schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | undefined,
|
|
17
|
-
spec: OpenAPIV3.Document
|
|
18
|
-
): OpenAPIV3.SchemaObject | undefined {
|
|
19
|
-
if (!schema) return undefined;
|
|
20
|
-
if ("$ref" in schema) {
|
|
21
|
-
const index = schema.$ref.split("/").pop();
|
|
22
|
-
return spec.components?.schemas?.[index as string] as OpenAPIV3.SchemaObject;
|
|
23
|
-
}
|
|
24
|
-
return schema;
|
|
25
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
type OperationInfo,
|
|
4
|
+
camelCase,
|
|
5
|
+
collectOperations,
|
|
6
|
+
getContentSchema,
|
|
7
|
+
pascalCase,
|
|
8
|
+
resolveSchema,
|
|
9
|
+
specTitle,
|
|
10
|
+
} from "../utils";
|
|
26
11
|
|
|
27
12
|
function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document): string {
|
|
28
13
|
const { method, path, operationId, summary, description, parameters, requestBody, responses } = operation;
|
|
@@ -34,9 +19,15 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
34
19
|
// Add parameter descriptions
|
|
35
20
|
parameters?.forEach((param) => {
|
|
36
21
|
const desc = param.description ? ` - ${param.description}` : "";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
const prefix =
|
|
23
|
+
param.in === "path"
|
|
24
|
+
? "params."
|
|
25
|
+
: param.in === "query"
|
|
26
|
+
? "query."
|
|
27
|
+
: param.in === "header"
|
|
28
|
+
? "headers."
|
|
29
|
+
: "";
|
|
30
|
+
jsDocLines.push(` * @param ${prefix}${param.name}${desc}`);
|
|
40
31
|
});
|
|
41
32
|
|
|
42
33
|
if (requestBody && "description" in requestBody) {
|
|
@@ -49,15 +40,10 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
49
40
|
const [code, response] = responseDetails;
|
|
50
41
|
const responseObj = response as OpenAPIV3.ResponseObject;
|
|
51
42
|
const desc = "description" in responseObj ? responseObj.description : "";
|
|
52
|
-
const
|
|
53
|
-
responseObj.content?.["application/ld+json"]?.schema ??
|
|
54
|
-
responseObj.content?.["application/json"]?.schema ??
|
|
55
|
-
responseObj.content?.["application/octet-stream"]?.schema ??
|
|
56
|
-
responseObj.content?.["application/json;charset=UTF-8"]?.schema;
|
|
57
|
-
|
|
43
|
+
const contentSchema = getContentSchema(responseObj.content);
|
|
58
44
|
const typeName = pascalCase(`${operationId}Response${code}`);
|
|
59
45
|
|
|
60
|
-
if (
|
|
46
|
+
if (contentSchema) {
|
|
61
47
|
if (desc) {
|
|
62
48
|
jsDocLines.push(` * @returns ${desc}`);
|
|
63
49
|
}
|
|
@@ -79,15 +65,9 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
79
65
|
? resolveSchema(requestBody.content["multipart/form-data"].schema, spec)
|
|
80
66
|
: undefined;
|
|
81
67
|
|
|
82
|
-
const
|
|
83
|
-
requestBody && "content" in requestBody
|
|
84
|
-
|
|
85
|
-
requestBody.content?.["application/json"]?.schema ??
|
|
86
|
-
requestBody.content?.["application/octet-stream"]?.schema ??
|
|
87
|
-
requestBody.content?.["application/json;charset=UTF-8"]?.schema)
|
|
88
|
-
: undefined;
|
|
89
|
-
|
|
90
|
-
const requestBodySchema = content ? resolveSchema(content, spec) : undefined;
|
|
68
|
+
const requestBodyContent =
|
|
69
|
+
requestBody && "content" in requestBody ? getContentSchema(requestBody.content) : undefined;
|
|
70
|
+
const requestBodySchema = requestBodyContent ? resolveSchema(requestBodyContent, spec) : undefined;
|
|
91
71
|
|
|
92
72
|
// Check if request body is a primitive type (string, number, boolean)
|
|
93
73
|
const isPrimitiveRequestBody =
|
|
@@ -151,15 +131,12 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
151
131
|
.join("\n ")}`
|
|
152
132
|
: "",
|
|
153
133
|
queryParams.length > 0 ? "axiosConfig.params = { ...axiosConfig.params, ...queryData };" : "",
|
|
134
|
+
headerParams.length > 0
|
|
135
|
+
? `axiosConfig.headers = { ...axiosConfig.headers, ${headerParams.map((p) => `["${p.name}"]: data["${p.name}"]`).join(", ")} };`
|
|
136
|
+
: "",
|
|
154
137
|
isFormData
|
|
155
138
|
? "axiosConfig.headers = { ...axiosConfig.headers, 'Content-Type': 'multipart/form-data' };"
|
|
156
139
|
: "",
|
|
157
|
-
headerParams.length > 0
|
|
158
|
-
? `const headerData = {
|
|
159
|
-
${headerParams.map((p) => `["${p.name}"]: data["${p.name}"]`).join(",\n ")}
|
|
160
|
-
};`
|
|
161
|
-
: "",
|
|
162
|
-
headerParams.length > 0 ? "axiosConfig.headers = { ...axiosConfig.headers, ...headerData };" : "",
|
|
163
140
|
requestBody
|
|
164
141
|
? `const res = await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || requestBodySchema?.properties ? "bodyData" : "data"}, axiosConfig);`
|
|
165
142
|
: `const res = await apiClient.${method}<${responseType}>(url, axiosConfig);`,
|
|
@@ -168,10 +145,6 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
168
145
|
.filter(Boolean)
|
|
169
146
|
.join("\n ");
|
|
170
147
|
|
|
171
|
-
// ${queryParams.length > 0 ? "params: queryData," : ""}
|
|
172
|
-
// ${requestBody ? `data: ${isFormData ? "formData" : "bodyData"},` : ""}
|
|
173
|
-
// ${isFormData ? `config: { headers: { 'Content-Type': 'multipart/form-data', ...axiosConfig?.headers }, ...axiosConfig },` : "...axiosConfig"}
|
|
174
|
-
|
|
175
148
|
const requestParms = hasData
|
|
176
149
|
? isPrimitiveRequestBody
|
|
177
150
|
? `props: { data: T.${pascalCase(operationId)}Params; axiosConfig?: AxiosRequestConfig; }`
|
|
@@ -186,53 +159,10 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
186
159
|
}
|
|
187
160
|
|
|
188
161
|
export function generateApiClient(spec: OpenAPIV3.Document): string {
|
|
189
|
-
const operations
|
|
190
|
-
|
|
191
|
-
const resolveParameters = (
|
|
192
|
-
parameters: (OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject)[]
|
|
193
|
-
): OpenAPIV3.ParameterObject[] => {
|
|
194
|
-
return parameters.map((p) => {
|
|
195
|
-
if ("$ref" in p) {
|
|
196
|
-
const index = p.$ref.split("/").pop();
|
|
197
|
-
return spec.components?.schemas?.[index as string] as OpenAPIV3.ParameterObject;
|
|
198
|
-
}
|
|
199
|
-
return p;
|
|
200
|
-
});
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
const resolveRequestBody = (
|
|
204
|
-
requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject | undefined
|
|
205
|
-
): OpenAPIV3.RequestBodyObject | undefined => {
|
|
206
|
-
if (!requestBody) return undefined;
|
|
207
|
-
if ("$ref" in requestBody) {
|
|
208
|
-
const index = requestBody.$ref.split("/").pop();
|
|
209
|
-
return spec.components?.schemas?.[index as string] as OpenAPIV3.RequestBodyObject;
|
|
210
|
-
}
|
|
211
|
-
return requestBody;
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// Collect all operations
|
|
215
|
-
Object.entries(spec.paths || {}).forEach(([path, pathItem]) => {
|
|
216
|
-
if (!pathItem) return;
|
|
217
|
-
["get", "post", "put", "delete", "patch"].forEach((method) => {
|
|
218
|
-
const operation = pathItem[method as keyof OpenAPIV3.PathItemObject] as OpenAPIV3.OperationObject;
|
|
219
|
-
if (!operation) return;
|
|
220
|
-
operations.push({
|
|
221
|
-
method: method,
|
|
222
|
-
path,
|
|
223
|
-
operationId: `${sanitizeTypeName(operation.operationId || `${path.replace(/\W+/g, "_")}`)}`,
|
|
224
|
-
summary: operation.summary,
|
|
225
|
-
description: operation.description,
|
|
226
|
-
parameters: resolveParameters([...(pathItem.parameters || []), ...(operation.parameters || [])]),
|
|
227
|
-
requestBody: resolveRequestBody(operation.requestBody),
|
|
228
|
-
responses: operation.responses,
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
162
|
+
const operations = collectOperations(spec);
|
|
233
163
|
const title = specTitle(spec);
|
|
234
164
|
|
|
235
|
-
return `import type {
|
|
165
|
+
return `import type { AxiosRequestConfig } from 'axios';
|
|
236
166
|
import { getApiClient } from './apiClient';
|
|
237
167
|
import type * as T from './${title}.schema';
|
|
238
168
|
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if ("$ref" in schema) {
|
|
11
|
-
const index = schema.$ref.split("/").pop();
|
|
12
|
-
return spec.components?.schemas?.[index as string] as OpenAPIV3.SchemaObject;
|
|
13
|
-
}
|
|
14
|
-
return schema;
|
|
15
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
type OperationInfo,
|
|
4
|
+
camelCase,
|
|
5
|
+
collectOperations,
|
|
6
|
+
getContentSchema,
|
|
7
|
+
resolveSchema,
|
|
8
|
+
specTitle,
|
|
9
|
+
} from "../utils";
|
|
16
10
|
|
|
17
11
|
function generateQueryOptions(operation: OperationInfo, spec: OpenAPIV3.Document): string {
|
|
18
|
-
const { operationId, parameters, requestBody
|
|
12
|
+
const { operationId, parameters, requestBody } = operation;
|
|
19
13
|
|
|
20
14
|
const hasData = (parameters && parameters.length > 0) || operation.requestBody;
|
|
21
15
|
|
|
@@ -32,13 +26,7 @@ function generateQueryOptions(operation: OperationInfo, spec: OpenAPIV3.Document
|
|
|
32
26
|
return schema.required?.map((p) => `'${p}'`) || [];
|
|
33
27
|
};
|
|
34
28
|
|
|
35
|
-
const content =
|
|
36
|
-
requestBody && "content" in requestBody
|
|
37
|
-
? (requestBody.content?.["application/ld+json"]?.schema ??
|
|
38
|
-
requestBody.content?.["application/json"]?.schema ??
|
|
39
|
-
requestBody.content?.["application/octet-stream"]?.schema)
|
|
40
|
-
: undefined;
|
|
41
|
-
|
|
29
|
+
const content = requestBody && "content" in requestBody ? getContentSchema(requestBody.content) : undefined;
|
|
42
30
|
const requestBodySchema = content ? resolveSchema(content, spec) : undefined;
|
|
43
31
|
|
|
44
32
|
// Check if request body is a primitive type (string, number, boolean)
|
|
@@ -104,30 +92,7 @@ export const ${namedQueryOptions} = (
|
|
|
104
92
|
}
|
|
105
93
|
|
|
106
94
|
export function generateReactQuery(spec: OpenAPIV3.Document): string {
|
|
107
|
-
const operations
|
|
108
|
-
|
|
109
|
-
// Collect operations (same as in clientGenerator)
|
|
110
|
-
Object.entries(spec.paths || {}).forEach(([path, pathItem]) => {
|
|
111
|
-
if (!pathItem) return;
|
|
112
|
-
|
|
113
|
-
["get", "post", "put", "delete", "patch"].forEach((method) => {
|
|
114
|
-
const operation = pathItem[method as keyof OpenAPIV3.PathItemObject] as OpenAPIV3.OperationObject;
|
|
115
|
-
if (!operation) return;
|
|
116
|
-
operations.push({
|
|
117
|
-
method: method,
|
|
118
|
-
path,
|
|
119
|
-
operationId: sanitizeTypeName(`${operation.operationId || `${path.replace(/\W+/g, "_")}`}`),
|
|
120
|
-
summary: operation.summary,
|
|
121
|
-
description: operation.description,
|
|
122
|
-
parameters: [
|
|
123
|
-
...(pathItem.parameters || []),
|
|
124
|
-
...(operation.parameters || []),
|
|
125
|
-
] as OpenAPIV3.ParameterObject[],
|
|
126
|
-
requestBody: operation.requestBody as OpenAPIV3.RequestBodyObject,
|
|
127
|
-
responses: operation.responses,
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
});
|
|
95
|
+
const operations = collectOperations(spec);
|
|
131
96
|
|
|
132
97
|
return `import { queryOptions, skipToken } from '@tanstack/react-query';
|
|
133
98
|
import * as apiClient from './${specTitle(spec)}.client';
|
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getContentSchema,
|
|
4
|
+
getTypeFromSchema,
|
|
5
|
+
pascalCase,
|
|
6
|
+
sanitizePropertyName,
|
|
7
|
+
sanitizeTypeName,
|
|
8
|
+
} from "../utils";
|
|
3
9
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Formats a parameter as a TypeScript property string with optional JSDoc.
|
|
12
|
+
*/
|
|
13
|
+
function formatParamProperty(param: OpenAPIV3.ParameterObject, forceRequired = false): string {
|
|
14
|
+
const safeName = sanitizePropertyName(param.name);
|
|
15
|
+
const isDeprecated = param.deprecated;
|
|
16
|
+
const hasDescription = param.description;
|
|
17
|
+
const isOptional = forceRequired ? false : !param.required;
|
|
18
|
+
|
|
19
|
+
const desc =
|
|
20
|
+
hasDescription || isDeprecated
|
|
21
|
+
? `/**${hasDescription ? `\n * ${param.description}` : ""}${isDeprecated ? "\n * @deprecated" : ""}\n */\n`
|
|
22
|
+
: "";
|
|
23
|
+
|
|
24
|
+
return `${desc}${safeName}${isOptional ? "?" : ""}: ${getTypeFromSchema(param.schema)}`;
|
|
7
25
|
}
|
|
8
26
|
|
|
9
27
|
function generateTypeDefinition(
|
|
@@ -26,18 +44,16 @@ function generateTypeDefinition(
|
|
|
26
44
|
* Generates TypeScript interface definitions from OpenAPI schemas
|
|
27
45
|
*/
|
|
28
46
|
export function generateTypeDefinitions(spec: OpenAPIV3.Document): string {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
generatedTypes: new Set(),
|
|
32
|
-
};
|
|
47
|
+
const schemas = (spec.components?.schemas as { [key: string]: OpenAPIV3.SchemaObject }) || {};
|
|
48
|
+
const generatedTypes = new Set<string>();
|
|
33
49
|
|
|
34
50
|
let output = "/* Generated TypeScript Definitions */\n\n";
|
|
35
51
|
|
|
36
52
|
// Generate types for all schema definitions
|
|
37
|
-
for (const [name, schema] of Object.entries(
|
|
38
|
-
if (
|
|
53
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
54
|
+
if (generatedTypes.has(name)) continue;
|
|
39
55
|
output += generateTypeDefinition(name, schema);
|
|
40
|
-
|
|
56
|
+
generatedTypes.add(name);
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
// Generate request/response types
|
|
@@ -54,34 +70,36 @@ export function generateTypeDefinitions(spec: OpenAPIV3.Document): string {
|
|
|
54
70
|
// Generate request body type
|
|
55
71
|
if (requestBody) {
|
|
56
72
|
const content = (requestBody as OpenAPIV3.RequestBodyObject).content;
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
content["application/json"] ??
|
|
60
|
-
content["multipart/form-data"] ??
|
|
61
|
-
content["application/octet-stream"] ??
|
|
62
|
-
content["application/json;charset=UTF-8"];
|
|
63
|
-
if (jsonContent?.schema) {
|
|
73
|
+
const requestSchema = getContentSchema(content);
|
|
74
|
+
if (requestSchema) {
|
|
64
75
|
const typeName = `${operationId}Request`;
|
|
65
|
-
output += generateTypeDefinition(typeName,
|
|
76
|
+
output += generateTypeDefinition(typeName, requestSchema as OpenAPIV3.SchemaObject);
|
|
66
77
|
}
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
// Generate response types
|
|
81
|
+
const errorTypes: string[] = [];
|
|
70
82
|
if (responses) {
|
|
71
83
|
for (const [code, response] of Object.entries(responses)) {
|
|
72
84
|
const responseObj = response as OpenAPIV3.ResponseObject;
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
responseObj.content?.["application/json"] ??
|
|
76
|
-
responseObj.content?.["application/octet-stream"] ??
|
|
77
|
-
responseObj.content?.["application/json;charset=UTF-8"];
|
|
78
|
-
if (content?.schema) {
|
|
85
|
+
const responseSchema = getContentSchema(responseObj.content);
|
|
86
|
+
if (responseSchema) {
|
|
79
87
|
const typeName = `${operationId}Response${code}`;
|
|
80
|
-
output += generateTypeDefinition(typeName,
|
|
88
|
+
output += generateTypeDefinition(typeName, responseSchema as OpenAPIV3.SchemaObject);
|
|
89
|
+
|
|
90
|
+
// Track non-2xx responses for error union type
|
|
91
|
+
if (!code.startsWith("2")) {
|
|
92
|
+
errorTypes.push(typeName);
|
|
93
|
+
}
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
97
|
|
|
98
|
+
// Generate error union type if there are error responses
|
|
99
|
+
if (errorTypes.length > 0) {
|
|
100
|
+
output += `export type ${pascalCase(operationId)}Error = ${errorTypes.join(" | ")};\n\n`;
|
|
101
|
+
}
|
|
102
|
+
|
|
85
103
|
// Build data type parts
|
|
86
104
|
const dataProps: string[] = [];
|
|
87
105
|
|
|
@@ -92,42 +110,10 @@ export function generateTypeDefinitions(spec: OpenAPIV3.Document): string {
|
|
|
92
110
|
const headerParams = (parameters?.filter((p) => "in" in p && p.in === "header") ||
|
|
93
111
|
[]) as OpenAPIV3.ParameterObject[];
|
|
94
112
|
|
|
95
|
-
// Add path and
|
|
96
|
-
urlParams.forEach((p) =>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const hasDescription = "description" in p && p.description;
|
|
100
|
-
const desc =
|
|
101
|
-
hasDescription || isDeprecated
|
|
102
|
-
? `/**${hasDescription ? `\n* ${p.description}` : ""}${isDeprecated ? "\n* @deprecated" : ""}
|
|
103
|
-
*/\n`
|
|
104
|
-
: "";
|
|
105
|
-
dataProps.push(`${desc}${safeName}: ${getTypeFromSchema(p.schema)}`);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
queryParams.forEach((p) => {
|
|
109
|
-
const safeName = sanitizePropertyName(p.name);
|
|
110
|
-
const isDeprecated = "deprecated" in p && p.deprecated;
|
|
111
|
-
const hasDescription = "description" in p && p.description;
|
|
112
|
-
const desc =
|
|
113
|
-
hasDescription || isDeprecated
|
|
114
|
-
? `\n/**${hasDescription ? `\n* ${p.description}` : ""}${isDeprecated ? "\n* @deprecated" : ""}
|
|
115
|
-
*/\n`
|
|
116
|
-
: "";
|
|
117
|
-
dataProps.push(`${desc}${safeName}${p.required ? "" : "?"}: ${getTypeFromSchema(p.schema)}`);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
headerParams.forEach((p) => {
|
|
121
|
-
const safeName = sanitizePropertyName(p.name);
|
|
122
|
-
const isDeprecated = "deprecated" in p && p.deprecated;
|
|
123
|
-
const hasDescription = "description" in p && p.description;
|
|
124
|
-
const desc =
|
|
125
|
-
hasDescription || isDeprecated
|
|
126
|
-
? `\n/**${hasDescription ? `\n* ${p.description}` : ""}${isDeprecated ? "\n* @deprecated" : ""}
|
|
127
|
-
*/\n`
|
|
128
|
-
: "";
|
|
129
|
-
dataProps.push(`${desc}${safeName}${p.required ? "" : "?"}: ${getTypeFromSchema(p.schema)}`);
|
|
130
|
-
});
|
|
113
|
+
// Add path, query, and header parameters
|
|
114
|
+
urlParams.forEach((p) => dataProps.push(formatParamProperty(p, true))); // Path params always required
|
|
115
|
+
queryParams.forEach((p) => dataProps.push(formatParamProperty(p)));
|
|
116
|
+
headerParams.forEach((p) => dataProps.push(formatParamProperty(p)));
|
|
131
117
|
|
|
132
118
|
// Add request body type if it exists
|
|
133
119
|
const hasData = (parameters && parameters.length > 0) || requestBody;
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,115 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from "openapi-types";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Supported content types in order of preference
|
|
5
|
+
*/
|
|
6
|
+
export const CONTENT_TYPES = [
|
|
7
|
+
"application/ld+json",
|
|
8
|
+
"application/json",
|
|
9
|
+
"multipart/form-data",
|
|
10
|
+
"application/octet-stream",
|
|
11
|
+
"application/json;charset=UTF-8",
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves a schema reference to its actual schema object.
|
|
16
|
+
* If the schema is already a concrete schema (not a $ref), returns it as-is.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveSchema(
|
|
19
|
+
schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | undefined,
|
|
20
|
+
spec: OpenAPIV3.Document
|
|
21
|
+
): OpenAPIV3.SchemaObject | undefined {
|
|
22
|
+
if (!schema) return undefined;
|
|
23
|
+
if ("$ref" in schema) {
|
|
24
|
+
const index = schema.$ref.split("/").pop();
|
|
25
|
+
return spec.components?.schemas?.[index as string] as OpenAPIV3.SchemaObject;
|
|
26
|
+
}
|
|
27
|
+
return schema;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extracts the schema from a content object, checking supported content types in order.
|
|
32
|
+
*/
|
|
33
|
+
export function getContentSchema(
|
|
34
|
+
content: { [media: string]: OpenAPIV3.MediaTypeObject } | undefined
|
|
35
|
+
): OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined {
|
|
36
|
+
if (!content) return undefined;
|
|
37
|
+
for (const type of CONTENT_TYPES) {
|
|
38
|
+
if (content[type]?.schema) {
|
|
39
|
+
return content[type].schema;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Operation info extracted from OpenAPI paths
|
|
47
|
+
*/
|
|
48
|
+
export interface OperationInfo {
|
|
49
|
+
method: string;
|
|
50
|
+
path: string;
|
|
51
|
+
operationId: string;
|
|
52
|
+
summary?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
parameters: OpenAPIV3.ParameterObject[];
|
|
55
|
+
requestBody?: OpenAPIV3.RequestBodyObject;
|
|
56
|
+
responses: OpenAPIV3.ResponsesObject;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const HTTP_METHODS = ["get", "post", "put", "delete", "patch"] as const;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Collects all operations from an OpenAPI spec, resolving parameter and request body references.
|
|
63
|
+
*/
|
|
64
|
+
export function collectOperations(spec: OpenAPIV3.Document): OperationInfo[] {
|
|
65
|
+
const operations: OperationInfo[] = [];
|
|
66
|
+
|
|
67
|
+
const resolveParameters = (
|
|
68
|
+
parameters: (OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject)[]
|
|
69
|
+
): OpenAPIV3.ParameterObject[] => {
|
|
70
|
+
return parameters.map((p) => {
|
|
71
|
+
if ("$ref" in p) {
|
|
72
|
+
const index = p.$ref.split("/").pop();
|
|
73
|
+
return spec.components?.parameters?.[index as string] as OpenAPIV3.ParameterObject;
|
|
74
|
+
}
|
|
75
|
+
return p;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const resolveRequestBody = (
|
|
80
|
+
requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject | undefined
|
|
81
|
+
): OpenAPIV3.RequestBodyObject | undefined => {
|
|
82
|
+
if (!requestBody) return undefined;
|
|
83
|
+
if ("$ref" in requestBody) {
|
|
84
|
+
const index = requestBody.$ref.split("/").pop();
|
|
85
|
+
return spec.components?.requestBodies?.[index as string] as OpenAPIV3.RequestBodyObject;
|
|
86
|
+
}
|
|
87
|
+
return requestBody;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
Object.entries(spec.paths || {}).forEach(([path, pathItem]) => {
|
|
91
|
+
if (!pathItem) return;
|
|
92
|
+
|
|
93
|
+
HTTP_METHODS.forEach((method) => {
|
|
94
|
+
const operation = pathItem[method] as OpenAPIV3.OperationObject | undefined;
|
|
95
|
+
if (!operation) return;
|
|
96
|
+
|
|
97
|
+
operations.push({
|
|
98
|
+
method,
|
|
99
|
+
path,
|
|
100
|
+
operationId: sanitizeTypeName(operation.operationId || path.replace(/\W+/g, "_")),
|
|
101
|
+
summary: operation.summary,
|
|
102
|
+
description: operation.description,
|
|
103
|
+
parameters: resolveParameters([...(pathItem.parameters || []), ...(operation.parameters || [])]),
|
|
104
|
+
requestBody: resolveRequestBody(operation.requestBody),
|
|
105
|
+
responses: operation.responses,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return operations;
|
|
111
|
+
}
|
|
112
|
+
|
|
3
113
|
export function camelCase(str: string): string {
|
|
4
114
|
return str
|
|
5
115
|
.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
|
|
@@ -63,6 +173,7 @@ export function specTitle(spec: OpenAPIV3.Document): string {
|
|
|
63
173
|
* Handles:
|
|
64
174
|
* - References ($ref) by extracting the type name
|
|
65
175
|
* - Nullable types by appending "| null"
|
|
176
|
+
* - Composition types (allOf → intersection, oneOf/anyOf → union)
|
|
66
177
|
* - Enums by creating union types of the values
|
|
67
178
|
* - OneOf schemas as union types
|
|
68
179
|
* - Basic types (string, number, boolean)
|
|
@@ -72,7 +183,7 @@ export function specTitle(spec: OpenAPIV3.Document): string {
|
|
|
72
183
|
* - Objects with additionalProperties as Records
|
|
73
184
|
* - Fallback to "any" for unknown types
|
|
74
185
|
*
|
|
75
|
-
* @param
|
|
186
|
+
* @param schema - The OpenAPI schema/parameter object to convert
|
|
76
187
|
* @returns The TypeScript type as a string
|
|
77
188
|
*/
|
|
78
189
|
export function getTypeFromSchema(
|
|
@@ -88,6 +199,30 @@ export function getTypeFromSchema(
|
|
|
88
199
|
// Add "| null" for nullable types
|
|
89
200
|
const nullable = "nullable" in schema && schema.nullable ? " | null" : "";
|
|
90
201
|
|
|
202
|
+
// Handle allOf (intersection types)
|
|
203
|
+
if ("allOf" in schema && schema.allOf) {
|
|
204
|
+
const types = schema.allOf.map((s) => getTypeFromSchema(s)).filter(Boolean) as string[];
|
|
205
|
+
if (types.length === 0) return "any";
|
|
206
|
+
const result = types.length === 1 ? types[0] : `(${types.join(" & ")})`;
|
|
207
|
+
return `${result}${nullable}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Handle oneOf (union types - exactly one)
|
|
211
|
+
if ("oneOf" in schema && schema.oneOf) {
|
|
212
|
+
const types = schema.oneOf.map((s) => getTypeFromSchema(s)).filter(Boolean) as string[];
|
|
213
|
+
if (types.length === 0) return "any";
|
|
214
|
+
const result = types.length === 1 ? types[0] : `(${types.join(" | ")})`;
|
|
215
|
+
return `${result}${nullable}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Handle anyOf (union types - one or more)
|
|
219
|
+
if ("anyOf" in schema && schema.anyOf) {
|
|
220
|
+
const types = schema.anyOf.map((s) => getTypeFromSchema(s)).filter(Boolean) as string[];
|
|
221
|
+
if (types.length === 0) return "any";
|
|
222
|
+
const result = types.length === 1 ? types[0] : `(${types.join(" | ")})`;
|
|
223
|
+
return `${result}${nullable}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
91
226
|
// Handle enums as union types
|
|
92
227
|
if ("enum" in schema && schema.enum) {
|
|
93
228
|
if (Object.values(schema.enum)?.length > 0) {
|
|
@@ -100,14 +235,6 @@ export function getTypeFromSchema(
|
|
|
100
235
|
return schema.enum.map((e) => (typeof e === "string" ? `'${e}'` : e)).join(" | ") + nullable;
|
|
101
236
|
}
|
|
102
237
|
|
|
103
|
-
// Handle oneOf as union types
|
|
104
|
-
if ("oneOf" in schema && schema.oneOf) {
|
|
105
|
-
const unionTypes = schema.oneOf
|
|
106
|
-
.map((subSchema) => getTypeFromSchema(subSchema))
|
|
107
|
-
.filter((type): type is string => type !== undefined);
|
|
108
|
-
return unionTypes.length > 0 ? `${unionTypes.join(" | ")}${nullable}` : `any${nullable}`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
238
|
// Handle types based on the "type" property
|
|
112
239
|
if ("type" in schema) {
|
|
113
240
|
switch (schema.type) {
|
|
@@ -143,8 +270,7 @@ export function getTypeFromSchema(
|
|
|
143
270
|
const hasDescription = "description" in prop && prop.description;
|
|
144
271
|
const desc =
|
|
145
272
|
hasDescription || isDeprecated
|
|
146
|
-
? `/**${hasDescription ? `\n* ${prop.description}` : ""}${isDeprecated ? "\n* @deprecated" : ""}
|
|
147
|
-
*/\n`
|
|
273
|
+
? `/**${hasDescription ? `\n * ${prop.description}` : ""}${isDeprecated ? "\n * @deprecated" : ""}\n */\n`
|
|
148
274
|
: "";
|
|
149
275
|
return `${desc}${safeName}${isRequired ? "" : "?"}: ${propertyType};`;
|
|
150
276
|
})
|