Skip to content

Commit

Permalink
fix(cookies): align RequestCookies with ResponseCookies (#187)
Browse files Browse the repository at this point in the history
* feat(cookies): align `RequestCookies` with ResponseCookies

* add changeset

* update docs with cookie methods

* re-add some doc

* revert formatting
  • Loading branch information
balazsorban44 authored Oct 24, 2022
1 parent d09eaf4 commit 473831f
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-sloths-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@edge-runtime/cookies': patch
---

Align `RequestCookies` API with `ResponseCookies`
18 changes: 10 additions & 8 deletions docs/pages/packages/cookies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ Notes:

#### Available methods

- `cookies.get()` returns the value of the cookie. The cookie configuration
(`Max-Age`, `Path` etc) is not being passed by the HTTP client, therefore it's
not possible to determine the cookie expiration date.
- `get` - A method that takes a cookie `name` and returns an object with `name` and `value`. If a cookie with `name` isn't found, it returns `undefined`. If multiple cookies match, it will only return the first match. The cookie configuration (Max-Age, Path etc) is not being passed by the HTTP client, therefore it's not possible to determine the cookie expiration date.
- `getAll` - A method that is similar to `get`, but returns a list of all the cookies with a matching `name`. If `name` is unspecified, it returns all the available cookies.
- `set` - A method that takes an object with properties of `CookieListItem` as defined in the [W3C CookieStore API](https://wicg.github.io/cookie-store/#dictdef-cookielistitem) spec.
- `delete` - A method that takes either a cookie `name` or a list of names. and removes the cookies matching the name(s). Returns `true` for deleted and `false` for undeleted cookies.
- `has` - A method that takes a cookie `name` and returns a `boolean` based on if the cookie exists (`true`) or not (`false`).
- `clear` - A method that takes no argument and will effectively remove the `Cookie` header.

### for Response

Expand All @@ -84,8 +87,7 @@ Notes:

#### Available methods

- `cookies.set()` sets a cookie with the provided name and value. The cookie
configuration (`Max-Age`, `Path` etc) is passed as an optional argument.

- `cookies.delete()` deletes a cookie with the provided name by setting an empty
value with an expiration date in the past.
- `get` - A method that takes a cookie `name` and returns an object with `name` and `value`. If a cookie with `name` isn't found, it returns `undefined`. If multiple cookies match, it will only return the first match.
- `getAll` - A method that is similar to `get`, but returns a list of all the cookies with a matching `name`. If `name` is unspecified, it returns all the available cookies.
- `set` - A method that takes an object with properties of `CookieListItem` as defined in the [W3C CookieStore API](https://wicg.github.io/cookie-store/#dictdef-cookielistitem) spec.
- `delete` - A method that takes either a cookie `name` or a list of names. and removes the cookies matching the name(s). Returns `true` for deleted and `false` for undeleted cookies.
20 changes: 12 additions & 8 deletions packages/cookies/src/request-cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ export class RequestCookies {
this.#headers = requestHeaders
}

#cache = cached((header: string | null) => {
#cache = cached((header: string | null): Map<string, RequestCookie> => {
const parsed = header ? parseCookieString(header) : new Map()
return parsed
const cached = new Map()
for (const [name, value] of parsed) {
cached.set(name, { name, value })
}
return cached
})

#parsed(): Map<string, string> {
#parsed(): Map<string, RequestCookie> {
const header = this.#headers.get('cookie')
return this.#cache(header)
}
Expand All @@ -41,11 +45,11 @@ export class RequestCookies {
getAll(...args: [name: string] | [RequestCookie] | []) {
const all = Array.from(this.#parsed())
if (!args.length) {
return all
return all.map(([_, value]) => value)
}

const name = typeof args[0] === 'string' ? args[0] : args[0]?.name
return all.filter(([n]) => n === name)
return all.filter(([n]) => n === name).map(([_, value]) => value)
}

has(name: string) {
Expand All @@ -57,12 +61,12 @@ export class RequestCookies {
args.length === 1 ? [args[0].name, args[0].value] : args

const map = this.#parsed()
map.set(name, value)
map.set(name, { name, value })

this.#headers.set(
'cookie',
Array.from(map)
.map(([name, value]) => serialize({ name, value }))
.map(([_, value]) => serialize(value))
.join('; ')
)
return this
Expand All @@ -82,7 +86,7 @@ export class RequestCookies {
this.#headers.set(
'cookie',
Array.from(map)
.map(([name, value]) => serialize({ name, value }))
.map(([_, value]) => serialize(value))
.join('; ')
)
return result
Expand Down
34 changes: 18 additions & 16 deletions packages/cookies/test/request-cookies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@ describe('input parsing', () => {
test('single element', () => {
const headers = requestHeadersWithCookies('a=1')
const cookies = new RequestCookies(headers)
expect([...cookies]).toEqual([['a', '1']])
expect(cookies.get('a')).toEqual({ name: 'a', value: '1' })
expect(cookies.getAll()).toEqual([{ name: 'a', value: '1' }])
})
test('multiple elements', () => {
const headers = requestHeadersWithCookies('a=1; b=2')
const cookies = new RequestCookies(headers)
expect([...cookies]).toEqual([
['a', '1'],
['b', '2'],
expect(cookies.getAll('a')).toEqual([{ name: 'a', value: '1' }])
expect(cookies.getAll()).toEqual([
{ name: 'a', value: '1' },
{ name: 'b', value: '2' },
])
})
test('multiple elements followed by a semicolon', () => {
const headers = requestHeadersWithCookies('a=1; b=2;')
const cookies = new RequestCookies(headers)
expect([...cookies]).toEqual([
['a', '1'],
['b', '2'],
expect(cookies.getAll()).toEqual([
{ name: 'a', value: '1' },
{ name: 'b', value: '2' },
])
})
})
Expand All @@ -30,27 +32,27 @@ test('updating a cookie', () => {

const cookies = new RequestCookies(headers)
cookies.set('b', 'hello!')
expect([...cookies]).toEqual([
['a', '1'],
['b', 'hello!'],
expect(cookies.getAll()).toEqual([
{ name: 'a', value: '1' },
{ name: 'b', value: 'hello!' },
])
})

test('deleting a cookie', () => {
const headers = requestHeadersWithCookies('a=1; b=2')
const cookies = new RequestCookies(headers)
cookies.delete('b')
expect([...cookies]).toEqual([['a', '1']])
expect(cookies.getAll()).toEqual([{ name: 'a', value: '1' }])
})

test('adding a cookie', () => {
const headers = requestHeadersWithCookies('a=1; b=2')
const cookies = new RequestCookies(headers)
cookies.set('c', '3')
expect([...cookies]).toEqual([
['a', '1'],
['b', '2'],
['c', '3'],
expect(cookies.getAll()).toEqual([
{ name: 'a', value: '1' },
{ name: 'b', value: '2' },
{ name: 'c', value: '3' },
])
})

Expand All @@ -61,7 +63,7 @@ test('formatting with @edge-runtime/format', () => {
const format = createFormat()
const result = format(cookies)
expect(result).toMatchInlineSnapshot(
`"RequestCookies {\\"a\\":\\"1\\",\\"b\\":\\"2\\"}"`
`"RequestCookies {\\"a\\":{\\"name\\":\\"a\\",\\"value\\":\\"1\\"},\\"b\\":{\\"name\\":\\"b\\",\\"value\\":\\"2\\"}}"`
)
})

Expand Down

1 comment on commit 473831f

@vercel
Copy link

@vercel vercel bot commented on 473831f Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

edge-runtime – ./

edge-runtime.vercel.app
edge-runtime.vercel.sh
edge-runtime-git-main.vercel.sh

Please sign in to comment.