react-query-lightbase-codegen 2.3.0 → 2.3.2
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/generator/clientGenerator.d.ts +1 -2
- package/dist/generator/clientGenerator.js +4 -24
- package/dist/generator/schemaGenerator.js +45 -63
- package/dist/index.js +1 -1
- package/dist/utils.d.ts +18 -0
- package/dist/utils.js +85 -0
- package/package.json +1 -1
- package/src/generator/clientGenerator.ts +12 -27
- package/src/generator/schemaGenerator.ts +48 -73
- package/src/index.ts +1 -1
- package/src/utils.ts +100 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
-
import type { OpenAPIConfig } from "../types/config";
|
|
3
2
|
export interface OperationInfo {
|
|
4
3
|
method: string;
|
|
5
4
|
path: string;
|
|
@@ -10,4 +9,4 @@ export interface OperationInfo {
|
|
|
10
9
|
requestBody?: OpenAPIV3.RequestBodyObject;
|
|
11
10
|
responses: OpenAPIV3.ResponsesObject;
|
|
12
11
|
}
|
|
13
|
-
export declare function generateApiClient(spec: OpenAPIV3.Document
|
|
12
|
+
export declare function generateApiClient(spec: OpenAPIV3.Document): string;
|
|
@@ -65,11 +65,11 @@ function generateAxiosMethod(operation, spec) {
|
|
|
65
65
|
// Add path and query parameters
|
|
66
66
|
urlParams.forEach((p) => {
|
|
67
67
|
const safeName = (0, utils_1.sanitizePropertyName)(p.name);
|
|
68
|
-
dataProps.push(`${safeName}: ${
|
|
68
|
+
dataProps.push(`${safeName}: ${(0, utils_1.getTypeFromSchema)(p.schema)}`);
|
|
69
69
|
});
|
|
70
70
|
queryParams.forEach((p) => {
|
|
71
71
|
const safeName = (0, utils_1.sanitizePropertyName)(p.name);
|
|
72
|
-
dataProps.push(`${safeName}${p.required ? "" : "?"}: ${
|
|
72
|
+
dataProps.push(`${safeName}${p.required ? "" : "?"}: ${(0, utils_1.getTypeFromSchema)(p.schema)}`);
|
|
73
73
|
});
|
|
74
74
|
// Add request body type if it exists
|
|
75
75
|
const hasData = (parameters && parameters.length > 0) || operation.requestBody;
|
|
@@ -134,35 +134,15 @@ function generateAxiosMethod(operation, spec) {
|
|
|
134
134
|
.filter(Boolean)
|
|
135
135
|
.join("\n ");
|
|
136
136
|
const requestParms = hasData
|
|
137
|
-
? `props:
|
|
137
|
+
? `props: T.${(0, utils_1.pascalCase)(operationId)}Params & { axiosConfig?: AxiosRequestConfig; }`
|
|
138
138
|
: "props?: { axiosConfig?: AxiosRequestConfig }";
|
|
139
139
|
return `
|
|
140
|
-
${hasData ? `export type ${(0, utils_1.pascalCase)(operationId)}Params = ${dataType};` : ""}
|
|
141
140
|
${jsDocLines.join("\n ")}
|
|
142
141
|
export async function ${(0, utils_1.camelCase)(operationId)}(${requestParms}): Promise<${responseType}> {
|
|
143
142
|
${methodBody}
|
|
144
143
|
}`;
|
|
145
144
|
}
|
|
146
|
-
function
|
|
147
|
-
if ("schema" in param) {
|
|
148
|
-
const schema = param.schema;
|
|
149
|
-
switch (schema.type) {
|
|
150
|
-
case "string":
|
|
151
|
-
return "string";
|
|
152
|
-
case "integer":
|
|
153
|
-
case "number":
|
|
154
|
-
return "number";
|
|
155
|
-
case "boolean":
|
|
156
|
-
return "boolean";
|
|
157
|
-
case "array":
|
|
158
|
-
return "Array<any>"; // You might want to make this more specific
|
|
159
|
-
default:
|
|
160
|
-
return "any";
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return "any";
|
|
164
|
-
}
|
|
165
|
-
function generateApiClient(spec, config) {
|
|
145
|
+
function generateApiClient(spec) {
|
|
166
146
|
const operations = [];
|
|
167
147
|
const resolveParameters = (parameters) => {
|
|
168
148
|
return parameters.map((p) => {
|
|
@@ -2,62 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateTypeDefinitions = generateTypeDefinitions;
|
|
4
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
|
-
const nullable = schema.nullable ? " | null" : "";
|
|
16
|
-
// Handle enum types properly
|
|
17
|
-
if (schema.enum) {
|
|
18
|
-
return schema.enum.map((e) => (typeof e === "string" ? `'${e}'` : e)).join(" | ") + nullable;
|
|
19
|
-
}
|
|
20
|
-
switch (schema.type) {
|
|
21
|
-
case "string":
|
|
22
|
-
if ("format" in schema && schema.format === "binary") {
|
|
23
|
-
return `string | { name?: string; type?: string; uri: string }${nullable}`;
|
|
24
|
-
}
|
|
25
|
-
return `string${nullable}`;
|
|
26
|
-
case "number":
|
|
27
|
-
case "integer":
|
|
28
|
-
return `number${nullable}`;
|
|
29
|
-
case "boolean":
|
|
30
|
-
return `boolean${nullable}`;
|
|
31
|
-
case "array": {
|
|
32
|
-
const itemType = getTypeFromSchema(schema.items, context);
|
|
33
|
-
return `Array<${itemType}>${nullable}`;
|
|
34
|
-
}
|
|
35
|
-
case "object":
|
|
36
|
-
if (schema.properties) {
|
|
37
|
-
const properties = Object.entries(schema.properties)
|
|
38
|
-
.map(([key, prop]) => {
|
|
39
|
-
const isRequired = schema.required?.includes(key);
|
|
40
|
-
const propertyType = getTypeFromSchema(prop, context);
|
|
41
|
-
const safeName = (0, utils_1.sanitizePropertyName)(key);
|
|
42
|
-
return ` ${safeName}${isRequired ? "" : "?"}: ${propertyType};`;
|
|
43
|
-
})
|
|
44
|
-
.join("\n");
|
|
45
|
-
return `{${properties}\n}${nullable}`;
|
|
46
|
-
}
|
|
47
|
-
if (schema.additionalProperties) {
|
|
48
|
-
const valueType = typeof schema.additionalProperties === "boolean"
|
|
49
|
-
? "any"
|
|
50
|
-
: getTypeFromSchema(schema.additionalProperties, context);
|
|
51
|
-
return `Record<string, ${valueType}>${nullable}`;
|
|
52
|
-
}
|
|
53
|
-
return `Record<string, any>${nullable}`;
|
|
54
|
-
default:
|
|
55
|
-
return `any${nullable}`;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function generateTypeDefinition(name, schema, context) {
|
|
5
|
+
function generateTypeDefinition(name, schema) {
|
|
59
6
|
const description = !("$ref" in schema) && schema.description ? `/**\n * ${schema.description}\n */\n` : "";
|
|
60
|
-
const typeValue =
|
|
7
|
+
const typeValue = (0, utils_1.getTypeFromSchema)(schema);
|
|
61
8
|
// Use 'type' for primitives, unions, and simple types
|
|
62
9
|
// Use 'interface' only for complex objects with properties
|
|
63
10
|
const isInterface = !("$ref" in schema) && schema.type === "object" && schema.properties;
|
|
@@ -78,7 +25,7 @@ function generateTypeDefinitions(spec) {
|
|
|
78
25
|
for (const [name, schema] of Object.entries(context.schemas)) {
|
|
79
26
|
if (context.generatedTypes.has(name))
|
|
80
27
|
continue;
|
|
81
|
-
output += generateTypeDefinition(name, schema
|
|
28
|
+
output += generateTypeDefinition(name, schema);
|
|
82
29
|
context.generatedTypes.add(name);
|
|
83
30
|
}
|
|
84
31
|
// Generate request/response types
|
|
@@ -90,32 +37,67 @@ function generateTypeDefinitions(spec) {
|
|
|
90
37
|
const operationObject = operation;
|
|
91
38
|
if (!operationObject)
|
|
92
39
|
continue;
|
|
93
|
-
const operationId
|
|
40
|
+
const { operationId: badOperationId, requestBody, responses, parameters } = operationObject;
|
|
41
|
+
const operationId = `${(0, utils_1.sanitizeTypeName)(badOperationId || `${path.replace(/\W+/g, "_")}`)}`;
|
|
94
42
|
// Generate request body type
|
|
95
|
-
if (
|
|
96
|
-
const content =
|
|
43
|
+
if (requestBody) {
|
|
44
|
+
const content = requestBody.content;
|
|
97
45
|
const jsonContent = content["application/ld+json"] ??
|
|
98
46
|
content["application/json"] ??
|
|
99
47
|
content["multipart/form-data"] ??
|
|
100
48
|
content["application/octet-stream"];
|
|
101
49
|
if (jsonContent?.schema) {
|
|
102
50
|
const typeName = `${operationId}Request`;
|
|
103
|
-
output += generateTypeDefinition(typeName, jsonContent.schema
|
|
51
|
+
output += generateTypeDefinition(typeName, jsonContent.schema);
|
|
104
52
|
}
|
|
105
53
|
}
|
|
106
54
|
// Generate response types
|
|
107
|
-
if (
|
|
108
|
-
for (const [code, response] of Object.entries(
|
|
55
|
+
if (responses) {
|
|
56
|
+
for (const [code, response] of Object.entries(responses)) {
|
|
109
57
|
const responseObj = response;
|
|
110
58
|
const content = responseObj.content?.["application/ld+json"] ??
|
|
111
59
|
responseObj.content?.["application/json"] ??
|
|
112
60
|
responseObj.content?.["application/octet-stream"];
|
|
113
61
|
if (content?.schema) {
|
|
114
62
|
const typeName = `${operationId}Response${code}`;
|
|
115
|
-
output += generateTypeDefinition(typeName, content.schema
|
|
63
|
+
output += generateTypeDefinition(typeName, content.schema);
|
|
116
64
|
}
|
|
117
65
|
}
|
|
118
66
|
}
|
|
67
|
+
// Build data type parts
|
|
68
|
+
const dataProps = [];
|
|
69
|
+
const urlParams = (parameters?.filter((p) => "in" in p && p.in === "path") ||
|
|
70
|
+
[]);
|
|
71
|
+
const queryParams = (parameters?.filter((p) => "in" in p && p.in === "query") ||
|
|
72
|
+
[]);
|
|
73
|
+
// Add path and query parameters
|
|
74
|
+
urlParams.forEach((p) => {
|
|
75
|
+
const safeName = (0, utils_1.sanitizePropertyName)(p.name);
|
|
76
|
+
dataProps.push(`${safeName}: ${(0, utils_1.getTypeFromSchema)(p.schema)}`);
|
|
77
|
+
});
|
|
78
|
+
queryParams.forEach((p) => {
|
|
79
|
+
const safeName = (0, utils_1.sanitizePropertyName)(p.name);
|
|
80
|
+
dataProps.push(`${safeName}${p.required ? "" : "?"}: ${(0, utils_1.getTypeFromSchema)(p.schema)}`);
|
|
81
|
+
});
|
|
82
|
+
// Add request body type if it exists
|
|
83
|
+
const hasData = (parameters && parameters.length > 0) || requestBody;
|
|
84
|
+
let dataType = "undefined";
|
|
85
|
+
const namedType = (0, utils_1.pascalCase)(operationId);
|
|
86
|
+
if (hasData) {
|
|
87
|
+
if (requestBody && dataProps.length > 0) {
|
|
88
|
+
dataType = `${namedType}Request & { ${dataProps.join("; ")} }`;
|
|
89
|
+
}
|
|
90
|
+
else if (requestBody) {
|
|
91
|
+
dataType = `${namedType}Request`;
|
|
92
|
+
}
|
|
93
|
+
else if (dataProps.length > 0) {
|
|
94
|
+
dataType = `{ ${dataProps.join("; ")} }`;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
dataType = "Record<string, never>";
|
|
98
|
+
}
|
|
99
|
+
output += `\n\nexport type ${(0, utils_1.pascalCase)(operationId)}Params = ${dataType};\n\n`;
|
|
100
|
+
}
|
|
119
101
|
}
|
|
120
102
|
}
|
|
121
103
|
}
|
package/dist/index.js
CHANGED
|
@@ -90,7 +90,7 @@ async function codegenerate(config) {
|
|
|
90
90
|
const typeDefinitions = (0, schemaGenerator_1.generateTypeDefinitions)(spec);
|
|
91
91
|
await (0, promises_1.writeFile)((0, node_path_1.resolve)(config.exportDir, `${title}.schema.ts`), typeDefinitions, "utf-8");
|
|
92
92
|
// Generate and write API client
|
|
93
|
-
const clientCode = (0, clientGenerator_1.generateApiClient)(spec
|
|
93
|
+
const clientCode = (0, clientGenerator_1.generateApiClient)(spec);
|
|
94
94
|
await (0, promises_1.writeFile)((0, node_path_1.resolve)(config.exportDir, `${title}.client.ts`), clientCode, "utf-8");
|
|
95
95
|
// Generate and write React Query options
|
|
96
96
|
const queryCode = (0, reactQueryGenerator_1.generateReactQuery)(spec);
|
package/dist/utils.d.ts
CHANGED
|
@@ -33,3 +33,21 @@ export declare function sanitizePropertyName(name: string): string;
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function sanitizeTypeName(name: string): string;
|
|
35
35
|
export declare function specTitle(spec: OpenAPIV3.Document): string;
|
|
36
|
+
/**
|
|
37
|
+
* Converts an OpenAPI schema object into a TypeScript type string.
|
|
38
|
+
*
|
|
39
|
+
* Handles:
|
|
40
|
+
* - References ($ref) by extracting the type name
|
|
41
|
+
* - Nullable types by appending "| null"
|
|
42
|
+
* - Enums by creating union types of the values
|
|
43
|
+
* - Basic types (string, number, boolean)
|
|
44
|
+
* - Binary format strings as a union with file metadata object
|
|
45
|
+
* - Arrays by recursively getting the item type
|
|
46
|
+
* - Objects with properties by creating interfaces
|
|
47
|
+
* - Objects with additionalProperties as Records
|
|
48
|
+
* - Fallback to "any" for unknown types
|
|
49
|
+
*
|
|
50
|
+
* @param param - The OpenAPI schema/parameter object to convert
|
|
51
|
+
* @returns The TypeScript type as a string
|
|
52
|
+
*/
|
|
53
|
+
export declare function getTypeFromSchema(schema: OpenAPIV3.ParameterObject | OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined): string | undefined;
|
package/dist/utils.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.pascalCase = pascalCase;
|
|
|
5
5
|
exports.sanitizePropertyName = sanitizePropertyName;
|
|
6
6
|
exports.sanitizeTypeName = sanitizeTypeName;
|
|
7
7
|
exports.specTitle = specTitle;
|
|
8
|
+
exports.getTypeFromSchema = getTypeFromSchema;
|
|
8
9
|
function camelCase(str) {
|
|
9
10
|
return str
|
|
10
11
|
.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
|
|
@@ -53,3 +54,87 @@ function sanitizeTypeName(name) {
|
|
|
53
54
|
function specTitle(spec) {
|
|
54
55
|
return camelCase(spec.info.title.toLowerCase().replace(/\s+/g, "-"));
|
|
55
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Converts an OpenAPI schema object into a TypeScript type string.
|
|
59
|
+
*
|
|
60
|
+
* Handles:
|
|
61
|
+
* - References ($ref) by extracting the type name
|
|
62
|
+
* - Nullable types by appending "| null"
|
|
63
|
+
* - Enums by creating union types of the values
|
|
64
|
+
* - Basic types (string, number, boolean)
|
|
65
|
+
* - Binary format strings as a union with file metadata object
|
|
66
|
+
* - Arrays by recursively getting the item type
|
|
67
|
+
* - Objects with properties by creating interfaces
|
|
68
|
+
* - Objects with additionalProperties as Records
|
|
69
|
+
* - Fallback to "any" for unknown types
|
|
70
|
+
*
|
|
71
|
+
* @param param - The OpenAPI schema/parameter object to convert
|
|
72
|
+
* @returns The TypeScript type as a string
|
|
73
|
+
*/
|
|
74
|
+
function getTypeFromSchema(schema) {
|
|
75
|
+
if (!schema)
|
|
76
|
+
return undefined;
|
|
77
|
+
// Handle $ref by extracting the referenced type name
|
|
78
|
+
if ("$ref" in schema) {
|
|
79
|
+
const refType = schema.$ref.split("/").pop();
|
|
80
|
+
return sanitizeTypeName(refType);
|
|
81
|
+
}
|
|
82
|
+
// Add "| null" for nullable types
|
|
83
|
+
const nullable = "nullable" in schema && schema.nullable ? " | null" : "";
|
|
84
|
+
// Handle enums as union types
|
|
85
|
+
if ("enum" in schema && schema.enum) {
|
|
86
|
+
if (Object.values(schema.enum)?.length > 0) {
|
|
87
|
+
return (Object.values(schema.enum)
|
|
88
|
+
.map((e) => (typeof e === "string" ? `'${e}'` : e))
|
|
89
|
+
.join(" | ") + nullable);
|
|
90
|
+
}
|
|
91
|
+
return schema.enum.map((e) => (typeof e === "string" ? `'${e}'` : e)).join(" | ") + nullable;
|
|
92
|
+
}
|
|
93
|
+
// Handle types based on the "type" property
|
|
94
|
+
if ("type" in schema) {
|
|
95
|
+
switch (schema.type) {
|
|
96
|
+
case "string":
|
|
97
|
+
// Special case for binary format strings
|
|
98
|
+
if ("format" in schema && schema.format === "binary") {
|
|
99
|
+
return `string | { name?: string; type?: string; uri: string }${nullable}`;
|
|
100
|
+
}
|
|
101
|
+
return `string${nullable}`;
|
|
102
|
+
case "number":
|
|
103
|
+
case "integer":
|
|
104
|
+
return `number${nullable}`;
|
|
105
|
+
case "boolean":
|
|
106
|
+
return `boolean${nullable}`;
|
|
107
|
+
case "array": {
|
|
108
|
+
// Recursively get the array item type
|
|
109
|
+
const itemType = getTypeFromSchema(schema.items);
|
|
110
|
+
return `Array<${itemType}>${nullable}`;
|
|
111
|
+
}
|
|
112
|
+
case "object":
|
|
113
|
+
// Handle objects with defined properties
|
|
114
|
+
if (schema.properties) {
|
|
115
|
+
const properties = Object.entries(schema.properties)
|
|
116
|
+
.map(([key, prop]) => {
|
|
117
|
+
const isRequired = schema.required?.includes(key);
|
|
118
|
+
const propertyType = getTypeFromSchema(prop);
|
|
119
|
+
const safeName = sanitizePropertyName(key);
|
|
120
|
+
return ` ${safeName}${isRequired ? "" : "?"}: ${propertyType};`;
|
|
121
|
+
})
|
|
122
|
+
.join("\n");
|
|
123
|
+
return `{${properties}\n}${nullable}`;
|
|
124
|
+
}
|
|
125
|
+
// Handle objects with additionalProperties
|
|
126
|
+
if (schema.additionalProperties) {
|
|
127
|
+
const valueType = typeof schema.additionalProperties === "boolean"
|
|
128
|
+
? "any"
|
|
129
|
+
: getTypeFromSchema(schema.additionalProperties);
|
|
130
|
+
return `Record<string, ${valueType}>${nullable}`;
|
|
131
|
+
}
|
|
132
|
+
// Default object type when no properties specified
|
|
133
|
+
return `Record<string, any>${nullable}`;
|
|
134
|
+
default:
|
|
135
|
+
return `any${nullable}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Fallback for schemas without a type
|
|
139
|
+
return "any";
|
|
140
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
camelCase,
|
|
4
|
+
getTypeFromSchema,
|
|
5
|
+
pascalCase,
|
|
6
|
+
sanitizePropertyName,
|
|
7
|
+
sanitizeTypeName,
|
|
8
|
+
specTitle,
|
|
9
|
+
} from "../utils";
|
|
4
10
|
|
|
5
11
|
export interface OperationInfo {
|
|
6
12
|
method: string;
|
|
@@ -93,11 +99,11 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
93
99
|
// Add path and query parameters
|
|
94
100
|
urlParams.forEach((p) => {
|
|
95
101
|
const safeName = sanitizePropertyName(p.name);
|
|
96
|
-
dataProps.push(`${safeName}: ${
|
|
102
|
+
dataProps.push(`${safeName}: ${getTypeFromSchema(p.schema)}`);
|
|
97
103
|
});
|
|
98
104
|
queryParams.forEach((p) => {
|
|
99
105
|
const safeName = sanitizePropertyName(p.name);
|
|
100
|
-
dataProps.push(`${safeName}${p.required ? "" : "?"}: ${
|
|
106
|
+
dataProps.push(`${safeName}${p.required ? "" : "?"}: ${getTypeFromSchema(p.schema)}`);
|
|
101
107
|
});
|
|
102
108
|
|
|
103
109
|
// Add request body type if it exists
|
|
@@ -167,38 +173,17 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
|
|
|
167
173
|
.join("\n ");
|
|
168
174
|
|
|
169
175
|
const requestParms = hasData
|
|
170
|
-
? `props:
|
|
176
|
+
? `props: T.${pascalCase(operationId)}Params & { axiosConfig?: AxiosRequestConfig; }`
|
|
171
177
|
: "props?: { axiosConfig?: AxiosRequestConfig }";
|
|
172
178
|
|
|
173
179
|
return `
|
|
174
|
-
${hasData ? `export type ${pascalCase(operationId)}Params = ${dataType};` : ""}
|
|
175
180
|
${jsDocLines.join("\n ")}
|
|
176
181
|
export async function ${camelCase(operationId)}(${requestParms}): Promise<${responseType}> {
|
|
177
182
|
${methodBody}
|
|
178
183
|
}`;
|
|
179
184
|
}
|
|
180
185
|
|
|
181
|
-
function
|
|
182
|
-
if ("schema" in param) {
|
|
183
|
-
const schema = param.schema as OpenAPIV3.SchemaObject;
|
|
184
|
-
switch (schema.type) {
|
|
185
|
-
case "string":
|
|
186
|
-
return "string";
|
|
187
|
-
case "integer":
|
|
188
|
-
case "number":
|
|
189
|
-
return "number";
|
|
190
|
-
case "boolean":
|
|
191
|
-
return "boolean";
|
|
192
|
-
case "array":
|
|
193
|
-
return "Array<any>"; // You might want to make this more specific
|
|
194
|
-
default:
|
|
195
|
-
return "any";
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return "any";
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function generateApiClient(spec: OpenAPIV3.Document, config: OpenAPIConfig): string {
|
|
186
|
+
export function generateApiClient(spec: OpenAPIV3.Document): string {
|
|
202
187
|
const operations: OperationInfo[] = [];
|
|
203
188
|
|
|
204
189
|
const resolveParameters = (
|
|
@@ -1,79 +1,17 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
-
import { sanitizePropertyName, sanitizeTypeName } from "../utils";
|
|
2
|
+
import { getTypeFromSchema, pascalCase, sanitizePropertyName, sanitizeTypeName } from "../utils";
|
|
3
3
|
|
|
4
4
|
interface SchemaContext {
|
|
5
5
|
schemas: { [key: string]: OpenAPIV3.SchemaObject };
|
|
6
6
|
generatedTypes: Set<string>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* Converts OpenAPI schema type to TypeScript type
|
|
11
|
-
*/
|
|
12
|
-
function getTypeFromSchema(
|
|
13
|
-
schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
|
|
14
|
-
context: SchemaContext
|
|
15
|
-
): string {
|
|
16
|
-
if (!schema) return "any";
|
|
17
|
-
|
|
18
|
-
if ("$ref" in schema) {
|
|
19
|
-
const refType = schema.$ref.split("/").pop();
|
|
20
|
-
return sanitizeTypeName(refType as string);
|
|
21
|
-
}
|
|
22
|
-
const nullable = schema.nullable ? " | null" : "";
|
|
23
|
-
|
|
24
|
-
// Handle enum types properly
|
|
25
|
-
if (schema.enum) {
|
|
26
|
-
return schema.enum.map((e) => (typeof e === "string" ? `'${e}'` : e)).join(" | ") + nullable;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
switch (schema.type) {
|
|
30
|
-
case "string":
|
|
31
|
-
if ("format" in schema && schema.format === "binary") {
|
|
32
|
-
return `string | { name?: string; type?: string; uri: string }${nullable}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return `string${nullable}`;
|
|
36
|
-
case "number":
|
|
37
|
-
case "integer":
|
|
38
|
-
return `number${nullable}`;
|
|
39
|
-
case "boolean":
|
|
40
|
-
return `boolean${nullable}`;
|
|
41
|
-
case "array": {
|
|
42
|
-
const itemType = getTypeFromSchema(schema.items, context);
|
|
43
|
-
return `Array<${itemType}>${nullable}`;
|
|
44
|
-
}
|
|
45
|
-
case "object":
|
|
46
|
-
if (schema.properties) {
|
|
47
|
-
const properties = Object.entries(schema.properties)
|
|
48
|
-
.map(([key, prop]) => {
|
|
49
|
-
const isRequired = schema.required?.includes(key);
|
|
50
|
-
const propertyType = getTypeFromSchema(prop, context);
|
|
51
|
-
const safeName = sanitizePropertyName(key);
|
|
52
|
-
return ` ${safeName}${isRequired ? "" : "?"}: ${propertyType};`;
|
|
53
|
-
})
|
|
54
|
-
.join("\n");
|
|
55
|
-
return `{${properties}\n}${nullable}`;
|
|
56
|
-
}
|
|
57
|
-
if (schema.additionalProperties) {
|
|
58
|
-
const valueType =
|
|
59
|
-
typeof schema.additionalProperties === "boolean"
|
|
60
|
-
? "any"
|
|
61
|
-
: getTypeFromSchema(schema.additionalProperties, context);
|
|
62
|
-
return `Record<string, ${valueType}>${nullable}`;
|
|
63
|
-
}
|
|
64
|
-
return `Record<string, any>${nullable}`;
|
|
65
|
-
default:
|
|
66
|
-
return `any${nullable}`;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
9
|
function generateTypeDefinition(
|
|
71
10
|
name: string,
|
|
72
|
-
schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject
|
|
73
|
-
context: SchemaContext
|
|
11
|
+
schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject
|
|
74
12
|
): string {
|
|
75
13
|
const description = !("$ref" in schema) && schema.description ? `/**\n * ${schema.description}\n */\n` : "";
|
|
76
|
-
const typeValue = getTypeFromSchema(schema
|
|
14
|
+
const typeValue = getTypeFromSchema(schema);
|
|
77
15
|
|
|
78
16
|
// Use 'type' for primitives, unions, and simple types
|
|
79
17
|
// Use 'interface' only for complex objects with properties
|
|
@@ -98,7 +36,7 @@ export function generateTypeDefinitions(spec: OpenAPIV3.Document): string {
|
|
|
98
36
|
// Generate types for all schema definitions
|
|
99
37
|
for (const [name, schema] of Object.entries(context.schemas)) {
|
|
100
38
|
if (context.generatedTypes.has(name)) continue;
|
|
101
|
-
output += generateTypeDefinition(name, schema
|
|
39
|
+
output += generateTypeDefinition(name, schema);
|
|
102
40
|
context.generatedTypes.add(name);
|
|
103
41
|
}
|
|
104
42
|
|
|
@@ -110,11 +48,12 @@ export function generateTypeDefinitions(spec: OpenAPIV3.Document): string {
|
|
|
110
48
|
|
|
111
49
|
const operationObject = operation as OpenAPIV3.OperationObject;
|
|
112
50
|
if (!operationObject) continue;
|
|
113
|
-
const
|
|
51
|
+
const { operationId: badOperationId, requestBody, responses, parameters } = operationObject;
|
|
52
|
+
const operationId = `${sanitizeTypeName(badOperationId || `${path.replace(/\W+/g, "_")}`)}`;
|
|
114
53
|
|
|
115
54
|
// Generate request body type
|
|
116
|
-
if (
|
|
117
|
-
const content = (
|
|
55
|
+
if (requestBody) {
|
|
56
|
+
const content = (requestBody as OpenAPIV3.RequestBodyObject).content;
|
|
118
57
|
const jsonContent =
|
|
119
58
|
content["application/ld+json"] ??
|
|
120
59
|
content["application/json"] ??
|
|
@@ -122,13 +61,13 @@ export function generateTypeDefinitions(spec: OpenAPIV3.Document): string {
|
|
|
122
61
|
content["application/octet-stream"];
|
|
123
62
|
if (jsonContent?.schema) {
|
|
124
63
|
const typeName = `${operationId}Request`;
|
|
125
|
-
output += generateTypeDefinition(typeName, jsonContent.schema as OpenAPIV3.SchemaObject
|
|
64
|
+
output += generateTypeDefinition(typeName, jsonContent.schema as OpenAPIV3.SchemaObject);
|
|
126
65
|
}
|
|
127
66
|
}
|
|
128
67
|
|
|
129
68
|
// Generate response types
|
|
130
|
-
if (
|
|
131
|
-
for (const [code, response] of Object.entries(
|
|
69
|
+
if (responses) {
|
|
70
|
+
for (const [code, response] of Object.entries(responses)) {
|
|
132
71
|
const responseObj = response as OpenAPIV3.ResponseObject;
|
|
133
72
|
const content =
|
|
134
73
|
responseObj.content?.["application/ld+json"] ??
|
|
@@ -136,10 +75,46 @@ export function generateTypeDefinitions(spec: OpenAPIV3.Document): string {
|
|
|
136
75
|
responseObj.content?.["application/octet-stream"];
|
|
137
76
|
if (content?.schema) {
|
|
138
77
|
const typeName = `${operationId}Response${code}`;
|
|
139
|
-
output += generateTypeDefinition(typeName, content.schema as OpenAPIV3.SchemaObject
|
|
78
|
+
output += generateTypeDefinition(typeName, content.schema as OpenAPIV3.SchemaObject);
|
|
140
79
|
}
|
|
141
80
|
}
|
|
142
81
|
}
|
|
82
|
+
|
|
83
|
+
// Build data type parts
|
|
84
|
+
const dataProps: string[] = [];
|
|
85
|
+
|
|
86
|
+
const urlParams = (parameters?.filter((p) => "in" in p && p.in === "path") ||
|
|
87
|
+
[]) as OpenAPIV3.ParameterObject[];
|
|
88
|
+
const queryParams = (parameters?.filter((p) => "in" in p && p.in === "query") ||
|
|
89
|
+
[]) as OpenAPIV3.ParameterObject[];
|
|
90
|
+
|
|
91
|
+
// Add path and query parameters
|
|
92
|
+
urlParams.forEach((p) => {
|
|
93
|
+
const safeName = sanitizePropertyName(p.name);
|
|
94
|
+
dataProps.push(`${safeName}: ${getTypeFromSchema(p.schema)}`);
|
|
95
|
+
});
|
|
96
|
+
queryParams.forEach((p) => {
|
|
97
|
+
const safeName = sanitizePropertyName(p.name);
|
|
98
|
+
dataProps.push(`${safeName}${p.required ? "" : "?"}: ${getTypeFromSchema(p.schema)}`);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Add request body type if it exists
|
|
102
|
+
const hasData = (parameters && parameters.length > 0) || requestBody;
|
|
103
|
+
|
|
104
|
+
let dataType = "undefined";
|
|
105
|
+
const namedType = pascalCase(operationId);
|
|
106
|
+
if (hasData) {
|
|
107
|
+
if (requestBody && dataProps.length > 0) {
|
|
108
|
+
dataType = `${namedType}Request & { ${dataProps.join("; ")} }`;
|
|
109
|
+
} else if (requestBody) {
|
|
110
|
+
dataType = `${namedType}Request`;
|
|
111
|
+
} else if (dataProps.length > 0) {
|
|
112
|
+
dataType = `{ ${dataProps.join("; ")} }`;
|
|
113
|
+
} else {
|
|
114
|
+
dataType = "Record<string, never>";
|
|
115
|
+
}
|
|
116
|
+
output += `\n\nexport type ${pascalCase(operationId)}Params = ${dataType};\n\n`;
|
|
117
|
+
}
|
|
143
118
|
}
|
|
144
119
|
}
|
|
145
120
|
}
|
package/src/index.ts
CHANGED
|
@@ -62,7 +62,7 @@ export async function codegenerate(config: OpenAPIConfig): Promise<void> {
|
|
|
62
62
|
await writeFile(resolve(config.exportDir, `${title}.schema.ts`), typeDefinitions, "utf-8");
|
|
63
63
|
|
|
64
64
|
// Generate and write API client
|
|
65
|
-
const clientCode = generateApiClient(spec
|
|
65
|
+
const clientCode = generateApiClient(spec);
|
|
66
66
|
await writeFile(resolve(config.exportDir, `${title}.client.ts`), clientCode, "utf-8");
|
|
67
67
|
|
|
68
68
|
// Generate and write React Query options
|
package/src/utils.ts
CHANGED
|
@@ -52,3 +52,103 @@ export function sanitizeTypeName(name: string): string {
|
|
|
52
52
|
export function specTitle(spec: OpenAPIV3.Document): string {
|
|
53
53
|
return camelCase(spec.info.title.toLowerCase().replace(/\s+/g, "-"));
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Converts an OpenAPI schema object into a TypeScript type string.
|
|
58
|
+
*
|
|
59
|
+
* Handles:
|
|
60
|
+
* - References ($ref) by extracting the type name
|
|
61
|
+
* - Nullable types by appending "| null"
|
|
62
|
+
* - Enums by creating union types of the values
|
|
63
|
+
* - Basic types (string, number, boolean)
|
|
64
|
+
* - Binary format strings as a union with file metadata object
|
|
65
|
+
* - Arrays by recursively getting the item type
|
|
66
|
+
* - Objects with properties by creating interfaces
|
|
67
|
+
* - Objects with additionalProperties as Records
|
|
68
|
+
* - Fallback to "any" for unknown types
|
|
69
|
+
*
|
|
70
|
+
* @param param - The OpenAPI schema/parameter object to convert
|
|
71
|
+
* @returns The TypeScript type as a string
|
|
72
|
+
*/
|
|
73
|
+
export function getTypeFromSchema(
|
|
74
|
+
schema: OpenAPIV3.ParameterObject | OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined
|
|
75
|
+
): string | undefined {
|
|
76
|
+
if (!schema) return undefined;
|
|
77
|
+
// Handle $ref by extracting the referenced type name
|
|
78
|
+
if ("$ref" in schema) {
|
|
79
|
+
const refType = schema.$ref.split("/").pop();
|
|
80
|
+
return sanitizeTypeName(refType as string);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Add "| null" for nullable types
|
|
84
|
+
const nullable = "nullable" in schema && schema.nullable ? " | null" : "";
|
|
85
|
+
|
|
86
|
+
// Handle enums as union types
|
|
87
|
+
if ("enum" in schema && schema.enum) {
|
|
88
|
+
if (Object.values(schema.enum)?.length > 0) {
|
|
89
|
+
return (
|
|
90
|
+
Object.values(schema.enum)
|
|
91
|
+
.map((e) => (typeof e === "string" ? `'${e}'` : e))
|
|
92
|
+
.join(" | ") + nullable
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return schema.enum.map((e) => (typeof e === "string" ? `'${e}'` : e)).join(" | ") + nullable;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Handle types based on the "type" property
|
|
99
|
+
if ("type" in schema) {
|
|
100
|
+
switch (schema.type) {
|
|
101
|
+
case "string":
|
|
102
|
+
// Special case for binary format strings
|
|
103
|
+
if ("format" in schema && schema.format === "binary") {
|
|
104
|
+
return `string | { name?: string; type?: string; uri: string }${nullable}`;
|
|
105
|
+
}
|
|
106
|
+
return `string${nullable}`;
|
|
107
|
+
|
|
108
|
+
case "number":
|
|
109
|
+
case "integer":
|
|
110
|
+
return `number${nullable}`;
|
|
111
|
+
|
|
112
|
+
case "boolean":
|
|
113
|
+
return `boolean${nullable}`;
|
|
114
|
+
|
|
115
|
+
case "array": {
|
|
116
|
+
// Recursively get the array item type
|
|
117
|
+
const itemType = getTypeFromSchema(schema.items);
|
|
118
|
+
return `Array<${itemType}>${nullable}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case "object":
|
|
122
|
+
// Handle objects with defined properties
|
|
123
|
+
if (schema.properties) {
|
|
124
|
+
const properties = Object.entries(schema.properties)
|
|
125
|
+
.map(([key, prop]) => {
|
|
126
|
+
const isRequired = schema.required?.includes(key);
|
|
127
|
+
const propertyType = getTypeFromSchema(prop);
|
|
128
|
+
const safeName = sanitizePropertyName(key);
|
|
129
|
+
return ` ${safeName}${isRequired ? "" : "?"}: ${propertyType};`;
|
|
130
|
+
})
|
|
131
|
+
.join("\n");
|
|
132
|
+
return `{${properties}\n}${nullable}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Handle objects with additionalProperties
|
|
136
|
+
if (schema.additionalProperties) {
|
|
137
|
+
const valueType =
|
|
138
|
+
typeof schema.additionalProperties === "boolean"
|
|
139
|
+
? "any"
|
|
140
|
+
: getTypeFromSchema(schema.additionalProperties);
|
|
141
|
+
return `Record<string, ${valueType}>${nullable}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Default object type when no properties specified
|
|
145
|
+
return `Record<string, any>${nullable}`;
|
|
146
|
+
|
|
147
|
+
default:
|
|
148
|
+
return `any${nullable}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Fallback for schemas without a type
|
|
153
|
+
return "any";
|
|
154
|
+
}
|