From e3b8b84a247b8873d2443bcfdd23cf7615078f4b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 28 Nov 2023 14:35:43 +0100 Subject: [PATCH] feat(nuxt): allow readonly option for `useCookie` (#24503) --- docs/3.api/2.composables/use-cookie.md | 4 ++++ packages/nuxt/src/app/composables/cookie.ts | 11 +++++++---- test/fixtures/basic-types/types.ts | 5 +++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/3.api/2.composables/use-cookie.md b/docs/3.api/2.composables/use-cookie.md index a218fd7761..33d9e61600 100644 --- a/docs/3.api/2.composables/use-cookie.md +++ b/docs/3.api/2.composables/use-cookie.md @@ -133,6 +133,10 @@ be returned as the cookie's value. Specifies a function that returns the cookie's default value. The function can also return a `Ref`. +### `readonly` + +Allows _accessing_ a cookie value without the ability to set it. + ### `watch` Specifies the `boolean` or `string` value for [watch](https://vuejs.org/api/reactivity-core.html#watch) cookie ref data. diff --git a/packages/nuxt/src/app/composables/cookie.ts b/packages/nuxt/src/app/composables/cookie.ts index 532e28bd85..15c96c6f40 100644 --- a/packages/nuxt/src/app/composables/cookie.ts +++ b/packages/nuxt/src/app/composables/cookie.ts @@ -16,6 +16,7 @@ export interface CookieOptions extends _CookieOptions { encode?(value: T): string default?: () => T | Ref watch?: boolean | 'shallow' + readonly?: boolean } export interface CookieRef extends Ref {} @@ -27,6 +28,8 @@ const CookieDefaults = { encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val)) } satisfies CookieOptions +export function useCookie (name: string, _opts?: CookieOptions & { readonly?: false }): CookieRef +export function useCookie (name: string, _opts: CookieOptions & { readonly: true }): Readonly> export function useCookie (name: string, _opts?: CookieOptions): CookieRef { const opts = { ...CookieDefaults, ..._opts } const cookies = readRawCookies(opts) || {} @@ -55,6 +58,7 @@ export function useCookie (name: string, _opts?: if (import.meta.client) { const channel = typeof BroadcastChannel === 'undefined' ? null : new BroadcastChannel(`nuxt:cookies:${name}`) const callback = () => { + if (opts.readonly || isEqual(cookie.value, cookies[name])) { return } writeClientCookie(name, cookie.value, opts as CookieSerializeOptions) channel?.postMessage(opts.encode(cookie.value as T)) } @@ -72,7 +76,7 @@ export function useCookie (name: string, _opts?: if (channel) { channel.onmessage = (event) => { watchPaused = true - cookie.value = opts.decode(event.data) + cookies[name] = cookie.value = opts.decode(event.data) nextTick(() => { watchPaused = false }) } } @@ -89,9 +93,8 @@ export function useCookie (name: string, _opts?: } else if (import.meta.server) { const nuxtApp = useNuxtApp() const writeFinalCookieValue = () => { - if (!isEqual(cookie.value, cookies[name])) { - writeServerCookie(useRequestEvent(nuxtApp), name, cookie.value, opts as CookieOptions) - } + if (opts.readonly || isEqual(cookie.value, cookies[name])) { return } + writeServerCookie(useRequestEvent(nuxtApp), name, cookie.value, opts as CookieOptions) } const unhook = nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue) nuxtApp.hooks.hookOnce('app:error', () => { diff --git a/test/fixtures/basic-types/types.ts b/test/fixtures/basic-types/types.ts index 7f8127dd33..a65bdd6d09 100644 --- a/test/fixtures/basic-types/types.ts +++ b/test/fixtures/basic-types/types.ts @@ -346,6 +346,11 @@ describe('composables', () => { expectTypeOf(useFetch('/test', { default: () => 500 }).data).toEqualTypeOf>() }) + it('enforces readonly cookies', () => { + // @ts-expect-error readonly cookie + useCookie('test', { readonly: true }).value = 'thing' + }) + it('correct types when using ResT type-assertion with default function', () => { // @ts-expect-error default type should match generic type useFetch('/test', { default: () => 0 })