react-query-lightbase-codegen 2.5.11 → 3.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.
- 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 +50 -82
- package/dist/generator/reactQueryGenerator.js +7 -44
- package/dist/generator/schemaGenerator.js +40 -51
- package/dist/utils.d.ts +35 -1
- package/dist/utils.js +118 -14
- package/dist/utils.js.map +1 -1
- package/package.json +4 -1
- package/src/cli.ts +212 -0
- package/src/generator/clientGenerator.ts +60 -110
- package/src/generator/reactQueryGenerator.ts +15 -50
- package/src/generator/schemaGenerator.ts +51 -62
- package/src/utils.ts +144 -15
package/src/cli.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { codegenerate } from "./index";
|
|
6
|
+
import type { OpenAPIConfig } from "./types/config";
|
|
7
|
+
|
|
8
|
+
const VERSION = "2.5.11";
|
|
9
|
+
|
|
10
|
+
const HELP = `
|
|
11
|
+
react-query-lightbase-codegen - Generate React Query clients from OpenAPI specs
|
|
12
|
+
|
|
13
|
+
USAGE:
|
|
14
|
+
npx react-query-lightbase-codegen [options] <spec...> -o <output>
|
|
15
|
+
npx react-query-lightbase-codegen --config <config-file>
|
|
16
|
+
|
|
17
|
+
ARGUMENTS:
|
|
18
|
+
<spec...> One or more OpenAPI spec files (local paths or URLs)
|
|
19
|
+
|
|
20
|
+
OPTIONS:
|
|
21
|
+
-o, --output <dir> Output directory for generated files (default: ./generated)
|
|
22
|
+
-c, --config <file> Path to JSON config file
|
|
23
|
+
-h, --help Show this help message
|
|
24
|
+
-v, --version Show version number
|
|
25
|
+
|
|
26
|
+
EXAMPLES:
|
|
27
|
+
# Single local spec
|
|
28
|
+
npx react-query-lightbase-codegen ./api.yaml -o ./src/generated
|
|
29
|
+
|
|
30
|
+
# Remote spec
|
|
31
|
+
npx react-query-lightbase-codegen https://api.example.com/openapi.json -o ./generated
|
|
32
|
+
|
|
33
|
+
# Multiple specs
|
|
34
|
+
npx react-query-lightbase-codegen ./auth.yaml ./users.yaml -o ./generated
|
|
35
|
+
|
|
36
|
+
# Using config file
|
|
37
|
+
npx react-query-lightbase-codegen --config ./codegen.json
|
|
38
|
+
|
|
39
|
+
CONFIG FILE FORMAT:
|
|
40
|
+
{
|
|
41
|
+
"specSource": "./api.yaml", // or ["./auth.yaml", "./users.yaml"]
|
|
42
|
+
"exportDir": "./src/generated"
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
interface ParsedArgs {
|
|
47
|
+
specs: string[];
|
|
48
|
+
output: string;
|
|
49
|
+
config?: string;
|
|
50
|
+
help: boolean;
|
|
51
|
+
version: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseArgs(args: string[]): ParsedArgs {
|
|
55
|
+
const result: ParsedArgs = {
|
|
56
|
+
specs: [],
|
|
57
|
+
output: "./generated",
|
|
58
|
+
help: false,
|
|
59
|
+
version: false,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let i = 0;
|
|
63
|
+
while (i < args.length) {
|
|
64
|
+
const arg = args[i];
|
|
65
|
+
|
|
66
|
+
switch (arg) {
|
|
67
|
+
case "-h":
|
|
68
|
+
case "--help":
|
|
69
|
+
result.help = true;
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case "-v":
|
|
73
|
+
case "--version":
|
|
74
|
+
result.version = true;
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case "-o":
|
|
78
|
+
case "--output":
|
|
79
|
+
i++;
|
|
80
|
+
if (i >= args.length) {
|
|
81
|
+
throw new Error("Missing value for --output");
|
|
82
|
+
}
|
|
83
|
+
result.output = args[i];
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case "-c":
|
|
87
|
+
case "--config":
|
|
88
|
+
i++;
|
|
89
|
+
if (i >= args.length) {
|
|
90
|
+
throw new Error("Missing value for --config");
|
|
91
|
+
}
|
|
92
|
+
result.config = args[i];
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
default:
|
|
96
|
+
if (arg.startsWith("-")) {
|
|
97
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
98
|
+
}
|
|
99
|
+
result.specs.push(arg);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
i++;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function loadConfigFile(configPath: string): Promise<OpenAPIConfig> {
|
|
109
|
+
try {
|
|
110
|
+
const content = await readFile(configPath, "utf-8");
|
|
111
|
+
const config = JSON.parse(content);
|
|
112
|
+
|
|
113
|
+
if (!config.specSource) {
|
|
114
|
+
throw new Error("Config file must contain 'specSource'");
|
|
115
|
+
}
|
|
116
|
+
if (!config.exportDir) {
|
|
117
|
+
throw new Error("Config file must contain 'exportDir'");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
specSource: config.specSource,
|
|
122
|
+
exportDir: resolve(process.cwd(), config.exportDir),
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
throw new Error(`Failed to load config file: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
throw new Error("Failed to load config file");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolveSpecPath(spec: string): string {
|
|
133
|
+
// Keep URLs as-is
|
|
134
|
+
if (spec.startsWith("http://") || spec.startsWith("https://")) {
|
|
135
|
+
return spec;
|
|
136
|
+
}
|
|
137
|
+
// Resolve local paths relative to cwd
|
|
138
|
+
return resolve(process.cwd(), spec);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function main(): Promise<void> {
|
|
142
|
+
const args = process.argv.slice(2);
|
|
143
|
+
|
|
144
|
+
if (args.length === 0) {
|
|
145
|
+
console.log(HELP);
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let parsed: ParsedArgs;
|
|
150
|
+
try {
|
|
151
|
+
parsed = parseArgs(args);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof Error) {
|
|
154
|
+
console.error(`Error: ${error.message}`);
|
|
155
|
+
}
|
|
156
|
+
console.error("Use --help for usage information");
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (parsed.help) {
|
|
161
|
+
console.log(HELP);
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (parsed.version) {
|
|
166
|
+
console.log(`react-query-lightbase-codegen v${VERSION}`);
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let config: OpenAPIConfig;
|
|
171
|
+
|
|
172
|
+
if (parsed.config) {
|
|
173
|
+
// Load from config file
|
|
174
|
+
config = await loadConfigFile(parsed.config);
|
|
175
|
+
} else {
|
|
176
|
+
// Build config from CLI args
|
|
177
|
+
if (parsed.specs.length === 0) {
|
|
178
|
+
console.error("Error: No spec files provided");
|
|
179
|
+
console.error("Use --help for usage information");
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const resolvedSpecs = parsed.specs.map(resolveSpecPath);
|
|
184
|
+
|
|
185
|
+
config = {
|
|
186
|
+
specSource: resolvedSpecs.length === 1 ? resolvedSpecs[0] : resolvedSpecs,
|
|
187
|
+
exportDir: resolve(process.cwd(), parsed.output),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
console.log("Generating API client...");
|
|
193
|
+
console.log(
|
|
194
|
+
` Specs: ${Array.isArray(config.specSource) ? config.specSource.join(", ") : config.specSource}`
|
|
195
|
+
);
|
|
196
|
+
console.log(` Output: ${config.exportDir}`);
|
|
197
|
+
console.log("");
|
|
198
|
+
|
|
199
|
+
await codegenerate(config);
|
|
200
|
+
|
|
201
|
+
console.log("Generation complete!");
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error instanceof Error) {
|
|
204
|
+
console.error(`Error: ${error.message}`);
|
|
205
|
+
} else {
|
|
206
|
+
console.error("An unknown error occurred");
|
|
207
|
+
}
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
main();
|
|
@@ -1,63 +1,55 @@
|
|
|
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
|
-
const { method, path, operationId, summary, description, parameters, requestBody, responses } =
|
|
13
|
+
const { method, path, operationId, summary, description, deprecated, parameters, requestBody, responses } =
|
|
14
|
+
operation;
|
|
29
15
|
// Generate JSDoc
|
|
30
16
|
const jsDocLines = ["/**"];
|
|
17
|
+
if (deprecated) jsDocLines.push(" * @deprecated");
|
|
31
18
|
if (summary) jsDocLines.push(` * ${summary}`);
|
|
32
19
|
if (description) jsDocLines.push(` * ${description}`);
|
|
33
20
|
|
|
34
21
|
// Add parameter descriptions
|
|
35
22
|
parameters?.forEach((param) => {
|
|
36
23
|
const desc = param.description ? ` - ${param.description}` : "";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
const prefix =
|
|
25
|
+
param.in === "path"
|
|
26
|
+
? "params."
|
|
27
|
+
: param.in === "query"
|
|
28
|
+
? "query."
|
|
29
|
+
: param.in === "header"
|
|
30
|
+
? "headers."
|
|
31
|
+
: param.in === "cookie"
|
|
32
|
+
? "cookies."
|
|
33
|
+
: "";
|
|
34
|
+
jsDocLines.push(` * @param ${prefix}${param.name}${desc}`);
|
|
40
35
|
});
|
|
41
36
|
|
|
42
37
|
if (requestBody && "description" in requestBody) {
|
|
43
38
|
jsDocLines.push(` * @param data - ${requestBody.description}`);
|
|
44
39
|
}
|
|
45
40
|
|
|
46
|
-
// Add return type description
|
|
47
|
-
const responseDetails =
|
|
41
|
+
// Add return type description - prefer 2xx responses, fall back to "default"
|
|
42
|
+
const responseDetails =
|
|
43
|
+
Object.entries(responses).find(([code]) => code.startsWith("2")) ||
|
|
44
|
+
Object.entries(responses).find(([code]) => code === "default");
|
|
48
45
|
if (responseDetails) {
|
|
49
46
|
const [code, response] = responseDetails;
|
|
50
47
|
const responseObj = response as OpenAPIV3.ResponseObject;
|
|
51
48
|
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
|
-
|
|
49
|
+
const contentSchema = getContentSchema(responseObj.content);
|
|
58
50
|
const typeName = pascalCase(`${operationId}Response${code}`);
|
|
59
51
|
|
|
60
|
-
if (
|
|
52
|
+
if (contentSchema) {
|
|
61
53
|
if (desc) {
|
|
62
54
|
jsDocLines.push(` * @returns ${desc}`);
|
|
63
55
|
}
|
|
@@ -72,6 +64,7 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
72
64
|
const urlParams = parameters?.filter((p) => p.in === "path") || [];
|
|
73
65
|
const queryParams = parameters?.filter((p) => p.in === "query") || [];
|
|
74
66
|
const headerParams = parameters?.filter((p) => p.in === "header") || [];
|
|
67
|
+
const cookieParams = parameters?.filter((p) => p.in === "cookie") || [];
|
|
75
68
|
|
|
76
69
|
const isFormData = requestBody && "content" in requestBody && requestBody.content?.["multipart/form-data"];
|
|
77
70
|
|
|
@@ -79,15 +72,9 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
79
72
|
? resolveSchema(requestBody.content["multipart/form-data"].schema, spec)
|
|
80
73
|
: undefined;
|
|
81
74
|
|
|
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;
|
|
75
|
+
const requestBodyContent =
|
|
76
|
+
requestBody && "content" in requestBody ? getContentSchema(requestBody.content) : undefined;
|
|
77
|
+
const requestBodySchema = requestBodyContent ? resolveSchema(requestBodyContent, spec) : undefined;
|
|
91
78
|
|
|
92
79
|
// Check if request body is a primitive type (string, number, boolean)
|
|
93
80
|
const isPrimitiveRequestBody =
|
|
@@ -102,11 +89,19 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
102
89
|
const namedType = pascalCase(operationId);
|
|
103
90
|
|
|
104
91
|
// Get response type from 2xx response
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
92
|
+
const responseType = (() => {
|
|
93
|
+
if (!responseDetails) return "unknown";
|
|
94
|
+
const [code, response] = responseDetails;
|
|
95
|
+
// If response has content, use the generated type
|
|
96
|
+
if ("content" in response && response.content) {
|
|
97
|
+
return `T.${namedType}Response${code}`;
|
|
98
|
+
}
|
|
99
|
+
// 204 (No Content) and 205 (Reset Content) should return void
|
|
100
|
+
if (code === "204" || code === "205") {
|
|
101
|
+
return "void";
|
|
102
|
+
}
|
|
103
|
+
return "unknown";
|
|
104
|
+
})();
|
|
110
105
|
|
|
111
106
|
const urlWithParams =
|
|
112
107
|
urlParams.length > 0 ? `\`${path.replace(/{(\w+)}/g, "${encodeURIComponent(data.$1)}")}\`` : `"${path}"`;
|
|
@@ -151,27 +146,25 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
151
146
|
.join("\n ")}`
|
|
152
147
|
: "",
|
|
153
148
|
queryParams.length > 0 ? "axiosConfig.params = { ...axiosConfig.params, ...queryData };" : "",
|
|
154
|
-
isFormData
|
|
155
|
-
? "axiosConfig.headers = { ...axiosConfig.headers, 'Content-Type': 'multipart/form-data' };"
|
|
156
|
-
: "",
|
|
157
149
|
headerParams.length > 0
|
|
158
|
-
? `
|
|
159
|
-
${headerParams.map((p) => `["${p.name}"]: data["${p.name}"]`).join(",\n ")}
|
|
160
|
-
};`
|
|
150
|
+
? `axiosConfig.headers = { ...axiosConfig.headers, ${headerParams.map((p) => `["${p.name}"]: data["${p.name}"]`).join(", ")} };`
|
|
161
151
|
: "",
|
|
162
|
-
|
|
152
|
+
cookieParams.length > 0
|
|
153
|
+
? `axiosConfig.headers = { ...axiosConfig.headers, Cookie: [${cookieParams.map((p) => `data["${p.name}"] != null ? \`${p.name}=\${data["${p.name}"]}\` : null`).join(", ")}].filter(Boolean).join("; ") };`
|
|
154
|
+
: "",
|
|
155
|
+
// Note: Don't set Content-Type for FormData - Axios will set it automatically with the correct boundary
|
|
163
156
|
requestBody
|
|
164
|
-
?
|
|
165
|
-
|
|
166
|
-
|
|
157
|
+
? responseType === "void"
|
|
158
|
+
? `await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || requestBodySchema?.properties ? "bodyData" : "data"}, axiosConfig);`
|
|
159
|
+
: `const res = await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || requestBodySchema?.properties ? "bodyData" : "data"}, axiosConfig);`
|
|
160
|
+
: responseType === "void"
|
|
161
|
+
? `await apiClient.${method}<${responseType}>(url, axiosConfig);`
|
|
162
|
+
: `const res = await apiClient.${method}<${responseType}>(url, axiosConfig);`,
|
|
163
|
+
responseType !== "void" ? "return res.data;" : "",
|
|
167
164
|
]
|
|
168
165
|
.filter(Boolean)
|
|
169
166
|
.join("\n ");
|
|
170
167
|
|
|
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
168
|
const requestParms = hasData
|
|
176
169
|
? isPrimitiveRequestBody
|
|
177
170
|
? `props: { data: T.${pascalCase(operationId)}Params; axiosConfig?: AxiosRequestConfig; }`
|
|
@@ -186,53 +179,10 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
186
179
|
}
|
|
187
180
|
|
|
188
181
|
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
|
-
|
|
182
|
+
const operations = collectOperations(spec);
|
|
233
183
|
const title = specTitle(spec);
|
|
234
184
|
|
|
235
|
-
return `import type {
|
|
185
|
+
return `import type { AxiosRequestConfig } from 'axios';
|
|
236
186
|
import { getApiClient } from './apiClient';
|
|
237
187
|
import type * as T from './${title}.schema';
|
|
238
188
|
|
|
@@ -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, deprecated } = 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)
|
|
@@ -90,8 +78,10 @@ function generateQueryOptions(operation: OperationInfo, spec: OpenAPIV3.Document
|
|
|
90
78
|
: `hasDefinedProps(${paramsVariable}, ${requiredParams.join(", ")})`
|
|
91
79
|
: "true";
|
|
92
80
|
|
|
81
|
+
const deprecatedComment = deprecated ? "/** @deprecated */\n" : "";
|
|
82
|
+
|
|
93
83
|
return `
|
|
94
|
-
export const ${namedQueryOptions} = (
|
|
84
|
+
${deprecatedComment}export const ${namedQueryOptions} = (
|
|
95
85
|
${hasData ? `props: Partial<Parameters<typeof apiClient.${namedQuery}>[0]>` : `props?: Partial<Parameters<typeof apiClient.${namedQuery}>[0]>`}
|
|
96
86
|
) => {
|
|
97
87
|
${destructuringLine}
|
|
@@ -104,37 +94,12 @@ export const ${namedQueryOptions} = (
|
|
|
104
94
|
}
|
|
105
95
|
|
|
106
96
|
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
|
-
});
|
|
97
|
+
const operations = collectOperations(spec);
|
|
131
98
|
|
|
132
99
|
return `import { queryOptions, skipToken } from '@tanstack/react-query';
|
|
133
100
|
import * as apiClient from './${specTitle(spec)}.client';
|
|
134
|
-
// TEMPORARY: allows for backward compatibility imports
|
|
135
|
-
export * from './${specTitle(spec)}.client';
|
|
136
101
|
|
|
137
|
-
const hasDefinedProps = <T extends { [P in K]?:
|
|
102
|
+
const hasDefinedProps = <T extends { [P in K]?: unknown }, K extends PropertyKey>(
|
|
138
103
|
obj: T,
|
|
139
104
|
...keys: K[]
|
|
140
105
|
): obj is T & { [P in K]-?: Exclude<T[P], undefined> } => {
|