react-query-lightbase-codegen 1.6.3 → 2.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/config/loadConfig.d.ts +2 -0
- package/dist/config/loadConfig.js +24 -0
- package/dist/generator/clientGenerator.d.ts +13 -0
- package/dist/generator/clientGenerator.js +199 -0
- package/dist/generator/instanceGenerator.d.ts +1 -0
- package/dist/generator/instanceGenerator.js +21 -0
- package/dist/generator/reactQueryGenerator.d.ts +2 -0
- package/dist/generator/reactQueryGenerator.js +82 -0
- package/dist/generator/schemaGenerator.d.ts +5 -0
- package/dist/generator/schemaGenerator.js +116 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +106 -4
- package/dist/types/config.d.ts +11 -0
- package/dist/types/config.js +2 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +31 -20
- package/dist/utils.js +57 -179
- package/package.json +54 -82
- package/readme.md +94 -0
- package/src/config/loadConfig.ts +25 -0
- package/src/generator/clientGenerator.ts +234 -0
- package/src/generator/instanceGenerator.ts +20 -0
- package/src/generator/reactQueryGenerator.ts +92 -0
- package/src/generator/schemaGenerator.ts +139 -0
- package/src/index.ts +78 -3
- package/src/types/config.ts +12 -0
- package/src/types.ts +5 -0
- package/src/utils.ts +56 -213
- package/README.md +0 -77
- package/dist/convertSwaggerFile.d.ts +0 -5
- package/dist/convertSwaggerFile.js +0 -26
- package/dist/convertSwaggerFile.js.map +0 -1
- package/dist/generateHooks.d.ts +0 -18
- package/dist/generateHooks.js +0 -444
- package/dist/generateHooks.js.map +0 -1
- package/dist/generateImports.d.ts +0 -6
- package/dist/generateImports.js +0 -21
- package/dist/generateImports.js.map +0 -1
- package/dist/generateSchemas.d.ts +0 -7
- package/dist/generateSchemas.js +0 -100
- package/dist/generateSchemas.js.map +0 -1
- package/dist/importSpecs.d.ts +0 -10
- package/dist/importSpecs.js +0 -127
- package/dist/importSpecs.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/src/convertSwaggerFile.ts +0 -25
- package/src/generateHooks.ts +0 -538
- package/src/generateImports.ts +0 -32
- package/src/generateSchemas.ts +0 -117
- package/src/importSpecs.ts +0 -168
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadConfig = loadConfig;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
async function loadConfig(configPath) {
|
|
6
|
+
try {
|
|
7
|
+
const configContent = await (0, promises_1.readFile)(configPath, "utf-8");
|
|
8
|
+
const config = JSON.parse(configContent);
|
|
9
|
+
// Validate required fields
|
|
10
|
+
if (!config.specSource) {
|
|
11
|
+
throw new Error("specSource is required in configuration");
|
|
12
|
+
}
|
|
13
|
+
if (!config.exportDir) {
|
|
14
|
+
throw new Error("exportDir is required in configuration");
|
|
15
|
+
}
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error instanceof Error) {
|
|
20
|
+
throw new Error(`Failed to load configuration: ${error.message}`);
|
|
21
|
+
}
|
|
22
|
+
throw new Error("Failed to load configuration: Unknown error");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
+
import type { OpenAPIConfig } from "../types/config";
|
|
3
|
+
export interface OperationInfo {
|
|
4
|
+
method: string;
|
|
5
|
+
path: string;
|
|
6
|
+
operationId: string;
|
|
7
|
+
summary?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
parameters?: OpenAPIV3.ParameterObject[];
|
|
10
|
+
requestBody?: OpenAPIV3.RequestBodyObject;
|
|
11
|
+
responses: OpenAPIV3.ResponsesObject;
|
|
12
|
+
}
|
|
13
|
+
export declare function generateApiClient(spec: OpenAPIV3.Document, config: OpenAPIConfig): string;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateApiClient = generateApiClient;
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
function resolveSchema(schema, spec) {
|
|
6
|
+
if (!schema)
|
|
7
|
+
return undefined;
|
|
8
|
+
if ("$ref" in schema) {
|
|
9
|
+
const index = schema.$ref.split("/").pop();
|
|
10
|
+
return spec.components?.schemas?.[index];
|
|
11
|
+
}
|
|
12
|
+
return schema;
|
|
13
|
+
}
|
|
14
|
+
function generateAxiosMethod(operation, spec) {
|
|
15
|
+
const { method, path, operationId, summary, description, parameters, requestBody, responses } = operation;
|
|
16
|
+
// Generate JSDoc
|
|
17
|
+
const jsDocLines = ["/**"];
|
|
18
|
+
if (summary)
|
|
19
|
+
jsDocLines.push(` * ${summary}`);
|
|
20
|
+
if (description)
|
|
21
|
+
jsDocLines.push(` * ${description}`);
|
|
22
|
+
// Add parameter descriptions
|
|
23
|
+
parameters?.forEach((param) => {
|
|
24
|
+
const desc = param.description ? ` - ${param.description}` : "";
|
|
25
|
+
jsDocLines.push(` * @param ${param.in === "path" ? "params." : param.in === "query" ? "query." : ""}${param.name}${desc}`);
|
|
26
|
+
});
|
|
27
|
+
if (requestBody && "description" in requestBody) {
|
|
28
|
+
jsDocLines.push(` * @param data - ${requestBody.description}`);
|
|
29
|
+
}
|
|
30
|
+
// Add return type description
|
|
31
|
+
const responseDetails = Object.entries(responses).find(([code]) => code.startsWith("2"));
|
|
32
|
+
if (responseDetails) {
|
|
33
|
+
const [code, response] = responseDetails;
|
|
34
|
+
const responseObj = response;
|
|
35
|
+
const desc = "description" in responseObj ? responseObj.description : "";
|
|
36
|
+
const contentType = responseObj.content?.["application/json"]?.schema;
|
|
37
|
+
const typeName = `${operationId}Response${code}`;
|
|
38
|
+
if (contentType) {
|
|
39
|
+
if (desc) {
|
|
40
|
+
jsDocLines.push(` * @returns ${desc}`);
|
|
41
|
+
}
|
|
42
|
+
jsDocLines.push(` * @see ${typeName}`);
|
|
43
|
+
}
|
|
44
|
+
else if (desc) {
|
|
45
|
+
jsDocLines.push(` * @returns ${desc}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
jsDocLines.push(" */");
|
|
49
|
+
const urlParams = parameters?.filter((p) => p.in === "path") || [];
|
|
50
|
+
const queryParams = parameters?.filter((p) => p.in === "query") || [];
|
|
51
|
+
const isFormData = requestBody && "content" in requestBody && requestBody.content?.["multipart/form-data"];
|
|
52
|
+
const formDataSchema = isFormData
|
|
53
|
+
? resolveSchema(requestBody.content["multipart/form-data"].schema, spec)
|
|
54
|
+
: undefined;
|
|
55
|
+
const requestBodySchema = requestBody?.content?.["application/json"]?.schema
|
|
56
|
+
? resolveSchema(requestBody.content["application/json"].schema, spec)
|
|
57
|
+
: undefined;
|
|
58
|
+
// Build data type parts
|
|
59
|
+
const dataProps = [];
|
|
60
|
+
// Add path and query parameters
|
|
61
|
+
urlParams.forEach((p) => {
|
|
62
|
+
const safeName = (0, utils_1.sanitizePropertyName)(p.name);
|
|
63
|
+
dataProps.push(`${safeName}: ${getTypeFromParam(p)}`);
|
|
64
|
+
});
|
|
65
|
+
queryParams.forEach((p) => {
|
|
66
|
+
const safeName = (0, utils_1.sanitizePropertyName)(p.name);
|
|
67
|
+
dataProps.push(`${safeName}${p.required ? "" : "?"}: ${getTypeFromParam(p)}`);
|
|
68
|
+
});
|
|
69
|
+
// Add request body type if it exists
|
|
70
|
+
const hasData = (parameters && parameters.length > 0) || operation.requestBody;
|
|
71
|
+
let dataType = "undefined";
|
|
72
|
+
const namedType = (0, utils_1.pascalCase)(operationId);
|
|
73
|
+
if (hasData) {
|
|
74
|
+
if (requestBody && dataProps.length > 0) {
|
|
75
|
+
dataType = `T.${namedType}Request & { ${dataProps.join("; ")} }`;
|
|
76
|
+
}
|
|
77
|
+
else if (requestBody) {
|
|
78
|
+
dataType = `T.${namedType}Request`;
|
|
79
|
+
}
|
|
80
|
+
else if (dataProps.length > 0) {
|
|
81
|
+
dataType = `{ ${dataProps.join("; ")} }`;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
dataType = "Record<string, never>";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Get response type from 2xx response
|
|
88
|
+
const successResponse = Object.entries(responses).find(([code]) => code.startsWith("2"));
|
|
89
|
+
const responseType = successResponse ? `T.${`${namedType}Response${successResponse[0]}`}` : "any";
|
|
90
|
+
const urlWithParams = urlParams.length > 0 ? path.replace(/{(\w+)}/g, "${data.$1}") : path;
|
|
91
|
+
const methodBody = [
|
|
92
|
+
"const apiClient = getApiClient();",
|
|
93
|
+
`const url = \`${urlWithParams}\`;`,
|
|
94
|
+
queryParams.length > 0
|
|
95
|
+
? `const queryData = {
|
|
96
|
+
${queryParams.map((p) => `["${p.name}"]: data["${p.name}"]`).join(",\n ")}
|
|
97
|
+
};`
|
|
98
|
+
: "",
|
|
99
|
+
requestBodySchema?.properties
|
|
100
|
+
? `const bodyData = {
|
|
101
|
+
${Object.entries(requestBodySchema.properties)
|
|
102
|
+
.map(([key]) => `${key}: data.${key}`)
|
|
103
|
+
.join(",\n ")}
|
|
104
|
+
};`
|
|
105
|
+
: "",
|
|
106
|
+
formDataSchema?.properties
|
|
107
|
+
? `const formData = new FormData();
|
|
108
|
+
${Object.entries(formDataSchema.properties)
|
|
109
|
+
.map(([key, prop]) => {
|
|
110
|
+
const schemaProperty = prop;
|
|
111
|
+
const isBinary = schemaProperty.format === "binary";
|
|
112
|
+
return formDataSchema?.required?.includes(key)
|
|
113
|
+
? `formData.append("${key}", ${isBinary ? "" : "String("}${queryParams.length > 0 ? "bodyData" : "data"}.${key}${isBinary ? "" : ")"});`
|
|
114
|
+
: `if (${queryParams.length > 0 ? "bodyData" : "data"}.${key} != null) {
|
|
115
|
+
formData.append("${key}", ${isBinary ? "" : "String("}${queryParams.length > 0 ? "bodyData" : "data"}.${key}${isBinary ? "" : ")"});
|
|
116
|
+
}`;
|
|
117
|
+
})
|
|
118
|
+
.join("\n ")}`
|
|
119
|
+
: "",
|
|
120
|
+
`return apiClient.${method.toLowerCase()}<${responseType}>(url, {
|
|
121
|
+
${queryParams.length > 0 ? "params: queryData," : ""}
|
|
122
|
+
${requestBody ? `data: ${isFormData ? "formData" : "bodyData"},` : ""}
|
|
123
|
+
${isFormData ? `config: { headers: { 'Content-Type': 'multipart/form-data', ...config.headers }, ...config },` : "...config"}
|
|
124
|
+
});`,
|
|
125
|
+
]
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.join("\n ");
|
|
128
|
+
return `
|
|
129
|
+
${jsDocLines.join("\n ")}
|
|
130
|
+
export function ${(0, utils_1.camelCase)(operationId)}(data${hasData ? `: ${dataType}` : "?: undefined"}, config?: AxiosRequestConfig): Promise<AxiosResponse<${responseType}>> {
|
|
131
|
+
${methodBody}
|
|
132
|
+
}`;
|
|
133
|
+
}
|
|
134
|
+
function getTypeFromParam(param) {
|
|
135
|
+
if ("schema" in param) {
|
|
136
|
+
const schema = param.schema;
|
|
137
|
+
switch (schema.type) {
|
|
138
|
+
case "string":
|
|
139
|
+
return "string";
|
|
140
|
+
case "integer":
|
|
141
|
+
case "number":
|
|
142
|
+
return "number";
|
|
143
|
+
case "boolean":
|
|
144
|
+
return "boolean";
|
|
145
|
+
case "array":
|
|
146
|
+
return "Array<any>"; // You might want to make this more specific
|
|
147
|
+
default:
|
|
148
|
+
return "any";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return "any";
|
|
152
|
+
}
|
|
153
|
+
function generateApiClient(spec, config) {
|
|
154
|
+
const operations = [];
|
|
155
|
+
const resolveParameters = (parameters) => {
|
|
156
|
+
return parameters.map((p) => {
|
|
157
|
+
if ("$ref" in p) {
|
|
158
|
+
const index = p.$ref.split("/").pop();
|
|
159
|
+
return spec.components?.schemas?.[index];
|
|
160
|
+
}
|
|
161
|
+
return p;
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
const resolveRequestBody = (requestBody) => {
|
|
165
|
+
if (!requestBody)
|
|
166
|
+
return undefined;
|
|
167
|
+
if ("$ref" in requestBody) {
|
|
168
|
+
const index = requestBody.$ref.split("/").pop();
|
|
169
|
+
return spec.components?.schemas?.[index];
|
|
170
|
+
}
|
|
171
|
+
return requestBody;
|
|
172
|
+
};
|
|
173
|
+
// Collect all operations
|
|
174
|
+
Object.entries(spec.paths || {}).forEach(([path, pathItem]) => {
|
|
175
|
+
if (!pathItem)
|
|
176
|
+
return;
|
|
177
|
+
["get", "post", "put", "delete", "patch"].forEach((method) => {
|
|
178
|
+
const operation = pathItem[method];
|
|
179
|
+
if (!operation)
|
|
180
|
+
return;
|
|
181
|
+
operations.push({
|
|
182
|
+
method: method.toUpperCase(),
|
|
183
|
+
path,
|
|
184
|
+
operationId: `${method}${(0, utils_1.sanitizeTypeName)(operation.operationId || `${path.replace(/\W+/g, "_")}`)}`,
|
|
185
|
+
summary: operation.summary,
|
|
186
|
+
description: operation.description,
|
|
187
|
+
parameters: resolveParameters([...(pathItem.parameters || []), ...(operation.parameters || [])]),
|
|
188
|
+
requestBody: resolveRequestBody(operation.requestBody),
|
|
189
|
+
responses: operation.responses,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
const title = (0, utils_1.specTitle)(spec);
|
|
194
|
+
return `import type { AxiosResponse, AxiosRequestConfig } from 'axios';
|
|
195
|
+
import { getApiClient } from './apiClient';
|
|
196
|
+
import type * as T from './${title}.schema';
|
|
197
|
+
|
|
198
|
+
${operations.map((op) => generateAxiosMethod(op, spec)).join("\n\n")}`;
|
|
199
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateInstance(): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateInstance = generateInstance;
|
|
4
|
+
function generateInstance() {
|
|
5
|
+
return `import type { AxiosInstance } from 'axios';
|
|
6
|
+
|
|
7
|
+
let apiClient: AxiosInstance;
|
|
8
|
+
|
|
9
|
+
export function setApiClient(instance: AxiosInstance): void {
|
|
10
|
+
apiClient = instance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getApiClient(): AxiosInstance {
|
|
14
|
+
if (!apiClient) {
|
|
15
|
+
throw new Error('API client not configured. Call setApiClient with an axios instance first.');
|
|
16
|
+
}
|
|
17
|
+
return apiClient;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { apiClient };`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateReactQuery = generateReactQuery;
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
function generateQueryOptions(operation, spec) {
|
|
6
|
+
const { operationId, parameters, requestBody } = operation;
|
|
7
|
+
const hasData = (parameters && parameters.length > 0) || operation.requestBody;
|
|
8
|
+
// Helper to get required fields from a schema
|
|
9
|
+
const getRequiredFields = (schema, context) => {
|
|
10
|
+
if ("$ref" in schema) {
|
|
11
|
+
const refType = schema.$ref.split("/").pop();
|
|
12
|
+
const refSchema = context.schemas[refType];
|
|
13
|
+
return refSchema?.required?.map((p) => `'${p}'`) || [];
|
|
14
|
+
}
|
|
15
|
+
return schema.required?.map((p) => `'${p}'`) || [];
|
|
16
|
+
};
|
|
17
|
+
// Get required parameter names from both parameters and request body
|
|
18
|
+
const requiredParams = [
|
|
19
|
+
...(parameters?.filter((p) => p.required).map((p) => `'${p.name}'`) || []),
|
|
20
|
+
...(requestBody && "content" in requestBody && requestBody.content?.["application/json"]?.schema
|
|
21
|
+
? getRequiredFields(requestBody.content["application/json"].schema, {
|
|
22
|
+
schemas: spec.components?.schemas || {},
|
|
23
|
+
})
|
|
24
|
+
: []),
|
|
25
|
+
...(requestBody && "content" in requestBody && requestBody.content?.["multipart/form-data"]?.schema
|
|
26
|
+
? getRequiredFields(requestBody.content["multipart/form-data"].schema, {
|
|
27
|
+
schemas: spec.components?.schemas || {},
|
|
28
|
+
})
|
|
29
|
+
: []),
|
|
30
|
+
];
|
|
31
|
+
const namedQuery = (0, utils_1.camelCase)(operationId);
|
|
32
|
+
return `
|
|
33
|
+
export const ${namedQuery}QueryOptions = (
|
|
34
|
+
${hasData ? `params: Partial<Parameters<typeof apiClient.${namedQuery}>[0]>, config?: Partial<Parameters<typeof apiClient.${namedQuery}>[1]>` : ""}
|
|
35
|
+
) => {
|
|
36
|
+
const enabled = ${hasData ? `hasDefinedProps(params, ${requiredParams.join(", ")})` : "true"};
|
|
37
|
+
return queryOptions({
|
|
38
|
+
queryKey: ['${(0, utils_1.camelCase)(operationId)}', ${hasData ? "params" : "undefined"}],
|
|
39
|
+
queryFn: enabled ? async () => {
|
|
40
|
+
const response = await apiClient.${namedQuery}(${hasData ? "params" : "undefined"}, config);
|
|
41
|
+
return response.data;
|
|
42
|
+
} : skipToken,
|
|
43
|
+
});
|
|
44
|
+
};`;
|
|
45
|
+
}
|
|
46
|
+
function generateReactQuery(spec) {
|
|
47
|
+
const operations = [];
|
|
48
|
+
// Collect operations (same as in clientGenerator)
|
|
49
|
+
Object.entries(spec.paths || {}).forEach(([path, pathItem]) => {
|
|
50
|
+
if (!pathItem)
|
|
51
|
+
return;
|
|
52
|
+
["get", "post", "put", "delete", "patch"].forEach((method) => {
|
|
53
|
+
const operation = pathItem[method];
|
|
54
|
+
if (!operation)
|
|
55
|
+
return;
|
|
56
|
+
operations.push({
|
|
57
|
+
method: method.toUpperCase(),
|
|
58
|
+
path,
|
|
59
|
+
operationId: `${method}${(0, utils_1.sanitizeTypeName)(operation.operationId || `${path.replace(/\W+/g, "_")}`)}`,
|
|
60
|
+
summary: operation.summary,
|
|
61
|
+
description: operation.description,
|
|
62
|
+
parameters: [
|
|
63
|
+
...(pathItem.parameters || []),
|
|
64
|
+
...(operation.parameters || []),
|
|
65
|
+
],
|
|
66
|
+
requestBody: operation.requestBody,
|
|
67
|
+
responses: operation.responses,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
return `import { queryOptions, skipToken } from '@tanstack/react-query';
|
|
72
|
+
import * as apiClient from './${(0, utils_1.specTitle)(spec)}.client';
|
|
73
|
+
const hasDefinedProps = <T extends { [P in K]?: any }, K extends PropertyKey>(
|
|
74
|
+
obj: T,
|
|
75
|
+
...keys: K[]
|
|
76
|
+
): obj is T & { [P in K]-?: Exclude<T[P], undefined> } => {
|
|
77
|
+
return keys.every((k) => obj[k] !== undefined);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
${operations.map((op) => generateQueryOptions(op, spec)).join("\n\n")}
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateTypeDefinitions = generateTypeDefinitions;
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
/**
|
|
6
|
+
* Converts OpenAPI schema type to TypeScript type
|
|
7
|
+
*/
|
|
8
|
+
function getTypeFromSchema(schema, context) {
|
|
9
|
+
if (!schema)
|
|
10
|
+
return "any";
|
|
11
|
+
if ("$ref" in schema) {
|
|
12
|
+
const refType = schema.$ref.split("/").pop();
|
|
13
|
+
return (0, utils_1.sanitizeTypeName)(refType);
|
|
14
|
+
}
|
|
15
|
+
// Handle enum types properly
|
|
16
|
+
if (schema.enum) {
|
|
17
|
+
return schema.enum.map((e) => (typeof e === "string" ? `'${e}'` : e)).join(" | ");
|
|
18
|
+
}
|
|
19
|
+
switch (schema.type) {
|
|
20
|
+
case "string":
|
|
21
|
+
return "string";
|
|
22
|
+
case "number":
|
|
23
|
+
case "integer":
|
|
24
|
+
return "number";
|
|
25
|
+
case "boolean":
|
|
26
|
+
return "boolean";
|
|
27
|
+
case "array": {
|
|
28
|
+
const itemType = getTypeFromSchema(schema.items, context);
|
|
29
|
+
return `Array<${itemType}>`;
|
|
30
|
+
}
|
|
31
|
+
case "object":
|
|
32
|
+
if (schema.properties) {
|
|
33
|
+
const properties = Object.entries(schema.properties)
|
|
34
|
+
.map(([key, prop]) => {
|
|
35
|
+
const isRequired = schema.required?.includes(key);
|
|
36
|
+
const propertyType = getTypeFromSchema(prop, context);
|
|
37
|
+
const safeName = (0, utils_1.sanitizePropertyName)(key);
|
|
38
|
+
return ` ${safeName}${isRequired ? "" : "?"}: ${propertyType};`;
|
|
39
|
+
})
|
|
40
|
+
.join("\n");
|
|
41
|
+
return `{\n${properties}\n}`;
|
|
42
|
+
}
|
|
43
|
+
if (schema.additionalProperties) {
|
|
44
|
+
const valueType = typeof schema.additionalProperties === "boolean"
|
|
45
|
+
? "any"
|
|
46
|
+
: getTypeFromSchema(schema.additionalProperties, context);
|
|
47
|
+
return `Record<string, ${valueType}>`;
|
|
48
|
+
}
|
|
49
|
+
return "Record<string, any>";
|
|
50
|
+
default:
|
|
51
|
+
return "any";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function generateTypeDefinition(badName, schema, context) {
|
|
55
|
+
const description = !("$ref" in schema) && schema.description ? `/**\n * ${schema.description}\n */\n` : "";
|
|
56
|
+
const name = (0, utils_1.sanitizeTypeName)(badName);
|
|
57
|
+
const typeValue = getTypeFromSchema(schema, context);
|
|
58
|
+
// Use 'type' for primitives, unions, and simple types
|
|
59
|
+
// Use 'interface' only for complex objects with properties
|
|
60
|
+
const isInterface = !("$ref" in schema) && schema.type === "object" && schema.properties;
|
|
61
|
+
const namedInterface = (0, utils_1.pascalCase)(name);
|
|
62
|
+
return isInterface
|
|
63
|
+
? `${description}export interface ${name} ${typeValue}\n\n`
|
|
64
|
+
: `${description}export type ${namedInterface} = ${typeValue}\n\n`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generates TypeScript interface definitions from OpenAPI schemas
|
|
68
|
+
*/
|
|
69
|
+
function generateTypeDefinitions(spec) {
|
|
70
|
+
const context = {
|
|
71
|
+
schemas: spec.components?.schemas || {},
|
|
72
|
+
generatedTypes: new Set(),
|
|
73
|
+
};
|
|
74
|
+
let output = "/* Generated TypeScript Definitions */\n\n";
|
|
75
|
+
// Generate types for all schema definitions
|
|
76
|
+
for (const [name, schema] of Object.entries(context.schemas)) {
|
|
77
|
+
if (context.generatedTypes.has(name))
|
|
78
|
+
continue;
|
|
79
|
+
output += generateTypeDefinition(name, schema, context);
|
|
80
|
+
context.generatedTypes.add(name);
|
|
81
|
+
}
|
|
82
|
+
// Generate request/response types
|
|
83
|
+
if (spec.paths) {
|
|
84
|
+
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
85
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
86
|
+
if (method === "$ref")
|
|
87
|
+
continue;
|
|
88
|
+
const operationObject = operation;
|
|
89
|
+
if (!operationObject)
|
|
90
|
+
continue;
|
|
91
|
+
// Generate request body type
|
|
92
|
+
if (operationObject.requestBody) {
|
|
93
|
+
const content = operationObject.requestBody.content;
|
|
94
|
+
const jsonContent = content["application/json"] || content["multipart/form-data"];
|
|
95
|
+
if (jsonContent?.schema) {
|
|
96
|
+
const typeName = (0, utils_1.sanitizeTypeName)(`${operationObject.operationId}Request`);
|
|
97
|
+
output += generateTypeDefinition(typeName, jsonContent.schema, context);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Generate response types
|
|
101
|
+
if (operationObject.responses) {
|
|
102
|
+
for (const [code, response] of Object.entries(operationObject.responses)) {
|
|
103
|
+
const responseObj = response;
|
|
104
|
+
const content = responseObj.content?.["application/json"];
|
|
105
|
+
if (content?.schema) {
|
|
106
|
+
const opName = `${method}${(0, utils_1.sanitizeTypeName)(operationObject.operationId || `${path.replace(/\W+/g, "_")}`)}`;
|
|
107
|
+
const typeName = (0, utils_1.sanitizeTypeName)(`${opName}_Response${code}`);
|
|
108
|
+
output += generateTypeDefinition(typeName, content.schema, context);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return output;
|
|
116
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { OpenAPIConfig } from "./types/config";
|
|
2
|
+
/**
|
|
3
|
+
* Main function to generate the API client
|
|
4
|
+
*/
|
|
5
|
+
export declare function codegenerate(config: OpenAPIConfig): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,106 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.codegenerate = codegenerate;
|
|
40
|
+
const promises_1 = require("node:fs/promises");
|
|
41
|
+
const node_path_1 = require("node:path");
|
|
42
|
+
const axios_1 = __importDefault(require("axios"));
|
|
43
|
+
const yaml = __importStar(require("yaml"));
|
|
44
|
+
const clientGenerator_1 = require("./generator/clientGenerator");
|
|
45
|
+
const instanceGenerator_1 = require("./generator/instanceGenerator");
|
|
46
|
+
const reactQueryGenerator_1 = require("./generator/reactQueryGenerator");
|
|
47
|
+
const schemaGenerator_1 = require("./generator/schemaGenerator");
|
|
48
|
+
const utils_1 = require("./utils");
|
|
49
|
+
/**
|
|
50
|
+
* Loads the OpenAPI specification from either a URL or local file
|
|
51
|
+
*/
|
|
52
|
+
async function loadOpenAPISpec(specSource) {
|
|
53
|
+
try {
|
|
54
|
+
if (specSource.startsWith("http")) {
|
|
55
|
+
const response = await axios_1.default.get(specSource);
|
|
56
|
+
// Check if response is YAML by looking for common YAML indicators
|
|
57
|
+
const isYaml = typeof response.data === "string" &&
|
|
58
|
+
(response.data.trim().startsWith("openapi:") || response.data.trim().startsWith("swagger:"));
|
|
59
|
+
return isYaml ? yaml.parse(response.data) : response.data;
|
|
60
|
+
}
|
|
61
|
+
const content = await (0, promises_1.readFile)(specSource, "utf-8");
|
|
62
|
+
// Handle both JSON and YAML formats
|
|
63
|
+
return specSource.endsWith(".json") ? JSON.parse(content) : yaml.parse(content);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof Error) {
|
|
67
|
+
throw new Error(`Failed to load OpenAPI spec: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
throw new Error("Failed to load OpenAPI spec: Unknown error");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Main function to generate the API client
|
|
74
|
+
*/
|
|
75
|
+
async function codegenerate(config) {
|
|
76
|
+
try {
|
|
77
|
+
// Load specs
|
|
78
|
+
const specs = Array.isArray(config.specSource)
|
|
79
|
+
? await Promise.all(config.specSource.map(loadOpenAPISpec))
|
|
80
|
+
: [await loadOpenAPISpec(config.specSource)];
|
|
81
|
+
// Create export directory if it doesn't exist
|
|
82
|
+
await (0, promises_1.mkdir)(config.exportDir, { recursive: true });
|
|
83
|
+
// Generate and write type definitions
|
|
84
|
+
const instance = (0, instanceGenerator_1.generateInstance)();
|
|
85
|
+
await (0, promises_1.writeFile)((0, node_path_1.resolve)(config.exportDir, "apiClient.ts"), instance, "utf-8");
|
|
86
|
+
// Generate for each spec...
|
|
87
|
+
for (const spec of specs) {
|
|
88
|
+
const title = (0, utils_1.specTitle)(spec);
|
|
89
|
+
// Generate and write type definitions
|
|
90
|
+
const typeDefinitions = (0, schemaGenerator_1.generateTypeDefinitions)(spec);
|
|
91
|
+
await (0, promises_1.writeFile)((0, node_path_1.resolve)(config.exportDir, `${title}.schema.ts`), typeDefinitions, "utf-8");
|
|
92
|
+
// Generate and write API client
|
|
93
|
+
const clientCode = (0, clientGenerator_1.generateApiClient)(spec, config);
|
|
94
|
+
await (0, promises_1.writeFile)((0, node_path_1.resolve)(config.exportDir, `${title}.client.ts`), clientCode, "utf-8");
|
|
95
|
+
// Generate and write React Query options
|
|
96
|
+
const queryCode = (0, reactQueryGenerator_1.generateReactQuery)(spec);
|
|
97
|
+
await (0, promises_1.writeFile)((0, node_path_1.resolve)(config.exportDir, `${title}.queryOptions.ts`), queryCode, "utf-8");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof Error) {
|
|
102
|
+
throw new Error(`Failed to generate API client: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
throw new Error("Failed to generate API client: Unknown error");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface OpenAPIConfig {
|
|
2
|
+
/**
|
|
3
|
+
* OpenAPI specification source - can be a URL or local file path
|
|
4
|
+
* Supports JSON, YAML, or YML formats
|
|
5
|
+
*/
|
|
6
|
+
specSource: string | string[];
|
|
7
|
+
/**
|
|
8
|
+
* Directory where generated files will be exported
|
|
9
|
+
*/
|
|
10
|
+
exportDir: string;
|
|
11
|
+
}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED