@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 lines • 145 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/core/base/Adaptor.ts","../src/core/constants/keywords.ts","../src/core/interface.ts","../src/core/base/Base.ts","../src/core/base/Provider.ts","../src/core/generator/index.ts","../src/core/client/axios.ts","../src/core/client/fetch.ts","../src/openapi/index.ts","../src/openapi/V2.ts","../src/openapi/V3.ts","../src/openapi/V3_1.ts","../src/vite-plugin/index.ts"],"sourcesContent":["export * from \"./core\";\nexport * from \"./core/interface\";\nexport * from \"./openapi\";\nexport * from \"./vite-plugin\";\n","/**\n * @file Adapter abstract class definition\n * @author [Your Name]\n * @description Base adapter implementation for various code generation tools\n */\n\nimport type {\n MediaTypeObject,\n ParameterObject,\n} from \"@apicodegen/core/interface\";\nimport type { Statement } from \"typescript\";\n\n/**\n * Base adapter for tool\n * This abstract class serves as the foundation for implementing adapters for different code generation tools\n */\nexport abstract class Adapter {\n /**\n * @abstract The unique name/identifier for this adapter implementation\n */\n abstract readonly name: string;\n\n /**\n * @abstract The name of the field used to specify the HTTP method in API calls\n */\n abstract readonly methodFieldName: string;\n\n /**\n * @abstract The name of the field used to specify the request body in API calls\n */\n abstract readonly bodyFieldName: string;\n\n /**\n * @abstract The name of the field used to specify request headers in API calls\n */\n abstract readonly headersFieldName: string;\n\n /**\n * @abstract The name of the field used to specify query parameters in API calls\n */\n abstract readonly queryFieldName: string;\n\n /**\n * @abstract\n * @param {string} uri - The API endpoint URI\n * @param {string} method - The HTTP method (e.g., GET, POST, etc.)\n * @param {ParameterObject[]} parameters - An array of parameters for the API call\n * @param {MediaTypeObject | undefined} requestBody - The request body payload (if applicable)\n * @param {MediaTypeObject | undefined} response - The expected response format (if applicable)\n * @param {Adapter} adapter - An instance of the adapter being used\n * @param {boolean} useFormData - Flag indicating whether to use FormData for the request body\n * @param {boolean} useJSONResponse - Flag indicating whether the response should be parsed as JSON\n * @returns {Statement[]} An array of TypeScript AST statements representing the generated code\n */\n abstract client(\n uri: string,\n method: string,\n parameters: ParameterObject[],\n requestBody: MediaTypeObject | undefined,\n response: MediaTypeObject | undefined,\n adapter: Adapter,\n useFormData: boolean,\n useJSONResponse: boolean,\n ): Statement[];\n}\n","export const typescriptKeywords = new Set([\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"enum\",\n \"export\",\n \"extends\",\n \"false\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"null\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"true\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"as\",\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n \"abstract\",\n \"any\",\n \"async\",\n \"await\",\n \"constructor\",\n \"declare\",\n \"from\",\n \"get\",\n \"is\",\n \"module\",\n \"namespace\",\n \"never\",\n \"require\",\n \"set\",\n \"type\",\n \"unknown\",\n \"readonly\",\n \"of\",\n \"asserts\",\n \"infer\",\n \"keyof\",\n \"boolean\",\n \"number\",\n \"string\",\n \"symbol\",\n \"object\",\n \"undefined\",\n \"bigint\",\n]);\n","/**\n * Simple represenration for JSON object\n */\nexport type JSONValue = {\n [K: string]:\n | string\n | number\n | boolean\n | JSONValue\n | (string | number | boolean | JSONValue)[];\n};\n\nexport enum SchemaType {\n schemas = \"schemas\",\n parameters = \"parameters\",\n responses = \"responses\",\n requestBodies = \"requestBodies\",\n}\n\nexport enum NonArraySchemaType {\n \"object\" = \"object\",\n \"string\" = \"string\",\n \"number\" = \"number\",\n \"boolean\" = \"boolean\",\n \"integer\" = \"integer\",\n \"enum\" = \"enum\",\n \"file\" = \"file\",\n}\n\nexport enum ArraySchemaType {\n \"array\" = \"array\",\n}\n\nexport enum SchemaFormatType {\n \"string\" = \"string\",\n \"number\" = \"number\",\n \"boolean\" = \"boolean\",\n \"file\" = \"file\",\n \"binary\" = \"binary\",\n \"blob\" = \"blob\",\n}\n\nexport enum ParameterIn {\n \"header\" = \"header\",\n \"body\" = \"body\",\n \"query\" = \"query\",\n \"cookie\" = \"cookie\",\n \"path\" = \"path\",\n \"formData\" = \"formData\",\n}\n\nexport interface ReferenceObject {\n $ref: string;\n}\n\nexport interface EnumSchemaObject {\n name: string;\n enum: (string | number)[];\n}\n\nexport interface SingleTypeSchemaObject {\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n type: keyof typeof NonArraySchemaType | string;\n description?: string;\n allOf?: SchemaObject[];\n anyOf?: SchemaObject[];\n deprecated?: boolean;\n enum?: (string | number)[];\n format?: keyof typeof SchemaFormatType;\n oneOf?: SchemaObject[];\n properties?: Record<string, SchemaObject>;\n readonly?: boolean;\n required?: string[] | boolean;\n ref?: string;\n}\n\nexport interface ArrayTypeSchemaObject {\n type: keyof typeof ArraySchemaType;\n items?: SchemaObject;\n required?: boolean;\n description?: string;\n ref?: string;\n}\n\nexport type SchemaObject = SingleTypeSchemaObject | ArrayTypeSchemaObject;\n\nexport type ParameterObject = {\n name: string;\n in: keyof typeof ParameterIn;\n schema?: SchemaObject;\n required?: boolean;\n description?: string;\n deprecated?: boolean;\n ref?: string;\n};\n\nexport enum MediaTypes {\n JSON = \"application/json\",\n TEXT = \"text\",\n IMAGE = \"image\",\n AUDIO = \"audio\",\n VIDEO = \"video\",\n}\n\nexport type MediaTypeObject = {\n type: MediaTypes | keyof typeof MediaTypes;\n schema?: SchemaObject;\n};\n\nexport type ResponsesObject = Record<string, MediaTypeObject[]>;\n\nexport type RequestBodyObject = ResponsesObject;\n\nexport enum HttpMethods {\n GET = \"get\",\n PUT = \"put\",\n POST = \"post\",\n DELETE = \"delete\",\n OPTIONS = \"options\",\n HEAD = \"head\",\n PATCH = \"patch\",\n TRACE = \"trace\",\n}\n\nexport type OperationObject = {\n method: string;\n summary?: string;\n description?: string;\n operationId?: string;\n externalDocs?: { url: string; description?: string }[];\n parameters?: ParameterObject[];\n requestBody?: MediaTypeObject[];\n responses: MediaTypeObject[];\n deprecated?: boolean;\n};\n\nexport type PathObject = {\n ref?: string;\n summary?: string;\n description?: string;\n parameters?: ParameterObject[];\n} & Partial<Record<HttpMethods, OperationObject>>;\n\nexport type PathsObject = Record<string, OperationObject[]>;\n\nexport type FetchDocRequestInit = {\n method?: string;\n body?: string | FormData;\n headers?: Record<string, string>;\n};\n\nexport enum Adaptors {\n fetch = \"fetch\",\n axios = \"axios\",\n}\n\nexport type ProviderInitOptions = {\n docURL: string;\n output: string;\n baseURL?: string;\n importClientSource?: string;\n requestOptions?: FetchDocRequestInit;\n verbose?: boolean;\n adaptor?: keyof typeof Adaptors;\n};\n\nexport interface ProviderInitResult {\n readonly enums: EnumSchemaObject[];\n readonly schemas: Record<string, SchemaObject>;\n readonly parameters: Record<string, ParameterObject>;\n readonly responses: Record<string, ResponsesObject>;\n readonly requestBodies: Record<string, RequestBodyObject>;\n readonly apis: PathsObject;\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * @file Base class implementation\n * @author [Your Name]\n * @description Base utility class providing common methods for code generation and API handling\n */\n\nimport { typescriptKeywords } from \"@apicodegen/core/constants/keywords\";\nimport type {\n EnumSchemaObject,\n FetchDocRequestInit,\n ReferenceObject,\n SchemaObject,\n SingleTypeSchemaObject,\n} from \"@apicodegen/core/interface\";\nimport { MediaTypes } from \"@apicodegen/core/interface\";\nimport { Agent, request } from \"undici\";\n\n/**\n * Represents success HTTP status codes.\n * Each key is a string representation of a success HTTP status code.\n */\nexport const SuccessHttpStatusCode = {\n \"200\": \"200\", // OK\n \"201\": \"201\", // Created\n \"202\": \"202\", // Accepted\n \"203\": \"203\", // Non-Authoritative Information\n \"204\": \"204\", // No Content\n \"205\": \"205\", // Reset Content\n \"206\": \"206\", // Partial Content\n \"207\": \"207\", // Multi_Status\n \"208\": \"208\", // Already_Reported\n \"226\": \"226\", // IM Used\n};\n\n/**\n * Base abstract class providing common utility methods.\n */\nexport abstract class Base {\n protected constructor() {\n if (new.target === Base) {\n throw new Error(\"Cannot instantiate abstract class\");\n }\n }\n\n /**\n * Converts a reference string to a meaningful name.\n * @param ref - The reference string to process.\n * @param [doc] - Optional document reference for context.\n * @returns - The processed name.\n */\n static ref2name(ref: string, doc?: any): string {\n const paths = ref.replace(/^#/, \"\").split(\"/\").filter(Boolean);\n\n if (!doc) {\n return paths.slice(-1)[0];\n }\n\n let temporary = doc as unknown;\n let lastPath = \"\";\n for (const path of paths) {\n // For handling path prefix with ~1\n const adjustedPath = path.replaceAll(\"~1\", \"/\");\n temporary = (temporary as Record<string, any>)[adjustedPath];\n lastPath = adjustedPath;\n }\n\n if (!temporary) {\n return \"unknown\";\n }\n\n return (temporary as unknown as { $ref: string }).$ref\n ? this.ref2name((temporary as unknown as { $ref: string }).$ref, doc)\n : lastPath;\n }\n\n /**\n * Converts an API path to a function name.\n * @param path - The API endpoint path.\n * @param [method] - The HTTP method (e.g., GET, POST).\n * @param [operationId] - Unique identifier for the operation.\n * @returns - The generated function name.\n */\n static pathToFnName(\n path: string,\n method?: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _operationId: string = \"\",\n ) {\n const name = this.normalize(this.camelCase(this.normalize(path)));\n const suffix = method\n ? this.capitalize(this.upperCamelCase(`using_${method}`))\n : \"\";\n\n return name + suffix;\n }\n\n /**\n * Normalizes a string by replacing special characters and avoiding TypeScript keywords.\n * @param text - Input text to normalize.\n * @returns - The normalized string.\n */\n static normalize(text: string) {\n if (typescriptKeywords.has(text)) {\n text += \"_\";\n }\n return text.replace(/[/\\-_{}():\\s`,*<>$#.]/gm, \"_\").replace(/^\\d./gm, '').replaceAll(\"...\", \"\");\n }\n\n /**\n * Capitalizes the first character of a string.\n * @param text - Input string.\n * @returns - Capitalized string.\n */\n static capitalize(text: string) {\n text = text.trim();\n return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;\n }\n\n /**\n * Converts a string to camelCase.\n * @param text - Input string.\n * @returns - CamelCase string.\n */\n static camelCase(text: string) {\n text = text.trim();\n return text\n .split(\"_\")\n .filter(Boolean)\n .map((t, index) => (index === 0 ? t : this.capitalize(t)))\n .join(\"\");\n }\n\n /**\n * Converts a string to UpperCamelCase.\n * @param text - Input string.\n * @returns - UpperCamelCase string.\n */\n static upperCamelCase(text: string) {\n return this.normalize(text)\n .replaceAll(\"...\", \"\")\n .split(\"_\")\n .filter(Boolean)\n .map(this.capitalize)\n .join(\"\");\n }\n\n /**\n * Fetches documentation from a given URL.\n * @param url - The URL to fetch the documentation from.\n * @param requestInit - Additional request parameters.\n * @returns - A promise resolving to the fetched documentation data.\n */\n static async fetchDoc<T = unknown>(\n url: string,\n requestInit: FetchDocRequestInit = {},\n ): Promise<T> {\n const agent = new Agent({\n connect: {\n rejectUnauthorized: false,\n },\n });\n\n // eslint-disable-next-line no-useless-catch\n try {\n const { body } = await request(url, {\n method: \"GET\",\n dispatcher: agent,\n ...requestInit,\n });\n return body.json() as T;\n } catch (error) {\n throw error;\n }\n }\n\n /**\n * Determines the media type from a given media type string.\n * @param mediaType - The media type string to evaluate.\n * @returns - The matched MediaTypes or null.\n */\n static getMediaType(mediaType: string): MediaTypes | undefined {\n // eslint-disable-next-line @typescript-eslint/no-for-in-array\n for (const type in Object.values(MediaTypes)) {\n if (new RegExp(type).test(mediaType)) {\n return type as MediaTypes;\n }\n }\n return;\n }\n\n /**\n * Checks if a schema is a valid enum type that isn't boolean.\n * @param a - The schema object to evaluate.\n * @returns - True if the schema is a valid non-boolean enum.\n */\n static isValidEnumType(a: SchemaObject) {\n return a.type !== \"boolean\" && !this.isBooleanEnum(a);\n }\n\n /**\n * Checks if a schema represents a boolean enum.\n * @param a - The schema object to evaluate.\n * @returns - True if the schema is a boolean enum.\n */\n static isBooleanEnum(a: SchemaObject) {\n return (\n a.type === \"boolean\" ||\n !!(a as SingleTypeSchemaObject).enum?.some(\n (member) => typeof member === \"boolean\",\n )\n );\n }\n\n /**\n * Checks if two enum schemas are identical.\n * @param a - First enum schema to compare.\n * @param b - Second enum schema to compare.\n * @returns - True if the enums are identical.\n */\n private static isSameEnum(a: EnumSchemaObject, b: EnumSchemaObject) {\n return (\n a.enum.length === b.enum.length &&\n a.enum.sort().every((v, index) => v === b.enum.sort()[index])\n );\n }\n\n /**\n * Filters out duplicate enum schemas from an array.\n * @param enums - Array of enum schemas to process.\n * @returns - Array of unique enum schemas.\n */\n static uniqueEnums(enums: EnumSchemaObject[]) {\n const uniqueEnums_: EnumSchemaObject[] = [];\n for (const enumObject of enums) {\n if (uniqueEnums_.length === 0) {\n uniqueEnums_.push(enumObject);\n } else {\n if (!uniqueEnums_.some((a) => this.isSameEnum(a, enumObject))) {\n uniqueEnums_.push(enumObject);\n }\n }\n }\n return uniqueEnums_;\n }\n\n /**\n * Finds the first occurrence of a matching enum schema in an array.\n * @param a - The enum schema to find.\n * @param enums - Array of enum schemas to search.\n * @returns - The found schema or undefined.\n */\n static findSameSchema(a: EnumSchemaObject, enums: EnumSchemaObject[]) {\n return enums.find((b) => this.isSameEnum(b, a));\n }\n\n /**\n * Checks if an object is a reference object.\n * @param schema - The object to check.\n * @returns - True if the object is a reference.\n */\n\n static isRef(schema: any): schema is ReferenceObject {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n return \"$ref\" in schema && typeof schema.$ref === \"string\";\n }\n}\n","/**\n * @file Provider abstract class.\n * This file defines the `Provider` abstract class, which serves as a base for providers responsible for parsing\n * and processing API documentation.\n */\n\nimport type {\n EnumSchemaObject,\n FetchDocRequestInit,\n OperationObject,\n ParameterObject,\n ProviderInitOptions,\n ProviderInitResult,\n RequestBodyObject,\n ResponsesObject,\n SchemaObject,\n} from \"@apicodegen/core/interface\";\n\n/**\n * Abstract Provider Class.\n *\n * The Provider class is designed to be extended by specific implementations (e.g., OpenAPI 2 provider, OpenAPI 3 provider).\n * It handles the initialization of the provider and the parsing of documentation into structured data.\n *\n * @example\n *\n * ```ts\n * /// Example of how this class might be used by a subclass:\n * class OpenAPIProvider extends Provider {\n * /// Implement the parse method to handle OpenAPI-specific documentation parsing.\n * parse(doc: unknown): ProviderInitResult {\n * /// Implementation details...\n * }\n * }\n *\n * /// Initializing a provider with configuration and documentation data:\n * const initOptions: ProviderInitOptions = {\n * docURL: \"https://example.com/api/swagger.json\",\n * baseURL: \"https://api.example.com\",\n * output: \"./generated\",\n * requestOptions: {\n * headers: { \"Content-Type\": \"application/json\" },\n * },\n * importClientSource: \"generated/client\",\n * };\n *\n * const docData = fetchSwaggerDoc();\n * const provider = new OpenAPIProvider(initOptions, docData);\n * ```\n */\nexport abstract class Provider\n implements ProviderInitResult, ProviderInitOptions\n{\n /** collection of enum schemas */\n readonly enums: EnumSchemaObject[] = [];\n /** collection of schemas indexed by name */\n readonly schemas: Record<string, SchemaObject> = {};\n /** collection of parameters indexed by name */\n readonly parameters: Record<string, ParameterObject> = {};\n /** collection of API responses indexed by name */\n readonly responses: Record<string, ResponsesObject> = {};\n /** collection of request bodies indexed by name */\n readonly requestBodies: Record<string, RequestBodyObject> = {};\n /** collection of API endpoints (operations) indexed by path */\n readonly apis: Record<string, OperationObject[]> = {};\n\n /** URL for fetching API documentation */\n readonly docURL: string;\n /** base URL for API endpoints */\n readonly baseURL: string;\n /** output directory for generated code */\n readonly output: string;\n /** request options for API documentation fetch */\n readonly requestOptions: FetchDocRequestInit;\n /** source path for imported client */\n readonly importClientSource: string;\n\n /**\n * Provider Constructor.\n * @param {ProviderInitOptions} initOptions - Initial configuration for the provider.\n * @param {unknown} doc - Raw API documentation data to be parsed.\n */\n constructor(initOptions: ProviderInitOptions, doc: unknown) {\n this.docURL = initOptions.docURL;\n this.baseURL = initOptions.baseURL ?? \"\";\n this.output = initOptions.output ?? \".\";\n this.requestOptions = initOptions.requestOptions ?? {};\n this.importClientSource = initOptions.importClientSource ?? \"\";\n\n const { enums, schemas, requestBodies, responses, parameters, apis } =\n this.parse(doc);\n\n this.enums = enums;\n this.schemas = schemas;\n this.responses = responses;\n this.parameters = parameters;\n this.requestBodies = requestBodies;\n this.apis = apis;\n }\n\n /**\n * Abstract Parse Method.\n * @abstract\n * @param {unknown} doc - Raw API documentation data.\n * @returns {ProviderInitResult} - Parsed documentation data.\n *\n * This method must be implemented by subclasses to parse the raw documentation into structured data.\n */\n abstract parse(doc: unknown): ProviderInitResult;\n}\n","/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */\n/* eslint-disable no-case-declarations */\nimport { Adapter } from \"@apicodegen/core/base/Adaptor\";\nimport { Base } from \"@apicodegen/core/base/Base\";\nimport type {\n ArrayTypeSchemaObject,\n MediaTypeObject,\n ParameterObject,\n ProviderInitOptions,\n ProviderInitResult,\n SchemaObject,\n SingleTypeSchemaObject,\n} from \"@apicodegen/core/interface\";\nimport {\n ArraySchemaType,\n MediaTypes,\n NonArraySchemaType,\n ParameterIn,\n SchemaFormatType,\n} from \"@apicodegen/core/interface\";\nimport { writeFile } from \"fs/promises\";\nimport { format } from \"prettier\";\nimport type {\n BindingElement,\n Block,\n Node,\n ParameterDeclaration,\n PropertySignature,\n Statement,\n TypeNode,\n} from \"typescript\";\nimport {\n addSyntheticLeadingComment,\n createPrinter,\n factory as t,\n NodeFlags,\n SyntaxKind,\n} from \"typescript\";\n\n/**\n * Represents a comment object with optional tag and message.\n */\nexport type CommentObject = {\n tag?: string;\n comment: string;\n};\n\n/**\n * Array of comment objects to be added to the code.\n */\nexport type Comments = CommentObject[];\n\nexport class Generator {\n /**\n * Converts an array of TypeScript statements into a formatted string of code.\n *\n * @param statements - The array of TypeScript statement nodes.\n * @returns Formatted code as a string.\n * @throws {Error} If no valid statements are provided.\n */\n static toCode(statements: Statement[]): string {\n if (statements.length === 0) {\n return \"// No api declaration found.\";\n }\n\n const sourceFile = t.createSourceFile(\n statements,\n t.createToken(SyntaxKind.EndOfFileToken),\n NodeFlags.None,\n );\n\n return createPrinter().printFile(sourceFile);\n }\n\n static async write(code: string, filepath: string) {\n try {\n await writeFile(filepath, code);\n } catch (error) {\n console.error(error);\n }\n }\n\n /**\n * Converts a path string with parameters into a TypeScript template expression.\n * Handles query parameters and path placeholders.\n *\n * @param path - The base path string containing placeholders.\n * @param parameters - Array of parameter objects defining the parameters.\n * @param basePath - Optional base path to prepend (default: \"\").\n * @returns A TypeScript template expressi\n */\n static toUrlTemplate(\n path: string,\n parameters: ParameterObject[],\n basePath = \"\",\n ) {\n // Extract query parameters\n const queryParameters = parameters.filter(\n (p) => p.in === ParameterIn.query,\n );\n\n if (queryParameters.length > 0) {\n const queryString = queryParameters\n .map(\n (qp, index) =>\n `${index === 0 ? \"?\" : \"&\"}${encodeURIComponent(qp.name)}={${Base.camelCase(Base.normalize(qp.name))}}`,\n )\n .join(\"\");\n path += queryString;\n }\n\n // Split the path into segments\n const pathSegments = path.replaceAll(\"{\", \"${\").split(\"$\").filter(Boolean);\n\n // If path segments only got one item, it means there are no parameters in path. So just return the path literal.\n if (pathSegments.length === 1) {\n return t.createNoSubstitutionTemplateLiteral(basePath + path);\n }\n\n return t.createTemplateExpression(\n t.createTemplateHead(basePath + pathSegments[0]),\n pathSegments.slice(1).map((segment, index) => {\n const match = /^{(.+)}(.+)?/gm.exec(segment);\n const isLastSegment = index === pathSegments.length - 2;\n\n if (!match) {\n throw new Error(`Invalid path segment: ${segment}`);\n }\n\n return t.createTemplateSpan(\n t.createIdentifier(match[1]),\n !isLastSegment\n ? t.createTemplateMiddle(match[2])\n : t.createTemplateTail(match[2] || \"\"),\n );\n }),\n );\n }\n\n /**\n * Adds synthetic comments to a TypeScript AST node.\n *\n * @param node - The target AST node.\n * @param comments - Array of comment objects to add.\n */\n static addComments(node: Node, comments: Comments) {\n if (!Array.isArray(comments) || comments.filter(Boolean).length === 0)\n return;\n\n const formatComment = (comment: CommentObject): string => {\n return comment.tag\n ? ` @${comment.tag} ${comment.comment ?? \"\"}`\n : ` ${comment.comment}`;\n };\n\n const formattedComments =\n \"*\\n\" + comments.map(formatComment).join(\"\\n\").trim() + \"\\n\";\n\n addSyntheticLeadingComment(\n node,\n SyntaxKind.MultiLineCommentTrivia,\n formattedComments,\n true,\n );\n }\n\n /**\n * Checks if a schema represents a binary type.\n *\n * @param schema - The schema object to check.\n * @returns true if the schema is a binary type, false otherwise.\n */\n static isBinarySchema(schema: SchemaObject): boolean {\n if (schema.type === \"array\") {\n const arraySchema = schema as ArrayTypeSchemaObject;\n return this.isBinarySchema(arraySchema.items!);\n }\n\n const nonArraySchema = schema as SingleTypeSchemaObject;\n return (\n nonArraySchema.format === SchemaFormatType.blob ||\n nonArraySchema.format === SchemaFormatType.binary ||\n nonArraySchema.type === SchemaFormatType.file\n );\n }\n\n static toRequestBodyTypeNode(schema: SchemaObject) {\n return t.createParameterDeclaration(\n undefined,\n undefined,\n t.createIdentifier(\"req\"),\n undefined,\n this.toTypeNode(schema),\n );\n }\n\n static toTypeNode(schema: SchemaObject): TypeNode {\n const { type, ref } = schema;\n\n if (ref) {\n const identify = Base.ref2name(ref);\n return t.createTypeReferenceNode(\n t.createIdentifier(\n identify === \"unknown\" ? identify : Base.upperCamelCase(identify),\n ),\n );\n }\n\n switch (type) {\n case ArraySchemaType.array:\n const { items } = schema as ArrayTypeSchemaObject;\n return t.createArrayTypeNode(this.toTypeNode(items!));\n case NonArraySchemaType.object:\n const propsCount = Object.keys(schema.properties ?? {}).length;\n if (!schema.properties || propsCount === 0) {\n // Record<string, unknown>\n return t.createTypeReferenceNode(t.createIdentifier(\"Record\"), [\n t.createToken(SyntaxKind.StringKeyword),\n t.createToken(SyntaxKind.UnknownKeyword),\n ]);\n }\n\n const props = Object.keys(schema.properties);\n\n return t.createTypeLiteralNode(\n props.map((propKey) => {\n const propSchema = schema.properties![propKey];\n return t.createPropertySignature(\n undefined,\n t.createStringLiteral(propKey),\n // When field is required, a refrence or binary value, don't add question mark.\n schema.required || schema.ref || this.isBinarySchema(schema)\n ? undefined\n : t.createToken(SyntaxKind.QuestionToken),\n this.toTypeNode(propSchema),\n );\n }),\n );\n case NonArraySchemaType.integer:\n case NonArraySchemaType.number:\n if (schema.enum) {\n return t.createUnionTypeNode(\n schema.enum.map((e) =>\n t.createLiteralTypeNode(t.createNumericLiteral(e)),\n ),\n );\n }\n return t.createToken(SyntaxKind.NumberKeyword);\n // case NonArraySchemaType.string:\n case NonArraySchemaType.boolean:\n return t.createToken(SyntaxKind.BooleanKeyword);\n case NonArraySchemaType.file:\n return t.createTypeReferenceNode(t.createIdentifier(\"Blob\"));\n default:\n const {\n format,\n oneOf,\n allOf,\n anyOf,\n type,\n enum: enum_,\n } = schema as SingleTypeSchemaObject;\n\n switch (format) {\n case SchemaFormatType.number:\n return t.createToken(SyntaxKind.NumberKeyword);\n case SchemaFormatType.string:\n return t.createToken(SyntaxKind.StringKeyword);\n case SchemaFormatType.boolean:\n return t.createToken(SyntaxKind.BooleanKeyword);\n case SchemaFormatType.blob:\n case SchemaFormatType.binary:\n return t.createTypeReferenceNode(t.createIdentifier(\"Blob\"));\n default:\n }\n\n if (enum_) {\n return t.createUnionTypeNode(\n enum_.map((e) =>\n t.createLiteralTypeNode(t.createStringLiteral(e as string)),\n ),\n );\n }\n\n if (type === NonArraySchemaType.string) {\n return t.createToken(SyntaxKind.StringKeyword);\n }\n\n if (oneOf) {\n return t.createUnionTypeNode(\n oneOf.map((schema) => this.toTypeNode(schema)),\n );\n }\n\n if (anyOf) {\n return t.createUnionTypeNode(\n anyOf.map((schema) => this.toTypeNode(schema)),\n );\n }\n\n if (allOf) {\n return t.createIntersectionTypeNode(\n allOf.map((schema) => this.toTypeNode(schema)),\n );\n }\n\n if (type && typeof type === \"string\") {\n return t.createTypeReferenceNode(\n type !== \"unknown\" && type !== \"null\"\n ? t.createIdentifier(Base.upperCamelCase(type))\n : type,\n );\n }\n }\n\n return t.createToken(SyntaxKind.UnknownKeyword);\n }\n\n static toDeclarationNode(\n parameters: ParameterObject[],\n ): ParameterDeclaration {\n const objectElements: BindingElement[] = [];\n const typeObjectElements: PropertySignature[] = [];\n\n for (const parameter of parameters) {\n if (parameter.ref) {\n t.createParameterDeclaration(\n undefined,\n undefined,\n t.createIdentifier(\n Base.camelCase(Base.normalize(Base.ref2name(parameter.ref))),\n ),\n undefined,\n t.createTypeReferenceNode(\n t.createIdentifier(\n Base.upperCamelCase(Base.normalize(Base.ref2name(parameter.ref))),\n ),\n ),\n undefined,\n );\n } else {\n const { name, schema, required } = parameter;\n objectElements.push(\n t.createBindingElement(\n undefined,\n undefined,\n t.createIdentifier(Base.camelCase(Base.normalize(name))),\n ),\n );\n\n typeObjectElements.push(\n t.createPropertySignature(\n [],\n t.createIdentifier(Base.camelCase(Base.normalize(name))),\n required ? undefined : t.createToken(SyntaxKind.QuestionToken),\n !schema\n ? t.createToken(SyntaxKind.UnknownKeyword)\n : this.toTypeNode(schema),\n ),\n );\n }\n }\n\n return t.createParameterDeclaration(\n undefined,\n undefined,\n t.createObjectBindingPattern(objectElements),\n undefined,\n t.createTypeLiteralNode(typeObjectElements),\n undefined,\n );\n }\n\n static toFormDataStatement(\n parameters: ParameterObject[],\n requestBody?: SchemaObject,\n ): Statement[] {\n const statements: Statement[] = [];\n const fdDeclaration = t.createVariableStatement(\n undefined,\n t.createVariableDeclarationList(\n [\n t.createVariableDeclaration(\n t.createIdentifier(\"fd\"),\n undefined,\n undefined,\n t.createNewExpression(\n t.createIdentifier(\"FormData\"),\n undefined,\n [],\n ),\n ),\n ],\n NodeFlags.Const,\n ),\n );\n\n statements.push(fdDeclaration);\n\n parameters.forEach((parameter) => {\n statements.push(\n t.createExpressionStatement(\n t.createBinaryExpression(\n t.createIdentifier(parameter.name),\n t.createToken(SyntaxKind.AmpersandAmpersandToken),\n t.createCallExpression(\n t.createPropertyAccessExpression(\n t.createIdentifier(\"fd\"),\n t.createIdentifier(\"append\"),\n ),\n undefined,\n [\n t.createStringLiteral(parameter.name),\n t.createIdentifier(parameter.name),\n ],\n ),\n ),\n ),\n );\n });\n\n if (\n requestBody &&\n requestBody.type === \"object\" &&\n requestBody.properties &&\n Object.keys(requestBody.properties).length !== 0\n ) {\n Object.keys(requestBody.properties).forEach((key) => {\n const schemaByKey = requestBody.properties![key];\n if (\n schemaByKey.type === ArraySchemaType.array &&\n this.isBinarySchema(schemaByKey)\n ) {\n statements.push(\n t.createForOfStatement(\n undefined,\n t.createVariableDeclarationList(\n [t.createVariableDeclaration(\"file\")],\n NodeFlags.Const,\n ),\n t.createElementAccessExpression(\n t.createIdentifier(\"req\"),\n t.createStringLiteral(key),\n ),\n t.createBlock([\n t.createExpressionStatement(\n t.createCallExpression(\n t.createPropertyAccessExpression(\n t.createIdentifier(\"fd\"),\n t.createIdentifier(\"append\"),\n ),\n [],\n [\n t.createStringLiteral(key),\n t.createIdentifier(\"file\"),\n t.createPropertyAccessExpression(\n t.createAsExpression(\n t.createIdentifier(\"file\"),\n t.createTypeReferenceNode(\n t.createIdentifier(\"File\"),\n undefined,\n ),\n ),\n t.createIdentifier(\"name\"),\n ),\n ],\n ),\n ),\n ]),\n ),\n );\n } else {\n if (schemaByKey.required) {\n statements.push(\n t.createExpressionStatement(\n t.createCallExpression(\n t.createPropertyAccessExpression(\n t.createIdentifier(\"fd\"),\n t.createIdentifier(\"append\"),\n ),\n undefined,\n [\n t.createStringLiteral(key),\n schemaByKey.type === \"string\"\n ? t.createElementAccessExpression(\n t.createIdentifier(\"req\"),\n t.createStringLiteral(key),\n )\n : t.createCallExpression(\n t.createIdentifier(\"String\"),\n undefined,\n [\n t.createElementAccessExpression(\n t.createIdentifier(\"req\"),\n t.createStringLiteral(key),\n ),\n ],\n ),\n ],\n ),\n ),\n );\n } else {\n statements.push(\n t.createExpressionStatement(\n t.createBinaryExpression(\n t.createElementAccessExpression(\n t.createIdentifier(\"req\"),\n t.createStringLiteral(key),\n ),\n t.createToken(SyntaxKind.AmpersandAmpersandToken),\n t.createCallExpression(\n t.createPropertyAccessExpression(\n t.createIdentifier(\"fd\"),\n t.createIdentifier(\"append\"),\n ),\n undefined,\n [\n t.createStringLiteral(key),\n schemaByKey.type === \"string\"\n ? t.createElementAccessExpression(\n t.createIdentifier(\"req\"),\n t.createStringLiteral(key),\n )\n : t.createCallExpression(\n t.createIdentifier(\"String\"),\n undefined,\n [\n t.createElementAccessExpression(\n t.createIdentifier(\"req\"),\n t.createStringLiteral(key),\n ),\n ],\n ),\n ],\n ),\n ),\n ),\n );\n }\n }\n });\n }\n\n return statements;\n }\n\n static bodyBlock(\n uri: string,\n method: string,\n parameters: ParameterObject[],\n requestBody: MediaTypeObject | undefined,\n response: MediaTypeObject | undefined,\n adapter: Adapter,\n ): Block {\n const isFormDataRequest =\n requestBody &&\n [\"multipart/form-data\", \"application/x-www-form-urlencoded\"].includes(\n requestBody.type,\n );\n\n const shouldParseResponseToJSON = \"application/json\" === response?.type;\n\n // Ignore one and only blob parameter.\n const isRequestBodyBinary =\n requestBody?.schema &&\n requestBody.schema.type === ArraySchemaType.array &&\n this.isBinarySchema(requestBody.schema);\n\n const parametersShouldPutInFormData = parameters.filter(\n (p) =>\n p.in === ParameterIn.formData ||\n (p.schema && this.isBinarySchema(p.schema)),\n );\n\n const parametersShouldNotPutInFormData = parameters.filter(\n (p) => !parametersShouldPutInFormData.includes(p),\n );\n\n const isRequestBodyContainsBinary =\n requestBody?.schema &&\n \"properties\" in requestBody.schema &&\n Object.values(requestBody.schema?.properties ?? {}).some((p) =>\n this.isBinarySchema(p),\n );\n\n const hasBinaryInParameters = parameters.some(\n (p) => p?.schema && this.isBinarySchema(p.schema),\n );\n\n const shouldPutParametersOrBodyInFormData =\n isFormDataRequest ||\n isRequestBodyBinary ||\n hasBinaryInParameters ||\n isRequestBodyContainsBinary ||\n parametersShouldPutInFormData.length > 0;\n\n return t.createBlock([\n ...(shouldPutParametersOrBodyInFormData\n ? this.toFormDataStatement(\n parametersShouldPutInFormData,\n requestBody?.schema,\n )\n : []),\n ...adapter.client(\n uri,\n method,\n parametersShouldNotPutInFormData,\n requestBody,\n response,\n adapter,\n shouldPutParametersOrBodyInFormData,\n shouldParseResponseToJSON,\n ),\n ]);\n }\n\n static schemaToStatemets(\n parsedDoc: ProviderInitResult,\n adaptor: Adapter,\n options: Omit<ProviderInitOptions, \"docURL\" | \"output\" | \"requestOptions\">,\n ): Statement[] {\n const statements = [] as Statement[];\n const { apis, schemas = {}, enums } = parsedDoc;\n\n const enumNames: string[] = [];\n\n for (const enumObject of enums) {\n enumNames.push(Base.capitalize(enumObject.name));\n statements.push(\n t.createEnumDeclaration(\n [t.createToken(SyntaxKind.ExportKeyword)],\n t.createIdentifier(Base.upperCamelCase(enumObject.name)),\n enumObject.enum.map((member) => {\n return t.createEnumMember(\n t.createStringLiteral(\n typeof member === \"string\" ? member : `${member}_`,\n ),\n typeof member === \"string\"\n ? t.createStringLiteral(member)\n : t.createNumericLiteral(member),\n );\n }),\n ),\n );\n }\n\n for (const schemaKey in schemas) {\n if (\n Object.hasOwnProperty.call(schemas, schemaKey) &&\n !enumNames.includes(Base.upperCamelCase(schemaKey))\n ) {\n const schema = schemas[schemaKey];\n statements.push(\n t.createTypeAliasDeclaration(\n [t.createModifier(SyntaxKind.ExportKeyword)],\n t.createIdentifier(Base.upperCamelCase(schemaKey)),\n undefined,\n this.toTypeNode(schema),\n ),\n );\n }\n }\n\n for (const uri in apis) {\n const operations = apis[uri];\n for (const operation of operations) {\n const {\n method,\n operationId,\n requestBody = [],\n responses = [],\n summary,\n deprecated,\n description,\n } = operation;\n\n let { parameters = [] } = operation;\n\n parameters = parameters.filter((p) => p.in !== \"cookie\");\n\n // Add a default request, with no schema.\n if (requestBody.length === 0) {\n requestBody.push({ type: MediaTypes.JSON });\n }\n\n const shouldAddExtraMethodNameSuffix = requestBody.length > 1;\n\n for (const req of requestBody) {\n const statement = t.createFunctionDeclaration(\n [\n t.createModifier(SyntaxKind.ExportKeyword),\n t.createModifier(SyntaxKind.AsyncKeyword),\n ],\n undefined,\n Base.pathToFnName(uri, method, operationId) +\n (shouldAddExtraMethodNameSuffix\n ? Base.capitalize(req.type.split(\"/\")[1])\n : \"\"),\n undefined,\n [\n parameters.length > 0\n ? Generator.toDeclarationNode(parameters)\n : undefined,\n req?.schema\n ? Generator.toRequestBodyTypeNode(req.schema)\n : undefined,\n ].filter(Boolean) as ParameterDeclaration[],\n undefined,\n this.bodyBlock(\n options.baseURL + uri,\n method,\n parameters,\n req,\n responses[0],\n adaptor,\n ),\n );\n\n this.addComments(\n statement,\n [\n description && {\n comment: description,\n },\n summary && {\n comment: summary,\n },\n deprecated && {\n tag: \"deprecated\",\n },\n ].filter(Boolean) as CommentObject[],\n );\n statements.push(statement);\n }\n }\n }\n\n return statements;\n }\n\n static async prettier(code: string) {\n return await format(code, {\n parser: \"typescript\",\n });\n }\n\n static async genCode(\n schema: ProviderInitResult,\n initOptions: ProviderInitOptions,\n adaptor: Adapter,\n ) {\n const { importClientSource } = initOptions;\n const statements = this.schemaToStatemets(schema, adaptor, {\n baseURL: initOptions.baseURL ?? \"\",\n });\n let code = this.toCode(statements);\n\n if (importClientSource) {\n code = importClientSource + \"\\n\\n\" + code;\n }\n\n return await this.prettier(code);\n }\n}\n","/* eslint-disable unicorn/prefer-spread */\n/**\n * File containing the implementation of the AxiosAdapter class.\n * This adapter is responsible for generating code that uses the Axios HTTP client library.\n */\n\nimport { Adapter } from \"@apicodegen/core/base/Adaptor\";\nimport { Base } from \"@apicodegen/core/base/Base\";\nimport { Generator } from \"@apicodegen/core/generator\";\nimport { MediaTypeObject, ParameterObject } from \"@apicodegen/core/interface\";\nimport type { Statement, TypeReferenceNode } from \"typescript\";\nimport { factory as t } from \"typescript\";\n\n/**\n * Adapter class implementing support for generating code that makes use of the Axios HTTP client library.\n * This class defines custom behavior and field mappings specific to the Axios client.\n */\nexport class AxiosAdapter extends Adapter {\n /**\n * Name of the field used to specify the HTTP method in the request configuration.\n */\n readonly methodFieldName = \"method\";\n\n /**\n * Name of the field used to specify the request body (data) in the request configuration.\n */\n readonly bodyFieldName = \"data\";\n\n /**\n * Name of the field used to specify the request headers in the request configuration.\n */\n readonly headersFieldName = \"headers\";\n\n /**\n * Name of the field used to specify the query parameters in the request configuration.\n */\n readonly queryFieldName = \"params\";\n\n /**\n * The name of the client this adapter is configured for, which is 'axios' in this case.\n */\n readonly name = \"axios\";\n\n /**\n * Method that should generate and return the client-specific configuration statements.\n *\n * @returns {Statement[]} An array of TypeScript statements that define the client configuration.\n *\n * @throws {Error} Indicates that the method is not yet implemented and needs to be filled in.\n */\n public client(\n uri: string,\n method: string,\n parameters: ParameterObject[],\n requestBody: MediaTypeObject | undefined,\n response: MediaTypeObject | undefined,\n adapter: Adapter,\n shouldUseFormData: boolean,\n ): Statement[] {\n const statements: Statement[] = [];\n\n // Split parameters into header and body parameters\n const inBody = parameters.filter((p) => !p.in || p.in === \"body\");\n const inHeader = parameters.filter((p) => p.in === \"header\");\n\n /**\n * Creates the literal object expression for fetch options\n * including method, headers, and body.\n * @returns - The constructed fetch options object\n */\n const toLiterlExpression = () => {\n return t.createObjectLiteralExpression(\n [\n // Set the HTTP method\n t.createPropertyAssignment(\n t.createIdentifier(adapter.methodFieldName),\n t.createStringLiteral(method.toUpperCase()),\n ),\n ]\n .concat(\n // Add headers if there are any\n inHeader.length > 0\n ? t.createPropertyAssignment(\n t.createIdentifier(adapter.headersFieldName),\n t.createObjectLiteralExpression(\n inHeader.map((p) =>\n t.createPropertyAssignment(\n t.createStringLiteral(p.name),\n t.createCallExpression(\n t.createIdentifier(\"encodeURIComponent\"),\n undefined,\n [\n t.createCallExpression(\n t.createIdentifier(\"String\"),\n undefined,\n [\n t.createIdentifier(\n Base.camelCase(Base.normalize(p.name)),\n ),\n ],\n ),\n ],\n ),\n ),\n ),\n ),\n )\n : [],\n )\n .concat(\n // Add body if needed\n shouldUseFormData || inBody.length > 0 || requestBody?.schema\n ? t.createPropertyAssignment(\n t.createIdentifier(adapter.bodyFieldName),\n shouldUseFormData\n ? t.createIdentifier(\"fd\")\n : inBody.length > 0 ||\n (requestBody?.schema &&\n !Generator.isBinarySchema(requestBody.schema))\n ? t.createIdentifier(\"req\")\n : t.createIdentifier(\"req\"),\n )\n : [],\n ),\n true,\n );\n };\n\n // Construct the fetch call and return statement\n statements.push(\n t.createReturnStatement(\n t.createCallExpression(\n t.createIdentifier(adapter.name),\n response?.schema\n ? [\n Generator.toTypeNode(\n response.schema,\n ) as unknown as TypeReferenceNode,\n ]\n : undefined,\n [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()],\n ),\n ),\n );\n\n return statements;\n }\n}\n","/* eslint-disable unicorn/prefer-spread */\nimport { Adapter } from \"@apicodegen/core/base/Adaptor\";\nimport { Base } from \"@apicodegen/core/base/Base\";\nimport { Generator } from \"@apicodegen/core/generator\";\nimport { MediaTypeObject, ParameterObject } from \"@apicodegen/core/interface\";\nimport type { Statement } from \"typescript\";\nimport { factory as t, SyntaxKind } from \"typescript\";\n\n/**\n * FetchAdapter is an adapter class that generates client-side fetch requests.\n * It handles parameters, headers, and request bodies to construct proper fetch calls.\n */\nexport class FetchAdapter extends Adapter {\n readonly methodFieldName = \"method\";\n readonly bodyFieldName = \"body\";\n readonly headersFieldName = \"headers\";\n readonly queryFieldName = \"\";\n readonly name = \"fetch\";\n\n /**\n * Generates client code for making API requests using the Fetch API.\