@moccona/apicodegen
Version:
`@moccona/apicodegen` is a command-line tool for generating TypeScript code from OpenAPI documentation. It provides a simple and efficient way to automate the process of creating API clients.
1,345 lines (1,334 loc) • 80.8 kB
JavaScript
"use strict";
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 __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Adapter: () => Adapter,
Adaptors: () => Adaptors,
ArraySchemaType: () => ArraySchemaType,
AxiosAdapter: () => AxiosAdapter,
Base: () => Base,
FetchAdapter: () => FetchAdapter,
Generator: () => Generator,
HttpMethods: () => HttpMethods,
MediaTypes: () => MediaTypes,
NonArraySchemaType: () => NonArraySchemaType,
OpenAPIProvider: () => OpenAPIProvider,
OpenAPIVersion: () => OpenAPIVersion,
ParameterIn: () => ParameterIn,
Provider: () => Provider,
SchemaFormatType: () => SchemaFormatType,
SchemaType: () => SchemaType,
SuccessHttpStatusCode: () => SuccessHttpStatusCode,
apiCodeGenPlugin: () => apiCodeGenPlugin,
codeGen: () => codeGen,
tsc: () => tsc
});
module.exports = __toCommonJS(index_exports);
// src/core/base/Adaptor.ts
var Adapter = class {
};
// src/core/constants/keywords.ts
var typescriptKeywords = /* @__PURE__ */ 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"
]);
// src/core/interface.ts
var SchemaType = /* @__PURE__ */ ((SchemaType2) => {
SchemaType2["schemas"] = "schemas";
SchemaType2["parameters"] = "parameters";
SchemaType2["responses"] = "responses";
SchemaType2["requestBodies"] = "requestBodies";
return SchemaType2;
})(SchemaType || {});
var NonArraySchemaType = /* @__PURE__ */ ((NonArraySchemaType2) => {
NonArraySchemaType2["object"] = "object";
NonArraySchemaType2["string"] = "string";
NonArraySchemaType2["number"] = "number";
NonArraySchemaType2["boolean"] = "boolean";
NonArraySchemaType2["integer"] = "integer";
NonArraySchemaType2["enum"] = "enum";
NonArraySchemaType2["file"] = "file";
return NonArraySchemaType2;
})(NonArraySchemaType || {});
var ArraySchemaType = /* @__PURE__ */ ((ArraySchemaType2) => {
ArraySchemaType2["array"] = "array";
return ArraySchemaType2;
})(ArraySchemaType || {});
var SchemaFormatType = /* @__PURE__ */ ((SchemaFormatType3) => {
SchemaFormatType3["string"] = "string";
SchemaFormatType3["number"] = "number";
SchemaFormatType3["boolean"] = "boolean";
SchemaFormatType3["file"] = "file";
SchemaFormatType3["binary"] = "binary";
SchemaFormatType3["blob"] = "blob";
return SchemaFormatType3;
})(SchemaFormatType || {});
var ParameterIn = /* @__PURE__ */ ((ParameterIn3) => {
ParameterIn3["header"] = "header";
ParameterIn3["body"] = "body";
ParameterIn3["query"] = "query";
ParameterIn3["cookie"] = "cookie";
ParameterIn3["path"] = "path";
ParameterIn3["formData"] = "formData";
return ParameterIn3;
})(ParameterIn || {});
var MediaTypes = /* @__PURE__ */ ((MediaTypes2) => {
MediaTypes2["JSON"] = "application/json";
MediaTypes2["TEXT"] = "text";
MediaTypes2["IMAGE"] = "image";
MediaTypes2["AUDIO"] = "audio";
MediaTypes2["VIDEO"] = "video";
return MediaTypes2;
})(MediaTypes || {});
var HttpMethods = /* @__PURE__ */ ((HttpMethods2) => {
HttpMethods2["GET"] = "get";
HttpMethods2["PUT"] = "put";
HttpMethods2["POST"] = "post";
HttpMethods2["DELETE"] = "delete";
HttpMethods2["OPTIONS"] = "options";
HttpMethods2["HEAD"] = "head";
HttpMethods2["PATCH"] = "patch";
HttpMethods2["TRACE"] = "trace";
return HttpMethods2;
})(HttpMethods || {});
var Adaptors = /* @__PURE__ */ ((Adaptors2) => {
Adaptors2["fetch"] = "fetch";
Adaptors2["axios"] = "axios";
return Adaptors2;
})(Adaptors || {});
// src/core/base/Base.ts
var import_undici = require("undici");
var SuccessHttpStatusCode = {
"200": "200",
// OK
"201": "201",
// Created
"202": "202",
// Accepted
"203": "203",
// Non-Authoritative Information
"204": "204",
// No Content
"205": "205",
// Reset Content
"206": "206",
// Partial Content
"207": "207",
// Multi_Status
"208": "208",
// Already_Reported
"226": "226"
// IM Used
};
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 ? this.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 = "") {
const name = this.normalize(this.camelCase(this.normalize(path)));
const suffix = method ? this.capitalize(this.upperCamelCase(`using_${method}`)) : "";
return name + suffix;
}
/**
* 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();
return text.split("_").filter(Boolean).map((t4, index) => index === 0 ? t4 : this.capitalize(t4)).join("");
}
/**
* Converts a string to UpperCamelCase.
* @param text - Input string.
* @returns - UpperCamelCase string.
*/
static upperCamelCase(text) {
return this.normalize(text).replaceAll("...", "").split("_").filter(Boolean).map(this.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 agent = new import_undici.Agent({
connect: {
rejectUnauthorized: false
}
});
try {
const { body } = await (0, import_undici.request)(url, {
method: "GET",
dispatcher: agent,
...requestInit
});
return body.json();
} catch (error) {
throw 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) {
for (const type in Object.values(MediaTypes)) {
if (new RegExp(type).test(mediaType)) {
return type;
}
}
return;
}
/**
* 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" && !this.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 uniqueEnums_ = [];
for (const enumObject of enums) {
if (uniqueEnums_.length === 0) {
uniqueEnums_.push(enumObject);
} else {
if (!uniqueEnums_.some((a) => this.isSameEnum(a, enumObject))) {
uniqueEnums_.push(enumObject);
}
}
}
return uniqueEnums_;
}
/**
* 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) => this.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 "$ref" in schema && typeof schema.$ref === "string";
}
};
// src/core/base/Provider.ts
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;
}
};
// src/core/generator/index.ts
var import_promises = require("fs/promises");
var import_prettier = require("prettier");
var import_typescript = require("typescript");
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 = import_typescript.factory.createSourceFile(
statements,
import_typescript.factory.createToken(import_typescript.SyntaxKind.EndOfFileToken),
import_typescript.NodeFlags.None
);
return (0, import_typescript.createPrinter)().printFile(sourceFile);
}
static async write(code, filepath) {
try {
await (0, import_promises.writeFile)(filepath, code);
} catch (error) {
console.error(error);
}
}
/**
* 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" /* query */
);
if (queryParameters.length > 0) {
const queryString = queryParameters.map(
(qp, index) => `${index === 0 ? "?" : "&"}${encodeURIComponent(qp.name)}={${Base.camelCase(Base.normalize(qp.name))}}`
).join("");
path += queryString;
}
const pathSegments = path.replaceAll("{", "${").split("$").filter(Boolean);
if (pathSegments.length === 1) {
return import_typescript.factory.createNoSubstitutionTemplateLiteral(basePath + path);
}
return import_typescript.factory.createTemplateExpression(
import_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 import_typescript.factory.createTemplateSpan(
import_typescript.factory.createIdentifier(match[1]),
!isLastSegment ? import_typescript.factory.createTemplateMiddle(match[2]) : import_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) => {
return comment.tag ? ` @${comment.tag} ${comment.comment ?? ""}` : ` ${comment.comment}`;
};
const formattedComments = "*\n" + comments.map(formatComment).join("\n").trim() + "\n";
(0, import_typescript.addSyntheticLeadingComment)(
node,
import_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 this.isBinarySchema(arraySchema.items);
}
const nonArraySchema = schema;
return nonArraySchema.format === "blob" /* blob */ || nonArraySchema.format === "binary" /* binary */ || nonArraySchema.type === "file" /* file */;
}
static toRequestBodyTypeNode(schema) {
return import_typescript.factory.createParameterDeclaration(
void 0,
void 0,
import_typescript.factory.createIdentifier("req"),
void 0,
this.toTypeNode(schema)
);
}
static toTypeNode(schema) {
const { type, ref } = schema;
if (ref) {
const identify = Base.ref2name(ref);
return import_typescript.factory.createTypeReferenceNode(
import_typescript.factory.createIdentifier(
identify === "unknown" ? identify : Base.upperCamelCase(identify)
)
);
}
switch (type) {
case "array" /* array */:
const { items } = schema;
return import_typescript.factory.createArrayTypeNode(this.toTypeNode(items));
case "object" /* object */:
const propsCount = Object.keys(schema.properties ?? {}).length;
if (!schema.properties || propsCount === 0) {
return import_typescript.factory.createTypeReferenceNode(import_typescript.factory.createIdentifier("Record"), [
import_typescript.factory.createToken(import_typescript.SyntaxKind.StringKeyword),
import_typescript.factory.createToken(import_typescript.SyntaxKind.UnknownKeyword)
]);
}
const props = Object.keys(schema.properties);
return import_typescript.factory.createTypeLiteralNode(
props.map((propKey) => {
const propSchema = schema.properties[propKey];
return import_typescript.factory.createPropertySignature(
void 0,
import_typescript.factory.createStringLiteral(propKey),
// When field is required, a refrence or binary value, don't add question mark.
schema.required || schema.ref || this.isBinarySchema(schema) ? void 0 : import_typescript.factory.createToken(import_typescript.SyntaxKind.QuestionToken),
this.toTypeNode(propSchema)
);
})
);
case "integer" /* integer */:
case "number" /* number */:
if (schema.enum) {
return import_typescript.factory.createUnionTypeNode(
schema.enum.map(
(e) => import_typescript.factory.createLiteralTypeNode(import_typescript.factory.createNumericLiteral(e))
)
);
}
return import_typescript.factory.createToken(import_typescript.SyntaxKind.NumberKeyword);
// case NonArraySchemaType.string:
case "boolean" /* boolean */:
return import_typescript.factory.createToken(import_typescript.SyntaxKind.BooleanKeyword);
case "file" /* file */:
return import_typescript.factory.createTypeReferenceNode(import_typescript.factory.createIdentifier("Blob"));
default:
const {
format: format2,
oneOf,
allOf,
anyOf,
type: type2,
enum: enum_
} = schema;
switch (format2) {
case "number" /* number */:
return import_typescript.factory.createToken(import_typescript.SyntaxKind.NumberKeyword);
case "string" /* string */:
return import_typescript.factory.createToken(import_typescript.SyntaxKind.StringKeyword);
case "boolean" /* boolean */:
return import_typescript.factory.createToken(import_typescript.SyntaxKind.BooleanKeyword);
case "blob" /* blob */:
case "binary" /* binary */:
return import_typescript.factory.createTypeReferenceNode(import_typescript.factory.createIdentifier("Blob"));
default:
}
if (enum_) {
return import_typescript.factory.createUnionTypeNode(
enum_.map(
(e) => import_typescript.factory.createLiteralTypeNode(import_typescript.factory.createStringLiteral(e))
)
);
}
if (type2 === "string" /* string */) {
return import_typescript.factory.createToken(import_typescript.SyntaxKind.StringKeyword);
}
if (oneOf) {
return import_typescript.factory.createUnionTypeNode(
oneOf.map((schema2) => this.toTypeNode(schema2))
);
}
if (anyOf) {
return import_typescript.factory.createUnionTypeNode(
anyOf.map((schema2) => this.toTypeNode(schema2))
);
}
if (allOf) {
return import_typescript.factory.createIntersectionTypeNode(
allOf.map((schema2) => this.toTypeNode(schema2))
);
}
if (type2 && typeof type2 === "string") {
return import_typescript.factory.createTypeReferenceNode(
type2 !== "unknown" && type2 !== "null" ? import_typescript.factory.createIdentifier(Base.upperCamelCase(type2)) : type2
);
}
}
return import_typescript.factory.createToken(import_typescript.SyntaxKind.UnknownKeyword);
}
static toDeclarationNode(parameters) {
const objectElements = [];
const typeObjectElements = [];
for (const parameter of parameters) {
if (parameter.ref) {
import_typescript.factory.createParameterDeclaration(
void 0,
void 0,
import_typescript.factory.createIdentifier(
Base.camelCase(Base.normalize(Base.ref2name(parameter.ref)))
),
void 0,
import_typescript.factory.createTypeReferenceNode(
import_typescript.factory.createIdentifier(
Base.upperCamelCase(Base.normalize(Base.ref2name(parameter.ref)))
)
),
void 0
);
} else {
const { name, schema, required } = parameter;
objectElements.push(
import_typescript.factory.createBindingElement(
void 0,
void 0,
import_typescript.factory.createIdentifier(Base.camelCase(Base.normalize(name)))
)
);
typeObjectElements.push(
import_typescript.factory.createPropertySignature(
[],
import_typescript.factory.createIdentifier(Base.camelCase(Base.normalize(name))),
required ? void 0 : import_typescript.factory.createToken(import_typescript.SyntaxKind.QuestionToken),
!schema ? import_typescript.factory.createToken(import_typescript.SyntaxKind.UnknownKeyword) : this.toTypeNode(schema)
)
);
}
}
return import_typescript.factory.createParameterDeclaration(
void 0,
void 0,
import_typescript.factory.createObjectBindingPattern(objectElements),
void 0,
import_typescript.factory.createTypeLiteralNode(typeObjectElements),
void 0
);
}
static toFormDataStatement(parameters, requestBody) {
const statements = [];
const fdDeclaration = import_typescript.factory.createVariableStatement(
void 0,
import_typescript.factory.createVariableDeclarationList(
[
import_typescript.factory.createVariableDeclaration(
import_typescript.factory.createIdentifier("fd"),
void 0,
void 0,
import_typescript.factory.createNewExpression(
import_typescript.factory.createIdentifier("FormData"),
void 0,
[]
)
)
],
import_typescript.NodeFlags.Const
)
);
statements.push(fdDeclaration);
parameters.forEach((parameter) => {
statements.push(
import_typescript.factory.createExpressionStatement(
import_typescript.factory.createBinaryExpression(
import_typescript.factory.createIdentifier(parameter.name),
import_typescript.factory.createToken(import_typescript.SyntaxKind.AmpersandAmpersandToken),
import_typescript.factory.createCallExpression(
import_typescript.factory.createPropertyAccessExpression(
import_typescript.factory.createIdentifier("fd"),
import_typescript.factory.createIdentifier("append")
),
void 0,
[
import_typescript.factory.createStringLiteral(parameter.name),
import_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" /* array */ && this.isBinarySchema(schemaByKey)) {
statements.push(
import_typescript.factory.createForOfStatement(
void 0,
import_typescript.factory.createVariableDeclarationList(
[import_typescript.factory.createVariableDeclaration("file")],
import_typescript.NodeFlags.Const
),
import_typescript.factory.createElementAccessExpression(
import_typescript.factory.createIdentifier("req"),
import_typescript.factory.createStringLiteral(key)
),
import_typescript.factory.createBlock([
import_typescript.factory.createExpressionStatement(
import_typescript.factory.createCallExpression(
import_typescript.factory.createPropertyAccessExpression(
import_typescript.factory.createIdentifier("fd"),
import_typescript.factory.createIdentifier("append")
),
[],
[
import_typescript.factory.createStringLiteral(key),
import_typescript.factory.createIdentifier("file"),
import_typescript.factory.createPropertyAccessExpression(
import_typescript.factory.createAsExpression(
import_typescript.factory.createIdentifier("file"),
import_typescript.factory.createTypeReferenceNode(
import_typescript.factory.createIdentifier("File"),
void 0
)
),
import_typescript.factory.createIdentifier("name")
)
]
)
)
])
)
);
} else {
if (schemaByKey.required) {
statements.push(
import_typescript.factory.createExpressionStatement(
import_typescript.factory.createCallExpression(
import_typescript.factory.createPropertyAccessExpression(
import_typescript.factory.createIdentifier("fd"),
import_typescript.factory.createIdentifier("append")
),
void 0,
[
import_typescript.factory.createStringLiteral(key),
schemaByKey.type === "string" ? import_typescript.factory.createElementAccessExpression(
import_typescript.factory.createIdentifier("req"),
import_typescript.factory.createStringLiteral(key)
) : import_typescript.factory.createCallExpression(
import_typescript.factory.createIdentifier("String"),
void 0,
[
import_typescript.factory.createElementAccessExpression(
import_typescript.factory.createIdentifier("req"),
import_typescript.factory.createStringLiteral(key)
)
]
)
]
)
)
);
} else {
statements.push(
import_typescript.factory.createExpressionStatement(
import_typescript.factory.createBinaryExpression(
import_typescript.factory.createElementAccessExpression(
import_typescript.factory.createIdentifier("req"),
import_typescript.factory.createStringLiteral(key)
),
import_typescript.factory.createToken(import_typescript.SyntaxKind.AmpersandAmpersandToken),
import_typescript.factory.createCallExpression(
import_typescript.factory.createPropertyAccessExpression(
import_typescript.factory.createIdentifier("fd"),
import_typescript.factory.createIdentifier("append")
),
void 0,
[
import_typescript.factory.createStringLiteral(key),
schemaByKey.type === "string" ? import_typescript.factory.createElementAccessExpression(
import_typescript.factory.createIdentifier("req"),
import_typescript.factory.createStringLiteral(key)
) : import_typescript.factory.createCallExpression(
import_typescript.factory.createIdentifier("String"),
void 0,
[
import_typescript.factory.createElementAccessExpression(
import_typescript.factory.createIdentifier("req"),
import_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 isRequestBodyBinary = requestBody?.schema && requestBody.schema.type === "array" /* array */ && this.isBinarySchema(requestBody.schema);
const parametersShouldPutInFormData = parameters.filter(
(p) => p.in === "formData" /* formData */ || p.schema && this.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) => this.isBinarySchema(p)
);
const hasBinaryInParameters = parameters.some(
(p) => p?.schema && this.isBinarySchema(p.schema)
);
const shouldPutParametersOrBodyInFormData = isFormDataRequest || isRequestBodyBinary || hasBinaryInParameters || isRequestBodyContainsBinary || parametersShouldPutInFormData.length > 0;
return import_typescript.factory.createBlock([
...shouldPutParametersOrBodyInFormData ? this.toFormDataStatement(
parametersShouldPutInFormData,
requestBody?.schema
) : [],
...adapter.client(
uri,
method,
parametersShouldNotPutInFormData,
requestBody,
response,
adapter,
shouldPutParametersOrBodyInFormData,
shouldParseResponseToJSON
)
]);
}
static schemaToStatemets(parsedDoc, adaptor, options) {
const statements = [];
const { apis, schemas = {}, enums } = parsedDoc;
const enumNames = [];
for (const enumObject of enums) {
enumNames.push(Base.capitalize(enumObject.name));
statements.push(
import_typescript.factory.createEnumDeclaration(
[import_typescript.factory.createToken(import_typescript.SyntaxKind.ExportKeyword)],
import_typescript.factory.createIdentifier(Base.upperCamelCase(enumObject.name)),
enumObject.enum.map((member) => {
return import_typescript.factory.createEnumMember(
import_typescript.factory.createStringLiteral(
typeof member === "string" ? member : `${member}_`
),
typeof member === "string" ? import_typescript.factory.createStringLiteral(member) : import_typescript.factory.createNumericLiteral(member)
);
})
)
);
}
for (const schemaKey in schemas) {
if (Object.hasOwnProperty.call(schemas, schemaKey) && !enumNames.includes(Base.upperCamelCase(schemaKey))) {
const schema = schemas[schemaKey];
statements.push(
import_typescript.factory.createTypeAliasDeclaration(
[import_typescript.factory.createModifier(import_typescript.SyntaxKind.ExportKeyword)],
import_typescript.factory.createIdentifier(Base.upperCamelCase(schemaKey)),
void 0,
this.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" /* JSON */ });
}
const shouldAddExtraMethodNameSuffix = requestBody.length > 1;
for (const req of requestBody) {
const statement = import_typescript.factory.createFunctionDeclaration(
[
import_typescript.factory.createModifier(import_typescript.SyntaxKind.ExportKeyword),
import_typescript.factory.createModifier(import_typescript.SyntaxKind.AsyncKeyword)
],
void 0,
Base.pathToFnName(uri, method, operationId) + (shouldAddExtraMethodNameSuffix ? Base.capitalize(req.type.split("/")[1]) : ""),
void 0,
[
parameters.length > 0 ? _Generator.toDeclarationNode(parameters) : void 0,
req?.schema ? _Generator.toRequestBodyTypeNode(req.schema) : void 0
].filter(Boolean),
void 0,
this.bodyBlock(
options.baseURL + uri,
method,
parameters,
req,
responses[0],
adaptor
)
);
this.addComments(
statement,
[
description && {
comment: description
},
summary && {
comment: summary
},
deprecated && {
tag: "deprecated"
}
].filter(Boolean)
);
statements.push(statement);
}
}
}
return statements;
}
static async prettier(code) {
return await (0, import_prettier.format)(code, {
parser: "typescript"
});
}
static async genCode(schema, initOptions, adaptor) {
const { importClientSource } = initOptions;
const statements = this.schemaToStatemets(schema, adaptor, {
baseURL: initOptions.baseURL ?? ""
});
let code = this.toCode(statements);
if (importClientSource) {
code = importClientSource + "\n\n" + code;
}
return await this.prettier(code);
}
};
// src/core/client/axios.ts
var import_typescript2 = require("typescript");
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";
/**
* Method that should generate and return the client-specific configuration statements.
*
* @returns {Statement[]} An array of TypeScript statements that define the client configuration.
*
* @throws {Error} Indicates that the method is not yet implemented and needs to be filled in.
*/
client(uri, method, parameters, requestBody, response, adapter, shouldUseFormData) {
const statements = [];
const inBody = parameters.filter((p) => !p.in || p.in === "body");
const inHeader = parameters.filter((p) => p.in === "header");
const toLiterlExpression = () => {
return import_typescript2.factory.createObjectLiteralExpression(
[
// Set the HTTP method
import_typescript2.factory.createPropertyAssignment(
import_typescript2.factory.createIdentifier(adapter.methodFieldName),
import_typescript2.factory.createStringLiteral(method.toUpperCase())
)
].concat(
// Add headers if there are any
inHeader.length > 0 ? import_typescript2.factory.createPropertyAssignment(
import_typescript2.factory.createIdentifier(adapter.headersFieldName),
import_typescript2.factory.createObjectLiteralExpression(
inHeader.map(
(p) => import_typescript2.factory.createPropertyAssignment(
import_typescript2.factory.createStringLiteral(p.name),
import_typescript2.factory.createCallExpression(
import_typescript2.factory.createIdentifier("encodeURIComponent"),
void 0,
[
import_typescript2.factory.createCallExpression(
import_typescript2.factory.createIdentifier("String"),
void 0,
[
import_typescript2.factory.createIdentifier(
Base.camelCase(Base.normalize(p.name))
)
]
)
]
)
)
)
)
) : []
).concat(
// Add body if needed
shouldUseFormData || inBody.length > 0 || requestBody?.schema ? import_typescript2.factory.createPropertyAssignment(
import_typescript2.factory.createIdentifier(adapter.bodyFieldName),
shouldUseFormData ? import_typescript2.factory.createIdentifier("fd") : inBody.length > 0 || requestBody?.schema && !Generator.isBinarySchema(requestBody.schema) ? import_typescript2.factory.createIdentifier("req") : import_typescript2.factory.createIdentifier("req")
) : []
),
true
);
};
statements.push(
import_typescript2.factory.createReturnStatement(
import_typescript2.factory.createCallExpression(
import_typescript2.factory.createIdentifier(adapter.name),
response?.schema ? [
Generator.toTypeNode(
response.schema
)
] : void 0,
[Generator.toUrlTemplate(uri, parameters), toLiterlExpression()]
)
)
);
return statements;
}
};
// src/core/client/fetch.ts
var import_typescript3 = require("typescript");
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
* @return - An array of generated TypeScript statements
*/
client(uri, method, parameters, requestBody, response, adapter, shouldUseFormData, shouldUseJSONResponse) {
const statements = [];
const inBody = parameters.filter((p) => !p.in || p.in === "body");
const inHeader = parameters.filter((p) => p.in === "header");
const toLiterlExpression = () => {
return import_typescript3.factory.createObjectLiteralExpression(
[
// Set the HTTP method
import_typescript3.factory.createPropertyAssignment(
import_typescript3.factory.createIdentifier(adapter.methodFieldName),
import_typescript3.factory.createStringLiteral(method.toUpperCase())
)
].concat(
// Add headers if there are any
inHeader.length > 0 ? import_typescript3.factory.createPropertyAssignment(
import_typescript3.factory.createIdentifier(adapter.headersFieldName),
import_typescript3.factory.createObjectLiteralExpression(
inHeader.map(
(p) => import_typescript3.factory.createPropertyAssignment(
import_typescript3.factory.createStringLiteral(p.name),
import_typescript3.factory.createCallExpression(
import_typescript3.factory.createIdentifier("encodeURIComponent"),
void 0,
[
import_typescript3.factory.createCallExpression(
import_typescript3.factory.createIdentifier("String"),
void 0,
[
import_typescript3.factory.createIdentifier(
Base.camelCase(Base.normalize(p.name))
)
]
)
]
)
)
)
)
) : []
).concat(
// Add body if needed
shouldUseFormData || inBody.length > 0 || requestBody?.schema ? import_typescript3.factory.createPropertyAssignment(
import_typescript3.factory.createIdentifier(adapter.bodyFieldName),
shouldUseFormData ? import_typescript3.factory.createIdentifier("fd") : inBody.length > 0 || requestBody?.schema && !Generator.isBinarySchema(requestBody.schema) ? import_typescript3.factory.createCallExpression(
import_typescript3.factory.createPropertyAccessExpression(
import_typescript3.factory.createIdentifier("JSON"),
import_typescript3.factory.createIdentifier("stringify")
),
[],
[
requestBody ? import_typescript3.factory.createIdentifier("req") : import_typescript3.factory.createObjectLiteralExpression(
inBody.map(
(b) => import_typescript3.factory.createShorthandPropertyAssignment(
import_typescript3.factory.createIdentifier(b.name)
)
),
true
)
]
) : (
// One File parameter
import_typescript3.factory.createIdentifier("req")
)
) : []
),
true
);
};
statements.push(
import_typescript3.factory.createReturnStatement(
shouldUseJSONResponse ? (
// Handle JSON response with proper type checking
import_typescript3.factory.createCallExpression(
import_typescript3.factory.createPropertyAccessExpression(
import_typescript3.factory.createCallExpression(
import_typescript3.factory.createIdentifier(adapter.name),
void 0,
[
Generator.toUrlTemplate(uri, parameters),
toLiterlExpression()
]
),
import_typescript3.factory.createIdentifier("then")
),
void 0,
[
import_typescript3.factory.createArrowFunction(
[import_typescript3.factory.createModifier(import_typescript3.SyntaxKind.AsyncKeyword)],
[],
[
import_typescript3.factory.createParameterDeclaration(
void 0,
void 0,
import_typescript3.factory.createIdentifier("response")
)
],
void 0,
import_typescript3.factory.createToken(import_typescript3.SyntaxKind.EqualsGreaterThanToken),
response?.schema ? import_typescript3.factory.createAsExpression(
import_typescript3.factory.createParenthesizedExpression(
import_typescript3.factory.createAwaitExpression(
import_typescript3.factory.createCallExpression(
import_typescript3.factory.createPropertyAccessExpression(
import_typescript3.factory.createIdentifier("response"),
import_typescript3.factory.createIdentifier("json")
),
void 0,
[]
)
)
),
response?.schema ? Generator.toTypeNode(response.schema) : import_typescript3.factory.createToken(import_typescript3.SyntaxKind.UnknownKeyword)
) : import_typescript3.factory.createParenthesizedExpression(
import_typescript3.factory.createAwaitExpression(
import_typescript3.factory.createCallExpression(
import_typescript3.factory.createPropertyAccessExpression(
import_typescript3.factory.createIdentifier("response"),
import_typescript3.factory.createIdentifier("json")
),
void 0,
[]
)
)
)
)
]
)
) : (
// Simple fetch call without JSON parsing
import_typescript3.factory.createCallExpression(
import_typescript3.factory.createIdentifier(adapter.name),
void 0,
[Generator.toUrlTemplate(uri, parameters), toLiterlExpression()]
)
)
)
);
return statements;
}
};
// src/openapi/index.ts
var import_logger = require("@moccona/logger");
// src/openapi/V2.ts
var V2 = class {
doc;
constructor(doc) {
this.doc = doc;
}
/**
* Is array schema.
*/
isOpenAPIArraySchema(schema) {
return typeof schema === "object" && schema.type === "array";
}
/**
* OpenAPI schema to base schema.
*/
getSchemaByRef(schema, reserveRef = false, enums = [], upLevelSchemaKey = "") {
let refName = "";
if (Base.isRef(schema)) {
refName = Base.upperCamelCase(Base.ref2name(schema.$ref));
if (reserveRef) {
return {
type: upLevelSchemaKey + refName
};
}
if (!this.doc.definitions) {
this.doc.definitions = {};
}
schema = this.doc.definitions[Base.ref2name(schema.$ref, this.doc)];
}
return this.toBaseSchema(schema, enums, "", upLevelSchemaKey + refName);
}
/**
* Transform all OpenAPI schema to Base Schema
*/
toBaseSchema(schema, enums = [], schemaKey = "", upLevelSchemaKey = "") {
if (!schema) {
return {
type: "unknown"
};
}
if (Base.isRef(schema)) {
return this.getSchemaByRef(schema, true);
}
if (this.isOpenAPIArraySchema(schema)) {
const { type, description, items, required } = schema;
return {
type,
required: !!required,
description,
items: this.toBaseSchema(items, enums, schemaKey, upLevelSchemaKey)
};
} else {
const {
required = [],
allOf,
anyOf,
description,
enum: enum_,
format: format2,
oneOf,
properties = {}
} = schema;
let { type } = schema;
if (enum_ && type !== "boolean") {
const name = Base.upperCamelCase(upLevelSchemaKey) + Base.upperCamelCase(schemaKey);
const enumObject = {
name,
enum: [...new Set(enum_)]
};
const sameObject = Base.findSameSchema(enumObject, enums);
i