@hey-api/openapi-ts
Version:
🌀 OpenAPI to TypeScript codegen. Production-grade SDKs, Zod schemas, TanStack Query hooks, and 20+ plugins. Used by Vercel, OpenCode, and PayPal.
254 lines (215 loc) • 6.98 kB
text/typescript
import type { HttpResponse } from '@angular/common/http';
import { HttpClient, HttpErrorResponse, HttpEventType, HttpRequest } from '@angular/common/http';
import {
assertInInjectionContext,
inject,
provideAppInitializer,
runInInjectionContext,
} from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { filter } from 'rxjs/operators';
import { createSseClient } from '../core/serverSentEvents';
import type { HttpMethod } from '../core/types';
import { getValidRequestBody } from '../core/utils';
import type {
Client,
Config,
RequestOptions,
ResolvedRequestOptions,
ResponseStyle,
} from './types';
import {
buildUrl,
createConfig,
createInterceptors,
mergeConfigs,
mergeHeaders,
setAuthParams,
} from './utils';
export function provideHeyApiClient(client: Client) {
return provideAppInitializer(() => {
const httpClient = inject(HttpClient);
client.setConfig({ httpClient });
});
}
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<
HttpRequest<unknown>,
HttpResponse<unknown>,
unknown,
ResolvedRequestOptions
>();
const requestOptions = <
TData = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: RequestOptions<TData, TResponseStyle, ThrowOnError>,
) => {
const opts = {
..._config,
...options,
headers: mergeHeaders(_config.headers, options.headers),
httpClient: options.httpClient ?? _config.httpClient,
serializedBody: undefined as string | undefined,
};
if (!opts.httpClient) {
if (opts.injector) {
opts.httpClient = runInInjectionContext(opts.injector, () => inject(HttpClient));
} else {
assertInInjectionContext(requestOptions);
opts.httpClient = inject(HttpClient);
}
}
if (opts.body !== undefined && opts.bodySerializer) {
opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined;
}
// remove Content-Type header if body is empty to avoid sending invalid requests
if (opts.body === undefined || opts.serializedBody === '') {
opts.headers.delete('Content-Type');
}
const url = buildUrl(opts as Config & RequestOptions);
const req = new HttpRequest<unknown>(opts.method ?? 'GET', url, getValidRequestBody(opts), {
redirect: 'follow',
...opts,
});
return { opts, req, url };
};
const beforeRequest = async <
TData = unknown,
TResponseStyle extends ResponseStyle = 'fields',
ThrowOnError extends boolean = boolean,
Url extends string = string,
>(
options: RequestOptions<TData, TResponseStyle, ThrowOnError, Url>,
) => {
const { opts, req, url } = requestOptions(options);
if (opts.security) {
await setAuthParams(opts);
}
if (opts.requestValidator) {
await opts.requestValidator(opts);
}
return { opts, req, url };
};
const request: Client['request'] = async (options) => {
const throwOnError = options.throwOnError ?? _config.throwOnError;
const responseStyle = options.responseStyle ?? _config.responseStyle;
const result: {
request?: HttpRequest<unknown>;
response?: any;
} = {
request: undefined,
response: undefined,
};
try {
const { opts, req: initialReq } = await beforeRequest(options);
let req = initialReq;
result.request = req;
for (const fn of interceptors.request.fns) {
if (fn) {
req = await fn(req, opts as ResolvedRequestOptions);
result.request = req;
}
}
result.response = await firstValueFrom(
opts
.httpClient!.request(req)
.pipe(filter((event) => event.type === HttpEventType.Response)),
);
for (const fn of interceptors.response.fns) {
if (fn) {
result.response = await fn(result.response, req, opts as ResolvedRequestOptions);
}
}
let bodyResponse = result.response.body;
if (opts.responseValidator) {
await opts.responseValidator(bodyResponse);
}
if (opts.responseTransformer) {
bodyResponse = await opts.responseTransformer(bodyResponse);
}
return opts.responseStyle === 'data' ? bodyResponse : { data: bodyResponse, ...result };
} catch (error) {
if (error instanceof HttpErrorResponse) {
result.response = error;
}
let finalError = error instanceof HttpErrorResponse ? error.error : error;
for (const fn of interceptors.error.fns) {
if (fn) {
finalError = await fn(
finalError,
result.response,
result.request,
options as ResolvedRequestOptions,
);
}
}
if (throwOnError) {
throw finalError;
}
return responseStyle === 'data'
? undefined
: {
error: finalError,
...result,
};
}
};
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
request({ ...options, method });
const makeSseFn = (method: Uppercase<HttpMethod>) => 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,
serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined,
url,
});
};
const _buildUrl: Client['buildUrl'] = (options) => buildUrl({ ..._config, ...options });
return {
buildUrl: _buildUrl,
connect: makeMethodFn('CONNECT'),
delete: makeMethodFn('DELETE'),
get: makeMethodFn('GET'),
getConfig,
head: makeMethodFn('HEAD'),
interceptors,
options: makeMethodFn('OPTIONS'),
patch: makeMethodFn('PATCH'),
post: makeMethodFn('POST'),
put: makeMethodFn('PUT'),
request,
requestOptions: (options) => {
if (options.security) {
throw new Error('Security is not supported in requestOptions');
}
if (options.requestValidator) {
throw new Error('Request validation is not supported in requestOptions');
}
return requestOptions(options).req;
},
setConfig,
sse: {
connect: makeSseFn('CONNECT'),
delete: makeSseFn('DELETE'),
get: makeSseFn('GET'),
head: makeSseFn('HEAD'),
options: makeSseFn('OPTIONS'),
patch: makeSseFn('PATCH'),
post: makeSseFn('POST'),
put: makeSseFn('PUT'),
trace: makeSseFn('TRACE'),
},
trace: makeMethodFn('TRACE'),
} as Client;
};