@moccona/apicodegen
Version:
A powerful OpenAPI code generator that automatically generates TypeScript API client code from OpenAPI specifications.
1,064 lines • 82 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let undici = require("undici");
let typescript = require("typescript");
let node_fs_promises = require("node:fs/promises");
let prettier = require("prettier");
let node_path = require("node:path");
node_path = __toESM(node_path, 1);
let fs_extra = require("fs-extra");
fs_extra = __toESM(fs_extra, 1);
let _moccona_logger = require("@moccona/logger");
//#region src/core/base/Adaptor.ts
/**
* Base adapter for tool
* This abstract class serves as the foundation for implementing adapters for different code generation tools
*/
var Adapter = class {};
//#endregion
//#region src/core/constants/keywords.ts
const typescriptKeywords = new Set([
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"import",
"in",
"instanceof",
"new",
"null",
"return",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
"as",
"implements",
"interface",
"let",
"package",
"private",
"protected",
"public",
"static",
"yield",
"abstract",
"any",
"async",
"await",
"constructor",
"declare",
"from",
"get",
"is",
"module",
"namespace",
"never",
"require",
"set",
"type",
"unknown",
"readonly",
"of",
"asserts",
"infer",
"keyof",
"boolean",
"number",
"string",
"symbol",
"object",
"undefined",
"bigint"
]);
//#endregion
//#region src/core/interface.ts
let SchemaType = /* @__PURE__ */ function(SchemaType) {
SchemaType["schemas"] = "schemas";
SchemaType["parameters"] = "parameters";
SchemaType["responses"] = "responses";
SchemaType["requestBodies"] = "requestBodies";
return SchemaType;
}({});
let NonArraySchemaType = /* @__PURE__ */ function(NonArraySchemaType) {
NonArraySchemaType["object"] = "object";
NonArraySchemaType["string"] = "string";
NonArraySchemaType["number"] = "number";
NonArraySchemaType["boolean"] = "boolean";
NonArraySchemaType["integer"] = "integer";
NonArraySchemaType["enum"] = "enum";
NonArraySchemaType["file"] = "file";
return NonArraySchemaType;
}({});
let ArraySchemaType = /* @__PURE__ */ function(ArraySchemaType) {
ArraySchemaType["array"] = "array";
return ArraySchemaType;
}({});
let SchemaFormatType = /* @__PURE__ */ function(SchemaFormatType) {
SchemaFormatType["string"] = "string";
SchemaFormatType["number"] = "number";
SchemaFormatType["boolean"] = "boolean";
SchemaFormatType["file"] = "file";
SchemaFormatType["binary"] = "binary";
SchemaFormatType["blob"] = "blob";
return SchemaFormatType;
}({});
let ParameterIn = /* @__PURE__ */ function(ParameterIn) {
ParameterIn["header"] = "header";
ParameterIn["body"] = "body";
ParameterIn["query"] = "query";
ParameterIn["cookie"] = "cookie";
ParameterIn["path"] = "path";
ParameterIn["formData"] = "formData";
return ParameterIn;
}({});
let MediaTypes = /* @__PURE__ */ function(MediaTypes) {
MediaTypes["JSON"] = "application/json";
MediaTypes["EVENT_STREAM"] = "text/event-stream";
MediaTypes["TEXT"] = "text";
MediaTypes["IMAGE"] = "image";
MediaTypes["AUDIO"] = "audio";
MediaTypes["VIDEO"] = "video";
return MediaTypes;
}({});
let HttpMethods = /* @__PURE__ */ function(HttpMethods) {
HttpMethods["GET"] = "get";
HttpMethods["PUT"] = "put";
HttpMethods["POST"] = "post";
HttpMethods["DELETE"] = "delete";
HttpMethods["OPTIONS"] = "options";
HttpMethods["HEAD"] = "head";
HttpMethods["PATCH"] = "patch";
HttpMethods["TRACE"] = "trace";
return HttpMethods;
}({});
let Adaptors = /* @__PURE__ */ function(Adaptors) {
Adaptors["fetch"] = "fetch";
Adaptors["axios"] = "axios";
return Adaptors;
}({});
//#endregion
//#region src/core/base/Base.ts
/**
* @file Base class implementation
* @author wp.l
* @description Base utility class providing common methods for code generation and API handling
*/
/**
* Represents success HTTP status codes.
* Each key is a string representation of a success HTTP status code.
*/
const SuccessHttpStatusCode = {
"200": "200",
"201": "201",
"202": "202",
"203": "203",
"204": "204",
"205": "205",
"206": "206",
"207": "207",
"208": "208",
"226": "226"
};
/**
* Base abstract class providing common utility methods.
*/
var Base = class Base {
constructor() {
if (new.target === Base) throw new Error("Cannot instantiate abstract class");
}
/**
* Converts a reference string to a meaningful name.
* @param ref - The reference string to process.
* @param [doc] - Optional document reference for context.
* @returns - The processed name.
*/
static ref2name(ref, doc) {
const paths = ref.replace(/^#/, "").split("/").filter(Boolean);
if (!doc) return paths.slice(-1)[0];
let temporary = doc;
let lastPath = "";
for (const path of paths) {
const adjustedPath = path.replaceAll("~1", "/");
temporary = temporary[adjustedPath];
lastPath = adjustedPath;
}
if (!temporary) return "unknown";
return temporary.$ref ? Base.ref2name(temporary.$ref, doc) : lastPath;
}
/**
* Converts an API path to a function name.
* @param path - The API endpoint path.
* @param [method] - The HTTP method (e.g., GET, POST).
* @param [operationId] - Unique identifier for the operation.
* @returns - The generated function name.
*/
static pathToFnName(path, method, _operationId = "") {
return Base.normalize(Base.camelCase(Base.normalize(path))) + (method ? Base.capitalize(Base.upperCamelCase(`using_${method}`)) : "");
}
/**
* Normalizes a string by replacing special characters and avoiding TypeScript keywords.
* @param text - Input text to normalize.
* @returns - The normalized string.
*/
static normalize(text) {
if (typescriptKeywords.has(text)) text += "_";
return text.replace(/[/\-_{}():\s`,*<>$#.]/gm, "_").replace(/^\d./gm, "").replaceAll("...", "");
}
/**
* Capitalizes the first character of a string.
* @param text - Input string.
* @returns - Capitalized string.
*/
static capitalize(text) {
text = text.trim();
return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
}
/**
* Converts a string to camelCase.
* @param text - Input string.
* @returns - CamelCase string.
*/
static camelCase(text) {
text = text.trim();
const parts = text.split("_").filter(Boolean);
while (parts[0]?.match(/^\d/)) parts.shift();
return parts.map((t, index) => index === 0 ? t : Base.capitalize(t)).join("");
}
/**
* Converts a string to UpperCamelCase.
* @param text - Input string.
* @returns - UpperCamelCase string.
*/
static upperCamelCase(text) {
return Base.normalize(text).replaceAll("...", "").split("_").filter(Boolean).map(Base.capitalize).join("");
}
/**
* Fetches documentation from a given URL.
* @param url - The URL to fetch the documentation from.
* @param requestInit - Additional request parameters.
* @returns - A promise resolving to the fetched documentation data.
*/
static async fetchDoc(url, requestInit = {}) {
const { body, statusCode } = await (0, undici.request)(url, {
method: "GET",
dispatcher: new undici.Agent({ connect: { rejectUnauthorized: false } }),
...requestInit
});
if (statusCode >= 400) throw new Error(`Failed to fetch OpenAPI documentation from ${url}: HTTP ${statusCode}`);
try {
return body.json();
} catch (error) {
throw new Error(`Failed to parse JSON response from ${url}: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Determines the media type from a given media type string.
* @param mediaType - The media type string to evaluate.
* @returns - The matched MediaTypes or null.
*/
static getMediaType(mediaType) {
return Object.values(MediaTypes).find((type) => mediaType.includes(type));
}
/**
* Checks if a schema is a valid enum type that isn't boolean.
* @param a - The schema object to evaluate.
* @returns - True if the schema is a valid non-boolean enum.
*/
static isValidEnumType(a) {
return a.type !== "boolean" && !Base.isBooleanEnum(a);
}
/**
* Checks if a schema represents a boolean enum.
* @param a - The schema object to evaluate.
* @returns - True if the schema is a boolean enum.
*/
static isBooleanEnum(a) {
return a.type === "boolean" || !!a.enum?.some((member) => typeof member === "boolean");
}
/**
* Checks if two enum schemas are identical.
* @param a - First enum schema to compare.
* @param b - Second enum schema to compare.
* @returns - True if the enums are identical.
*/
static isSameEnum(a, b) {
return a.enum.length === b.enum.length && a.enum.sort().every((v, index) => v === b.enum.sort()[index]);
}
/**
* Filters out duplicate enum schemas from an array.
* @param enums - Array of enum schemas to process.
* @returns - Array of unique enum schemas.
*/
static uniqueEnums(enums) {
const enumMap = /* @__PURE__ */ new Map();
for (const e of enums) {
const existing = enumMap.get(e.name);
if (existing) for (const value of e.enum) existing.add(value);
else enumMap.set(e.name, new Set(e.enum));
}
return Array.from(enumMap.entries()).map(([name, values]) => ({
name,
enum: Array.from(values)
}));
}
/**
* Finds the first occurrence of a matching enum schema in an array.
* @param a - The enum schema to find.
* @param enums - Array of enum schemas to search.
* @returns - The found schema or undefined.
*/
static findSameSchema(a, enums) {
return enums.find((b) => Base.isSameEnum(b, a));
}
/**
* Checks if an object is a reference object.
* @param schema - The object to check.
* @returns - True if the object is a reference.
*/
static isRef(schema) {
return typeof schema === "object" && schema !== null && "$ref" in schema && typeof schema.$ref === "string";
}
};
//#endregion
//#region src/core/base/Provider.ts
/**
* Abstract Provider Class.
*
* The Provider class is designed to be extended by specific implementations (e.g., OpenAPI 2 provider, OpenAPI 3 provider).
* It handles the initialization of the provider and the parsing of documentation into structured data.
*
* @example
*
* ```ts
* /// Example of how this class might be used by a subclass:
* class OpenAPIProvider extends Provider {
* /// Implement the parse method to handle OpenAPI-specific documentation parsing.
* parse(doc: unknown): ProviderInitResult {
* /// Implementation details...
* }
* }
*
* /// Initializing a provider with configuration and documentation data:
* const initOptions: ProviderInitOptions = {
* docURL: "https://example.com/api/swagger.json",
* baseURL: "https://api.example.com",
* output: "./generated",
* requestOptions: {
* headers: { "Content-Type": "application/json" },
* },
* importClientSource: "generated/client",
* };
*
* const docData = fetchSwaggerDoc();
* const provider = new OpenAPIProvider(initOptions, docData);
* ```
*/
var Provider = class {
/** collection of enum schemas */
enums = [];
/** collection of schemas indexed by name */
schemas = {};
/** collection of parameters indexed by name */
parameters = {};
/** collection of API responses indexed by name */
responses = {};
/** collection of request bodies indexed by name */
requestBodies = {};
/** collection of API endpoints (operations) indexed by path */
apis = {};
/** URL for fetching API documentation */
docURL;
/** base URL for API endpoints */
baseURL;
/** output directory for generated code */
output;
/** request options for API documentation fetch */
requestOptions;
/** source path for imported client */
importClientSource;
/**
* Provider Constructor.
* @param {ProviderInitOptions} initOptions - Initial configuration for the provider.
* @param {unknown} doc - Raw API documentation data to be parsed.
*/
constructor(initOptions, doc) {
this.docURL = initOptions.docURL;
this.baseURL = initOptions.baseURL ?? "";
this.output = initOptions.output ?? ".";
this.requestOptions = initOptions.requestOptions ?? {};
this.importClientSource = initOptions.importClientSource ?? "";
const { enums, schemas, requestBodies, responses, parameters, apis } = this.parse(doc);
this.enums = enums;
this.schemas = schemas;
this.responses = responses;
this.parameters = parameters;
this.requestBodies = requestBodies;
this.apis = apis;
}
};
//#endregion
//#region src/core/errors.ts
/**
* Error handling utilities for api-codegen
*/
const ErrorCodes = {
SPEC_NOT_FOUND: "E_SPEC_NOT_FOUND",
SPEC_FETCH_FAILED: "E_SPEC_FETCH_FAILED",
SPEC_PARSE_FAILED: "E_SPEC_PARSE_FAILED",
OUTPUT_DIR_MISSING: "E_OUTPUT_DIR_MISSING",
CONFIG_INVALID: "E_CONFIG_INVALID",
VALIDATION_FAILED: "E_VALIDATION_FAILED",
GENERATION_FAILED: "E_GENERATION_FAILED",
TYPE_CHECK_FAILED: "E_TYPE_CHECK_FAILED"
};
/**
* Custom error class for api-codegen with rich context
*/
var ApicodegenError = class ApicodegenError extends Error {
code;
location;
line;
column;
path;
suggestions;
cause;
constructor(context) {
super(context.message);
this.name = "ApicodegenError";
this.code = context.code;
this.location = context.location;
this.line = context.line;
this.column = context.column;
this.path = context.path;
this.suggestions = context.suggestions || [];
this.cause = context.cause;
if (Error.captureStackTrace) Error.captureStackTrace(this, ApicodegenError);
}
/**
* Convert error to formatted string for CLI output
*/
toString(verbose = false) {
const lines = [];
lines.push(`\x1b[1;31mError [${this.code}]\x1b[0m ${this.message}`);
if (this.location) lines.push(` \x1b[36m→ Location:\x1b[0m ${this.location}`);
if (this.path) lines.push(` \x1b[36m→ Path:\x1b[0m ${this.path}`);
if (this.line !== void 0) {
let lineInfo = ` \x1b[36m→ Line:\x1b[0m ${this.line}`;
if (this.column !== void 0) lineInfo += `, Column: ${this.column}`;
lines.push(lineInfo);
}
if (this.suggestions.length > 0) for (const suggestion of this.suggestions) lines.push(` \x1b[32m→ Suggestion:\x1b[0m ${suggestion}`);
if (verbose && this.cause) {
lines.push(`\n \x1b[90mOriginal Error:\x1b[0m ${this.cause.message}`);
if (this.stack) {
const stackLines = this.stack.split("\n").slice(1).join("\n");
lines.push(`\x1b[90m${stackLines}\x1b[0m`);
}
}
return lines.join("\n");
}
/**
* Convert to JSON-serializable object
*/
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
location: this.location,
line: this.line,
column: this.column,
path: this.path,
suggestions: this.suggestions,
cause: this.cause?.message
};
}
};
/**
* ANSI color codes for terminal output
*/
const Colors = {
reset: "\x1B[0m",
bold: "\x1B[1m",
red: "\x1B[31m",
green: "\x1B[32m",
yellow: "\x1B[33m",
blue: "\x1B[34m",
cyan: "\x1B[36m",
gray: "\x1B[90m",
brightRed: "\x1B[91m",
brightGreen: "\x1B[92m"
};
/**
* Format error for CLI output
*/
function formatError(error, verbose = false) {
if (error instanceof ApicodegenError) return error.toString(verbose);
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}` : ""}`;
return `${Colors.red}${Colors.bold}Error${Colors.reset}: ${String(error)}`;
}
/**
* Print error to console with formatting
*/
function printError(error, verbose = false, stream = process.stderr) {
stream.write(formatError(error, verbose));
stream.write("\n");
}
/**
* Create error with common patterns
*/
const createErrors = {
specNotFound(path, cause) {
return new ApicodegenError({
code: ErrorCodes.SPEC_NOT_FOUND,
message: "OpenAPI spec file not found",
location: path,
suggestions: [
"Check if the file exists using 'ls -la'",
"Use --spec to provide the correct path",
"For remote specs, ensure the URL is accessible"
],
cause
});
},
specFetchFailed(url, statusCode, cause) {
const message = statusCode ? `Failed to fetch OpenAPI spec (HTTP ${statusCode})` : "Failed to fetch OpenAPI spec from URL";
return new ApicodegenError({
code: ErrorCodes.SPEC_FETCH_FAILED,
message,
location: url,
suggestions: [
"Check if the URL is accessible in a browser",
"Download the spec file locally and use the local path",
"Verify CORS settings if fetching from a different origin"
],
cause
});
},
specParseFailed(path, line, column, cause) {
return new ApicodegenError({
code: ErrorCodes.SPEC_PARSE_FAILED,
message: "Failed to parse OpenAPI spec (invalid JSON or YAML)",
location: path,
line,
column,
suggestions: [
"Validate JSON syntax using jsonlint.com",
"For YAML specs, ensure proper indentation",
"Check for trailing commas or unquoted special characters"
],
cause
});
},
outputDirMissing(path, cause) {
return new ApicodegenError({
code: ErrorCodes.OUTPUT_DIR_MISSING,
message: "Output directory does not exist",
location: path,
suggestions: ["Create the directory: mkdir -p $(dirname <output>)", "Check if the path is correct"],
cause
});
},
configInvalid(path, cause) {
return new ApicodegenError({
code: ErrorCodes.CONFIG_INVALID,
message: "Invalid configuration file",
location: path,
suggestions: ["Validate JSON syntax in the config file", "Check for required fields (spec, output)"],
cause
});
},
validationFailed(path, details, cause) {
return new ApicodegenError({
code: ErrorCodes.VALIDATION_FAILED,
message: "OpenAPI spec validation failed",
location: path,
path: details,
suggestions: [
"Check OpenAPI spec structure at the specified path",
"Ensure all required fields are present",
"Validate using swagger.io editor"
],
cause
});
},
generationFailed(cause) {
return new ApicodegenError({
code: ErrorCodes.GENERATION_FAILED,
message: "Code generation failed",
suggestions: [
"Check for unsupported OpenAPI features",
"Ensure spec follows OpenAPI 2.0, 3.0, or 3.1 specification",
"Use --verbose for more details"
],
cause
});
},
typeCheckFailed(path, _errors, cause) {
return new ApicodegenError({
code: ErrorCodes.TYPE_CHECK_FAILED,
message: "TypeScript type check failed",
location: path,
suggestions: [
"Review type errors above",
"Check for schema inconsistencies",
"Update generated types or fix source schema"
],
cause
});
},
missingRequiredField(field, context) {
return new ApicodegenError({
code: ErrorCodes.VALIDATION_FAILED,
message: `Missing required field: ${field}`,
path: context,
suggestions: [`Add the '${field}' field to your configuration`]
});
}
};
/**
* Wrap unknown error in ApicodegenError if needed
*/
function wrapError(error, context) {
if (error instanceof ApicodegenError) return error;
if (error instanceof Error) return new ApicodegenError({
code: context?.code || ErrorCodes.GENERATION_FAILED,
message: context?.message || error.message,
location: context?.location,
suggestions: context?.suggestions,
cause: error
});
return new ApicodegenError({
code: context?.code || ErrorCodes.GENERATION_FAILED,
message: String(error),
suggestions: context?.suggestions
});
}
/**
* Check if error is an ApicodegenError
*/
function isApicodegenError(error) {
return error instanceof ApicodegenError;
}
//#endregion
//#region src/core/generator/index.ts
var Generator = class Generator {
/**
* Converts an array of TypeScript statements into a formatted string of code.
*
* @param statements - The array of TypeScript statement nodes.
* @returns Formatted code as a string.
* @throws {Error} If no valid statements are provided.
*/
static toCode(statements) {
if (statements.length === 0) return "// No api declaration found.";
const sourceFile = typescript.factory.createSourceFile(statements, typescript.factory.createToken(typescript.SyntaxKind.EndOfFileToken), typescript.NodeFlags.None);
return (0, typescript.createPrinter)().printFile(sourceFile);
}
static async write(code, filepath) {
const { mkdir } = await import("node:fs/promises");
const { dirname } = await import("node:path");
try {
await mkdir(dirname(filepath), { recursive: true });
await (0, node_fs_promises.writeFile)(filepath, code);
} catch (error) {
throw new ApicodegenError({
code: ErrorCodes.OUTPUT_DIR_MISSING,
message: "Failed to write generated code to output file",
location: filepath,
cause: error instanceof Error ? error : new Error(String(error)),
suggestions: ["Verify the output directory path is writable", "Check that the parent directory exists or can be created"]
});
}
}
/**
* Converts a path string with parameters into a TypeScript template expression.
* Handles query parameters and path placeholders.
*
* @param path - The base path string containing placeholders.
* @param parameters - Array of parameter objects defining the parameters.
* @param basePath - Optional base path to prepend (default: "").
* @returns A TypeScript template expressi
*/
static toUrlTemplate(path, parameters, basePath = "") {
const queryParameters = parameters.filter((p) => p.in === "query");
if (queryParameters.length > 0) {
const queryString = queryParameters.map((qp, index) => `${index === 0 ? "?" : "&"}${encodeURIComponent(qp.name)}={${Base.normalize(qp.name)}}`).join("");
path += queryString;
}
const pathSegments = path.replaceAll("{", "${").split("$").filter(Boolean);
if (pathSegments.length === 1) return typescript.factory.createNoSubstitutionTemplateLiteral(basePath + path);
return typescript.factory.createTemplateExpression(typescript.factory.createTemplateHead(basePath + pathSegments[0]), pathSegments.slice(1).map((segment, index) => {
const match = /^{(.+)}(.+)?/gm.exec(segment);
const isLastSegment = index === pathSegments.length - 2;
if (!match) throw new Error(`Invalid path segment: ${segment}`);
return typescript.factory.createTemplateSpan(typescript.factory.createIdentifier(Base.normalize(match[1])), !isLastSegment ? typescript.factory.createTemplateMiddle(match[2]) : typescript.factory.createTemplateTail(match[2] || ""));
}));
}
/**
* Adds synthetic comments to a TypeScript AST node.
*
* @param node - The target AST node.
* @param comments - Array of comment objects to add.
*/
static addComments(node, comments) {
if (!Array.isArray(comments) || comments.filter(Boolean).length === 0) return;
const formatComment = (comment) => {
if (comment.tag === "returns") return `* @returns {${comment.type}} ${comment.comment ?? ""}`;
if (comment.tag === "param") return comment.comment ? `* @param ${comment.paramName} - ${comment.comment}` : `* @param ${comment.paramName}`;
if (comment.tag) return `* @${comment.tag} ${comment.comment ?? ""}`;
return `* ${comment.comment}`;
};
const formattedComments = comments.map(formatComment).join("\n").trim() + "\n";
(0, typescript.addSyntheticLeadingComment)(node, typescript.SyntaxKind.MultiLineCommentTrivia, formattedComments, true);
}
/**
* Checks if a schema represents a binary type.
*
* @param schema - The schema object to check.
* @returns true if the schema is a binary type, false otherwise.
*/
static isBinarySchema(schema) {
if (schema.type === "array") {
const arraySchema = schema;
return Generator.isBinarySchema(arraySchema.items);
}
const nonArraySchema = schema;
return nonArraySchema.format === "blob" || nonArraySchema.format === "binary" || nonArraySchema.type === "file";
}
static schemaToTypeString(schema) {
if (schema.type === "array") {
const arraySchema = schema;
return arraySchema.items ? `${Generator.schemaToTypeString(arraySchema.items)}[]` : "unknown";
}
const singleSchema = schema;
if (schema.type === "string") return "string";
if (schema.type === "number" || schema.type === "integer") return "number";
if (schema.type === "boolean") return "boolean";
if (schema.type === "object" || schema.properties) return "object";
if (singleSchema.format === "binary" || singleSchema.type === "file") return "Blob";
if (singleSchema.format === "blob") return "Blob";
if (singleSchema.ref) return singleSchema.ref;
return "unknown";
}
static generateParamTags(parameters, requestBody) {
const tags = [];
for (const p of parameters) {
const paramName = Base.normalize(p.name);
let paramType = "unknown";
if (p.schema) paramType = Generator.schemaToTypeString(p.schema);
const isOptional = p.required === false;
tags.push({
tag: "param",
paramName,
type: `${paramType}${isOptional ? " | undefined" : ""}`,
comment: p.description ?? ""
});
}
if (requestBody?.schema && "properties" in requestBody.schema) {
const properties = requestBody.schema.properties;
const required = requestBody.schema.required;
const requiredArray = Array.isArray(required) ? required : [];
for (const [key, schema] of Object.entries(properties ?? {})) {
const paramName = `req.${key}`;
const paramType = Generator.schemaToTypeString(schema);
const isOptional = !requiredArray.includes(key);
tags.push({
tag: "param",
paramName,
type: `${paramType}${isOptional ? " | undefined" : ""}`,
comment: schema.description ?? ""
});
}
}
return tags;
}
static toRequestBodyTypeNode(schema) {
return typescript.factory.createParameterDeclaration(void 0, void 0, typescript.factory.createIdentifier("req"), void 0, Generator.toTypeNode(schema));
}
static toTypeNode(schema) {
const { type, ref } = schema;
if (ref) {
const identify = Base.ref2name(ref);
return typescript.factory.createTypeReferenceNode(typescript.factory.createIdentifier(identify === "unknown" ? identify : Base.upperCamelCase(identify)));
}
switch (type) {
case "array": {
const { items } = schema;
return typescript.factory.createArrayTypeNode(Generator.toTypeNode(items));
}
case "object": {
const propsCount = Object.keys(schema.properties ?? {}).length;
if (!schema.properties || propsCount === 0) return typescript.factory.createTypeReferenceNode(typescript.factory.createIdentifier("Record"), [typescript.factory.createToken(typescript.SyntaxKind.StringKeyword), typescript.factory.createToken(typescript.SyntaxKind.UnknownKeyword)]);
const props = Object.keys(schema.properties);
return typescript.factory.createTypeLiteralNode(props.map((propKey) => {
const propSchema = schema.properties[propKey];
return typescript.factory.createPropertySignature(void 0, typescript.factory.createStringLiteral(propKey), schema.required || schema.ref || Generator.isBinarySchema(schema) ? void 0 : typescript.factory.createToken(typescript.SyntaxKind.QuestionToken), Generator.toTypeNode(propSchema));
}));
}
case "integer":
case "number":
if (schema.enum) return typescript.factory.createUnionTypeNode(schema.enum.map((e) => typescript.factory.createLiteralTypeNode(typescript.factory.createNumericLiteral(e))));
return typescript.factory.createToken(typescript.SyntaxKind.NumberKeyword);
case "boolean": return typescript.factory.createToken(typescript.SyntaxKind.BooleanKeyword);
case "file": return typescript.factory.createTypeReferenceNode(typescript.factory.createIdentifier("Blob"));
default: {
const { format, oneOf, allOf, anyOf, type, enum: enum_ } = schema;
switch (format) {
case "number": return typescript.factory.createToken(typescript.SyntaxKind.NumberKeyword);
case "string": return typescript.factory.createToken(typescript.SyntaxKind.StringKeyword);
case "boolean": return typescript.factory.createToken(typescript.SyntaxKind.BooleanKeyword);
case "blob":
case "binary": return typescript.factory.createTypeReferenceNode(typescript.factory.createIdentifier("Blob"));
default:
}
if (enum_) return typescript.factory.createUnionTypeNode(enum_.map((e) => typescript.factory.createLiteralTypeNode(typescript.factory.createStringLiteral(e))));
if (type === "string") return typescript.factory.createToken(typescript.SyntaxKind.StringKeyword);
if (oneOf) return typescript.factory.createUnionTypeNode(oneOf.map((schema) => Generator.toTypeNode(schema)));
if (anyOf) return typescript.factory.createUnionTypeNode(anyOf.map((schema) => Generator.toTypeNode(schema)));
if (allOf) return typescript.factory.createIntersectionTypeNode(allOf.map((schema) => Generator.toTypeNode(schema)));
if (type && typeof type === "string") return typescript.factory.createTypeReferenceNode(type !== "unknown" && type !== "null" ? typescript.factory.createIdentifier(Base.upperCamelCase(type)) : type);
}
}
return typescript.factory.createToken(typescript.SyntaxKind.UnknownKeyword);
}
static toDeclarationNodes(parameters) {
const objectElements = [];
const typeObjectElements = [];
const refParameters = [];
for (const parameter of parameters) if (parameter.ref) {
const refName = Base.ref2name(parameter.ref);
refParameters.push(typescript.factory.createParameterDeclaration(void 0, void 0, typescript.factory.createIdentifier(Base.normalize(refName)), void 0, typescript.factory.createTypeReferenceNode(typescript.factory.createIdentifier(Base.upperCamelCase(Base.normalize(refName)))), void 0));
} else {
const { name, schema, required } = parameter;
objectElements.push(typescript.factory.createBindingElement(void 0, void 0, typescript.factory.createIdentifier(Base.normalize(name))));
typeObjectElements.push(typescript.factory.createPropertySignature([], typescript.factory.createIdentifier(Base.normalize(name)), required ? void 0 : typescript.factory.createToken(typescript.SyntaxKind.QuestionToken), !schema ? typescript.factory.createToken(typescript.SyntaxKind.UnknownKeyword) : Generator.toTypeNode(schema)));
}
if (objectElements.length > 0) return [typescript.factory.createParameterDeclaration(void 0, void 0, typescript.factory.createObjectBindingPattern(objectElements), void 0, typescript.factory.createTypeLiteralNode(typeObjectElements), void 0), ...refParameters];
return refParameters;
}
static toFormDataStatement(parameters, requestBody) {
const statements = [];
const fdDeclaration = typescript.factory.createVariableStatement(void 0, typescript.factory.createVariableDeclarationList([typescript.factory.createVariableDeclaration(typescript.factory.createIdentifier("fd"), void 0, void 0, typescript.factory.createNewExpression(typescript.factory.createIdentifier("FormData"), void 0, []))], typescript.NodeFlags.Const));
statements.push(fdDeclaration);
parameters.forEach((parameter) => {
statements.push(typescript.factory.createExpressionStatement(typescript.factory.createBinaryExpression(typescript.factory.createIdentifier(parameter.name), typescript.factory.createToken(typescript.SyntaxKind.AmpersandAmpersandToken), typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("fd"), typescript.factory.createIdentifier("append")), void 0, [typescript.factory.createStringLiteral(parameter.name), typescript.factory.createIdentifier(parameter.name)]))));
});
if (requestBody && requestBody.type === "object" && requestBody.properties && Object.keys(requestBody.properties).length !== 0) Object.keys(requestBody.properties).forEach((key) => {
const schemaByKey = requestBody.properties[key];
if (schemaByKey.type === "array" && Generator.isBinarySchema(schemaByKey)) statements.push(typescript.factory.createForOfStatement(void 0, typescript.factory.createVariableDeclarationList([typescript.factory.createVariableDeclaration("file")], typescript.NodeFlags.Const), typescript.factory.createElementAccessExpression(typescript.factory.createIdentifier("req"), typescript.factory.createStringLiteral(key)), typescript.factory.createBlock([typescript.factory.createExpressionStatement(typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("fd"), typescript.factory.createIdentifier("append")), [], [
typescript.factory.createStringLiteral(key),
typescript.factory.createIdentifier("file"),
typescript.factory.createPropertyAccessExpression(typescript.factory.createAsExpression(typescript.factory.createIdentifier("file"), typescript.factory.createTypeReferenceNode(typescript.factory.createIdentifier("File"), void 0)), typescript.factory.createIdentifier("name"))
]))])));
else if (schemaByKey.required) statements.push(typescript.factory.createExpressionStatement(typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("fd"), typescript.factory.createIdentifier("append")), void 0, [typescript.factory.createStringLiteral(key), schemaByKey.type === "string" || Generator.isBinarySchema(schemaByKey) || schemaByKey.isRef ? typescript.factory.createElementAccessExpression(typescript.factory.createIdentifier("req"), typescript.factory.createStringLiteral(key)) : schemaByKey.type === "array" || schemaByKey.type === "object" ? typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("JSON"), typescript.factory.createIdentifier("stringify")), void 0, [typescript.factory.createElementAccessExpression(typescript.factory.createIdentifier("req"), typescript.factory.createStringLiteral(key))]) : typescript.factory.createCallExpression(typescript.factory.createIdentifier("String"), void 0, [typescript.factory.createElementAccessExpression(typescript.factory.createIdentifier("req"), typescript.factory.createStringLiteral(key))])])));
else statements.push(typescript.factory.createExpressionStatement(typescript.factory.createBinaryExpression(typescript.factory.createElementAccessExpression(typescript.factory.createIdentifier("req"), typescript.factory.createStringLiteral(key)), typescript.factory.createToken(typescript.SyntaxKind.AmpersandAmpersandToken), typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("fd"), typescript.factory.createIdentifier("append")), void 0, [typescript.factory.createStringLiteral(key), schemaByKey.type === "string" || Generator.isBinarySchema(schemaByKey) || schemaByKey.isRef ? typescript.factory.createElementAccessExpression(typescript.factory.createIdentifier("req"), typescript.factory.createStringLiteral(key)) : schemaByKey.type === "array" || schemaByKey.type === "object" ? typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("JSON"), typescript.factory.createIdentifier("stringify")), void 0, [typescript.factory.createElementAccessExpression(typescript.factory.createIdentifier("req"), typescript.factory.createStringLiteral(key))]) : typescript.factory.createCallExpression(typescript.factory.createIdentifier("String"), void 0, [typescript.factory.createElementAccessExpression(typescript.factory.createIdentifier("req"), typescript.factory.createStringLiteral(key))])]))));
});
return statements;
}
static bodyBlock(uri, method, parameters, requestBody, response, adapter) {
const isFormDataRequest = requestBody && ["multipart/form-data", "application/x-www-form-urlencoded"].includes(requestBody.type);
const shouldParseResponseToJSON = "application/json" === response?.type;
const isEventStream = response?.type === "text/event-stream";
const isRequestBodyBinary = requestBody?.schema && requestBody.schema.type === "array" && Generator.isBinarySchema(requestBody.schema);
const parametersShouldPutInFormData = parameters.filter((p) => p.in === "formData" || p.schema && Generator.isBinarySchema(p.schema));
const parametersShouldNotPutInFormData = parameters.filter((p) => !parametersShouldPutInFormData.includes(p));
const isRequestBodyContainsBinary = requestBody?.schema && "properties" in requestBody.schema && Object.values(requestBody.schema?.properties ?? {}).some((p) => Generator.isBinarySchema(p));
const hasBinaryInParameters = parameters.some((p) => p?.schema && Generator.isBinarySchema(p.schema));
const shouldPutParametersOrBodyInFormData = !!isFormDataRequest && (isRequestBodyBinary || hasBinaryInParameters || isRequestBodyContainsBinary || parametersShouldPutInFormData.length > 0);
return typescript.factory.createBlock([...shouldPutParametersOrBodyInFormData ? Generator.toFormDataStatement(parametersShouldPutInFormData, requestBody?.schema) : [], ...adapter.client(uri, method, parametersShouldNotPutInFormData, requestBody, response, adapter, shouldPutParametersOrBodyInFormData, shouldParseResponseToJSON, isEventStream)]);
}
static schemaToStatemets(parsedDoc, adaptor, options) {
const statements = [];
const { apis, schemas = {}, enums } = parsedDoc;
const enumNames = [];
for (const enumObject of enums) {
enumNames.push(Base.upperCamelCase(enumObject.name));
statements.push(typescript.factory.createEnumDeclaration([typescript.factory.createToken(typescript.SyntaxKind.ExportKeyword)], typescript.factory.createIdentifier(Base.upperCamelCase(enumObject.name)), enumObject.enum.map((member) => {
return typescript.factory.createEnumMember(typescript.factory.createStringLiteral(typeof member === "string" ? member : `${member}_`), typeof member === "string" ? typescript.factory.createStringLiteral(member) : typescript.factory.createNumericLiteral(member));
})));
}
for (const schemaKey in schemas) if (Object.hasOwn(schemas, schemaKey) && !enumNames.includes(Base.upperCamelCase(schemaKey))) {
const schema = schemas[schemaKey];
statements.push(typescript.factory.createTypeAliasDeclaration([typescript.factory.createModifier(typescript.SyntaxKind.ExportKeyword)], typescript.factory.createIdentifier(Base.upperCamelCase(schemaKey)), void 0, Generator.toTypeNode(schema)));
}
for (const uri in apis) {
const operations = apis[uri];
for (const operation of operations) {
const { method, operationId, requestBody = [], responses = [], summary, deprecated, description } = operation;
let { parameters = [] } = operation;
parameters = parameters.filter((p) => p.in !== "cookie");
if (requestBody.length === 0) requestBody.push({ type: "application/json" });
const shouldAddExtraMethodNameSuffix = requestBody.length > 1;
for (const req of requestBody) {
const statement = typescript.factory.createFunctionDeclaration([typescript.factory.createModifier(typescript.SyntaxKind.ExportKeyword), typescript.factory.createModifier(typescript.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));
const mergedDescription = [description, summary].filter(Boolean).join(". ");
Generator.addComments(statement, [
mergedDescription && { comment: mergedDescription },
deprecated && { tag: "deprecated" },
...Generator.generateParamTags(parameters, req)
].filter(Boolean));
statements.push(statement);
}
}
}
return statements;
}
static async prettier(code) {
return await (0, prettier.format)(code, { parser: "typescript" });
}
static async genCode(schema, initOptions, adaptor) {
const { importClientSource } = initOptions;
const statements = Generator.schemaToStatemets(schema, adaptor, { baseURL: initOptions.baseURL ?? "" });
let code = Generator.toCode(statements);
if (importClientSource) code = importClientSource + "\n\n" + code;
return await Generator.prettier(code);
}
};
//#endregion
//#region src/core/client/axios.ts
/**
* Adapter class implementing support for generating code that makes use of the Axios HTTP client library.
* This class defines custom behavior and field mappings specific to the Axios client.
*/
var AxiosAdapter = class extends Adapter {
/**
* Name of the field used to specify the HTTP method in the request configuration.
*/
methodFieldName = "method";
/**
* Name of the field used to specify the request body (data) in the request configuration.
*/
bodyFieldName = "data";
/**
* Name of the field used to specify the request headers in the request configuration.
*/
headersFieldName = "headers";
/**
* Name of the field used to specify the query parameters in the request configuration.
*/
queryFieldName = "params";
/**
* The name of the client this adapter is configured for, which is 'axios' in this case.
*/
name = "axios";
/**
* Generates client code for making API requests using Axios.
* @param uri - The API endpoint URI
* @param method - The HTTP method (GET, POST, etc.)
* @param parameters - Array of parameters to include in the request
* @param requestBody - The request body media type definition
* @param response - The response media type definition
* @param adapter - The adapter instance
* @param shouldUseFormData - Flag to use FormData for the request body
* @param shouldUseJSONResponse - Unused by AxiosAdapter; present to align with the abstract signature so positional args bind correctly
* @param isEventStream - Flag indicating a text/event-stream response; when true the raw AxiosResponse is returned without JSON parsing
* @return - An array of generated TypeScript statements
*/
client(uri, method, parameters, requestBody, response, adapter, shouldUseFormData, _shouldUseJSONResponse, isEventStream) {
const statements = [];
const inBody = parameters.filter((p) => !p.in || p.in === "body");
const inHeader = parameters.filter((p) => p.in === "header");
/**
* Creates the literal object expression for fetch options
* including method, headers, and body.
* @returns - The constructed fetch options object
*/
const toLiterlExpression = () => {
return typescript.factory.createObjectLiteralExpression([typescript.factory.createPropertyAssignment(typescript.factory.createIdentifier(adapter.methodFieldName), typescript.factory.createStringLiteral(method.toUpperCase()))].concat(inHeader.length > 0 ? typescript.factory.createPropertyAssignment(typescript.factory.createIdentifier(adapter.headersFieldName), typescript.factory.createObjectLiteralExpression(inHeader.map((p) => typescript.factory.createPropertyAssignment(typescript.factory.createStringLiteral(p.name), typescript.factory.createCallExpression(typescript.factory.createIdentifier("encodeURIComponent"), void 0, [typescript.factory.createCallExpression(typescript.factory.createIdentifier("String"), void 0, [typescript.factory.createIdentifier(Base.normalize(p.name))])]))))) : []).concat(shouldUseFormData || inBody.length > 0 || requestBody?.schema ? typescript.factory.createPropertyAssignment(typescript.factory.createIdentifier(adapter.bodyFieldName), shouldUseFormData ? typescript.factory.createIdentifier("fd") : inBody.length > 0 || requestBody?.schema && !Generator.isBinarySchema(requestBody.schema) ? typescript.factory.createIdentifier("req") : typescript.factory.createIdentifier("req")) : []), true);
};
if (isEventStream) {
statements.push(typescript.factory.createReturnStatement(typescript.factory.createCallExpression(typescript.factory.createIdentifier(adapter.name), void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()])));
return statements;
}
statements.push(typescript.factory.createReturnStatement(typescript.factory.createCallExpression(typescript.factory.createIdentifier(adapter.name), response?.schema ? [Generator.toTypeNode(response.schema)] : void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()])));
return statements;
}
};
//#endregion
//#region src/core/client/fetch.ts
/**
* FetchAdapter is an adapter class that generates client-side fetch requests.
* It handles parameters, headers, and request bodies to construct proper fetch calls.
*/
var FetchAdapter = class extends Adapter {
methodFieldName = "method";
bodyFieldName = "body";
headersFieldName = "headers";
queryFieldName = "";
name = "fetch";
/**
* Generates client code for making API requests using the Fetch API.
* @param uri - The API endpoint URI
* @param method - The HTTP method (GET, POST, etc.)
* @param parameters - Array of parameters to include in the request
* @param requestBody - The request body media type definition
* @param response - The response media type definition
* @param adapter - The adapter instance
* @param shouldUseFormData - Flag to use FormData for the request body
* @param shouldUseJSONResponse - Flag to use JSON parsing for the response
* @param isEventStream - Flag indicating a text/event-stream response; when true the raw Response is returned unparsed
* @return - An array of generated TypeScript statements
*/
client(uri, method, parameters, requestBody, response, adapter, shouldUseFormData, shouldUseJSONResponse, isEventStream) {
const statements = [];
const inBody = parameters.filter((p) => !p.in || p.in === "body");
const inHeader = parameters.filter((p) => p.in === "header");
/**
* Creates the literal object expression for fetch options
* including method, headers, and body.
* @returns - The constructed fetch options object
*/
const toLiterlExpression = () => {
return typescript.factory.createObjectLiteralExpression([typescript.factory.createPropertyAssignment(typescript.factory.createIdentifier(adapter.methodFieldName), typescript.factory.createStringLiteral(method.toUpperCase()))].concat(inHeader.length > 0 ? typescript.factory.createPropertyAssignment(typescript.factory.createIdentifier(adapter.headersFieldName), typescript.factory.createObjectLiteralExpression(inHeader.map((p) => typescript.factory.createPropertyAssignment(typescript.factory.createStringLiteral(p.name), typescript.factory.createCallExpression(typescript.factory.createIdentifier("encodeURIComponent"), void 0, [typescript.factory.createCallExpression(typescript.factory.createIdentifier("String"), void 0, [typescript.factory.createIdentifier(Base.normalize(p.name))])]))))) : []).concat(shouldUseFormData || inBody.length > 0 || requestBody?.schema ? typescript.factory.createPropertyAssignment(typescript.factory.createIdentifier(adapter.bodyFieldName), shouldUseFormData ? typescript.factory.createIdentifier("fd") : inBody.length > 0 || requestBody?.schema && !Generator.isBinarySchema(requestBody.schema) ? typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("JSON"), typescript.factory.createIdentifier("stringify")), [], [requestBody ? typescript.factory.createIdentifier("req") : typescript.factory.createObjectLiteralExpression(inBody.map((b) => typescript.factory.createShorthandPropertyAssignment(typescript.factory.createIdentifier(b.name))), true)]) : typescript.factory.createIdentifier("req")) : []), true);
};
if (isEventStream) {
statements.push(typescript.factory.createReturnStatement(typescript.factory.createCallExpression(typescript.factory.createIdentifier(adapter.name), void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()])));
return statements;
}
statements.push(typescript.factory.createReturnStatement(shouldUseJSONResponse ? typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createCallExpression(typescript.factory.createIdentifier(adapter.name), void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()]), typescript.factory.createIdentifier("then")), void 0, [typescript.factory.createArrowFunction([typescript.factory.createModifier(typescript.SyntaxKind.AsyncKeyword)], [], [typescript.factory.createParameterDeclaration(void 0, void 0, typescript.factory.createIdentifier("response"))], void 0, typescript.factory.createToken(typescript.SyntaxKind.EqualsGreaterThanToken), response?.schema ? typescript.factory.createAsExpression(typescript.factory.createParenthesizedExpression(typescript.factory.createAwaitExpression(typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("response"), typescript.factory.createIdentifier("json")), void 0, []))), response?.schema ? Generator.toTypeNode(response.schema) : typescript.factory.createToken(typescript.SyntaxKind.UnknownKeyword)) : typescript.factory.createParenthesizedExpression(typescript.factory.createAwaitExpression(typescript.factory.createCallExpression(typescript.factory.createPropertyAccessExpression(typescript.factory.createIdentifier("response"), typescript.factory.createIdentifier("json")), void 0, []))))]) : typescript.factory.createCallExpression(typescript.factory.createIdentifier(adapter.name), void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()])));
retu