schemantic 0.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/CHANGELOG.md +92 -0
- package/LICENSE +21 -0
- package/README.md +827 -0
- package/dist/__tests__/setup.d.ts +6 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +30 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/test-config.d.ts +36 -0
- package/dist/__tests__/test-config.d.ts.map +1 -0
- package/dist/__tests__/test-config.js +563 -0
- package/dist/__tests__/test-config.js.map +1 -0
- package/dist/cli/index.d.ts +118 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +657 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +21 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/schemantic.d.ts +95 -0
- package/dist/core/schemantic.d.ts.map +1 -0
- package/dist/core/schemantic.js +543 -0
- package/dist/core/schemantic.js.map +1 -0
- package/dist/generators/api-client-generator.d.ts +145 -0
- package/dist/generators/api-client-generator.d.ts.map +1 -0
- package/dist/generators/api-client-generator.js +856 -0
- package/dist/generators/api-client-generator.js.map +1 -0
- package/dist/generators/base.d.ts +117 -0
- package/dist/generators/base.d.ts.map +1 -0
- package/dist/generators/base.js +232 -0
- package/dist/generators/base.js.map +1 -0
- package/dist/generators/enum-generator.d.ts +52 -0
- package/dist/generators/enum-generator.d.ts.map +1 -0
- package/dist/generators/enum-generator.js +178 -0
- package/dist/generators/enum-generator.js.map +1 -0
- package/dist/generators/factory.d.ts +59 -0
- package/dist/generators/factory.d.ts.map +1 -0
- package/dist/generators/factory.js +127 -0
- package/dist/generators/factory.js.map +1 -0
- package/dist/generators/hook-generator.d.ts +36 -0
- package/dist/generators/hook-generator.d.ts.map +1 -0
- package/dist/generators/hook-generator.js +495 -0
- package/dist/generators/hook-generator.js.map +1 -0
- package/dist/generators/index.d.ts +10 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +26 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/object-generator.d.ts +48 -0
- package/dist/generators/object-generator.d.ts.map +1 -0
- package/dist/generators/object-generator.js +280 -0
- package/dist/generators/object-generator.js.map +1 -0
- package/dist/generators/primitive-generator.d.ts +40 -0
- package/dist/generators/primitive-generator.d.ts.map +1 -0
- package/dist/generators/primitive-generator.js +150 -0
- package/dist/generators/primitive-generator.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/base.d.ts +101 -0
- package/dist/parsers/base.d.ts.map +1 -0
- package/dist/parsers/base.js +77 -0
- package/dist/parsers/base.js.map +1 -0
- package/dist/parsers/factory.d.ts +64 -0
- package/dist/parsers/factory.d.ts.map +1 -0
- package/dist/parsers/factory.js +116 -0
- package/dist/parsers/factory.js.map +1 -0
- package/dist/parsers/index.d.ts +7 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +23 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/openapi.d.ts +52 -0
- package/dist/parsers/openapi.d.ts.map +1 -0
- package/dist/parsers/openapi.js +396 -0
- package/dist/parsers/openapi.js.map +1 -0
- package/dist/plugins/branded-types.d.ts +28 -0
- package/dist/plugins/branded-types.d.ts.map +1 -0
- package/dist/plugins/branded-types.js +568 -0
- package/dist/plugins/branded-types.js.map +1 -0
- package/dist/plugins/builtin.d.ts +38 -0
- package/dist/plugins/builtin.d.ts.map +1 -0
- package/dist/plugins/builtin.js +283 -0
- package/dist/plugins/builtin.js.map +1 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +23 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/loader.d.ts +92 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +245 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/manager.d.ts +99 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +233 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/performance-monitoring.d.ts +98 -0
- package/dist/plugins/performance-monitoring.d.ts.map +1 -0
- package/dist/plugins/performance-monitoring.js +596 -0
- package/dist/plugins/performance-monitoring.js.map +1 -0
- package/dist/plugins/request-deduplication.d.ts +27 -0
- package/dist/plugins/request-deduplication.d.ts.map +1 -0
- package/dist/plugins/request-deduplication.js +650 -0
- package/dist/plugins/request-deduplication.js.map +1 -0
- package/dist/plugins/zod-validation.d.ts +63 -0
- package/dist/plugins/zod-validation.d.ts.map +1 -0
- package/dist/plugins/zod-validation.js +743 -0
- package/dist/plugins/zod-validation.js.map +1 -0
- package/dist/types/core.d.ts +203 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +24 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/openapi.d.ts +250 -0
- package/dist/types/openapi.d.ts.map +1 -0
- package/dist/types/openapi.js +8 -0
- package/dist/types/openapi.js.map +1 -0
- package/dist/types/schema.d.ts +68 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +22 -0
- package/dist/types/schema.js.map +1 -0
- package/docs/ADVANCED_PLUGINS_IMPLEMENTATION.md +355 -0
- package/docs/ADVANCED_PLUGIN_DEVELOPMENT.md +309 -0
- package/docs/API.md +600 -0
- package/docs/CONFIGURATION.md +722 -0
- package/docs/FASTAPI_TUTORIAL.md +1497 -0
- package/docs/PLUGINS.md +683 -0
- package/docs/PLUGIN_DEVELOPMENT.md +720 -0
- package/package.json +90 -0
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* API client generator for OpenAPI specifications
|
|
4
|
+
* Generates TypeScript API client classes with proper typing from OpenAPI schemas
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ApiClientGenerator = void 0;
|
|
8
|
+
// Removed unused TypeGeneratorFactory import
|
|
9
|
+
/**
|
|
10
|
+
* API client generator implementation
|
|
11
|
+
*/
|
|
12
|
+
class ApiClientGenerator {
|
|
13
|
+
constructor(context) {
|
|
14
|
+
// Store context for access to config (namingConvention, etc.)
|
|
15
|
+
this.context = context;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate API client from OpenAPI schema
|
|
19
|
+
*/
|
|
20
|
+
generate(context) {
|
|
21
|
+
const schema = context.schema;
|
|
22
|
+
const clientName = this.getClientName(schema);
|
|
23
|
+
// Extract endpoints from paths
|
|
24
|
+
const endpoints = this.extractEndpoints(schema.paths, context);
|
|
25
|
+
// Generate client class
|
|
26
|
+
const clientContent = this.generateClientClass(clientName, endpoints);
|
|
27
|
+
// Generate imports
|
|
28
|
+
const imports = this.generateImports(endpoints);
|
|
29
|
+
// Generate full content
|
|
30
|
+
const fullContent = imports + clientContent;
|
|
31
|
+
return {
|
|
32
|
+
name: clientName,
|
|
33
|
+
content: fullContent,
|
|
34
|
+
dependencies: this.getDependencies(endpoints),
|
|
35
|
+
exports: [clientName, "ApiClientError"],
|
|
36
|
+
endpoints,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract endpoints from OpenAPI paths
|
|
41
|
+
*/
|
|
42
|
+
extractEndpoints(paths, context) {
|
|
43
|
+
const endpoints = [];
|
|
44
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
45
|
+
if (!pathItem || typeof pathItem !== "object") {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// Extract operations from path item
|
|
49
|
+
const operations = [
|
|
50
|
+
"get",
|
|
51
|
+
"put",
|
|
52
|
+
"post",
|
|
53
|
+
"delete",
|
|
54
|
+
"options",
|
|
55
|
+
"head",
|
|
56
|
+
"patch",
|
|
57
|
+
"trace",
|
|
58
|
+
];
|
|
59
|
+
for (const method of operations) {
|
|
60
|
+
const operation = pathItem[method];
|
|
61
|
+
if (operation) {
|
|
62
|
+
const endpoint = this.extractEndpoint(path, method.toUpperCase(), operation, context);
|
|
63
|
+
if (endpoint) {
|
|
64
|
+
endpoints.push(endpoint);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return endpoints;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Extract endpoint information from operation
|
|
73
|
+
*/
|
|
74
|
+
extractEndpoint(path, method, operation, context) {
|
|
75
|
+
const operationId = operation.operationId ||
|
|
76
|
+
this.generateOperationId(path, method, operation);
|
|
77
|
+
const functionName = this.convertToFunctionName(operationId);
|
|
78
|
+
// Extract parameters
|
|
79
|
+
const parameters = this.extractParameters(operation.parameters || [], context);
|
|
80
|
+
// Extract request body
|
|
81
|
+
const requestBody = this.extractRequestBody(operation.requestBody, context);
|
|
82
|
+
// Extract responses
|
|
83
|
+
const responses = this.extractResponses(operation.responses, context);
|
|
84
|
+
// Determine return type
|
|
85
|
+
const returnType = this.determineReturnType(responses);
|
|
86
|
+
const endpoint = {
|
|
87
|
+
operationId,
|
|
88
|
+
method,
|
|
89
|
+
path,
|
|
90
|
+
parameters,
|
|
91
|
+
responses,
|
|
92
|
+
returnType,
|
|
93
|
+
functionName,
|
|
94
|
+
};
|
|
95
|
+
if (requestBody) {
|
|
96
|
+
endpoint.requestBody = requestBody;
|
|
97
|
+
}
|
|
98
|
+
return endpoint;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Extract parameters from operation
|
|
102
|
+
*/
|
|
103
|
+
extractParameters(parameters, context) {
|
|
104
|
+
const validParameters = [];
|
|
105
|
+
for (const param of parameters) {
|
|
106
|
+
if (typeof param !== "object" || param === null) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Skip $ref parameters for now (would need to be resolved)
|
|
110
|
+
if ("$ref" in param) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const type = this.extractParameterType(param.schema, context);
|
|
114
|
+
const isRequired = param.required || false;
|
|
115
|
+
validParameters.push({
|
|
116
|
+
name: param.name,
|
|
117
|
+
type,
|
|
118
|
+
isOptional: !isRequired,
|
|
119
|
+
isRequired,
|
|
120
|
+
location: param.in,
|
|
121
|
+
...(param.description && { description: param.description }),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return validParameters;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract request body from operation
|
|
128
|
+
*/
|
|
129
|
+
extractRequestBody(requestBody, context) {
|
|
130
|
+
if (!requestBody || typeof requestBody !== "object") {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
// Handle reference
|
|
134
|
+
if ("$ref" in requestBody) {
|
|
135
|
+
const resolvedSchema = context.schemaResolver(requestBody.$ref);
|
|
136
|
+
if (resolvedSchema) {
|
|
137
|
+
const type = this.extractSchemaType(resolvedSchema, context);
|
|
138
|
+
return {
|
|
139
|
+
type,
|
|
140
|
+
isOptional: true, // Refs don't have required field directly
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
// Handle inline request body
|
|
146
|
+
if ("content" in requestBody && requestBody.content) {
|
|
147
|
+
const contentType = Object.keys(requestBody.content)[0];
|
|
148
|
+
if (!contentType)
|
|
149
|
+
return undefined;
|
|
150
|
+
const mediaType = requestBody.content[contentType];
|
|
151
|
+
if (mediaType && mediaType.schema) {
|
|
152
|
+
let type = this.extractSchemaType(mediaType.schema, context);
|
|
153
|
+
const body = {
|
|
154
|
+
type,
|
|
155
|
+
isOptional: !requestBody.required,
|
|
156
|
+
...(requestBody.description && {
|
|
157
|
+
description: requestBody.description,
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
if (contentType) {
|
|
161
|
+
body.contentType = contentType;
|
|
162
|
+
}
|
|
163
|
+
return body;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Extract responses from operation
|
|
170
|
+
*/
|
|
171
|
+
extractResponses(responses, context) {
|
|
172
|
+
const responseList = [];
|
|
173
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
174
|
+
if (typeof response !== "object" || response === null) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const responseObj = response;
|
|
178
|
+
// Handle reference
|
|
179
|
+
if ("$ref" in responseObj) {
|
|
180
|
+
const resolvedSchema = context.schemaResolver(responseObj.$ref);
|
|
181
|
+
if (resolvedSchema) {
|
|
182
|
+
const type = this.extractSchemaType(resolvedSchema, context);
|
|
183
|
+
responseList.push({
|
|
184
|
+
statusCode,
|
|
185
|
+
type,
|
|
186
|
+
isOptional: false,
|
|
187
|
+
description: ("description" in resolvedSchema && resolvedSchema.description) ||
|
|
188
|
+
"",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Handle inline response
|
|
194
|
+
if ("content" in responseObj && responseObj.content) {
|
|
195
|
+
const contentType = Object.keys(responseObj.content)[0];
|
|
196
|
+
if (!contentType) {
|
|
197
|
+
// Response without content type
|
|
198
|
+
responseList.push({
|
|
199
|
+
statusCode,
|
|
200
|
+
type: "void",
|
|
201
|
+
isOptional: false,
|
|
202
|
+
description: responseObj.description || "",
|
|
203
|
+
});
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const mediaType = responseObj.content[contentType];
|
|
207
|
+
if (mediaType && mediaType.schema) {
|
|
208
|
+
const type = this.extractSchemaType(mediaType.schema, context);
|
|
209
|
+
responseList.push({
|
|
210
|
+
statusCode,
|
|
211
|
+
type,
|
|
212
|
+
isOptional: false,
|
|
213
|
+
description: responseObj.description || "",
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Response without content
|
|
219
|
+
responseList.push({
|
|
220
|
+
statusCode,
|
|
221
|
+
type: "void",
|
|
222
|
+
isOptional: false,
|
|
223
|
+
description: responseObj.description || "",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return responseList;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Determine return type from responses
|
|
231
|
+
*/
|
|
232
|
+
determineReturnType(responses) {
|
|
233
|
+
if (responses.length === 0) {
|
|
234
|
+
return "void";
|
|
235
|
+
}
|
|
236
|
+
if (responses.length === 1) {
|
|
237
|
+
return responses[0].type;
|
|
238
|
+
}
|
|
239
|
+
// Multiple responses - create union type
|
|
240
|
+
const types = responses.map((r) => r.type);
|
|
241
|
+
return types.join(" | ");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Extract parameter type from schema
|
|
245
|
+
*/
|
|
246
|
+
extractParameterType(schema, context) {
|
|
247
|
+
if (!schema) {
|
|
248
|
+
return "unknown";
|
|
249
|
+
}
|
|
250
|
+
return this.extractSchemaType(schema, context);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Extract schema type
|
|
254
|
+
*/
|
|
255
|
+
extractSchemaType(schema, context) {
|
|
256
|
+
// Handle reference
|
|
257
|
+
if ("$ref" in schema) {
|
|
258
|
+
if (typeof schema.$ref === "string") {
|
|
259
|
+
const refType = this.extractTypeNameFromRef(schema.$ref);
|
|
260
|
+
// Normalize and apply prefix/suffix to match type generator output
|
|
261
|
+
return this.formatTypeName(refType, context);
|
|
262
|
+
}
|
|
263
|
+
// $ref exists but isn't a string; cannot extract a type name
|
|
264
|
+
return "unknown";
|
|
265
|
+
}
|
|
266
|
+
// Handle arrays
|
|
267
|
+
if ("type" in schema && schema.type === "array") {
|
|
268
|
+
const items = schema.items;
|
|
269
|
+
const itemType = items
|
|
270
|
+
? this.extractSchemaType(items, context)
|
|
271
|
+
: "unknown";
|
|
272
|
+
return `${itemType}[]`;
|
|
273
|
+
}
|
|
274
|
+
// Handle objects with properties or additionalProperties
|
|
275
|
+
if ("type" in schema && schema.type === "object") {
|
|
276
|
+
const hasProps = !!schema.properties && Object.keys(schema.properties).length > 0;
|
|
277
|
+
if (hasProps) {
|
|
278
|
+
// Prefer named types if nested refs exist; otherwise inline object type
|
|
279
|
+
const lines = [];
|
|
280
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
281
|
+
const t = this.extractSchemaType(propSchema, context);
|
|
282
|
+
lines.push(`${propName}: ${t}`);
|
|
283
|
+
}
|
|
284
|
+
return `{ ${lines.join("; ")} }`;
|
|
285
|
+
}
|
|
286
|
+
if (schema.additionalProperties !== undefined) {
|
|
287
|
+
if (schema.additionalProperties === true)
|
|
288
|
+
return "Record<string, unknown>";
|
|
289
|
+
if (typeof schema.additionalProperties === "object") {
|
|
290
|
+
const vt = this.extractSchemaType(schema.additionalProperties, context);
|
|
291
|
+
return `Record<string, ${vt}>`;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return "Record<string, unknown>";
|
|
295
|
+
}
|
|
296
|
+
// Handle basic types
|
|
297
|
+
if ("type" in schema && schema.type) {
|
|
298
|
+
return this.mapSchemaTypeToTypeScript(schema.type, schema.format);
|
|
299
|
+
}
|
|
300
|
+
// Handle enums
|
|
301
|
+
if ("enum" in schema && schema.enum) {
|
|
302
|
+
return schema.enum.map((val) => JSON.stringify(val)).join(" | ");
|
|
303
|
+
}
|
|
304
|
+
// Handle const
|
|
305
|
+
if ("const" in schema && schema.const !== undefined) {
|
|
306
|
+
return JSON.stringify(schema.const);
|
|
307
|
+
}
|
|
308
|
+
return "unknown";
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Generate client class
|
|
312
|
+
*/
|
|
313
|
+
generateClientClass(clientName, endpoints) {
|
|
314
|
+
let content = "";
|
|
315
|
+
// Generate configuration interface
|
|
316
|
+
content += this.generateConfigInterface();
|
|
317
|
+
content += "\n\n";
|
|
318
|
+
// Generate error class
|
|
319
|
+
content += this.generateErrorClass();
|
|
320
|
+
content += "\n\n";
|
|
321
|
+
// Generate client class
|
|
322
|
+
content += `export class ${clientName} {\n`;
|
|
323
|
+
content += ` private baseUrl: string;\n`;
|
|
324
|
+
content += ` private config: ApiClientConfig;\n\n`;
|
|
325
|
+
// Generate constructor
|
|
326
|
+
content += this.generateConstructor();
|
|
327
|
+
content += "\n\n";
|
|
328
|
+
// Generate endpoint methods
|
|
329
|
+
for (const endpoint of endpoints) {
|
|
330
|
+
content += this.generateEndpointMethod(endpoint);
|
|
331
|
+
content += "\n\n";
|
|
332
|
+
}
|
|
333
|
+
// Generate utility methods
|
|
334
|
+
content += this.generateUtilityMethods();
|
|
335
|
+
content += "}\n";
|
|
336
|
+
return content;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Generate configuration interface
|
|
340
|
+
*/
|
|
341
|
+
generateConfigInterface() {
|
|
342
|
+
return `export interface ApiClientConfig {
|
|
343
|
+
baseUrl: string;
|
|
344
|
+
timeout?: number;
|
|
345
|
+
headers?: Record<string, string>;
|
|
346
|
+
retries?: number;
|
|
347
|
+
retryDelay?: number;
|
|
348
|
+
}`;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Generate error class
|
|
352
|
+
*/
|
|
353
|
+
generateErrorClass() {
|
|
354
|
+
return `export class ApiClientError extends Error {
|
|
355
|
+
constructor(
|
|
356
|
+
message: string,
|
|
357
|
+
public status?: number,
|
|
358
|
+
public response?: Response
|
|
359
|
+
) {
|
|
360
|
+
super(message);
|
|
361
|
+
this.name = 'ApiClientError';
|
|
362
|
+
}
|
|
363
|
+
}`;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Generate constructor
|
|
367
|
+
*/
|
|
368
|
+
generateConstructor() {
|
|
369
|
+
return ` constructor(config: ApiClientConfig) {
|
|
370
|
+
this.baseUrl = config.baseUrl.replace(/\\/$/, '');
|
|
371
|
+
this.config = {
|
|
372
|
+
timeout: 30000,
|
|
373
|
+
retries: 3,
|
|
374
|
+
retryDelay: 1000,
|
|
375
|
+
...config,
|
|
376
|
+
};
|
|
377
|
+
}`;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Generate endpoint method
|
|
381
|
+
*/
|
|
382
|
+
generateEndpointMethod(endpoint) {
|
|
383
|
+
const methodName = endpoint.functionName;
|
|
384
|
+
const parameters = this.generateMethodParameters(endpoint);
|
|
385
|
+
const returnType = endpoint.returnType;
|
|
386
|
+
let method = ` async ${methodName}(${parameters}): Promise<${returnType}> {\n`;
|
|
387
|
+
// Generate method body
|
|
388
|
+
method += this.generateMethodBody(endpoint);
|
|
389
|
+
method += " }";
|
|
390
|
+
return method;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Generate method parameters
|
|
394
|
+
*/
|
|
395
|
+
generateMethodParameters(endpoint) {
|
|
396
|
+
const params = [];
|
|
397
|
+
// Add path parameters
|
|
398
|
+
const pathParams = endpoint.parameters.filter((p) => p.location === "path");
|
|
399
|
+
if (pathParams.length > 0) {
|
|
400
|
+
const pathParamTypes = pathParams.map((p) => `${p.name}: ${p.type}`);
|
|
401
|
+
params.push(...pathParamTypes);
|
|
402
|
+
}
|
|
403
|
+
// Add query parameters
|
|
404
|
+
const queryParams = endpoint.parameters.filter((p) => p.location === "query");
|
|
405
|
+
if (queryParams.length > 0) {
|
|
406
|
+
const queryParamTypes = queryParams.map((p) => `${p.name}${p.isOptional ? "?" : ""}: ${p.type}`);
|
|
407
|
+
params.push(...queryParamTypes);
|
|
408
|
+
}
|
|
409
|
+
// Add request body
|
|
410
|
+
if (endpoint.requestBody) {
|
|
411
|
+
const isMultipart = !!endpoint.requestBody.contentType &&
|
|
412
|
+
/^multipart\/form-data/i.test(endpoint.requestBody.contentType);
|
|
413
|
+
const bodyType = isMultipart
|
|
414
|
+
? `${endpoint.requestBody.type} | FormData`
|
|
415
|
+
: endpoint.requestBody.type;
|
|
416
|
+
const bodyParam = `body${endpoint.requestBody.isOptional ? "?" : ""}: ${bodyType}`;
|
|
417
|
+
params.push(bodyParam);
|
|
418
|
+
}
|
|
419
|
+
// Add options parameter
|
|
420
|
+
params.push("options?: RequestInit");
|
|
421
|
+
return params.join(", ");
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Generate method body
|
|
425
|
+
*/
|
|
426
|
+
generateMethodBody(endpoint) {
|
|
427
|
+
let body = "";
|
|
428
|
+
// Generate URL construction
|
|
429
|
+
body += this.generateUrlConstruction(endpoint);
|
|
430
|
+
body += "\n";
|
|
431
|
+
// Generate request options
|
|
432
|
+
body += this.generateRequestOptions(endpoint);
|
|
433
|
+
body += "\n";
|
|
434
|
+
// Generate fetch call
|
|
435
|
+
body += this.generateFetchCall(endpoint);
|
|
436
|
+
body += "\n";
|
|
437
|
+
// Generate response handling
|
|
438
|
+
body += this.generateResponseHandling(endpoint);
|
|
439
|
+
return body;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Generate URL construction
|
|
443
|
+
*/
|
|
444
|
+
generateUrlConstruction(endpoint) {
|
|
445
|
+
let urlConstruction = " const url = new URL(`${this.baseUrl}${this.buildPath(`";
|
|
446
|
+
// Replace path parameters
|
|
447
|
+
const pathParams = endpoint.parameters.filter((p) => p.location === "path");
|
|
448
|
+
let path = endpoint.path;
|
|
449
|
+
for (const param of pathParams) {
|
|
450
|
+
path = path.replace(`{${param.name}}`, `\${${param.name}}`);
|
|
451
|
+
}
|
|
452
|
+
urlConstruction += path;
|
|
453
|
+
// Add path parameters
|
|
454
|
+
if (pathParams.length > 0) {
|
|
455
|
+
const pathParamEntries = pathParams.map((p) => `${p.name}`).join(", ");
|
|
456
|
+
// Close buildPath first argument backtick, add params object, close buildPath, close ${}, close template, then close new URL
|
|
457
|
+
urlConstruction += "`, { " + pathParamEntries + " })}`);\n";
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
// Close buildPath first argument backtick, add empty params, close buildPath, close ${}, close template, then close new URL
|
|
461
|
+
urlConstruction += "`, {})}`);\n";
|
|
462
|
+
}
|
|
463
|
+
// Add query parameters
|
|
464
|
+
const queryParams = endpoint.parameters.filter((p) => p.location === "query");
|
|
465
|
+
if (queryParams.length > 0) {
|
|
466
|
+
urlConstruction += "\n // Add query parameters\n";
|
|
467
|
+
for (const param of queryParams) {
|
|
468
|
+
urlConstruction += " if (" + param.name + " !== undefined) {\n";
|
|
469
|
+
urlConstruction +=
|
|
470
|
+
" url.searchParams.set('" +
|
|
471
|
+
param.name +
|
|
472
|
+
"', String(" +
|
|
473
|
+
param.name +
|
|
474
|
+
"));\n";
|
|
475
|
+
urlConstruction += " }\n";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return urlConstruction;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Generate request options
|
|
482
|
+
*/
|
|
483
|
+
generateRequestOptions(endpoint) {
|
|
484
|
+
let options = " const requestOptions: RequestInit = {\n";
|
|
485
|
+
options += " method: '" + endpoint.method + "',\n";
|
|
486
|
+
options += " headers: {\n";
|
|
487
|
+
options += " ...this.config.headers,\n";
|
|
488
|
+
// Add content type for request body only for JSON payloads
|
|
489
|
+
if (endpoint.requestBody &&
|
|
490
|
+
endpoint.requestBody.contentType &&
|
|
491
|
+
/application\/json/i.test(endpoint.requestBody.contentType)) {
|
|
492
|
+
options += " 'Content-Type': 'application/json',\n";
|
|
493
|
+
}
|
|
494
|
+
options += " ...options?.headers,\n";
|
|
495
|
+
options += " },\n";
|
|
496
|
+
// Add request body
|
|
497
|
+
if (endpoint.requestBody) {
|
|
498
|
+
const isJson = !!endpoint.requestBody.contentType &&
|
|
499
|
+
/application\/json/i.test(endpoint.requestBody.contentType);
|
|
500
|
+
const isMultipart = !!endpoint.requestBody.contentType &&
|
|
501
|
+
/^multipart\/form-data/i.test(endpoint.requestBody.contentType);
|
|
502
|
+
if (isJson) {
|
|
503
|
+
options +=
|
|
504
|
+
" body: body !== undefined ? JSON.stringify(body) : undefined,\n";
|
|
505
|
+
}
|
|
506
|
+
else if (isMultipart) {
|
|
507
|
+
options +=
|
|
508
|
+
" body: (body instanceof FormData ? body : (body !== undefined ? this.toFormData(body as any) : undefined)) as any,\n";
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
options += " body: body as any,\n";
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
options += " ...options,\n";
|
|
515
|
+
options += " };\n";
|
|
516
|
+
return options;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Generate fetch call
|
|
520
|
+
*/
|
|
521
|
+
generateFetchCall(_endpoint) {
|
|
522
|
+
// Delegate to typed request<T> with retry
|
|
523
|
+
return "";
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Generate response handling
|
|
527
|
+
*/
|
|
528
|
+
generateResponseHandling(endpoint) {
|
|
529
|
+
// Use request helper for both void and typed responses
|
|
530
|
+
if (endpoint.returnType === "void") {
|
|
531
|
+
return " await this.request<void>(url.toString(), requestOptions);\n return;\n";
|
|
532
|
+
}
|
|
533
|
+
return ` return this.request<${endpoint.returnType}>(url.toString(), requestOptions);\n`;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Generate utility methods
|
|
537
|
+
*/
|
|
538
|
+
generateUtilityMethods() {
|
|
539
|
+
return (" private buildPath(template: string, params: Record<string, string | number>): string {\n" +
|
|
540
|
+
" return template.replace(/\\{([^}]+)\\}/g, (match, key) => {\n" +
|
|
541
|
+
" const value = params[key];\n" +
|
|
542
|
+
" if (value === undefined) {\n" +
|
|
543
|
+
" throw new Error('Missing required path parameter: ' + key);\n" +
|
|
544
|
+
" }\n" +
|
|
545
|
+
" return String(value);\n" +
|
|
546
|
+
" });\n" +
|
|
547
|
+
" }\n\n" +
|
|
548
|
+
" private toFormData(input: any): FormData {\n" +
|
|
549
|
+
" const form = new FormData();\n" +
|
|
550
|
+
" if (input && typeof input === 'object') {\n" +
|
|
551
|
+
" for (const [key, value] of Object.entries(input)) {\n" +
|
|
552
|
+
" if (value === undefined || value === null) continue;\n" +
|
|
553
|
+
" if (value instanceof Blob || (typeof File !== 'undefined' && value instanceof File)) {\n" +
|
|
554
|
+
" form.append(key, value as any);\n" +
|
|
555
|
+
" } else if (Array.isArray(value)) {\n" +
|
|
556
|
+
" for (const v of value) {\n" +
|
|
557
|
+
" if (v instanceof Blob || (typeof File !== 'undefined' && v instanceof File)) form.append(key, v as any);\n" +
|
|
558
|
+
" else if (typeof v === 'object') form.append(key, new Blob([JSON.stringify(v)], { type: 'application/json' }));\n" +
|
|
559
|
+
" else form.append(key, String(v));\n" +
|
|
560
|
+
" }\n" +
|
|
561
|
+
" } else if (typeof value === 'object') {\n" +
|
|
562
|
+
" form.append(key, new Blob([JSON.stringify(value)], { type: 'application/json' }));\n" +
|
|
563
|
+
" } else {\n" +
|
|
564
|
+
" form.append(key, String(value));\n" +
|
|
565
|
+
" }\n" +
|
|
566
|
+
" }\n" +
|
|
567
|
+
" }\n" +
|
|
568
|
+
" return form;\n" +
|
|
569
|
+
" }\n\n" +
|
|
570
|
+
" private async request<T>(url: string, options: RequestInit): Promise<T> {\n" +
|
|
571
|
+
" let lastError: unknown;\n" +
|
|
572
|
+
" const retries = this.config.retries ?? 0;\n" +
|
|
573
|
+
" const delayMs = this.config.retryDelay ?? 0;\n" +
|
|
574
|
+
" for (let attempt = 0; attempt <= retries; attempt++) {\n" +
|
|
575
|
+
" try {\n" +
|
|
576
|
+
" const controller = new AbortController();\n" +
|
|
577
|
+
" const timeout = this.config.timeout ?? 0;\n" +
|
|
578
|
+
" const timer = timeout > 0 ? setTimeout(() => controller.abort(), timeout) : undefined;\n" +
|
|
579
|
+
" // Link external AbortSignal if provided\n" +
|
|
580
|
+
" if (options.signal) {\n" +
|
|
581
|
+
" const ext = options.signal;\n" +
|
|
582
|
+
" if (ext.aborted) controller.abort();\n" +
|
|
583
|
+
" else ext.addEventListener('abort', () => controller.abort(), { once: true });\n" +
|
|
584
|
+
" }\n" +
|
|
585
|
+
" const { signal: _omit, ...rest } = options as any;\n" +
|
|
586
|
+
" const response = await fetch(url, { ...rest, signal: controller.signal });\n" +
|
|
587
|
+
" if (timer) clearTimeout(timer);\n" +
|
|
588
|
+
" if (!response.ok) {\n" +
|
|
589
|
+
" throw new ApiClientError('Request failed: ' + response.status + ' ' + response.statusText, response.status, response);\n" +
|
|
590
|
+
" }\n" +
|
|
591
|
+
" if (response.status === 204 || options.method === 'HEAD') {\n" +
|
|
592
|
+
" return undefined as unknown as T;\n" +
|
|
593
|
+
" }\n" +
|
|
594
|
+
" return (await response.json()) as T;\n" +
|
|
595
|
+
" } catch (err) {\n" +
|
|
596
|
+
" lastError = err;\n" +
|
|
597
|
+
" if (attempt < retries && delayMs > 0) {\n" +
|
|
598
|
+
" await new Promise((r) => setTimeout(r, delayMs));\n" +
|
|
599
|
+
" continue;\n" +
|
|
600
|
+
" }\n" +
|
|
601
|
+
" throw lastError;\n" +
|
|
602
|
+
" }\n" +
|
|
603
|
+
" }\n" +
|
|
604
|
+
" throw lastError as Error;\n" +
|
|
605
|
+
" }\n\n" +
|
|
606
|
+
" // Convenience helpers for bearer auth\n" +
|
|
607
|
+
" public setAuthToken(token: string) {\n" +
|
|
608
|
+
" this.config.headers = { ...(this.config.headers || {}), Authorization: `Bearer ${token}` };\n" +
|
|
609
|
+
" }\n" +
|
|
610
|
+
" public clearAuthToken() {\n" +
|
|
611
|
+
" if (this.config.headers) delete (this.config.headers as any)['Authorization'];\n" +
|
|
612
|
+
" }");
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Generate imports
|
|
616
|
+
*/
|
|
617
|
+
generateImports(endpoints) {
|
|
618
|
+
const imports = [];
|
|
619
|
+
// Add type imports. Endpoint types are already formatted; don't re-apply prefix/suffix.
|
|
620
|
+
const typeNames = this.getTypeNamesFromEndpoints(endpoints);
|
|
621
|
+
if (typeNames.length > 0) {
|
|
622
|
+
imports.push(`import { ${typeNames.join(", ")} } from './types';`);
|
|
623
|
+
}
|
|
624
|
+
return imports.length > 0 ? imports.join("\n") + "\n\n" : "";
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Get type names from endpoints
|
|
628
|
+
*/
|
|
629
|
+
getTypeNamesFromEndpoints(endpoints) {
|
|
630
|
+
const typeNames = new Set();
|
|
631
|
+
for (const endpoint of endpoints) {
|
|
632
|
+
// Extract type names from return type
|
|
633
|
+
const returnTypeNames = this.extractTypeNamesFromType(endpoint.returnType);
|
|
634
|
+
returnTypeNames.forEach((name) => typeNames.add(name));
|
|
635
|
+
// Extract type names from request body
|
|
636
|
+
if (endpoint.requestBody) {
|
|
637
|
+
const bodyTypeNames = this.extractTypeNamesFromType(endpoint.requestBody.type);
|
|
638
|
+
bodyTypeNames.forEach((name) => typeNames.add(name));
|
|
639
|
+
}
|
|
640
|
+
// Extract type names from parameters
|
|
641
|
+
for (const param of endpoint.parameters) {
|
|
642
|
+
const paramTypeNames = this.extractTypeNamesFromType(param.type);
|
|
643
|
+
paramTypeNames.forEach((name) => typeNames.add(name));
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return Array.from(typeNames);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Extract type names from type string
|
|
650
|
+
*/
|
|
651
|
+
extractTypeNamesFromType(type) {
|
|
652
|
+
// Capture PascalCase identifiers used as type names
|
|
653
|
+
const matches = type.match(/\b[A-Z][A-Za-z0-9_]*\b/g);
|
|
654
|
+
return matches || [];
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get dependencies from endpoints
|
|
658
|
+
*/
|
|
659
|
+
getDependencies(endpoints) {
|
|
660
|
+
return this.getTypeNamesFromEndpoints(endpoints);
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Apply configured prefix/suffix to a type name
|
|
664
|
+
*/
|
|
665
|
+
/**
|
|
666
|
+
* Normalize a type name using the same rules as BaseTypeGenerator and apply prefix/suffix
|
|
667
|
+
*/
|
|
668
|
+
formatTypeName(name, context) {
|
|
669
|
+
// Sanitize and convert to PascalCase for type identifiers
|
|
670
|
+
const sanitized = name
|
|
671
|
+
.replace(/\[/g, "<")
|
|
672
|
+
.replace(/\]/g, ">")
|
|
673
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
674
|
+
.replace(/^_+|_+$/g, "")
|
|
675
|
+
.replace(/_+/g, "_");
|
|
676
|
+
const converted = this.toPascalCase(sanitized);
|
|
677
|
+
const { typePrefix, typeSuffix } = context.config;
|
|
678
|
+
let result = converted;
|
|
679
|
+
if (typePrefix)
|
|
680
|
+
result = `${typePrefix}${result}`;
|
|
681
|
+
if (typeSuffix)
|
|
682
|
+
result = `${result}${typeSuffix}`;
|
|
683
|
+
return result;
|
|
684
|
+
}
|
|
685
|
+
toPascalCase(str) {
|
|
686
|
+
if (!str)
|
|
687
|
+
return str;
|
|
688
|
+
const tokens = str.split(/_+/).filter(Boolean);
|
|
689
|
+
return tokens.map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join("");
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get client name
|
|
693
|
+
*/
|
|
694
|
+
getClientName(schema) {
|
|
695
|
+
if (schema.info && schema.info.title) {
|
|
696
|
+
const raw = schema.info.title.trim();
|
|
697
|
+
// Tokenize on non-alphanumerics, drop empty
|
|
698
|
+
const tokens = raw
|
|
699
|
+
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
700
|
+
.trim()
|
|
701
|
+
.split(/\s+/)
|
|
702
|
+
.filter(Boolean);
|
|
703
|
+
// Avoid duplicate 'Api'
|
|
704
|
+
if (tokens.length > 1 && /^(api)$/i.test(tokens[tokens.length - 1])) {
|
|
705
|
+
tokens.pop();
|
|
706
|
+
}
|
|
707
|
+
const pascal = tokens
|
|
708
|
+
.map((t) => t.charAt(0).toUpperCase() + t.slice(1))
|
|
709
|
+
.join("");
|
|
710
|
+
return (pascal || "Api") + "ApiClient";
|
|
711
|
+
}
|
|
712
|
+
return "ApiClient";
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Generate operation ID
|
|
716
|
+
*/
|
|
717
|
+
generateOperationId(path, method, _operation) {
|
|
718
|
+
const tokens = path.split("/").filter(Boolean);
|
|
719
|
+
const nonParamSegments = tokens.filter((t) => !t.startsWith("{"));
|
|
720
|
+
const hasPathParams = tokens.some((t) => t.startsWith("{"));
|
|
721
|
+
const resource = nonParamSegments.length
|
|
722
|
+
? nonParamSegments[nonParamSegments.length - 1]
|
|
723
|
+
: path.replace(/[^a-zA-Z0-9]/g, "");
|
|
724
|
+
const singular = (name) => {
|
|
725
|
+
if (/ies$/i.test(name))
|
|
726
|
+
return name.replace(/ies$/i, "y");
|
|
727
|
+
if (/ses$/i.test(name))
|
|
728
|
+
return name.replace(/es$/i, "");
|
|
729
|
+
if (/s$/i.test(name))
|
|
730
|
+
return name.replace(/s$/i, "");
|
|
731
|
+
return name;
|
|
732
|
+
};
|
|
733
|
+
const lowerMethod = method.toLowerCase();
|
|
734
|
+
let opVerb = lowerMethod;
|
|
735
|
+
switch (lowerMethod) {
|
|
736
|
+
case "get":
|
|
737
|
+
opVerb = hasPathParams ? "get" : "list";
|
|
738
|
+
return `${opVerb}${this.capitalize(hasPathParams ? singular(resource) : resource)}`;
|
|
739
|
+
case "post":
|
|
740
|
+
opVerb = "create";
|
|
741
|
+
return `${opVerb}${this.capitalize(singular(resource))}`;
|
|
742
|
+
case "put":
|
|
743
|
+
case "patch":
|
|
744
|
+
opVerb = "update";
|
|
745
|
+
return `${opVerb}${this.capitalize(singular(resource))}`;
|
|
746
|
+
case "delete":
|
|
747
|
+
opVerb = "delete";
|
|
748
|
+
return `${opVerb}${this.capitalize(singular(resource))}`;
|
|
749
|
+
default:
|
|
750
|
+
return `${lowerMethod}${nonParamSegments
|
|
751
|
+
.map((part) => this.capitalize(part))
|
|
752
|
+
.join("")}`;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Convert to function name
|
|
757
|
+
*/
|
|
758
|
+
convertToFunctionName(operationId) {
|
|
759
|
+
// Normalize to tokens (split on non-alphanumerics)
|
|
760
|
+
const cleaned = operationId.replace(/[^a-zA-Z0-9]+/g, " ").trim();
|
|
761
|
+
const parts = cleaned.split(/\s+/).filter(Boolean);
|
|
762
|
+
if (parts.length === 0)
|
|
763
|
+
return "operation";
|
|
764
|
+
const toPascal = (tokens) => tokens.map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join("");
|
|
765
|
+
const toCamel = (tokens) => {
|
|
766
|
+
const [first, ...rest] = tokens;
|
|
767
|
+
return (first.charAt(0).toLowerCase() +
|
|
768
|
+
first.slice(1) +
|
|
769
|
+
rest.map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(""));
|
|
770
|
+
};
|
|
771
|
+
const toSnake = (tokens) => tokens.map((t) => t.toLowerCase()).join("_");
|
|
772
|
+
const convention = this.context.config.namingConvention || "camelCase";
|
|
773
|
+
let name;
|
|
774
|
+
switch (convention) {
|
|
775
|
+
case "PascalCase":
|
|
776
|
+
name = toPascal(parts);
|
|
777
|
+
break;
|
|
778
|
+
case "snake_case":
|
|
779
|
+
name = toSnake(parts);
|
|
780
|
+
break;
|
|
781
|
+
case "camelCase":
|
|
782
|
+
default:
|
|
783
|
+
name = toCamel(parts);
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
// Ensure it starts with a valid character
|
|
787
|
+
if (/^[0-9]/.test(name)) {
|
|
788
|
+
name = "op" + name;
|
|
789
|
+
}
|
|
790
|
+
return name;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Capitalize string
|
|
794
|
+
*/
|
|
795
|
+
capitalize(str) {
|
|
796
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Extract type name from reference
|
|
800
|
+
*/
|
|
801
|
+
extractTypeNameFromRef(ref) {
|
|
802
|
+
const parts = ref.split("/");
|
|
803
|
+
return parts[parts.length - 1] || "Unknown";
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Map schema type to TypeScript
|
|
807
|
+
*/
|
|
808
|
+
mapSchemaTypeToTypeScript(type, format) {
|
|
809
|
+
const types = Array.isArray(type) ? type : [type];
|
|
810
|
+
// Handle format-specific mappings
|
|
811
|
+
if (format) {
|
|
812
|
+
const formatMappings = {
|
|
813
|
+
date: "string",
|
|
814
|
+
"date-time": "string",
|
|
815
|
+
time: "string",
|
|
816
|
+
email: "string",
|
|
817
|
+
hostname: "string",
|
|
818
|
+
ipv4: "string",
|
|
819
|
+
ipv6: "string",
|
|
820
|
+
uri: "string",
|
|
821
|
+
"uri-reference": "string",
|
|
822
|
+
"uri-template": "string",
|
|
823
|
+
url: "string",
|
|
824
|
+
uuid: "string",
|
|
825
|
+
password: "string",
|
|
826
|
+
byte: "string",
|
|
827
|
+
binary: "string",
|
|
828
|
+
int32: "number",
|
|
829
|
+
int64: "number",
|
|
830
|
+
float: "number",
|
|
831
|
+
double: "number",
|
|
832
|
+
decimal: "number",
|
|
833
|
+
};
|
|
834
|
+
if (formatMappings[format]) {
|
|
835
|
+
return formatMappings[format];
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// Handle basic type mappings
|
|
839
|
+
const typeMappings = {
|
|
840
|
+
string: "string",
|
|
841
|
+
number: "number",
|
|
842
|
+
integer: "number",
|
|
843
|
+
boolean: "boolean",
|
|
844
|
+
array: "unknown[]",
|
|
845
|
+
object: "Record<string, unknown>",
|
|
846
|
+
null: "null",
|
|
847
|
+
};
|
|
848
|
+
const mappedTypes = types.map((t) => typeMappings[t] || "unknown");
|
|
849
|
+
if (mappedTypes.length === 1) {
|
|
850
|
+
return mappedTypes[0];
|
|
851
|
+
}
|
|
852
|
+
return mappedTypes.join(" | ");
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
exports.ApiClientGenerator = ApiClientGenerator;
|
|
856
|
+
//# sourceMappingURL=api-client-generator.js.map
|