UNPKG

@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
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; };