From a4969dca0527d52a72410d23c8c77d54089391cb Mon Sep 17 00:00:00 2001 From: "Xin Du (Clark)" Date: Mon, 6 Mar 2023 18:52:25 +0000 Subject: [PATCH] fix(type): improve type inference on asyncData option --- .../nuxt/src/app/composables/asyncData.ts | 87 ++++++++++--------- packages/nuxt/src/app/composables/fetch.ts | 44 +++++----- test/fixtures/basic/types.ts | 9 +- 3 files changed, 73 insertions(+), 67 deletions(-) diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts index 31f5dfce91c1..e42d064b0c60 100644 --- a/packages/nuxt/src/app/composables/asyncData.ts +++ b/packages/nuxt/src/app/composables/asyncData.ts @@ -27,14 +27,14 @@ export type KeyOfRes = KeysOf | object)[] export interface AsyncDataOptions< - DataT, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes<_Transform> = KeyOfRes + ResT, + DataT = ResT, + PickKeys extends KeysOf =KeysOf, > { server?: boolean lazy?: boolean default?: () => DataT | Ref | null - transform?: Transform + transform?: _Transform, pick?: PickKeys watch?: MultiWatchSources immediate?: boolean @@ -62,35 +62,35 @@ export type AsyncData = _AsyncData & Promise<_AsyncDat const getDefault = () => null export function useAsyncData< - DataT, + ResT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes + DataT = ResT, + PickKeys extends KeysOf = KeysOf > ( - handler: (ctx?: NuxtApp) => Promise, - options?: AsyncDataOptions -): AsyncData, PickKeys>, DataE | null> + handler: (ctx?: NuxtApp) => Promise, + options?: AsyncDataOptions +): AsyncData, DataE | null> export function useAsyncData< - DataT, + ResT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes + DataT = ResT, + PickKeys extends KeysOf = KeysOf > ( key: string, - handler: (ctx?: NuxtApp) => Promise, - options?: AsyncDataOptions -): AsyncData, PickKeys>, DataE | null> + handler: (ctx?: NuxtApp) => Promise, + options?: AsyncDataOptions +): AsyncData, DataE | null> export function useAsyncData< - DataT, + ResT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes -> (...args: any[]): AsyncData, PickKeys>, DataE | null> { + DataT = ResT, + PickKeys extends KeysOf = KeysOf +> (...args: any[]): AsyncData, DataE | null> { const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined if (typeof args[0] !== 'string') { args.unshift(autoKey) } // eslint-disable-next-line prefer-const - let [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions] + let [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions] // Validate arguments if (typeof key !== 'string') { @@ -138,7 +138,7 @@ export function useAsyncData< } asyncData.pending.value = true // TODO: Cancel previous promise - const promise = new Promise( + const promise = new Promise( (resolve, reject) => { try { resolve(handler(nuxt)) @@ -146,12 +146,13 @@ export function useAsyncData< reject(err) } }) - .then((result) => { + .then((_result) => { // If this request is cancelled, resolve to the latest request. if ((promise as any).cancelled) { return nuxt._asyncDataPromises[key] } + let result = _result as unknown as DataT if (options.transform) { - result = options.transform(result) + result = options.transform(_result) } if (options.pick) { result = pick(result as any, options.pick) as DataT @@ -236,39 +237,39 @@ export function useAsyncData< } // Allow directly awaiting on asyncData - const asyncDataPromise = Promise.resolve(nuxt._asyncDataPromises[key]).then(() => asyncData) as AsyncData + const asyncDataPromise = Promise.resolve(nuxt._asyncDataPromises[key]).then(() => asyncData) as AsyncData Object.assign(asyncDataPromise, asyncData) - return asyncDataPromise as AsyncData, PickKeys>, DataE> + return asyncDataPromise as AsyncData, DataE> } export function useLazyAsyncData< - DataT, + ResT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes + DataT = ResT, + PickKeys extends KeysOf = KeysOf > ( - handler: (ctx?: NuxtApp) => Promise, - options?: Omit, 'lazy'> -): AsyncData, PickKeys>, DataE | null> + handler: (ctx?: NuxtApp) => Promise, + options?: Omit, 'lazy'> +): AsyncData, DataE | null> export function useLazyAsyncData< - DataT, + ResT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes + DataT = ResT, + PickKeys extends KeysOf = KeysOf > ( key: string, - handler: (ctx?: NuxtApp) => Promise, - options?: Omit, 'lazy'> -): AsyncData, PickKeys>, DataE | null> + handler: (ctx?: NuxtApp) => Promise, + options?: Omit, 'lazy'> +): AsyncData, DataE | null> export function useLazyAsyncData< - DataT, + ResT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes -> (...args: any[]): AsyncData, PickKeys>, DataE | null> { + DataT = ResT, + PickKeys extends KeysOf = KeysOf +> (...args: any[]): AsyncData, DataE | null> { const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined if (typeof args[0] !== 'string') { args.unshift(autoKey) } - const [key, handler, options] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions] + const [key, handler, options] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions] // @ts-ignore return useAsyncData(key, handler, { ...options, lazy: true }, null) } diff --git a/packages/nuxt/src/app/composables/fetch.ts b/packages/nuxt/src/app/composables/fetch.ts index 844abcacad84..d3193f30921b 100644 --- a/packages/nuxt/src/app/composables/fetch.ts +++ b/packages/nuxt/src/app/composables/fetch.ts @@ -4,7 +4,7 @@ import type { Ref } from 'vue' import { computed, unref, reactive } from 'vue' import { hash } from 'ohash' import { useRequestFetch } from './ssr' -import type { AsyncDataOptions, _Transform, KeyOfRes, AsyncData, PickFrom } from './asyncData' +import type { AsyncDataOptions, _Transform, KeysOf, AsyncData, PickFrom } from './asyncData' import { useAsyncData } from './asyncData' export type FetchResult> = TypedInternalResponse @@ -16,12 +16,12 @@ type ComputedOptions> = { type ComputedFetchOptions> = ComputedOptions> export interface UseFetchOptions< - DataT, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes, + ResT, + DataT = ResT, + PickKeys extends KeysOf = KeysOf, R extends NitroFetchRequest = string & {}, M extends AvailableRouterMethod = AvailableRouterMethod -> extends AsyncDataOptions, ComputedFetchOptions { +> extends AsyncDataOptions, ComputedFetchOptions { key?: string $fetch?: typeof globalThis.$fetch } @@ -32,23 +32,23 @@ export function useFetch< ReqT extends NitroFetchRequest = NitroFetchRequest, Method extends AvailableRouterMethod = 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod, _ResT = ResT extends void ? FetchResult : ResT, - Transform extends (res: _ResT) => any = (res: _ResT) => _ResT, - PickKeys extends KeyOfRes = KeyOfRes + DataT = _ResT, + PickKeys extends KeysOf = KeysOf > ( request: Ref | ReqT | (() => ReqT), - opts?: UseFetchOptions<_ResT, Transform, PickKeys, ReqT, Method> -): AsyncData, PickKeys>, ErrorT | null> + opts?: UseFetchOptions<_ResT, DataT, PickKeys, ReqT, Method> +): AsyncData, ErrorT | null> export function useFetch< ResT = void, ErrorT = FetchError, ReqT extends NitroFetchRequest = NitroFetchRequest, Method extends AvailableRouterMethod = 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod, _ResT = ResT extends void ? FetchResult : ResT, - Transform extends (res: _ResT) => any = (res: _ResT) => _ResT, - PickKeys extends KeyOfRes = KeyOfRes + DataT = _ResT, + PickKeys extends KeysOf = KeysOf > ( request: Ref | ReqT | (() => ReqT), - arg1?: string | UseFetchOptions<_ResT, Transform, PickKeys, ReqT, Method>, + arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, ReqT, Method>, arg2?: string ) { const [opts = {}, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2] @@ -85,7 +85,7 @@ export function useFetch< cache: typeof opts.cache === 'boolean' ? undefined : opts.cache }) - const _asyncDataOptions: AsyncDataOptions<_ResT, Transform, PickKeys> = { + const _asyncDataOptions: AsyncDataOptions<_ResT, DataT, PickKeys> = { server, lazy, default: defaultFn, @@ -101,7 +101,7 @@ export function useFetch< let controller: AbortController - const asyncData = useAsyncData<_ResT, ErrorT, Transform, PickKeys>(key, () => { + const asyncData = useAsyncData<_ResT, ErrorT, DataT, PickKeys>(key, () => { controller?.abort?.() controller = typeof AbortController !== 'undefined' ? new AbortController() : {} as AbortController @@ -124,28 +124,28 @@ export function useLazyFetch< ReqT extends NitroFetchRequest = NitroFetchRequest, Method extends AvailableRouterMethod = 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod, _ResT = ResT extends void ? FetchResult : ResT, - Transform extends (res: _ResT) => any = (res: _ResT) => _ResT, - PickKeys extends KeyOfRes = KeyOfRes + DataT = _ResT, + PickKeys extends KeysOf = KeysOf > ( request: Ref | ReqT | (() => ReqT), - opts?: Omit, 'lazy'> -): AsyncData, PickKeys>, ErrorT | null> + opts?: Omit, 'lazy'> +): AsyncData, ErrorT | null> export function useLazyFetch< ResT = void, ErrorT = FetchError, ReqT extends NitroFetchRequest = NitroFetchRequest, Method extends AvailableRouterMethod = 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod, _ResT = ResT extends void ? FetchResult : ResT, - Transform extends (res: _ResT) => any = (res: _ResT) => _ResT, - PickKeys extends KeyOfRes = KeyOfRes + DataT = _ResT, + PickKeys extends KeysOf = KeysOf > ( request: Ref | ReqT | (() => ReqT), - arg1?: string | Omit, 'lazy'>, + arg1?: string | Omit, 'lazy'>, arg2?: string ) { const [opts, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2] - return useFetch(request, { + return useFetch(request, { ...opts, lazy: true }, diff --git a/test/fixtures/basic/types.ts b/test/fixtures/basic/types.ts index c1ba1b0df750..9a6818d1f44a 100644 --- a/test/fixtures/basic/types.ts +++ b/test/fixtures/basic/types.ts @@ -199,9 +199,7 @@ describe('composables', () => { expectTypeOf(useAsyncData('test', () => Promise.resolve(500), { default: () => ref(500) }).data).toEqualTypeOf>() expectTypeOf(useAsyncData('test', () => Promise.resolve(500), { default: () => 500 }).data).toEqualTypeOf>() - // @ts-expect-error expectTypeOf(useAsyncData('test', () => Promise.resolve('500'), { default: () => ref(500) }).data).toEqualTypeOf>() - // @ts-expect-error expectTypeOf(useAsyncData('test', () => Promise.resolve('500'), { default: () => 500 }).data).toEqualTypeOf>() expectTypeOf(useFetch('/test', { default: () => ref(500) }).data).toEqualTypeOf>() @@ -235,6 +233,13 @@ describe('composables', () => { .toEqualTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: Math.random() }))) expectTypeOf(useLazyAsyncData('test', () => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo })) .toEqualTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo })) + + // Default values: #14437 + expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: { bar: 500 } }), { default: () => ({ bar: 500 }), transform: v => v.foo }).data).toEqualTypeOf>() + expectTypeOf(useLazyAsyncData('test', () => Promise.resolve({ foo: { bar: 500 } }), { default: () => ({ bar: 500 }), transform: v => v.foo })) + .toEqualTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: { bar: 500 } }), { default: () => ({ bar: 500 }), transform: v => v.foo })) + expectTypeOf(useFetch('/api/hey', { default: () => 'bar', transform: v => v.foo }).data).toEqualTypeOf>() + expectTypeOf(useLazyFetch('/api/hey', { default: () => 'bar', transform: v => v.foo }).data).toEqualTypeOf>() }) })