From 54c18260dfa4ca7a4bb9ac5c9390f96ea58a3d62 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 00:19:10 +0000 Subject: [PATCH 1/4] docs(core): stop showing `flattenStandardHeader` on `set-cookie` The header helpers example flattened `set-cookie` into `'a=1, b=2'`. Copied into an adapter that writes one value per header, that sends a single folded Set-Cookie line, which browsers parse as one cookie, so attributes such as HttpOnly can be lost and later cookies dropped. Show the helper on a multi-value `vary` header instead, warn that it must not be used for `set-cookie`, and add a JSDoc note on the helper. A README regression test keeps the docs from flattening `set-cookie` again. Runtime behavior is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016RHRvK3Kt3oPLgn8Tmk85r --- packages/core/README.md | 12 +++++++----- packages/core/src/readme.test.ts | 11 +++++++++++ packages/core/src/utils.ts | 5 +++++ 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/readme.test.ts diff --git a/packages/core/README.md b/packages/core/README.md index aab769d2..670d219a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -304,19 +304,21 @@ const filename = getFilenameFromContentDisposition(disposition) import { flattenStandardHeader, mergeStandardHeaders } from '@standard-server/core' const headers = mergeStandardHeaders( - { 'accept': 'application/json', 'set-cookie': ['a=1'] }, - { 'set-cookie': 'b=2', 'vary': 'accept', 'warning': undefined }, + { 'accept': 'application/json', 'set-cookie': ['a=1'], 'vary': 'accept' }, + { 'set-cookie': 'b=2', 'vary': 'accept-encoding', 'warning': undefined }, ) // { // accept: 'application/json', // 'set-cookie': ['a=1', 'b=2'], -// vary: 'accept', +// vary: ['accept', 'accept-encoding'], // } -const cookieHeader = flattenStandardHeader(headers['set-cookie']) -// 'a=1, b=2' +const varyHeader = flattenStandardHeader(headers['vary']) +// 'accept, accept-encoding' ``` +`flattenStandardHeader()` joins values with `, ` like `Headers.get()`, so never use it for `set-cookie`: each `Set-Cookie` value must be sent as its own header line, the way `toLambdaHeaders()` in `@standard-server/aws-lambda` keeps them in a separate `cookies` list. + ### URL parsing `parseStandardUrl()` splits a `StandardUrl` into `[pathname, search, hash]` without requiring a full origin. diff --git a/packages/core/src/readme.test.ts b/packages/core/src/readme.test.ts new file mode 100644 index 00000000..3bae69b8 --- /dev/null +++ b/packages/core/src/readme.test.ts @@ -0,0 +1,11 @@ +import { readFile } from 'node:fs/promises' + +it('readme never flattens set-cookie', async () => { + const readme = await readFile(new URL('../README.md', import.meta.url), 'utf8') + const calls = [...readme.matchAll(/```[\s\S]*?```/g)].flatMap( + ([block]) => block.match(/flattenStandardHeader\([^)]*\)/g) ?? [], + ) + + expect(calls.length).toBeGreaterThan(0) + expect(calls.filter((call) => /set-cookie/i.test(call))).toEqual([]) +}) diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 1e6252c8..12d74741 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -73,6 +73,11 @@ export function getFilenameFromContentDisposition(contentDisposition: string): s return getContentDispositionParam(contentDisposition, 'filename') } +/** + * Join a header's values into one string with `, `, like `Headers.get()`. + * Not safe for `set-cookie`: a folded line corrupts the cookies, + * so send each value as its own header line instead. + */ export function flattenStandardHeader( header: string | readonly string[] | undefined, ): string | undefined { From 2060d64f4be3c0ede752a840121362a1e032631e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 01:52:08 +0000 Subject: [PATCH 2/4] docs(core): tighten the `flattenStandardHeader` set-cookie warning Move the README warning into a WARNING callout like the rest of the README, and link to the AWS Lambda adapter's docs instead of restating how `toLambdaHeaders()` handles cookies. Shorten the JSDoc to two lines to match its neighbours. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016RHRvK3Kt3oPLgn8Tmk85r --- packages/core/README.md | 3 ++- packages/core/src/utils.ts | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/README.md b/packages/core/README.md index 670d219a..427a7f9a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -317,7 +317,8 @@ const varyHeader = flattenStandardHeader(headers['vary']) // 'accept, accept-encoding' ``` -`flattenStandardHeader()` joins values with `, ` like `Headers.get()`, so never use it for `set-cookie`: each `Set-Cookie` value must be sent as its own header line, the way `toLambdaHeaders()` in `@standard-server/aws-lambda` keeps them in a separate `cookies` list. +> [!WARNING] +> `flattenStandardHeader()` joins values with `, ` like `Headers.get()`, so never use it for `set-cookie`: each cookie must be sent as its own header line, as the [AWS Lambda adapter](https://github.com/middleapi/standard-server/blob/main/packages/aws-lambda/README.md#lambda-behavior-to-be-aware-of) does. ### URL parsing diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 12d74741..ad91f7a9 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -74,9 +74,8 @@ export function getFilenameFromContentDisposition(contentDisposition: string): s } /** - * Join a header's values into one string with `, `, like `Headers.get()`. - * Not safe for `set-cookie`: a folded line corrupts the cookies, - * so send each value as its own header line instead. + * Join a header's values with `, `, like `Headers.get()`. + * Not safe for `set-cookie`: each cookie must stay its own header line. */ export function flattenStandardHeader( header: string | readonly string[] | undefined, From 7983ec109db3e7af6782838253094e07021402f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 01:56:19 +0000 Subject: [PATCH 3/4] docs(core): drop the `flattenStandardHeader` set-cookie warning Remove the README callout and the JSDoc note; the example already shows the helper on `vary` instead of `set-cookie`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016RHRvK3Kt3oPLgn8Tmk85r --- packages/core/README.md | 3 --- packages/core/src/utils.ts | 4 ---- 2 files changed, 7 deletions(-) diff --git a/packages/core/README.md b/packages/core/README.md index 427a7f9a..424d437a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -317,9 +317,6 @@ const varyHeader = flattenStandardHeader(headers['vary']) // 'accept, accept-encoding' ``` -> [!WARNING] -> `flattenStandardHeader()` joins values with `, ` like `Headers.get()`, so never use it for `set-cookie`: each cookie must be sent as its own header line, as the [AWS Lambda adapter](https://github.com/middleapi/standard-server/blob/main/packages/aws-lambda/README.md#lambda-behavior-to-be-aware-of) does. - ### URL parsing `parseStandardUrl()` splits a `StandardUrl` into `[pathname, search, hash]` without requiring a full origin. diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index ad91f7a9..1e6252c8 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -73,10 +73,6 @@ export function getFilenameFromContentDisposition(contentDisposition: string): s return getContentDispositionParam(contentDisposition, 'filename') } -/** - * Join a header's values with `, `, like `Headers.get()`. - * Not safe for `set-cookie`: each cookie must stay its own header line. - */ export function flattenStandardHeader( header: string | readonly string[] | undefined, ): string | undefined { From b7d62891aaf47072ce68bfccb8171e01cd233079 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:31:20 +0000 Subject: [PATCH 4/4] test(core): drop the README regression test Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016RHRvK3Kt3oPLgn8Tmk85r --- packages/core/src/readme.test.ts | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 packages/core/src/readme.test.ts diff --git a/packages/core/src/readme.test.ts b/packages/core/src/readme.test.ts deleted file mode 100644 index 3bae69b8..00000000 --- a/packages/core/src/readme.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { readFile } from 'node:fs/promises' - -it('readme never flattens set-cookie', async () => { - const readme = await readFile(new URL('../README.md', import.meta.url), 'utf8') - const calls = [...readme.matchAll(/```[\s\S]*?```/g)].flatMap( - ([block]) => block.match(/flattenStandardHeader\([^)]*\)/g) ?? [], - ) - - expect(calls.length).toBeGreaterThan(0) - expect(calls.filter((call) => /set-cookie/i.test(call))).toEqual([]) -})