@moccona/apicodegen 0.0.3 → 0.0.4

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.
@@ -0,0 +1,1990 @@
1
+ import path from "node:path";
2
+ import { createScopedLogger } from "@moccona/logger";
3
+ import fs from "fs-extra";
4
+ import { Agent, request } from "undici";
5
+ import { NodeFlags, SyntaxKind, addSyntheticLeadingComment, createPrinter, factory } from "typescript";
6
+ import { writeFile } from "node:fs/promises";
7
+ import { format } from "prettier";
8
+ //#region src/core/config.ts
9
+ /**
10
+ * Environment variable mappings
11
+ */
12
+ const ENV_MAPPINGS = {
13
+ APICODEGEN_SPEC: "spec",
14
+ APICODEGEN_OUTPUT: "output",
15
+ APICODEGEN_BASE_URL: "baseURL",
16
+ APICODEGEN_ADAPTOR: "adaptor",
17
+ APICODEGEN_VERBOSE: "verbose",
18
+ APICODEGEN_WATCH: "watch",
19
+ APICODEGEN_TYPE_CHECK: "typeCheck"
20
+ };
21
+ /**
22
+ * Load config from environment variables
23
+ */
24
+ function loadFromEnv() {
25
+ const config = {};
26
+ for (const [envKey, configKey] of Object.entries(ENV_MAPPINGS)) {
27
+ const value = process.env[envKey];
28
+ if (value !== void 0) switch (configKey) {
29
+ case "verbose":
30
+ case "watch":
31
+ case "typeCheck":
32
+ config[configKey] = value === "true" || value === "1";
33
+ break;
34
+ case "adaptor":
35
+ config[configKey] = value;
36
+ break;
37
+ default: config[configKey] = value;
38
+ }
39
+ }
40
+ return config;
41
+ }
42
+ /**
43
+ * Load config from a file
44
+ */
45
+ async function loadFromFile(filePath) {
46
+ const ext = path.extname(filePath).toLowerCase();
47
+ try {
48
+ if (ext === ".json" || ext === ".jsonc") {
49
+ const content = await fs.readFile(filePath, "utf-8");
50
+ return JSON.parse(content);
51
+ }
52
+ if (ext === ".js" || ext === ".cjs" || ext === ".mjs") {
53
+ const mod = await import(filePath);
54
+ return mod.default || mod;
55
+ }
56
+ if (ext === ".ts") {
57
+ const content = await fs.readFile(filePath, "utf-8");
58
+ try {
59
+ return JSON.parse(content);
60
+ } catch {
61
+ const jsonMatch = content.match(/export\s+default\s+(\{.+\})/s);
62
+ if (jsonMatch) return JSON.parse(jsonMatch[1]);
63
+ }
64
+ }
65
+ const content = await fs.readFile(filePath, "utf-8");
66
+ return JSON.parse(content);
67
+ } catch (error) {
68
+ throw new Error(`Failed to load config from ${filePath}: ${error}`);
69
+ }
70
+ }
71
+ /**
72
+ * Find config file in project root
73
+ */
74
+ async function findConfigFile(cwd) {
75
+ for (const fileName of [
76
+ "apicodegen.config.json",
77
+ "apicodegen.config.js",
78
+ "apicodegen.config.mjs",
79
+ ".apicodegenrc",
80
+ ".apicodegenrc.json",
81
+ ".apicodegenrc.js",
82
+ ".apicodegenrc.mjs"
83
+ ]) {
84
+ const filePath = path.join(cwd, fileName);
85
+ if (await fs.pathExists(filePath)) return filePath;
86
+ }
87
+ const packageJsonPath = path.join(cwd, "package.json");
88
+ if (await fs.pathExists(packageJsonPath)) try {
89
+ const pkg = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
90
+ if (pkg.apicodegen && typeof pkg.apicodegen === "string") return path.resolve(cwd, pkg.apicodegen);
91
+ } catch {}
92
+ return null;
93
+ }
94
+ /**
95
+ * Merge multiple config sources with priority
96
+ * Priority: defaults < env vars < config file < CLI args
97
+ */
98
+ function mergeConfigs(base, ...sources) {
99
+ const result = { ...base };
100
+ for (const source of sources) {
101
+ if (!source) continue;
102
+ for (const [key, value] of Object.entries(source)) if (value !== void 0) result[key] = value;
103
+ }
104
+ return result;
105
+ }
106
+ /**
107
+ * Validate config has required fields
108
+ */
109
+ function validateConfig(config) {
110
+ if (!config.spec) throw new Error("Missing required field: spec (OpenAPI spec file path or URL)");
111
+ return true;
112
+ }
113
+ /**
114
+ * Load and resolve config from multiple sources
115
+ */
116
+ async function loadConfig(options = {}) {
117
+ const cwd = options.cwd || process.cwd();
118
+ const cliOptions = options.cliOptions || {};
119
+ const envConfig = loadFromEnv();
120
+ let fileConfig = {};
121
+ let configFilePath;
122
+ if (options.configFile) {
123
+ configFilePath = path.resolve(cwd, options.configFile);
124
+ fileConfig = await loadFromFile(configFilePath);
125
+ } else {
126
+ const foundPath = await findConfigFile(cwd);
127
+ if (foundPath) {
128
+ configFilePath = foundPath;
129
+ fileConfig = await loadFromFile(foundPath);
130
+ }
131
+ }
132
+ const packageJsonPath = path.join(cwd, "package.json");
133
+ let inlineConfig = {};
134
+ if (await fs.pathExists(packageJsonPath)) try {
135
+ const pkg = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
136
+ if (pkg.apicodegen && typeof pkg.apicodegen === "object") inlineConfig = pkg.apicodegen;
137
+ } catch {}
138
+ const merged = mergeConfigs({
139
+ spec: "",
140
+ output: "./output.ts"
141
+ }, envConfig, inlineConfig, fileConfig, cliOptions);
142
+ validateConfig(merged);
143
+ const name = options.name || merged.baseURL || merged.spec;
144
+ return {
145
+ ...merged,
146
+ configFilePath,
147
+ name
148
+ };
149
+ }
150
+ /**
151
+ * Convert resolved config to provider options format
152
+ */
153
+ function toProviderOptions(config) {
154
+ return {
155
+ docURL: config.spec,
156
+ output: config.output,
157
+ adaptor: config.adaptor,
158
+ baseURL: config.baseURL,
159
+ importClientSource: config.importClientSource,
160
+ verbose: config.verbose,
161
+ requestOptions: config.requestOptions
162
+ };
163
+ }
164
+ //#endregion
165
+ //#region src/core/errors.ts
166
+ /**
167
+ * Error handling utilities for api-codegen
168
+ */
169
+ const ErrorCodes = {
170
+ SPEC_NOT_FOUND: "E_SPEC_NOT_FOUND",
171
+ SPEC_FETCH_FAILED: "E_SPEC_FETCH_FAILED",
172
+ SPEC_PARSE_FAILED: "E_SPEC_PARSE_FAILED",
173
+ OUTPUT_DIR_MISSING: "E_OUTPUT_DIR_MISSING",
174
+ CONFIG_INVALID: "E_CONFIG_INVALID",
175
+ VALIDATION_FAILED: "E_VALIDATION_FAILED",
176
+ GENERATION_FAILED: "E_GENERATION_FAILED",
177
+ TYPE_CHECK_FAILED: "E_TYPE_CHECK_FAILED"
178
+ };
179
+ /**
180
+ * Custom error class for api-codegen with rich context
181
+ */
182
+ var ApicodegenError = class ApicodegenError extends Error {
183
+ code;
184
+ location;
185
+ line;
186
+ column;
187
+ path;
188
+ suggestions;
189
+ cause;
190
+ constructor(context) {
191
+ super(context.message);
192
+ this.name = "ApicodegenError";
193
+ this.code = context.code;
194
+ this.location = context.location;
195
+ this.line = context.line;
196
+ this.column = context.column;
197
+ this.path = context.path;
198
+ this.suggestions = context.suggestions || [];
199
+ this.cause = context.cause;
200
+ if (Error.captureStackTrace) Error.captureStackTrace(this, ApicodegenError);
201
+ }
202
+ /**
203
+ * Convert error to formatted string for CLI output
204
+ */
205
+ toString(verbose = false) {
206
+ const lines = [];
207
+ lines.push(`\x1b[1;31mError [${this.code}]\x1b[0m ${this.message}`);
208
+ if (this.location) lines.push(` \x1b[36m→ Location:\x1b[0m ${this.location}`);
209
+ if (this.path) lines.push(` \x1b[36m→ Path:\x1b[0m ${this.path}`);
210
+ if (this.line !== void 0) {
211
+ let lineInfo = ` \x1b[36m→ Line:\x1b[0m ${this.line}`;
212
+ if (this.column !== void 0) lineInfo += `, Column: ${this.column}`;
213
+ lines.push(lineInfo);
214
+ }
215
+ if (this.suggestions.length > 0) for (const suggestion of this.suggestions) lines.push(` \x1b[32m→ Suggestion:\x1b[0m ${suggestion}`);
216
+ if (verbose && this.cause) {
217
+ lines.push(`\n \x1b[90mOriginal Error:\x1b[0m ${this.cause.message}`);
218
+ if (this.stack) {
219
+ const stackLines = this.stack.split("\n").slice(1).join("\n");
220
+ lines.push(`\x1b[90m${stackLines}\x1b[0m`);
221
+ }
222
+ }
223
+ return lines.join("\n");
224
+ }
225
+ /**
226
+ * Convert to JSON-serializable object
227
+ */
228
+ toJSON() {
229
+ return {
230
+ name: this.name,
231
+ code: this.code,
232
+ message: this.message,
233
+ location: this.location,
234
+ line: this.line,
235
+ column: this.column,
236
+ path: this.path,
237
+ suggestions: this.suggestions,
238
+ cause: this.cause?.message
239
+ };
240
+ }
241
+ };
242
+ /**
243
+ * ANSI color codes for terminal output
244
+ */
245
+ const Colors = {
246
+ reset: "\x1B[0m",
247
+ bold: "\x1B[1m",
248
+ red: "\x1B[31m",
249
+ green: "\x1B[32m",
250
+ yellow: "\x1B[33m",
251
+ blue: "\x1B[34m",
252
+ cyan: "\x1B[36m",
253
+ gray: "\x1B[90m",
254
+ brightRed: "\x1B[91m",
255
+ brightGreen: "\x1B[92m"
256
+ };
257
+ /**
258
+ * Format error for CLI output
259
+ */
260
+ function formatError(error, verbose = false) {
261
+ if (error instanceof ApicodegenError) return error.toString(verbose);
262
+ if (error instanceof Error) return `${Colors.red}${Colors.bold}Error${Colors.reset}: ${error.message}${verbose && error.stack ? `\n\n${Colors.gray}${error.stack}${Colors.reset}` : ""}`;
263
+ return `${Colors.red}${Colors.bold}Error${Colors.reset}: ${String(error)}`;
264
+ }
265
+ /**
266
+ * Create error with common patterns
267
+ */
268
+ const createErrors = {
269
+ specNotFound(path, cause) {
270
+ return new ApicodegenError({
271
+ code: ErrorCodes.SPEC_NOT_FOUND,
272
+ message: "OpenAPI spec file not found",
273
+ location: path,
274
+ suggestions: [
275
+ "Check if the file exists using 'ls -la'",
276
+ "Use --spec to provide the correct path",
277
+ "For remote specs, ensure the URL is accessible"
278
+ ],
279
+ cause
280
+ });
281
+ },
282
+ specFetchFailed(url, statusCode, cause) {
283
+ const message = statusCode ? `Failed to fetch OpenAPI spec (HTTP ${statusCode})` : "Failed to fetch OpenAPI spec from URL";
284
+ return new ApicodegenError({
285
+ code: ErrorCodes.SPEC_FETCH_FAILED,
286
+ message,
287
+ location: url,
288
+ suggestions: [
289
+ "Check if the URL is accessible in a browser",
290
+ "Download the spec file locally and use the local path",
291
+ "Verify CORS settings if fetching from a different origin"
292
+ ],
293
+ cause
294
+ });
295
+ },
296
+ specParseFailed(path, line, column, cause) {
297
+ return new ApicodegenError({
298
+ code: ErrorCodes.SPEC_PARSE_FAILED,
299
+ message: "Failed to parse OpenAPI spec (invalid JSON or YAML)",
300
+ location: path,
301
+ line,
302
+ column,
303
+ suggestions: [
304
+ "Validate JSON syntax using jsonlint.com",
305
+ "For YAML specs, ensure proper indentation",
306
+ "Check for trailing commas or unquoted special characters"
307
+ ],
308
+ cause
309
+ });
310
+ },
311
+ outputDirMissing(path, cause) {
312
+ return new ApicodegenError({
313
+ code: ErrorCodes.OUTPUT_DIR_MISSING,
314
+ message: "Output directory does not exist",
315
+ location: path,
316
+ suggestions: ["Create the directory: mkdir -p $(dirname <output>)", "Check if the path is correct"],
317
+ cause
318
+ });
319
+ },
320
+ configInvalid(path, cause) {
321
+ return new ApicodegenError({
322
+ code: ErrorCodes.CONFIG_INVALID,
323
+ message: "Invalid configuration file",
324
+ location: path,
325
+ suggestions: ["Validate JSON syntax in the config file", "Check for required fields (spec, output)"],
326
+ cause
327
+ });
328
+ },
329
+ validationFailed(path, details, cause) {
330
+ return new ApicodegenError({
331
+ code: ErrorCodes.VALIDATION_FAILED,
332
+ message: "OpenAPI spec validation failed",
333
+ location: path,
334
+ path: details,
335
+ suggestions: [
336
+ "Check OpenAPI spec structure at the specified path",
337
+ "Ensure all required fields are present",
338
+ "Validate using swagger.io editor"
339
+ ],
340
+ cause
341
+ });
342
+ },
343
+ generationFailed(cause) {
344
+ return new ApicodegenError({
345
+ code: ErrorCodes.GENERATION_FAILED,
346
+ message: "Code generation failed",
347
+ suggestions: [
348
+ "Check for unsupported OpenAPI features",
349
+ "Ensure spec follows OpenAPI 2.0, 3.0, or 3.1 specification",
350
+ "Use --verbose for more details"
351
+ ],
352
+ cause
353
+ });
354
+ },
355
+ typeCheckFailed(path, _errors, cause) {
356
+ return new ApicodegenError({
357
+ code: ErrorCodes.TYPE_CHECK_FAILED,
358
+ message: "TypeScript type check failed",
359
+ location: path,
360
+ suggestions: [
361
+ "Review type errors above",
362
+ "Check for schema inconsistencies",
363
+ "Update generated types or fix source schema"
364
+ ],
365
+ cause
366
+ });
367
+ },
368
+ missingRequiredField(field, context) {
369
+ return new ApicodegenError({
370
+ code: ErrorCodes.VALIDATION_FAILED,
371
+ message: `Missing required field: ${field}`,
372
+ path: context,
373
+ suggestions: [`Add the '${field}' field to your configuration`]
374
+ });
375
+ }
376
+ };
377
+ /**
378
+ * Wrap unknown error in ApicodegenError if needed
379
+ */
380
+ function wrapError(error, context) {
381
+ if (error instanceof ApicodegenError) return error;
382
+ if (error instanceof Error) return new ApicodegenError({
383
+ code: context?.code || ErrorCodes.GENERATION_FAILED,
384
+ message: context?.message || error.message,
385
+ location: context?.location,
386
+ suggestions: context?.suggestions,
387
+ cause: error
388
+ });
389
+ return new ApicodegenError({
390
+ code: context?.code || ErrorCodes.GENERATION_FAILED,
391
+ message: String(error),
392
+ suggestions: context?.suggestions
393
+ });
394
+ }
395
+ /**
396
+ * Check if error is an ApicodegenError
397
+ */
398
+ function isApicodegenError(error) {
399
+ return error instanceof ApicodegenError;
400
+ }
401
+ //#endregion
402
+ //#region src/core/base/Adaptor.ts
403
+ /**
404
+ * Base adapter for tool
405
+ * This abstract class serves as the foundation for implementing adapters for different code generation tools
406
+ */
407
+ var Adapter = class {};
408
+ //#endregion
409
+ //#region src/core/constants/keywords.ts
410
+ const typescriptKeywords = new Set([
411
+ "break",
412
+ "case",
413
+ "catch",
414
+ "class",
415
+ "const",
416
+ "continue",
417
+ "debugger",
418
+ "default",
419
+ "delete",
420
+ "do",
421
+ "else",
422
+ "enum",
423
+ "export",
424
+ "extends",
425
+ "false",
426
+ "finally",
427
+ "for",
428
+ "function",
429
+ "if",
430
+ "import",
431
+ "in",
432
+ "instanceof",
433
+ "new",
434
+ "null",
435
+ "return",
436
+ "super",
437
+ "switch",
438
+ "this",
439
+ "throw",
440
+ "true",
441
+ "try",
442
+ "typeof",
443
+ "var",
444
+ "void",
445
+ "while",
446
+ "with",
447
+ "as",
448
+ "implements",
449
+ "interface",
450
+ "let",
451
+ "package",
452
+ "private",
453
+ "protected",
454
+ "public",
455
+ "static",
456
+ "yield",
457
+ "abstract",
458
+ "any",
459
+ "async",
460
+ "await",
461
+ "constructor",
462
+ "declare",
463
+ "from",
464
+ "get",
465
+ "is",
466
+ "module",
467
+ "namespace",
468
+ "never",
469
+ "require",
470
+ "set",
471
+ "type",
472
+ "unknown",
473
+ "readonly",
474
+ "of",
475
+ "asserts",
476
+ "infer",
477
+ "keyof",
478
+ "boolean",
479
+ "number",
480
+ "string",
481
+ "symbol",
482
+ "object",
483
+ "undefined",
484
+ "bigint"
485
+ ]);
486
+ //#endregion
487
+ //#region src/core/interface.ts
488
+ let MediaTypes = /* @__PURE__ */ function(MediaTypes) {
489
+ MediaTypes["JSON"] = "application/json";
490
+ MediaTypes["TEXT"] = "text";
491
+ MediaTypes["IMAGE"] = "image";
492
+ MediaTypes["AUDIO"] = "audio";
493
+ MediaTypes["VIDEO"] = "video";
494
+ return MediaTypes;
495
+ }({});
496
+ let HttpMethods = /* @__PURE__ */ function(HttpMethods) {
497
+ HttpMethods["GET"] = "get";
498
+ HttpMethods["PUT"] = "put";
499
+ HttpMethods["POST"] = "post";
500
+ HttpMethods["DELETE"] = "delete";
501
+ HttpMethods["OPTIONS"] = "options";
502
+ HttpMethods["HEAD"] = "head";
503
+ HttpMethods["PATCH"] = "patch";
504
+ HttpMethods["TRACE"] = "trace";
505
+ return HttpMethods;
506
+ }({});
507
+ //#endregion
508
+ //#region src/core/base/Base.ts
509
+ /**
510
+ * @file Base class implementation
511
+ * @author wp.l
512
+ * @description Base utility class providing common methods for code generation and API handling
513
+ */
514
+ /**
515
+ * Base abstract class providing common utility methods.
516
+ */
517
+ var Base = class Base {
518
+ constructor() {
519
+ if (new.target === Base) throw new Error("Cannot instantiate abstract class");
520
+ }
521
+ /**
522
+ * Converts a reference string to a meaningful name.
523
+ * @param ref - The reference string to process.
524
+ * @param [doc] - Optional document reference for context.
525
+ * @returns - The processed name.
526
+ */
527
+ static ref2name(ref, doc) {
528
+ const paths = ref.replace(/^#/, "").split("/").filter(Boolean);
529
+ if (!doc) return paths.slice(-1)[0];
530
+ let temporary = doc;
531
+ let lastPath = "";
532
+ for (const path of paths) {
533
+ const adjustedPath = path.replaceAll("~1", "/");
534
+ temporary = temporary[adjustedPath];
535
+ lastPath = adjustedPath;
536
+ }
537
+ if (!temporary) return "unknown";
538
+ return temporary.$ref ? Base.ref2name(temporary.$ref, doc) : lastPath;
539
+ }
540
+ /**
541
+ * Converts an API path to a function name.
542
+ * @param path - The API endpoint path.
543
+ * @param [method] - The HTTP method (e.g., GET, POST).
544
+ * @param [operationId] - Unique identifier for the operation.
545
+ * @returns - The generated function name.
546
+ */
547
+ static pathToFnName(path, method, _operationId = "") {
548
+ return Base.normalize(Base.camelCase(Base.normalize(path))) + (method ? Base.capitalize(Base.upperCamelCase(`using_${method}`)) : "");
549
+ }
550
+ /**
551
+ * Normalizes a string by replacing special characters and avoiding TypeScript keywords.
552
+ * @param text - Input text to normalize.
553
+ * @returns - The normalized string.
554
+ */
555
+ static normalize(text) {
556
+ if (typescriptKeywords.has(text)) text += "_";
557
+ return text.replace(/[/\-_{}():\s`,*<>$#.]/gm, "_").replace(/^\d./gm, "").replaceAll("...", "");
558
+ }
559
+ /**
560
+ * Capitalizes the first character of a string.
561
+ * @param text - Input string.
562
+ * @returns - Capitalized string.
563
+ */
564
+ static capitalize(text) {
565
+ text = text.trim();
566
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
567
+ }
568
+ /**
569
+ * Converts a string to camelCase.
570
+ * @param text - Input string.
571
+ * @returns - CamelCase string.
572
+ */
573
+ static camelCase(text) {
574
+ text = text.trim();
575
+ return text.split("_").filter(Boolean).map((t, index) => index === 0 ? t : Base.capitalize(t)).join("");
576
+ }
577
+ /**
578
+ * Converts a string to UpperCamelCase.
579
+ * @param text - Input string.
580
+ * @returns - UpperCamelCase string.
581
+ */
582
+ static upperCamelCase(text) {
583
+ return Base.normalize(text).replaceAll("...", "").split("_").filter(Boolean).map(Base.capitalize).join("");
584
+ }
585
+ /**
586
+ * Fetches documentation from a given URL.
587
+ * @param url - The URL to fetch the documentation from.
588
+ * @param requestInit - Additional request parameters.
589
+ * @returns - A promise resolving to the fetched documentation data.
590
+ */
591
+ static async fetchDoc(url, requestInit = {}) {
592
+ const { body, statusCode } = await request(url, {
593
+ method: "GET",
594
+ dispatcher: new Agent({ connect: { rejectUnauthorized: false } }),
595
+ ...requestInit
596
+ });
597
+ if (statusCode >= 400) throw new Error(`Failed to fetch OpenAPI documentation from ${url}: HTTP ${statusCode}`);
598
+ try {
599
+ return body.json();
600
+ } catch (error) {
601
+ throw new Error(`Failed to parse JSON response from ${url}: ${error instanceof Error ? error.message : String(error)}`);
602
+ }
603
+ }
604
+ /**
605
+ * Determines the media type from a given media type string.
606
+ * @param mediaType - The media type string to evaluate.
607
+ * @returns - The matched MediaTypes or null.
608
+ */
609
+ static getMediaType(mediaType) {
610
+ return Object.values(MediaTypes).find((type) => mediaType.includes(type));
611
+ }
612
+ /**
613
+ * Checks if a schema is a valid enum type that isn't boolean.
614
+ * @param a - The schema object to evaluate.
615
+ * @returns - True if the schema is a valid non-boolean enum.
616
+ */
617
+ static isValidEnumType(a) {
618
+ return a.type !== "boolean" && !Base.isBooleanEnum(a);
619
+ }
620
+ /**
621
+ * Checks if a schema represents a boolean enum.
622
+ * @param a - The schema object to evaluate.
623
+ * @returns - True if the schema is a boolean enum.
624
+ */
625
+ static isBooleanEnum(a) {
626
+ return a.type === "boolean" || !!a.enum?.some((member) => typeof member === "boolean");
627
+ }
628
+ /**
629
+ * Checks if two enum schemas are identical.
630
+ * @param a - First enum schema to compare.
631
+ * @param b - Second enum schema to compare.
632
+ * @returns - True if the enums are identical.
633
+ */
634
+ static isSameEnum(a, b) {
635
+ return a.enum.length === b.enum.length && a.enum.sort().every((v, index) => v === b.enum.sort()[index]);
636
+ }
637
+ /**
638
+ * Filters out duplicate enum schemas from an array.
639
+ * @param enums - Array of enum schemas to process.
640
+ * @returns - Array of unique enum schemas.
641
+ */
642
+ static uniqueEnums(enums) {
643
+ const enumMap = /* @__PURE__ */ new Map();
644
+ for (const e of enums) {
645
+ const existing = enumMap.get(e.name);
646
+ if (existing) for (const value of e.enum) existing.add(value);
647
+ else enumMap.set(e.name, new Set(e.enum));
648
+ }
649
+ return Array.from(enumMap.entries()).map(([name, values]) => ({
650
+ name,
651
+ enum: Array.from(values)
652
+ }));
653
+ }
654
+ /**
655
+ * Finds the first occurrence of a matching enum schema in an array.
656
+ * @param a - The enum schema to find.
657
+ * @param enums - Array of enum schemas to search.
658
+ * @returns - The found schema or undefined.
659
+ */
660
+ static findSameSchema(a, enums) {
661
+ return enums.find((b) => Base.isSameEnum(b, a));
662
+ }
663
+ /**
664
+ * Checks if an object is a reference object.
665
+ * @param schema - The object to check.
666
+ * @returns - True if the object is a reference.
667
+ */
668
+ static isRef(schema) {
669
+ return typeof schema === "object" && schema !== null && "$ref" in schema && typeof schema.$ref === "string";
670
+ }
671
+ };
672
+ //#endregion
673
+ //#region src/core/base/Provider.ts
674
+ /**
675
+ * Abstract Provider Class.
676
+ *
677
+ * The Provider class is designed to be extended by specific implementations (e.g., OpenAPI 2 provider, OpenAPI 3 provider).
678
+ * It handles the initialization of the provider and the parsing of documentation into structured data.
679
+ *
680
+ * @example
681
+ *
682
+ * ```ts
683
+ * /// Example of how this class might be used by a subclass:
684
+ * class OpenAPIProvider extends Provider {
685
+ * /// Implement the parse method to handle OpenAPI-specific documentation parsing.
686
+ * parse(doc: unknown): ProviderInitResult {
687
+ * /// Implementation details...
688
+ * }
689
+ * }
690
+ *
691
+ * /// Initializing a provider with configuration and documentation data:
692
+ * const initOptions: ProviderInitOptions = {
693
+ * docURL: "https://example.com/api/swagger.json",
694
+ * baseURL: "https://api.example.com",
695
+ * output: "./generated",
696
+ * requestOptions: {
697
+ * headers: { "Content-Type": "application/json" },
698
+ * },
699
+ * importClientSource: "generated/client",
700
+ * };
701
+ *
702
+ * const docData = fetchSwaggerDoc();
703
+ * const provider = new OpenAPIProvider(initOptions, docData);
704
+ * ```
705
+ */
706
+ var Provider = class {
707
+ /** collection of enum schemas */
708
+ enums = [];
709
+ /** collection of schemas indexed by name */
710
+ schemas = {};
711
+ /** collection of parameters indexed by name */
712
+ parameters = {};
713
+ /** collection of API responses indexed by name */
714
+ responses = {};
715
+ /** collection of request bodies indexed by name */
716
+ requestBodies = {};
717
+ /** collection of API endpoints (operations) indexed by path */
718
+ apis = {};
719
+ /** URL for fetching API documentation */
720
+ docURL;
721
+ /** base URL for API endpoints */
722
+ baseURL;
723
+ /** output directory for generated code */
724
+ output;
725
+ /** request options for API documentation fetch */
726
+ requestOptions;
727
+ /** source path for imported client */
728
+ importClientSource;
729
+ /**
730
+ * Provider Constructor.
731
+ * @param {ProviderInitOptions} initOptions - Initial configuration for the provider.
732
+ * @param {unknown} doc - Raw API documentation data to be parsed.
733
+ */
734
+ constructor(initOptions, doc) {
735
+ this.docURL = initOptions.docURL;
736
+ this.baseURL = initOptions.baseURL ?? "";
737
+ this.output = initOptions.output ?? ".";
738
+ this.requestOptions = initOptions.requestOptions ?? {};
739
+ this.importClientSource = initOptions.importClientSource ?? "";
740
+ const { enums, schemas, requestBodies, responses, parameters, apis } = this.parse(doc);
741
+ this.enums = enums;
742
+ this.schemas = schemas;
743
+ this.responses = responses;
744
+ this.parameters = parameters;
745
+ this.requestBodies = requestBodies;
746
+ this.apis = apis;
747
+ }
748
+ };
749
+ //#endregion
750
+ //#region src/core/generator/index.ts
751
+ var Generator = class Generator {
752
+ /**
753
+ * Converts an array of TypeScript statements into a formatted string of code.
754
+ *
755
+ * @param statements - The array of TypeScript statement nodes.
756
+ * @returns Formatted code as a string.
757
+ * @throws {Error} If no valid statements are provided.
758
+ */
759
+ static toCode(statements) {
760
+ if (statements.length === 0) return "// No api declaration found.";
761
+ const sourceFile = factory.createSourceFile(statements, factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None);
762
+ return createPrinter().printFile(sourceFile);
763
+ }
764
+ static async write(code, filepath) {
765
+ try {
766
+ await writeFile(filepath, code);
767
+ } catch (error) {
768
+ console.error(error);
769
+ }
770
+ }
771
+ /**
772
+ * Converts a path string with parameters into a TypeScript template expression.
773
+ * Handles query parameters and path placeholders.
774
+ *
775
+ * @param path - The base path string containing placeholders.
776
+ * @param parameters - Array of parameter objects defining the parameters.
777
+ * @param basePath - Optional base path to prepend (default: "").
778
+ * @returns A TypeScript template expressi
779
+ */
780
+ static toUrlTemplate(path, parameters, basePath = "") {
781
+ const queryParameters = parameters.filter((p) => p.in === "query");
782
+ if (queryParameters.length > 0) {
783
+ const queryString = queryParameters.map((qp, index) => `${index === 0 ? "?" : "&"}${encodeURIComponent(qp.name)}={${Base.camelCase(Base.normalize(qp.name))}}`).join("");
784
+ path += queryString;
785
+ }
786
+ const pathSegments = path.replaceAll("{", "${").split("$").filter(Boolean);
787
+ if (pathSegments.length === 1) return factory.createNoSubstitutionTemplateLiteral(basePath + path);
788
+ return factory.createTemplateExpression(factory.createTemplateHead(basePath + pathSegments[0]), pathSegments.slice(1).map((segment, index) => {
789
+ const match = /^{(.+)}(.+)?/gm.exec(segment);
790
+ const isLastSegment = index === pathSegments.length - 2;
791
+ if (!match) throw new Error(`Invalid path segment: ${segment}`);
792
+ return factory.createTemplateSpan(factory.createIdentifier(match[1]), !isLastSegment ? factory.createTemplateMiddle(match[2]) : factory.createTemplateTail(match[2] || ""));
793
+ }));
794
+ }
795
+ /**
796
+ * Adds synthetic comments to a TypeScript AST node.
797
+ *
798
+ * @param node - The target AST node.
799
+ * @param comments - Array of comment objects to add.
800
+ */
801
+ static addComments(node, comments) {
802
+ if (!Array.isArray(comments) || comments.filter(Boolean).length === 0) return;
803
+ const formatComment = (comment) => {
804
+ return comment.tag ? ` @${comment.tag} ${comment.comment ?? ""}` : ` ${comment.comment}`;
805
+ };
806
+ const formattedComments = "*\n" + comments.map(formatComment).join("\n").trim() + "\n";
807
+ addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, formattedComments, true);
808
+ }
809
+ /**
810
+ * Checks if a schema represents a binary type.
811
+ *
812
+ * @param schema - The schema object to check.
813
+ * @returns true if the schema is a binary type, false otherwise.
814
+ */
815
+ static isBinarySchema(schema) {
816
+ if (schema.type === "array") {
817
+ const arraySchema = schema;
818
+ return Generator.isBinarySchema(arraySchema.items);
819
+ }
820
+ const nonArraySchema = schema;
821
+ return nonArraySchema.format === "blob" || nonArraySchema.format === "binary" || nonArraySchema.type === "file";
822
+ }
823
+ static toRequestBodyTypeNode(schema) {
824
+ return factory.createParameterDeclaration(void 0, void 0, factory.createIdentifier("req"), void 0, Generator.toTypeNode(schema));
825
+ }
826
+ static toTypeNode(schema) {
827
+ const { type, ref } = schema;
828
+ if (ref) {
829
+ const identify = Base.ref2name(ref);
830
+ return factory.createTypeReferenceNode(factory.createIdentifier(identify === "unknown" ? identify : Base.upperCamelCase(identify)));
831
+ }
832
+ switch (type) {
833
+ case "array": {
834
+ const { items } = schema;
835
+ return factory.createArrayTypeNode(Generator.toTypeNode(items));
836
+ }
837
+ case "object": {
838
+ const propsCount = Object.keys(schema.properties ?? {}).length;
839
+ if (!schema.properties || propsCount === 0) return factory.createTypeReferenceNode(factory.createIdentifier("Record"), [factory.createToken(SyntaxKind.StringKeyword), factory.createToken(SyntaxKind.UnknownKeyword)]);
840
+ const props = Object.keys(schema.properties);
841
+ return factory.createTypeLiteralNode(props.map((propKey) => {
842
+ const propSchema = schema.properties[propKey];
843
+ return factory.createPropertySignature(void 0, factory.createStringLiteral(propKey), schema.required || schema.ref || Generator.isBinarySchema(schema) ? void 0 : factory.createToken(SyntaxKind.QuestionToken), Generator.toTypeNode(propSchema));
844
+ }));
845
+ }
846
+ case "integer":
847
+ case "number":
848
+ if (schema.enum) return factory.createUnionTypeNode(schema.enum.map((e) => factory.createLiteralTypeNode(factory.createNumericLiteral(e))));
849
+ return factory.createToken(SyntaxKind.NumberKeyword);
850
+ case "boolean": return factory.createToken(SyntaxKind.BooleanKeyword);
851
+ case "file": return factory.createTypeReferenceNode(factory.createIdentifier("Blob"));
852
+ default: {
853
+ const { format, oneOf, allOf, anyOf, type, enum: enum_ } = schema;
854
+ switch (format) {
855
+ case "number": return factory.createToken(SyntaxKind.NumberKeyword);
856
+ case "string": return factory.createToken(SyntaxKind.StringKeyword);
857
+ case "boolean": return factory.createToken(SyntaxKind.BooleanKeyword);
858
+ case "blob":
859
+ case "binary": return factory.createTypeReferenceNode(factory.createIdentifier("Blob"));
860
+ default:
861
+ }
862
+ if (enum_) return factory.createUnionTypeNode(enum_.map((e) => factory.createLiteralTypeNode(factory.createStringLiteral(e))));
863
+ if (type === "string") return factory.createToken(SyntaxKind.StringKeyword);
864
+ if (oneOf) return factory.createUnionTypeNode(oneOf.map((schema) => Generator.toTypeNode(schema)));
865
+ if (anyOf) return factory.createUnionTypeNode(anyOf.map((schema) => Generator.toTypeNode(schema)));
866
+ if (allOf) return factory.createIntersectionTypeNode(allOf.map((schema) => Generator.toTypeNode(schema)));
867
+ if (type && typeof type === "string") return factory.createTypeReferenceNode(type !== "unknown" && type !== "null" ? factory.createIdentifier(Base.upperCamelCase(type)) : type);
868
+ }
869
+ }
870
+ return factory.createToken(SyntaxKind.UnknownKeyword);
871
+ }
872
+ static toDeclarationNodes(parameters) {
873
+ const objectElements = [];
874
+ const typeObjectElements = [];
875
+ const refParameters = [];
876
+ for (const parameter of parameters) if (parameter.ref) {
877
+ const refName = Base.ref2name(parameter.ref);
878
+ refParameters.push(factory.createParameterDeclaration(void 0, void 0, factory.createIdentifier(Base.camelCase(Base.normalize(refName))), void 0, factory.createTypeReferenceNode(factory.createIdentifier(Base.upperCamelCase(Base.normalize(refName)))), void 0));
879
+ } else {
880
+ const { name, schema, required } = parameter;
881
+ objectElements.push(factory.createBindingElement(void 0, void 0, factory.createIdentifier(Base.camelCase(Base.normalize(name)))));
882
+ typeObjectElements.push(factory.createPropertySignature([], factory.createIdentifier(Base.camelCase(Base.normalize(name))), required ? void 0 : factory.createToken(SyntaxKind.QuestionToken), !schema ? factory.createToken(SyntaxKind.UnknownKeyword) : Generator.toTypeNode(schema)));
883
+ }
884
+ if (objectElements.length > 0) return [factory.createParameterDeclaration(void 0, void 0, factory.createObjectBindingPattern(objectElements), void 0, factory.createTypeLiteralNode(typeObjectElements), void 0), ...refParameters];
885
+ return refParameters;
886
+ }
887
+ static toFormDataStatement(parameters, requestBody) {
888
+ const statements = [];
889
+ const fdDeclaration = factory.createVariableStatement(void 0, factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("fd"), void 0, void 0, factory.createNewExpression(factory.createIdentifier("FormData"), void 0, []))], NodeFlags.Const));
890
+ statements.push(fdDeclaration);
891
+ parameters.forEach((parameter) => {
892
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier(parameter.name), factory.createToken(SyntaxKind.AmpersandAmpersandToken), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("fd"), factory.createIdentifier("append")), void 0, [factory.createStringLiteral(parameter.name), factory.createIdentifier(parameter.name)]))));
893
+ });
894
+ if (requestBody && requestBody.type === "object" && requestBody.properties && Object.keys(requestBody.properties).length !== 0) Object.keys(requestBody.properties).forEach((key) => {
895
+ const schemaByKey = requestBody.properties[key];
896
+ if (schemaByKey.type === "array" && Generator.isBinarySchema(schemaByKey)) statements.push(factory.createForOfStatement(void 0, factory.createVariableDeclarationList([factory.createVariableDeclaration("file")], NodeFlags.Const), factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key)), factory.createBlock([factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("fd"), factory.createIdentifier("append")), [], [
897
+ factory.createStringLiteral(key),
898
+ factory.createIdentifier("file"),
899
+ factory.createPropertyAccessExpression(factory.createAsExpression(factory.createIdentifier("file"), factory.createTypeReferenceNode(factory.createIdentifier("File"), void 0)), factory.createIdentifier("name"))
900
+ ]))])));
901
+ else if (schemaByKey.required) statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("fd"), factory.createIdentifier("append")), void 0, [factory.createStringLiteral(key), schemaByKey.type === "string" ? factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key)) : factory.createCallExpression(factory.createIdentifier("String"), void 0, [factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key))])])));
902
+ else statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key)), factory.createToken(SyntaxKind.AmpersandAmpersandToken), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("fd"), factory.createIdentifier("append")), void 0, [factory.createStringLiteral(key), schemaByKey.type === "string" ? factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key)) : factory.createCallExpression(factory.createIdentifier("String"), void 0, [factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key))])]))));
903
+ });
904
+ return statements;
905
+ }
906
+ static bodyBlock(uri, method, parameters, requestBody, response, adapter) {
907
+ const isFormDataRequest = requestBody && ["multipart/form-data", "application/x-www-form-urlencoded"].includes(requestBody.type);
908
+ const shouldParseResponseToJSON = "application/json" === response?.type;
909
+ const isRequestBodyBinary = requestBody?.schema && requestBody.schema.type === "array" && Generator.isBinarySchema(requestBody.schema);
910
+ const parametersShouldPutInFormData = parameters.filter((p) => p.in === "formData" || p.schema && Generator.isBinarySchema(p.schema));
911
+ const parametersShouldNotPutInFormData = parameters.filter((p) => !parametersShouldPutInFormData.includes(p));
912
+ const isRequestBodyContainsBinary = requestBody?.schema && "properties" in requestBody.schema && Object.values(requestBody.schema?.properties ?? {}).some((p) => Generator.isBinarySchema(p));
913
+ const hasBinaryInParameters = parameters.some((p) => p?.schema && Generator.isBinarySchema(p.schema));
914
+ const shouldPutParametersOrBodyInFormData = isFormDataRequest || isRequestBodyBinary || hasBinaryInParameters || isRequestBodyContainsBinary || parametersShouldPutInFormData.length > 0;
915
+ return factory.createBlock([...shouldPutParametersOrBodyInFormData ? Generator.toFormDataStatement(parametersShouldPutInFormData, requestBody?.schema) : [], ...adapter.client(uri, method, parametersShouldNotPutInFormData, requestBody, response, adapter, shouldPutParametersOrBodyInFormData, shouldParseResponseToJSON)]);
916
+ }
917
+ static schemaToStatemets(parsedDoc, adaptor, options) {
918
+ const statements = [];
919
+ const { apis, schemas = {}, enums } = parsedDoc;
920
+ const enumNames = [];
921
+ for (const enumObject of enums) {
922
+ enumNames.push(Base.upperCamelCase(enumObject.name));
923
+ statements.push(factory.createEnumDeclaration([factory.createToken(SyntaxKind.ExportKeyword)], factory.createIdentifier(Base.upperCamelCase(enumObject.name)), enumObject.enum.map((member) => {
924
+ return factory.createEnumMember(factory.createStringLiteral(typeof member === "string" ? member : `${member}_`), typeof member === "string" ? factory.createStringLiteral(member) : factory.createNumericLiteral(member));
925
+ })));
926
+ }
927
+ for (const schemaKey in schemas) if (Object.hasOwn(schemas, schemaKey) && !enumNames.includes(Base.upperCamelCase(schemaKey))) {
928
+ const schema = schemas[schemaKey];
929
+ statements.push(factory.createTypeAliasDeclaration([factory.createModifier(SyntaxKind.ExportKeyword)], factory.createIdentifier(Base.upperCamelCase(schemaKey)), void 0, Generator.toTypeNode(schema)));
930
+ }
931
+ for (const uri in apis) {
932
+ const operations = apis[uri];
933
+ for (const operation of operations) {
934
+ const { method, operationId, requestBody = [], responses = [], summary, deprecated, description } = operation;
935
+ let { parameters = [] } = operation;
936
+ parameters = parameters.filter((p) => p.in !== "cookie");
937
+ if (requestBody.length === 0) requestBody.push({ type: "application/json" });
938
+ const shouldAddExtraMethodNameSuffix = requestBody.length > 1;
939
+ for (const req of requestBody) {
940
+ const statement = factory.createFunctionDeclaration([factory.createModifier(SyntaxKind.ExportKeyword), factory.createModifier(SyntaxKind.AsyncKeyword)], void 0, Base.pathToFnName(uri, method, operationId) + (shouldAddExtraMethodNameSuffix ? Base.capitalize(req.type.split("/")[1]) : ""), void 0, [...parameters.length > 0 ? Generator.toDeclarationNodes(parameters) : [], ...req?.schema ? [Generator.toRequestBodyTypeNode(req.schema)] : []].filter(Boolean), void 0, Generator.bodyBlock(options.baseURL + uri, method, parameters, req, responses[0], adaptor));
941
+ Generator.addComments(statement, [
942
+ description && { comment: description },
943
+ summary && { comment: summary },
944
+ deprecated && { tag: "deprecated" }
945
+ ].filter(Boolean));
946
+ statements.push(statement);
947
+ }
948
+ }
949
+ }
950
+ return statements;
951
+ }
952
+ static async prettier(code) {
953
+ return await format(code, { parser: "typescript" });
954
+ }
955
+ static async genCode(schema, initOptions, adaptor) {
956
+ const { importClientSource } = initOptions;
957
+ const statements = Generator.schemaToStatemets(schema, adaptor, { baseURL: initOptions.baseURL ?? "" });
958
+ let code = Generator.toCode(statements);
959
+ if (importClientSource) code = importClientSource + "\n\n" + code;
960
+ return await Generator.prettier(code);
961
+ }
962
+ };
963
+ //#endregion
964
+ //#region src/core/client/axios.ts
965
+ /**
966
+ * Adapter class implementing support for generating code that makes use of the Axios HTTP client library.
967
+ * This class defines custom behavior and field mappings specific to the Axios client.
968
+ */
969
+ var AxiosAdapter = class extends Adapter {
970
+ /**
971
+ * Name of the field used to specify the HTTP method in the request configuration.
972
+ */
973
+ methodFieldName = "method";
974
+ /**
975
+ * Name of the field used to specify the request body (data) in the request configuration.
976
+ */
977
+ bodyFieldName = "data";
978
+ /**
979
+ * Name of the field used to specify the request headers in the request configuration.
980
+ */
981
+ headersFieldName = "headers";
982
+ /**
983
+ * Name of the field used to specify the query parameters in the request configuration.
984
+ */
985
+ queryFieldName = "params";
986
+ /**
987
+ * The name of the client this adapter is configured for, which is 'axios' in this case.
988
+ */
989
+ name = "axios";
990
+ /**
991
+ * Method that should generate and return the client-specific configuration statements.
992
+ *
993
+ * @returns {Statement[]} An array of TypeScript statements that define the client configuration.
994
+ *
995
+ * @throws {Error} Indicates that the method is not yet implemented and needs to be filled in.
996
+ */
997
+ client(uri, method, parameters, requestBody, response, adapter, shouldUseFormData) {
998
+ const statements = [];
999
+ const inBody = parameters.filter((p) => !p.in || p.in === "body");
1000
+ const inHeader = parameters.filter((p) => p.in === "header");
1001
+ /**
1002
+ * Creates the literal object expression for fetch options
1003
+ * including method, headers, and body.
1004
+ * @returns - The constructed fetch options object
1005
+ */
1006
+ const toLiterlExpression = () => {
1007
+ return factory.createObjectLiteralExpression([factory.createPropertyAssignment(factory.createIdentifier(adapter.methodFieldName), factory.createStringLiteral(method.toUpperCase()))].concat(inHeader.length > 0 ? factory.createPropertyAssignment(factory.createIdentifier(adapter.headersFieldName), factory.createObjectLiteralExpression(inHeader.map((p) => factory.createPropertyAssignment(factory.createStringLiteral(p.name), factory.createCallExpression(factory.createIdentifier("encodeURIComponent"), void 0, [factory.createCallExpression(factory.createIdentifier("String"), void 0, [factory.createIdentifier(Base.camelCase(Base.normalize(p.name)))])]))))) : []).concat(shouldUseFormData || inBody.length > 0 || requestBody?.schema ? factory.createPropertyAssignment(factory.createIdentifier(adapter.bodyFieldName), shouldUseFormData ? factory.createIdentifier("fd") : inBody.length > 0 || requestBody?.schema && !Generator.isBinarySchema(requestBody.schema) ? factory.createIdentifier("req") : factory.createIdentifier("req")) : []), true);
1008
+ };
1009
+ statements.push(factory.createReturnStatement(factory.createCallExpression(factory.createIdentifier(adapter.name), response?.schema ? [Generator.toTypeNode(response.schema)] : void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()])));
1010
+ return statements;
1011
+ }
1012
+ };
1013
+ //#endregion
1014
+ //#region src/core/client/fetch.ts
1015
+ /**
1016
+ * FetchAdapter is an adapter class that generates client-side fetch requests.
1017
+ * It handles parameters, headers, and request bodies to construct proper fetch calls.
1018
+ */
1019
+ var FetchAdapter = class extends Adapter {
1020
+ methodFieldName = "method";
1021
+ bodyFieldName = "body";
1022
+ headersFieldName = "headers";
1023
+ queryFieldName = "";
1024
+ name = "fetch";
1025
+ /**
1026
+ * Generates client code for making API requests using the Fetch API.
1027
+ * @param uri - The API endpoint URI
1028
+ * @param method - The HTTP method (GET, POST, etc.)
1029
+ * @param parameters - Array of parameters to include in the request
1030
+ * @param requestBody - The request body media type definition
1031
+ * @param response - The response media type definition
1032
+ * @param adapter - The adapter instance
1033
+ * @param shouldUseFormData - Flag to use FormData for the request body
1034
+ * @param shouldUseJSONResponse - Flag to use JSON parsing for the response
1035
+ * @return - An array of generated TypeScript statements
1036
+ */
1037
+ client(uri, method, parameters, requestBody, response, adapter, shouldUseFormData, shouldUseJSONResponse) {
1038
+ const statements = [];
1039
+ const inBody = parameters.filter((p) => !p.in || p.in === "body");
1040
+ const inHeader = parameters.filter((p) => p.in === "header");
1041
+ /**
1042
+ * Creates the literal object expression for fetch options
1043
+ * including method, headers, and body.
1044
+ * @returns - The constructed fetch options object
1045
+ */
1046
+ const toLiterlExpression = () => {
1047
+ return factory.createObjectLiteralExpression([factory.createPropertyAssignment(factory.createIdentifier(adapter.methodFieldName), factory.createStringLiteral(method.toUpperCase()))].concat(inHeader.length > 0 ? factory.createPropertyAssignment(factory.createIdentifier(adapter.headersFieldName), factory.createObjectLiteralExpression(inHeader.map((p) => factory.createPropertyAssignment(factory.createStringLiteral(p.name), factory.createCallExpression(factory.createIdentifier("encodeURIComponent"), void 0, [factory.createCallExpression(factory.createIdentifier("String"), void 0, [factory.createIdentifier(Base.camelCase(Base.normalize(p.name)))])]))))) : []).concat(shouldUseFormData || inBody.length > 0 || requestBody?.schema ? factory.createPropertyAssignment(factory.createIdentifier(adapter.bodyFieldName), shouldUseFormData ? factory.createIdentifier("fd") : inBody.length > 0 || requestBody?.schema && !Generator.isBinarySchema(requestBody.schema) ? factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("JSON"), factory.createIdentifier("stringify")), [], [requestBody ? factory.createIdentifier("req") : factory.createObjectLiteralExpression(inBody.map((b) => factory.createShorthandPropertyAssignment(factory.createIdentifier(b.name))), true)]) : factory.createIdentifier("req")) : []), true);
1048
+ };
1049
+ statements.push(factory.createReturnStatement(shouldUseJSONResponse ? factory.createCallExpression(factory.createPropertyAccessExpression(factory.createCallExpression(factory.createIdentifier(adapter.name), void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()]), factory.createIdentifier("then")), void 0, [factory.createArrowFunction([factory.createModifier(SyntaxKind.AsyncKeyword)], [], [factory.createParameterDeclaration(void 0, void 0, factory.createIdentifier("response"))], void 0, factory.createToken(SyntaxKind.EqualsGreaterThanToken), response?.schema ? factory.createAsExpression(factory.createParenthesizedExpression(factory.createAwaitExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("response"), factory.createIdentifier("json")), void 0, []))), response?.schema ? Generator.toTypeNode(response.schema) : factory.createToken(SyntaxKind.UnknownKeyword)) : factory.createParenthesizedExpression(factory.createAwaitExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("response"), factory.createIdentifier("json")), void 0, []))))]) : factory.createCallExpression(factory.createIdentifier(adapter.name), void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()])));
1050
+ return statements;
1051
+ }
1052
+ };
1053
+ //#endregion
1054
+ //#region src/openapi/V2.ts
1055
+ var V2 = class {
1056
+ doc;
1057
+ constructor(doc) {
1058
+ this.doc = doc;
1059
+ }
1060
+ /**
1061
+ * Resolves a path $ref to the actual path object.
1062
+ */
1063
+ resolvePathRef($ref) {
1064
+ const refName = Base.ref2name($ref, this.doc);
1065
+ return this.doc.paths?.[refName];
1066
+ }
1067
+ /**
1068
+ * Is array schema.
1069
+ */
1070
+ isOpenAPIArraySchema(schema) {
1071
+ return typeof schema === "object" && schema.type === "array";
1072
+ }
1073
+ /**
1074
+ * OpenAPI schema to base schema.
1075
+ */
1076
+ getSchemaByRef(schema, reserveRef = false, enums = [], upLevelSchemaKey = "") {
1077
+ let refName = "";
1078
+ if (Base.isRef(schema)) {
1079
+ refName = Base.upperCamelCase(Base.ref2name(schema.$ref));
1080
+ if (reserveRef) return { type: upLevelSchemaKey + refName };
1081
+ if (!this.doc.definitions) this.doc.definitions = {};
1082
+ schema = this.doc.definitions[Base.ref2name(schema.$ref, this.doc)];
1083
+ }
1084
+ return this.toBaseSchema(schema, enums, "", upLevelSchemaKey + refName);
1085
+ }
1086
+ /**
1087
+ * Transform all OpenAPI schema to Base Schema
1088
+ */
1089
+ toBaseSchema(schema, enums = [], schemaKey = "", upLevelSchemaKey = "") {
1090
+ if (!schema) return { type: "unknown" };
1091
+ if (Base.isRef(schema)) return this.getSchemaByRef(schema, true);
1092
+ if (this.isOpenAPIArraySchema(schema)) {
1093
+ const { type, description, items, required } = schema;
1094
+ return {
1095
+ type,
1096
+ required: !!required,
1097
+ description,
1098
+ items: this.toBaseSchema(items, enums, schemaKey, upLevelSchemaKey)
1099
+ };
1100
+ } else {
1101
+ const { required = [], allOf, anyOf, description, enum: enum_, format, oneOf, properties = {} } = schema;
1102
+ let { type } = schema;
1103
+ if (enum_ && type !== "boolean") {
1104
+ const enumObject = {
1105
+ name: Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(schemaKey)),
1106
+ enum: [...new Set(enum_)]
1107
+ };
1108
+ const sameObject = Base.findSameSchema(enumObject, enums);
1109
+ if (!sameObject && Base.isValidEnumType(schema)) enums.push(enumObject);
1110
+ return {
1111
+ type: sameObject ? sameObject.name : Base.isBooleanEnum(schema) ? "boolean" : enumObject.name,
1112
+ required,
1113
+ description
1114
+ };
1115
+ }
1116
+ if (type === void 0 && Object.keys(properties).length > 0) type = "object";
1117
+ return {
1118
+ type,
1119
+ required,
1120
+ description,
1121
+ enum: enum_,
1122
+ format,
1123
+ allOf: allOf?.map((s) => Base.isRef(s) ? {
1124
+ ...s,
1125
+ ref: s.$ref,
1126
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1127
+ } : this.toBaseSchema(s, enums)),
1128
+ anyOf: anyOf?.map((s) => Base.isRef(s) ? {
1129
+ ...s,
1130
+ ref: s.$ref,
1131
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1132
+ } : this.toBaseSchema(s, enums)),
1133
+ oneOf: oneOf?.map((s) => Base.isRef(s) ? {
1134
+ ...s,
1135
+ ref: s.$ref,
1136
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1137
+ } : this.toBaseSchema(s, enums)),
1138
+ properties: Object.keys(properties).reduce((acc, p) => {
1139
+ const propSchema = properties[p];
1140
+ return {
1141
+ ...acc,
1142
+ [p]: Base.isRef(propSchema) ? { type: Base.capitalize(Base.ref2name(propSchema.$ref, this.doc)) } : this.toBaseSchema(propSchema, enums, p, upLevelSchemaKey)
1143
+ };
1144
+ }, {})
1145
+ };
1146
+ }
1147
+ }
1148
+ /**
1149
+ * OpenAPI parameter to base parameter.
1150
+ */
1151
+ getParameterByRef(parameter, enums = [], upLevelSchemaKey = "") {
1152
+ if (Base.isRef(parameter)) {
1153
+ const refName = Base.ref2name(parameter.$ref, this.doc);
1154
+ const resolved = this.doc.parameters?.[refName];
1155
+ if (!resolved) throw new Error(`Parameter reference not found: ${parameter.$ref}`);
1156
+ parameter = resolved;
1157
+ }
1158
+ const p = parameter;
1159
+ const { name, required, description, type, items, enum: enum_, properties, schema } = p;
1160
+ if (enum_) {
1161
+ const type = Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(name));
1162
+ const enumSchema = {
1163
+ name: type,
1164
+ enum: [...new Set(enum_)]
1165
+ };
1166
+ const sameEnum = Base.findSameSchema(enumSchema, enums);
1167
+ if (!sameEnum && Base.isValidEnumType({
1168
+ type,
1169
+ enum: enum_
1170
+ })) enums.push(enumSchema);
1171
+ return {
1172
+ name,
1173
+ required,
1174
+ description,
1175
+ in: p.in,
1176
+ schema: { type: sameEnum?.name ?? type }
1177
+ };
1178
+ }
1179
+ if (items) return {
1180
+ name,
1181
+ required,
1182
+ description,
1183
+ in: p.in,
1184
+ schema: {
1185
+ type,
1186
+ items
1187
+ }
1188
+ };
1189
+ if (schema && Base.isRef(schema)) return {
1190
+ name,
1191
+ required,
1192
+ description,
1193
+ in: p.in,
1194
+ schema: { type: Base.capitalize(Base.ref2name(schema.$ref)) }
1195
+ };
1196
+ return {
1197
+ name,
1198
+ required,
1199
+ description,
1200
+ in: p.in,
1201
+ schema: {
1202
+ type,
1203
+ properties
1204
+ }
1205
+ };
1206
+ }
1207
+ /**
1208
+ * OpenAPI schema to base response
1209
+ */
1210
+ getResponseByRef(schema) {
1211
+ if (Base.isRef(schema)) schema = this.doc.responses[Base.ref2name(schema.$ref, this.doc)];
1212
+ const { schema: responseSchema } = schema;
1213
+ return [{
1214
+ type: "application/json",
1215
+ schema: responseSchema && this.getSchemaByRef(responseSchema, true)
1216
+ }];
1217
+ }
1218
+ init() {
1219
+ const { definitions = {}, responses = {}, paths = {} } = this.doc;
1220
+ const enums = [];
1221
+ const definitions_ = Object.keys(definitions).reduce((acc, key) => {
1222
+ const schema = definitions[key];
1223
+ return {
1224
+ ...acc,
1225
+ [key]: this.getSchemaByRef(schema, false, enums, key)
1226
+ };
1227
+ }, {});
1228
+ const responses_ = Object.keys(responses).reduce((acc, key) => {
1229
+ const response = responses[key];
1230
+ return {
1231
+ ...acc,
1232
+ [key]: this.getResponseByRef(response)
1233
+ };
1234
+ }, {});
1235
+ const apis = Object.keys(paths).reduce((acc, path) => {
1236
+ let pathObject = paths[path] ?? {};
1237
+ if (pathObject.$ref) {
1238
+ const resolved = this.resolvePathRef(pathObject.$ref);
1239
+ if (resolved) pathObject = resolved;
1240
+ }
1241
+ const { parameters = [] } = pathObject;
1242
+ const methodApis = [];
1243
+ Object.values(HttpMethods).forEach((method) => {
1244
+ const methodObject = pathObject[method];
1245
+ if (methodObject) {
1246
+ const { deprecated, operationId, summary: summary_, description: description_, responses = {} } = methodObject;
1247
+ const { parameters: parameters_ = [] } = methodObject;
1248
+ const baseParameters = [...parameters, ...parameters_].map((parameter) => this.getParameterByRef(parameter, enums));
1249
+ const uniqueParameterName = [...new Set(baseParameters.map((p) => p.name))];
1250
+ if (Object.keys(responses).length === 0) Object.assign(responses, { 200: { description: "Successful response" } });
1251
+ const inBody = baseParameters.filter((p) => p.in === "body" || p.in === "formData");
1252
+ const notInBody = baseParameters.filter((p) => p.in !== "body" && p.in !== "formData");
1253
+ const httpCodes = Object.keys(responses);
1254
+ for (const code of httpCodes) if (code in responses) {
1255
+ const response = responses[code];
1256
+ const responseSchema = this.getResponseByRef(response);
1257
+ const inBodyOnlyHasBody = inBody && inBody.length === 1 && inBody[0].in === "body" && inBody[0].name === "body";
1258
+ methodApis.push({
1259
+ method,
1260
+ operationId,
1261
+ summary: summary_,
1262
+ deprecated,
1263
+ description: description_,
1264
+ parameters: uniqueParameterName.map((name) => notInBody.find((p) => p.name === name)).filter(Boolean),
1265
+ responses: responseSchema,
1266
+ requestBody: inBody.length > 0 ? inBodyOnlyHasBody ? [{
1267
+ type: "application/json",
1268
+ schema: inBody[0].schema
1269
+ }] : [{
1270
+ type: "application/json",
1271
+ schema: {
1272
+ type: "object",
1273
+ properties: inBody.reduce((a, p) => {
1274
+ return {
1275
+ ...a,
1276
+ [p.name]: {
1277
+ type: p.schema?.type ?? "unknown",
1278
+ required: p.schema?.required,
1279
+ items: p.schema?.items,
1280
+ description: p.schema?.description
1281
+ }
1282
+ };
1283
+ }, {})
1284
+ }
1285
+ }] : void 0
1286
+ });
1287
+ break;
1288
+ }
1289
+ }
1290
+ });
1291
+ return {
1292
+ ...acc,
1293
+ [path]: methodApis
1294
+ };
1295
+ }, {});
1296
+ return {
1297
+ enums: Base.uniqueEnums(enums),
1298
+ schemas: definitions_,
1299
+ responses: responses_,
1300
+ parameters: {},
1301
+ requestBodies: {},
1302
+ apis
1303
+ };
1304
+ }
1305
+ };
1306
+ //#endregion
1307
+ //#region src/openapi/V3.ts
1308
+ var V3 = class {
1309
+ doc;
1310
+ constructor(doc) {
1311
+ this.doc = doc;
1312
+ }
1313
+ /**
1314
+ * Resolves a path $ref to the actual path object.
1315
+ */
1316
+ resolvePathRef($ref) {
1317
+ const refName = Base.ref2name($ref, this.doc);
1318
+ return this.doc.paths?.[refName];
1319
+ }
1320
+ /**
1321
+ * Is array schema.
1322
+ */
1323
+ isOpenAPIArraySchema(schema) {
1324
+ return typeof schema === "object" && schema.type === "array";
1325
+ }
1326
+ /**
1327
+ * OpenAPI schema to base schema.
1328
+ */
1329
+ getSchemaByRef(schema, reserveRef = false, enums = [], upLevelSchemaKey = "") {
1330
+ let refName = "";
1331
+ if (Base.isRef(schema)) {
1332
+ refName = Base.capitalize(Base.ref2name(schema.$ref));
1333
+ if (reserveRef) return { type: upLevelSchemaKey + refName };
1334
+ const resolvedSchema = this.doc.components?.schemas?.[Base.ref2name(schema.$ref, this.doc)];
1335
+ if (!resolvedSchema) return { type: "unknown" };
1336
+ schema = resolvedSchema;
1337
+ }
1338
+ return this.toBaseSchema(schema, enums, "", upLevelSchemaKey + refName);
1339
+ }
1340
+ /**
1341
+ * OpenAPI parameter to base parameter.
1342
+ */
1343
+ getParameterByRef(schema, enums = [], upLevelSchemaKey = "") {
1344
+ if (Base.isRef(schema)) {
1345
+ const resolvedSchema = this.doc.components?.parameters?.[Base.ref2name(schema.$ref, this.doc)];
1346
+ if (!resolvedSchema) return {
1347
+ name: "unknown",
1348
+ in: "query"
1349
+ };
1350
+ schema = resolvedSchema;
1351
+ }
1352
+ const { name, required, deprecated, description, schema: parameterSchema } = schema;
1353
+ if (parameterSchema && !Base.isRef(parameterSchema) && parameterSchema.enum) {
1354
+ const type = Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(name));
1355
+ const enumSchema = {
1356
+ name: type,
1357
+ enum: [...new Set(parameterSchema.enum)]
1358
+ };
1359
+ const sameEnum = Base.findSameSchema(enumSchema, enums);
1360
+ if (!sameEnum && Base.isValidEnumType(parameterSchema)) enums.push(enumSchema);
1361
+ return {
1362
+ name,
1363
+ required,
1364
+ description,
1365
+ deprecated,
1366
+ in: schema.in,
1367
+ schema: { type: sameEnum?.name ?? type }
1368
+ };
1369
+ }
1370
+ return {
1371
+ name,
1372
+ required,
1373
+ description,
1374
+ deprecated,
1375
+ in: schema.in,
1376
+ schema: schema.schema && this.getSchemaByRef(schema.schema, false, enums, upLevelSchemaKey + Base.capitalize(name))
1377
+ };
1378
+ }
1379
+ /**
1380
+ * OpenAPI schema to base response
1381
+ */
1382
+ getResponseByRef(schema) {
1383
+ if (Base.isRef(schema)) {
1384
+ const resolvedSchema = this.doc.components?.responses?.[Base.ref2name(schema.$ref, this.doc)];
1385
+ if (!resolvedSchema) return [];
1386
+ schema = resolvedSchema;
1387
+ }
1388
+ const { content = {} } = schema;
1389
+ return Object.keys(content).map((c) => ({
1390
+ type: c,
1391
+ schema: content[c].schema && this.getSchemaByRef(content[c].schema, true)
1392
+ }));
1393
+ }
1394
+ /**
1395
+ * OpenAPI schema to requestBody.
1396
+ */
1397
+ getRequestBodyByRef(schema, enums = []) {
1398
+ if (Base.isRef(schema)) {
1399
+ const resolvedSchema = this.doc.components?.requestBodies?.[Base.ref2name(schema.$ref, this.doc)];
1400
+ if (!resolvedSchema) return [];
1401
+ schema = resolvedSchema;
1402
+ }
1403
+ const { content = {} } = schema;
1404
+ return Object.keys(content).map((c) => ({
1405
+ type: c,
1406
+ schema: content[c].schema && this.getSchemaByRef(content[c].schema, false, enums)
1407
+ }));
1408
+ }
1409
+ /**
1410
+ * Transform all OpenAPI schema to Base Schema
1411
+ */
1412
+ toBaseSchema(schema, enums = [], schemaKey = "", upLevelSchemaKey = "") {
1413
+ if (!schema) return { type: "unknown" };
1414
+ if (Base.isRef(schema)) return this.getSchemaByRef(schema, true);
1415
+ if (this.isOpenAPIArraySchema(schema)) {
1416
+ const { type, description, items, required } = schema;
1417
+ return {
1418
+ type,
1419
+ required: !!required,
1420
+ description,
1421
+ items: this.toBaseSchema(items, enums, schemaKey, upLevelSchemaKey)
1422
+ };
1423
+ } else {
1424
+ const { required = [], allOf, anyOf, description, deprecated, enum: enum_, format, oneOf, properties = {} } = schema;
1425
+ let { type } = schema;
1426
+ if (enum_ && type !== "boolean") {
1427
+ const enumObject = {
1428
+ name: Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(schemaKey)),
1429
+ enum: [...new Set(enum_)]
1430
+ };
1431
+ const sameObject = Base.findSameSchema(enumObject, enums);
1432
+ if (!sameObject && Base.isValidEnumType(schema)) enums.push(enumObject);
1433
+ return {
1434
+ type: sameObject ? sameObject.name : Base.isBooleanEnum(schema) ? "boolean" : enumObject.name,
1435
+ required,
1436
+ description,
1437
+ deprecated
1438
+ };
1439
+ }
1440
+ if (type === void 0 && Object.keys(properties).length > 0) type = "object";
1441
+ return {
1442
+ type,
1443
+ required,
1444
+ description,
1445
+ deprecated,
1446
+ enum: enum_,
1447
+ format,
1448
+ allOf: allOf?.map((s) => Base.isRef(s) ? {
1449
+ ...s,
1450
+ ref: s.$ref,
1451
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1452
+ } : this.toBaseSchema(s, enums)),
1453
+ anyOf: anyOf?.map((s) => Base.isRef(s) ? {
1454
+ ...s,
1455
+ ref: s.$ref,
1456
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1457
+ } : this.toBaseSchema(s, enums)),
1458
+ oneOf: oneOf?.map((s) => Base.isRef(s) ? {
1459
+ ...s,
1460
+ ref: s.$ref,
1461
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1462
+ } : this.toBaseSchema(s, enums)),
1463
+ properties: Object.keys(properties).reduce((acc, p) => {
1464
+ const propSchema = properties[p];
1465
+ return {
1466
+ ...acc,
1467
+ [p]: Base.isRef(propSchema) ? { type: Base.capitalize(Base.ref2name(propSchema.$ref, this.doc)) } : this.toBaseSchema(propSchema, enums, p, upLevelSchemaKey)
1468
+ };
1469
+ }, {})
1470
+ };
1471
+ }
1472
+ }
1473
+ init() {
1474
+ const { components = {}, paths = {} } = this.doc;
1475
+ const enums = [];
1476
+ const { requestBodies = {}, responses = {}, parameters = {}, schemas = {} } = components;
1477
+ const schemas_ = Object.keys(schemas).reduce((acc, key) => {
1478
+ const schema = schemas[key];
1479
+ return {
1480
+ ...acc,
1481
+ [key]: this.getSchemaByRef(schema, false, enums, key)
1482
+ };
1483
+ }, {});
1484
+ const parameters_ = Object.keys(parameters).reduce((acc, key) => {
1485
+ const parameter = parameters[key];
1486
+ return {
1487
+ ...acc,
1488
+ [key]: this.getParameterByRef(parameter, enums, key)
1489
+ };
1490
+ }, {});
1491
+ const responses_ = Object.keys(responses).reduce((acc, key) => {
1492
+ const response = responses[key];
1493
+ return {
1494
+ ...acc,
1495
+ [key]: this.getResponseByRef(response)
1496
+ };
1497
+ }, {});
1498
+ const requestBodies_ = Object.keys(requestBodies).reduce((acc, key) => {
1499
+ const requestBody = requestBodies[key];
1500
+ return {
1501
+ ...acc,
1502
+ [key]: this.getRequestBodyByRef(requestBody, enums)
1503
+ };
1504
+ }, {});
1505
+ const apis = Object.keys(paths).reduce((acc, path) => {
1506
+ let pathObject = paths[path] ?? {};
1507
+ if (pathObject.$ref) {
1508
+ const resolved = this.resolvePathRef(pathObject.$ref);
1509
+ if (resolved) pathObject = resolved;
1510
+ }
1511
+ const { parameters = [], description, summary } = pathObject;
1512
+ const methodApis = [];
1513
+ Object.values(HttpMethods).forEach((method) => {
1514
+ const methodObject = pathObject[method];
1515
+ if (methodObject) {
1516
+ const { deprecated, operationId, responses = {}, summary: summary_, description: description_, requestBody = { content: {} } } = methodObject;
1517
+ const { parameters: parameters_ = [] } = methodObject;
1518
+ const baseParameters = [...parameters, ...parameters_].map((parameter) => this.getParameterByRef(parameter, enums));
1519
+ const baseRequestBody = this.getRequestBodyByRef(requestBody, enums);
1520
+ const uniqueParameterName = [...new Set(baseParameters.map((p) => p.name))];
1521
+ if (Object.keys(responses).length === 0) Object.assign(responses, { 200: { description: "Successful response" } });
1522
+ const httpCodes = Object.keys(responses);
1523
+ for (const code of httpCodes) if (code in responses) {
1524
+ const response = responses[code];
1525
+ const responseSchema = this.getResponseByRef(response);
1526
+ methodApis.push({
1527
+ method,
1528
+ operationId,
1529
+ summary: summary_ ?? summary,
1530
+ description: description_ ?? description,
1531
+ deprecated,
1532
+ parameters: uniqueParameterName.map((name) => baseParameters.find((p) => p.name === name)).filter((p) => p !== void 0),
1533
+ responses: responseSchema,
1534
+ requestBody: baseRequestBody
1535
+ });
1536
+ break;
1537
+ }
1538
+ }
1539
+ });
1540
+ return {
1541
+ ...acc,
1542
+ [path]: methodApis
1543
+ };
1544
+ }, {});
1545
+ return {
1546
+ enums: Base.uniqueEnums(enums),
1547
+ schemas: schemas_,
1548
+ responses: responses_,
1549
+ parameters: parameters_,
1550
+ requestBodies: requestBodies_,
1551
+ apis
1552
+ };
1553
+ }
1554
+ };
1555
+ //#endregion
1556
+ //#region src/openapi/V3_1.ts
1557
+ var V3_1 = class {
1558
+ doc;
1559
+ constructor(doc) {
1560
+ this.doc = doc;
1561
+ }
1562
+ /**
1563
+ * Resolves a path $ref to the actual path object.
1564
+ */
1565
+ resolvePathRef($ref) {
1566
+ const refName = Base.ref2name($ref, this.doc);
1567
+ return this.doc.paths?.[refName];
1568
+ }
1569
+ /**
1570
+ * Is array schema.
1571
+ */
1572
+ isOpenAPIArraySchema(schema) {
1573
+ return typeof schema === "object" && schema.type === "array";
1574
+ }
1575
+ /**
1576
+ * OpenAPI schema to base schema.
1577
+ */
1578
+ getSchemaByRef(schema, reserveRef = false, enums = [], upLevelSchemaKey = "") {
1579
+ let refName = "";
1580
+ if (Base.isRef(schema)) {
1581
+ refName = Base.upperCamelCase(Base.ref2name(schema.$ref));
1582
+ if (reserveRef) return { type: upLevelSchemaKey + refName };
1583
+ if (!this.doc.components) this.doc.components = { schemas: {} };
1584
+ const resolvedSchema = this.doc.components.schemas?.[Base.ref2name(schema.$ref, this.doc)];
1585
+ if (!resolvedSchema) throw new Error(`Schema reference not found: ${refName}`);
1586
+ schema = resolvedSchema;
1587
+ }
1588
+ return this.toBaseSchema(schema, enums, "", upLevelSchemaKey + refName);
1589
+ }
1590
+ /**
1591
+ * OpenAPI parameter to base parameter.
1592
+ */
1593
+ getParameterByRef(schema, enums = [], upLevelSchemaKey = "") {
1594
+ if (Base.isRef(schema)) schema = this.doc.components?.parameters?.[Base.ref2name(schema.$ref, this.doc)];
1595
+ const { name, required, deprecated, description, schema: parameterSchema } = schema;
1596
+ if (parameterSchema && !Base.isRef(parameterSchema) && parameterSchema.enum) {
1597
+ const type = Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(name));
1598
+ const enumSchema = {
1599
+ name: type,
1600
+ enum: [...new Set(parameterSchema.enum)]
1601
+ };
1602
+ const sameEnum = Base.findSameSchema(enumSchema, enums);
1603
+ if (!sameEnum && Base.isValidEnumType(parameterSchema)) enums.push(enumSchema);
1604
+ return {
1605
+ name,
1606
+ required,
1607
+ description,
1608
+ deprecated,
1609
+ in: schema.in,
1610
+ schema: { type: sameEnum?.name ?? type }
1611
+ };
1612
+ }
1613
+ return {
1614
+ name,
1615
+ required,
1616
+ description,
1617
+ deprecated,
1618
+ in: schema.in,
1619
+ schema: schema.schema && this.getSchemaByRef(schema.schema, false, enums, upLevelSchemaKey + Base.capitalize(name))
1620
+ };
1621
+ }
1622
+ /**
1623
+ * OpenAPI schema to base response
1624
+ */
1625
+ getResponseByRef(schema) {
1626
+ if (Base.isRef(schema)) schema = this.doc.components?.responses?.[Base.ref2name(schema.$ref, this.doc)];
1627
+ const { content = {} } = schema;
1628
+ return Object.keys(content).map((c) => ({
1629
+ type: c,
1630
+ schema: content[c].schema && this.getSchemaByRef(content[c].schema, true)
1631
+ }));
1632
+ }
1633
+ /**
1634
+ * OpenAPI schema to requestBody.
1635
+ */
1636
+ getRequestBodyByRef(schema, enums = []) {
1637
+ if (Base.isRef(schema)) schema = this.doc.components?.requestBodies?.[Base.ref2name(schema.$ref, this.doc)];
1638
+ const { content = {} } = schema;
1639
+ return Object.keys(content).map((c) => ({
1640
+ type: c,
1641
+ schema: content[c].schema && this.getSchemaByRef(content[c].schema, true, enums)
1642
+ }));
1643
+ }
1644
+ /**
1645
+ * Transform all OpenAPI schema to Base Schema
1646
+ */
1647
+ toBaseSchema(schema, enums = [], schemaKey = "", upLevelSchemaKey = "") {
1648
+ if (!schema) return { type: "unknown" };
1649
+ if (Base.isRef(schema)) return this.getSchemaByRef(schema, true);
1650
+ if (this.isOpenAPIArraySchema(schema)) {
1651
+ const { type, description, items, required } = schema;
1652
+ return {
1653
+ type,
1654
+ required: !!required,
1655
+ description,
1656
+ items: this.toBaseSchema(items, enums, schemaKey, upLevelSchemaKey)
1657
+ };
1658
+ } else {
1659
+ const { required = [], allOf, anyOf, description, deprecated, enum: enum_, format, oneOf, properties = {} } = schema;
1660
+ let { type } = schema;
1661
+ if (enum_ && type !== "boolean") {
1662
+ const enumObject = {
1663
+ name: Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(schemaKey)),
1664
+ enum: [...new Set(enum_)]
1665
+ };
1666
+ const sameObject = Base.findSameSchema(enumObject, enums);
1667
+ if (!sameObject && Base.isValidEnumType(schema)) enums.push(enumObject);
1668
+ return {
1669
+ type: sameObject ? sameObject.name : Base.isBooleanEnum(schema) ? "boolean" : enumObject.name,
1670
+ required,
1671
+ description,
1672
+ deprecated
1673
+ };
1674
+ }
1675
+ if (type === void 0 && Object.keys(properties).length > 0) type = "object";
1676
+ return {
1677
+ type,
1678
+ required,
1679
+ description,
1680
+ deprecated,
1681
+ enum: enum_,
1682
+ format,
1683
+ allOf: allOf?.map((s) => Base.isRef(s) ? {
1684
+ ...s,
1685
+ ref: s.$ref,
1686
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1687
+ } : this.toBaseSchema(s, enums)),
1688
+ anyOf: anyOf?.map((s) => Base.isRef(s) ? {
1689
+ ...s,
1690
+ ref: s.$ref,
1691
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1692
+ } : this.toBaseSchema(s, enums)),
1693
+ oneOf: oneOf?.map((s) => Base.isRef(s) ? {
1694
+ ...s,
1695
+ ref: s.$ref,
1696
+ type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
1697
+ } : this.toBaseSchema(s, enums)),
1698
+ properties: Object.keys(properties).reduce((acc, p) => {
1699
+ const propSchema = properties[p];
1700
+ return {
1701
+ ...acc,
1702
+ [p]: Base.isRef(propSchema) ? { type: Base.capitalize(Base.ref2name(propSchema.$ref, this.doc)) } : this.toBaseSchema(propSchema, enums, p, upLevelSchemaKey)
1703
+ };
1704
+ }, {})
1705
+ };
1706
+ }
1707
+ }
1708
+ init() {
1709
+ const { components = {}, paths = {} } = this.doc;
1710
+ const enums = [];
1711
+ const { requestBodies = {}, responses = {}, parameters = {}, schemas = {} } = components;
1712
+ const schemas_ = Object.keys(schemas).reduce((acc, key) => {
1713
+ const schema = schemas[key];
1714
+ return {
1715
+ ...acc,
1716
+ [key]: this.getSchemaByRef(schema, false, enums, key)
1717
+ };
1718
+ }, {});
1719
+ const parameters_ = Object.keys(parameters).reduce((acc, key) => {
1720
+ const parameter = parameters[key];
1721
+ return {
1722
+ ...acc,
1723
+ [key]: this.getParameterByRef(parameter, enums, key)
1724
+ };
1725
+ }, {});
1726
+ const responses_ = Object.keys(responses).reduce((acc, key) => {
1727
+ const response = responses[key];
1728
+ return {
1729
+ ...acc,
1730
+ [key]: this.getResponseByRef(response)
1731
+ };
1732
+ }, {});
1733
+ const requestBodies_ = Object.keys(requestBodies).reduce((acc, key) => {
1734
+ const requestBody = requestBodies[key];
1735
+ return {
1736
+ ...acc,
1737
+ [key]: this.getRequestBodyByRef(requestBody, enums)
1738
+ };
1739
+ }, {});
1740
+ const apis = Object.keys(paths).reduce((acc, path) => {
1741
+ let pathObject = paths[path] ?? {};
1742
+ if (pathObject.$ref) {
1743
+ const resolved = this.resolvePathRef(pathObject.$ref);
1744
+ if (resolved) pathObject = resolved;
1745
+ }
1746
+ const { parameters = [], description, summary } = pathObject;
1747
+ const methodApis = [];
1748
+ Object.values(HttpMethods).forEach((method) => {
1749
+ const methodObject = pathObject[method];
1750
+ if (methodObject) {
1751
+ const { deprecated, operationId, summary: summary_, description: description_, responses = {}, requestBody = { content: {} } } = methodObject;
1752
+ const { parameters: parameters_ = [] } = methodObject;
1753
+ const baseParameters = [...parameters, ...parameters_].map((parameter) => this.getParameterByRef(parameter, enums));
1754
+ const baseRequestBody = this.getRequestBodyByRef(requestBody, enums);
1755
+ const uniqueParameterName = [...new Set(baseParameters.map((p) => p.name))];
1756
+ if (Object.keys(responses).length === 0) Object.assign(responses, { 200: { description: "Successful response" } });
1757
+ const httpCodes = Object.keys(responses);
1758
+ for (const code of httpCodes) if (code in responses) {
1759
+ const response = responses[code];
1760
+ const responseSchema = this.getResponseByRef(response);
1761
+ methodApis.push({
1762
+ method,
1763
+ operationId,
1764
+ summary: summary_ ?? summary,
1765
+ description: description_ ?? description,
1766
+ deprecated,
1767
+ parameters: uniqueParameterName.map((name) => baseParameters.find((p) => p.name === name)).filter((p) => p !== void 0),
1768
+ responses: responseSchema,
1769
+ requestBody: baseRequestBody
1770
+ });
1771
+ break;
1772
+ }
1773
+ }
1774
+ });
1775
+ return {
1776
+ ...acc,
1777
+ [path]: methodApis
1778
+ };
1779
+ }, {});
1780
+ return {
1781
+ enums: Base.uniqueEnums(enums),
1782
+ schemas: schemas_,
1783
+ responses: responses_,
1784
+ parameters: parameters_,
1785
+ requestBodies: requestBodies_,
1786
+ apis
1787
+ };
1788
+ }
1789
+ };
1790
+ //#endregion
1791
+ //#region src/openapi/index.ts
1792
+ const logger$1 = createScopedLogger("OpenAPI");
1793
+ function getDocVersion(doc) {
1794
+ switch ((doc.openapi || doc.swagger).slice(0, 3)) {
1795
+ case "3.1": return "v3_1";
1796
+ case "3.0": return "v3";
1797
+ case "2.0": return "v2";
1798
+ default: return "unknown";
1799
+ }
1800
+ }
1801
+ var OpenAPIProvider = class extends Provider {
1802
+ parse(doc) {
1803
+ const version = getDocVersion(doc);
1804
+ logger$1.debug(`openapi version ${version}`);
1805
+ switch (version) {
1806
+ case "v2": return new V2(doc).init();
1807
+ case "v3": return new V3(doc).init();
1808
+ case "v3_1": return new V3_1(doc).init();
1809
+ default: throw new Error(`Not a valid OpenAPI version: ${version}`);
1810
+ }
1811
+ }
1812
+ };
1813
+ function getAdaptor(type) {
1814
+ switch (type) {
1815
+ case "axios": return new AxiosAdapter();
1816
+ default: return new FetchAdapter();
1817
+ }
1818
+ }
1819
+ async function codeGen(initOptions) {
1820
+ const startTime = Date.now();
1821
+ const { verbose } = initOptions;
1822
+ if (verbose) logger$1.setLevel("debug");
1823
+ else logger$1.setLevel("info");
1824
+ logger$1.info(`Fetch document from ${initOptions.docURL}`);
1825
+ const { enums, schemas, parameters, responses, requestBodies, apis } = new OpenAPIProvider(initOptions, await Base.fetchDoc(initOptions.docURL, initOptions.requestOptions));
1826
+ const adaptor = getAdaptor(initOptions.adaptor ?? "fetch");
1827
+ const code = await Generator.genCode({
1828
+ enums,
1829
+ schemas,
1830
+ parameters,
1831
+ responses,
1832
+ requestBodies,
1833
+ apis
1834
+ }, initOptions, adaptor);
1835
+ if (initOptions.output) await Generator.write(code, initOptions.output);
1836
+ const duration = Date.now() - startTime;
1837
+ return {
1838
+ code,
1839
+ stats: {
1840
+ endpoints: Object.keys(apis).length,
1841
+ schemas: Object.keys(schemas).length,
1842
+ duration
1843
+ }
1844
+ };
1845
+ }
1846
+ //#endregion
1847
+ //#region src/vite-plugin/index.ts
1848
+ const PLUGIN_NAME = "api-code-gen";
1849
+ const logger = createScopedLogger("api-code-gen");
1850
+ /**
1851
+ * Run TypeScript type checking on generated file
1852
+ */
1853
+ async function runTypeCheck(filePath) {
1854
+ const { execaCommand } = await import("execa");
1855
+ const errors = [];
1856
+ try {
1857
+ await execaCommand(`npx tsc ${filePath} --noEmit`, { shell: true });
1858
+ } catch (error) {
1859
+ if (error instanceof Error) errors.push(error.message);
1860
+ }
1861
+ return errors;
1862
+ }
1863
+ /**
1864
+ * Validate spec path exists
1865
+ */
1866
+ async function validateSpecPath(specPath) {
1867
+ if (specPath.startsWith("http://") || specPath.startsWith("https://")) return;
1868
+ const filePath = specPath.replace(/^file:\/\//, "");
1869
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
1870
+ if (!await fs.pathExists(absolutePath)) throw createErrors.specNotFound(absolutePath);
1871
+ }
1872
+ /**
1873
+ * Generate code for a single API configuration
1874
+ */
1875
+ async function generateForOption(option) {
1876
+ const { name, typeCheck = true, verbose, ...restOptions } = option;
1877
+ try {
1878
+ console.log(`\x1b[36m├─\x1b[0m ${name}`);
1879
+ const config = await loadConfig({
1880
+ name,
1881
+ cliOptions: {
1882
+ ...restOptions,
1883
+ verbose
1884
+ }
1885
+ });
1886
+ await validateSpecPath(config.spec);
1887
+ if (config.output) {
1888
+ const outputDir = path.dirname(config.output);
1889
+ await fs.ensureDir(outputDir);
1890
+ }
1891
+ let docURL = config.spec;
1892
+ if (!docURL.startsWith("http://") && !docURL.startsWith("https://")) if (docURL.startsWith("/") || docURL.match(/^[A-Za-z]:/)) docURL = `file://${docURL}`;
1893
+ else docURL = path.resolve(process.cwd(), docURL);
1894
+ const result = await codeGen({
1895
+ ...toProviderOptions(config),
1896
+ docURL
1897
+ });
1898
+ if (config.output) await fs.writeFile(config.output, result.code);
1899
+ if (typeCheck && config.output) {
1900
+ const typeErrors = await runTypeCheck(config.output);
1901
+ if (typeErrors.length > 0) {
1902
+ logger.warn(`Type check failed for ${config.output}`);
1903
+ if (verbose) for (const error of typeErrors) logger.warn(` ${error}`);
1904
+ }
1905
+ }
1906
+ return {
1907
+ success: true,
1908
+ name,
1909
+ output: config.output,
1910
+ stats: result.stats
1911
+ };
1912
+ } catch (error) {
1913
+ return {
1914
+ success: false,
1915
+ name,
1916
+ error
1917
+ };
1918
+ }
1919
+ }
1920
+ /**
1921
+ * Main Vite plugin function
1922
+ *
1923
+ * @example
1924
+ * ```ts
1925
+ * // vite.config.ts
1926
+ * import { apiCodeGenPlugin } from '@moccona/apicodegen/vite';
1927
+ *
1928
+ * export default defineConfig({
1929
+ * plugins: [
1930
+ * apiCodeGenPlugin([
1931
+ * {
1932
+ * name: 'my-api',
1933
+ * spec: './openapi.json',
1934
+ * output: './src/api/generated.ts',
1935
+ * baseURL: 'https://api.example.com',
1936
+ * },
1937
+ * ]),
1938
+ * ],
1939
+ * });
1940
+ * ```
1941
+ */
1942
+ function apiCodeGenPlugin(options) {
1943
+ if (!Array.isArray(options) || options.length === 0) {
1944
+ logger.warn("No API configurations provided to apiCodeGenPlugin");
1945
+ return { name: PLUGIN_NAME };
1946
+ }
1947
+ return {
1948
+ name: PLUGIN_NAME,
1949
+ async config(_config, env) {
1950
+ console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
1951
+ console.log(`\x1b[1m\x1b[36mAPI Code Gen\x1b[0m`);
1952
+ console.log(`\x1b[90mMode:\x1b[0m ${env?.command || "unknown"}`);
1953
+ console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
1954
+ const results = await Promise.all(options.map(generateForOption));
1955
+ const successCount = results.filter((r) => r.success).length;
1956
+ const failCount = options.length - successCount;
1957
+ console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
1958
+ for (const result of results) if (result.success) {
1959
+ const { name, output, stats } = result;
1960
+ if (stats) console.log(`\x1b[32m✓\x1b[0m ${name} → ${output} (${stats.endpoints} endpoints, ${stats.schemas} schemas) ${stats.duration}ms`);
1961
+ else console.log(`\x1b[32m✓\x1b[0m ${name} → ${output || "N/A"}`);
1962
+ } else {
1963
+ const { name, error } = result;
1964
+ if (isApicodegenError(error)) {
1965
+ console.log(`\x1b[31m✗\x1b[0m ${name}`);
1966
+ console.log(`\x1b[90m${formatError(error, true)}\x1b[0m`);
1967
+ } else {
1968
+ const wrapped = wrapError(error, {
1969
+ code: "E_GENERATION_FAILED",
1970
+ message: `Failed to generate API "${name}"`
1971
+ });
1972
+ console.log(`\x1b[31m✗\x1b[0m ${name}`);
1973
+ console.log(`\x1b[90m${formatError(wrapped, true)}\x1b[0m`);
1974
+ }
1975
+ }
1976
+ console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
1977
+ const totalDuration = results.reduce((sum, r) => sum + (r.stats?.duration || 0), 0);
1978
+ const totalEndpoints = results.reduce((sum, r) => sum + (r.stats?.endpoints || 0), 0);
1979
+ const totalSchemas = results.reduce((sum, r) => sum + (r.stats?.schemas || 0), 0);
1980
+ if (failCount === 0) console.log(`\x1b[32m✓\x1b[0m API Code Gen - Complete (\x1b[90m${successCount}/${options.length} succeeded\x1b[0m, ${totalEndpoints} endpoints, ${totalSchemas} schemas, ${totalDuration}ms\x1b[0m)`);
1981
+ else console.log(`\x1b[33m⚠\x1b[0m API Code Gen - Complete (\x1b[90m${successCount} succeeded, ${failCount} failed\x1b[0m, ${totalEndpoints} endpoints, ${totalSchemas} schemas, ${totalDuration}ms\x1b[0m)`);
1982
+ console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
1983
+ return {};
1984
+ }
1985
+ };
1986
+ }
1987
+ //#endregion
1988
+ export { apiCodeGenPlugin, apiCodeGenPlugin as default };
1989
+
1990
+ //# sourceMappingURL=index.mjs.map