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