From 562532778bb92418263e2e79e2ff8774e9069646 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 15 Nov 2023 20:40:55 +0100 Subject: [PATCH] fix(nuxt): deeply unwrap headers/query for `useFetch` key (#24307) --- packages/nuxt/src/app/composables/fetch.ts | 27 +++++++++++++--- test/nuxt/composables.test.ts | 37 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/nuxt/src/app/composables/fetch.ts b/packages/nuxt/src/app/composables/fetch.ts index 71f1bdd251..a18934b301 100644 --- a/packages/nuxt/src/app/composables/fetch.ts +++ b/packages/nuxt/src/app/composables/fetch.ts @@ -1,7 +1,7 @@ import type { FetchError, FetchOptions } from 'ofetch' import type { NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod as _AvailableRouterMethod } from 'nitropack' import type { MaybeRef, Ref } from 'vue' -import { computed, reactive, unref } from 'vue' +import { computed, reactive, toValue } from 'vue' import { hash } from 'ohash' import { useRequestFetch } from './ssr' @@ -86,10 +86,10 @@ export function useFetch< if (typeof r === 'function') { r = r() } - return unref(r) + return toValue(r) }) - const _key = opts.key || hash([autoKey, unref(opts.method as MaybeRef | undefined)?.toUpperCase() || 'GET', unref(opts.baseURL), typeof _request.value === 'string' ? _request.value : '', unref(opts.params || opts.query), unref(opts.headers)]) + const _key = opts.key || hash([autoKey, typeof _request.value === 'string' ? _request.value : '', ...generateOptionSegments(opts)]) if (!_key || typeof _key !== 'string') { throw new TypeError('[nuxt] [useFetch] key must be a string: ' + _key) } @@ -144,7 +144,7 @@ export function useFetch< // Use fetch with request context and headers for server direct API calls if (import.meta.server && !opts.$fetch) { - const isLocalFetch = typeof _request.value === 'string' && _request.value.startsWith('/') && (!unref(opts.baseURL) || unref(opts.baseURL)!.startsWith('/')) + const isLocalFetch = typeof _request.value === 'string' && _request.value.startsWith('/') && (!toValue(opts.baseURL) || toValue(opts.baseURL)!.startsWith('/')) if (isLocalFetch) { _$fetch = useRequestFetch() } @@ -205,3 +205,22 @@ export function useLazyFetch< // @ts-expect-error we pass an extra argument with the resolved auto-key to prevent another from being injected autoKey) } + +function generateOptionSegments <_ResT, DataT, DefaultT>(opts: UseFetchOptions<_ResT, DataT, any, DefaultT, any, any>) { + const segments: Array> = [ + toValue(opts.method as MaybeRef | undefined)?.toUpperCase() || 'GET', + toValue(opts.baseURL), + ] + for (const _obj of [opts.params || opts.query, opts.headers]) { + const obj = toValue(_obj) + if (!obj) { continue } + + const unwrapped: Record = {} + const iterator = Array.isArray(obj) ? obj : obj instanceof Headers ? obj.entries() : Object.entries(obj) + for (const [key, value] of iterator) { + unwrapped[toValue(key)] = toValue(value) + } + segments.push(unwrapped) + } + return segments +} diff --git a/test/nuxt/composables.test.ts b/test/nuxt/composables.test.ts index 25102c50b6..6086ea5102 100644 --- a/test/nuxt/composables.test.ts +++ b/test/nuxt/composables.test.ts @@ -38,6 +38,10 @@ registerEndpoint('/_nuxt/builds/meta/override.json', defineEventHandler(() => ({ }, prerendered: ['/specific-prerendered'] }))) +registerEndpoint('/api/test', defineEventHandler((event) => ({ + method: event.method, + headers: Object.fromEntries(event.headers.entries()) +}))) describe('app config', () => { it('can be updated', () => { @@ -237,6 +241,39 @@ describe('useAsyncData', () => { }) }) +describe('useFetch', () => { + it('should match with/without computed values', async () => { + const nuxtApp = useNuxtApp() + const getPayloadEntries = () => Object.keys(nuxtApp.payload.data).length + const baseCount = getPayloadEntries() + + await useFetch('/api/test') + expect(getPayloadEntries()).toBe(baseCount + 1) + + /* @ts-expect-error Overriding auto-key */ + await useFetch('/api/test', { method: 'POST' }, '') + /* @ts-expect-error Overriding auto-key */ + await useFetch('/api/test', { method: ref('POST') }, '') + expect.soft(getPayloadEntries()).toBe(baseCount + 2) + + /* @ts-expect-error Overriding auto-key */ + await useFetch('/api/test', { headers: { id: '3' } }, '') + /* @ts-expect-error Overriding auto-key */ + await useFetch('/api/test', { headers: { id: ref('3') } }, '') + const headers = new Headers() + headers.append('id', '3') + /* @ts-expect-error Overriding auto-key */ + await useFetch('/api/test', { headers }, '') + /* @ts-expect-error Overriding auto-key */ + await useFetch('/api/test', { headers: [['id', '3']] }, '') + /* @ts-expect-error Overriding auto-key */ + await useFetch('/api/test', { headers: [['id', ref('3')]] }, '') + /* @ts-expect-error Overriding auto-key */ + await useFetch('/api/test', { headers: [[computed(() => 'id'), '3']] }, '') + expect.soft(getPayloadEntries()).toBe(baseCount + 3) + }) +}) + describe('errors', () => { it('createError', () => { expect(createError({ statusCode: 404 }).toJSON()).toMatchInlineSnapshot(`