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.
Files changed (131) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/LICENSE +21 -0
  3. package/README.md +827 -0
  4. package/dist/__tests__/setup.d.ts +6 -0
  5. package/dist/__tests__/setup.d.ts.map +1 -0
  6. package/dist/__tests__/setup.js +30 -0
  7. package/dist/__tests__/setup.js.map +1 -0
  8. package/dist/__tests__/test-config.d.ts +36 -0
  9. package/dist/__tests__/test-config.d.ts.map +1 -0
  10. package/dist/__tests__/test-config.js +563 -0
  11. package/dist/__tests__/test-config.js.map +1 -0
  12. package/dist/cli/index.d.ts +118 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +657 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/core/index.d.ts +5 -0
  17. package/dist/core/index.d.ts.map +1 -0
  18. package/dist/core/index.js +21 -0
  19. package/dist/core/index.js.map +1 -0
  20. package/dist/core/schemantic.d.ts +95 -0
  21. package/dist/core/schemantic.d.ts.map +1 -0
  22. package/dist/core/schemantic.js +543 -0
  23. package/dist/core/schemantic.js.map +1 -0
  24. package/dist/generators/api-client-generator.d.ts +145 -0
  25. package/dist/generators/api-client-generator.d.ts.map +1 -0
  26. package/dist/generators/api-client-generator.js +856 -0
  27. package/dist/generators/api-client-generator.js.map +1 -0
  28. package/dist/generators/base.d.ts +117 -0
  29. package/dist/generators/base.d.ts.map +1 -0
  30. package/dist/generators/base.js +232 -0
  31. package/dist/generators/base.js.map +1 -0
  32. package/dist/generators/enum-generator.d.ts +52 -0
  33. package/dist/generators/enum-generator.d.ts.map +1 -0
  34. package/dist/generators/enum-generator.js +178 -0
  35. package/dist/generators/enum-generator.js.map +1 -0
  36. package/dist/generators/factory.d.ts +59 -0
  37. package/dist/generators/factory.d.ts.map +1 -0
  38. package/dist/generators/factory.js +127 -0
  39. package/dist/generators/factory.js.map +1 -0
  40. package/dist/generators/hook-generator.d.ts +36 -0
  41. package/dist/generators/hook-generator.d.ts.map +1 -0
  42. package/dist/generators/hook-generator.js +495 -0
  43. package/dist/generators/hook-generator.js.map +1 -0
  44. package/dist/generators/index.d.ts +10 -0
  45. package/dist/generators/index.d.ts.map +1 -0
  46. package/dist/generators/index.js +26 -0
  47. package/dist/generators/index.js.map +1 -0
  48. package/dist/generators/object-generator.d.ts +48 -0
  49. package/dist/generators/object-generator.d.ts.map +1 -0
  50. package/dist/generators/object-generator.js +280 -0
  51. package/dist/generators/object-generator.js.map +1 -0
  52. package/dist/generators/primitive-generator.d.ts +40 -0
  53. package/dist/generators/primitive-generator.d.ts.map +1 -0
  54. package/dist/generators/primitive-generator.js +150 -0
  55. package/dist/generators/primitive-generator.js.map +1 -0
  56. package/dist/index.d.ts +17 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +42 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/parsers/base.d.ts +101 -0
  61. package/dist/parsers/base.d.ts.map +1 -0
  62. package/dist/parsers/base.js +77 -0
  63. package/dist/parsers/base.js.map +1 -0
  64. package/dist/parsers/factory.d.ts +64 -0
  65. package/dist/parsers/factory.d.ts.map +1 -0
  66. package/dist/parsers/factory.js +116 -0
  67. package/dist/parsers/factory.js.map +1 -0
  68. package/dist/parsers/index.d.ts +7 -0
  69. package/dist/parsers/index.d.ts.map +1 -0
  70. package/dist/parsers/index.js +23 -0
  71. package/dist/parsers/index.js.map +1 -0
  72. package/dist/parsers/openapi.d.ts +52 -0
  73. package/dist/parsers/openapi.d.ts.map +1 -0
  74. package/dist/parsers/openapi.js +396 -0
  75. package/dist/parsers/openapi.js.map +1 -0
  76. package/dist/plugins/branded-types.d.ts +28 -0
  77. package/dist/plugins/branded-types.d.ts.map +1 -0
  78. package/dist/plugins/branded-types.js +568 -0
  79. package/dist/plugins/branded-types.js.map +1 -0
  80. package/dist/plugins/builtin.d.ts +38 -0
  81. package/dist/plugins/builtin.d.ts.map +1 -0
  82. package/dist/plugins/builtin.js +283 -0
  83. package/dist/plugins/builtin.js.map +1 -0
  84. package/dist/plugins/index.d.ts +7 -0
  85. package/dist/plugins/index.d.ts.map +1 -0
  86. package/dist/plugins/index.js +23 -0
  87. package/dist/plugins/index.js.map +1 -0
  88. package/dist/plugins/loader.d.ts +92 -0
  89. package/dist/plugins/loader.d.ts.map +1 -0
  90. package/dist/plugins/loader.js +245 -0
  91. package/dist/plugins/loader.js.map +1 -0
  92. package/dist/plugins/manager.d.ts +99 -0
  93. package/dist/plugins/manager.d.ts.map +1 -0
  94. package/dist/plugins/manager.js +233 -0
  95. package/dist/plugins/manager.js.map +1 -0
  96. package/dist/plugins/performance-monitoring.d.ts +98 -0
  97. package/dist/plugins/performance-monitoring.d.ts.map +1 -0
  98. package/dist/plugins/performance-monitoring.js +596 -0
  99. package/dist/plugins/performance-monitoring.js.map +1 -0
  100. package/dist/plugins/request-deduplication.d.ts +27 -0
  101. package/dist/plugins/request-deduplication.d.ts.map +1 -0
  102. package/dist/plugins/request-deduplication.js +650 -0
  103. package/dist/plugins/request-deduplication.js.map +1 -0
  104. package/dist/plugins/zod-validation.d.ts +63 -0
  105. package/dist/plugins/zod-validation.d.ts.map +1 -0
  106. package/dist/plugins/zod-validation.js +743 -0
  107. package/dist/plugins/zod-validation.js.map +1 -0
  108. package/dist/types/core.d.ts +203 -0
  109. package/dist/types/core.d.ts.map +1 -0
  110. package/dist/types/core.js +24 -0
  111. package/dist/types/core.js.map +1 -0
  112. package/dist/types/index.d.ts +7 -0
  113. package/dist/types/index.d.ts.map +1 -0
  114. package/dist/types/index.js +23 -0
  115. package/dist/types/index.js.map +1 -0
  116. package/dist/types/openapi.d.ts +250 -0
  117. package/dist/types/openapi.d.ts.map +1 -0
  118. package/dist/types/openapi.js +8 -0
  119. package/dist/types/openapi.js.map +1 -0
  120. package/dist/types/schema.d.ts +68 -0
  121. package/dist/types/schema.d.ts.map +1 -0
  122. package/dist/types/schema.js +22 -0
  123. package/dist/types/schema.js.map +1 -0
  124. package/docs/ADVANCED_PLUGINS_IMPLEMENTATION.md +355 -0
  125. package/docs/ADVANCED_PLUGIN_DEVELOPMENT.md +309 -0
  126. package/docs/API.md +600 -0
  127. package/docs/CONFIGURATION.md +722 -0
  128. package/docs/FASTAPI_TUTORIAL.md +1497 -0
  129. package/docs/PLUGINS.md +683 -0
  130. package/docs/PLUGIN_DEVELOPMENT.md +720 -0
  131. 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