type RequestResult< T = void, TType extends 'json' | 'void' = 'json' > = TType extends 'void' ? undefined : TType extends 'json' ? T : void export class RequestError extends Error { constructor(public response: Response, message: string) { super(message) } } export class ResponseEmptyError extends RequestError { constructor(response: Response, message: string) { super(response, message) } } export const request = async < TResult = void, TType extends 'json' | 'void' = 'json' >( url: string, options?: RequestInit, type?: TType ): Promise> => { const response = await fetch(url, options) if (!response.ok) { throw new RequestError( response, `${response.status} ${response.statusText}` ) } if (type === 'void') { return undefined as RequestResult } const text = await response.text() if (!text) { if (!type || type === 'json') { throw new ResponseEmptyError( response, 'Expected json, got empty response' ) } return undefined as RequestResult } return JSON.parse(text) as RequestResult }