···88import { ExprMixin } from '../mixins/expr';
99import { OperatorMixin } from '../mixins/operator';
1010import { OptionalMixin } from '../mixins/optional';
1111+import { SpreadMixin } from '../mixins/spread';
1112import { TokenTsDsl } from '../token';
1213import { f } from '../utils/factories';
1314import { regexp } from '../utils/regexp';
···18191920const Mixed = AsMixin(
2021 ExprMixin(
2121- OperatorMixin(OptionalMixin(TsDsl<ts.PropertyAccessExpression | ts.ElementAccessExpression>)),
2222+ OperatorMixin(
2323+ OptionalMixin(SpreadMixin(TsDsl<ts.PropertyAccessExpression | ts.ElementAccessExpression>)),
2424+ ),
2225 ),
2326);
2427
+2-1
packages/openapi-ts/src/ts-dsl/expr/call.ts
···77import { ArgsMixin } from '../mixins/args';
88import { AsMixin } from '../mixins/as';
99import { ExprMixin } from '../mixins/expr';
1010+import { SpreadMixin } from '../mixins/spread';
1011import { TypeArgsMixin } from '../mixins/type-args';
1112import { f } from '../utils/factories';
1213···1415export type CallCallee = NodeName | MaybeTsDsl<ts.Expression>;
1516export type CallCtor = (callee: CallCallee, ...args: CallArgs) => CallTsDsl;
16171717-const Mixed = ArgsMixin(AsMixin(ExprMixin(TypeArgsMixin(TsDsl<ts.CallExpression>))));
1818+const Mixed = ArgsMixin(AsMixin(ExprMixin(SpreadMixin(TypeArgsMixin(TsDsl<ts.CallExpression>)))));
18191920export class CallTsDsl extends Mixed {
2021 readonly '~dsl' = 'CallTsDsl';
+2-1
packages/openapi-ts/src/ts-dsl/expr/expr.ts
···77import { AsMixin } from '../mixins/as';
88import { ExprMixin } from '../mixins/expr';
99import { OperatorMixin } from '../mixins/operator';
1010+import { SpreadMixin } from '../mixins/spread';
1011import { TypeExprMixin } from '../mixins/type-expr';
11121213type Id = NodeName | MaybeTsDsl<ts.Expression>;
13141414-const Mixed = AsMixin(ExprMixin(OperatorMixin(TypeExprMixin(TsDsl<ts.Expression>))));
1515+const Mixed = AsMixin(ExprMixin(OperatorMixin(SpreadMixin(TypeExprMixin(TsDsl<ts.Expression>)))));
15161617export class ExprTsDsl extends Mixed {
1718 readonly '~dsl' = 'ExprTsDsl';
+2-1
packages/openapi-ts/src/ts-dsl/expr/new.ts
···77import { ArgsMixin } from '../mixins/args';
88import { AsMixin } from '../mixins/as';
99import { ExprMixin } from '../mixins/expr';
1010+import { SpreadMixin } from '../mixins/spread';
1011import { TypeArgsMixin } from '../mixins/type-args';
1112import { f } from '../utils/factories';
1213···1415export type NewExpr = NodeName | MaybeTsDsl<ts.Expression>;
1516export type NewCtor = (expr: NewExpr, ...args: NewArgs) => NewTsDsl;
16171717-const Mixed = ArgsMixin(AsMixin(ExprMixin(TypeArgsMixin(TsDsl<ts.NewExpression>))));
1818+const Mixed = ArgsMixin(AsMixin(ExprMixin(SpreadMixin(TypeArgsMixin(TsDsl<ts.NewExpression>)))));
18191920export class NewTsDsl extends Mixed {
2021 readonly '~dsl' = 'NewTsDsl';
+26-9
packages/openapi-ts/src/ts-dsl/expr/object.ts
···3344import type { MaybeTsDsl } from '../base';
55import { TsDsl } from '../base';
66+import type { MethodTsDsl } from '../decl/method';
67import { AsMixin } from '../mixins/as';
78import { ExprMixin } from '../mixins/expr';
89import { HintMixin } from '../mixins/hint';
910import { LayoutMixin } from '../mixins/layout';
1111+import { f } from '../utils/factories';
1012import { ObjectPropTsDsl } from './prop';
11131214type Expr = NodeName | MaybeTsDsl<ts.Expression>;
1315type Stmt = NodeName | MaybeTsDsl<ts.Statement>;
1414-type ExprFn = Expr | ((p: ObjectPropTsDsl) => void);
1515-type StmtFn = Stmt | ((p: ObjectPropTsDsl) => void);
16161717const Mixed = AsMixin(ExprMixin(HintMixin(LayoutMixin(TsDsl<ts.ObjectLiteralExpression>))));
1818···2222 protected _props = new Map<string, ObjectPropTsDsl>();
2323 protected _spreadCounter = 0;
24242525- constructor(...props: Array<ObjectPropTsDsl>) {
2525+ constructor(...props: Array<ObjectPropTsDsl> | [(o: ObjectTsDsl) => void]) {
2626 super();
2727- this.props(...props);
2727+ if (props.length === 1 && typeof props[0] === 'function') {
2828+ props[0](this);
2929+ } else {
3030+ this.props(...(props as Array<ObjectPropTsDsl>));
3131+ }
2832 }
29333034 override analyze(ctx: AnalysisContext): void {
···4347 }
44484549 /** Adds a computed property (e.g., `{ [expr]: value }`), or removes if null. */
4646- computed(name: string, expr: ExprFn | null): this {
5050+ computed(name: string, expr: Expr | null): this {
4751 if (expr === null) {
4852 this._props.delete(`computed:${name}`);
4953 } else {
···5660 }
57615862 /** Adds a getter property (e.g., `{ get foo() { ... } }`), or removes if null. */
5959- getter(name: string, stmt: StmtFn | null): this {
6363+ getter(name: string, stmt: Stmt | null): this {
6064 if (stmt === null) {
6165 this._props.delete(`getter:${name}`);
6266 } else {
···7579 return this._props.size === 0;
7680 }
77818282+ /** Adds a method property (e.g., `{ foo() { ... } }`), or removes if null. */
8383+ method(name: string, fn: ((m: MethodTsDsl) => void) | null): this {
8484+ if (fn === null) {
8585+ this._props.delete(`method:${name}`);
8686+ } else {
8787+ this._props.set(
8888+ `method:${name}`,
8989+ new ObjectPropTsDsl({ kind: 'method', name }).value(f.method(name, fn)),
9090+ );
9191+ }
9292+ return this;
9393+ }
9494+7895 /** Adds a property assignment, or removes if null. */
7979- prop(name: string, expr: ExprFn | null): this {
9696+ prop(name: string, expr: Expr | null): this {
8097 if (expr === null) {
8198 this._props.delete(`prop:${name}`);
8299 } else {
···94111 }
9511296113 /** Adds a setter property (e.g., `{ set foo(v) { ... } }`), or removes if null. */
9797- setter(name: string, stmt: StmtFn | null): this {
114114+ setter(name: string, stmt: Stmt | null): this {
98115 if (stmt === null) {
99116 this._props.delete(`setter:${name}`);
100117 } else {
···104121 }
105122106123 /** Adds a spread property (e.g., `{ ...options }`). */
107107- spread(expr: ExprFn): this {
124124+ spread(expr: Expr): this {
108125 const key = `spread:${this._spreadCounter++}`;
109126 this._props.set(key, new ObjectPropTsDsl({ kind: 'spread' }).value(expr));
110127 return this;
+26-24
packages/openapi-ts/src/ts-dsl/expr/prop.ts
···77import { GetterTsDsl } from '../decl/getter';
88import { SetterTsDsl } from '../decl/setter';
99import { DocMixin } from '../mixins/doc';
1010+import type { f } from '../utils/factories';
1011import { safePropName } from '../utils/name';
1112import { IdTsDsl } from './id';
12131313-type Expr = NodeName | MaybeTsDsl<ts.Expression>;
1414-type Stmt = NodeName | MaybeTsDsl<ts.Statement>;
1515-1616-export type ObjectPropKind = 'computed' | 'getter' | 'prop' | 'setter' | 'spread';
1414+export type ObjectPropKind = 'computed' | 'getter' | 'method' | 'prop' | 'setter' | 'spread';
1515+export type ObjectPropValue =
1616+ | NodeName
1717+ | MaybeTsDsl<ts.Expression | ts.Statement>
1818+ | ReturnType<typeof f.method>;
17191820type Meta =
1921 | { kind: 'computed'; name: string }
2022 | { kind: 'getter'; name: string }
2323+ | { kind: 'method'; name: string }
2124 | { kind: 'prop'; name: string }
2225 | { kind: 'setter'; name: string }
2326 | { kind: 'spread'; name?: undefined };
···2730export class ObjectPropTsDsl extends Mixed {
2831 readonly '~dsl' = 'ObjectPropTsDsl';
29323030- protected _value?: Ref<Expr | Stmt>;
3333+ protected _value?: Ref<ObjectPropValue>;
3134 protected _meta: Meta;
32353336 constructor(meta: Meta) {
···3538 this._meta = meta;
3639 }
37403838- get kind(): ObjectPropKind {
3939- return this._meta.kind;
4040- }
4141-4242- get propName(): string | undefined {
4343- return this._meta.name;
4444- }
4545-4641 override analyze(ctx: AnalysisContext): void {
4742 super.analyze(ctx);
4843 ctx.analyze(this._value);
···5247 return !this.missingRequiredCalls().length;
5348 }
54495555- value(value: Expr | Stmt | ((p: ObjectPropTsDsl) => void)) {
5656- if (typeof value === 'function') {
5757- value(this);
5858- } else {
5959- this._value = ref(value);
6060- }
5050+ get kind(): ObjectPropKind {
5151+ return this._meta.kind;
5252+ }
5353+5454+ get propName(): string | undefined {
5555+ return this._meta.name;
5656+ }
5757+5858+ value(value: ObjectPropValue): this {
5959+ this._value = ref(value);
6160 return this;
6261 }
6362···6867 if (ts.isStatement(node)) {
6968 throw new Error('Invalid spread: object spread must be an expression, not a statement.');
7069 }
7171- const result = ts.factory.createSpreadAssignment(node);
7070+ const result = ts.factory.createSpreadAssignment(node as ts.Expression);
7271 return this.$docs(result);
7372 }
7473 if (this._meta.kind === 'getter') {
7575- const getter = new GetterTsDsl(this._meta.name).do(node);
7474+ const getter = new GetterTsDsl(this._meta.name).do(node as ts.Statement);
7675 const result = this.$node(getter);
7776 return this.$docs(result);
7877 }
7978 if (this._meta.kind === 'setter') {
8080- const setter = new SetterTsDsl(this._meta.name).do(node);
7979+ const setter = new SetterTsDsl(this._meta.name).do(node as ts.Statement);
8180 const result = this.$node(setter);
8281 return this.$docs(result);
8382 }
8383+ if (this._meta.kind === 'method') {
8484+ return this.$docs(node as ts.MethodDeclaration);
8585+ }
8486 if (ts.isIdentifier(node) && node.text === this._meta.name) {
8587 const result = ts.factory.createShorthandPropertyAssignment(this._meta.name);
8688 return this.$docs(result);
···9496 this._meta.kind === 'computed'
9597 ? ts.factory.createComputedPropertyName(this.$node(new IdTsDsl(this._meta.name)))
9698 : this.$node(safePropName(this._meta.name)),
9797- node,
9999+ node as ts.Expression,
98100 );
99101 return this.$docs(result);
100102 }
101103102104 $validate(): asserts this is this & {
103103- _value: Expr | Stmt;
105105+ _value: ObjectPropValue;
104106 kind: ObjectPropKind;
105107 } {
106108 const missing = this.missingRequiredCalls();
+34
packages/openapi-ts/src/ts-dsl/expr/spread.ts
···11+import type { AnalysisContext, NodeName, Ref } from '@hey-api/codegen-core';
22+import { ref } from '@hey-api/codegen-core';
33+import ts from 'typescript';
44+55+import type { MaybeTsDsl } from '../base';
66+import { TsDsl } from '../base';
77+import { f } from '../utils/factories';
88+99+export type SpreadExpr = NodeName | MaybeTsDsl<ts.Expression>;
1010+export type SpreadCtor = (expr: SpreadExpr) => SpreadTsDsl;
1111+1212+const Mixed = TsDsl<ts.SpreadElement>;
1313+1414+export class SpreadTsDsl extends Mixed {
1515+ readonly '~dsl' = 'SpreadTsDsl';
1616+1717+ protected _expr: Ref<SpreadExpr>;
1818+1919+ constructor(expr: SpreadExpr) {
2020+ super();
2121+ this._expr = ref(expr);
2222+ }
2323+2424+ override analyze(ctx: AnalysisContext): void {
2525+ super.analyze(ctx);
2626+ ctx.analyze(this._expr);
2727+ }
2828+2929+ override toAst() {
3030+ return ts.factory.createSpreadElement(this.$node(this._expr));
3131+ }
3232+}
3333+3434+f.spread.set((...args) => new SpreadTsDsl(...args));
+2-2
packages/openapi-ts/src/ts-dsl/expr/typeof.ts
···11-import type { AnalysisContext } from '@hey-api/codegen-core';
11+import type { AnalysisContext, NodeName } from '@hey-api/codegen-core';
22import ts from 'typescript';
3344import type { MaybeTsDsl } from '../base';
···66import { OperatorMixin } from '../mixins/operator';
77import { f } from '../utils/factories';
8899-export type TypeOfExpr = string | MaybeTsDsl<ts.Expression>;
99+export type TypeOfExpr = NodeName | MaybeTsDsl<ts.Expression>;
1010export type TypeOfExprCtor = (expr: TypeOfExpr) => TypeOfExprTsDsl;
11111212const Mixed = OperatorMixin(TsDsl<ts.TypeOfExpression>);
+13-3
packages/openapi-ts/src/ts-dsl/index.ts
···11+import type { NodeName } from '@hey-api/codegen-core';
12import type ts from 'typescript';
2334import { ClassTsDsl } from './decl/class';
···2829import { PrefixTsDsl } from './expr/prefix';
2930import { ObjectPropTsDsl } from './expr/prop';
3031import { RegExpTsDsl } from './expr/regexp';
3232+import { SpreadTsDsl } from './expr/spread';
3133import { TemplateTsDsl } from './expr/template';
3234import { TernaryTsDsl } from './expr/ternary';
3335import { TypeOfExprTsDsl } from './expr/typeof';
···6163import { TypeQueryTsDsl } from './type/query';
6264import { TypeTemplateTsDsl } from './type/template';
6365import { TypeTupleTsDsl } from './type/tuple';
6666+import { TypeTupleMemberTsDsl } from './type/tuple-member';
6467import { LazyTsDsl } from './utils/lazy';
65686669const tsDsl = {
···135138 }) as {
136139 (): FuncTsDsl<'arrow'>;
137140 (fn: (f: FuncTsDsl<'arrow'>) => void): FuncTsDsl<'arrow'>;
138138- (name: string): FuncTsDsl<'decl'>;
139139- (name: string, fn: (f: FuncTsDsl<'decl'>) => void): FuncTsDsl<'decl'>;
140140- (name?: string, fn?: (f: FuncTsDsl<'decl'>) => void): FuncTsDsl<'arrow'> | FuncTsDsl<'decl'>;
141141+ (name: NodeName): FuncTsDsl<'decl'>;
142142+ (name: NodeName, fn: (f: FuncTsDsl<'decl'>) => void): FuncTsDsl<'decl'>;
143143+ (name?: NodeName, fn?: (f: FuncTsDsl<'decl'>) => void): FuncTsDsl<'arrow'> | FuncTsDsl<'decl'>;
141144 },
142145143146 /** Creates a getter method declaration. */
···212215213216 /** Creates a setter method declaration. */
214217 setter: (...args: ConstructorParameters<typeof SetterTsDsl>) => new SetterTsDsl(...args),
218218+219219+ /** Creates a spread element from an expression (e.g., `...expr`). */
220220+ spread: (...args: ConstructorParameters<typeof SpreadTsDsl>) => new SpreadTsDsl(...args),
215221216222 /** Wraps an expression or statement-like value into a `StmtTsDsl`. */
217223 stmt: (...args: ConstructorParameters<typeof StmtTsDsl>) => new StmtTsDsl(...args),
···287293288294 /** Creates a tuple type (e.g., [A, B, C]). */
289295 tuple: (...args: ConstructorParameters<typeof TypeTupleTsDsl>) => new TypeTupleTsDsl(...args),
296296+297297+ /** Creates a named tuple element (e.g., `[resolver?: R]`). */
298298+ tupleMember: (...args: ConstructorParameters<typeof TypeTupleMemberTsDsl>) =>
299299+ new TypeTupleMemberTsDsl(...args),
290300 },
291301 ),
292302
+4-4
packages/openapi-ts/src/ts-dsl/mixins/args.ts
···55import type { MaybeTsDsl } from '../base';
66import type { BaseCtor, MixinCtor } from './types';
7788-type Arg = NodeName | MaybeTsDsl<ts.Expression>;
88+type Arg = NodeName | MaybeTsDsl<ts.Expression | ts.SpreadElement>;
991010export interface ArgsMethods extends Node {
1111- /** Renders the arguments into an array of `Expression`s. */
1212- $args(): ReadonlyArray<ts.Expression>;
1111+ /** Renders the arguments into an array of `Expression`s or `SpreadElement`s. */
1212+ $args(): ReadonlyArray<ts.Expression | ts.SpreadElement>;
1313 /** Adds a single expression argument. */
1414 arg(arg: Arg | undefined): this;
1515 /** Adds one or more expression arguments. */
···4242 return this;
4343 }
44444545- protected $args(): ReadonlyArray<ts.Expression> {
4545+ protected $args(): ReadonlyArray<ts.Expression | ts.SpreadElement> {
4646 return this.$node(this._args).map((arg) => this.$node(arg));
4747 }
4848 }
+24
packages/openapi-ts/src/ts-dsl/mixins/spread.ts
···11+import type { AnalysisContext, Node } from '@hey-api/codegen-core';
22+import type ts from 'typescript';
33+44+import { f } from '../utils/factories';
55+import type { BaseCtor, MixinCtor } from './types';
66+77+export interface SpreadMethods extends Node {
88+ /** Produces a spread element from the current expression (e.g., `...expr`). */
99+ spread(): ReturnType<typeof f.spread>;
1010+}
1111+1212+export function SpreadMixin<T extends ts.Expression, TBase extends BaseCtor<T>>(Base: TBase) {
1313+ abstract class Spread extends Base {
1414+ override analyze(ctx: AnalysisContext): void {
1515+ super.analyze(ctx);
1616+ }
1717+1818+ protected spread(): ReturnType<typeof f.spread> {
1919+ return f.spread(this);
2020+ }
2121+ }
2222+2323+ return Spread as unknown as MixinCtor<TBase, SpreadMethods>;
2424+}
+2-2
packages/openapi-ts/src/ts-dsl/stmt/if.ts
···11-import type { AnalysisContext } from '@hey-api/codegen-core';
11+import type { AnalysisContext, NodeName } from '@hey-api/codegen-core';
22import ts from 'typescript';
3344import type { MaybeTsDsl } from '../base';
···77import { DoMixin } from '../mixins/do';
88import { BlockTsDsl } from './block';
991010-export type IfCondition = string | MaybeTsDsl<ts.Expression>;
1010+export type IfCondition = NodeName | MaybeTsDsl<ts.Expression>;
11111212const Mixed = DoMixin(TsDsl<ts.IfStatement>);
1313
+2-2
packages/openapi-ts/src/ts-dsl/stmt/var.ts
···5252 }
53535454 /** Sets the variable type. */
5555- type(type: string | TypeTsDsl): this {
5656- this._type = type instanceof TypeTsDsl ? type : new TypeExprTsDsl(type);
5555+ type(node: NodeName | TypeTsDsl): this {
5656+ this._type = node instanceof TypeTsDsl ? node : new TypeExprTsDsl(node);
5757 return this;
5858 }
5959