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" ;
22
33// settings & const
44const DEFAULT_HEADERS = {
55 "Content-Type" : "application/json" ,
66} ;
7- const TRAILING_SLASH_RE = / \/ * $ / ;
87
98// Note: though "any" is considered bad practice in general, this library relies
109// on "any" for type inference only it can give. Same goes for the "{}" type.
@@ -46,11 +45,16 @@ export type RequestOptions<T> = ParamsOption<T> &
4645export default function createClient < Paths extends { } > ( clientOptions : ClientOptions = { } ) {
4746 const { fetch = globalThis . fetch , querySerializer : globalQuerySerializer , bodySerializer : globalBodySerializer , ...options } = clientOptions ;
4847
48+ let baseUrl = options . baseUrl ?? "" ;
49+ if ( baseUrl . endsWith ( "/" ) ) {
50+ baseUrl = baseUrl . slice ( 0 , - 1 ) ; // remove trailing slash
51+ }
52+
4953 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 > > {
5054 const { headers, body : requestBody , params = { } , parseAs = "json" , querySerializer = globalQuerySerializer ?? defaultQuerySerializer , bodySerializer = globalBodySerializer ?? defaultBodySerializer , ...init } = fetchOptions || { } ;
5155
5256 // URL
53- const finalURL = createFinalURL ( url as string , { baseUrl : options . baseUrl , params, querySerializer } ) ;
57+ const finalURL = createFinalURL ( url as string , { baseUrl, params, querySerializer } ) ;
5458 const finalHeaders = mergeHeaders ( DEFAULT_HEADERS , clientOptions ?. headers , headers , ( params as any ) . header ) ;
5559
5660 // fetch!
@@ -89,38 +93,55 @@ export default function createClient<Paths extends {}>(clientOptions: ClientOpti
8993 return { error, response : response as any } ;
9094 }
9195
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+
92113 return {
93114 /** 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 ) ;
96117 } ,
97118 /** 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 ) ;
100121 } ,
101122 /** 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 ) ;
104125 } ,
105126 /** 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 ) ;
108129 } ,
109130 /** 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 ) ;
112133 } ,
113134 /** 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 ) ;
116137 } ,
117138 /** 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 ) ;
120141 } ,
121142 /** 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 ) ;
124145 } ,
125146 } ;
126147}
@@ -145,8 +166,8 @@ export function defaultBodySerializer<T>(body: T): string {
145166}
146167
147168/** 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 } ` ;
150171 if ( options . params . path ) {
151172 for ( const [ k , v ] of Object . entries ( options . params . path ) ) finalURL = finalURL . replace ( `{${ k } }` , encodeURIComponent ( String ( v ) ) ) ;
152173 }
0 commit comments