Skip to content

Commit e5e00bf

Browse files
authored
Merge pull request #2959 from hey-api/refactor/dsl-type-nodes-2
Refactor/dsl type nodes 2
2 parents 680ae2a + 870b19d commit e5e00bf

File tree

20 files changed

+470
-353
lines changed

20 files changed

+470
-353
lines changed

docs/.vitepress/theme/components/Layout.vue

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,14 @@ const { Layout } = DefaultTheme;
88
<Layout>
99
<template #layout-top>
1010
<div class="announcement">
11-
<span> Build better APIs with Hey API Platform </span>
11+
<span>Build better APIs with Hey API Platform</span>
1212
<a
1313
href="https://app.heyapi.dev/"
1414
rel="noopener noreferrer"
1515
target="_blank"
1616
>Dashboard</a
1717
>
1818
</div>
19-
<!-- <div class="announcement">
20-
<span>
21-
Request a feature<span class="hide-sm"> for your business</span>
22-
</span>
23-
<a
24-
aria-label="Send an email to Lubos"
25-
href="mailto:lubos@heyapi.dev?subject=Priority%20Feature%20Request"
26-
target="_blank"
27-
>
28-
Let's Talk
29-
</a>
30-
</div> -->
3119
</template>
3220
<!-- <template #home-features-before>
3321
<a

packages/openapi-ts/src/plugins/@hey-api/sdk/shared/class.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,12 @@ const createClientClass = ({
132132
.param('args', (p) =>
133133
p
134134
.optional(optionalClient)
135-
.type()
136-
.object((o) =>
137-
o.prop('client', (p) =>
138-
p.optional(optionalClient).type(symbolClient.placeholder),
139-
),
135+
.type(
136+
$.type
137+
.object()
138+
.prop('client', (p) =>
139+
p.optional(optionalClient).type(symbolClient.placeholder),
140+
),
140141
),
141142
)
142143
.do(
@@ -445,18 +446,14 @@ export const generateClassSdk = ({
445446
const ctor = $.init((i) =>
446447
i
447448
.param('args', (p) =>
448-
p
449-
.optional(!isClientRequired)
450-
.type()
451-
.object((o) =>
452-
o
453-
.prop('client', (p) =>
454-
p
455-
.optional(!isClientRequired)
456-
.type(symbolClient.placeholder),
457-
)
458-
.prop('key', (p) => p.optional().type('string')),
459-
),
449+
p.optional(!isClientRequired).type(
450+
$.type
451+
.object()
452+
.prop('client', (p) =>
453+
p.optional(!isClientRequired).type(symbolClient.placeholder),
454+
)
455+
.prop('key', (p) => p.optional().type('string')),
456+
),
460457
)
461458
.do(
462459
$('super').call('args'),
Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { Symbol } from '@hey-api/codegen-core';
2-
import type ts from 'typescript';
32

43
import type { IR } from '~/ir/types';
54
import { createSchemaComment } from '~/plugins/shared/utils/schema';
6-
import { tsc } from '~/tsc';
5+
import { $ } from '~/ts-dsl';
76

87
import { identifiers } from '../v1/constants';
98
import { pipesToAst } from './pipesToAst';
@@ -25,19 +24,18 @@ export const exportAst = ({
2524
resource: 'valibot.v',
2625
});
2726

28-
const statement = tsc.constVariable({
29-
comment: plugin.config.comments
30-
? createSchemaComment({ schema })
31-
: undefined,
32-
exportConst: symbol.exported,
33-
expression: pipesToAst({ pipes: ast.pipes, plugin }),
34-
name: symbol.placeholder,
35-
typeName: state.hasLazyExpression.value
36-
? (tsc.propertyAccessExpression({
37-
expression: v.placeholder,
38-
name: ast.typeName || identifiers.types.GenericSchema.text,
39-
}) as unknown as ts.TypeNode)
40-
: undefined,
41-
});
27+
const statement = $.const(symbol.placeholder)
28+
.export(symbol.exported)
29+
.$if(plugin.config.comments && createSchemaComment({ schema }), (c, v) =>
30+
c.describe(v as Array<string>),
31+
)
32+
.$if(state.hasLazyExpression.value, (c) =>
33+
c.type(
34+
$.type(v.placeholder).attr(
35+
ast.typeName || identifiers.types.GenericSchema.text,
36+
),
37+
),
38+
)
39+
.assign(pipesToAst({ pipes: ast.pipes, plugin }));
4240
plugin.setSymbolValue(symbol, statement);
4341
};

packages/openapi-ts/src/plugins/valibot/shared/pipesToAst.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type ts from 'typescript';
22

3-
import { tsc } from '~/tsc';
3+
import { $ } from '~/ts-dsl';
44

55
import type { ValibotPlugin } from '../types';
66
import { identifiers } from '../v1/constants';
@@ -20,12 +20,8 @@ export const pipesToAst = ({
2020
category: 'external',
2121
resource: 'valibot.v',
2222
});
23-
const expression = tsc.callExpression({
24-
functionName: tsc.propertyAccessExpression({
25-
expression: v.placeholder,
26-
name: identifiers.methods.pipe,
27-
}),
28-
parameters: pipes,
29-
});
30-
return expression;
23+
return $(v.placeholder)
24+
.attr(identifiers.methods.pipe)
25+
.call(...pipes)
26+
.$render();
3127
};

packages/openapi-ts/src/ts-dsl/base.ts

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ export abstract class TsDsl<T extends ts.Node = ts.Node> implements ITsDsl<T> {
101101
if (typeof input === 'string') {
102102
return this.$expr(input) as NodeOfMaybe<I>;
103103
}
104+
if (typeof input === 'boolean') {
105+
return (
106+
input ? ts.factory.createTrue() : ts.factory.createFalse()
107+
) as NodeOfMaybe<I>;
108+
}
104109
if (input instanceof Array) {
105110
return input.map((item) => this._render(item)) as NodeOfMaybe<I>;
106111
}
@@ -113,29 +118,33 @@ export abstract class TsDsl<T extends ts.Node = ts.Node> implements ITsDsl<T> {
113118
const arr = input instanceof Array ? input : [input];
114119
return arr.map((item) => {
115120
const node =
116-
typeof item === 'string'
117-
? ts.factory.createIdentifier(item)
118-
: this._render(item as any);
121+
typeof item === 'string' ? this.$expr(item) : this._render(item as any);
119122
return ts.isExpression(node)
120123
? ts.factory.createExpressionStatement(node)
121124
: (node as ts.Statement);
122125
});
123126
}
124127

125-
protected $type<I>(type: I): TypeOfMaybe<I> {
126-
if (type === undefined) {
128+
protected $type<I>(
129+
input: I,
130+
args?: ReadonlyArray<ts.TypeNode>,
131+
): TypeOfMaybe<I> {
132+
if (input === undefined) {
127133
return undefined as TypeOfMaybe<I>;
128134
}
129-
if (typeof type === 'string') {
130-
return ts.factory.createTypeReferenceNode(
131-
type,
132-
) as unknown as TypeOfMaybe<I>;
135+
if (typeof input === 'string') {
136+
return ts.factory.createTypeReferenceNode(input, args) as TypeOfMaybe<I>;
133137
}
134-
if (typeof type === 'boolean') {
135-
const literal = type ? ts.factory.createTrue() : ts.factory.createFalse();
138+
if (typeof input === 'boolean') {
139+
const literal = input
140+
? ts.factory.createTrue()
141+
: ts.factory.createFalse();
136142
return ts.factory.createLiteralTypeNode(literal) as TypeOfMaybe<I>;
137143
}
138-
return this._render(type as any) as TypeOfMaybe<I>;
144+
if (input instanceof Array) {
145+
return input.map((item) => this.$type(item, args)) as TypeOfMaybe<I>;
146+
}
147+
return this._render(input as any) as TypeOfMaybe<I>;
139148
}
140149

141150
private _render<T extends ts.Node>(value: MaybeTsDsl<T>): T {
@@ -152,33 +161,51 @@ type NodeOf<I> =
152161
? ReadonlyArray<U extends TsDsl<infer N> ? N : U>
153162
: I extends string
154163
? ts.Expression
155-
: I extends TsDsl<infer N>
156-
? N
157-
: I extends ts.Node
158-
? I
159-
: never;
160-
161-
type TypeOfMaybe<I> = undefined extends I
162-
? TypeOf<NonNullable<I>> | undefined
163-
: TypeOf<I>;
164-
165-
type TypeOf<I> = I extends string
166-
? ts.TypeNode
167-
: I extends boolean
168-
? ts.LiteralTypeNode
169-
: I extends TsDsl<infer N>
170-
? N
171-
: I extends ts.TypeNode
172-
? I
173-
: never;
164+
: I extends boolean
165+
? ts.Expression
166+
: I extends TsDsl<infer N>
167+
? N
168+
: I extends ts.Node
169+
? I
170+
: never;
174171

175172
export type MaybeTsDsl<T> =
176-
// if T includes string in the union
173+
// if T includes string
177174
string extends T
178175
? Exclude<T, string> extends ts.Node
179176
? string | Exclude<T, string> | TsDsl<Exclude<T, string>>
180177
: string
181-
: // otherwise only node or DSL
182-
T extends ts.Node
183-
? T | TsDsl<T>
184-
: never;
178+
: // if it's a DSL itself
179+
T extends TsDsl<any>
180+
? T
181+
: // otherwise if it’s a Node
182+
T extends ts.Node
183+
? T | TsDsl<T>
184+
: never;
185+
186+
export type TypeOfTsDsl<T> = T extends TsDsl<infer U> ? U : never;
187+
188+
export abstract class TypeTsDsl<
189+
T extends
190+
| ts.TypeNode
191+
| ts.TypeElement
192+
| ts.LiteralTypeNode
193+
| ts.TypeParameterDeclaration = ts.TypeNode,
194+
> extends TsDsl<T> {}
195+
196+
type TypeOfMaybe<I> = undefined extends I
197+
? TypeOf<NonNullable<I>> | undefined
198+
: TypeOf<I>;
199+
200+
type TypeOf<I> =
201+
I extends ReadonlyArray<infer U>
202+
? ReadonlyArray<TypeOf<U>>
203+
: I extends string
204+
? ts.TypeNode
205+
: I extends boolean
206+
? ts.LiteralTypeNode
207+
: I extends TsDsl<infer N>
208+
? N
209+
: I extends ts.TypeNode
210+
? I
211+
: never;

packages/openapi-ts/src/ts-dsl/class.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export class ClassTsDsl extends TsDsl<ts.ClassDeclaration> {
8282
const body = this.$node(this.body) as ReadonlyArray<ts.ClassElement>;
8383
return ts.factory.createClassDeclaration(
8484
[...this.$decorators(), ...this.modifiers.list()],
85-
ts.factory.createIdentifier(this.name),
85+
this.$expr(this.name),
8686
this.$generics(),
8787
this.heritageClauses,
8888
body,

packages/openapi-ts/src/ts-dsl/field.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
22
import ts from 'typescript';
33

4-
import { TsDsl } from './base';
4+
import { TsDsl, TypeTsDsl } from './base';
55
import { mixin } from './mixins/apply';
66
import { DecoratorMixin } from './mixins/decorator';
77
import { DescribeMixin } from './mixins/describe';
@@ -13,30 +13,33 @@ import {
1313
ReadonlyMixin,
1414
StaticMixin,
1515
} from './mixins/modifiers';
16-
import { createTypeAccessor, type TypeAccessor } from './mixins/type';
1716
import { ValueMixin } from './mixins/value';
17+
import { TypeExprTsDsl } from './type/expr';
1818

1919
export class FieldTsDsl extends TsDsl<ts.PropertyDeclaration> {
2020
private modifiers = createModifierAccessor(this);
2121
private name: string;
22-
private _type: TypeAccessor<FieldTsDsl> = createTypeAccessor(this);
23-
24-
/** Sets the property's type. */
25-
type: TypeAccessor<FieldTsDsl>['fn'] = this._type.fn;
22+
private _type?: TypeTsDsl;
2623

2724
constructor(name: string, fn?: (f: FieldTsDsl) => void) {
2825
super();
2926
this.name = name;
3027
fn?.(this);
3128
}
3229

30+
/** Sets the field type. */
31+
type(type: string | TypeTsDsl): this {
32+
this._type = type instanceof TypeTsDsl ? type : new TypeExprTsDsl(type);
33+
return this;
34+
}
35+
3336
/** Builds the `PropertyDeclaration` node. */
3437
$render(): ts.PropertyDeclaration {
3538
return ts.factory.createPropertyDeclaration(
3639
[...this.$decorators(), ...this.modifiers.list()],
37-
ts.factory.createIdentifier(this.name),
40+
this.$expr(this.name),
3841
undefined,
39-
this._type.$render(),
42+
this.$type(this._type),
4043
this.$value(),
4144
);
4245
}

0 commit comments

Comments
 (0)