[PR #761] [MERGED] fix: ResponseCookies deduplicate Set-Cookie headers and add missing API surface #824

Closed
opened 2026-05-06 13:10:19 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/761
Author: @NathanDrake2406
Created: 4/3/2026
Status: Merged
Merged: 4/3/2026
Merged by: @james-elicx

Base: mainHead: fix/response-cookies-correctness


📝 Commits (3)

  • 9a2338f fix: ResponseCookies deduplicate Set-Cookie headers and add missing API surface
  • f0a1979 fix: only forward path/domain in ResponseCookies.delete()
  • 6dce232 fix: forward httpOnly/secure/sameSite in ResponseCookies.delete()

📊 Changes

2 files changed (+322 additions, -69 deletions)

View changed files

📝 packages/vinext/src/shims/server.ts (+93 -67)
📝 tests/shims.test.ts (+229 -2)

📄 Description

Summary

  • Bug 1 (critical): ResponseCookies.set() unconditionally appended Set-Cookie headers — setting the same cookie name twice produced duplicate headers, and get() returned the stale first match. Rewritten to use an internal Map<name, {serialized, entry}> as the single source of truth (matching @edge-runtime/cookies's architecture). The _syncHeaders() method deletes all Set-Cookie headers and re-appends from the map on every mutation.
  • Bug 2: Added object form for set({ name, value, httpOnly, ... }) matching Next.js docs and middleware patterns.
  • Bug 3: Added name filter parameter to getAll(name?) and getAll({ name }).
  • Bug 4: Added object form to delete({ name, path, domain }) with path/domain propagation.
  • Constructor now hydrates the internal map from existing Set-Cookie headers.
  • get() accepts object form get({ name }) matching edge-runtime.
  • delete() now uses Expires=epoch (matching edge-runtime) instead of Max-Age=0.
  • delete() now forwards httpOnly, secure, and sameSite attributes (matching edge-runtime — browsers need matching Secure to actually delete a cookie).
  • Behavioral change: set() now defaults Path=/ when no path option is given (previously omitted Path entirely). This matches @edge-runtime/cookies's normalizeCookie behavior.

Test plan

  • Test: set same cookie name twice → only one Set-Cookie header, get() returns latest
  • Test: set replaces only matching cookie, preserves others
  • Test: set({ name, value, httpOnly }) object form works
  • Test: object form replaces existing cookie of same name
  • Test: getAll("name") filters correctly
  • Test: getAll({ name }) object form filters correctly
  • Test: getAll(name) returns empty array for non-existent cookie
  • Test: delete({ name, path, domain }) produces correct Set-Cookie with matching path/domain
  • Test: delete() forwards httpOnly/secure/sameSite attributes
  • Test: delete() replaces existing cookie's Set-Cookie (no duplicates)
  • Test: constructor parses existing Set-Cookie headers
  • Test: has() works with internal map after delete()
  • Test: get({ name }) object form
  • All existing shims tests pass

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/761 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 4/3/2026 **Status:** ✅ Merged **Merged:** 4/3/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/response-cookies-correctness` --- ### 📝 Commits (3) - [`9a2338f`](https://github.com/cloudflare/vinext/commit/9a2338ff9c16a1cbba1f1302a8e0387e6a7cac60) fix: ResponseCookies deduplicate Set-Cookie headers and add missing API surface - [`f0a1979`](https://github.com/cloudflare/vinext/commit/f0a1979749a54b60e91a1b6f0c3e2a5f5c135326) fix: only forward path/domain in ResponseCookies.delete() - [`6dce232`](https://github.com/cloudflare/vinext/commit/6dce2329cb73caa51ed7433872df7f43ea5977f5) fix: forward httpOnly/secure/sameSite in ResponseCookies.delete() ### 📊 Changes **2 files changed** (+322 additions, -69 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/shims/server.ts` (+93 -67) 📝 `tests/shims.test.ts` (+229 -2) </details> ### 📄 Description ## Summary - **Bug 1 (critical):** `ResponseCookies.set()` unconditionally appended Set-Cookie headers — setting the same cookie name twice produced duplicate headers, and `get()` returned the stale first match. Rewritten to use an internal `Map<name, {serialized, entry}>` as the single source of truth (matching `@edge-runtime/cookies`'s architecture). The `_syncHeaders()` method deletes all Set-Cookie headers and re-appends from the map on every mutation. - **Bug 2:** Added object form for `set({ name, value, httpOnly, ... })` matching Next.js docs and middleware patterns. - **Bug 3:** Added name filter parameter to `getAll(name?)` and `getAll({ name })`. - **Bug 4:** Added object form to `delete({ name, path, domain })` with path/domain propagation. - Constructor now hydrates the internal map from existing Set-Cookie headers. - `get()` accepts object form `get({ name })` matching edge-runtime. - `delete()` now uses `Expires=epoch` (matching edge-runtime) instead of `Max-Age=0`. - `delete()` now forwards `httpOnly`, `secure`, and `sameSite` attributes (matching edge-runtime — browsers need matching `Secure` to actually delete a cookie). - **Behavioral change:** `set()` now defaults `Path=/` when no path option is given (previously omitted `Path` entirely). This matches `@edge-runtime/cookies`'s `normalizeCookie` behavior. ## Test plan - [x] Test: set same cookie name twice → only one Set-Cookie header, get() returns latest - [x] Test: set replaces only matching cookie, preserves others - [x] Test: set({ name, value, httpOnly }) object form works - [x] Test: object form replaces existing cookie of same name - [x] Test: getAll("name") filters correctly - [x] Test: getAll({ name }) object form filters correctly - [x] Test: getAll(name) returns empty array for non-existent cookie - [x] Test: delete({ name, path, domain }) produces correct Set-Cookie with matching path/domain - [x] Test: delete() forwards httpOnly/secure/sameSite attributes - [x] Test: delete() replaces existing cookie's Set-Cookie (no duplicates) - [x] Test: constructor parses existing Set-Cookie headers - [x] Test: has() works with internal map after delete() - [x] Test: get({ name }) object form - [x] All existing shims tests pass --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:10:19 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#824
No description provided.