fork of hey-api/openapi-ts because I need some additional things
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};