@hey-api/openapi-ts
Version:
🚀 The OpenAPI to TypeScript codegen. Generate clients, SDKs, validators, and more.
212 lines (179 loc) • 4.87 kB
text/typescript
import { createSseClient } from '../core/serverSentEvents';
import type {
Client,
Config,
RequestOptions,
ResolvedRequestOptions,
} from './types';
import {
buildUrl,
createConfig,
createInterceptors,
getParseAs,
mergeConfigs,
mergeHeaders,
setAuthParams,
} from './utils';
type ReqInit = Omit<RequestInit, 'body' | 'headers'> & {
body?: any;
headers: ReturnType<typeof mergeHeaders>;
};
export const createClient = (config: Config = {}): Client => {
let _config = mergeConfigs(createConfig(), config);
const getConfig = (): Config => ({ ..._config });
const setConfig = (config: Config): Config => {
_config = mergeConfigs(_config, config);
return getConfig();
};
const interceptors = createInterceptors<
Response,
unknown,
ResolvedRequestOptions
>();
const beforeRequest = async (options: RequestOptions) => {
const opts = {
..._config,
...options,
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
headers: mergeHeaders(_config.headers, options.headers),
serializedBody: undefined,
};
if (opts.security) {
await setAuthParams({
...opts,
security: opts.security,
});
}
if (opts.requestValidator) {
await opts.requestValidator(opts);
}
if (opts.body && opts.bodySerializer) {
opts.serializedBody = opts.bodySerializer(opts.body);
}
// remove Content-Type header if body is empty to avoid sending invalid requests
if (opts.serializedBody === undefined || opts.serializedBody === '') {
opts.headers.delete('Content-Type');
}
const url = buildUrl(opts);
return { opts, url };
};
// @ts-expect-error
const request: Client['request'] = async (options) => {
// @ts-expect-error
const { opts, url } = await beforeRequest(options);
for (const fn of interceptors.request._fns) {
if (fn) {
await fn(opts);
}
}
// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = opts.fetch!;
let response = await _fetch(url, {
...opts,
body: opts.serializedBody as ReqInit['body'],
});
for (const fn of interceptors.response._fns) {
if (fn) {
response = await fn(response, opts);
}
}
const result = {
response,
};
if (response.ok) {
if (
response.status === 204 ||
response.headers.get('Content-Length') === '0'
) {
return {
data: {},
...result,
};
}
const parseAs =
(opts.parseAs === 'auto'
? getParseAs(response.headers.get('Content-Type'))
: opts.parseAs) ?? 'json';
let data: any;
switch (parseAs) {
case 'arrayBuffer':
case 'blob':
case 'formData':
case 'json':
case 'text':
data = await response[parseAs]();
break;
case 'stream':
return {
data: response.body,
...result,
};
}
if (parseAs === 'json') {
if (opts.responseValidator) {
await opts.responseValidator(data);
}
if (opts.responseTransformer) {
data = await opts.responseTransformer(data);
}
}
return {
data,
...result,
};
}
const textError = await response.text();
let jsonError: unknown;
try {
jsonError = JSON.parse(textError);
} catch {
// noop
}
const error = jsonError ?? textError;
let finalError = error;
for (const fn of interceptors.error._fns) {
if (fn) {
finalError = (await fn(error, response, opts)) as string;
}
}
finalError = finalError || ({} as string);
if (opts.throwOnError) {
throw finalError;
}
return {
error: finalError,
...result,
};
};
const makeMethod = (method: Required<Config>['method']) => {
const fn = (options: RequestOptions) => request({ ...options, method });
fn.sse = async (options: RequestOptions) => {
const { opts, url } = await beforeRequest(options);
return createSseClient({
...opts,
body: opts.body as BodyInit | null | undefined,
headers: opts.headers as unknown as Record<string, string>,
method,
url,
});
};
return fn;
};
return {
buildUrl,
connect: makeMethod('CONNECT'),
delete: makeMethod('DELETE'),
get: makeMethod('GET'),
getConfig,
head: makeMethod('HEAD'),
interceptors,
options: makeMethod('OPTIONS'),
patch: makeMethod('PATCH'),
post: makeMethod('POST'),
put: makeMethod('PUT'),
request,
setConfig,
trace: makeMethod('TRACE'),
} as Client;
};