mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
fix(nuxt): ensure error
in useAsyncData
has correct type (#24396)
This commit is contained in:
parent
4e0d2c073f
commit
72c8503236
@ -2,6 +2,7 @@ import { getCurrentInstance, onBeforeMount, onServerPrefetch, onUnmounted, ref,
|
||||
import type { Ref, WatchSource } from 'vue'
|
||||
import type { NuxtApp } from '../nuxt'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
import type { NuxtError} from './error';
|
||||
import { createError } from './error'
|
||||
import { onNuxtReady } from './ready'
|
||||
|
||||
@ -82,27 +83,27 @@ const isDefer = (dedupe?: boolean | 'cancel' | 'defer') => dedupe === 'defer' ||
|
||||
|
||||
export function useAsyncData<
|
||||
ResT,
|
||||
DataE = Error,
|
||||
NuxtErrorDataT = unknown,
|
||||
DataT = ResT,
|
||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||
DefaultT = null,
|
||||
> (
|
||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
||||
export function useAsyncData<
|
||||
ResT,
|
||||
DataE = Error,
|
||||
NuxtErrorDataT = unknown,
|
||||
DataT = ResT,
|
||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||
DefaultT = DataT,
|
||||
> (
|
||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
||||
export function useAsyncData<
|
||||
ResT,
|
||||
DataE = Error,
|
||||
NuxtErrorDataT = unknown,
|
||||
DataT = ResT,
|
||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||
DefaultT = null,
|
||||
@ -110,10 +111,10 @@ export function useAsyncData<
|
||||
key: string,
|
||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
||||
export function useAsyncData<
|
||||
ResT,
|
||||
DataE = Error,
|
||||
NuxtErrorDataT = unknown,
|
||||
DataT = ResT,
|
||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||
DefaultT = DataT,
|
||||
@ -121,14 +122,14 @@ export function useAsyncData<
|
||||
key: string,
|
||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
||||
export function useAsyncData<
|
||||
ResT,
|
||||
DataE = Error,
|
||||
NuxtErrorDataT = unknown,
|
||||
DataT = ResT,
|
||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||
DefaultT = null,
|
||||
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys>, DataE | null> {
|
||||
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null> {
|
||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||
|
||||
@ -177,7 +178,7 @@ export function useAsyncData<
|
||||
}
|
||||
|
||||
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
|
||||
const asyncData = { ...nuxt._asyncData[key] } as AsyncData<DataT | DefaultT, DataE>
|
||||
const asyncData = { ...nuxt._asyncData[key] } as AsyncData<DataT | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
|
||||
|
||||
asyncData.refresh = asyncData.execute = (opts = {}) => {
|
||||
if (nuxt._asyncDataPromises[key]) {
|
||||
@ -224,7 +225,7 @@ export function useAsyncData<
|
||||
// If this request is cancelled, resolve to the latest request.
|
||||
if ((promise as any).cancelled) { return nuxt._asyncDataPromises[key] }
|
||||
|
||||
asyncData.error.value = createError(error) as DataE
|
||||
asyncData.error.value = createError<NuxtErrorDataT>(error) as (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)
|
||||
asyncData.data.value = unref(options.default!())
|
||||
asyncData.status.value = 'error'
|
||||
})
|
||||
@ -295,10 +296,10 @@ export function useAsyncData<
|
||||
}
|
||||
|
||||
// Allow directly awaiting on asyncData
|
||||
const asyncDataPromise = Promise.resolve(nuxt._asyncDataPromises[key]).then(() => asyncData) as AsyncData<ResT, DataE>
|
||||
const asyncDataPromise = Promise.resolve(nuxt._asyncDataPromises[key]).then(() => asyncData) as AsyncData<ResT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
|
||||
Object.assign(asyncDataPromise, asyncData)
|
||||
|
||||
return asyncDataPromise as AsyncData<PickFrom<DataT, PickKeys>, DataE>
|
||||
return asyncDataPromise as AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
|
||||
}
|
||||
export function useLazyAsyncData<
|
||||
ResT,
|
||||
|
@ -1,44 +1,65 @@
|
||||
import type { H3Error } from 'h3'
|
||||
import { createError as _createError } from 'h3'
|
||||
import { createError as createH3Error } from 'h3'
|
||||
import { toRef } from 'vue'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
import { useRouter } from './router'
|
||||
|
||||
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
|
||||
|
||||
export const useError = () => toRef(useNuxtApp().payload, 'error')
|
||||
|
||||
export interface NuxtError extends H3Error {}
|
||||
export interface NuxtError<DataT = unknown> extends H3Error<DataT> {}
|
||||
|
||||
export const showError = (_err: string | Error | Partial<NuxtError>) => {
|
||||
const err = createError(_err)
|
||||
export const showError = <DataT = unknown>(
|
||||
error: string | Error | Partial<NuxtError<DataT>>
|
||||
) => {
|
||||
const nuxtError = createError<DataT>(error)
|
||||
|
||||
try {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const error = useError()
|
||||
|
||||
if (import.meta.client) {
|
||||
nuxtApp.hooks.callHook('app:error', err)
|
||||
}
|
||||
error.value = error.value || err
|
||||
} catch {
|
||||
throw err
|
||||
nuxtApp.hooks.callHook('app:error', nuxtError)
|
||||
}
|
||||
|
||||
return err
|
||||
error.value = error.value || nuxtError
|
||||
} catch {
|
||||
throw nuxtError
|
||||
}
|
||||
|
||||
return nuxtError
|
||||
}
|
||||
|
||||
export const clearError = async (options: { redirect?: string } = {}) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const error = useError()
|
||||
|
||||
nuxtApp.callHook('app:error:cleared', options)
|
||||
|
||||
if (options.redirect) {
|
||||
await useRouter().replace(options.redirect)
|
||||
}
|
||||
|
||||
error.value = null
|
||||
}
|
||||
|
||||
export const isNuxtError = (err?: string | object): err is NuxtError => !!(err && typeof err === 'object' && ('__nuxt_error' in err))
|
||||
export const isNuxtError = <DataT = unknown>(
|
||||
error?: string | object
|
||||
): error is NuxtError<DataT> => (
|
||||
!!error && typeof error === 'object' && NUXT_ERROR_SIGNATURE in error
|
||||
)
|
||||
|
||||
export const createError = (err: string | Partial<NuxtError>): NuxtError => {
|
||||
const _err: NuxtError = _createError(err)
|
||||
;(_err as any).__nuxt_error = true
|
||||
return _err
|
||||
export const createError = <DataT = unknown>(
|
||||
error: string | Partial<NuxtError<DataT>>
|
||||
) => {
|
||||
const nuxtError: NuxtError<DataT> = createH3Error<DataT>(error)
|
||||
|
||||
Object.defineProperty(nuxtError, NUXT_ERROR_SIGNATURE, {
|
||||
value: true,
|
||||
configurable: false,
|
||||
writable: false
|
||||
})
|
||||
|
||||
return nuxtError
|
||||
}
|
||||
|
8
test/fixtures/basic-types/types.ts
vendored
8
test/fixtures/basic-types/types.ts
vendored
@ -6,6 +6,7 @@ import type { NavigationFailure, RouteLocationNormalized, RouteLocationRaw, Rout
|
||||
import type { AppConfig, RuntimeValue, UpperSnakeCase } from 'nuxt/schema'
|
||||
import { defineNuxtConfig } from 'nuxt/config'
|
||||
import { callWithNuxt, isVue3 } from '#app'
|
||||
import type { NuxtError } from '#app'
|
||||
import type { NavigateToOptions } from '#app/composables/router'
|
||||
import { NuxtLayout, NuxtLink, NuxtPage, WithTypes } from '#components'
|
||||
import { useRouter } from '#imports'
|
||||
@ -38,8 +39,11 @@ describe('API routes', () => {
|
||||
expectTypeOf(useAsyncData('api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
||||
expectTypeOf(useAsyncData<TestResponse>('api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | null>>()
|
||||
|
||||
expectTypeOf(useAsyncData('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | null>>()
|
||||
expectTypeOf(useAsyncData<any, string>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<string | null>>()
|
||||
expectTypeOf(useAsyncData('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<unknown> | null>>()
|
||||
expectTypeOf(useAsyncData<any, string>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | null>>()
|
||||
// backwards compatibility
|
||||
expectTypeOf(useAsyncData<any, Error>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | null>>()
|
||||
expectTypeOf(useAsyncData<any, NuxtError<string>>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | null>>()
|
||||
|
||||
expectTypeOf(useLazyAsyncData('lazy-api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | null>>()
|
||||
expectTypeOf(useLazyAsyncData('lazy-api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
||||
|
Loading…
Reference in New Issue
Block a user