diff --git a/.changeset/gentle-socks-wink.md b/.changeset/gentle-socks-wink.md new file mode 100644 index 000000000..5521e1704 --- /dev/null +++ b/.changeset/gentle-socks-wink.md @@ -0,0 +1,5 @@ +--- +"openapi-fetch": patch +--- + +Make headers typing friendlier diff --git a/.changeset/weak-games-exercise.md b/.changeset/weak-games-exercise.md new file mode 100644 index 000000000..75f21c602 --- /dev/null +++ b/.changeset/weak-games-exercise.md @@ -0,0 +1,5 @@ +--- +"openapi-fetch": patch +--- + +Allow unsetting headers diff --git a/packages/openapi-fetch/src/index.test.ts b/packages/openapi-fetch/src/index.test.ts index ad89b6ffb..483e7da13 100644 --- a/packages/openapi-fetch/src/index.test.ts +++ b/packages/openapi-fetch/src/index.test.ts @@ -337,6 +337,16 @@ describe("client", () => { ); }); + it("allows unsetting headers", async () => { + const client = createClient({ headers: { "Content-Type": null } }); + mockFetchOnce({ status: 200, body: JSON.stringify({ email: "user@user.com" }) }); + await client.GET("/self", { params: {} }); + + // assert default headers were passed + const options = fetchMocker.mock.calls[0][1]; + expect(options?.headers).toEqual(new Headers()); + }); + it("accepts a custom fetch function", async () => { const data = { works: true }; const customFetch = { diff --git a/packages/openapi-fetch/src/index.ts b/packages/openapi-fetch/src/index.ts index 4749f90b3..619f82a85 100644 --- a/packages/openapi-fetch/src/index.ts +++ b/packages/openapi-fetch/src/index.ts @@ -11,7 +11,7 @@ const TRAILING_SLASH_RE = /\/*$/; /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types */ /** options for each client instance */ -interface ClientOptions extends RequestInit { +interface ClientOptions extends Omit { /** set the common root URL for all API requests */ baseUrl?: string; /** custom fetch (defaults to globalThis.fetch) */ @@ -20,7 +20,10 @@ interface ClientOptions extends RequestInit { querySerializer?: QuerySerializer; /** global bodySerializer */ bodySerializer?: BodySerializer; + // headers override to make typing friendlier + headers?: HeadersOptions; } +export type HeadersOptions = HeadersInit | Record; export type QuerySerializer = (query: T extends { parameters: any } ? NonNullable : Record) => string; export type BodySerializer = (body: OperationRequestBodyContent) => any; export type ParseAs = "json" | "text" | "blob" | "arrayBuffer" | "stream"; @@ -43,17 +46,12 @@ export type RequestOptions = ParamsOption & export default function createClient(clientOptions: ClientOptions = {}) { const { fetch = globalThis.fetch, querySerializer: globalQuerySerializer, bodySerializer: globalBodySerializer, ...options } = clientOptions; - const defaultHeaders = new Headers({ - ...DEFAULT_HEADERS, - ...(options.headers ?? {}), - }); - async function coreFetch

(url: P, fetchOptions: FetchOptions): Promise> { const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = globalQuerySerializer ?? defaultQuerySerializer, bodySerializer = globalBodySerializer ?? defaultBodySerializer, ...init } = fetchOptions || {}; // URL const finalURL = createFinalURL(url as string, { baseUrl: options.baseUrl, params, querySerializer }); - const finalHeaders = mergeHeaders(defaultHeaders as any, headers as any, (params as any).header); + const finalHeaders = mergeHeaders(DEFAULT_HEADERS, clientOptions?.headers, headers, (params as any).header); // fetch! const requestInit: RequestInit = { redirect: "follow", ...options, ...init, headers: finalHeaders }; @@ -157,13 +155,15 @@ export function createFinalURL(url: string, options: { baseUrl?: string; para } /** merge headers a and b, with b taking priority */ -export function mergeHeaders(...allHeaders: (Record | Headers)[]): Headers { +export function mergeHeaders(...allHeaders: (HeadersOptions | undefined)[]): Headers { const headers = new Headers(); for (const headerSet of allHeaders) { if (!headerSet || typeof headerSet !== "object") continue; const iterator = headerSet instanceof Headers ? headerSet.entries() : Object.entries(headerSet); for (const [k, v] of iterator) { - if (v !== undefined && v !== null) { + if (v === null) { + headers.delete(k); + } else if (v !== undefined) { headers.set(k, v as any); } }