Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nuxt): deeply unwrap headers/query for useFetch key #24307

Merged
merged 7 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions packages/nuxt/src/app/composables/fetch.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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<string | undefined> | 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)
}
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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<string | undefined | Record<string, string>> = [
toValue(opts.method as MaybeRef<string | undefined> | 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<string, string> = {}
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
}
37 changes: 37 additions & 0 deletions test/nuxt/composables.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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(`
Expand Down