diff --git a/http/cookie.ts b/http/cookie.ts index 91a2d14d8126..98d9fc2fe1df 100644 --- a/http/cookie.ts +++ b/http/cookie.ts @@ -233,6 +233,12 @@ function validateDomain(domain: string) { /** * Parse cookies of a header * + * The returned object has a null prototype (so it does not inherit from + * `Object.prototype` — properties like `toString` or `hasOwnProperty` will be + * `undefined` rather than the inherited methods). The return type is + * `Partial>` to reflect that arbitrary key access may + * yield `undefined` when no cookie of that name was set. + * * @example Usage * ```ts * import { getCookies } from "@std/http/cookie"; @@ -242,15 +248,21 @@ function validateDomain(domain: string) { * headers.set("Cookie", "full=of; tasty=chocolate"); * * const cookies = getCookies(headers); - * assertEquals(cookies, { full: "of", tasty: "chocolate" }); + * assertEquals(cookies.full, "of"); + * assertEquals(cookies.tasty, "chocolate"); + * assertEquals(cookies.missing, undefined); * ``` * * @param headers The headers instance to get cookies from - * @return Object with cookie names as keys + * @return Object with cookie names as keys (null-prototype). Values are + * typed as `string | undefined` so missing-key access is caught at compile + * time. */ -export function getCookies(headers: Headers): Record { +export function getCookies( + headers: Headers, +): Partial> { const cookie = headers.get("Cookie"); - const out: Record = Object.create(null); + const out: Partial> = Object.create(null); if (cookie !== null) { const c = cookie.split(";"); for (const kv of c) { diff --git a/http/cookie_test.ts b/http/cookie_test.ts index 72f5eecacb2c..fac04877a33d 100644 --- a/http/cookie_test.ts +++ b/http/cookie_test.ts @@ -45,9 +45,12 @@ Deno.test({ fn() { const headers = new Headers([["Cookie", "foo=bar"]]); const cookies = getCookies(headers); - assertType>>(true); - // allowed due to `noUncheckedIndexedAccess` - const baz = cookies.baz as undefined; + // Return type is Partial> so arbitrary key access + // surfaces as `string | undefined` regardless of `noUncheckedIndexedAccess`. + // Closes the type/runtime gap reported in #6852 and #7053 — at runtime the + // returned object is null-prototyped, so missing keys really are undefined. + assertType>>>(true); + const baz: string | undefined = cookies.baz; assertEquals(baz, undefined); assertEquals(Object.getPrototypeOf(cookies), null); assertEquals(cookies.toString, undefined);