From afde9290dbd3994aa8c417ce7424873ee9ba4bae Mon Sep 17 00:00:00 2001 From: Rastislav Date: Wed, 11 Mar 2026 19:45:36 +0100 Subject: [PATCH] Fix #118 toResponse overwrites multiple Set-Cookie headers --- packages/better-call/src/to-response.test.ts | 47 ++++++++++++++++++++ packages/better-call/src/to-response.ts | 27 ++++++++--- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/packages/better-call/src/to-response.test.ts b/packages/better-call/src/to-response.test.ts index f297dd4..62ea033 100644 --- a/packages/better-call/src/to-response.test.ts +++ b/packages/better-call/src/to-response.test.ts @@ -162,6 +162,53 @@ describe("toResponse", () => { }); }); + describe("Set-Cookie header preservation", () => { + it("should preserve multiple Set-Cookie when merging onto existing Response", () => { + const res = new Response("ok"); + const initHeaders = new Headers(); + initHeaders.append("set-cookie", "session=abc123; Path=/"); + initHeaders.append("set-cookie", "session_data=xyz; Path=/"); + const response = toResponse(res, { headers: initHeaders }); + const setCookies = response.headers.getSetCookie?.() ?? []; + expect(setCookies).toHaveLength(2); + expect(setCookies[0]).toContain("session=abc123"); + expect(setCookies[1]).toContain("session_data=xyz"); + }); + + it("should preserve multiple Set-Cookie in _flag=json with init.headers", () => { + const flaggedData = { + _flag: "json", + body: { ok: true }, + status: 200, + }; + const initHeaders = new Headers(); + initHeaders.append("set-cookie", "session_token=token1; Path=/; HttpOnly"); + initHeaders.append("set-cookie", "session_data=cache; Path=/"); + const response = toResponse(flaggedData, { headers: initHeaders }); + const setCookies = response.headers.getSetCookie?.() ?? []; + expect(setCookies).toHaveLength(2); + expect(setCookies[0]).toContain("session_token=token1"); + expect(setCookies[1]).toContain("session_data=cache"); + }); + + it("should preserve multiple Set-Cookie from routerResponse.headers in _flag=json", () => { + const routerHeaders = new Headers(); + routerHeaders.append("set-cookie", "a=1; Path=/"); + routerHeaders.append("set-cookie", "b=2; Path=/"); + const flaggedData = { + _flag: "json", + body: { ok: true }, + routerResponse: { headers: routerHeaders }, + status: 200, + }; + const response = toResponse(flaggedData); + const setCookies = response.headers.getSetCookie?.() ?? []; + expect(setCookies).toHaveLength(2); + expect(setCookies[0]).toContain("a=1"); + expect(setCookies[1]).toContain("b=2"); + }); + }); + describe("BigInt handling", () => { it("should handle simple bigint values", async () => { const data = { id: BigInt(9007199254740991) }; diff --git a/packages/better-call/src/to-response.ts b/packages/better-call/src/to-response.ts index f8ddf69..bac0453 100644 --- a/packages/better-call/src/to-response.ts +++ b/packages/better-call/src/to-response.ts @@ -76,7 +76,11 @@ export function toResponse(data?: any, init?: ResponseInit): Response { if (data instanceof Response) { if (init?.headers instanceof Headers) { init.headers.forEach((value, key) => { - data.headers.set(key, value); + if (key.toLowerCase() === "set-cookie") { + data.headers.append(key, value); + } else { + data.headers.set(key, value); + } }); } return data; @@ -90,19 +94,30 @@ export function toResponse(data?: any, init?: ResponseInit): Response { } const headers = new Headers(); if (routerResponse?.headers) { - const headers = new Headers(routerResponse.headers); - for (const [key, value] of headers.entries()) { - headers.set(key, value); + for (const [key, value] of new Headers(routerResponse.headers).entries()) { + if (key.toLowerCase() === "set-cookie") { + headers.append(key, value); + } else { + headers.set(key, value); + } } } if (data.headers) { for (const [key, value] of new Headers(data.headers).entries()) { - headers.set(key, value); + if (key.toLowerCase() === "set-cookie") { + headers.append(key, value); + } else { + headers.set(key, value); + } } } if (init?.headers) { for (const [key, value] of new Headers(init.headers).entries()) { - headers.set(key, value); + if (key.toLowerCase() === "set-cookie") { + headers.append(key, value); + } else { + headers.set(key, value); + } } }