@@ -23,12 +23,18 @@ export type OpArgType<OP> = OP extends {
2323 }
2424 // openapi 3
2525 requestBody ?: {
26- content : {
27- 'application/json' : infer RB
28- }
26+ content :
27+ | {
28+ 'application/json' : infer RB
29+ }
30+ | {
31+ 'multipart/form-data' : infer FD
32+ }
2933 }
3034}
31- ? P & Q & ( B extends Record < string , unknown > ? B [ keyof B ] : unknown ) & RB
35+ ? FD extends Record < string , string >
36+ ? FormData
37+ : P & Q & ( B extends Record < string , unknown > ? B [ keyof B ] : unknown ) & RB
3238 : Record < string , never >
3339
3440type OpResponseTypes < OP > = OP extends {
@@ -65,11 +71,11 @@ export type OpDefaultReturnType<OP> = _OpDefaultReturnType<OpResponseTypes<OP>>
6571const never : unique symbol = Symbol ( )
6672
6773type _OpErrorType < T > = {
68- [ S in Exclude < keyof T , 200 | 201 > ] : {
74+ [ S in Exclude < keyof T , 200 | 201 | 204 > ] : {
6975 status : S extends 'default' ? typeof never : S
7076 data : T [ S ]
7177 }
72- } [ Exclude < keyof T , 200 | 201 > ]
78+ } [ Exclude < keyof T , 200 | 201 | 204 > ]
7379
7480type Coalesce < T , D > = [ T ] extends [ never ] ? D : T
7581
@@ -236,10 +242,14 @@ function getQuery(
236242 return queryString ( queryObj )
237243}
238244
239- function getHeaders ( body ?: string , init ?: HeadersInit ) {
245+ function getHeaders ( body ?: CustomRequestInit [ 'body' ] , init ?: HeadersInit ) {
240246 const headers = new Headers ( init )
241247
242- if ( body !== undefined && ! headers . has ( 'Content-Type' ) ) {
248+ if (
249+ body !== undefined &&
250+ ! ( body instanceof FormData ) &&
251+ ! headers . has ( 'Content-Type' )
252+ ) {
243253 headers . append ( 'Content-Type' , 'application/json' )
244254 }
245255
@@ -250,8 +260,13 @@ function getHeaders(body?: string, init?: HeadersInit) {
250260 return headers
251261}
252262
253- function getBody ( method : Method , payload : any ) {
254- const body = sendBody ( method ) ? JSON . stringify ( payload ) : undefined
263+ function getBody ( method : Method , payload : unknown ) : CustomRequestInit [ 'body' ] {
264+ if ( ! sendBody ( method ) ) {
265+ return
266+ }
267+
268+ const body = payload instanceof FormData ? payload : JSON . stringify ( payload )
269+
255270 // if delete don't send body if empty
256271 return method === 'delete' && body === '{}' ? undefined : body
257272}
@@ -272,7 +287,10 @@ function mergeRequestInit(
272287 return { ...first , ...second , headers }
273288}
274289
275- function getFetchParams ( request : Request ) {
290+ function getFetchParams ( request : Request ) : {
291+ url : string
292+ init : CustomRequestInit
293+ } {
276294 // clone payload
277295 // if body is a top level array [ 'a', 'b', param: value ] with param values
278296 // using spread [ ...payload ] returns [ 'a', 'b' ] and skips custom keys
@@ -288,7 +306,8 @@ function getFetchParams(request: Request) {
288306 const headers = getHeaders ( body , request . init ?. headers )
289307 const url = request . baseUrl + path + query
290308
291- const init = {
309+ // @ts -expect-error `body` is the correct type, but because we're using exact optional types, `body` is not allowed to be explicitly `undefined`. It's inferred as `undefined` because of the union return type of `getBody`, and there's no way to tell TS that it's optional here.
310+ const init : CustomRequestInit = {
292311 ...request . init ,
293312 method : request . method . toUpperCase ( ) ,
294313 headers,
@@ -429,7 +448,7 @@ function fetcher<Paths>() {
429448 init : mergeRequestInit ( defaultInit , init ) ,
430449 fetch,
431450 } ) ,
432- ) ) as CreateFetch < M , Paths [ P ] [ M ] > ,
451+ ) ) as unknown as CreateFetch < M , Paths [ P ] [ M ] > ,
433452 } ) ,
434453 } ) ,
435454 }
0 commit comments