fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 182 lines 4.6 kB view raw
1import type { Client, Config, RequestOptions } from './types'; 2import { 3 buildUrl, 4 createConfig, 5 createInterceptors, 6 getParseAs, 7 mergeConfigs, 8 mergeHeaders, 9 setAuthParams, 10} from './utils'; 11 12type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 13 body?: any; 14 headers: ReturnType<typeof mergeHeaders>; 15}; 16 17export const createClient = (config: Config = {}): Client => { 18 let _config = mergeConfigs(createConfig(), config); 19 20 const getConfig = (): Config => ({ ..._config }); 21 22 const setConfig = (config: Config): Config => { 23 _config = mergeConfigs(_config, config); 24 return getConfig(); 25 }; 26 27 const interceptors = createInterceptors<Request, Response, unknown, RequestOptions>(); 28 29 // @ts-expect-error 30 const request: Client['request'] = async (options) => { 31 const opts = { 32 ..._config, 33 ...options, 34 fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 35 headers: mergeHeaders(_config.headers, options.headers), 36 }; 37 38 if (opts.security) { 39 await setAuthParams({ 40 ...opts, 41 security: opts.security, 42 }); 43 } 44 45 if (opts.requestValidator) { 46 await opts.requestValidator(opts); 47 } 48 49 if (opts.body && opts.bodySerializer) { 50 opts.body = opts.bodySerializer(opts.body); 51 } 52 53 // remove Content-Type header if body is empty to avoid sending invalid requests 54 if (opts.body === undefined || opts.body === '') { 55 opts.headers.delete('Content-Type'); 56 } 57 58 const url = buildUrl(opts); 59 const requestInit: ReqInit = { 60 redirect: 'follow', 61 ...opts, 62 }; 63 64 let request = new Request(url, requestInit); 65 66 for (const fn of interceptors.request.fns) { 67 if (fn) { 68 request = await fn(request, opts); 69 } 70 } 71 72 // fetch must be assigned here, otherwise it would throw the error: 73 // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 74 const _fetch = opts.fetch!; 75 let response = await _fetch(request); 76 77 for (const fn of interceptors.response.fns) { 78 if (fn) { 79 response = await fn(response, request, opts); 80 } 81 } 82 83 const result = { 84 request, 85 response, 86 }; 87 88 if (response.ok) { 89 if (response.status === 204 || response.headers.get('Content-Length') === '0') { 90 return { 91 data: {}, 92 ...result, 93 }; 94 } 95 96 const parseAs = 97 (opts.parseAs === 'auto' 98 ? getParseAs(response.headers.get('Content-Type')) 99 : opts.parseAs) ?? 'json'; 100 101 let data: any; 102 switch (parseAs) { 103 case 'arrayBuffer': 104 case 'blob': 105 case 'formData': 106 case 'text': 107 data = await response[parseAs](); 108 break; 109 case 'json': { 110 // Some servers return 200 with no Content-Length and empty body. 111 // response.json() would throw; read as text and parse if non-empty. 112 const text = await response.text(); 113 data = text ? JSON.parse(text) : {}; 114 break; 115 } 116 case 'stream': 117 return { 118 data: response.body, 119 ...result, 120 }; 121 } 122 if (parseAs === 'json') { 123 if (opts.responseValidator) { 124 await opts.responseValidator(data); 125 } 126 127 if (opts.responseTransformer) { 128 data = await opts.responseTransformer(data); 129 } 130 } 131 132 return { 133 data, 134 ...result, 135 }; 136 } 137 138 let error = await response.text(); 139 140 try { 141 error = JSON.parse(error); 142 } catch { 143 // noop 144 } 145 146 let finalError = error; 147 148 for (const fn of interceptors.error.fns) { 149 if (fn) { 150 finalError = (await fn(error, response, request, opts)) as string; 151 } 152 } 153 154 finalError = finalError || ({} as string); 155 156 if (opts.throwOnError) { 157 throw finalError; 158 } 159 160 return { 161 error: finalError, 162 ...result, 163 }; 164 }; 165 166 return { 167 buildUrl, 168 connect: (options) => request({ ...options, method: 'CONNECT' }), 169 delete: (options) => request({ ...options, method: 'DELETE' }), 170 get: (options) => request({ ...options, method: 'GET' }), 171 getConfig, 172 head: (options) => request({ ...options, method: 'HEAD' }), 173 interceptors, 174 options: (options) => request({ ...options, method: 'OPTIONS' }), 175 patch: (options) => request({ ...options, method: 'PATCH' }), 176 post: (options) => request({ ...options, method: 'POST' }), 177 put: (options) => request({ ...options, method: 'PUT' }), 178 request, 179 setConfig, 180 trace: (options) => request({ ...options, method: 'TRACE' }), 181 }; 182};