@@ -15,10 +15,7 @@ import {
1515 * Generates a TypeScript API client using openapi-fetch based on TypeScript interface definitions
1616 * generated by openapi-typescript.
1717 */
18- export function generateClient (
19- schemaFilePath : string ,
20- defaultHeaderNames : string [ ] ,
21- ) : string {
18+ export function generateClient ( schemaFilePath : string ) : string {
2219 const project = new Project ( {
2320 compilerOptions : {
2421 target : ScriptTarget . Latest ,
@@ -32,11 +29,7 @@ export function generateClient(
3229 throw new Error ( `Interface "paths" not found in ${ schemaFilePath } ` ) ;
3330 }
3431
35- return generateClientCode (
36- pathsInterface ,
37- path . basename ( schemaFilePath ) ,
38- defaultHeaderNames ,
39- ) ;
32+ return generateClientCode ( pathsInterface , path . basename ( schemaFilePath ) ) ;
4033}
4134
4235function findInterface (
@@ -53,23 +46,16 @@ interface EndpointInfo {
5346 commentLines : string [ ] ;
5447 paramsType : string | null ;
5548 bodyType : string | null ;
56- headerExists : boolean ;
49+ headerType : string | null ;
5750}
5851
5952function generateClientCode (
6053 pathsInterface : InterfaceDeclaration ,
6154 schemaFileName : string ,
62- defaultHeaderNames : string [ ] ,
6355) : string {
64- const defaultHeaderExists = defaultHeaderNames . length > 0 ;
65-
66- const endpoints = extractEndpointsInfo ( pathsInterface , defaultHeaderExists ) ;
56+ const endpoints = extractEndpointsInfo ( pathsInterface ) ;
6757
68- const defaultHeaderType =
69- defaultHeaderNames . length > 0
70- ? `type DefaultHeaders = Record<${ defaultHeaderNames . map ( ( n ) => `"${ n } "` ) . join ( "|" ) } , string>\n`
71- : "" ;
72- const clientClass = generateClientClass ( endpoints , defaultHeaderExists ) ;
58+ const clientClass = generateClientClass ( endpoints ) ;
7359
7460 const code = [
7561 "// THIS FILE IS AUTO-GENERATED BY openapi-fetch-gen." ,
@@ -78,7 +64,6 @@ function generateClientCode(
7864 `import createClient, { type ClientOptions } from "openapi-fetch";` ,
7965 `import type { paths } from "./${ schemaFileName } "; // generated by openapi-typescript` ,
8066 "" ,
81- defaultHeaderType ,
8267 clientClass ,
8368 ] . join ( "\n" ) ;
8469
@@ -90,7 +75,6 @@ function generateClientCode(
9075
9176function extractEndpointsInfo (
9277 pathsInterface : InterfaceDeclaration ,
93- defaultHeaderExists : boolean ,
9478) : EndpointInfo [ ] {
9579 const endpoints : EndpointInfo [ ] = [ ] ;
9680
@@ -148,20 +132,49 @@ function extractEndpointsInfo(
148132 . filter ( ( kv ) => kv [ "text" ] !== "" ) ;
149133
150134 const headerType = paramTypes . find ( ( kv ) => kv . name === "header" ) ;
151- if ( defaultHeaderExists && headerType ) {
135+ if ( headerType ) {
136+ // header exists in params
152137 const nonHeaderParams = paramTypes
153138 . filter ( ( kv ) => kv . name !== "header" )
154139 . map ( ( kv ) => `${ kv . name } : ${ kv . text } ` )
155140 . join ( "\n" ) ;
156141
157- paramsType = `keyof Omit<${ headerType [ "text" ] } , keyof DefaultHeaders> extends never ?
158- {
142+ paramsType =
143+ `[
144+ Exclude< // Missed Header Keys for default headers
145+ keyof ${ headerType [ "text" ] } ,
146+ Extract< // Provided header keys by default headers' keys
147+ keyof HT, keyof ${ headerType [ "text" ] }
148+ >
149+ >,
150+ ] extends [never] ? ` +
151+ // When the default headers cover all the headers (i.e. `Exclude<...>` derived as `never`),
152+ // header parameter becomes optional (omitting or )overriding default headers.
153+ `{
159154 header?: ${ headerType [ "text" ] } ,
160155 ${ nonHeaderParams }
161- } :
162- {
156+ } : ` + // Else, header parameter is required as either follows:
157+ // 1. requires sorely missed header values
158+ // 2. requires all the header values (overriding default headers)
159+ `{
163160 header:
164- | Omit<${ headerType [ "text" ] } , keyof DefaultHeaders>
161+ | (Pick< // Pick the header keys that are not in the default headers
162+ ${ headerType [ "text" ] } ,
163+ Exclude< // Missed Header Keys for default headers
164+ keyof ${ headerType [ "text" ] } ,
165+ Extract< // Provided header keys by default headers' keys
166+ keyof HT, keyof ${ headerType [ "text" ] }
167+ >
168+ >
169+ > &
170+ Partial< // Disallow default headers' keys to be in the header param
171+ Record<
172+ Extract< // Provided header keys by default headers' keys
173+ keyof HT, keyof ${ headerType [ "text" ] }
174+ >,
175+ never
176+ >
177+ >)
165178 | ${ headerType [ "text" ] } ,
166179 ${ nonHeaderParams }
167180}
@@ -227,26 +240,23 @@ function extractEndpointsInfo(
227240 commentLines,
228241 paramsType,
229242 bodyType : requestBodyType ,
230- headerExists : headerType !== undefined ,
243+ headerType : headerType ? headerType [ "text" ] : null ,
231244 } ) ;
232245 }
233246 }
234247
235248 return endpoints ;
236249}
237250
238- function generateClientClass (
239- endpoints : EndpointInfo [ ] ,
240- defaultHeaderExists : boolean ,
241- ) : string {
251+ function generateClientClass ( endpoints : EndpointInfo [ ] ) : string {
242252 const classCode = [
243- `export class Client {
253+ `export class Client<HT extends Record<string, string>> {
244254 private readonly client;
245- ${ defaultHeaderExists ? " private readonly defaultHeaders: DefaultHeaders;" : "" }
255+ private readonly defaultHeaders: HT;
246256
247- constructor(clientOptions: ClientOptions${ defaultHeaderExists ? " , defaultHeaders: DefaultHeaders" : "" } ) {
257+ constructor(clientOptions: ClientOptions, defaultHeaders?: HT ) {
248258 this.client = createClient<paths>(clientOptions);
249- ${ defaultHeaderExists ? " this.defaultHeaders = defaultHeaders;" : "" }
259+ this.defaultHeaders = defaultHeaders ?? ({} as HT);
250260 }
251261 ` ,
252262 ] ;
@@ -297,10 +307,10 @@ function generateClientClass(
297307 ) ;
298308
299309 if ( paramsType ) {
300- if ( defaultHeaderExists && endpoint . headerExists ) {
310+ if ( endpoint . headerType ) {
301311 classCode . push ( `params: {
302312 ...params,
303- header: {...this.defaultHeaders, ...params.header},
313+ header: {...this.defaultHeaders, ...params.header} as ${ endpoint . headerType } ,
304314},` ) ;
305315 } else {
306316 classCode . push ( "params," ) ;
0 commit comments