diff --git a/dev/python/presets.ts b/dev/python/presets.ts index bf667381b..1ccdc10c8 100644 --- a/dev/python/presets.ts +++ b/dev/python/presets.ts @@ -9,11 +9,14 @@ export const presets = { containerName: 'OpenCode', strategy: 'single', }, + paramsStructure: 'flat', }), ], validated: () => [ /** SDK + Pydantic validation */ - sdk(), + sdk({ + paramsStructure: 'flat', + }), pydantic(), ], } as const satisfies Record ReadonlyArray>; diff --git a/packages/openapi-python/src/plugins/pydantic/index.ts b/packages/openapi-python/src/plugins/pydantic/index.ts index f05eaac4c..ff3145be3 100644 --- a/packages/openapi-python/src/plugins/pydantic/index.ts +++ b/packages/openapi-python/src/plugins/pydantic/index.ts @@ -1,2 +1,9 @@ export { defaultConfig, defineConfig } from './config'; +export type { + EnumResolverContext, + NumberResolverContext, + ObjectResolverContext, + Resolvers, + StringResolverContext, +} from './resolvers'; export type { PydanticPlugin } from './types'; diff --git a/packages/openapi-python/src/plugins/pydantic/resolvers.ts b/packages/openapi-python/src/plugins/pydantic/resolvers.ts new file mode 100644 index 000000000..34d037d12 --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/resolvers.ts @@ -0,0 +1,99 @@ +import type { Plugin, SchemaVisitorContext, SchemaWithType, Walker } from '@hey-api/shared'; + +import type { DollarPyDsl } from '../../py-dsl'; +import type { PydanticField, PydanticFinal, PydanticResult, PydanticType } from './shared/types'; +import type { PydanticPlugin } from './types'; + +export type Resolvers = Plugin.Resolvers<{ + /** + * Resolver for enum schemas. + * + * Allows customization of how enum types are rendered. + * + * Returning `undefined` will execute the default resolver logic. + */ + enum?: (ctx: EnumResolverContext) => PydanticType | undefined; + /** + * Resolver for number schemas. + * + * Allows customization of how number types are rendered. + * + * Returning `undefined` will execute the default resolver logic. + */ + number?: (ctx: NumberResolverContext) => PydanticType | undefined; + /** + * Resolver for object schemas. + * + * Allows customization of how object types are rendered. + * + * Returning `undefined` will execute the default resolver logic. + */ + object?: ( + ctx: ObjectResolverContext, + ) => (PydanticType & { fields?: Array }) | undefined; + /** + * Resolver for string schemas. + * + * Allows customization of how string types are rendered. + * + * Returning `undefined` will execute the default resolver logic. + */ + string?: (ctx: StringResolverContext) => PydanticType | undefined; +}>; + +interface BaseContext extends DollarPyDsl { + /** The plugin instance. */ + plugin: PydanticPlugin['Instance']; +} + +export interface EnumResolverContext extends BaseContext { + /** + * Nodes used to build different parts of the result. + */ + nodes: { + base: (ctx: EnumResolverContext) => PydanticType; + items: (ctx: EnumResolverContext) => { + enumMembers: Required['enumMembers']; + isNullable: boolean; + }; + }; + schema: SchemaWithType<'enum'>; +} + +export interface NumberResolverContext extends BaseContext { + /** + * Nodes used to build different parts of the result. + */ + nodes: { + base: (ctx: NumberResolverContext) => PydanticType; + const: (ctx: NumberResolverContext) => PydanticType | undefined; + }; + schema: SchemaWithType<'integer' | 'number'>; +} + +export interface ObjectResolverContext extends BaseContext { + _childResults: Array; + applyModifiers: (result: PydanticResult, opts: { optional?: boolean }) => PydanticFinal; + /** + * Nodes used to build different parts of the result. + */ + nodes: { + additionalProperties: (ctx: ObjectResolverContext) => PydanticType | null | undefined; + base: (ctx: ObjectResolverContext) => PydanticType & { fields?: Array }; + fields: (ctx: ObjectResolverContext) => Array; + }; + schema: SchemaWithType<'object'>; + walk: Walker; + walkerCtx: SchemaVisitorContext; +} + +export interface StringResolverContext extends BaseContext { + /** + * Nodes used to build different parts of the result. + */ + nodes: { + base: (ctx: StringResolverContext) => PydanticType; + const: (ctx: StringResolverContext) => PydanticType | undefined; + }; + schema: SchemaWithType<'string'>; +} diff --git a/packages/openapi-python/src/plugins/pydantic/types.ts b/packages/openapi-python/src/plugins/pydantic/types.ts index 09fd194ef..e14c2b15e 100644 --- a/packages/openapi-python/src/plugins/pydantic/types.ts +++ b/packages/openapi-python/src/plugins/pydantic/types.ts @@ -7,10 +7,13 @@ import type { Plugin, } from '@hey-api/shared'; +import type { Resolvers } from './resolvers'; + export type UserConfig = Plugin.Name<'pydantic'> & Plugin.Hooks & Plugin.UserComments & - Plugin.UserExports & { + Plugin.UserExports & + Resolvers & { /** * Casing convention for generated names. * @@ -185,7 +188,8 @@ export type UserConfig = Plugin.Name<'pydantic'> & export type Config = Plugin.Name<'pydantic'> & Plugin.Hooks & Plugin.Comments & - Plugin.Exports & { + Plugin.Exports & + Resolvers & { /** Casing convention for generated names. */ case: Casing; /** Configuration for reusable schema definitions. */ diff --git a/packages/openapi-python/src/plugins/pydantic/v2/toAst/enum.ts b/packages/openapi-python/src/plugins/pydantic/v2/toAst/enum.ts index 899a14c17..be3975c17 100644 --- a/packages/openapi-python/src/plugins/pydantic/v2/toAst/enum.ts +++ b/packages/openapi-python/src/plugins/pydantic/v2/toAst/enum.ts @@ -1,8 +1,8 @@ -import type { Symbol } from '@hey-api/codegen-core'; import type { SchemaWithType } from '@hey-api/shared'; import { toCase } from '@hey-api/shared'; import { $ } from '../../../../py-dsl'; +import type { EnumResolverContext } from '../../resolvers'; import type { PydanticFinal, PydanticType } from '../../shared/types'; import type { PydanticPlugin } from '../../types'; @@ -21,13 +21,8 @@ function toEnumMemberName(value: string | number): string { return toCase(value, 'SCREAMING_SNAKE_CASE'); } -function extractEnumMembers( - schema: SchemaWithType<'enum'>, - plugin: PydanticPlugin['Instance'], -): { - enumMembers: Required['enumMembers']; - isNullable: boolean; -} { +function itemsNode(ctx: EnumResolverContext) { + const { plugin, schema } = ctx; const enumMembers: Required['enumMembers'] = []; let isNullable = false; @@ -51,51 +46,67 @@ function extractEnumMembers( return { enumMembers, isNullable }; } -function toLiteralType( - enumMembers: Required['enumMembers'], - plugin: PydanticPlugin['Instance'], -): string | Symbol | ReturnType { +function baseNode(ctx: EnumResolverContext): PydanticType { + const { plugin } = ctx; + const { enumMembers } = ctx.nodes.items(ctx); + if (enumMembers.length === 0) { - return plugin.external('typing.Any'); + return { + type: plugin.external('typing.Any'), + }; + } + + const mode = plugin.config.enums ?? 'enum'; + + if (mode === 'literal') { + if (enumMembers.length === 0) { + return { + type: plugin.external('typing.Any'), + }; + } + + const literal = plugin.external('typing.Literal'); + const values = enumMembers.map((m) => + // TODO: replace + typeof m.value === 'string' ? `"<<<<${m.value}"` : `<<<${m.value}`, + ); + + return { + type: $(literal).slice(...values), + }; } - const literal = plugin.external('typing.Literal'); - const values = enumMembers.map((m) => - // TODO: replace - typeof m.value === 'string' ? `"<<<<${m.value}"` : `<<<${m.value}`, - ); + return {}; +} - return $(literal).slice(...values); +function enumResolver(ctx: EnumResolverContext): PydanticType { + return ctx.nodes.base(ctx); } export function enumToType({ - mode = 'enum', plugin, schema, }: { - mode?: 'enum' | 'literal'; plugin: PydanticPlugin['Instance']; schema: SchemaWithType<'enum'>; }): EnumToTypeResult { - const { enumMembers, isNullable } = extractEnumMembers(schema, plugin); + const ctx: EnumResolverContext = { + $, + nodes: { + base: baseNode, + items: itemsNode, + }, + plugin, + schema, + }; - if (enumMembers.length === 0) { - return { - enumMembers, - isNullable, - type: plugin.external('typing.Any'), - }; - } + const resolver = plugin.config['~resolvers']?.enum; + const resolved = resolver?.(ctx) ?? enumResolver(ctx); - if (mode === 'literal') { - return { - enumMembers, - isNullable, - type: toLiteralType(enumMembers, plugin), - }; - } + const { enumMembers, isNullable } = ctx.nodes.items(ctx); return { + ...resolved, enumMembers, isNullable, }; diff --git a/packages/openapi-python/src/plugins/pydantic/v2/toAst/number.ts b/packages/openapi-python/src/plugins/pydantic/v2/toAst/number.ts index 306bdd404..4b739bb6e 100644 --- a/packages/openapi-python/src/plugins/pydantic/v2/toAst/number.ts +++ b/packages/openapi-python/src/plugins/pydantic/v2/toAst/number.ts @@ -1,18 +1,13 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../py-dsl'; +import type { NumberResolverContext } from '../../resolvers'; import type { PydanticType } from '../../shared/types'; import type { PydanticPlugin } from '../../types'; import type { FieldConstraints } from '../constants'; -export function numberToType({ - plugin, - schema, -}: { - plugin: PydanticPlugin['Instance']; - schema: SchemaWithType<'integer' | 'number'>; -}): PydanticType { - const constraints: FieldConstraints = {}; +function constNode(ctx: NumberResolverContext): PydanticType | undefined { + const { plugin, schema } = ctx; if (typeof schema.const === 'number') { const literal = plugin.external('typing.Literal'); @@ -21,6 +16,14 @@ export function numberToType({ }; } + return undefined; +} + +function baseNode(ctx: NumberResolverContext): PydanticType { + const { schema } = ctx; + + const constraints: FieldConstraints = {}; + if (schema.minimum !== undefined) { constraints.ge = schema.minimum; } @@ -46,3 +49,31 @@ export function numberToType({ type: schema.type === 'integer' ? 'int' : 'float', }; } + +function numberResolver(ctx: NumberResolverContext): PydanticType { + const constResult = ctx.nodes.const(ctx); + if (constResult) return constResult; + + return ctx.nodes.base(ctx); +} + +export function numberToType({ + plugin, + schema, +}: { + plugin: PydanticPlugin['Instance']; + schema: SchemaWithType<'integer' | 'number'>; +}): PydanticType { + const ctx: NumberResolverContext = { + $, + nodes: { + base: baseNode, + const: constNode, + }, + plugin, + schema, + }; + + const resolver = plugin.config['~resolvers']?.number; + return resolver?.(ctx) ?? numberResolver(ctx); +} diff --git a/packages/openapi-python/src/plugins/pydantic/v2/toAst/object.ts b/packages/openapi-python/src/plugins/pydantic/v2/toAst/object.ts index bad174e50..fe035cf94 100644 --- a/packages/openapi-python/src/plugins/pydantic/v2/toAst/object.ts +++ b/packages/openapi-python/src/plugins/pydantic/v2/toAst/object.ts @@ -1,25 +1,15 @@ -import type { SchemaVisitorContext, SchemaWithType, Walker } from '@hey-api/shared'; import { childContext, toCase } from '@hey-api/shared'; -import { $, type VarType } from '../../../../py-dsl'; +import { $ } from '../../../../py-dsl'; import { safeRuntimeName } from '../../../../py-dsl/utils/name'; -import type { PydanticField, PydanticFinal, PydanticResult } from '../../shared/types'; -import type { PydanticPlugin } from '../../types'; - -interface ObjectResolverContext { - _childResults: Array; - applyModifiers: (result: PydanticResult, options?: { optional?: boolean }) => PydanticFinal; - plugin: PydanticPlugin['Instance']; - schema: SchemaWithType<'object'>; - walk: Walker; - walkerCtx: SchemaVisitorContext; -} +import type { ObjectResolverContext } from '../../resolvers'; +import type { PydanticField, PydanticResult, PydanticType } from '../../shared/types'; export interface ObjectToFieldsResult extends Pick { childResults: Array; } -function resolveAdditionalProperties(ctx: ObjectResolverContext): VarType | null | undefined { +function additionalPropertiesNode(ctx: ObjectResolverContext): PydanticType | null | undefined { const { schema } = ctx; if (!schema.additionalProperties || !schema.additionalProperties.type) return undefined; @@ -31,10 +21,12 @@ function resolveAdditionalProperties(ctx: ObjectResolverContext): VarType | null ); ctx._childResults.push(result); - return result.type; + return { + type: result.type, + }; } -function resolveFields(ctx: ObjectResolverContext): Array { +function fieldsNode(ctx: ObjectResolverContext): Array { const { schema } = ctx; const fields: Array = []; @@ -59,13 +51,11 @@ function resolveFields(ctx: ObjectResolverContext): Array { return fields; } -function objectResolver(ctx: ObjectResolverContext): Omit { - const additional = resolveAdditionalProperties(ctx); +function baseNode(ctx: ObjectResolverContext): PydanticType & { fields?: Array } { + const additional = additionalPropertiesNode(ctx); - // additionalProperties: false → strict — still emit a class, just no extra fields - // additionalProperties: { type: 'never' } → null sentinel → strict class if (additional === null) { - const fields = resolveFields(ctx); + const fields = fieldsNode(ctx); return { fields }; } @@ -77,10 +67,9 @@ function objectResolver(ctx: ObjectResolverContext): Omit, ): ObjectToFieldsResult { @@ -95,17 +88,24 @@ export function objectToFields( const childResults: Array = []; const extendedCtx: ObjectResolverContext = { + $, _childResults: childResults, applyModifiers, + nodes: { + additionalProperties: additionalPropertiesNode, + base: baseNode, + fields: fieldsNode, + }, plugin, schema, walk, walkerCtx, }; - // const resolver = plugin.config?.['~resolvers']?.object; - // const resolved = resolver?.(extendedCtx) ?? objectResolver(extendedCtx); - const resolved = objectResolver(extendedCtx); + const resolver = plugin.config['~resolvers']?.object; + const resolved = resolver?.(extendedCtx) ?? objectResolver(extendedCtx); + + const childResultsFinal = extendedCtx._childResults; - return { childResults, ...resolved }; + return { childResults: childResultsFinal, ...resolved }; } diff --git a/packages/openapi-python/src/plugins/pydantic/v2/toAst/string.ts b/packages/openapi-python/src/plugins/pydantic/v2/toAst/string.ts index 8d6f51495..25892fb14 100644 --- a/packages/openapi-python/src/plugins/pydantic/v2/toAst/string.ts +++ b/packages/openapi-python/src/plugins/pydantic/v2/toAst/string.ts @@ -1,18 +1,13 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../py-dsl'; +import type { StringResolverContext } from '../../resolvers'; import type { PydanticType } from '../../shared/types'; import type { PydanticPlugin } from '../../types'; import type { FieldConstraints } from '../constants'; -export function stringToType({ - plugin, - schema, -}: { - plugin: PydanticPlugin['Instance']; - schema: SchemaWithType<'string'>; -}): PydanticType { - const constraints: FieldConstraints = {}; +function constNode(ctx: StringResolverContext): PydanticType | undefined { + const { plugin, schema } = ctx; if (typeof schema.const === 'string') { const literal = plugin.external('typing.Literal'); @@ -21,6 +16,14 @@ export function stringToType({ }; } + return undefined; +} + +function baseNode(ctx: StringResolverContext): PydanticType { + const { schema } = ctx; + + const constraints: FieldConstraints = {}; + if (schema.minLength !== undefined) { constraints.min_length = schema.minLength; } @@ -42,3 +45,31 @@ export function stringToType({ type: 'str', }; } + +function stringResolver(ctx: StringResolverContext): PydanticType { + const constResult = ctx.nodes.const(ctx); + if (constResult) return constResult; + + return ctx.nodes.base(ctx); +} + +export function stringToType({ + plugin, + schema, +}: { + plugin: PydanticPlugin['Instance']; + schema: SchemaWithType<'string'>; +}): PydanticType { + const ctx: StringResolverContext = { + $, + nodes: { + base: baseNode, + const: constNode, + }, + plugin, + schema, + }; + + const resolver = plugin.config['~resolvers']?.string; + return resolver?.(ctx) ?? stringResolver(ctx); +} diff --git a/packages/openapi-python/src/plugins/pydantic/v2/walker.ts b/packages/openapi-python/src/plugins/pydantic/v2/walker.ts index d88498f89..48b38c29a 100644 --- a/packages/openapi-python/src/plugins/pydantic/v2/walker.ts +++ b/packages/openapi-python/src/plugins/pydantic/v2/walker.ts @@ -85,8 +85,7 @@ export function createVisitor( }; }, enum(schema, ctx) { - const mode = ctx.plugin.config.enums ?? 'enum'; - const result = enumToType({ mode, plugin: ctx.plugin, schema }); + const result = enumToType({ plugin: ctx.plugin, schema }); return { ...result, meta: defaultMeta(schema), diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/resolvers.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/resolvers.ts index 3a04e0691..8bdcbc78a 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/resolvers.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/resolvers.ts @@ -1,7 +1,8 @@ import type { Plugin, SchemaVisitorContext, SchemaWithType, Walker } from '@hey-api/shared'; import type { $, DollarTsDsl } from '../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type, TypeScriptResult } from './shared/types'; +import type { Type, TypeScriptResult } from './shared/types'; +import type { HeyApiTypeScriptPlugin } from './types'; export type Resolvers = Plugin.Resolvers<{ /** diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/types.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/types.ts index 5e8649d02..43c24f5f2 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/types.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/types.ts @@ -2,8 +2,6 @@ import type { IR } from '@hey-api/shared'; import type { MaybeTsDsl, TypeTsDsl } from '../../../../ts-dsl'; -export type { HeyApiTypeScriptPlugin } from '../types'; - export type Type = MaybeTsDsl; /** diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts index c889000fe..79f858a53 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts @@ -4,8 +4,8 @@ import { applyNaming } from '@hey-api/shared'; import { createSchemaComment } from '../../../../plugins/shared/utils/schema'; import { $ } from '../../../../ts-dsl'; +import type { HeyApiTypeScriptPlugin } from '../types'; import { createProcessor } from '../v1/processor'; -import type { HeyApiTypeScriptPlugin } from './types'; export function webhookToType({ operation, diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/array.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/array.ts index fb87996e8..d876b29b1 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/array.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/array.ts @@ -4,8 +4,9 @@ import type { Walker } from '@hey-api/shared'; import { deduplicateSchema } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; import type { TypeScriptResult } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; export function arrayToAst({ plugin, diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/boolean.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/boolean.ts index 3fc74a615..490a914e6 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/boolean.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/boolean.ts @@ -1,7 +1,8 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; export function booleanToAst({ schema, diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/enum.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/enum.ts index 0b0056604..935f15809 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/enum.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/enum.ts @@ -2,8 +2,9 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; import type { EnumResolverContext } from '../../resolvers'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; import type { TypeScriptEnumData } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; function buildEnumData( plugin: HeyApiTypeScriptPlugin['Instance'], diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/never.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/never.ts index c43f71d72..2e89ca3b7 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/never.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/never.ts @@ -1,7 +1,8 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export function neverToAst(args: { diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/null.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/null.ts index 35cb5f519..2dde88ace 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/null.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/null.ts @@ -1,7 +1,8 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export function nullToAst(args: { diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/number.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/number.ts index 6b6a10305..599f48909 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/number.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/number.ts @@ -2,7 +2,8 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; import type { NumberResolverContext } from '../../resolvers'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; function constNode(ctx: NumberResolverContext): Type | undefined { const { schema } = ctx; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/object.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/object.ts index dc81a4c31..8685379ac 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/object.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/object.ts @@ -5,8 +5,9 @@ import { deduplicateSchema } from '@hey-api/shared'; import { createSchemaComment } from '../../../../../plugins/shared/utils/schema'; import { $ } from '../../../../../ts-dsl'; import type { ObjectResolverContext } from '../../resolvers'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; import type { TypeScriptResult } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; function shapeNode(ctx: ObjectResolverContext): ReturnType { const { schema, walk, walkerCtx } = ctx; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts index 19e1c8706..4c5c7238b 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts @@ -4,7 +4,8 @@ import { toCase } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; import type { StringResolverContext } from '../../resolvers'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; function constNode(ctx: StringResolverContext): Type | undefined { const { schema } = ctx; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/tuple.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/tuple.ts index b50c09ff8..7b342df39 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/tuple.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/tuple.ts @@ -3,8 +3,9 @@ import type { SchemaWithType } from '@hey-api/shared'; import type { Walker } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; import type { TypeScriptResult } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; export function tupleToAst({ plugin, diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/undefined.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/undefined.ts index 2ec16f5c2..ce0366e9c 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/undefined.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/undefined.ts @@ -1,7 +1,8 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export function undefinedToAst(args: { diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/unknown.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/unknown.ts index ad7707a6b..001ad83ba 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/unknown.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/unknown.ts @@ -1,7 +1,8 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; export function unknownToAst({ plugin, diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/void.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/void.ts index 46d95ebea..1154f48f4 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/void.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/void.ts @@ -1,7 +1,8 @@ import type { SchemaWithType } from '@hey-api/shared'; import { $ } from '../../../../../ts-dsl'; -import type { HeyApiTypeScriptPlugin, Type } from '../../shared/types'; +import type { Type } from '../../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../../types'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export function voidToAst(args: { diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/walker.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/walker.ts index 241385ea3..08f4abe74 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/walker.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/walker.ts @@ -5,7 +5,8 @@ import { pathToJsonPointer } from '@hey-api/shared'; import { $ } from '../../../../ts-dsl'; import { defaultMeta, inheritMeta } from '../shared/meta'; import type { ProcessorContext } from '../shared/processor'; -import type { HeyApiTypeScriptPlugin, TypeScriptResult } from '../shared/types'; +import type { TypeScriptResult } from '../shared/types'; +import type { HeyApiTypeScriptPlugin } from '../types'; import { arrayToAst } from './toAst/array'; import { booleanToAst } from './toAst/boolean'; import { enumToAst } from './toAst/enum'; -- 2.51.2 From 8ad7991c7a1e2f8967419ec6e8241ef3df15842f Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 10 Mar 2026 03:59:04 +0100 Subject: [PATCH 2/2] docs: add resolvers to typescript plugin --- docs/openapi-ts/plugins/concepts/resolvers.md | 2 +- docs/openapi-ts/plugins/typescript.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/openapi-ts/plugins/concepts/resolvers.md b/docs/openapi-ts/plugins/concepts/resolvers.md index 804fcbd75..dd2b4421c 100644 --- a/docs/openapi-ts/plugins/concepts/resolvers.md +++ b/docs/openapi-ts/plugins/concepts/resolvers.md @@ -7,7 +7,7 @@ description: Understand the concepts behind plugins. Sometimes the default plugin behavior isn't what you need or expect. Resolvers let you patch plugins in a safe and performant way, without forking or reimplementing core logic. -Currently available for [Valibot](/openapi-ts/plugins/valibot) and [Zod](/openapi-ts/plugins/zod). +Currently available for [TypeScript](/openapi-ts/plugins/typescript), [Valibot](/openapi-ts/plugins/valibot), and [Zod](/openapi-ts/plugins/zod). ## Examples diff --git a/docs/openapi-ts/plugins/typescript.md b/docs/openapi-ts/plugins/typescript.md index 1710bd762..90c4079ed 100644 --- a/docs/openapi-ts/plugins/typescript.md +++ b/docs/openapi-ts/plugins/typescript.md @@ -166,6 +166,10 @@ export default { ::: +## Resolvers + +You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers). + ## API You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/@hey-api/typescript/types.ts) interface.