@bagelink/sdk 0.0.216 → 0.0.221
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/index.cjs +281 -3
- package/dist/index.d.cts +8 -1
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.mjs +280 -3
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/openAPITools/functionGenerator.ts +245 -0
- package/src/openAPITools/index.ts +25 -0
- package/src/openAPITools/openApiTypes.ts +70 -0
- package/src/openAPITools/typeGenerator.ts +16 -0
- package/src/openAPITools/utils.ts +56 -0
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,287 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const axios$1 = require('axios');
|
|
4
4
|
|
|
5
5
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const axios__default = /*#__PURE__*/_interopDefaultCompat(axios$1);
|
|
8
|
+
|
|
9
|
+
const resolveReference = (ref) => {
|
|
10
|
+
const t = ref.split("/").pop() || "any";
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
const schemaToType = (schema) => {
|
|
14
|
+
if (!schema)
|
|
15
|
+
return "any";
|
|
16
|
+
if (schema.anyOf)
|
|
17
|
+
return schema.anyOf.map((s) => schemaToType(s)).filter((p) => p !== "any").join(" | ");
|
|
18
|
+
if (schema.allOf)
|
|
19
|
+
return schema.allOf.map((s) => schemaToType(s)).filter((p) => p !== "any").join(" & ");
|
|
20
|
+
if (schema.$ref)
|
|
21
|
+
return resolveReference(schema.$ref);
|
|
22
|
+
switch (schema.type) {
|
|
23
|
+
case "object":
|
|
24
|
+
return "Record<string, any>";
|
|
25
|
+
case "string":
|
|
26
|
+
return "string";
|
|
27
|
+
case "integer":
|
|
28
|
+
return "number";
|
|
29
|
+
case "number":
|
|
30
|
+
return "number";
|
|
31
|
+
case "boolean":
|
|
32
|
+
return "boolean";
|
|
33
|
+
case "array":
|
|
34
|
+
return `${schemaToType(schema.items)}[]`;
|
|
35
|
+
case "null":
|
|
36
|
+
return "null";
|
|
37
|
+
default:
|
|
38
|
+
return "any";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const toCamelCase = (str) => str?.replace(/[-_\s]+(.)?/g, (_, c) => c?.toUpperCase() || "").replace(/^./, (str2) => str2.toLowerCase()) || str;
|
|
42
|
+
const toPascalCase = (str) => str?.replace(/[-_\s]+(.)?/g, (_, c) => c?.toUpperCase() || "").replace(/^./, (str2) => str2.toUpperCase()) || str;
|
|
43
|
+
const isOptional = (schema) => {
|
|
44
|
+
const type = schemaToType(schema);
|
|
45
|
+
return type?.split(/\s+\|\s+/).includes("null");
|
|
46
|
+
};
|
|
47
|
+
const cleanNulls = (str) => str.split(" | ").filter((t) => t !== "null").join(" | ");
|
|
48
|
+
function formatVarType(varName, schema, required = false, defaultValue = null) {
|
|
49
|
+
let type = schemaToType(schema);
|
|
50
|
+
const optionalStr = required || isOptional(schema) ? "?" : "";
|
|
51
|
+
type = cleanNulls(type);
|
|
52
|
+
let defaultStr = defaultValue ? ` = ${defaultValue}` : "";
|
|
53
|
+
if (defaultStr && type === "string")
|
|
54
|
+
defaultStr = ` = '${defaultValue}'`;
|
|
55
|
+
else if (defaultValue && (typeof defaultValue === "object" || defaultValue === "{}"))
|
|
56
|
+
defaultStr = " = {}";
|
|
57
|
+
return `${varName}${optionalStr}: ${type}${defaultStr}`;
|
|
58
|
+
}
|
|
59
|
+
function cleanPath(path) {
|
|
60
|
+
return path.split("/").filter((p) => p && !p.match(/\{|\}/)).join("/");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const generateTypes = (schemas) => Object.entries(schemas).map(([typeName, schema]) => {
|
|
64
|
+
if (schema?.enum) {
|
|
65
|
+
return `export type ${typeName} = ${schema.enum.map((item) => `'${item}'`).join(" | ")};
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
if (!schema?.properties)
|
|
69
|
+
return "";
|
|
70
|
+
const properties = Object.entries(schema.properties).map(([key, value]) => {
|
|
71
|
+
const varType = formatVarType(key, value);
|
|
72
|
+
return ` ${varType}`;
|
|
73
|
+
}).join(";\n ");
|
|
74
|
+
return `export type ${typeName} = {
|
|
75
|
+
${properties};
|
|
76
|
+
};
|
|
77
|
+
`;
|
|
78
|
+
}).join("\n");
|
|
79
|
+
|
|
80
|
+
const allTypes = [];
|
|
81
|
+
function collectTypeForImportStatement(typeName) {
|
|
82
|
+
typeName = typeName.trim().replace("[]", "");
|
|
83
|
+
if (typeName.includes("|")) {
|
|
84
|
+
typeName.split("|").forEach(
|
|
85
|
+
(singleType) => collectTypeForImportStatement(singleType)
|
|
86
|
+
);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const primitiveTypes = ["string", "number", "boolean", "null", "void", "any", "Record<string, any>"];
|
|
90
|
+
const isPrimitive = primitiveTypes.includes(typeName);
|
|
91
|
+
if (!typeName || isPrimitive)
|
|
92
|
+
return;
|
|
93
|
+
if (!allTypes.includes(typeName))
|
|
94
|
+
allTypes.push(typeName);
|
|
95
|
+
}
|
|
96
|
+
function getResponseType(response) {
|
|
97
|
+
const mediaTypeObject = response.content?.["application/json"];
|
|
98
|
+
if (!mediaTypeObject || !mediaTypeObject.schema)
|
|
99
|
+
return null;
|
|
100
|
+
const responseType = schemaToType(mediaTypeObject.schema);
|
|
101
|
+
collectTypeForImportStatement(responseType);
|
|
102
|
+
return responseType;
|
|
103
|
+
}
|
|
104
|
+
function generateResponseType(responses) {
|
|
105
|
+
if (!responses)
|
|
106
|
+
return "";
|
|
107
|
+
const types = [];
|
|
108
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
109
|
+
if (statusCode.startsWith("2")) {
|
|
110
|
+
const responseType = getResponseType(response);
|
|
111
|
+
if (responseType && responseType !== "any") {
|
|
112
|
+
types.push(responseType);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return types?.join(" | ");
|
|
117
|
+
}
|
|
118
|
+
function generateAxiosFunction(method, formattedPath, allParams, responseTypeStr, parameters, requestBodyPayload) {
|
|
119
|
+
let axiosFunction = `async (${allParams})${responseTypeStr} => axios.${method}(`;
|
|
120
|
+
const paramStr = parameters?.config?.params ? `, { params: {${parameters.config.params}} }` : "";
|
|
121
|
+
if (["get", "delete"].includes(method)) {
|
|
122
|
+
axiosFunction += `${formattedPath}${paramStr}`;
|
|
123
|
+
} else if (["post", "put", "patch"].includes(method)) {
|
|
124
|
+
const bodyVar = requestBodyPayload ? `, ${requestBodyPayload}` : "";
|
|
125
|
+
axiosFunction += `${formattedPath}${bodyVar}${paramStr}`;
|
|
126
|
+
}
|
|
127
|
+
axiosFunction += ")";
|
|
128
|
+
return axiosFunction;
|
|
129
|
+
}
|
|
130
|
+
const pathParamRegex = /\{([^}]+)\}/g;
|
|
131
|
+
const getParamsFromPath = (path) => {
|
|
132
|
+
const params = path.match(pathParamRegex)?.map((p) => p.slice(1, -1));
|
|
133
|
+
return params;
|
|
134
|
+
};
|
|
135
|
+
function formatPathWithParams(path) {
|
|
136
|
+
const params = getParamsFromPath(path);
|
|
137
|
+
const formattedPath = params ? `\`${path.replace(pathParamRegex, (v) => `$${toCamelCase(v)}`)}\`` : `'${path}'`;
|
|
138
|
+
return formattedPath;
|
|
139
|
+
}
|
|
140
|
+
function generateRequestBody(requestBody) {
|
|
141
|
+
const bodySchema = requestBody?.content?.["application/json"]?.schema;
|
|
142
|
+
if (!bodySchema)
|
|
143
|
+
return { requestBodyParam: "", requestBodyPayload: "" };
|
|
144
|
+
const requestBodyType = schemaToType(bodySchema);
|
|
145
|
+
collectTypeForImportStatement(requestBodyType);
|
|
146
|
+
const requestBodyPayload = toCamelCase(bodySchema?.title) || toCamelCase(requestBodyType) || "requestBody";
|
|
147
|
+
const defaultValue = requestBody?.content?.["application/json"]?.schema?.default;
|
|
148
|
+
const requestBodyParam = formatVarType(requestBodyPayload, bodySchema, defaultValue);
|
|
149
|
+
return { requestBodyParam, requestBodyPayload };
|
|
150
|
+
}
|
|
151
|
+
function combineAllParams(parameters, requestBodyParam) {
|
|
152
|
+
let allParamsArray = [];
|
|
153
|
+
if (parameters && parameters.params)
|
|
154
|
+
allParamsArray = parameters.params.split(",").map((p) => p.trim());
|
|
155
|
+
if (requestBodyParam)
|
|
156
|
+
allParamsArray.push(requestBodyParam.trim());
|
|
157
|
+
allParamsArray = allParamsArray.filter((p) => p).sort((a, b) => (a.includes("?") ? 1 : -1) - (b.includes("?") ? 1 : -1));
|
|
158
|
+
return allParamsArray.join(", ");
|
|
159
|
+
}
|
|
160
|
+
function generateFunctionParameters(params) {
|
|
161
|
+
if (!params || params.length === 0)
|
|
162
|
+
return {};
|
|
163
|
+
const functionParams = [];
|
|
164
|
+
const paramList = [];
|
|
165
|
+
for (const param of params) {
|
|
166
|
+
const paramType = schemaToType(param.schema);
|
|
167
|
+
collectTypeForImportStatement(paramType);
|
|
168
|
+
const paramName = param.name;
|
|
169
|
+
const varName = toCamelCase(param.name) || "param";
|
|
170
|
+
if (param.in === "path" || param.in === "query" || param.in === "header") {
|
|
171
|
+
const defaultValue = param?.schema?.default;
|
|
172
|
+
const varType = formatVarType(varName, param.schema, param.required, defaultValue);
|
|
173
|
+
functionParams.push(varType);
|
|
174
|
+
}
|
|
175
|
+
if (param.in === "query" || param.in === "header") {
|
|
176
|
+
if (paramName === varName)
|
|
177
|
+
paramList.push(paramName);
|
|
178
|
+
else
|
|
179
|
+
paramList.push(`'${paramName}': ${varName}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const paramsString = functionParams.join(", ");
|
|
183
|
+
const config = {};
|
|
184
|
+
if (paramList.length > 0)
|
|
185
|
+
config.params = paramList.join(", ");
|
|
186
|
+
return { params: paramsString, config };
|
|
187
|
+
}
|
|
188
|
+
function generateFunctionForOperation(method, path, operation) {
|
|
189
|
+
if (!operation)
|
|
190
|
+
return "";
|
|
191
|
+
const parameters = generateFunctionParameters(operation.parameters);
|
|
192
|
+
const responseType = generateResponseType(operation.responses);
|
|
193
|
+
const formattedPath = formatPathWithParams(path);
|
|
194
|
+
const { requestBodyParam, requestBodyPayload } = generateRequestBody(operation.requestBody);
|
|
195
|
+
const allParams = combineAllParams(parameters, requestBodyParam);
|
|
196
|
+
const responseTypeStr = responseType ? `: Promise<AxiosResponse<${responseType}>>` : "";
|
|
197
|
+
return generateAxiosFunction(method, formattedPath, allParams, responseTypeStr, parameters, requestBodyPayload);
|
|
198
|
+
}
|
|
199
|
+
const generateRandomString = () => Math.random().toString(36).substring(7);
|
|
200
|
+
function fileTemplate(tsString, typeForImport, baseURL) {
|
|
201
|
+
const templateCode = `import axios, { type AxiosResponse } from 'axios';
|
|
202
|
+
import type {${typeForImport.join(", ")}} from './types.d';
|
|
203
|
+
|
|
204
|
+
axios.defaults.baseURL = ${baseURL};
|
|
205
|
+
|
|
206
|
+
${tsString}`;
|
|
207
|
+
const doubleQuoteRegex = /"([^"]+)":/g;
|
|
208
|
+
return templateCode.replace(doubleQuoteRegex, "$1:");
|
|
209
|
+
}
|
|
210
|
+
const functionsInventory = {};
|
|
211
|
+
const pathOperations = [];
|
|
212
|
+
const hasConflict = (path, method) => {
|
|
213
|
+
const cleanPathName = path.split("/").filter((p) => p && !p.match(/\{|\}/)).join("/");
|
|
214
|
+
const matchingPaths = pathOperations.filter((p) => p.path === cleanPathName && p.method === method);
|
|
215
|
+
pathOperations.push({ path: cleanPathName, method });
|
|
216
|
+
return matchingPaths.length > 0;
|
|
217
|
+
};
|
|
218
|
+
const createFunctionPlaceholder = (path, method, operation) => {
|
|
219
|
+
const funcID = generateRandomString();
|
|
220
|
+
functionsInventory[funcID] = generateFunctionForOperation(method, path, operation);
|
|
221
|
+
return funcID;
|
|
222
|
+
};
|
|
223
|
+
function handlePathSegment(path, operation, existingObj = null) {
|
|
224
|
+
const methods = Object.keys(operation);
|
|
225
|
+
const obj = {};
|
|
226
|
+
for (const method of methods) {
|
|
227
|
+
let functionName = method.toLowerCase();
|
|
228
|
+
if (hasConflict(path, method)) {
|
|
229
|
+
const params = getParamsFromPath(path);
|
|
230
|
+
functionName += params ? `By${toPascalCase(params?.pop() || "")}` : "All";
|
|
231
|
+
}
|
|
232
|
+
obj[functionName] = createFunctionPlaceholder(path, method, operation[method]);
|
|
233
|
+
}
|
|
234
|
+
return { ...obj, ...existingObj };
|
|
235
|
+
}
|
|
236
|
+
function generateFunctions(paths, baseURL) {
|
|
237
|
+
let tsString = "";
|
|
238
|
+
const body = {};
|
|
239
|
+
const allPathsClean = Object.keys(paths).map(cleanPath);
|
|
240
|
+
for (const [path, operation] of Object.entries(paths)) {
|
|
241
|
+
const splitPath = path.split("/").filter((p) => p && !p.match(/\{|\}/));
|
|
242
|
+
splitPath.reduce((acc, key, index, array) => {
|
|
243
|
+
const objFuncKey = toCamelCase(key);
|
|
244
|
+
if (!objFuncKey)
|
|
245
|
+
return acc;
|
|
246
|
+
const methods = Object.keys(operation);
|
|
247
|
+
if (index === array.length - 1 && methods.length === 1 && allPathsClean.filter((p) => p === cleanPath(path)).length === 1) {
|
|
248
|
+
const method = methods[0];
|
|
249
|
+
const opp = { ...operation }[method];
|
|
250
|
+
acc[objFuncKey] = createFunctionPlaceholder(path, methods[0], opp);
|
|
251
|
+
} else if (index === array.length - 1)
|
|
252
|
+
acc[objFuncKey] = handlePathSegment(path, operation, acc[objFuncKey]);
|
|
253
|
+
else if (!acc[objFuncKey] || typeof acc[objFuncKey] !== "object")
|
|
254
|
+
acc[objFuncKey] = {};
|
|
255
|
+
return acc[objFuncKey];
|
|
256
|
+
}, body);
|
|
257
|
+
}
|
|
258
|
+
for (const [parent, object] of Object.entries(body)) {
|
|
259
|
+
tsString += `export const ${parent} = ${JSON.stringify(object, null, 2)};
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
Object.entries(functionsInventory).forEach(([key, value]) => {
|
|
263
|
+
tsString = tsString.replace(`"${key}"`, value);
|
|
264
|
+
});
|
|
265
|
+
tsString = fileTemplate(tsString, allTypes, baseURL);
|
|
266
|
+
return tsString;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const index = async (openApiUrl, baseURL) => {
|
|
270
|
+
try {
|
|
271
|
+
const { data: openApi } = await axios__default.get(openApiUrl);
|
|
272
|
+
const schemas = openApi.components?.schemas;
|
|
273
|
+
if (!schemas)
|
|
274
|
+
throw new Error("No schemas found in OpenAPI document");
|
|
275
|
+
const types = generateTypes(schemas);
|
|
276
|
+
const { paths } = openApi;
|
|
277
|
+
if (!paths)
|
|
278
|
+
throw new Error("No paths found in OpenAPI document");
|
|
279
|
+
const code = generateFunctions(paths, baseURL);
|
|
280
|
+
return { types, code };
|
|
281
|
+
} catch (error) {
|
|
282
|
+
throw new Error(error);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
8
285
|
|
|
9
286
|
var __defProp = Object.defineProperty;
|
|
10
287
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -12,7 +289,7 @@ var __publicField = (obj, key, value) => {
|
|
|
12
289
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
13
290
|
return value;
|
|
14
291
|
};
|
|
15
|
-
const axios =
|
|
292
|
+
const axios = axios__default.create({
|
|
16
293
|
// withCredentials to true to send cookies with requests
|
|
17
294
|
withCredentials: true
|
|
18
295
|
});
|
|
@@ -234,3 +511,4 @@ class Bagel {
|
|
|
234
511
|
}
|
|
235
512
|
|
|
236
513
|
exports.Bagel = Bagel;
|
|
514
|
+
exports.openAPI = index;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
type OpenAPIResponse = {
|
|
2
|
+
types: string;
|
|
3
|
+
code: string;
|
|
4
|
+
};
|
|
5
|
+
declare const _default: (openApiUrl: string, baseURL: string) => Promise<OpenAPIResponse>;
|
|
6
|
+
|
|
1
7
|
type Tables = '';
|
|
2
8
|
type TableToTypeMapping = Record<Tables, any>;
|
|
9
|
+
|
|
3
10
|
interface User {
|
|
4
11
|
id: string;
|
|
5
12
|
first_name?: string;
|
|
@@ -58,4 +65,4 @@ declare class Bagel {
|
|
|
58
65
|
uploadFile(file: File, options?: UploadOptions): Promise<any>;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
export { Bagel, type TableToTypeMapping, type Tables, type User };
|
|
68
|
+
export { Bagel, type TableToTypeMapping, type Tables, type User, _default as openAPI };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
type OpenAPIResponse = {
|
|
2
|
+
types: string;
|
|
3
|
+
code: string;
|
|
4
|
+
};
|
|
5
|
+
declare const _default: (openApiUrl: string, baseURL: string) => Promise<OpenAPIResponse>;
|
|
6
|
+
|
|
1
7
|
type Tables = '';
|
|
2
8
|
type TableToTypeMapping = Record<Tables, any>;
|
|
9
|
+
|
|
3
10
|
interface User {
|
|
4
11
|
id: string;
|
|
5
12
|
first_name?: string;
|
|
@@ -58,4 +65,4 @@ declare class Bagel {
|
|
|
58
65
|
uploadFile(file: File, options?: UploadOptions): Promise<any>;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
export { Bagel, type TableToTypeMapping, type Tables, type User };
|
|
68
|
+
export { Bagel, type TableToTypeMapping, type Tables, type User, _default as openAPI };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
type OpenAPIResponse = {
|
|
2
|
+
types: string;
|
|
3
|
+
code: string;
|
|
4
|
+
};
|
|
5
|
+
declare const _default: (openApiUrl: string, baseURL: string) => Promise<OpenAPIResponse>;
|
|
6
|
+
|
|
1
7
|
type Tables = '';
|
|
2
8
|
type TableToTypeMapping = Record<Tables, any>;
|
|
9
|
+
|
|
3
10
|
interface User {
|
|
4
11
|
id: string;
|
|
5
12
|
first_name?: string;
|
|
@@ -58,4 +65,4 @@ declare class Bagel {
|
|
|
58
65
|
uploadFile(file: File, options?: UploadOptions): Promise<any>;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
export { Bagel, type TableToTypeMapping, type Tables, type User };
|
|
68
|
+
export { Bagel, type TableToTypeMapping, type Tables, type User, _default as openAPI };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,281 @@
|
|
|
1
|
-
import
|
|
1
|
+
import axios$1 from 'axios';
|
|
2
|
+
|
|
3
|
+
const resolveReference = (ref) => {
|
|
4
|
+
const t = ref.split("/").pop() || "any";
|
|
5
|
+
return t;
|
|
6
|
+
};
|
|
7
|
+
const schemaToType = (schema) => {
|
|
8
|
+
if (!schema)
|
|
9
|
+
return "any";
|
|
10
|
+
if (schema.anyOf)
|
|
11
|
+
return schema.anyOf.map((s) => schemaToType(s)).filter((p) => p !== "any").join(" | ");
|
|
12
|
+
if (schema.allOf)
|
|
13
|
+
return schema.allOf.map((s) => schemaToType(s)).filter((p) => p !== "any").join(" & ");
|
|
14
|
+
if (schema.$ref)
|
|
15
|
+
return resolveReference(schema.$ref);
|
|
16
|
+
switch (schema.type) {
|
|
17
|
+
case "object":
|
|
18
|
+
return "Record<string, any>";
|
|
19
|
+
case "string":
|
|
20
|
+
return "string";
|
|
21
|
+
case "integer":
|
|
22
|
+
return "number";
|
|
23
|
+
case "number":
|
|
24
|
+
return "number";
|
|
25
|
+
case "boolean":
|
|
26
|
+
return "boolean";
|
|
27
|
+
case "array":
|
|
28
|
+
return `${schemaToType(schema.items)}[]`;
|
|
29
|
+
case "null":
|
|
30
|
+
return "null";
|
|
31
|
+
default:
|
|
32
|
+
return "any";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const toCamelCase = (str) => str?.replace(/[-_\s]+(.)?/g, (_, c) => c?.toUpperCase() || "").replace(/^./, (str2) => str2.toLowerCase()) || str;
|
|
36
|
+
const toPascalCase = (str) => str?.replace(/[-_\s]+(.)?/g, (_, c) => c?.toUpperCase() || "").replace(/^./, (str2) => str2.toUpperCase()) || str;
|
|
37
|
+
const isOptional = (schema) => {
|
|
38
|
+
const type = schemaToType(schema);
|
|
39
|
+
return type?.split(/\s+\|\s+/).includes("null");
|
|
40
|
+
};
|
|
41
|
+
const cleanNulls = (str) => str.split(" | ").filter((t) => t !== "null").join(" | ");
|
|
42
|
+
function formatVarType(varName, schema, required = false, defaultValue = null) {
|
|
43
|
+
let type = schemaToType(schema);
|
|
44
|
+
const optionalStr = required || isOptional(schema) ? "?" : "";
|
|
45
|
+
type = cleanNulls(type);
|
|
46
|
+
let defaultStr = defaultValue ? ` = ${defaultValue}` : "";
|
|
47
|
+
if (defaultStr && type === "string")
|
|
48
|
+
defaultStr = ` = '${defaultValue}'`;
|
|
49
|
+
else if (defaultValue && (typeof defaultValue === "object" || defaultValue === "{}"))
|
|
50
|
+
defaultStr = " = {}";
|
|
51
|
+
return `${varName}${optionalStr}: ${type}${defaultStr}`;
|
|
52
|
+
}
|
|
53
|
+
function cleanPath(path) {
|
|
54
|
+
return path.split("/").filter((p) => p && !p.match(/\{|\}/)).join("/");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const generateTypes = (schemas) => Object.entries(schemas).map(([typeName, schema]) => {
|
|
58
|
+
if (schema?.enum) {
|
|
59
|
+
return `export type ${typeName} = ${schema.enum.map((item) => `'${item}'`).join(" | ")};
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
if (!schema?.properties)
|
|
63
|
+
return "";
|
|
64
|
+
const properties = Object.entries(schema.properties).map(([key, value]) => {
|
|
65
|
+
const varType = formatVarType(key, value);
|
|
66
|
+
return ` ${varType}`;
|
|
67
|
+
}).join(";\n ");
|
|
68
|
+
return `export type ${typeName} = {
|
|
69
|
+
${properties};
|
|
70
|
+
};
|
|
71
|
+
`;
|
|
72
|
+
}).join("\n");
|
|
73
|
+
|
|
74
|
+
const allTypes = [];
|
|
75
|
+
function collectTypeForImportStatement(typeName) {
|
|
76
|
+
typeName = typeName.trim().replace("[]", "");
|
|
77
|
+
if (typeName.includes("|")) {
|
|
78
|
+
typeName.split("|").forEach(
|
|
79
|
+
(singleType) => collectTypeForImportStatement(singleType)
|
|
80
|
+
);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const primitiveTypes = ["string", "number", "boolean", "null", "void", "any", "Record<string, any>"];
|
|
84
|
+
const isPrimitive = primitiveTypes.includes(typeName);
|
|
85
|
+
if (!typeName || isPrimitive)
|
|
86
|
+
return;
|
|
87
|
+
if (!allTypes.includes(typeName))
|
|
88
|
+
allTypes.push(typeName);
|
|
89
|
+
}
|
|
90
|
+
function getResponseType(response) {
|
|
91
|
+
const mediaTypeObject = response.content?.["application/json"];
|
|
92
|
+
if (!mediaTypeObject || !mediaTypeObject.schema)
|
|
93
|
+
return null;
|
|
94
|
+
const responseType = schemaToType(mediaTypeObject.schema);
|
|
95
|
+
collectTypeForImportStatement(responseType);
|
|
96
|
+
return responseType;
|
|
97
|
+
}
|
|
98
|
+
function generateResponseType(responses) {
|
|
99
|
+
if (!responses)
|
|
100
|
+
return "";
|
|
101
|
+
const types = [];
|
|
102
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
103
|
+
if (statusCode.startsWith("2")) {
|
|
104
|
+
const responseType = getResponseType(response);
|
|
105
|
+
if (responseType && responseType !== "any") {
|
|
106
|
+
types.push(responseType);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return types?.join(" | ");
|
|
111
|
+
}
|
|
112
|
+
function generateAxiosFunction(method, formattedPath, allParams, responseTypeStr, parameters, requestBodyPayload) {
|
|
113
|
+
let axiosFunction = `async (${allParams})${responseTypeStr} => axios.${method}(`;
|
|
114
|
+
const paramStr = parameters?.config?.params ? `, { params: {${parameters.config.params}} }` : "";
|
|
115
|
+
if (["get", "delete"].includes(method)) {
|
|
116
|
+
axiosFunction += `${formattedPath}${paramStr}`;
|
|
117
|
+
} else if (["post", "put", "patch"].includes(method)) {
|
|
118
|
+
const bodyVar = requestBodyPayload ? `, ${requestBodyPayload}` : "";
|
|
119
|
+
axiosFunction += `${formattedPath}${bodyVar}${paramStr}`;
|
|
120
|
+
}
|
|
121
|
+
axiosFunction += ")";
|
|
122
|
+
return axiosFunction;
|
|
123
|
+
}
|
|
124
|
+
const pathParamRegex = /\{([^}]+)\}/g;
|
|
125
|
+
const getParamsFromPath = (path) => {
|
|
126
|
+
const params = path.match(pathParamRegex)?.map((p) => p.slice(1, -1));
|
|
127
|
+
return params;
|
|
128
|
+
};
|
|
129
|
+
function formatPathWithParams(path) {
|
|
130
|
+
const params = getParamsFromPath(path);
|
|
131
|
+
const formattedPath = params ? `\`${path.replace(pathParamRegex, (v) => `$${toCamelCase(v)}`)}\`` : `'${path}'`;
|
|
132
|
+
return formattedPath;
|
|
133
|
+
}
|
|
134
|
+
function generateRequestBody(requestBody) {
|
|
135
|
+
const bodySchema = requestBody?.content?.["application/json"]?.schema;
|
|
136
|
+
if (!bodySchema)
|
|
137
|
+
return { requestBodyParam: "", requestBodyPayload: "" };
|
|
138
|
+
const requestBodyType = schemaToType(bodySchema);
|
|
139
|
+
collectTypeForImportStatement(requestBodyType);
|
|
140
|
+
const requestBodyPayload = toCamelCase(bodySchema?.title) || toCamelCase(requestBodyType) || "requestBody";
|
|
141
|
+
const defaultValue = requestBody?.content?.["application/json"]?.schema?.default;
|
|
142
|
+
const requestBodyParam = formatVarType(requestBodyPayload, bodySchema, defaultValue);
|
|
143
|
+
return { requestBodyParam, requestBodyPayload };
|
|
144
|
+
}
|
|
145
|
+
function combineAllParams(parameters, requestBodyParam) {
|
|
146
|
+
let allParamsArray = [];
|
|
147
|
+
if (parameters && parameters.params)
|
|
148
|
+
allParamsArray = parameters.params.split(",").map((p) => p.trim());
|
|
149
|
+
if (requestBodyParam)
|
|
150
|
+
allParamsArray.push(requestBodyParam.trim());
|
|
151
|
+
allParamsArray = allParamsArray.filter((p) => p).sort((a, b) => (a.includes("?") ? 1 : -1) - (b.includes("?") ? 1 : -1));
|
|
152
|
+
return allParamsArray.join(", ");
|
|
153
|
+
}
|
|
154
|
+
function generateFunctionParameters(params) {
|
|
155
|
+
if (!params || params.length === 0)
|
|
156
|
+
return {};
|
|
157
|
+
const functionParams = [];
|
|
158
|
+
const paramList = [];
|
|
159
|
+
for (const param of params) {
|
|
160
|
+
const paramType = schemaToType(param.schema);
|
|
161
|
+
collectTypeForImportStatement(paramType);
|
|
162
|
+
const paramName = param.name;
|
|
163
|
+
const varName = toCamelCase(param.name) || "param";
|
|
164
|
+
if (param.in === "path" || param.in === "query" || param.in === "header") {
|
|
165
|
+
const defaultValue = param?.schema?.default;
|
|
166
|
+
const varType = formatVarType(varName, param.schema, param.required, defaultValue);
|
|
167
|
+
functionParams.push(varType);
|
|
168
|
+
}
|
|
169
|
+
if (param.in === "query" || param.in === "header") {
|
|
170
|
+
if (paramName === varName)
|
|
171
|
+
paramList.push(paramName);
|
|
172
|
+
else
|
|
173
|
+
paramList.push(`'${paramName}': ${varName}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const paramsString = functionParams.join(", ");
|
|
177
|
+
const config = {};
|
|
178
|
+
if (paramList.length > 0)
|
|
179
|
+
config.params = paramList.join(", ");
|
|
180
|
+
return { params: paramsString, config };
|
|
181
|
+
}
|
|
182
|
+
function generateFunctionForOperation(method, path, operation) {
|
|
183
|
+
if (!operation)
|
|
184
|
+
return "";
|
|
185
|
+
const parameters = generateFunctionParameters(operation.parameters);
|
|
186
|
+
const responseType = generateResponseType(operation.responses);
|
|
187
|
+
const formattedPath = formatPathWithParams(path);
|
|
188
|
+
const { requestBodyParam, requestBodyPayload } = generateRequestBody(operation.requestBody);
|
|
189
|
+
const allParams = combineAllParams(parameters, requestBodyParam);
|
|
190
|
+
const responseTypeStr = responseType ? `: Promise<AxiosResponse<${responseType}>>` : "";
|
|
191
|
+
return generateAxiosFunction(method, formattedPath, allParams, responseTypeStr, parameters, requestBodyPayload);
|
|
192
|
+
}
|
|
193
|
+
const generateRandomString = () => Math.random().toString(36).substring(7);
|
|
194
|
+
function fileTemplate(tsString, typeForImport, baseURL) {
|
|
195
|
+
const templateCode = `import axios, { type AxiosResponse } from 'axios';
|
|
196
|
+
import type {${typeForImport.join(", ")}} from './types.d';
|
|
197
|
+
|
|
198
|
+
axios.defaults.baseURL = ${baseURL};
|
|
199
|
+
|
|
200
|
+
${tsString}`;
|
|
201
|
+
const doubleQuoteRegex = /"([^"]+)":/g;
|
|
202
|
+
return templateCode.replace(doubleQuoteRegex, "$1:");
|
|
203
|
+
}
|
|
204
|
+
const functionsInventory = {};
|
|
205
|
+
const pathOperations = [];
|
|
206
|
+
const hasConflict = (path, method) => {
|
|
207
|
+
const cleanPathName = path.split("/").filter((p) => p && !p.match(/\{|\}/)).join("/");
|
|
208
|
+
const matchingPaths = pathOperations.filter((p) => p.path === cleanPathName && p.method === method);
|
|
209
|
+
pathOperations.push({ path: cleanPathName, method });
|
|
210
|
+
return matchingPaths.length > 0;
|
|
211
|
+
};
|
|
212
|
+
const createFunctionPlaceholder = (path, method, operation) => {
|
|
213
|
+
const funcID = generateRandomString();
|
|
214
|
+
functionsInventory[funcID] = generateFunctionForOperation(method, path, operation);
|
|
215
|
+
return funcID;
|
|
216
|
+
};
|
|
217
|
+
function handlePathSegment(path, operation, existingObj = null) {
|
|
218
|
+
const methods = Object.keys(operation);
|
|
219
|
+
const obj = {};
|
|
220
|
+
for (const method of methods) {
|
|
221
|
+
let functionName = method.toLowerCase();
|
|
222
|
+
if (hasConflict(path, method)) {
|
|
223
|
+
const params = getParamsFromPath(path);
|
|
224
|
+
functionName += params ? `By${toPascalCase(params?.pop() || "")}` : "All";
|
|
225
|
+
}
|
|
226
|
+
obj[functionName] = createFunctionPlaceholder(path, method, operation[method]);
|
|
227
|
+
}
|
|
228
|
+
return { ...obj, ...existingObj };
|
|
229
|
+
}
|
|
230
|
+
function generateFunctions(paths, baseURL) {
|
|
231
|
+
let tsString = "";
|
|
232
|
+
const body = {};
|
|
233
|
+
const allPathsClean = Object.keys(paths).map(cleanPath);
|
|
234
|
+
for (const [path, operation] of Object.entries(paths)) {
|
|
235
|
+
const splitPath = path.split("/").filter((p) => p && !p.match(/\{|\}/));
|
|
236
|
+
splitPath.reduce((acc, key, index, array) => {
|
|
237
|
+
const objFuncKey = toCamelCase(key);
|
|
238
|
+
if (!objFuncKey)
|
|
239
|
+
return acc;
|
|
240
|
+
const methods = Object.keys(operation);
|
|
241
|
+
if (index === array.length - 1 && methods.length === 1 && allPathsClean.filter((p) => p === cleanPath(path)).length === 1) {
|
|
242
|
+
const method = methods[0];
|
|
243
|
+
const opp = { ...operation }[method];
|
|
244
|
+
acc[objFuncKey] = createFunctionPlaceholder(path, methods[0], opp);
|
|
245
|
+
} else if (index === array.length - 1)
|
|
246
|
+
acc[objFuncKey] = handlePathSegment(path, operation, acc[objFuncKey]);
|
|
247
|
+
else if (!acc[objFuncKey] || typeof acc[objFuncKey] !== "object")
|
|
248
|
+
acc[objFuncKey] = {};
|
|
249
|
+
return acc[objFuncKey];
|
|
250
|
+
}, body);
|
|
251
|
+
}
|
|
252
|
+
for (const [parent, object] of Object.entries(body)) {
|
|
253
|
+
tsString += `export const ${parent} = ${JSON.stringify(object, null, 2)};
|
|
254
|
+
`;
|
|
255
|
+
}
|
|
256
|
+
Object.entries(functionsInventory).forEach(([key, value]) => {
|
|
257
|
+
tsString = tsString.replace(`"${key}"`, value);
|
|
258
|
+
});
|
|
259
|
+
tsString = fileTemplate(tsString, allTypes, baseURL);
|
|
260
|
+
return tsString;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const index = async (openApiUrl, baseURL) => {
|
|
264
|
+
try {
|
|
265
|
+
const { data: openApi } = await axios$1.get(openApiUrl);
|
|
266
|
+
const schemas = openApi.components?.schemas;
|
|
267
|
+
if (!schemas)
|
|
268
|
+
throw new Error("No schemas found in OpenAPI document");
|
|
269
|
+
const types = generateTypes(schemas);
|
|
270
|
+
const { paths } = openApi;
|
|
271
|
+
if (!paths)
|
|
272
|
+
throw new Error("No paths found in OpenAPI document");
|
|
273
|
+
const code = generateFunctions(paths, baseURL);
|
|
274
|
+
return { types, code };
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw new Error(error);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
2
279
|
|
|
3
280
|
var __defProp = Object.defineProperty;
|
|
4
281
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -6,7 +283,7 @@ var __publicField = (obj, key, value) => {
|
|
|
6
283
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
7
284
|
return value;
|
|
8
285
|
};
|
|
9
|
-
const axios =
|
|
286
|
+
const axios = axios$1.create({
|
|
10
287
|
// withCredentials to true to send cookies with requests
|
|
11
288
|
withCredentials: true
|
|
12
289
|
});
|
|
@@ -227,4 +504,4 @@ class Bagel {
|
|
|
227
504
|
}
|
|
228
505
|
}
|
|
229
506
|
|
|
230
|
-
export { Bagel };
|
|
507
|
+
export { Bagel, index as openAPI };
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
OperationObject, ParameterObject, PathsObject, RequestBodyObject, ResponseObject, ResponsesObject,
|
|
3
|
+
} from './openApiTypes';
|
|
4
|
+
import {
|
|
5
|
+
cleanPath,
|
|
6
|
+
formatVarType, schemaToType, toCamelCase, toPascalCase,
|
|
7
|
+
} from './utils';
|
|
8
|
+
|
|
9
|
+
const allTypes: string[] = [];
|
|
10
|
+
|
|
11
|
+
function collectTypeForImportStatement(typeName: string) {
|
|
12
|
+
typeName = typeName.trim().replace('[]', '');
|
|
13
|
+
if (typeName.includes('|')) {
|
|
14
|
+
typeName
|
|
15
|
+
.split('|')
|
|
16
|
+
.forEach(
|
|
17
|
+
(singleType) => collectTypeForImportStatement(singleType),
|
|
18
|
+
);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const primitiveTypes = ['string', 'number', 'boolean', 'null', 'void', 'any', 'Record<string, any>'];
|
|
22
|
+
const isPrimitive = primitiveTypes.includes(typeName);
|
|
23
|
+
if (!typeName || isPrimitive) return;
|
|
24
|
+
if (!allTypes.includes(typeName)) allTypes.push(typeName);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getResponseType(response: ResponseObject): string | null {
|
|
28
|
+
const mediaTypeObject = response.content?.['application/json'];
|
|
29
|
+
if (!mediaTypeObject || !mediaTypeObject.schema) return null;
|
|
30
|
+
const responseType = schemaToType(mediaTypeObject.schema);
|
|
31
|
+
collectTypeForImportStatement(responseType);
|
|
32
|
+
return responseType;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function generateResponseType(responses?: ResponsesObject): string {
|
|
36
|
+
if (!responses) return '';
|
|
37
|
+
const types: string[] = [];
|
|
38
|
+
|
|
39
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
40
|
+
if (statusCode.startsWith('2')) {
|
|
41
|
+
const responseType = getResponseType(response);
|
|
42
|
+
if (responseType && responseType !== 'any') {
|
|
43
|
+
types.push(responseType);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return types?.join(' | ');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generates the string representation of an Axios function call.
|
|
52
|
+
* @param method - The HTTP method (get, post, etc.).
|
|
53
|
+
* @param formattedPath - The API endpoint path, formatted with template literals for parameters.
|
|
54
|
+
* @param allParams - All parameters required for the function, formatted as a string.
|
|
55
|
+
* @param responseTypeStr - The expected response type for the function.
|
|
56
|
+
* @param parameters - Additional configuration parameters, if any.
|
|
57
|
+
* @returns A string representing the Axios function.
|
|
58
|
+
*/
|
|
59
|
+
function generateAxiosFunction(
|
|
60
|
+
method: string,
|
|
61
|
+
formattedPath: string,
|
|
62
|
+
allParams: string,
|
|
63
|
+
responseTypeStr: string,
|
|
64
|
+
parameters: any,
|
|
65
|
+
requestBodyPayload: string,
|
|
66
|
+
): string {
|
|
67
|
+
let axiosFunction = `async (${allParams})${responseTypeStr} => axios.${method}(`;
|
|
68
|
+
// Different handling based on the HTTP method
|
|
69
|
+
const paramStr = parameters?.config?.params ? `, { params: {${parameters.config.params}} }` : '';
|
|
70
|
+
if (['get', 'delete'].includes(method)) {
|
|
71
|
+
axiosFunction += `${formattedPath}${paramStr}`;
|
|
72
|
+
} else if (['post', 'put', 'patch'].includes(method)) {
|
|
73
|
+
const bodyVar = requestBodyPayload ? `, ${requestBodyPayload}` : '';
|
|
74
|
+
|
|
75
|
+
axiosFunction += `${formattedPath}${bodyVar}${paramStr}`;
|
|
76
|
+
}
|
|
77
|
+
axiosFunction += ')';
|
|
78
|
+
return axiosFunction;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const pathParamRegex = /\{([^}]+)\}/g;
|
|
82
|
+
const getParamsFromPath = (path: string) => {
|
|
83
|
+
const params = path.match(pathParamRegex)?.map((p) => p.slice(1, -1));
|
|
84
|
+
return params;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function formatPathWithParams(path: string) {
|
|
88
|
+
const params = getParamsFromPath(path);
|
|
89
|
+
const formattedPath = params ? `\`${path.replace(pathParamRegex, (v) => `$${toCamelCase(v)}`)}\`` : `'${path}'`;
|
|
90
|
+
return formattedPath;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function generateRequestBody(requestBody?: RequestBodyObject): Record<string, string> {
|
|
94
|
+
const bodySchema = requestBody?.content?.['application/json']?.schema;
|
|
95
|
+
if (!bodySchema) return { requestBodyParam: '', requestBodyPayload: '' };
|
|
96
|
+
const requestBodyType = schemaToType(bodySchema);
|
|
97
|
+
collectTypeForImportStatement(requestBodyType);
|
|
98
|
+
const requestBodyPayload = toCamelCase(bodySchema?.title) || toCamelCase(requestBodyType) || 'requestBody';
|
|
99
|
+
const defaultValue = requestBody?.content?.['application/json']?.schema?.default;
|
|
100
|
+
const requestBodyParam = formatVarType(requestBodyPayload, bodySchema, defaultValue);
|
|
101
|
+
return { requestBodyParam, requestBodyPayload };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Combines and formats function parameters and request body parameters into a single parameter string.
|
|
106
|
+
* @param parameters - The parameters derived from the OpenAPI specification.
|
|
107
|
+
* @param requestBodyParam - The parameter representing the request body.
|
|
108
|
+
* @returns A string representing all combined parameters.
|
|
109
|
+
*/
|
|
110
|
+
function combineAllParams(parameters: { params?: string }, requestBodyParam: string): string {
|
|
111
|
+
let allParamsArray: string[] = [];
|
|
112
|
+
if (parameters && parameters.params) allParamsArray = parameters.params.split(',').map((p) => p.trim());
|
|
113
|
+
if (requestBodyParam) allParamsArray.push(requestBodyParam.trim());
|
|
114
|
+
|
|
115
|
+
allParamsArray = allParamsArray
|
|
116
|
+
.filter((p) => p).sort((a, b) => (a.includes('?') ? 1 : -1) - (b.includes('?') ? 1 : -1));
|
|
117
|
+
|
|
118
|
+
return allParamsArray.join(', ');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function generateFunctionParameters(params?: ParameterObject[]) {
|
|
122
|
+
if (!params || params.length === 0) return {};
|
|
123
|
+
|
|
124
|
+
const functionParams: string[] = [];
|
|
125
|
+
const paramList: string[] = [];
|
|
126
|
+
|
|
127
|
+
for (const param of params) {
|
|
128
|
+
const paramType = schemaToType(param.schema);
|
|
129
|
+
collectTypeForImportStatement(paramType);
|
|
130
|
+
const paramName = param.name;
|
|
131
|
+
const varName = toCamelCase(param.name) || 'param';
|
|
132
|
+
if (param.in === 'path' || param.in === 'query' || param.in === 'header') {
|
|
133
|
+
const defaultValue = param?.schema?.default;
|
|
134
|
+
const varType = formatVarType(varName, param.schema, param.required, defaultValue);
|
|
135
|
+
functionParams.push(varType);
|
|
136
|
+
}
|
|
137
|
+
if (param.in === 'query' || param.in === 'header') {
|
|
138
|
+
if (paramName === varName) paramList.push(paramName);
|
|
139
|
+
else paramList.push(`'${paramName}': ${varName}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const paramsString = functionParams.join(', ');
|
|
144
|
+
const config: { params?: string } = {};
|
|
145
|
+
|
|
146
|
+
if (paramList.length > 0) config.params = paramList.join(', ');
|
|
147
|
+
return { params: paramsString, config };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function generateFunctionForOperation(method: string, path: string, operation: OperationObject): string {
|
|
151
|
+
if (!operation) return '';
|
|
152
|
+
const parameters = generateFunctionParameters(operation.parameters);
|
|
153
|
+
const responseType = generateResponseType(operation.responses);
|
|
154
|
+
const formattedPath = formatPathWithParams(path);
|
|
155
|
+
const { requestBodyParam, requestBodyPayload } = generateRequestBody(operation.requestBody);
|
|
156
|
+
const allParams = combineAllParams(parameters, requestBodyParam);
|
|
157
|
+
const responseTypeStr = responseType ? `: Promise<AxiosResponse<${responseType}>>` : '';
|
|
158
|
+
return generateAxiosFunction(method, formattedPath, allParams, responseTypeStr, parameters, requestBodyPayload);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const generateRandomString = () => Math.random().toString(36).substring(7);
|
|
162
|
+
|
|
163
|
+
function fileTemplate(tsString: string, typeForImport: string[], baseURL: string) {
|
|
164
|
+
const templateCode = (
|
|
165
|
+
`import axios, { type AxiosResponse } from 'axios';
|
|
166
|
+
import type {${typeForImport.join(', ')}} from './types.d';
|
|
167
|
+
|
|
168
|
+
axios.defaults.baseURL = ${baseURL};
|
|
169
|
+
|
|
170
|
+
${tsString}`
|
|
171
|
+
);
|
|
172
|
+
const doubleQuoteRegex = /"([^"]+)":/g;
|
|
173
|
+
return templateCode.replace(doubleQuoteRegex, '$1:');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const functionsInventory: Record<string, any> = {};
|
|
177
|
+
const pathOperations: any[] = [];
|
|
178
|
+
|
|
179
|
+
const hasConflict = (path: string, method: string) => {
|
|
180
|
+
const cleanPathName = path.split('/').filter((p) => p && !p.match(/\{|\}/)).join('/');
|
|
181
|
+
const matchingPaths = pathOperations.filter((p) => p.path === cleanPathName && p.method === method);
|
|
182
|
+
pathOperations.push({ path: cleanPathName, method });
|
|
183
|
+
return matchingPaths.length > 0;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Creates a placeholder for a function and stores its body in the inventory
|
|
187
|
+
const createFunctionPlaceholder = (path: string, method: string, operation: any) => {
|
|
188
|
+
const funcID = generateRandomString();
|
|
189
|
+
functionsInventory[funcID] = generateFunctionForOperation(method, path, operation);
|
|
190
|
+
return funcID;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
function handlePathSegment(
|
|
194
|
+
path: string,
|
|
195
|
+
operation: any,
|
|
196
|
+
existingObj: Record<string, any> | null = null,
|
|
197
|
+
) {
|
|
198
|
+
const methods = Object.keys(operation);
|
|
199
|
+
const obj: Record<string, any> = {};
|
|
200
|
+
for (const method of methods) {
|
|
201
|
+
let functionName = method.toLowerCase();
|
|
202
|
+
if (hasConflict(path, method)) {
|
|
203
|
+
const params: string[] | undefined = getParamsFromPath(path);
|
|
204
|
+
functionName += (params ? `By${toPascalCase(params?.pop() || '')}` : 'All');
|
|
205
|
+
}
|
|
206
|
+
obj[functionName] = createFunctionPlaceholder(path, method, operation[method]);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { ...obj, ...existingObj };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function generateFunctions(paths: PathsObject, baseURL: string) {
|
|
213
|
+
let tsString = '';
|
|
214
|
+
const body: Record<string, any> = {};
|
|
215
|
+
const allPathsClean = Object.keys(paths).map(cleanPath);
|
|
216
|
+
for (const [path, operation] of Object.entries(paths)) {
|
|
217
|
+
const splitPath = path.split('/').filter((p) => p && !p.match(/\{|\}/));
|
|
218
|
+
splitPath.reduce((acc, key: string, index: number, array: string[]) => {
|
|
219
|
+
const objFuncKey = toCamelCase(key);
|
|
220
|
+
if (!objFuncKey) return acc;
|
|
221
|
+
const methods = Object.keys(operation);
|
|
222
|
+
if (
|
|
223
|
+
index === array.length - 1 &&
|
|
224
|
+
methods.length === 1 &&
|
|
225
|
+
allPathsClean.filter((p) => p === cleanPath(path)).length === 1
|
|
226
|
+
) {
|
|
227
|
+
const method: string = methods[0];
|
|
228
|
+
const opp:any = { ...operation }[method];
|
|
229
|
+
acc[objFuncKey] = createFunctionPlaceholder(path, methods[0], opp);
|
|
230
|
+
} else if (
|
|
231
|
+
index === array.length - 1
|
|
232
|
+
) acc[objFuncKey] = handlePathSegment(path, operation, acc[objFuncKey]);
|
|
233
|
+
else if (!acc[objFuncKey] || typeof acc[objFuncKey] !== 'object') acc[objFuncKey] = {};
|
|
234
|
+
return acc[objFuncKey];
|
|
235
|
+
}, body);
|
|
236
|
+
}
|
|
237
|
+
for (const [parent, object] of Object.entries(body)) {
|
|
238
|
+
tsString += `export const ${parent} = ${JSON.stringify(object, null, 2)};\n`;
|
|
239
|
+
}
|
|
240
|
+
Object.entries(functionsInventory).forEach(([key, value]) => {
|
|
241
|
+
tsString = tsString.replace(`"${key}"`, value);
|
|
242
|
+
});
|
|
243
|
+
tsString = fileTemplate(tsString, allTypes, baseURL);
|
|
244
|
+
return tsString;
|
|
245
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import type { OpenAPIDocument } from './openApiTypes';
|
|
3
|
+
import { generateTypes } from './typeGenerator';
|
|
4
|
+
import { generateFunctions } from './functionGenerator';
|
|
5
|
+
|
|
6
|
+
type OpenAPIResponse = {
|
|
7
|
+
types: string;
|
|
8
|
+
code: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default async (openApiUrl: string, baseURL: string): Promise<OpenAPIResponse> => {
|
|
12
|
+
try {
|
|
13
|
+
const { data: openApi } = await axios.get<OpenAPIDocument>(openApiUrl);
|
|
14
|
+
const schemas = openApi.components?.schemas;
|
|
15
|
+
if (!schemas) throw new Error('No schemas found in OpenAPI document');
|
|
16
|
+
const types = generateTypes(schemas);
|
|
17
|
+
// Generate Functions
|
|
18
|
+
const { paths } = openApi;
|
|
19
|
+
if (!paths) throw new Error('No paths found in OpenAPI document');
|
|
20
|
+
const code = generateFunctions(paths, baseURL);
|
|
21
|
+
return { types, code };
|
|
22
|
+
} catch (error: any) {
|
|
23
|
+
throw new Error(error);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface OpenAPIDocument {
|
|
2
|
+
openapi: string;
|
|
3
|
+
info: InfoObject;
|
|
4
|
+
paths: PathsObject,
|
|
5
|
+
components?: ComponentsObject;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type PathsObject = { [path: string]: PathItemObject };
|
|
9
|
+
|
|
10
|
+
export interface InfoObject {
|
|
11
|
+
title: string;
|
|
12
|
+
version: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PathItemObject {
|
|
16
|
+
get?: OperationObject;
|
|
17
|
+
put?: OperationObject;
|
|
18
|
+
post?: OperationObject;
|
|
19
|
+
delete?: OperationObject;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type SchemasObject = { [schema: string]: SchemaObject };
|
|
23
|
+
|
|
24
|
+
export interface ComponentsObject {
|
|
25
|
+
schemas?: SchemasObject
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface OperationObject {
|
|
29
|
+
summary?: string;
|
|
30
|
+
operationId?: string;
|
|
31
|
+
parameters?: ParameterObject[];
|
|
32
|
+
requestBody?: RequestBodyObject;
|
|
33
|
+
responses: ResponsesObject;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ResponsesObject = { [statusCode: string]: ResponseObject };
|
|
37
|
+
|
|
38
|
+
export interface SchemaObject {
|
|
39
|
+
type?: string;
|
|
40
|
+
properties?: { [property: string]: SchemaObject };
|
|
41
|
+
items?: SchemaObject;
|
|
42
|
+
$ref?: string;
|
|
43
|
+
enum: string[];
|
|
44
|
+
anyOf?: SchemaObject[];
|
|
45
|
+
allOf?: SchemaObject[];
|
|
46
|
+
title?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
default: any
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ParameterObject {
|
|
52
|
+
name: string;
|
|
53
|
+
in: string;
|
|
54
|
+
schema: SchemaObject;
|
|
55
|
+
required?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface RequestBodyObject {
|
|
59
|
+
description?: string;
|
|
60
|
+
content: { [mediaType: string]: MediaTypeObject };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ResponseObject {
|
|
64
|
+
description: string;
|
|
65
|
+
content?: { [mediaType: string]: MediaTypeObject };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface MediaTypeObject {
|
|
69
|
+
schema?: SchemaObject;
|
|
70
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { formatVarType } from './utils';
|
|
2
|
+
import type { SchemasObject } from './openApiTypes';
|
|
3
|
+
|
|
4
|
+
export const generateTypes = (schemas: SchemasObject): string => Object.entries(schemas).map(([typeName, schema]) => {
|
|
5
|
+
if (schema?.enum) {
|
|
6
|
+
return `export type ${typeName} = ${schema.enum.map((item: string) => `'${item}'`).join(' | ')};\n`;
|
|
7
|
+
}
|
|
8
|
+
if (!schema?.properties) return '';
|
|
9
|
+
|
|
10
|
+
const properties = Object.entries(schema.properties).map(([key, value]) => {
|
|
11
|
+
const varType = formatVarType(key, value);
|
|
12
|
+
return `\t\t${varType}`;
|
|
13
|
+
}).join(';\n ');
|
|
14
|
+
|
|
15
|
+
return `export type ${typeName} = {\n ${properties};\n };\n`;
|
|
16
|
+
}).join('\n');
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { SchemaObject } from './openApiTypes';
|
|
2
|
+
|
|
3
|
+
const resolveReference = (ref: string): string => {
|
|
4
|
+
const t = ref.split('/').pop() || 'any';
|
|
5
|
+
return t;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const schemaToType = (schema?: SchemaObject): string => {
|
|
9
|
+
if (!schema) return 'any';
|
|
10
|
+
if (schema.anyOf) return schema.anyOf.map((s) => schemaToType(s)).filter((p) => p !== 'any').join(' | ');
|
|
11
|
+
if (schema.allOf) return schema.allOf.map((s) => schemaToType(s)).filter((p) => p !== 'any').join(' & ');
|
|
12
|
+
if (schema.$ref) return resolveReference(schema.$ref);
|
|
13
|
+
switch (schema.type) {
|
|
14
|
+
case 'object':
|
|
15
|
+
return 'Record<string, any>';
|
|
16
|
+
case 'string':
|
|
17
|
+
return 'string';
|
|
18
|
+
case 'integer':
|
|
19
|
+
return 'number';
|
|
20
|
+
case 'number':
|
|
21
|
+
return 'number';
|
|
22
|
+
case 'boolean':
|
|
23
|
+
return 'boolean';
|
|
24
|
+
case 'array':
|
|
25
|
+
return `${schemaToType(schema.items)}[]`;
|
|
26
|
+
case 'null':
|
|
27
|
+
return 'null';
|
|
28
|
+
default:
|
|
29
|
+
return 'any';
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const toCamelCase = (str?: string) => str?.replace(/[-_\s]+(.)?/g, (_, c) => c?.toUpperCase() || '').replace(/^./, (str) => str.toLowerCase()) || str;
|
|
34
|
+
export const toPascalCase = (str?: string) => str?.replace(/[-_\s]+(.)?/g, (_, c) => c?.toUpperCase() || '').replace(/^./, (str) => str.toUpperCase()) || str;
|
|
35
|
+
|
|
36
|
+
export const isOptional = (schema: SchemaObject) => {
|
|
37
|
+
/// / !schema?.required?.includes(schema.title || '')
|
|
38
|
+
const type = schemaToType(schema);
|
|
39
|
+
return type?.split(/\s+\|\s+/).includes('null');
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const cleanNulls = (str: string) => str.split(' | ').filter((t) => t !== 'null').join(' | ');
|
|
43
|
+
|
|
44
|
+
export function formatVarType(varName: string, schema: any, required = false, defaultValue: any = null) {
|
|
45
|
+
let type = schemaToType(schema);
|
|
46
|
+
const optionalStr = required || isOptional(schema) ? '?' : '';
|
|
47
|
+
type = cleanNulls(type);
|
|
48
|
+
let defaultStr = defaultValue ? ` = ${defaultValue}` : '';
|
|
49
|
+
if (defaultStr && type === 'string') defaultStr = ` = '${defaultValue}'`;
|
|
50
|
+
else if (defaultValue && (typeof defaultValue === 'object' || defaultValue === '{}')) defaultStr = ' = {}';
|
|
51
|
+
return `${varName}${optionalStr}: ${type}${defaultStr}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function cleanPath(path: string) {
|
|
55
|
+
return path.split('/').filter((p) => p && !p.match(/\{|\}/)).join('/');
|
|
56
|
+
}
|