UNPKG

@moccona/apicodegen

Version:

A powerful OpenAPI code generator that automatically generates TypeScript API client code from OpenAPI specifications.

1 lines 171 kB
{"version":3,"file":"index.mjs","names":["t","t","t","logger"],"sources":["../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/errors.ts","../src/core/generator/index.ts","../src/core/client/axios.ts","../src/core/client/fetch.ts","../src/core/config.ts","../src/core/logger.ts","../src/openapi/VersionedProvider.ts","../src/openapi/V2.ts","../src/openapi/V3.ts","../src/openapi/V3_1.ts","../src/openapi/index.ts","../src/vite-plugin/index.ts"],"sourcesContent":["/**\n * @file Adapter abstract class definition\n * @author wp.l\n * @description Base adapter implementation for various code generation tools\n */\n\nimport type { Statement } from 'typescript';\nimport type { MediaTypeObject, ParameterObject } from '../interface.js';\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\t/**\n\t * @abstract The unique name/identifier for this adapter implementation\n\t */\n\tabstract readonly name: string;\n\n\t/**\n\t * @abstract The name of the field used to specify the HTTP method in API calls\n\t */\n\tabstract readonly methodFieldName: string;\n\n\t/**\n\t * @abstract The name of the field used to specify the request body in API calls\n\t */\n\tabstract readonly bodyFieldName: string;\n\n\t/**\n\t * @abstract The name of the field used to specify request headers in API calls\n\t */\n\tabstract readonly headersFieldName: string;\n\n\t/**\n\t * @abstract The name of the field used to specify query parameters in API calls\n\t */\n\tabstract readonly queryFieldName: string;\n\n\t/**\n\t * @abstract\n\t * @param {string} uri - The API endpoint URI\n\t * @param {string} method - The HTTP method (e.g., GET, POST, etc.)\n\t * @param {ParameterObject[]} parameters - An array of parameters for the API call\n\t * @param {MediaTypeObject | undefined} requestBody - The request body payload (if applicable)\n\t * @param {MediaTypeObject | undefined} response - The expected response format (if applicable)\n\t * @param {Adapter} adapter - An instance of the adapter being used\n\t * @param {boolean} useFormData - Flag indicating whether to use FormData for the request body\n\t * @param {boolean} useJSONResponse - Flag indicating whether the response should be parsed as JSON\n\t * @param {boolean} isEventStream - Flag indicating whether the response is a text/event-stream (SSE) stream; when true, the adapter must return the raw response without parsing\n\t * @returns {Statement[]} An array of TypeScript AST statements representing the generated code\n\t */\n\tabstract client(\n\t\turi: string,\n\t\tmethod: string,\n\t\tparameters: ParameterObject[],\n\t\trequestBody: MediaTypeObject | undefined,\n\t\tresponse: MediaTypeObject | undefined,\n\t\tadapter: Adapter,\n\t\tuseFormData: boolean,\n\t\tuseJSONResponse: boolean,\n\t\tisEventStream: boolean\n\t): Statement[];\n}\n","export const typescriptKeywords = new Set([\n\t'break',\n\t'case',\n\t'catch',\n\t'class',\n\t'const',\n\t'continue',\n\t'debugger',\n\t'default',\n\t'delete',\n\t'do',\n\t'else',\n\t'enum',\n\t'export',\n\t'extends',\n\t'false',\n\t'finally',\n\t'for',\n\t'function',\n\t'if',\n\t'import',\n\t'in',\n\t'instanceof',\n\t'new',\n\t'null',\n\t'return',\n\t'super',\n\t'switch',\n\t'this',\n\t'throw',\n\t'true',\n\t'try',\n\t'typeof',\n\t'var',\n\t'void',\n\t'while',\n\t'with',\n\t'as',\n\t'implements',\n\t'interface',\n\t'let',\n\t'package',\n\t'private',\n\t'protected',\n\t'public',\n\t'static',\n\t'yield',\n\t'abstract',\n\t'any',\n\t'async',\n\t'await',\n\t'constructor',\n\t'declare',\n\t'from',\n\t'get',\n\t'is',\n\t'module',\n\t'namespace',\n\t'never',\n\t'require',\n\t'set',\n\t'type',\n\t'unknown',\n\t'readonly',\n\t'of',\n\t'asserts',\n\t'infer',\n\t'keyof',\n\t'boolean',\n\t'number',\n\t'string',\n\t'symbol',\n\t'object',\n\t'undefined',\n\t'bigint',\n]);\n","/**\n * Simple represenration for JSON object\n */\nexport type JSONValue = {\n\t[K: string]:\n\t\t| string\n\t\t| number\n\t\t| boolean\n\t\t| JSONValue\n\t\t| (string | number | boolean | JSONValue)[];\n};\n\nexport enum SchemaType {\n\tschemas = 'schemas',\n\tparameters = 'parameters',\n\tresponses = 'responses',\n\trequestBodies = 'requestBodies',\n}\n\nexport enum NonArraySchemaType {\n\tobject = 'object',\n\tstring = 'string',\n\tnumber = 'number',\n\tboolean = 'boolean',\n\tinteger = 'integer',\n\tenum = 'enum',\n\tfile = 'file',\n}\n\nexport enum ArraySchemaType {\n\tarray = 'array',\n}\n\nexport enum SchemaFormatType {\n\tstring = 'string',\n\tnumber = 'number',\n\tboolean = 'boolean',\n\tfile = 'file',\n\tbinary = 'binary',\n\tblob = 'blob',\n}\n\nexport enum ParameterIn {\n\theader = 'header',\n\tbody = 'body',\n\tquery = 'query',\n\tcookie = 'cookie',\n\tpath = 'path',\n\tformData = 'formData',\n}\n\nexport interface ReferenceObject {\n\t$ref: string;\n}\n\nexport interface EnumSchemaObject {\n\tname: string;\n\tenum: (string | number)[];\n}\n\nexport interface SingleTypeSchemaObject {\n\t// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n\ttype: keyof typeof NonArraySchemaType | string;\n\tdescription?: string;\n\tallOf?: SchemaObject[];\n\tanyOf?: SchemaObject[];\n\tdeprecated?: boolean;\n\tenum?: (string | number)[];\n\tformat?: keyof typeof SchemaFormatType;\n\toneOf?: SchemaObject[];\n\tproperties?: Record<string, SchemaObject>;\n\treadonly?: boolean;\n\trequired?: string[] | boolean;\n\tref?: string;\n\tisRef?: boolean;\n}\n\nexport interface ArrayTypeSchemaObject {\n\ttype: keyof typeof ArraySchemaType;\n\titems?: SchemaObject;\n\trequired?: boolean;\n\tdescription?: string;\n\tref?: string;\n}\n\nexport type SchemaObject = SingleTypeSchemaObject | ArrayTypeSchemaObject;\n\nexport type ParameterObject = {\n\tname: string;\n\tin: keyof typeof ParameterIn;\n\tschema?: SchemaObject;\n\trequired?: boolean;\n\tdescription?: string;\n\tdeprecated?: boolean;\n\tref?: string;\n};\n\nexport enum MediaTypes {\n\tJSON = 'application/json',\n\tEVENT_STREAM = 'text/event-stream',\n\tTEXT = 'text',\n\tIMAGE = 'image',\n\tAUDIO = 'audio',\n\tVIDEO = 'video',\n}\n\nexport type MediaTypeObject = {\n\ttype: MediaTypes | keyof typeof MediaTypes;\n\tschema?: SchemaObject;\n};\n\nexport type ResponsesObject = Record<string, MediaTypeObject[]>;\n\nexport type RequestBodyObject = ResponsesObject;\n\nexport enum HttpMethods {\n\tGET = 'get',\n\tPUT = 'put',\n\tPOST = 'post',\n\tDELETE = 'delete',\n\tOPTIONS = 'options',\n\tHEAD = 'head',\n\tPATCH = 'patch',\n\tTRACE = 'trace',\n}\n\nexport type OperationObject = {\n\tmethod: string;\n\tsummary?: string;\n\tdescription?: string;\n\toperationId?: string;\n\texternalDocs?: { url: string; description?: string }[];\n\tparameters?: ParameterObject[];\n\trequestBody?: MediaTypeObject[];\n\tresponses: MediaTypeObject[];\n\tdeprecated?: boolean;\n};\n\nexport type PathObject = {\n\tref?: string;\n\tsummary?: string;\n\tdescription?: string;\n\tparameters?: ParameterObject[];\n} & Partial<Record<HttpMethods, OperationObject>>;\n\nexport type PathsObject = Record<string, OperationObject[]>;\n\nexport type FetchDocRequestInit = {\n\tmethod?: string;\n\tbody?: string | FormData;\n\theaders?: Record<string, string>;\n};\n\nexport enum Adaptors {\n\tfetch = 'fetch',\n\taxios = 'axios',\n}\n\nexport type ProviderInitOptions = {\n\tdocURL: string;\n\toutput: string;\n\tbaseURL?: string;\n\timportClientSource?: string;\n\trequestOptions?: FetchDocRequestInit;\n\tverbose?: boolean;\n\tadaptor?: keyof typeof Adaptors;\n};\n\nexport interface ProviderInitResult {\n\treadonly enums: EnumSchemaObject[];\n\treadonly schemas: Record<string, SchemaObject>;\n\treadonly parameters: Record<string, ParameterObject>;\n\treadonly responses: Record<string, ResponsesObject>;\n\treadonly requestBodies: Record<string, RequestBodyObject>;\n\treadonly apis: PathsObject;\n}\n","/**\n * @file Base class implementation\n * @author wp.l\n * @description Base utility class providing common methods for code generation and API handling\n */\n\nimport { Agent, request } from 'undici';\nimport { typescriptKeywords } from '../constants/keywords.js';\nimport type {\n\tEnumSchemaObject,\n\tFetchDocRequestInit,\n\tReferenceObject,\n\tSchemaObject,\n\tSingleTypeSchemaObject,\n} from '../interface.js';\nimport { MediaTypes } from '../interface.js';\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\t'200': '200', // OK\n\t'201': '201', // Created\n\t'202': '202', // Accepted\n\t'203': '203', // Non-Authoritative Information\n\t'204': '204', // No Content\n\t'205': '205', // Reset Content\n\t'206': '206', // Partial Content\n\t'207': '207', // Multi_Status\n\t'208': '208', // Already_Reported\n\t'226': '226', // IM Used\n};\n\n/**\n * Base abstract class providing common utility methods.\n */\nexport abstract class Base {\n\tprotected constructor() {\n\t\tif (new.target === Base) {\n\t\t\tthrow new Error('Cannot instantiate abstract class');\n\t\t}\n\t}\n\n\t/**\n\t * Converts a reference string to a meaningful name.\n\t * @param ref - The reference string to process.\n\t * @param [doc] - Optional document reference for context.\n\t * @returns - The processed name.\n\t */\n\tstatic ref2name(ref: string, doc?: any): string {\n\t\tconst paths = ref.replace(/^#/, '').split('/').filter(Boolean);\n\n\t\tif (!doc) {\n\t\t\treturn paths.slice(-1)[0];\n\t\t}\n\n\t\tlet temporary = doc as unknown;\n\t\tlet lastPath = '';\n\t\tfor (const path of paths) {\n\t\t\t// For handling path prefix with ~1\n\t\t\tconst adjustedPath = path.replaceAll('~1', '/');\n\t\t\ttemporary = (temporary as Record<string, any>)[adjustedPath];\n\t\t\tlastPath = adjustedPath;\n\t\t}\n\n\t\tif (!temporary) {\n\t\t\treturn 'unknown';\n\t\t}\n\n\t\treturn (temporary as unknown as { $ref: string }).$ref\n\t\t\t? Base.ref2name((temporary as unknown as { $ref: string }).$ref, doc)\n\t\t\t: lastPath;\n\t}\n\n\t/**\n\t * Converts an API path to a function name.\n\t * @param path - The API endpoint path.\n\t * @param [method] - The HTTP method (e.g., GET, POST).\n\t * @param [operationId] - Unique identifier for the operation.\n\t * @returns - The generated function name.\n\t */\n\tstatic pathToFnName(\n\t\tpath: string,\n\t\tmethod?: string,\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t_operationId: string = ''\n\t) {\n\t\tconst name = Base.normalize(Base.camelCase(Base.normalize(path)));\n\t\tconst suffix = method\n\t\t\t? Base.capitalize(Base.upperCamelCase(`using_${method}`))\n\t\t\t: '';\n\n\t\treturn name + suffix;\n\t}\n\n\t/**\n\t * Normalizes a string by replacing special characters and avoiding TypeScript keywords.\n\t * @param text - Input text to normalize.\n\t * @returns - The normalized string.\n\t */\n\tstatic normalize(text: string) {\n\t\tif (typescriptKeywords.has(text)) {\n\t\t\ttext += '_';\n\t\t}\n\t\treturn text\n\t\t\t.replace(/[/\\-_{}():\\s`,*<>$#.]/gm, '_')\n\t\t\t.replace(/^\\d./gm, '')\n\t\t\t.replaceAll('...', '');\n\t}\n\n\t/**\n\t * Capitalizes the first character of a string.\n\t * @param text - Input string.\n\t * @returns - Capitalized string.\n\t */\n\tstatic capitalize(text: string) {\n\t\ttext = text.trim();\n\t\treturn `${text.charAt(0).toUpperCase()}${text.slice(1)}`;\n\t}\n\n\t/**\n\t * Converts a string to camelCase.\n\t * @param text - Input string.\n\t * @returns - CamelCase string.\n\t */\n\tstatic camelCase(text: string) {\n\t\ttext = text.trim();\n\t\tconst parts = text.split('_').filter(Boolean);\n\t\twhile (parts[0]?.match(/^\\d/)) {\n\t\t\tparts.shift();\n\t\t}\n\t\treturn parts\n\t\t\t.map((t, index) => (index === 0 ? t : Base.capitalize(t)))\n\t\t\t.join('');\n\t}\n\n\t/**\n\t * Converts a string to UpperCamelCase.\n\t * @param text - Input string.\n\t * @returns - UpperCamelCase string.\n\t */\n\tstatic upperCamelCase(text: string) {\n\t\treturn Base.normalize(text)\n\t\t\t.replaceAll('...', '')\n\t\t\t.split('_')\n\t\t\t.filter(Boolean)\n\t\t\t.map(Base.capitalize)\n\t\t\t.join('');\n\t}\n\n\t/**\n\t * Fetches documentation from a given URL.\n\t * @param url - The URL to fetch the documentation from.\n\t * @param requestInit - Additional request parameters.\n\t * @returns - A promise resolving to the fetched documentation data.\n\t */\n\tstatic async fetchDoc<T = unknown>(\n\t\turl: string,\n\t\trequestInit: FetchDocRequestInit = {}\n\t): Promise<T> {\n\t\tconst agent = new Agent({\n\t\t\tconnect: { rejectUnauthorized: false },\n\t\t});\n\n\t\tconst { body, statusCode } = await request(url, {\n\t\t\tmethod: 'GET',\n\t\t\tdispatcher: agent,\n\t\t\t...requestInit,\n\t\t});\n\n\t\tif (statusCode >= 400) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to fetch OpenAPI documentation from ${url}: HTTP ${statusCode}`\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\treturn body.json() as T;\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to parse JSON response from ${url}: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Determines the media type from a given media type string.\n\t * @param mediaType - The media type string to evaluate.\n\t * @returns - The matched MediaTypes or null.\n\t */\n\tstatic getMediaType(mediaType: string): MediaTypes | undefined {\n\t\tconst mediaTypeValues = Object.values(MediaTypes) as string[];\n\t\tconst found = mediaTypeValues.find((type) => mediaType.includes(type));\n\t\treturn found as MediaTypes | undefined;\n\t}\n\n\t/**\n\t * Checks if a schema is a valid enum type that isn't boolean.\n\t * @param a - The schema object to evaluate.\n\t * @returns - True if the schema is a valid non-boolean enum.\n\t */\n\tstatic isValidEnumType(a: SchemaObject) {\n\t\treturn a.type !== 'boolean' && !Base.isBooleanEnum(a);\n\t}\n\n\t/**\n\t * Checks if a schema represents a boolean enum.\n\t * @param a - The schema object to evaluate.\n\t * @returns - True if the schema is a boolean enum.\n\t */\n\tstatic isBooleanEnum(a: SchemaObject) {\n\t\treturn (\n\t\t\ta.type === 'boolean' ||\n\t\t\t!!(a as SingleTypeSchemaObject).enum?.some(\n\t\t\t\t(member) => typeof member === 'boolean'\n\t\t\t)\n\t\t);\n\t}\n\n\t/**\n\t * Checks if two enum schemas are identical.\n\t * @param a - First enum schema to compare.\n\t * @param b - Second enum schema to compare.\n\t * @returns - True if the enums are identical.\n\t */\n\tprivate static isSameEnum(a: EnumSchemaObject, b: EnumSchemaObject) {\n\t\treturn (\n\t\t\ta.enum.length === b.enum.length &&\n\t\t\ta.enum.sort().every((v, index) => v === b.enum.sort()[index])\n\t\t);\n\t}\n\n\t/**\n\t * Filters out duplicate enum schemas from an array.\n\t * @param enums - Array of enum schemas to process.\n\t * @returns - Array of unique enum schemas.\n\t */\n\tstatic uniqueEnums(enums: EnumSchemaObject[]): EnumSchemaObject[] {\n\t\tconst enumMap = new Map<string, Set<string | number>>();\n\n\t\tfor (const e of enums) {\n\t\t\tconst existing = enumMap.get(e.name);\n\t\t\tif (existing) {\n\t\t\t\t// Merge enum values with the same name\n\t\t\t\tfor (const value of e.enum) {\n\t\t\t\t\texisting.add(value);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tenumMap.set(e.name, new Set(e.enum));\n\t\t\t}\n\t\t}\n\n\t\t// Convert back to array\n\t\treturn Array.from(enumMap.entries()).map(([name, values]) => ({\n\t\t\tname,\n\t\t\tenum: Array.from(values),\n\t\t}));\n\t}\n\n\t/**\n\t * Finds the first occurrence of a matching enum schema in an array.\n\t * @param a - The enum schema to find.\n\t * @param enums - Array of enum schemas to search.\n\t * @returns - The found schema or undefined.\n\t */\n\tstatic findSameSchema(a: EnumSchemaObject, enums: EnumSchemaObject[]) {\n\t\treturn enums.find((b) => Base.isSameEnum(b, a));\n\t}\n\n\t/**\n\t * Checks if an object is a reference object.\n\t * @param schema - The object to check.\n\t * @returns - True if the object is a reference.\n\t */\n\n\tstatic isRef(schema: unknown): schema is ReferenceObject {\n\t\treturn (\n\t\t\ttypeof schema === 'object' &&\n\t\t\tschema !== null &&\n\t\t\t'$ref' in schema &&\n\t\t\ttypeof (schema as Record<string, unknown>).$ref === 'string'\n\t\t);\n\t}\n}\n","/**\n * @file Base class implementation\n * @author wp.l\n * @description This file defines the `Provider` abstract class, which serves as a base for providers responsible for parsing and processing API documentation.\n */\n\nimport type {\n\tEnumSchemaObject,\n\tFetchDocRequestInit,\n\tOperationObject,\n\tParameterObject,\n\tProviderInitOptions,\n\tProviderInitResult,\n\tRequestBodyObject,\n\tResponsesObject,\n\tSchemaObject,\n} from '../interface.js';\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\timplements ProviderInitResult, ProviderInitOptions\n{\n\t/** collection of enum schemas */\n\treadonly enums: EnumSchemaObject[] = [];\n\t/** collection of schemas indexed by name */\n\treadonly schemas: Record<string, SchemaObject> = {};\n\t/** collection of parameters indexed by name */\n\treadonly parameters: Record<string, ParameterObject> = {};\n\t/** collection of API responses indexed by name */\n\treadonly responses: Record<string, ResponsesObject> = {};\n\t/** collection of request bodies indexed by name */\n\treadonly requestBodies: Record<string, RequestBodyObject> = {};\n\t/** collection of API endpoints (operations) indexed by path */\n\treadonly apis: Record<string, OperationObject[]> = {};\n\n\t/** URL for fetching API documentation */\n\treadonly docURL: string;\n\t/** base URL for API endpoints */\n\treadonly baseURL: string;\n\t/** output directory for generated code */\n\treadonly output: string;\n\t/** request options for API documentation fetch */\n\treadonly requestOptions: FetchDocRequestInit;\n\t/** source path for imported client */\n\treadonly importClientSource: string;\n\n\t/**\n\t * Provider Constructor.\n\t * @param {ProviderInitOptions} initOptions - Initial configuration for the provider.\n\t * @param {unknown} doc - Raw API documentation data to be parsed.\n\t */\n\tconstructor(initOptions: ProviderInitOptions, doc: unknown) {\n\t\tthis.docURL = initOptions.docURL;\n\t\tthis.baseURL = initOptions.baseURL ?? '';\n\t\tthis.output = initOptions.output ?? '.';\n\t\tthis.requestOptions = initOptions.requestOptions ?? {};\n\t\tthis.importClientSource = initOptions.importClientSource ?? '';\n\n\t\tconst { enums, schemas, requestBodies, responses, parameters, apis } =\n\t\t\tthis.parse(doc);\n\n\t\tthis.enums = enums;\n\t\tthis.schemas = schemas;\n\t\tthis.responses = responses;\n\t\tthis.parameters = parameters;\n\t\tthis.requestBodies = requestBodies;\n\t\tthis.apis = apis;\n\t}\n\n\t/**\n\t * Abstract Parse Method.\n\t * @abstract\n\t * @param {unknown} doc - Raw API documentation data.\n\t * @returns {ProviderInitResult} - Parsed documentation data.\n\t *\n\t * This method must be implemented by subclasses to parse the raw documentation into structured data.\n\t */\n\tabstract parse(doc: unknown): ProviderInitResult;\n}\n","/**\n * Error handling utilities for api-codegen\n */\n\n// Error codes\nexport const ErrorCodes = {\n\tSPEC_NOT_FOUND: 'E_SPEC_NOT_FOUND',\n\tSPEC_FETCH_FAILED: 'E_SPEC_FETCH_FAILED',\n\tSPEC_PARSE_FAILED: 'E_SPEC_PARSE_FAILED',\n\tOUTPUT_DIR_MISSING: 'E_OUTPUT_DIR_MISSING',\n\tCONFIG_INVALID: 'E_CONFIG_INVALID',\n\tVALIDATION_FAILED: 'E_VALIDATION_FAILED',\n\tGENERATION_FAILED: 'E_GENERATION_FAILED',\n\tTYPE_CHECK_FAILED: 'E_TYPE_CHECK_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/**\n * Error context for ApicodegenError\n */\nexport interface ApicodegenErrorContext {\n\t/** Error code */\n\tcode: ErrorCode;\n\t/** Human readable message */\n\tmessage: string;\n\t/** File/URL related to error */\n\tlocation?: string;\n\t/** Line number if applicable */\n\tline?: number;\n\t/** Column number if applicable */\n\tcolumn?: number;\n\t/** Related schema/path if applicable */\n\tpath?: string;\n\t/** Suggested fixes */\n\tsuggestions?: string[];\n\t/** Original error */\n\tcause?: Error;\n}\n\n/**\n * Custom error class for api-codegen with rich context\n */\nexport class ApicodegenError extends Error {\n\treadonly code: ErrorCode;\n\treadonly location?: string;\n\treadonly line?: number;\n\treadonly column?: number;\n\treadonly path?: string;\n\treadonly suggestions: string[];\n\treadonly cause?: Error;\n\n\tconstructor(context: ApicodegenErrorContext) {\n\t\tsuper(context.message);\n\t\tthis.name = 'ApicodegenError';\n\t\tthis.code = context.code;\n\t\tthis.location = context.location;\n\t\tthis.line = context.line;\n\t\tthis.column = context.column;\n\t\tthis.path = context.path;\n\t\tthis.suggestions = context.suggestions || [];\n\t\tthis.cause = context.cause;\n\n\t\t// Maintains proper stack trace in V8 environments\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, ApicodegenError);\n\t\t}\n\t}\n\n\t/**\n\t * Convert error to formatted string for CLI output\n\t */\n\ttoString(verbose = false): string {\n\t\tconst lines: string[] = [];\n\n\t\t// Error header with code\n\t\tlines.push(`\\x1b[1;31mError [${this.code}]\\x1b[0m ${this.message}`);\n\n\t\t// Location\n\t\tif (this.location) {\n\t\t\tlines.push(` \\x1b[36m→ Location:\\x1b[0m ${this.location}`);\n\t\t}\n\n\t\t// Path\n\t\tif (this.path) {\n\t\t\tlines.push(` \\x1b[36m→ Path:\\x1b[0m ${this.path}`);\n\t\t}\n\n\t\t// Line/column\n\t\tif (this.line !== undefined) {\n\t\t\tlet lineInfo = ` \\x1b[36m→ Line:\\x1b[0m ${this.line}`;\n\t\t\tif (this.column !== undefined) {\n\t\t\t\tlineInfo += `, Column: ${this.column}`;\n\t\t\t}\n\t\t\tlines.push(lineInfo);\n\t\t}\n\n\t\t// Suggestions\n\t\tif (this.suggestions.length > 0) {\n\t\t\tfor (const suggestion of this.suggestions) {\n\t\t\t\tlines.push(` \\x1b[32m→ Suggestion:\\x1b[0m ${suggestion}`);\n\t\t\t}\n\t\t}\n\n\t\t// Stack trace in verbose mode\n\t\tif (verbose && this.cause) {\n\t\t\tlines.push(`\\n \\x1b[90mOriginal Error:\\x1b[0m ${this.cause.message}`);\n\t\t\tif (this.stack) {\n\t\t\t\t// Skip first few lines of stack (our error header)\n\t\t\t\tconst stackLines = this.stack.split('\\n').slice(1).join('\\n');\n\t\t\t\tlines.push(`\\x1b[90m${stackLines}\\x1b[0m`);\n\t\t\t}\n\t\t}\n\n\t\treturn lines.join('\\n');\n\t}\n\n\t/**\n\t * Convert to JSON-serializable object\n\t */\n\ttoJSON(): object {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcode: this.code,\n\t\t\tmessage: this.message,\n\t\t\tlocation: this.location,\n\t\t\tline: this.line,\n\t\t\tcolumn: this.column,\n\t\t\tpath: this.path,\n\t\t\tsuggestions: this.suggestions,\n\t\t\tcause: this.cause?.message,\n\t\t};\n\t}\n}\n\n/**\n * ANSI color codes for terminal output\n */\nexport const Colors = {\n\treset: '\\x1b[0m',\n\tbold: '\\x1b[1m',\n\tred: '\\x1b[31m',\n\tgreen: '\\x1b[32m',\n\tyellow: '\\x1b[33m',\n\tblue: '\\x1b[34m',\n\tcyan: '\\x1b[36m',\n\tgray: '\\x1b[90m',\n\tbrightRed: '\\x1b[91m',\n\tbrightGreen: '\\x1b[92m',\n} as const;\n\n/**\n * Format error for CLI output\n */\nexport function formatError(error: unknown, verbose = false): string {\n\tif (error instanceof ApicodegenError) {\n\t\treturn error.toString(verbose);\n\t}\n\n\tif (error instanceof Error) {\n\t\treturn `${Colors.red}${Colors.bold}Error${Colors.reset}: ${error.message}${verbose && error.stack ? `\\n\\n${Colors.gray}${error.stack}${Colors.reset}` : ''}`;\n\t}\n\n\treturn `${Colors.red}${Colors.bold}Error${Colors.reset}: ${String(error)}`;\n}\n\n/**\n * Print error to console with formatting\n */\nexport function printError(\n\terror: unknown,\n\tverbose = false,\n\tstream: NodeJS.WriteStream = process.stderr\n): void {\n\tstream.write(formatError(error, verbose));\n\tstream.write('\\n');\n}\n\n/**\n * Create error with common patterns\n */\nexport const createErrors = {\n\tspecNotFound(path: string, cause?: Error): ApicodegenError {\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.SPEC_NOT_FOUND,\n\t\t\tmessage: 'OpenAPI spec file not found',\n\t\t\tlocation: path,\n\t\t\tsuggestions: [\n\t\t\t\t\"Check if the file exists using 'ls -la'\",\n\t\t\t\t'Use --spec to provide the correct path',\n\t\t\t\t'For remote specs, ensure the URL is accessible',\n\t\t\t],\n\t\t\tcause,\n\t\t});\n\t},\n\n\tspecFetchFailed(\n\t\turl: string,\n\t\tstatusCode?: number,\n\t\tcause?: Error\n\t): ApicodegenError {\n\t\tconst message = statusCode\n\t\t\t? `Failed to fetch OpenAPI spec (HTTP ${statusCode})`\n\t\t\t: 'Failed to fetch OpenAPI spec from URL';\n\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.SPEC_FETCH_FAILED,\n\t\t\tmessage,\n\t\t\tlocation: url,\n\t\t\tsuggestions: [\n\t\t\t\t'Check if the URL is accessible in a browser',\n\t\t\t\t'Download the spec file locally and use the local path',\n\t\t\t\t'Verify CORS settings if fetching from a different origin',\n\t\t\t],\n\t\t\tcause,\n\t\t});\n\t},\n\n\tspecParseFailed(\n\t\tpath: string,\n\t\tline?: number,\n\t\tcolumn?: number,\n\t\tcause?: Error\n\t): ApicodegenError {\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.SPEC_PARSE_FAILED,\n\t\t\tmessage: 'Failed to parse OpenAPI spec (invalid JSON or YAML)',\n\t\t\tlocation: path,\n\t\t\tline,\n\t\t\tcolumn,\n\t\t\tsuggestions: [\n\t\t\t\t'Validate JSON syntax using jsonlint.com',\n\t\t\t\t'For YAML specs, ensure proper indentation',\n\t\t\t\t'Check for trailing commas or unquoted special characters',\n\t\t\t],\n\t\t\tcause,\n\t\t});\n\t},\n\n\toutputDirMissing(path: string, cause?: Error): ApicodegenError {\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.OUTPUT_DIR_MISSING,\n\t\t\tmessage: 'Output directory does not exist',\n\t\t\tlocation: path,\n\t\t\tsuggestions: [\n\t\t\t\t'Create the directory: mkdir -p $(dirname <output>)',\n\t\t\t\t'Check if the path is correct',\n\t\t\t],\n\t\t\tcause,\n\t\t});\n\t},\n\n\tconfigInvalid(path: string, cause?: Error): ApicodegenError {\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.CONFIG_INVALID,\n\t\t\tmessage: 'Invalid configuration file',\n\t\t\tlocation: path,\n\t\t\tsuggestions: [\n\t\t\t\t'Validate JSON syntax in the config file',\n\t\t\t\t'Check for required fields (spec, output)',\n\t\t\t],\n\t\t\tcause,\n\t\t});\n\t},\n\n\tvalidationFailed(\n\t\tpath: string,\n\t\tdetails: string,\n\t\tcause?: Error\n\t): ApicodegenError {\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.VALIDATION_FAILED,\n\t\t\tmessage: 'OpenAPI spec validation failed',\n\t\t\tlocation: path,\n\t\t\tpath: details,\n\t\t\tsuggestions: [\n\t\t\t\t'Check OpenAPI spec structure at the specified path',\n\t\t\t\t'Ensure all required fields are present',\n\t\t\t\t'Validate using swagger.io editor',\n\t\t\t],\n\t\t\tcause,\n\t\t});\n\t},\n\n\tgenerationFailed(cause?: Error): ApicodegenError {\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.GENERATION_FAILED,\n\t\t\tmessage: 'Code generation failed',\n\t\t\tsuggestions: [\n\t\t\t\t'Check for unsupported OpenAPI features',\n\t\t\t\t'Ensure spec follows OpenAPI 2.0, 3.0, or 3.1 specification',\n\t\t\t\t'Use --verbose for more details',\n\t\t\t],\n\t\t\tcause,\n\t\t});\n\t},\n\n\ttypeCheckFailed(\n\t\tpath: string,\n\t\t_errors: string[],\n\t\tcause?: Error\n\t): ApicodegenError {\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.TYPE_CHECK_FAILED,\n\t\t\tmessage: 'TypeScript type check failed',\n\t\t\tlocation: path,\n\t\t\tsuggestions: [\n\t\t\t\t'Review type errors above',\n\t\t\t\t'Check for schema inconsistencies',\n\t\t\t\t'Update generated types or fix source schema',\n\t\t\t],\n\t\t\tcause,\n\t\t});\n\t},\n\n\tmissingRequiredField(field: string, context?: string): ApicodegenError {\n\t\treturn new ApicodegenError({\n\t\t\tcode: ErrorCodes.VALIDATION_FAILED,\n\t\t\tmessage: `Missing required field: ${field}`,\n\t\t\tpath: context,\n\t\t\tsuggestions: [`Add the '${field}' field to your configuration`],\n\t\t});\n\t},\n};\n\n/**\n * Wrap unknown error in ApicodegenError if needed\n */\nexport function wrapError(\n\terror: unknown,\n\tcontext?: Partial<ApicodegenErrorContext>\n): ApicodegenError {\n\tif (error instanceof ApicodegenError) {\n\t\treturn error;\n\t}\n\n\tif (error instanceof Error) {\n\t\treturn new ApicodegenError({\n\t\t\tcode: context?.code || ErrorCodes.GENERATION_FAILED,\n\t\t\tmessage: context?.message || error.message,\n\t\t\tlocation: context?.location,\n\t\t\tsuggestions: context?.suggestions,\n\t\t\tcause: error,\n\t\t});\n\t}\n\n\treturn new ApicodegenError({\n\t\tcode: context?.code || ErrorCodes.GENERATION_FAILED,\n\t\tmessage: String(error),\n\t\tsuggestions: context?.suggestions,\n\t});\n}\n\n/**\n * Check if error is an ApicodegenError\n */\nexport function isApicodegenError(error: unknown): error is ApicodegenError {\n\treturn error instanceof ApicodegenError;\n}\n","/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */\n/* eslint-disable no-case-declarations */\n\nimport { writeFile } from 'node:fs/promises';\nimport { format } from 'prettier';\nimport type {\n\tBindingElement,\n\tBlock,\n\tNode,\n\tParameterDeclaration,\n\tPropertySignature,\n\tStatement,\n\tTypeNode,\n} from 'typescript';\nimport {\n\taddSyntheticLeadingComment,\n\tcreatePrinter,\n\tNodeFlags,\n\tSyntaxKind,\n\tfactory as t,\n} from 'typescript';\nimport type { Adapter } from '../base/Adaptor.js';\nimport { Base } from '../base/Base.js';\nimport { ApicodegenError, ErrorCodes } from '../errors.js';\nimport type {\n\tArrayTypeSchemaObject,\n\tMediaTypeObject,\n\tParameterObject,\n\tProviderInitOptions,\n\tProviderInitResult,\n\tSchemaObject,\n\tSingleTypeSchemaObject,\n} from '../interface.js';\nimport {\n\tArraySchemaType,\n\tMediaTypes,\n\tNonArraySchemaType,\n\tParameterIn,\n\tSchemaFormatType,\n} from '../interface.js';\n\n/**\n * Represents a comment object with optional tag and message.\n */\nexport type CommentObject = {\n\ttag?: 'deprecated' | 'param' | 'returns';\n\tcomment: string;\n\tparamName?: string;\n\ttype?: 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\t/**\n\t * Converts an array of TypeScript statements into a formatted string of code.\n\t *\n\t * @param statements - The array of TypeScript statement nodes.\n\t * @returns Formatted code as a string.\n\t * @throws {Error} If no valid statements are provided.\n\t */\n\tstatic toCode(statements: Statement[]): string {\n\t\tif (statements.length === 0) {\n\t\t\treturn '// No api declaration found.';\n\t\t}\n\n\t\tconst sourceFile = t.createSourceFile(\n\t\t\tstatements,\n\t\t\tt.createToken(SyntaxKind.EndOfFileToken),\n\t\t\tNodeFlags.None\n\t\t);\n\n\t\treturn createPrinter().printFile(sourceFile);\n\t}\n\n\tstatic async write(code: string, filepath: string) {\n\t\tconst { mkdir } = await import('node:fs/promises');\n\t\tconst { dirname } = await import('node:path');\n\t\ttry {\n\t\t\tawait mkdir(dirname(filepath), { recursive: true });\n\t\t\tawait writeFile(filepath, code);\n\t\t} catch (error) {\n\t\t\tthrow new ApicodegenError({\n\t\t\t\tcode: ErrorCodes.OUTPUT_DIR_MISSING,\n\t\t\t\tmessage: 'Failed to write generated code to output file',\n\t\t\t\tlocation: filepath,\n\t\t\t\tcause: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\tsuggestions: [\n\t\t\t\t\t'Verify the output directory path is writable',\n\t\t\t\t\t'Check that the parent directory exists or can be created',\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Converts a path string with parameters into a TypeScript template expression.\n\t * Handles query parameters and path placeholders.\n\t *\n\t * @param path - The base path string containing placeholders.\n\t * @param parameters - Array of parameter objects defining the parameters.\n\t * @param basePath - Optional base path to prepend (default: \"\").\n\t * @returns A TypeScript template expressi\n\t */\n\tstatic toUrlTemplate(\n\t\tpath: string,\n\t\tparameters: ParameterObject[],\n\t\tbasePath = ''\n\t) {\n\t\t// Extract query parameters\n\t\tconst queryParameters = parameters.filter(\n\t\t\t(p) => p.in === ParameterIn.query\n\t\t);\n\n\t\tif (queryParameters.length > 0) {\n\t\t\tconst queryString = queryParameters\n\t\t\t\t.map(\n\t\t\t\t\t(qp, index) =>\n\t\t\t\t\t\t`${index === 0 ? '?' : '&'}${encodeURIComponent(qp.name)}={${Base.normalize(qp.name)}}`\n\t\t\t\t)\n\t\t\t\t.join('');\n\t\t\tpath += queryString;\n\t\t}\n\n\t\t// Split the path into segments\n\t\tconst pathSegments = path.replaceAll('{', '${').split('$').filter(Boolean);\n\n\t\t// If path segments only got one item, it means there are no parameters in path. So just return the path literal.\n\t\tif (pathSegments.length === 1) {\n\t\t\treturn t.createNoSubstitutionTemplateLiteral(basePath + path);\n\t\t}\n\n\t\treturn t.createTemplateExpression(\n\t\t\tt.createTemplateHead(basePath + pathSegments[0]),\n\t\t\tpathSegments.slice(1).map((segment, index) => {\n\t\t\t\tconst match = /^{(.+)}(.+)?/gm.exec(segment);\n\t\t\t\tconst isLastSegment = index === pathSegments.length - 2;\n\n\t\t\t\tif (!match) {\n\t\t\t\t\tthrow new Error(`Invalid path segment: ${segment}`);\n\t\t\t\t}\n\n\t\t\t\treturn t.createTemplateSpan(\n\t\t\t\t\tt.createIdentifier(Base.normalize(match[1])),\n\t\t\t\t\t!isLastSegment\n\t\t\t\t\t\t? t.createTemplateMiddle(match[2])\n\t\t\t\t\t\t: t.createTemplateTail(match[2] || '')\n\t\t\t\t);\n\t\t\t})\n\t\t);\n\t}\n\n\t/**\n\t * Adds synthetic comments to a TypeScript AST node.\n\t *\n\t * @param node - The target AST node.\n\t * @param comments - Array of comment objects to add.\n\t */\n\tstatic addComments(node: Node, comments: Comments) {\n\t\tif (!Array.isArray(comments) || comments.filter(Boolean).length === 0)\n\t\t\treturn;\n\n\t\tconst formatComment = (comment: CommentObject): string => {\n\t\t\tif (comment.tag === 'returns') {\n\t\t\t\treturn `* @returns {${comment.type}} ${comment.comment ?? ''}`;\n\t\t\t}\n\t\t\tif (comment.tag === 'param') {\n\t\t\t\treturn comment.comment\n\t\t\t\t\t? `* @param ${comment.paramName} - ${comment.comment}`\n\t\t\t\t\t: `* @param ${comment.paramName}`;\n\t\t\t}\n\t\t\tif (comment.tag) {\n\t\t\t\treturn `* @${comment.tag} ${comment.comment ?? ''}`;\n\t\t\t}\n\t\t\treturn `* ${comment.comment}`;\n\t\t};\n\n\t\tconst formattedComments =\n\t\t\tcomments.map(formatComment).join('\\n').trim() + '\\n';\n\n\t\taddSyntheticLeadingComment(\n\t\t\tnode,\n\t\t\tSyntaxKind.MultiLineCommentTrivia,\n\t\t\tformattedComments,\n\t\t\ttrue\n\t\t);\n\t}\n\n\t/**\n\t * Checks if a schema represents a binary type.\n\t *\n\t * @param schema - The schema object to check.\n\t * @returns true if the schema is a binary type, false otherwise.\n\t */\n\tstatic isBinarySchema(schema: SchemaObject): boolean {\n\t\tif (schema.type === 'array') {\n\t\t\tconst arraySchema = schema as ArrayTypeSchemaObject;\n\t\t\treturn Generator.isBinarySchema(arraySchema.items!);\n\t\t}\n\n\t\tconst nonArraySchema = schema as SingleTypeSchemaObject;\n\t\treturn (\n\t\t\tnonArraySchema.format === SchemaFormatType.blob ||\n\t\t\tnonArraySchema.format === SchemaFormatType.binary ||\n\t\t\tnonArraySchema.type === SchemaFormatType.file\n\t\t);\n\t}\n\n\tstatic schemaToTypeString(schema: SchemaObject): string {\n\t\tif (schema.type === 'array') {\n\t\t\tconst arraySchema = schema as ArrayTypeSchemaObject;\n\t\t\treturn arraySchema.items\n\t\t\t\t? `${Generator.schemaToTypeString(arraySchema.items)}[]`\n\t\t\t\t: 'unknown';\n\t\t}\n\t\tconst singleSchema = schema as SingleTypeSchemaObject;\n\t\tif (schema.type === 'string') return 'string';\n\t\tif (schema.type === 'number' || schema.type === 'integer') return 'number';\n\t\tif (schema.type === 'boolean') return 'boolean';\n\t\tif (\n\t\t\tschema.type === 'object' ||\n\t\t\t(schema as SingleTypeSchemaObject).properties\n\t\t)\n\t\t\treturn 'object';\n\t\tif (singleSchema.format === 'binary' || singleSchema.type === 'file')\n\t\t\treturn 'Blob';\n\t\tif (singleSchema.format === 'blob') return 'Blob';\n\t\tif (singleSchema.ref) return singleSchema.ref;\n\t\treturn 'unknown';\n\t}\n\n\tstatic generateParamTags(\n\t\tparameters: ParameterObject[],\n\t\trequestBody?: MediaTypeObject\n\t): CommentObject[] {\n\t\tconst tags: CommentObject[] = [];\n\n\t\tfor (const p of parameters) {\n\t\t\tconst paramName = Base.normalize(p.name);\n\t\t\tlet paramType = 'unknown';\n\n\t\t\tif (p.schema) {\n\t\t\t\tparamType = Generator.schemaToTypeString(p.schema);\n\t\t\t}\n\n\t\t\tconst isOptional = p.required === false;\n\t\t\ttags.push({\n\t\t\t\ttag: 'param',\n\t\t\t\tparamName: paramName,\n\t\t\t\ttype: `${paramType}${isOptional ? ' | undefined' : ''}`,\n\t\t\t\tcomment: p.description ?? '',\n\t\t\t});\n\t\t}\n\n\t\tif (requestBody?.schema && 'properties' in requestBody.schema) {\n\t\t\tconst properties = requestBody.schema.properties as Record<\n\t\t\t\tstring,\n\t\t\t\tSchemaObject\n\t\t\t>;\n\t\t\tconst required = requestBody.schema.required;\n\t\t\tconst requiredArray = Array.isArray(required) ? required : [];\n\t\t\tfor (const [key, schema] of Object.entries(properties ?? {})) {\n\t\t\t\tconst paramName = `req.${key}`;\n\t\t\t\tconst paramType = Generator.schemaToTypeString(schema);\n\t\t\t\tconst isOptional = !requiredArray.includes(key);\n\t\t\t\ttags.push({\n\t\t\t\t\ttag: 'param',\n\t\t\t\t\tparamName: paramName,\n\t\t\t\t\ttype: `${paramType}${isOptional ? ' | undefined' : ''}`,\n\t\t\t\t\tcomment: schema.description ?? '',\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn tags;\n\t}\n\n\tstatic toRequestBodyTypeNode(schema: SchemaObject) {\n\t\treturn t.createParameterDeclaration(\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tt.createIdentifier('req'),\n\t\t\tundefined,\n\t\t\tGenerator.toTypeNode(schema)\n\t\t);\n\t}\n\n\tstatic toTypeNode(schema: SchemaObject): TypeNode {\n\t\tconst { type, ref } = schema;\n\n\t\tif (ref) {\n\t\t\tconst identify = Base.ref2name(ref);\n\t\t\treturn t.createTypeReferenceNode(\n\t\t\t\tt.createIdentifier(\n\t\t\t\t\tidentify === 'unknown' ? identify : Base.upperCamelCase(identify)\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\tswitch (type) {\n\t\t\tcase ArraySchemaType.array: {\n\t\t\t\tconst { items } = schema as ArrayTypeSchemaObject;\n\t\t\t\treturn t.createArrayTypeNode(Generator.toTypeNode(items!));\n\t\t\t}\n\t\t\tcase NonArraySchemaType.object: {\n\t\t\t\tconst propsCount = Object.keys(schema.properties ?? {}).length;\n\t\t\t\tif (!schema.properties || propsCount === 0) {\n\t\t\t\t\t// Record<string, unknown>\n\t\t\t\t\treturn t.createTypeReferenceNode(t.createIdentifier('Record'), [\n\t\t\t\t\t\tt.createToken(SyntaxKind.StringKeyword),\n\t\t\t\t\t\tt.createToken(SyntaxKind.UnknownKeyword),\n\t\t\t\t\t]);\n\t\t\t\t}\n\n\t\t\t\tconst props = Object.keys(schema.properties);\n\n\t\t\t\treturn t.createTypeLiteralNode(\n\t\t\t\t\tprops.map((propKey) => {\n\t\t\t\t\t\tconst propSchema = schema.properties![propKey];\n\t\t\t\t\t\treturn t.createPropertySignature(\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tt.createStringLiteral(propKey),\n\t\t\t\t\t\t\t// When field is required, a refrence or binary value, don't add question mark.\n\t\t\t\t\t\t\tschema.required || schema.ref || Generator.isBinarySchema(schema)\n\t\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t\t: t.createToken(SyntaxKind.QuestionToken),\n\t\t\t\t\t\t\tGenerator.toTypeNode(propSchema)\n\t\t\t\t\t\t);\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t}\n\t\t\tcase NonArraySchemaType.integer:\n\t\t\tcase NonArraySchemaType.number:\n\t\t\t\tif (schema.enum) {\n\t\t\t\t\treturn t.createUnionTypeNode(\n\t\t\t\t\t\tschema.enum.map((e) =>\n\t\t\t\t\t\t\tt.createLiteralTypeNode(t.createNumericLiteral(e))\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn t.createToken(SyntaxKind.NumberKeyword);\n\t\t\t// case NonArraySchemaType.string:\n\t\t\tcase NonArraySchemaType.boolean:\n\t\t\t\treturn t.createToken(SyntaxKind.BooleanKeyword);\n\t\t\tcase NonArraySchemaType.file:\n\t\t\t\treturn t.createTypeReferenceNode(t.createIdentifier('Blob'));\n\t\t\tdefault: {\n\t\t\t\tconst {\n\t\t\t\t\tformat,\n\t\t\t\t\toneOf,\n\t\t\t\t\tallOf,\n\t\t\t\t\tanyOf,\n\t\t\t\t\ttype,\n\t\t\t\t\tenum: enum_,\n\t\t\t\t} = schema as SingleTypeSchemaObject;\n\n\t\t\t\tswitch (format) {\n\t\t\t\t\tcase SchemaFormatType.number:\n\t\t\t\t\t\treturn t.createToken(SyntaxKind.NumberKeyword);\n\t\t\t\t\tcase SchemaFormatType.string:\n\t\t\t\t\t\treturn t.createToken(SyntaxKind.StringKeyword);\n\t\t\t\t\tcase SchemaFormatType.boolean:\n\t\t\t\t\t\treturn t.createToken(SyntaxKind.BooleanKeyword);\n\t\t\t\t\tcase SchemaFormatType.blob:\n\t\t\t\t\tcase SchemaFormatType.binary:\n\t\t\t\t\t\treturn t.createTypeReferenceNode(t.createIdentifier('Blob'));\n\t\t\t\t\tdefault:\n\t\t\t\t}\n\n\t\t\t\tif (enum_) {\n\t\t\t\t\treturn t.createUnionTypeNode(\n\t\t\t\t\t\tenum_.map((e) =>\n\t\t\t\t\t\t\tt.createLiteralTypeNode(t.createStringLiteral(e as string))\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (type === NonArraySchemaType.string) {\n\t\t\t\t\treturn t.createToken(SyntaxKind.StringKeyword);\n\t\t\t\t}\n\n\t\t\t\tif (oneOf) {\n\t\t\t\t\treturn t.createUnionTypeNode(\n\t\t\t\t\t\toneOf.map((schema) => Generator.toTypeNode(schema))\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (anyOf) {\n\t\t\t\t\treturn t.createUnionTypeNode(\n\t\t\t\t\t\tanyOf.map((schema) => Generator.toTypeNode(schema))\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (allOf) {\n\t\t\t\t\treturn t.createIntersectionTypeNode(\n\t\t\t\t\t\tallOf.map((schema) => Generator.toTypeNode(schema))\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (type && typeof type === 'string') {\n\t\t\t\t\treturn t.createTypeReferenceNode(\n\t\t\t\t\t\ttype !== 'unknown' && type !== 'null'\n\t\t\t\t\t\t\t? t.createIdentifier(Base.upperCamelCase(type))\n\t\t\t\t\t\t\t: type\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn t.createToken(SyntaxKind.UnknownKeyword);\n\t}\n\n\tstatic toDeclarationNodes(\n\t\tparameters: ParameterObject[]\n\t): ParameterDeclaration[] {\n\t\tconst objectElements: BindingElement[] = [];\n\t\tconst typeObjectElements: PropertySignature[] = [];\n\t\tconst refParameters: ParameterDeclaration[] = [];\n\n\t\tfor (const parameter of parameters) {\n\t\t\tif (parameter.ref) {\n\t\t\t\t// Handle reference parameters as standalone parameters\n\t\t\t\tconst refName = Base.ref2name(parameter.ref);\n\t\t\t\trefParameters.push(\n\t\t\t\t\tt.createParameterDeclaration(\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tt.createIdentifier(Base.normalize(refName)),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tt.createTypeReferenceNode(\n\t\t\t\t\t\t\tt.createIdentifier(Base.upperCamelCase(Base.normalize(refName)))\n\t\t\t\t\t\t),\n\t\t\t\t\t\tundefined\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconst { name, schema, required } = parameter;\n\t\t\t\tobjectElements.push(\n\t\t\t\t\tt.createBindingElement(\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tt.createIdentifier(Base.normalize(name))\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\ttypeObjectElements.push(\n\t\t\t\t\tt.createPropertySignature(\n\t\t\t\t\t\t[],\n\t\t\t\t\t\tt.createIdentifier(Base.normalize(name)),\n\t\t\t\t\t\trequired ? undefined : t.createToken(SyntaxKind.QuestionToken),\n\t\t\t\t\t\t!schema\n\t\t\t\t\t\t\t? t.createToken(SyntaxKind.UnknownKeyword)\n\t\t\t\t\t\t\t: Generator.toTypeNode(schema)\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Build object destructuring parameter for non-ref parameters\n\t\tif (objectElements.length > 0) {\n\t\t\tconst objectParam = t.createParameterDeclaration(\n\t\t\t\tundefined,\n\t\t\t\tundefined,\n\t\t\t\tt.createObjectBindingPattern(objectElements),\n\t\t\t\tundefined,\n\t\t\t\tt.createTypeLiteralNode(typeObjectElements),\n\t\t\t\tundefined\n\t\t\t);\n\t\t\treturn [objectParam, ...refParameters];\n\t\t}\n\n\t\treturn refParameters;\n\t}\n\n\tstatic toFormDataStatement(\n\t\tparameters: ParameterObject[],\n\t\trequestBody?: SchemaObject\n\t): Statement[] {\n\t\tconst statements: Statement[] = [];\n\t\tconst fdDeclaration = t.createVariableStatement(\n\t\t\tundefined,\n\t\t\tt.createVariableDeclarationList(\n\t\t\t\t[\n\t\t\t\t\tt.createVariableDeclaration(\n\t\t\t\t\t\tt.createIdentifier('fd'),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tt.createNewExpression(t.createIdentifier('FormData'), undefined, [])\n\t\t\t\t\t),\n\t\t\t\t],\n\t\t\t\tNodeFlags.Const\n\t\t\t)\n\t\t);\n\n\t\tstatements.push(fdDeclaration);\n\n\t\tparameters.forEach((parameter) => {\n\t\t\tstatements.push(\n\t\t\t\tt.createExpressionStatement(\n\t\t\t\t\tt.createBinaryExpression(\n\t\t\t\t\t\tt.createIdentifier(parameter.name),\n\t\t\t\t\t\tt.createToken(SyntaxKind.AmpersandAmpersandToken),\n\t\t\t\t\t\tt.createCallExpression(\n\t\t\t\t\t\t\tt.createPropertyAccessExpression(\n\t\t\t\t\t\t\t\tt.createIdentifier('fd'),\n\t\t\t\t\t\t\t\tt.createIdentifier('append')\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\tt.createStringLiteral(parameter.name),\n\t\t\t\t\t\t\t\tt.createIdentifier(parameter.name),\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t);\n\t\t});\n\n\t\tif (\n\t\t\trequestBody &&\n\t\t\trequestBody.type === 'object' &&\n\t\t\trequestBody.properties &&\n\t\t\tObject.keys(requestBody.properties).length !== 0\n\t\t) {\n\t\t\tObject.keys(requestBody.properties).forEach((key) => {\n\t\t\t\tconst schemaByKey = requestBody.properties![key];\n\t\t\t\tif (\n\t\t\t\t\tschemaByKey.type === ArraySchemaType.array &&\n\t\t\t\t\tGenerator.isBinarySchema(schemaByKey)\n\t\t\t\t) {\n\t\t\t\t\tstatements.push(\n\t\t\t\t\t\tt.createForOfStatement(\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tt.createVariableDeclarationList(\n\t\t\t\t\t\t\t\t[t.createVariableDeclaration('file')],\n\t\t\t\t\t\t\t\tNodeFlags.Const\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tt.createElementAccessExpression(\n\t\t\t\t\t\t\t\tt.createIdentifier('req'),\n\t\t\t\t\t\t\t\tt.createStringLiteral(key)\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tt.createBlock([\n\t\t\t\t\t\t\t\tt.createExpressionStatement(\n\t\t\t\t\t\t\t\t\tt.createCallExpression(\n\t\t\t\t\t\t\t\t\t\tt.createPropertyAccessExpression(\n\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('fd'),\n\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('append')\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t[],\n\t\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t\tt.createStringLiteral(key),\n\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('file'),\n\t\t\t\t\t\t\t\t\t\t\tt.createPropertyAccessExpression(\n\t\t\t\t\t\t\t\t\t\t\t\tt.createAsExpression(\n\t\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('file'),\n\t\t\t\t\t\t\t\t\t\t\t\t\tt.createTypeReferenceNode(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('File'),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tundefined\n\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('name')\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t])\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tif (schemaByKey.required) {\n\t\t\t\t\t\tstatements.push(\n\t\t\t\t\t\t\tt.createExpressionStatement(\n\t\t\t\t\t\t\t\tt.createCallExpression(\n\t\t\t\t\t\t\t\t\tt.createPropertyAccessExpression(\n\t\t\t\t\t\t\t\t\t\tt.createIdentifier('fd'),\n\t\t\t\t\t\t\t\t\t\tt.createIdentifier('append')\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\tt.createStringLiteral(key),\n\t\t\t\t\t\t\t\t\t\tschemaByKey.type === 'string' ||\n\t\t\t\t\t\t\t\t\t\tGenerator.isBinarySchema(schemaByKey as SchemaObject) ||\n\t\t\t\t\t\t\t\t\t\t(schemaByKey as SingleTypeSchemaObject).isRef\n\t\t\t\t\t\t\t\t\t\t\t? t.createElementAccessExpression(\n\t\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('req'),\n\t\t\t\t\t\t\t\t\t\t\t\t\tt.createStringLiteral(key)\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t: schemaByKey.type === 'array' ||\n\t\t\t\t\t\t\t\t\t\t\t\t\tschemaByKey.type === 'object'\n\t\t\t\t\t\t\t\t\t\t\t\t? t.createCallExpression(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createPropertyAccessExpression(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('JSON'),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('stringify')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createElementAccessExpression(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('req'),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createStringLiteral(key)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t: t.createCallExpression(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('String'),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createElementAccessExpression(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createIdentifier('req'),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tt.createStringLiteral(key)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t