import destr from 'destr' ; import { withBase , withQuery } from 'ufo' ; import type { Fetch , RequestInfo , RequestInit , Response } from './types' ; import { createFetchError } from './error' ; import { isPayloadMethod , isJSONSerializable , detectResponseType , ResponseType , MappedType } from './utils' ; export interface CreateFetchOptions { defaults ? : FetchOptions ; fetch : Fetch ; Headers : typeof Headers ; } export type FetchRequest = RequestInfo ; export interface FetchResponse < T > extends Response { data ? : T ; } export interface SearchParams { [ key ] : any ; } export interface FetchContext < T = any , R extends ResponseType = ResponseType > { request : FetchRequest ; options : FetchOptions < R > ; response ? : FetchResponse < T > ; error ? : Error ; } export interface FetchOptions < R extends ResponseType = ResponseType > extends Omit < RequestInit , 'body' > { baseURL ? : string ; body ? : RequestInit [ 'body' ] | Record < string , any > ; params ? : SearchParams ; parseResponse ? : ( responseText : string ) => any ; responseType ? : R ; response ? : boolean ; retry ? : number | false ; onRequest ? ( ctx ) : Promise < void > ; onRequestError ? ( ctx ) : Promise < void > ; onResponse ? ( ctx ) : Promise < void > ; onResponseError ? ( ctx ) : Promise < void > ; } export interface $Fetch { < T = any , R extends ResponseType = 'json' > ( request , opts ? ) : Promise < MappedType < R , T > > ; raw < T = any , R extends ResponseType = 'json' > ( request , opts ? ) : Promise < FetchResponse < MappedType < R , T > > > ; create ( defaults ) : $Fetch ; } const retryStatusCodes = new Set ( [ 408 , 409 , 425 , 429 , 500 , 502 , 503 , 504 ] ) ; export function createFetch ( globalOptions ) { const { fetch , Headers } = globalOptions ; function onError ( ctx ) { if ( ctx . options . retry !== false ) { const retries = typeof ctx . options . retry === 'number' ? ctx . options . retry : ( isPayloadMethod ( ctx . options . method ) ? 0 : 1 ) ; const responseCode = ( ctx . response && ctx . response . status ) || 500 ; if ( retries > 0 && retryStatusCodes . has ( responseCode ) ) { return $fetchRaw ( ctx . request , { ... ctx . options , retry : retries - 1 } ) ; } } const err = createFetchError ( ctx . request , ctx . error , ctx . response ) ; if ( Error . captureStackTrace ) { Error . captureStackTrace ( err , $fetchRaw ) ; } throw err ; } const $fetchRaw = async function $fetchRaw ( _request , _opts = { } ) { const ctx = { request : _request , options : { ... globalOptions . defaults , ... _opts } , response : undefined , error : undefined } ; if ( ctx . options . onRequest ) { await ctx . options . onRequest ( ctx ) ; } if ( typeof ctx . request === 'string' ) { if ( ctx . options . baseURL ) { ctx . request = withBase ( ctx . request , ctx . options . baseURL ) ; } if ( ctx . options . params ) { ctx . request = withQuery ( ctx . request , ctx . options . params ) ; } if ( ctx . options . body && isPayloadMethod ( ctx . options . method ) ) { if ( isJSONSerializable ( ctx . options . body ) ) { ctx . options . body = JSON . stringify ( ctx . options . body ) ; ctx . options . headers = new Headers ( ctx . options . headers ) ; if ( ! ctx . options . headers . has ( 'content-type' ) ) { ctx . options . headers . set ( 'content-type' , 'application/json' ) ; } if ( ! ctx . options . headers . has ( 'accept' ) ) { ctx . options . headers . set ( 'accept' , 'application/json' ) ; } } } } ctx . response = await fetch ( ctx . request , ctx . options as RequestInit ) . catch ( async ( error ) => { ctx . error = error ; if ( ctx . options . onRequestError ) { await ctx . options . onRequestError ( ctx as any ) ; } return onError ( ctx ) ; } ) ; const responseType = ( ctx . options . parseResponse ? 'json' : ctx . options . responseType ) || detectResponseType ( ctx . response . headers . get ( 'content-type' ) || '' ) ; if ( responseType === 'json' ) { const data = await ctx . response . text ( ) ; const parseFn = ctx . options . parseResponse || destr ; ctx . response . data = parseFn ( data ) ; } else { ctx . response . data = await ctx . response [ responseType ] ( ) ; } if ( ctx . options . onResponse ) { await ctx . options . onResponse ( ctx as any ) ; } if ( ! ctx . response . ok ) { if ( ctx . options . onResponseError ) { await ctx . options . onResponseError ( ctx as any ) ; } } return ctx . response . ok ? ctx . response : onError ( ctx ) ; } ; const $fetch = function $fetch ( request , opts ) { return $fetchRaw ( request , opts ) . then ( r => r . data ) ; } as $Fetch ; $fetch . raw = $fetchRaw ; $fetch . create = ( defaultOptions = { } ) => createFetch ( { ... globalOptions , defaults : { ... globalOptions . defaults , ... defaultOptions } } ) ; return $fetch ; }