Skip to content

Commit f3ceb92

Browse files
authored
fix(create-fetch): keep options.headers a plain object so plugins can spread it (#101)
1 parent 82fb728 commit f3ceb92

2 files changed

Lines changed: 30 additions & 6 deletions

File tree

packages/better-fetch/src/test/create.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,4 +705,25 @@ describe("create-fetch-headers", () => {
705705
expect(get()?.get("x-base")).toBe("base");
706706
expect(get()?.get("cookie")).toBe("session=abc");
707707
});
708+
709+
it("exposes options.headers to plugins as a spreadable plain object", async () => {
710+
// The electron/expo client plugins spread `options.headers`; a `Headers`
711+
// instance would spread to `{}` and drop the user's headers.
712+
let spread: Record<string, string> | undefined;
713+
const inspectPlugin: BetterFetchPlugin = {
714+
id: "inspect",
715+
name: "inspect",
716+
async init(url, options) {
717+
spread = { ...(options?.headers as Record<string, string>) };
718+
return { url, options };
719+
},
720+
};
721+
const $fetch = createFetch({
722+
baseURL: "http://localhost:4001",
723+
customFetchImpl: async () => new Response(null, { status: 200 }),
724+
plugins: [inspectPlugin],
725+
});
726+
await $fetch("/x", { headers: new Headers({ "x-user": "u" }) });
727+
expect(spread).toEqual({ "x-user": "u" });
728+
});
708729
});

packages/better-fetch/src/utils.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,23 @@ export function isRouteMethod(method?: string) {
103103

104104
export function mergeHeaders(
105105
...sources: (HeadersInit | Record<string, string | undefined> | undefined)[]
106-
): Headers {
107-
const merged = new Headers();
106+
): Record<string, string> {
107+
const merged: Record<string, string> = {};
108108
for (const source of sources) {
109109
if (!source) {
110110
continue;
111111
}
112-
// Object.entries/spread on a Headers instance yields nothing, dropping it.
112+
// Headers has no enumerable keys (Object.entries/spread drops it); the
113+
// result stays a plain object so plugins can spread `options.headers`.
113114
if (source instanceof Headers) {
114-
source.forEach((value, key) => merged.set(key, value));
115+
source.forEach((value, key) => {
116+
merged[key] = value;
117+
});
115118
} else {
116119
const entries = Array.isArray(source) ? source : Object.entries(source);
117120
for (const [key, value] of entries) {
118121
if (value !== null && value !== undefined) {
119-
merged.set(key, value);
122+
merged[key] = value;
120123
}
121124
}
122125
}
@@ -125,7 +128,7 @@ export function mergeHeaders(
125128
}
126129

127130
export async function getHeaders(opts?: BetterFetchOption) {
128-
const headers = mergeHeaders(opts?.headers, await getAuthHeader(opts));
131+
const headers = new Headers(mergeHeaders(opts?.headers, await getAuthHeader(opts)));
129132

130133
if (!headers.has("content-type")) {
131134
const contentType = detectContentType(opts?.body);

0 commit comments

Comments
 (0)