From bfe25fe74741653a6e0271b26cd1b7104b7e6f60 Mon Sep 17 00:00:00 2001 From: Calvin Tsang Date: Wed, 17 Sep 2025 12:00:16 +0800 Subject: [PATCH 1/2] fix(miniflare): api proxy preserve multiple Set-Cookie headers --- .changeset/hungry-crews-tap.md | 5 ++++ .../miniflare/src/workers/core/devalue.ts | 4 +-- .../test/workers/core/serialize.spec.ts | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 .changeset/hungry-crews-tap.md create mode 100644 packages/miniflare/test/workers/core/serialize.spec.ts diff --git a/.changeset/hungry-crews-tap.md b/.changeset/hungry-crews-tap.md new file mode 100644 index 00000000000..c4376be1653 --- /dev/null +++ b/.changeset/hungry-crews-tap.md @@ -0,0 +1,5 @@ +--- +"miniflare": patch +--- + +fix: api proxy preserve multiple Set-Cookie headers diff --git a/packages/miniflare/src/workers/core/devalue.ts b/packages/miniflare/src/workers/core/devalue.ts index 495b0fdf32b..74e6367224f 100644 --- a/packages/miniflare/src/workers/core/devalue.ts +++ b/packages/miniflare/src/workers/core/devalue.ts @@ -134,7 +134,7 @@ export function createHTTPReducers( ): ReducersRevivers { return { Headers(val) { - if (val instanceof impl.Headers) return Object.fromEntries(val); + if (val instanceof impl.Headers) return [...val.entries()]; }, Request(val) { if (val instanceof impl.Request) { @@ -154,7 +154,7 @@ export function createHTTPRevivers( return { Headers(value) { assert(typeof value === "object" && value !== null); - return new impl.Headers(value as Record); + return new impl.Headers(value as string[][]); }, Request(value) { assert(Array.isArray(value)); diff --git a/packages/miniflare/test/workers/core/serialize.spec.ts b/packages/miniflare/test/workers/core/serialize.spec.ts new file mode 100644 index 00000000000..e40b785dc8b --- /dev/null +++ b/packages/miniflare/test/workers/core/serialize.spec.ts @@ -0,0 +1,25 @@ +import test from "ava"; +import { parse, stringify } from "devalue"; +import { createHTTPReducers, createHTTPRevivers } from "miniflare"; +import { NODE_PLATFORM_IMPL } from "../../../src/plugins/core/proxy/types"; + +test("serialize Headers object consisting of multiple Set-Cookie headers", (t) => { + const impl = NODE_PLATFORM_IMPL; + + const headers = new impl.Headers([ + ["content-type", "application/json"], + ["authorization", "Bearer token"], + ]); + headers.append("Set-Cookie", "cookie1=value_for_cookie_1; Path=/; HttpOnly;"); + headers.append("Set-Cookie", "cookie2=value_for_cookie_2; Path=/; HttpOnly;"); + + const serialized = stringify(headers, createHTTPReducers(impl)); + const deserialized = parse(serialized, createHTTPRevivers(impl)); + t.true(deserialized instanceof impl.Headers); + t.is(deserialized.get("content-type"), "application/json"); + t.is(deserialized.get("authorization"), "Bearer token"); + t.deepEqual(deserialized.getSetCookie(), [ + "cookie1=value_for_cookie_1; Path=/; HttpOnly;", + "cookie2=value_for_cookie_2; Path=/; HttpOnly;", + ]); +}); From 740647a4f35bba5cab596cada05ba5d4ea0471af Mon Sep 17 00:00:00 2001 From: Enchan Date: Thu, 5 Jun 2025 00:30:27 +0900 Subject: [PATCH 2/2] [miniflare] custom serialization for RegExp object (#9454) * feat: encode pattern when serialize RegExp object * test: check if test suites can be filtered by pattern includes non-ascii string * chore: remove unneeded comment * test: serialize RegExp object * docs: add changeset for vitest-pool-workers to describe fix --- .../miniflare/src/workers/core/devalue.ts | 15 +++++++++++ .../test/workers/core/serialize.spec.ts | 27 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/miniflare/src/workers/core/devalue.ts b/packages/miniflare/src/workers/core/devalue.ts index 74e6367224f..4255e36e54f 100644 --- a/packages/miniflare/src/workers/core/devalue.ts +++ b/packages/miniflare/src/workers/core/devalue.ts @@ -60,6 +60,13 @@ export const structuredSerializableReducers: ReducersRevivers = { ]; } }, + RegExp(value) { + if (value instanceof RegExp) { + const { source, flags } = value; + const encoded = Buffer.from(source).toString("base64"); + return flags ? ["RegExp", encoded, flags] : ["RegExp", encoded]; + } + }, Error(value) { for (const ctor of ALLOWED_ERROR_CONSTRUCTORS) { if (value instanceof ctor && value.name === ctor.name) { @@ -97,6 +104,14 @@ export const structuredSerializableRevivers: ReducersRevivers = { if ("BYTES_PER_ELEMENT" in ctor) length /= ctor.BYTES_PER_ELEMENT; return new ctor(buffer as ArrayBuffer, byteOffset, length); }, + RegExp(value) { + assert(Array.isArray(value)); + const [name, encoded, flags] = value; + assert(typeof name === "string"); + assert(typeof encoded === "string"); + const source = Buffer.from(encoded, "base64").toString("utf-8"); + return new RegExp(source, flags); + }, Error(value) { assert(Array.isArray(value)); const [name, message, stack, cause] = value as unknown[]; diff --git a/packages/miniflare/test/workers/core/serialize.spec.ts b/packages/miniflare/test/workers/core/serialize.spec.ts index e40b785dc8b..aafa9fc9d8d 100644 --- a/packages/miniflare/test/workers/core/serialize.spec.ts +++ b/packages/miniflare/test/workers/core/serialize.spec.ts @@ -1,6 +1,11 @@ import test from "ava"; import { parse, stringify } from "devalue"; -import { createHTTPReducers, createHTTPRevivers } from "miniflare"; +import { + createHTTPReducers, + createHTTPRevivers, + structuredSerializableReducers, + structuredSerializableRevivers, +} from "miniflare"; import { NODE_PLATFORM_IMPL } from "../../../src/plugins/core/proxy/types"; test("serialize Headers object consisting of multiple Set-Cookie headers", (t) => { @@ -23,3 +28,23 @@ test("serialize Headers object consisting of multiple Set-Cookie headers", (t) = "cookie2=value_for_cookie_2; Path=/; HttpOnly;", ]); }); + +test("serialize RegExp object consisting of only ascii chars", (t) => { + const input = new RegExp(/HelloWorld/); + + const serialized = stringify(input, structuredSerializableReducers); + t.is(serialized, '[["RegExp",1],[2,3],"RegExp","SGVsbG9Xb3JsZA=="]'); + + const deserialized = parse(serialized, structuredSerializableRevivers); + t.deepEqual(deserialized, input); +}); + +test("serialize RegExp object containing non-ascii chars", (t) => { + const input = new RegExp(/こんにちは/); + + const serialized = stringify(input, structuredSerializableReducers); + t.is(serialized, '[["RegExp",1],[2,3],"RegExp","44GT44KT44Gr44Gh44Gv"]'); + + const deserialized = parse(serialized, structuredSerializableRevivers); + t.deepEqual(deserialized, input); +});