diff --git a/packages/openapi-ts/src/ts-dsl/base.ts b/packages/openapi-ts/src/ts-dsl/base.ts index 329537f58..f826f909e 100644 --- a/packages/openapi-ts/src/ts-dsl/base.ts +++ b/packages/openapi-ts/src/ts-dsl/base.ts @@ -101,6 +101,11 @@ export abstract class TsDsl implements ITsDsl { if (typeof input === 'string') { return this.$expr(input) as NodeOfMaybe; } + if (typeof input === 'boolean') { + return ( + input ? ts.factory.createTrue() : ts.factory.createFalse() + ) as NodeOfMaybe; + } if (input instanceof Array) { return input.map((item) => this._render(item)) as NodeOfMaybe; } @@ -122,20 +127,26 @@ export abstract class TsDsl implements ITsDsl { }); } - protected $type(type: I): TypeOfMaybe { - if (type === undefined) { + protected $type( + input: I, + args?: ReadonlyArray, + ): TypeOfMaybe { + if (input === undefined) { return undefined as TypeOfMaybe; } - if (typeof type === 'string') { - return ts.factory.createTypeReferenceNode( - type, - ) as unknown as TypeOfMaybe; + if (typeof input === 'string') { + return ts.factory.createTypeReferenceNode(input, args) as TypeOfMaybe; } - if (typeof type === 'boolean') { - const literal = type ? ts.factory.createTrue() : ts.factory.createFalse(); + if (typeof input === 'boolean') { + const literal = input + ? ts.factory.createTrue() + : ts.factory.createFalse(); return ts.factory.createLiteralTypeNode(literal) as TypeOfMaybe; } - return this._render(type as any) as TypeOfMaybe; + if (input instanceof Array) { + return input.map((item) => this.$type(item, args)) as TypeOfMaybe; + } + return this._render(input as any) as TypeOfMaybe; } private _render(value: MaybeTsDsl): T { @@ -152,25 +163,30 @@ type NodeOf = ? ReadonlyArray ? N : U> : I extends string ? ts.Expression - : I extends TsDsl - ? N - : I extends ts.Node - ? I - : never; + : I extends boolean + ? ts.Expression + : I extends TsDsl + ? N + : I extends ts.Node + ? I + : never; type TypeOfMaybe = undefined extends I ? TypeOf> | undefined : TypeOf; -type TypeOf = I extends string - ? ts.TypeNode - : I extends boolean - ? ts.LiteralTypeNode - : I extends TsDsl - ? N - : I extends ts.TypeNode - ? I - : never; +type TypeOf = + I extends ReadonlyArray + ? ReadonlyArray> + : I extends string + ? ts.TypeNode + : I extends boolean + ? ts.LiteralTypeNode + : I extends TsDsl + ? N + : I extends ts.TypeNode + ? I + : never; export type MaybeTsDsl = // if T includes string in the union @@ -182,3 +198,5 @@ export type MaybeTsDsl = T extends ts.Node ? T | TsDsl : never; + +export type TypeOfTsDsl = T extends TsDsl ? U : never; diff --git a/packages/openapi-ts/src/ts-dsl/mixins/type.ts b/packages/openapi-ts/src/ts-dsl/mixins/type.ts index ff0708d22..5e837b1b3 100644 --- a/packages/openapi-ts/src/ts-dsl/mixins/type.ts +++ b/packages/openapi-ts/src/ts-dsl/mixins/type.ts @@ -23,6 +23,7 @@ export function createTypeAccessor( function fn(type: TypeInput): Parent; function fn(type?: TypeInput): ReturnType | Parent { if (type === undefined) { + // @ts-expect-error --- overload resolution if (!_type) _type = TypeTsDsl(); return _type; } diff --git a/packages/openapi-ts/src/ts-dsl/type.ts b/packages/openapi-ts/src/ts-dsl/type.ts index 865a3ea0d..a0ada32f5 100644 --- a/packages/openapi-ts/src/ts-dsl/type.ts +++ b/packages/openapi-ts/src/ts-dsl/type.ts @@ -7,32 +7,10 @@ import { LiteralTsDsl } from './literal'; import { mixin } from './mixins/apply'; import { GenericsMixin } from './mixins/generics'; import { OptionalMixin } from './mixins/optional'; +import { TypeExprTsDsl } from './type/expr'; export type TypeInput = WithString>; -export abstract class BaseTypeTsDsl< - T extends ts.TypeNode | ts.TypeParameterDeclaration, -> extends TsDsl { - protected base?: ts.EntityName; - protected constraint?: TypeInput; - protected defaultValue?: TypeInput; - - constructor(base?: WithString) { - super(); - if (base) this.base = this.$expr(base); - } - - default(value: TypeInput): this { - this.defaultValue = value; - return this; - } - - extends(constraint: TypeInput): this { - this.constraint = constraint; - return this; - } -} - export class TypeLiteralTsDsl extends TsDsl { private literal: LiteralTsDsl; @@ -67,15 +45,30 @@ export class TypeObjectTsDsl extends TsDsl { } } -export class TypeParamTsDsl extends BaseTypeTsDsl { +export class TypeParamTsDsl extends TsDsl { + protected base?: ts.EntityName; + protected constraint?: TypeInput; + protected defaultValue?: TypeInput; + constructor( name?: WithString, fn?: (base: TypeParamTsDsl) => void, ) { - super(name); + super(); + if (name) this.base = this.$expr(name); fn?.(this); } + default(value: TypeInput): this { + this.defaultValue = value; + return this; + } + + extends(constraint: TypeInput): this { + this.constraint = constraint; + return this; + } + $render(): ts.TypeParameterDeclaration { if (!this.base) throw new Error('Missing type name'); return ts.factory.createTypeParameterDeclaration( @@ -127,49 +120,20 @@ export class TypePropTsDsl extends TsDsl { export interface TypePropTsDsl extends OptionalMixin {} mixin(TypePropTsDsl, OptionalMixin); -export class TypeReferenceTsDsl extends BaseTypeTsDsl { - private _object?: TypeObjectTsDsl; - - constructor( - name?: WithString, - fn?: (base: TypeReferenceTsDsl) => void, - ) { - super(name); - fn?.(this); - } - - /** Starts an object type literal (e.g. `{ foo: string }`). */ - object(fn?: (o: TypeObjectTsDsl) => void): this { - this._object = new TypeObjectTsDsl(fn); - return this; - } - - $render(): ts.TypeNode { - if (this._object) return this.$node(this._object); - if (!this.base) throw new Error('Missing base type'); - const types = this._generics?.map((arg) => this.$type(arg)); - return ts.factory.createTypeReferenceNode( - this.base, - // @ts-expect-error --- generics are not officially supported on type references yet - types, - ); - } -} - -export interface TypeReferenceTsDsl extends GenericsMixin {} -mixin(TypeReferenceTsDsl, GenericsMixin); - +const base = { + literal: (...args: ConstructorParameters) => + new TypeLiteralTsDsl(...args), + object: (...args: ConstructorParameters) => + new TypeObjectTsDsl(...args), + param: (...args: ConstructorParameters) => + new TypeParamTsDsl(...args), + ref: (...args: ConstructorParameters) => + new TypeExprTsDsl(...args), +}; + +/** Creates a general expression node. */ export const TypeTsDsl = Object.assign( - (...args: ConstructorParameters) => - new TypeReferenceTsDsl(...args), - { - literal: (...args: ConstructorParameters) => - new TypeLiteralTsDsl(...args), - object: (...args: ConstructorParameters) => - new TypeObjectTsDsl(...args), - param: (...args: ConstructorParameters) => - new TypeParamTsDsl(...args), - ref: (...args: ConstructorParameters) => - new TypeReferenceTsDsl(...args), - }, + (...args: ConstructorParameters) => + new TypeExprTsDsl(...args), + base, ); diff --git a/packages/openapi-ts/src/ts-dsl/type/expr.ts b/packages/openapi-ts/src/ts-dsl/type/expr.ts new file mode 100644 index 000000000..83eb34c80 --- /dev/null +++ b/packages/openapi-ts/src/ts-dsl/type/expr.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ +import ts from 'typescript'; + +import { TsDsl, type WithString } from '../base'; +import { mixin } from '../mixins/apply'; +import { GenericsMixin } from '../mixins/generics'; +import { TypeObjectTsDsl } from '../type'; + +export class TypeExprTsDsl extends TsDsl { + private _exprInput: WithString; + private _object?: TypeObjectTsDsl; + + constructor(id: WithString) { + super(); + this._exprInput = id; + } + + /** Starts an object type literal (e.g. `{ foo: string }`). */ + object(fn?: (o: TypeObjectTsDsl) => void): this { + this._object = new TypeObjectTsDsl(fn); + return this; + } + + $render(): ts.TypeNode { + if (this._object) return this.$node(this._object); + const types = this._generics?.map((arg) => this.$type(arg)); + return ts.factory.createTypeReferenceNode( + this.$expr(this._exprInput), + // @ts-expect-error --- generics are not officially supported on type references yet + types, + ); + } +} + +export interface TypeExprTsDsl extends GenericsMixin {} +mixin(TypeExprTsDsl, GenericsMixin); -- 2.51.2 From 870b19d3de13feb00da11dd81df24a0d5c6a177d Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 11 Nov 2025 20:30:30 +0800 Subject: [PATCH 2/2] refactor: type dsl --- docs/.vitepress/theme/components/Layout.vue | 14 +- .../src/plugins/@hey-api/sdk/shared/class.ts | 31 ++-- .../src/plugins/valibot/shared/export.ts | 30 ++-- .../src/plugins/valibot/shared/pipesToAst.ts | 14 +- packages/openapi-ts/src/ts-dsl/base.ts | 41 ++++-- packages/openapi-ts/src/ts-dsl/class.ts | 2 +- packages/openapi-ts/src/ts-dsl/field.ts | 19 ++- packages/openapi-ts/src/ts-dsl/func.ts | 24 +-- packages/openapi-ts/src/ts-dsl/index.ts | 85 ++++++++++- packages/openapi-ts/src/ts-dsl/method.ts | 19 ++- .../openapi-ts/src/ts-dsl/mixins/generics.ts | 17 ++- packages/openapi-ts/src/ts-dsl/mixins/type.ts | 42 ------ packages/openapi-ts/src/ts-dsl/param.ts | 17 ++- packages/openapi-ts/src/ts-dsl/type.ts | 139 ------------------ packages/openapi-ts/src/ts-dsl/type/attr.ts | 45 ++++++ packages/openapi-ts/src/ts-dsl/type/expr.ts | 44 ++++-- .../openapi-ts/src/ts-dsl/type/literal.ts | 19 +++ packages/openapi-ts/src/ts-dsl/type/object.ts | 69 +++++++++ packages/openapi-ts/src/ts-dsl/type/param.ts | 39 +++++ packages/openapi-ts/src/ts-dsl/var.ts | 12 +- 20 files changed, 410 insertions(+), 312 deletions(-) delete mode 100644 packages/openapi-ts/src/ts-dsl/mixins/type.ts delete mode 100644 packages/openapi-ts/src/ts-dsl/type.ts create mode 100644 packages/openapi-ts/src/ts-dsl/type/attr.ts create mode 100644 packages/openapi-ts/src/ts-dsl/type/literal.ts create mode 100644 packages/openapi-ts/src/ts-dsl/type/object.ts create mode 100644 packages/openapi-ts/src/ts-dsl/type/param.ts diff --git a/docs/.vitepress/theme/components/Layout.vue b/docs/.vitepress/theme/components/Layout.vue index 4c9acda0d..7eb058bca 100644 --- a/docs/.vitepress/theme/components/Layout.vue +++ b/docs/.vitepress/theme/components/Layout.vue @@ -8,7 +8,7 @@ const { Layout } = DefaultTheme;