mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +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 { Ref, WatchSource } from 'vue'
|
||||||
import type { NuxtApp } from '../nuxt'
|
import type { NuxtApp } from '../nuxt'
|
||||||
import { useNuxtApp } from '../nuxt'
|
import { useNuxtApp } from '../nuxt'
|
||||||
|
import type { NuxtError} from './error';
|
||||||
import { createError } from './error'
|
import { createError } from './error'
|
||||||
import { onNuxtReady } from './ready'
|
import { onNuxtReady } from './ready'
|
||||||
|
|
||||||
@ -82,27 +83,27 @@ const isDefer = (dedupe?: boolean | 'cancel' | 'defer') => dedupe === 'defer' ||
|
|||||||
|
|
||||||
export function useAsyncData<
|
export function useAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = null,
|
||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
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<
|
export function useAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = DataT,
|
DefaultT = DataT,
|
||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
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<
|
export function useAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = null,
|
||||||
@ -110,10 +111,10 @@ export function useAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
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<
|
export function useAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = DataT,
|
DefaultT = DataT,
|
||||||
@ -121,14 +122,14 @@ export function useAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
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<
|
export function useAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
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
|
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
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
|
// 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 = {}) => {
|
asyncData.refresh = asyncData.execute = (opts = {}) => {
|
||||||
if (nuxt._asyncDataPromises[key]) {
|
if (nuxt._asyncDataPromises[key]) {
|
||||||
@ -224,7 +225,7 @@ export function useAsyncData<
|
|||||||
// If this request is cancelled, resolve to the latest request.
|
// If this request is cancelled, resolve to the latest request.
|
||||||
if ((promise as any).cancelled) { return nuxt._asyncDataPromises[key] }
|
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.data.value = unref(options.default!())
|
||||||
asyncData.status.value = 'error'
|
asyncData.status.value = 'error'
|
||||||
})
|
})
|
||||||
@ -295,10 +296,10 @@ export function useAsyncData<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allow directly awaiting on asyncData
|
// 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)
|
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<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
|
@ -1,44 +1,65 @@
|
|||||||
import type { H3Error } from 'h3'
|
import type { H3Error } from 'h3'
|
||||||
import { createError as _createError } from 'h3'
|
import { createError as createH3Error } from 'h3'
|
||||||
import { toRef } from 'vue'
|
import { toRef } from 'vue'
|
||||||
import { useNuxtApp } from '../nuxt'
|
import { useNuxtApp } from '../nuxt'
|
||||||
import { useRouter } from './router'
|
import { useRouter } from './router'
|
||||||
|
|
||||||
|
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
|
||||||
|
|
||||||
export const useError = () => toRef(useNuxtApp().payload, '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>) => {
|
export const showError = <DataT = unknown>(
|
||||||
const err = createError(_err)
|
error: string | Error | Partial<NuxtError<DataT>>
|
||||||
|
) => {
|
||||||
|
const nuxtError = createError<DataT>(error)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const error = useError()
|
const error = useError()
|
||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
nuxtApp.hooks.callHook('app:error', err)
|
nuxtApp.hooks.callHook('app:error', nuxtError)
|
||||||
}
|
|
||||||
error.value = error.value || err
|
|
||||||
} catch {
|
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
error.value = error.value || nuxtError
|
||||||
|
} catch {
|
||||||
|
throw nuxtError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nuxtError
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clearError = async (options: { redirect?: string } = {}) => {
|
export const clearError = async (options: { redirect?: string } = {}) => {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const error = useError()
|
const error = useError()
|
||||||
|
|
||||||
nuxtApp.callHook('app:error:cleared', options)
|
nuxtApp.callHook('app:error:cleared', options)
|
||||||
|
|
||||||
if (options.redirect) {
|
if (options.redirect) {
|
||||||
await useRouter().replace(options.redirect)
|
await useRouter().replace(options.redirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
error.value = null
|
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 => {
|
export const createError = <DataT = unknown>(
|
||||||
const _err: NuxtError = _createError(err)
|
error: string | Partial<NuxtError<DataT>>
|
||||||
;(_err as any).__nuxt_error = true
|
) => {
|
||||||
return _err
|
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 type { AppConfig, RuntimeValue, UpperSnakeCase } from 'nuxt/schema'
|
||||||
import { defineNuxtConfig } from 'nuxt/config'
|
import { defineNuxtConfig } from 'nuxt/config'
|
||||||
import { callWithNuxt, isVue3 } from '#app'
|
import { callWithNuxt, isVue3 } from '#app'
|
||||||
|
import type { NuxtError } from '#app'
|
||||||
import type { NavigateToOptions } from '#app/composables/router'
|
import type { NavigateToOptions } from '#app/composables/router'
|
||||||
import { NuxtLayout, NuxtLink, NuxtPage, WithTypes } from '#components'
|
import { NuxtLayout, NuxtLink, NuxtPage, WithTypes } from '#components'
|
||||||
import { useRouter } from '#imports'
|
import { useRouter } from '#imports'
|
||||||
@ -38,8 +39,11 @@ describe('API routes', () => {
|
|||||||
expectTypeOf(useAsyncData('api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
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<TestResponse>('api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | null>>()
|
||||||
|
|
||||||
expectTypeOf(useAsyncData('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | 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<string | 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-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>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
||||||
|
Loading…
Reference in New Issue
Block a user