From 89190284ce31aed5e84d22939ee938584883bd6d Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 25 Mar 2026 20:59:09 +0100 Subject: [PATCH 1/4] fix: clone request instead of mutating --- packages/start/src/server/server-functions-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/start/src/server/server-functions-handler.ts b/packages/start/src/server/server-functions-handler.ts index 0ba1dd0bd..7a2ceebce 100644 --- a/packages/start/src/server/server-functions-handler.ts +++ b/packages/start/src/server/server-functions-handler.ts @@ -200,7 +200,7 @@ function createSingleFlightHeaders(sourceEvent: FetchEvent) { // unclear if h3 internals are available on all platforms but we need a way to // update request headers on the underlying H3 event. - const headers = sourceEvent.request.headers; + const headers = new Headers(sourceEvent.request.headers); const cookies = parseCookies(sourceEvent.nativeEvent); const SetCookies = sourceEvent.response.headers.getSetCookie(); headers.delete("cookie"); From 9bf6646b0f1be8ed34e9008b576b6a7a50fb8ec1 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 25 Mar 2026 21:04:10 +0100 Subject: [PATCH 2/4] add tests --- .../server/server-functions-handler.spec.ts | 83 +++++++++++++++++++ .../src/server/server-functions-handler.ts | 2 +- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 packages/start/src/server/server-functions-handler.spec.ts diff --git a/packages/start/src/server/server-functions-handler.spec.ts b/packages/start/src/server/server-functions-handler.spec.ts new file mode 100644 index 000000000..eab39e860 --- /dev/null +++ b/packages/start/src/server/server-functions-handler.spec.ts @@ -0,0 +1,83 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; +import type { FetchEvent } from "./types.ts"; + +vi.mock("h3", () => ({ + parseCookies: vi.fn(() => ({})), + parseSetCookie: vi.fn(), +})); + +vi.mock("solid-js/web", () => ({ + renderToString: vi.fn(), + provideRequestEvent: vi.fn((_event, fn) => fn()), +})); + +vi.mock("solidstart:server-fn-manifest", () => ({ + getServerFnById: vi.fn(), +})); + +vi.mock("./handler.ts", () => ({ + createPageEvent: vi.fn(), +})); + +vi.mock("./fetchEvent.ts", () => ({ + getFetchEvent: vi.fn(), + mergeResponseHeaders: vi.fn(), +})); + +function createMockFetchEvent(headers: Record = {}): FetchEvent { + return { + request: new Request("http://localhost/test", { headers }), + response: { + headers: { + getSetCookie: () => [], + }, + }, + nativeEvent: {}, + } as FetchEvent; +} + +describe("createSingleFlightHeaders", () => { + let createSingleFlightHeaders: (sourceEvent: FetchEvent) => Headers; + + beforeEach(async () => { + vi.clearAllMocks(); + const module = await import("./server-functions-handler.ts"); + createSingleFlightHeaders = module.createSingleFlightHeaders; + }); + + it("should create a new Headers object instead of returning the original", () => { + const sourceEvent = createMockFetchEvent({ + "content-type": "application/json", + }); + + const result = createSingleFlightHeaders(sourceEvent); + + expect(result).not.toBe(sourceEvent.request.headers); + }); + + it("should not mutate the original request headers", () => { + const originalHeaders = new Headers({ + "content-type": "application/json", + "cookie": "session=abc123", + "cf-ray": "abc123", + "cf-cache-status": "HIT", + }); + const sourceEvent: FetchEvent = { + request: new Request("http://localhost/test", { headers: originalHeaders }), + response: { + headers: { + getSetCookie: () => [], + }, + }, + nativeEvent: {}, + } as FetchEvent; + + const originalCookieHeader = sourceEvent.request.headers.get("cookie"); + const originalCfRay = sourceEvent.request.headers.get("cf-ray"); + + createSingleFlightHeaders(sourceEvent); + + expect(sourceEvent.request.headers.get("cookie")).toBe(originalCookieHeader); + expect(sourceEvent.request.headers.get("cf-ray")).toBe(originalCfRay); + }); +}); diff --git a/packages/start/src/server/server-functions-handler.ts b/packages/start/src/server/server-functions-handler.ts index 7a2ceebce..3da37fd90 100644 --- a/packages/start/src/server/server-functions-handler.ts +++ b/packages/start/src/server/server-functions-handler.ts @@ -195,7 +195,7 @@ function handleNoJS(result: any, request: Request, parsed: any[], thrown?: boole } let App: any; -function createSingleFlightHeaders(sourceEvent: FetchEvent) { +export function createSingleFlightHeaders(sourceEvent: FetchEvent) { // cookie handling logic is pretty simplistic so this might be imperfect // unclear if h3 internals are available on all platforms but we need a way to // update request headers on the underlying H3 event. From cc9eb685edbd91499f201b8e2f0c5d537d384c42 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 25 Mar 2026 21:09:19 +0100 Subject: [PATCH 3/4] add changeset --- .changeset/funny-pandas-clean.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/funny-pandas-clean.md diff --git a/.changeset/funny-pandas-clean.md b/.changeset/funny-pandas-clean.md new file mode 100644 index 000000000..5c5c06007 --- /dev/null +++ b/.changeset/funny-pandas-clean.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +fix: clone request headers in single-flight to avoid mutating immutable headers From 03f4b344a018fbad73b0e8fcdade3c3f2158f50a Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Thu, 26 Mar 2026 14:39:41 +0100 Subject: [PATCH 4/4] fix types on test / remove types from tsc check --- packages/start/src/server/server-functions-handler.spec.ts | 2 ++ packages/start/tsconfig.json | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/start/src/server/server-functions-handler.spec.ts b/packages/start/src/server/server-functions-handler.spec.ts index eab39e860..0016ff990 100644 --- a/packages/start/src/server/server-functions-handler.spec.ts +++ b/packages/start/src/server/server-functions-handler.spec.ts @@ -33,6 +33,7 @@ function createMockFetchEvent(headers: Record = {}): FetchEvent }, }, nativeEvent: {}, + locals: {}, } as FetchEvent; } @@ -70,6 +71,7 @@ describe("createSingleFlightHeaders", () => { }, }, nativeEvent: {}, + locals: {}, } as FetchEvent; const originalCookieHeader = sourceEvent.request.headers.get("cookie"); diff --git a/packages/start/tsconfig.json b/packages/start/tsconfig.json index f0939803d..e2ce74e9d 100644 --- a/packages/start/tsconfig.json +++ b/packages/start/tsconfig.json @@ -20,4 +20,5 @@ "rootDir": "./src", }, "include": ["env.d.ts", "./src", "./src/env.d.ts"], + "exclude": ["**/*.spec.ts"] }