1
- import type { ErrorResponse , HttpMethod , SuccessResponse , FilterKeys , MediaType , PathsWithMethod , ResponseObjectMap , OperationRequestBodyContent } from "openapi-typescript-helpers" ;
1
+ import type { ErrorResponse , HttpMethod , SuccessResponse , FilterKeys , MediaType , PathsWithMethod , ResponseObjectMap , OperationRequestBodyContent , HasRequiredKeys } from "openapi-typescript-helpers" ;
2
2
3
3
// settings & const
4
4
const DEFAULT_HEADERS = {
5
5
"Content-Type" : "application/json" ,
6
6
} ;
7
- const TRAILING_SLASH_RE = / \/ * $ / ;
8
7
9
8
// Note: though "any" is considered bad practice in general, this library relies
10
9
// on "any" for type inference only it can give. Same goes for the "{}" type.
@@ -46,11 +45,16 @@ export type RequestOptions<T> = ParamsOption<T> &
46
45
export default function createClient < Paths extends { } > ( clientOptions : ClientOptions = { } ) {
47
46
const { fetch = globalThis . fetch , querySerializer : globalQuerySerializer , bodySerializer : globalBodySerializer , ...options } = clientOptions ;
48
47
48
+ let baseUrl = options . baseUrl ?? "" ;
49
+ if ( baseUrl . endsWith ( "/" ) ) {
50
+ baseUrl = baseUrl . slice ( 0 , - 1 ) ; // remove trailing slash
51
+ }
52
+
49
53
async function coreFetch < P extends keyof Paths , M extends HttpMethod > ( url : P , fetchOptions : FetchOptions < M extends keyof Paths [ P ] ? Paths [ P ] [ M ] : never > ) : Promise < FetchResponse < M extends keyof Paths [ P ] ? Paths [ P ] [ M ] : unknown > > {
50
54
const { headers, body : requestBody , params = { } , parseAs = "json" , querySerializer = globalQuerySerializer ?? defaultQuerySerializer , bodySerializer = globalBodySerializer ?? defaultBodySerializer , ...init } = fetchOptions || { } ;
51
55
52
56
// URL
53
- const finalURL = createFinalURL ( url as string , { baseUrl : options . baseUrl , params, querySerializer } ) ;
57
+ const finalURL = createFinalURL ( url as string , { baseUrl, params, querySerializer } ) ;
54
58
const finalHeaders = mergeHeaders ( DEFAULT_HEADERS , clientOptions ?. headers , headers , ( params as any ) . header ) ;
55
59
56
60
// fetch!
@@ -89,38 +93,55 @@ export default function createClient<Paths extends {}>(clientOptions: ClientOpti
89
93
return { error, response : response as any } ;
90
94
}
91
95
96
+ type GetPaths = PathsWithMethod < Paths , "get" > ;
97
+ type PutPaths = PathsWithMethod < Paths , "put" > ;
98
+ type PostPaths = PathsWithMethod < Paths , "post" > ;
99
+ type DeletePaths = PathsWithMethod < Paths , "delete" > ;
100
+ type OptionsPaths = PathsWithMethod < Paths , "options" > ;
101
+ type HeadPaths = PathsWithMethod < Paths , "head" > ;
102
+ type PatchPaths = PathsWithMethod < Paths , "patch" > ;
103
+ type TracePaths = PathsWithMethod < Paths , "trace" > ;
104
+ type GetFetchOptions < P extends GetPaths > = FetchOptions < FilterKeys < Paths [ P ] , "get" > > ;
105
+ type PutFetchOptions < P extends PutPaths > = FetchOptions < FilterKeys < Paths [ P ] , "put" > > ;
106
+ type PostFetchOptions < P extends PostPaths > = FetchOptions < FilterKeys < Paths [ P ] , "post" > > ;
107
+ type DeleteFetchOptions < P extends DeletePaths > = FetchOptions < FilterKeys < Paths [ P ] , "delete" > > ;
108
+ type OptionsFetchOptions < P extends OptionsPaths > = FetchOptions < FilterKeys < Paths [ P ] , "options" > > ;
109
+ type HeadFetchOptions < P extends HeadPaths > = FetchOptions < FilterKeys < Paths [ P ] , "head" > > ;
110
+ type PatchFetchOptions < P extends PatchPaths > = FetchOptions < FilterKeys < Paths [ P ] , "patch" > > ;
111
+ type TraceFetchOptions < P extends TracePaths > = FetchOptions < FilterKeys < Paths [ P ] , "trace" > > ;
112
+
92
113
return {
93
114
/** Call a GET endpoint */
94
- async GET < P extends PathsWithMethod < Paths , "get" > > ( url : P , init : FetchOptions < FilterKeys < Paths [ P ] , "get" > > ) {
95
- return coreFetch < P , "get" > ( url , { ...init , method : "GET" } as any ) ;
115
+ async GET < P extends GetPaths > ( url : P , ... init : HasRequiredKeys < GetFetchOptions < P > > extends never ? [ GetFetchOptions < P > ? ] : [ GetFetchOptions < P > ] ) {
116
+ return coreFetch < P , "get" > ( url , { ...init [ 0 ] , method : "GET" } as any ) ;
96
117
} ,
97
118
/** Call a PUT endpoint */
98
- async PUT < P extends PathsWithMethod < Paths , "put" > > ( url : P , init : FetchOptions < FilterKeys < Paths [ P ] , "put" > > ) {
99
- return coreFetch < P , "put" > ( url , { ...init , method : "PUT" } as any ) ;
119
+ async PUT < P extends PutPaths > ( url : P , ... init : HasRequiredKeys < PutFetchOptions < P > > extends never ? [ PutFetchOptions < P > ? ] : [ PutFetchOptions < P > ] ) {
120
+ return coreFetch < P , "put" > ( url , { ...init [ 0 ] , method : "PUT" } as any ) ;
100
121
} ,
101
122
/** Call a POST endpoint */
102
- async POST < P extends PathsWithMethod < Paths , "post" > > ( url : P , init : FetchOptions < FilterKeys < Paths [ P ] , "post" > > ) {
103
- return coreFetch < P , "post" > ( url , { ...init , method : "POST" } as any ) ;
123
+ async POST < P extends PostPaths > ( url : P , ... init : HasRequiredKeys < PostFetchOptions < P > > extends never ? [ PostFetchOptions < P > ? ] : [ PostFetchOptions < P > ] ) {
124
+ return coreFetch < P , "post" > ( url , { ...init [ 0 ] , method : "POST" } as any ) ;
104
125
} ,
105
126
/** Call a DELETE endpoint */
106
- async DELETE < P extends PathsWithMethod < Paths , "delete" > > ( url : P , init : FetchOptions < FilterKeys < Paths [ P ] , "delete" > > ) {
107
- return coreFetch < P , "delete" > ( url , { ...init , method : "DELETE" } as any ) ;
127
+ async DELETE < P extends DeletePaths > ( url : P , ... init : HasRequiredKeys < DeleteFetchOptions < P > > extends never ? [ DeleteFetchOptions < P > ? ] : [ DeleteFetchOptions < P > ] ) {
128
+ return coreFetch < P , "delete" > ( url , { ...init [ 0 ] , method : "DELETE" } as any ) ;
108
129
} ,
109
130
/** Call a OPTIONS endpoint */
110
- async OPTIONS < P extends PathsWithMethod < Paths , "options" > > ( url : P , init : FetchOptions < FilterKeys < Paths [ P ] , "options" > > ) {
111
- return coreFetch < P , "options" > ( url , { ...init , method : "OPTIONS" } as any ) ;
131
+ async OPTIONS < P extends OptionsPaths > ( url : P , ... init : HasRequiredKeys < OptionsFetchOptions < P > > extends never ? [ OptionsFetchOptions < P > ? ] : [ OptionsFetchOptions < P > ] ) {
132
+ return coreFetch < P , "options" > ( url , { ...init [ 0 ] , method : "OPTIONS" } as any ) ;
112
133
} ,
113
134
/** Call a HEAD endpoint */
114
- async HEAD < P extends PathsWithMethod < Paths , "head" > > ( url : P , init : FetchOptions < FilterKeys < Paths [ P ] , "head" > > ) {
115
- return coreFetch < P , "head" > ( url , { ...init , method : "HEAD" } as any ) ;
135
+ async HEAD < P extends HeadPaths > ( url : P , ... init : HasRequiredKeys < HeadFetchOptions < P > > extends never ? [ HeadFetchOptions < P > ? ] : [ HeadFetchOptions < P > ] ) {
136
+ return coreFetch < P , "head" > ( url , { ...init [ 0 ] , method : "HEAD" } as any ) ;
116
137
} ,
117
138
/** Call a PATCH endpoint */
118
- async PATCH < P extends PathsWithMethod < Paths , "patch" > > ( url : P , init : FetchOptions < FilterKeys < Paths [ P ] , "patch" > > ) {
119
- return coreFetch < P , "patch" > ( url , { ...init , method : "PATCH" } as any ) ;
139
+ async PATCH < P extends PatchPaths > ( url : P , ... init : HasRequiredKeys < PatchFetchOptions < P > > extends never ? [ PatchFetchOptions < P > ? ] : [ PatchFetchOptions < P > ] ) {
140
+ return coreFetch < P , "patch" > ( url , { ...init [ 0 ] , method : "PATCH" } as any ) ;
120
141
} ,
121
142
/** Call a TRACE endpoint */
122
- async TRACE < P extends PathsWithMethod < Paths , "trace" > > ( url : P , init : FetchOptions < FilterKeys < Paths [ P ] , "trace" > > ) {
123
- return coreFetch < P , "trace" > ( url , { ...init , method : "TRACE" } as any ) ;
143
+ async TRACE < P extends TracePaths > ( url : P , ... init : HasRequiredKeys < TraceFetchOptions < P > > extends never ? [ TraceFetchOptions < P > ? ] : [ TraceFetchOptions < P > ] ) {
144
+ return coreFetch < P , "trace" > ( url , { ...init [ 0 ] , method : "TRACE" } as any ) ;
124
145
} ,
125
146
} ;
126
147
}
@@ -145,8 +166,8 @@ export function defaultBodySerializer<T>(body: T): string {
145
166
}
146
167
147
168
/** Construct URL string from baseUrl and handle path and query params */
148
- export function createFinalURL < O > ( url : string , options : { baseUrl ? : string ; params : { query ?: Record < string , unknown > ; path ?: Record < string , unknown > } ; querySerializer : QuerySerializer < O > } ) : string {
149
- let finalURL = `${ options . baseUrl ? options . baseUrl . replace ( TRAILING_SLASH_RE , "" ) : "" } ${ url as string } ` ;
169
+ export function createFinalURL < O > ( pathname : string , options : { baseUrl : string ; params : { query ?: Record < string , unknown > ; path ?: Record < string , unknown > } ; querySerializer : QuerySerializer < O > } ) : string {
170
+ let finalURL = `${ options . baseUrl } ${ pathname } ` ;
150
171
if ( options . params . path ) {
151
172
for ( const [ k , v ] of Object . entries ( options . params . path ) ) finalURL = finalURL . replace ( `{${ k } }` , encodeURIComponent ( String ( v ) ) ) ;
152
173
}
0 commit comments