oazapfts
Version:
OpenApi TypeScript client generator
1 lines • 94.8 kB
Source Map (JSON)
{"version":3,"file":"generateClientMethod-B8zYb-98.cjs","names":[],"sources":["../src/helpers/mimeTypes.ts","../src/helpers/getFormatter.ts","../src/helpers/callQsFunction.ts","../src/helpers/createUrlExpression.ts","../src/helpers/callOazapftsFunction.ts","../src/helpers/getBodyFormatter.ts","../src/helpers/wrapResult.ts","../src/helpers/findAvailableRef.ts","../src/helpers/isNamedEnumSchema.ts","../src/helpers/getEnumStyle.ts","../src/helpers/getUniqueAlias.ts","../src/helpers/getEnumUniqueAlias.ts","../src/helpers/getCustomNames.ts","../src/helpers/checkSchemaOnlyMode.ts","../src/helpers/isNullable.ts","../src/helpers/isHttpMethod.ts","../src/helpers/emptySchemaType.ts","../src/generate/getTypeFromSchema/getTrueEnum.ts","../src/generate/getTypeFromSchema/getAsConstEnum.ts","../src/generate/getTypeFromSchema/getTypeFromEnum.ts","../src/generate/getTypeFromSchema/getDiscriminatorType.ts","../src/generate/getTypeFromSchema/getUnionType.ts","../src/generate/getTypeFromSchema/getTypeFromProperties.ts","../src/generate/getTypeFromSchema/getTypeFromSchema.ts","../src/generate/getRefAlias.ts","../src/generate/preprocessComponents.ts","../src/generate/generateApi.ts","../src/generate/createDefaultsStatement.ts","../src/generate/generateImports.ts","../src/generate/getSchemaFromContent.ts","../src/generate/getTypeFromParameter.ts","../src/generate/getResponseType.ts","../src/generate/getTypeFromResponses.ts","../src/generate/getOperationName.ts","../src/generate/generateClientMethod.ts"],"sourcesContent":["export type ContentType = \"json\" | \"form\" | \"multipart\";\n\nexport const contentTypes: Record<string, ContentType> = {\n \"*/*\": \"json\",\n \"application/json\": \"json\",\n \"application/x-www-form-urlencoded\": \"form\",\n \"multipart/form-data\": \"multipart\",\n};\n\nexport function isMimeType(s: unknown) {\n return typeof s === \"string\" && /^[^/]+\\/[^/]+$/.test(s);\n}\n\nexport function isJsonMimeType(mime: string) {\n return contentTypes[mime] === \"json\" || /\\bjson\\b/i.test(mime);\n}\n","import { ParameterObject } from \"./openApi3-x\";\nimport { isJsonMimeType } from \"./mimeTypes\";\n\n/**\n * Get the name of a formatter function for a given parameter.\n */\nexport function getFormatter({\n style = \"form\",\n explode = true,\n content,\n}: ParameterObject) {\n if (content) {\n const medias = Object.keys(content);\n if (medias.length !== 1) {\n throw new Error(\n \"Parameters with content property must specify one media type\",\n );\n }\n if (!isJsonMimeType(medias[0])) {\n throw new Error(\n \"Parameters with content property must specify a JSON compatible media type\",\n );\n }\n return \"json\";\n }\n if (explode && style === \"deepObject\") return \"deep\";\n if (explode) return \"explode\";\n if (style === \"spaceDelimited\") return \"space\";\n if (style === \"pipeDelimited\") return \"pipe\";\n return \"form\";\n}\n","import ts from \"typescript\";\nimport { createCall } from \"../generate/tscodegen\";\n\n/**\n * Create a call expression for one of the QS runtime functions.\n */\nexport function callQsFunction(name: string, args: ts.Expression[]) {\n return createCall(\n ts.factory.createPropertyAccessExpression(\n ts.factory.createIdentifier(\"QS\"),\n name,\n ),\n { args },\n );\n}\n","import ts from \"typescript\";\nimport { toIdentifier } from \"./toIdentifier\";\nimport { createCall, createTemplateString } from \"../generate/tscodegen\";\n\n/**\n * Create a template string literal from the given OpenAPI urlTemplate.\n * Curly braces in the path are turned into identifier expressions,\n * which are read from the local scope during runtime.\n */\nexport function createUrlExpression(path: string, qs?: ts.Expression) {\n const spans: Array<{ expression: ts.Expression; literal: string }> = [];\n // Use a replacer function to collect spans as a side effect:\n const head = path.replace(\n /(.*?)\\{(.+?)\\}(.*?)(?=\\{|$)/g,\n (_substr, head, name, literal) => {\n const expression = toIdentifier(name);\n spans.push({\n expression: createCall(\n ts.factory.createIdentifier(\"encodeURIComponent\"),\n {\n args: [ts.factory.createIdentifier(expression)],\n },\n ),\n literal,\n });\n return head;\n },\n );\n\n if (qs) {\n // add the query string as last span\n spans.push({ expression: qs, literal: \"\" });\n }\n return createTemplateString(head, spans);\n}\n","import ts from \"typescript\";\nimport { createCall } from \"../generate/tscodegen\";\n\n/**\n * Create a call expression for one of the oazapfts runtime functions.\n */\nexport function callOazapftsFunction(\n name: string,\n args: ts.Expression[],\n typeArgs?: ts.TypeNode[],\n) {\n return createCall(\n ts.factory.createPropertyAccessExpression(\n ts.factory.createIdentifier(\"oazapfts\"),\n name,\n ),\n { args, typeArgs },\n );\n}\n","import { RequestBodyObject } from \"./openApi3-x\";\nimport { contentTypes, isJsonMimeType } from \"./mimeTypes\";\n\nexport function getBodyFormatter(body?: RequestBodyObject) {\n if (body?.content) {\n for (const contentType of Object.keys(body.content)) {\n const formatter = contentTypes[contentType];\n if (formatter) return formatter;\n if (isJsonMimeType(contentType)) return \"json\";\n }\n }\n}\n","import ts from \"typescript\";\nimport { OazapftsContext } from \"../context\";\nimport { callOazapftsFunction } from \"./callOazapftsFunction\";\n\nexport function wrapResult(ex: ts.Expression, ctx: OazapftsContext) {\n return ctx.opts?.optimistic ? callOazapftsFunction(\"ok\", [ex]) : ex;\n}\n","import { OazapftsContext } from \"../context\";\nimport { resolve } from \"@oazapfts/resolve\";\n\nexport function findAvailableRef(ref: string, ctx: OazapftsContext) {\n const available = (ref: string) => {\n try {\n resolve({ $ref: ref }, ctx);\n return false;\n } catch (error) {\n return true;\n }\n };\n\n if (available(ref)) return ref;\n\n let i = 2;\n while (true) {\n const key = ref + String(i);\n if (available(key)) return key;\n i += 1;\n }\n}\n","import { SchemaObject } from \"./openApi3-x\";\n\n/**\n * Check if a schema is suitable for generating a named enum declaration\n * (has enum values, has a name, and is not a boolean type).\n */\nexport function isNamedEnumSchema(\n schema: SchemaObject,\n name?: string,\n): name is string {\n return Boolean(\n typeof schema !== \"boolean\" &&\n schema.enum &&\n name &&\n schema.type !== \"boolean\",\n );\n}\n","export const enumStyleOptions = [\"union\", \"enum\", \"as-const\"] as const;\nexport type EnumStyle = (typeof enumStyleOptions)[number];\n\n/**\n * Resolve the effective enum style from options.\n * `enumStyle` takes precedence over the deprecated `useEnumType`.\n */\nexport function getEnumStyle(opts: {\n enumStyle?: EnumStyle;\n useEnumType?: boolean;\n}): EnumStyle {\n if (opts.enumStyle !== undefined) return opts.enumStyle;\n if (opts.useEnumType) return \"enum\";\n return \"union\";\n}\n","import { OazapftsContext } from \"../context\";\n\nexport function getUniqueAlias(name: string, ctx: OazapftsContext) {\n let used = ctx.typeAliases[name] || 0;\n if (used) {\n ctx.typeAliases[name] = ++used;\n name += used;\n }\n\n ctx.typeAliases[name] = 1;\n return name;\n}\n","import { OazapftsContext } from \"../context\";\nimport { getUniqueAlias } from \"./getUniqueAlias\";\n\n/**\n * Return a unique alias for an enum declaration.\n * Reuses the existing name when the same enum values have already been registered.\n */\nexport function getEnumUniqueAlias(\n name: string,\n values: string,\n ctx: OazapftsContext,\n) {\n if (ctx.enumRefs[name] && ctx.enumRefs[name].values === values) {\n return name;\n }\n\n return getUniqueAlias(name, ctx);\n}\n","import { SchemaObject } from \"./openApi3-x\";\n\n/**\n * Extract custom member names from `x-enumNames` or `x-enum-varnames` extensions.\n * Returns `undefined` when neither extension is present.\n */\nexport function getCustomNames(\n schema: Exclude<SchemaObject, boolean>,\n values: unknown[],\n) {\n const names =\n \"x-enumNames\" in schema\n ? schema[\"x-enumNames\"]\n : \"x-enum-varnames\" in schema\n ? schema[\"x-enum-varnames\"]\n : undefined;\n\n if (names) {\n if (!Array.isArray(names)) {\n throw new Error(\"enum names must be an array\");\n }\n if (names.length !== values.length) {\n throw new Error(\"enum names must have the same length as enum values\");\n }\n if (names.some((name) => typeof name !== \"string\")) {\n throw new Error(\"enum names must be an array of strings\");\n }\n return names as string[];\n }\n\n return undefined;\n}\n","import { isReference, resolve } from \"@oazapfts/resolve\";\nimport { OazapftsContext, OnlyModes } from \"../context\";\nimport { ReferenceObject, SchemaObject } from \"./openApi3-x\";\n\n/**\n * Checks if readOnly/writeOnly properties are present in the given schema.\n * Returns a tuple of booleans; the first one is about readOnly, the second\n * one is about writeOnly.\n */\nexport function checkSchemaOnlyMode(\n schema: SchemaObject | ReferenceObject,\n ctx: OazapftsContext,\n resolveRefs = true,\n): OnlyModes {\n if (ctx.opts.mergeReadWriteOnly) {\n return { readOnly: false, writeOnly: false };\n }\n\n const check = (\n schema: SchemaObject | ReferenceObject,\n history: Set<string>,\n ): OnlyModes => {\n if (isReference(schema)) {\n if (!resolveRefs) return { readOnly: false, writeOnly: false };\n\n // history is used to prevent infinite recursion\n if (history.has(schema.$ref))\n return { readOnly: false, writeOnly: false };\n\n // check if the result is cached in `this.refsOnlyMode`\n const cached = ctx.refsOnlyMode.get(schema.$ref);\n if (cached) return cached;\n\n history.add(schema.$ref);\n const ret = check(resolve(schema, ctx), history);\n history.delete(schema.$ref);\n\n // cache the result\n ctx.refsOnlyMode.set(schema.$ref, ret);\n\n return ret;\n }\n\n if (typeof schema === \"boolean\") {\n return { readOnly: false, writeOnly: false };\n }\n\n let readOnly = schema.readOnly ?? false;\n let writeOnly = schema.writeOnly ?? false;\n\n const subSchemas: (ReferenceObject | SchemaObject)[] = [];\n if (\"items\" in schema && schema.items) {\n subSchemas.push(schema.items);\n } else {\n subSchemas.push(...Object.values(schema.properties ?? {}));\n subSchemas.push(...(schema.allOf ?? []));\n subSchemas.push(...(schema.anyOf ?? []));\n subSchemas.push(...(schema.oneOf ?? []));\n }\n\n for (const schema of subSchemas) {\n // `readOnly` and `writeOnly` do not change once they become true,\n // so you can exit early if both are true.\n if (readOnly && writeOnly) break;\n\n const result = check(schema, history);\n readOnly = readOnly || result.readOnly;\n writeOnly = writeOnly || result.writeOnly;\n }\n\n return { readOnly, writeOnly };\n };\n\n return check(schema, new Set<string>());\n}\n","import { isReference } from \"@oazapfts/resolve\";\nimport { ReferenceObject, SchemaObject } from \"./openApi3-x\";\n\nexport function isNullable(schema?: SchemaObject | ReferenceObject) {\n if (typeof schema === \"boolean\") return schema;\n\n if (schema && \"nullable\" in schema)\n return !isReference(schema) && schema.nullable;\n\n return false;\n}\n","const HttpMethods = [\n \"GET\",\n \"PUT\",\n \"POST\",\n \"DELETE\",\n \"OPTIONS\",\n \"HEAD\",\n \"PATCH\",\n \"TRACE\",\n] as const;\nexport type HttpMethod = (typeof HttpMethods)[number];\n\nexport function isHttpMethod(method: string): method is HttpMethod {\n return HttpMethods.includes(method as HttpMethod);\n}\n","import { OazapftsContext } from \"../context\";\nimport * as cg from \"../generate/tscodegen\";\n\nexport function getEmptySchemaType(ctx: OazapftsContext) {\n return ctx.opts.useUnknown ? cg.keywordType.unknown : cg.keywordType.any;\n}\n","import ts from \"typescript\";\nimport _ from \"lodash\";\nimport { OazapftsContext } from \"../../context\";\nimport { SchemaObject } from \"../../helpers/openApi3-x\";\nimport * as cg from \"../tscodegen\";\nimport * as h from \"../../helpers\";\nimport { getEnumUniqueAlias } from \"../../helpers/getEnumUniqueAlias\";\nimport { getCustomNames } from \"../../helpers/getCustomNames\";\n\n/**\n * Creates a enum \"ref\" if not used, reuse existing if values and name matches or creates a new one\n * with a new name adding a number\n */\nexport function getTrueEnum(\n schema: SchemaObject,\n propName: string,\n ctx: OazapftsContext,\n) {\n if (typeof schema === \"boolean\") {\n // this should never be thrown, since `getTrueEnum` calls are\n // behind an `isNamedEnumSchema` check, which returns false for boolean schemas.\n throw new Error(\n \"cannot get enum from boolean schema. schema must be an object\",\n );\n }\n const baseName = schema.title || _.upperFirst(propName);\n // TODO: use _.camelCase in future major version\n // (currently we allow _ and $ for backwards compatibility)\n const proposedName = baseName\n .split(/[^A-Za-z0-9$_]/g)\n .map((n) => _.upperFirst(n))\n .join(\"\");\n const stringEnumValue = (schema.enum ?? []).join(\"_\");\n\n const name = getEnumUniqueAlias(proposedName, stringEnumValue, ctx);\n\n if (ctx.enumRefs[proposedName] && proposedName === name) {\n return ctx.enumRefs[proposedName].type;\n }\n\n const values = schema.enum ? schema.enum : [];\n\n const names = getCustomNames(schema, values);\n\n const members = values.map((s, index) => {\n if (\n schema.type === \"number\" ||\n schema.type === \"integer\" ||\n schema.type === \"string\"\n ) {\n const name = names ? names[index] : String(s);\n return ts.factory.createEnumMember(\n ts.factory.createIdentifier(h.toIdentifier(name, true)),\n cg.createLiteral(s),\n );\n }\n return ts.factory.createEnumMember(\n ts.factory.createIdentifier(h.toIdentifier(String(s), true)),\n cg.createLiteral(s),\n );\n });\n ctx.enumAliases.push(\n ts.factory.createEnumDeclaration([cg.modifier.export], name, members),\n );\n\n const type = ts.factory.createTypeReferenceNode(name, undefined);\n\n ctx.enumRefs[proposedName] = {\n values: stringEnumValue,\n type: ts.factory.createTypeReferenceNode(name, undefined),\n };\n\n return type;\n}\n","import ts from \"typescript\";\nimport _ from \"lodash\";\nimport { OazapftsContext } from \"../../context\";\nimport { SchemaObject } from \"../../helpers/openApi3-x\";\nimport * as cg from \"../tscodegen\";\nimport * as h from \"../../helpers\";\nimport { getEnumUniqueAlias } from \"../../helpers/getEnumUniqueAlias\";\nimport { getCustomNames } from \"../../helpers/getCustomNames\";\n\n/**\n * Creates an `as const` object declaration with a companion type alias.\n *\n * Generates:\n * ```\n * export const Name = { A: 'a', B: 'b' } as const;\n * export type Name = (typeof Name)[keyof typeof Name];\n * ```\n */\nexport function getAsConstEnum(\n schema: SchemaObject,\n propName: string,\n ctx: OazapftsContext,\n) {\n if (typeof schema === \"boolean\") {\n throw new Error(\n \"cannot get enum from boolean schema. schema must be an object\",\n );\n }\n\n const baseName = schema.title || _.upperFirst(propName);\n const proposedName = baseName\n .split(/[^A-Za-z0-9$_]/g)\n .map((n) => _.upperFirst(n))\n .join(\"\");\n const stringEnumValue = (schema.enum ?? []).join(\"_\");\n\n const name = getEnumUniqueAlias(proposedName, stringEnumValue, ctx);\n\n if (ctx.enumRefs[proposedName] && proposedName === name) {\n return ctx.enumRefs[proposedName].type;\n }\n\n const values = schema.enum ? schema.enum : [];\n const names = getCustomNames(schema, values);\n\n const properties = values.map((s, index) => {\n if (\n schema.type === \"number\" ||\n schema.type === \"integer\" ||\n schema.type === \"string\"\n ) {\n const memberName = names ? names[index] : String(s);\n return ts.factory.createPropertyAssignment(\n ts.factory.createIdentifier(h.toIdentifier(memberName, true)),\n cg.createLiteral(s),\n );\n }\n return ts.factory.createPropertyAssignment(\n ts.factory.createIdentifier(h.toIdentifier(String(s), true)),\n cg.createLiteral(s),\n );\n });\n\n // export const Name = { ... } as const;\n const constStatement = ts.factory.createVariableStatement(\n [cg.modifier.export],\n ts.factory.createVariableDeclarationList(\n [\n ts.factory.createVariableDeclaration(\n name,\n undefined,\n undefined,\n ts.factory.createAsExpression(\n ts.factory.createObjectLiteralExpression(properties, true),\n ts.factory.createTypeReferenceNode(\"const\"),\n ),\n ),\n ],\n ts.NodeFlags.Const,\n ),\n );\n\n // export type Name = (typeof Name)[keyof typeof Name];\n const typeStatement = cg.createTypeAliasDeclaration({\n modifiers: [cg.modifier.export],\n name,\n type: ts.factory.createIndexedAccessTypeNode(\n ts.factory.createParenthesizedType(\n ts.factory.createTypeQueryNode(ts.factory.createIdentifier(name)),\n ),\n ts.factory.createTypeOperatorNode(\n ts.SyntaxKind.KeyOfKeyword,\n ts.factory.createTypeQueryNode(ts.factory.createIdentifier(name)),\n ),\n ),\n });\n\n ctx.enumAliases.push(constStatement, typeStatement);\n\n const type = ts.factory.createTypeReferenceNode(name, undefined);\n\n ctx.enumRefs[proposedName] = {\n values: stringEnumValue,\n type,\n };\n\n return type;\n}\n","import ts from \"typescript\";\nimport { keywordType } from \"../tscodegen\";\n\n/**\n * Creates literal type (or union) from an array of values\n */\nexport function getTypeFromEnum(values: unknown[]) {\n const types = values.map((s) => {\n if (s === null) return keywordType.null;\n if (typeof s === \"boolean\")\n return s\n ? ts.factory.createLiteralTypeNode(\n ts.factory.createToken(ts.SyntaxKind.TrueKeyword),\n )\n : ts.factory.createLiteralTypeNode(\n ts.factory.createToken(ts.SyntaxKind.FalseKeyword),\n );\n if (typeof s === \"number\")\n return ts.factory.createLiteralTypeNode(\n ts.factory.createNumericLiteral(s),\n );\n if (typeof s === \"string\")\n return ts.factory.createLiteralTypeNode(\n ts.factory.createStringLiteral(s),\n );\n throw new Error(`Unexpected ${String(s)} of type ${typeof s} in enum`);\n });\n return types.length > 1 ? ts.factory.createUnionTypeNode(types) : types[0];\n}\n","import ts, { factory } from \"typescript\";\nimport { resolve } from \"@oazapfts/resolve\";\nimport { OazapftsContext } from \"../../context\";\nimport { getEnumStyle, isNamedEnumSchema, toIdentifier } from \"../../helpers\";\nimport * as OpenApi from \"../../helpers/openApi3-x\";\nimport { getTrueEnum } from \"./getTrueEnum\";\nimport { getAsConstEnum } from \"./getAsConstEnum\";\nimport { getTypeFromEnum } from \"./getTypeFromEnum\";\n\n/**\n * Get enum member reference type for discriminator values when enumStyle is \"enum\" or \"as-const\"\n */\nexport function getDiscriminatorType(\n ctx: OazapftsContext,\n discriminatingSchemaRef:\n | Exclude<OpenApi.SchemaObject, boolean>\n | OpenApi.ReferenceObject,\n propertyName: string,\n matches: string[],\n) {\n const enumStyle = getEnumStyle(ctx.opts);\n if (enumStyle === \"union\") {\n return getTypeFromEnum(matches);\n }\n\n const discriminatingSchema = resolve(discriminatingSchemaRef, ctx);\n // Get the discriminator property schema to check if it should use enum types\n // Check the schema's own properties first, then search in allOf parents\n let discriminatorPropertySchema = resolve(\n discriminatingSchema.properties?.[propertyName],\n ctx,\n );\n\n if (!discriminatorPropertySchema && discriminatingSchema.allOf) {\n for (const allOfSchema of discriminatingSchema.allOf) {\n const resolvedAllOf = resolve(allOfSchema, ctx);\n if (resolvedAllOf.properties?.[propertyName]) {\n discriminatorPropertySchema = resolve(\n resolvedAllOf.properties[propertyName],\n ctx,\n );\n break;\n }\n }\n }\n\n if (\n !discriminatorPropertySchema ||\n !isNamedEnumSchema(discriminatorPropertySchema, propertyName)\n ) {\n return getTypeFromEnum(matches);\n }\n\n const enumTypeRef =\n enumStyle === \"as-const\"\n ? getAsConstEnum(discriminatorPropertySchema, propertyName, ctx)\n : getTrueEnum(discriminatorPropertySchema, propertyName, ctx);\n\n const memberTypes = matches.map((value) => {\n const entity = factory.createQualifiedName(\n enumTypeRef.typeName,\n factory.createIdentifier(toIdentifier(value, true)),\n );\n\n if (enumStyle === \"as-const\") {\n return factory.createTypeQueryNode(entity);\n }\n\n return factory.createTypeReferenceNode(entity);\n });\n\n return memberTypes.length === 1\n ? memberTypes[0]\n : factory.createUnionTypeNode(memberTypes);\n}\n","import ts from \"typescript\";\nimport _ from \"lodash\";\nimport { isReference, resolve, getRefBasename } from \"@oazapfts/resolve\";\nimport { OazapftsContext } from \"../../context\";\nimport * as OpenApi from \"../../helpers/openApi3-x\";\nimport { createPropertySignature } from \"../tscodegen\";\nimport { getTypeFromSchema } from \"./getTypeFromSchema\";\nimport { getDiscriminatorType } from \"./getDiscriminatorType\";\n\nexport function getUnionType(\n variants: (OpenApi.ReferenceObject | OpenApi.SchemaObject)[],\n ctx: OazapftsContext,\n discriminator?: OpenApi.DiscriminatorObject,\n) {\n if (discriminator) {\n // oneOf + discriminator -> tagged union (polymorphism)\n if (discriminator.propertyName === undefined) {\n throw new Error(\"Discriminators require a propertyName\");\n }\n\n // By default, the last component of the ref name (i.e., after the last trailing slash) is\n // used as the discriminator value for each variant. This can be overridden using the\n // discriminator.mapping property.\n const mappedValues = new Set(\n Object.values(discriminator.mapping || {}).map(getRefBasename),\n );\n\n return ts.factory.createUnionTypeNode(\n (\n [\n ...Object.entries(discriminator.mapping || {}).map(\n ([discriminatorValue, variantRef]) => [\n discriminatorValue,\n { $ref: variantRef },\n ],\n ),\n ...variants\n .filter((variant) => {\n if (!isReference(variant)) {\n // From the Swagger spec: \"When using the discriminator, inline schemas will not be\n // considered.\"\n throw new Error(\n \"Discriminators require references, not inline schemas\",\n );\n }\n return !mappedValues.has(getRefBasename(variant.$ref));\n })\n .map((schema) => {\n const schemaBaseName = getRefBasename(\n (schema as OpenApi.ReferenceObject).$ref,\n );\n // TODO: handle boolean\n const resolvedSchema = resolve(schema, ctx) as Exclude<\n OpenApi.SchemaObject,\n boolean\n >;\n const discriminatorProperty =\n resolvedSchema.properties?.[discriminator.propertyName];\n const variantName =\n discriminatorProperty && \"enum\" in discriminatorProperty\n ? discriminatorProperty?.enum?.[0]\n : \"\";\n return [variantName || schemaBaseName, schema];\n }),\n ] as [string, OpenApi.ReferenceObject][]\n ).map(([discriminatorValue, variant]) =>\n // Yields: { [discriminator.propertyName]: discriminatorValue } & variant\n ts.factory.createIntersectionTypeNode([\n ts.factory.createTypeLiteralNode([\n createPropertySignature({\n name: discriminator.propertyName,\n type: getDiscriminatorType(\n ctx,\n variant,\n discriminator.propertyName,\n [discriminatorValue],\n ),\n }),\n ]),\n getTypeFromSchema(ctx, variant),\n ]),\n ),\n );\n } else {\n // oneOf -> untagged union\n return ts.factory.createUnionTypeNode(\n _.uniq(variants.map((schema) => getTypeFromSchema(ctx, schema))),\n );\n }\n}\n","import ts from \"typescript\";\nimport { OazapftsContext } from \"../../context\";\nimport { ReferenceObject, SchemaObject } from \"../../helpers/openApi3-x\";\nimport * as cg from \"../tscodegen\";\nimport { checkSchemaOnlyMode } from \"../../helpers\";\nimport { getTypeFromSchema } from \"./getTypeFromSchema\";\nimport { getEmptySchemaType } from \"../../helpers/emptySchemaType\";\n\n/**\n * Recursively creates a type literal with the given props.\n */\nexport function getTypeFromProperties(\n props: {\n [prop: string]: SchemaObject | ReferenceObject;\n },\n ctx: OazapftsContext,\n required?: string[],\n additionalProperties?: boolean | SchemaObject | ReferenceObject,\n): ts.TypeLiteralNode {\n const currentMode = ctx.mode;\n\n // Check if any of the props are readOnly or writeOnly schemas\n const propertyNames = Object.keys(props);\n const filteredPropertyNames = propertyNames.filter((name) => {\n const schema = props[name];\n const { readOnly, writeOnly } = checkSchemaOnlyMode(schema, ctx, false);\n\n switch (currentMode) {\n case \"readOnly\":\n return readOnly || !writeOnly;\n case \"writeOnly\":\n return writeOnly || !readOnly;\n default:\n return !readOnly && !writeOnly;\n }\n });\n\n const members: ts.TypeElement[] = filteredPropertyNames.map((name) => {\n const schema = props[name];\n const isRequired = required && required.includes(name);\n let type = getTypeFromSchema(ctx, schema, name);\n if (!isRequired && ctx.opts.unionUndefined) {\n type = ts.factory.createUnionTypeNode([type, cg.keywordType.undefined]);\n }\n\n const signature = cg.createPropertySignature({\n questionToken: !isRequired,\n name,\n type,\n });\n\n if (\n typeof schema !== \"boolean\" &&\n \"description\" in schema &&\n schema.description\n ) {\n // Escape any JSDoc comment closing tags in description\n const description = schema.description.replace(\"*/\", \"*\\\\/\");\n\n ts.addSyntheticLeadingComment(\n signature,\n ts.SyntaxKind.MultiLineCommentTrivia,\n // Ensures it is formatted like a JSDoc comment: /** description here */\n `* ${description} `,\n true,\n );\n }\n\n return signature;\n });\n if (additionalProperties) {\n const type =\n additionalProperties === true\n ? getEmptySchemaType(ctx)\n : getTypeFromSchema(ctx, additionalProperties);\n\n members.push(cg.createIndexSignature(type));\n }\n return ts.factory.createTypeLiteralNode(members);\n}\n","import ts, { factory } from \"typescript\";\nimport _ from \"lodash\";\nimport { isReference, resolve } from \"@oazapfts/resolve\";\nimport { OazapftsContext } from \"../../context\";\nimport * as OpenApi from \"../../helpers/openApi3-x\";\nimport * as cg from \"../tscodegen\";\nimport * as h from \"../../helpers\";\nimport { getEmptySchemaType } from \"../../helpers/emptySchemaType\";\nimport { getRefAlias } from \"../getRefAlias\";\nimport { getUnionType } from \"./getUnionType\";\nimport { getTypeFromProperties } from \"./getTypeFromProperties\";\nimport { getTrueEnum } from \"./getTrueEnum\";\nimport { getAsConstEnum } from \"./getAsConstEnum\";\nimport { getTypeFromEnum } from \"./getTypeFromEnum\";\nimport { getDiscriminatorType } from \"./getDiscriminatorType\";\n\n/**\n * Creates a type node from a given schema.\n * Delegates to getBaseTypeFromSchema internally and\n * optionally adds a union with null.\n */\nexport function getTypeFromSchema(\n ctx: OazapftsContext,\n schema?: OpenApi.SchemaObject | OpenApi.ReferenceObject,\n name?: string,\n) {\n const type = getBaseTypeFromSchema(ctx, schema, name);\n return h.isNullable(schema)\n ? ts.factory.createUnionTypeNode([type, cg.keywordType.null])\n : type;\n}\n\n/**\n * This is the very core of the OpenAPI to TS conversion - it takes a\n * schema and returns the appropriate type.\n */\nfunction getBaseTypeFromSchema(\n ctx: OazapftsContext,\n schema?: OpenApi.SchemaObject | OpenApi.ReferenceObject,\n name?: string,\n): ts.TypeNode {\n if (schema === undefined) return getEmptySchemaType(ctx);\n if (isReference(schema)) {\n return getRefAlias(schema, ctx) as ts.TypeReferenceNode;\n }\n\n if (schema === true) {\n return getEmptySchemaType(ctx);\n }\n\n if (schema === false) {\n return cg.keywordType.never;\n }\n\n if (schema.oneOf) {\n const clone = { ...schema };\n delete clone.oneOf;\n // oneOf -> union\n return getUnionType(\n schema.oneOf.map((variant) =>\n // ensure that base properties from the schema are included in the oneOf variants\n _.mergeWith({}, clone, variant, (objValue, srcValue) => {\n if (_.isArray(objValue)) {\n return objValue.concat(srcValue);\n }\n }),\n ),\n ctx,\n schema.discriminator,\n );\n }\n if (schema.anyOf) {\n // anyOf -> union\n return getUnionType(schema.anyOf, ctx);\n }\n if (schema.discriminator?.mapping) {\n // discriminating schema -> union\n const mapping = schema.discriminator.mapping;\n return getUnionType(\n Object.values(mapping).map((ref) => ({ $ref: ref })),\n ctx,\n undefined,\n );\n }\n if (schema.allOf) {\n // allOf -> intersection\n const types: ts.TypeNode[] = [];\n for (const childSchema of schema.allOf) {\n if (\n isReference(childSchema) &&\n ctx.discriminatingSchemas.has(\n resolve(childSchema, ctx) as OpenApi.SchemaObject,\n )\n ) {\n const discriminatingSchema =\n resolve<OpenApi.UNSTABLE_DiscriminatingSchemaObject>(\n childSchema,\n ctx,\n );\n const discriminator = discriminatingSchema.discriminator;\n\n const matches = Object.entries(discriminator.mapping ?? {})\n .filter(([, ref]) => resolve({ $ref: ref }, ctx) === schema)\n .map(([discriminatorValue]) => discriminatorValue);\n if (matches.length > 0) {\n types.push(\n ts.factory.createTypeLiteralNode([\n cg.createPropertySignature({\n name: discriminator.propertyName,\n type: getDiscriminatorType(\n ctx,\n childSchema,\n discriminator.propertyName,\n matches,\n ),\n }),\n ]),\n );\n }\n types.push(\n getRefAlias(childSchema, ctx, /* ignoreDiscriminator */ true),\n );\n } else {\n types.push(\n getTypeFromSchema(ctx, {\n required: schema.required,\n ...childSchema,\n }),\n );\n }\n }\n\n if (schema.properties || schema.additionalProperties) {\n // properties -> literal type\n types.push(\n getTypeFromProperties(\n schema.properties || {},\n ctx,\n schema.required,\n schema.additionalProperties,\n ),\n );\n }\n return ts.factory.createIntersectionTypeNode(types);\n }\n // Union types defined by an array in schema.type\n if (Array.isArray(schema.type)) {\n return factory.createUnionTypeNode(\n schema.type.map((type) => {\n const subSchema = { ...schema, type } as Exclude<\n OpenApi.SchemaObject,\n boolean\n >;\n // Remove items if the type isn't array since it's not relevant\n if (\"items\" in subSchema && type !== \"array\") {\n delete subSchema.items;\n }\n if (\"properties\" in subSchema && type !== \"object\") {\n delete subSchema.properties;\n }\n\n return getBaseTypeFromSchema(ctx, subSchema, name);\n }),\n );\n }\n if (\"items\" in schema) {\n const schemaItems = schema.items;\n\n // items -> array of enums or unions\n if (schemaItems && !isReference(schemaItems) && schemaItems.enum) {\n const enumStyle = h.getEnumStyle(ctx.opts);\n let enumType;\n if (enumStyle !== \"union\" && h.isNamedEnumSchema(schemaItems, name)) {\n enumType =\n enumStyle === \"as-const\"\n ? getAsConstEnum(schemaItems, name, ctx)\n : getTrueEnum(schemaItems, name, ctx);\n } else {\n enumType = cg.createEnumTypeNode(schemaItems.enum);\n }\n\n return factory.createArrayTypeNode(enumType);\n }\n\n // items -> array\n return ts.factory.createArrayTypeNode(getTypeFromSchema(ctx, schema.items));\n }\n if (\"prefixItems\" in schema && Array.isArray(schema.prefixItems)) {\n // prefixItems -> typed tuple\n return ts.factory.createTupleTypeNode(\n schema.prefixItems.map((schema) => getTypeFromSchema(ctx, schema)),\n );\n }\n if (schema.properties || schema.additionalProperties) {\n // properties -> literal type\n return getTypeFromProperties(\n schema.properties || {},\n ctx,\n schema.required,\n schema.additionalProperties,\n );\n }\n\n if (schema.enum) {\n const enumStyle = h.getEnumStyle(ctx.opts);\n if (enumStyle !== \"union\" && h.isNamedEnumSchema(schema, name)) {\n return enumStyle === \"as-const\"\n ? getAsConstEnum(schema, name, ctx)\n : getTrueEnum(schema, name, ctx);\n }\n return cg.createEnumTypeNode(schema.enum);\n }\n if (schema.format == \"binary\") {\n return ts.factory.createTypeReferenceNode(\"Blob\", []);\n }\n if (\"const\" in schema && schema.const) {\n return getTypeFromEnum([schema.const]);\n }\n if (schema.type !== undefined) {\n if (schema.type === null) return cg.keywordType.null;\n if (isKeyOfKeywordType(schema.type)) return cg.keywordType[schema.type];\n }\n\n return getEmptySchemaType(ctx);\n}\n\nfunction isKeyOfKeywordType(key: string): key is keyof typeof cg.keywordType {\n return key in cg.keywordType;\n}\n","import ts from \"typescript\";\nimport _ from \"lodash\";\nimport { resolve, getRefName } from \"@oazapfts/resolve\";\nimport { OazapftsContext, withMode } from \"../context\";\nimport * as cg from \"./tscodegen\";\nimport * as OpenApi from \"../helpers/openApi3-x\";\nimport * as h from \"../helpers\";\nimport { getTypeFromSchema } from \"./getTypeFromSchema\";\n\n/**\n * Create a type alias for the schema referenced by the given ReferenceObject\n */\nexport function getRefAlias(\n obj: OpenApi.ReferenceObject,\n ctx: OazapftsContext,\n // If true, the discriminator property of the schema referenced by `obj` will be ignored.\n // This is meant to be used when getting the type of a discriminating schema in an `allOf`\n // construct.\n ignoreDiscriminator?: boolean,\n) {\n const $ref = ignoreDiscriminator\n ? h.findAvailableRef(obj.$ref + \"Base\", ctx)\n : obj.$ref;\n\n if (!ctx.refs[$ref]) {\n let schema = resolve<OpenApi.SchemaObject>(obj, ctx);\n if (typeof schema !== \"boolean\" && ignoreDiscriminator) {\n schema = _.cloneDeep(schema);\n delete schema.discriminator;\n }\n const name =\n (typeof schema !== \"boolean\" && schema.title) || getRefName($ref);\n const identifier = h.toIdentifier(name, true);\n\n // When this is a named enum (enum or as-const) we can reference it directly,\n // no need to create a type alias\n if (\n h.getEnumStyle(ctx.opts) !== \"union\" &&\n h.isNamedEnumSchema(schema, name)\n ) {\n return getTypeFromSchema(ctx, schema, name);\n }\n\n const alias = h.getUniqueAlias(identifier, ctx);\n\n ctx.refs[$ref] = {\n base: ts.factory.createTypeReferenceNode(alias, undefined),\n readOnly: undefined,\n writeOnly: undefined,\n };\n\n const baseType = getTypeFromSchema(withMode(ctx, undefined), schema);\n ctx.aliases.push(\n cg.createTypeAliasDeclaration({\n modifiers: [cg.modifier.export],\n name: alias,\n type: baseType,\n }),\n );\n\n const { readOnly, writeOnly } = h.checkSchemaOnlyMode(schema, ctx);\n\n if (readOnly) {\n const readOnlyAlias = h.getUniqueAlias(\n h.toIdentifier(name, true, \"readOnly\"),\n ctx,\n );\n ctx.refs[$ref][\"readOnly\"] = ts.factory.createTypeReferenceNode(\n readOnlyAlias,\n undefined,\n );\n\n const readOnlyType = getTypeFromSchema(\n withMode(ctx, \"readOnly\"),\n schema,\n name,\n );\n ctx.aliases.push(\n cg.createTypeAliasDeclaration({\n modifiers: [cg.modifier.export],\n name: readOnlyAlias,\n type: readOnlyType,\n }),\n );\n }\n\n if (writeOnly) {\n const writeOnlyAlias = h.getUniqueAlias(\n h.toIdentifier(name, true, \"writeOnly\"),\n ctx,\n );\n ctx.refs[$ref][\"writeOnly\"] = ts.factory.createTypeReferenceNode(\n writeOnlyAlias,\n undefined,\n );\n const writeOnlyType = getTypeFromSchema(\n withMode(ctx, \"writeOnly\"),\n schema,\n name,\n );\n ctx.aliases.push(\n cg.createTypeAliasDeclaration({\n modifiers: [cg.modifier.export],\n name: writeOnlyAlias,\n type: writeOnlyType,\n }),\n );\n }\n }\n\n // If not ref fallback to the regular reference\n return ctx.refs[$ref][ctx.mode || \"base\"] ?? ctx.refs[$ref].base;\n}\n","import { isReference, resolve, getRefBasename } from \"@oazapfts/resolve\";\nimport { OazapftsContext } from \"../context\";\nimport * as OpenApi from \"../helpers/openApi3-x\";\n\n/**\n * In order to support discriminated unions.\n *\n * @see https://github.com/oazapfts/oazapfts/pull/473\n *\n * Does three things:\n * 1. Add a `x-component-ref-path` property.\n * 2. Record discriminating schemas in `this.discriminatingSchemas`. A discriminating schema\n * refers to a schema that has a `discriminator` property which is neither used in conjunction\n * with `oneOf` nor `anyOf`.\n * 3. Make all mappings of discriminating schemas explicit to generate types immediately.\n */\nexport function preprocessComponents(ctx: OazapftsContext) {\n if (!ctx.spec.components?.schemas) {\n return;\n }\n\n const prefix = \"#/components/schemas/\";\n const schemas = ctx.spec.components.schemas;\n\n // First scan: Add `x-component-ref-path` property and record discriminating schemas\n for (const name of Object.keys(schemas)) {\n const schema = schemas[name];\n if (isReference(schema) || typeof schema === \"boolean\") continue;\n\n if (schema.discriminator && !schema.oneOf && !schema.anyOf) {\n ctx.discriminatingSchemas.add(schema);\n }\n }\n\n const isExplicit = (\n discriminator: OpenApi.DiscriminatorObject,\n ref: string,\n ) => {\n const refs = Object.values(discriminator.mapping || {});\n return refs.includes(ref);\n };\n\n // Second scan: Make all mappings of discriminating schemas explicit\n for (const name of Object.keys(schemas)) {\n const schema = schemas[name];\n\n if (isReference(schema) || typeof schema === \"boolean\" || !schema.allOf) {\n continue;\n }\n\n for (const childSchema of schema.allOf) {\n if (\n !isReference(childSchema) ||\n !ctx.discriminatingSchemas.has(\n resolve<OpenApi.SchemaObject>(childSchema, ctx),\n )\n ) {\n continue;\n }\n\n const discriminatingSchema = schemas[\n getRefBasename(childSchema.$ref)\n ] as OpenApi.UNSTABLE_DiscriminatingSchemaObject;\n if (isReference(discriminatingSchema)) {\n throw new Error(\"Unexpected nested reference\");\n }\n\n const discriminator = discriminatingSchema.discriminator!;\n\n if (isExplicit(discriminator, prefix + name)) continue;\n if (!discriminator.mapping) {\n discriminator.mapping = {};\n }\n discriminator.mapping[name] = prefix + name;\n }\n }\n}\n","import ts from \"typescript\";\nimport { OazapftsContext } from \"../context\";\nimport * as h from \"../helpers\";\nimport * as OpenAPI from \"../helpers/openApi3-x\";\nimport type { UNSTABLE_OazapftsPluginHooks } from \"../plugin\";\nimport { getRefAlias } from \"./getRefAlias\";\nimport { preprocessComponents } from \"./preprocessComponents\";\n\nexport async function generateApi(\n ctx: OazapftsContext,\n hooks: UNSTABLE_OazapftsPluginHooks,\n): Promise<ts.SourceFile> {\n // Preprocess components (needs mutable context)\n preprocessComponents(ctx);\n\n // Hook: prepare - allow plugins to modify spec, context, or template parts\n await hooks.prepare.promise(ctx);\n\n // Generate methods with hook support\n const methods: ts.Statement[] = [];\n for (const [path, pathItem] of Object.entries(ctx.spec.paths || {})) {\n if (!pathItem) continue;\n\n for (const [verb, operation] of Object.entries(pathItem)) {\n if (!operation) continue;\n const method = verb.toUpperCase();\n if (!h.isHttpMethod(method)) continue;\n const endpoint = {\n method,\n path,\n operation: operation as OpenAPI.OperationObject,\n pathItem,\n };\n\n // Hook: filterEndpoint - allow plugins to skip endpoint generation\n const shouldGenerate = hooks.filterEndpoint.call(true, endpoint, ctx);\n if (!shouldGenerate) continue;\n\n // Hook: generateMethod - first plugin returning methods wins\n const generatedMethods =\n (await hooks.generateMethod.promise(endpoint, ctx)) ?? [];\n const refinedMethods = await hooks.refineMethod.promise(\n generatedMethods,\n endpoint,\n ctx,\n );\n\n methods.push(...refinedMethods);\n }\n }\n\n if (ctx.opts.allSchemas && ctx.spec.components?.schemas) {\n for (const [name] of Object.entries(ctx.spec.components.schemas)) {\n getRefAlias({ $ref: `#/components/schemas/${name}` }, ctx);\n }\n }\n\n // Hook: composeSource/refineSource - compose and refine top-level statements\n const composedStatements =\n (await hooks.composeSource.promise(ctx, methods)) ?? [];\n const statements = await hooks.refineSource.promise(\n composedStatements,\n ctx,\n methods,\n );\n\n // Add banner comment to first statement if present\n if (ctx.banner && statements.length > 0) {\n const bannerComment = `*\\n * ${ctx.banner.split(\"\\n\").join(\"\\n * \")}\\n `;\n statements[0] = ts.addSyntheticLeadingComment(\n statements[0],\n ts.SyntaxKind.MultiLineCommentTrivia,\n bannerComment,\n true,\n );\n }\n\n // Create the source file with all parts in order\n let apiSourceFile = ts.factory.createSourceFile(\n statements,\n ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),\n ts.NodeFlags.None,\n );\n\n // Hook: astGenerated - allow plugins to modify final AST\n apiSourceFile = await hooks.astGenerated.promise(apiSourceFile, ctx);\n\n return apiSourceFile;\n}\n","import ts from \"typescript\";\nimport type { Defaults } from \"../context\";\n\n/** Creates: export const defaults: Oazapfts.Defaults<Oazapfts.CustomHeaders> = { ... }; */\nexport function createDefaultsStatement(\n defaults: Defaults,\n): ts.VariableStatement {\n const properties: ts.PropertyAssignment[] = [];\n\n // headers (always include, default to empty object)\n properties.push(\n ts.factory.createPropertyAssignment(\n \"headers\",\n defaults.headers\n ? ts.factory.createObjectLiteralExpression(\n Object.entries(defaults.headers)\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) =>\n ts.factory.createPropertyAssignment(\n ts.factory.createStringLiteral(key),\n createHeaderValueLiteral(value),\n ),\n ),\n )\n : ts.factory.createObjectLiteralExpression([]),\n ),\n );\n\n // baseUrl (include if defined)\n if (defaults.baseUrl !== undefined) {\n properties.push(\n ts.factory.createPropertyAssignment(\n \"baseUrl\",\n ts.factory.createStringLiteral(defaults.baseUrl),\n ),\n );\n }\n\n // fetch (expression-based: FunctionExpression, ArrowFunction, or Identifier)\n if (defaults.fetch) {\n properties.push(\n ts.factory.createPropertyAssignment(\"fetch\", defaults.fetch),\n );\n }\n\n // FormData (expression-based: ClassExpression or Identifier)\n if (defaults.FormData) {\n properties.push(\n ts.factory.createPropertyAssignment(\"FormData\", defaults.FormData),\n );\n }\n\n return ts.factory.createVariableStatement(\n [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],\n ts.factory.createVariableDeclarationList(\n [\n ts.factory.createVariableDeclaration(\n \"defaults\",\n undefined,\n ts.factory.createTypeReferenceNode(\n ts.factory.createQualifiedName(\n ts.factory.createIdentifier(\"Oazapfts\"),\n \"Defaults\",\n ),\n [\n ts.factory.createTypeReferenceNode(\n ts.factory.createQualifiedName(\n ts.factory.createIdentifier(\"Oazapfts\"),\n \"CustomHeaders\",\n ),\n ),\n ],\n ),\n ts.factory.createObjectLiteralExpression(properties, true),\n ),\n ],\n ts.NodeFlags.Const,\n ),\n );\n}\n\n/** Create a literal expression for a header value (string | number | boolean | null) */\nexport function createHeaderValueLiteral(\n value: string | number | boolean | null | undefined,\n): ts.Expression {\n if (value === null) {\n return ts.factory.createNull();\n }\n if (typeof value === \"boolean\") {\n return value ? ts.factory.createTrue() : ts.factory.createFalse();\n }\n if (typeof value === \"number\") {\n return ts.factory.createNumericLiteral(value);\n }\n return ts.factory.createStringLiteral(String(value));\n}\n","import ts from \"typescript\";\nimport type { Import, ImportSpecifier } from \"../context\";\n\n/**\n * Convert an Import definition to a TypeScript ImportDeclaration AST node.\n *\n * Supported formats:\n * - string: side-effect import `import \"module\";`\n * - [specifiers[], { from }]: named imports `import { a, b } from \"module\";`\n * - [default, { from }]: default import `import X from \"module\";`\n * - [default, specifiers[], { from }]: default + named `import X, { a } from \"module\";`\n * - [{ namespace }, { from }]: namespace import `import * as X from \"module\";`\n */\nexport function createImportStatement(imp: Import): ts.ImportDeclaration {\n // Side-effect import: import \"module\";\n if (typeof imp === \"string\") {\n return ts.factory.createImportDeclaration(\n undefined,\n undefined,\n ts.factory.createStringLiteral(imp),\n );\n }\n\n // Namespace import: [{ namespace: \"X\" }, { from: \"module\" }]\n if (imp.length === 2 && typeof imp[0] === \"object\" && \"namespace\" in imp[0]) {\n const [{ namespace }, { from }] = imp as [\n { namespace: string },\n { from: string },\n ];\n return ts.factory.createImportDeclaration(\n undefined,\n ts.factory.createImportClause(\n false,\n undefined,\n ts.factory.createNamespaceImport(\n ts.factory.createIdentifier(namespace),\n ),\n ),\n ts.factory.createStringLiteral(from),\n );\n }\n\n // Default import: [\"Default\", { from: \"module\" }]\n if (\n imp.length === 2 &&\n typeof imp[0] === \"string\" &&\n typeof imp[1] === \"object\" &&\n \"from\" in imp[1]\n ) {\n const [defaultName, { from }] = imp as [string, { from: string }];\n return ts.factory.createImportDeclaration(\n undefined,\n ts.factory.createImportClause(\n false,\n ts.factory.createIdentifier(defaultName),\n undefined,\n ),\n ts.factory.createStringLiteral(from),\n );\n }\n\n // Named imports: [[specifiers], { from: \"module\" }]\n if (imp.length === 2 && Array.isArray(imp[0])) {\n const [specifiers, { from }] = imp as [\n (ImportSpecifier | string)[],\n { from: string },\n ];\n return ts.factory.createImportDeclaration(\n undefined,\n ts.factory.createImportClause(\n false,\n undefined,\n ts.factory.createNamedImports(specifiers.map(createImportSpecifier)),\n ),\n ts.factory.createStringLiteral(from),\n );\n }\n\n // Default + named imports: [\"Default\", [specifiers], { from: \"module\" }]\n if (imp.length === 3) {\n const [defaultName, specifiers, { from }] = imp as [\n string,\n (ImportSpecifier | string)[],\n { from: string },\n ];\n return ts.factory.createImportDeclaration(\n undefined,\n ts.factory.createImportClause(\n false,\n ts.factory.createIdentifier(defaultName),\n ts.factory.createNamedImports(specifiers.map(cre