From e1c8015ff01842f8eba82e57bc693ac5ac1bcfab Mon Sep 17 00:00:00 2001 From: "Xin Du (Clark)" Date: Mon, 6 Mar 2023 18:39:53 +0000 Subject: [PATCH] fix(type): improve type inference on asyncData option --- .../nuxt/src/app/composables/asyncData.ts | 67 ++++++++++--------- packages/nuxt/src/app/composables/fetch.ts | 42 ++++++------ test/fixtures/basic/types.ts | 9 ++- 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts index 31f5dfce91..70a3b8e60a 100644 --- a/packages/nuxt/src/app/composables/asyncData.ts +++ b/packages/nuxt/src/app/composables/asyncData.ts @@ -28,13 +28,13 @@ type MultiWatchSources = (WatchSource | object)[] export interface AsyncDataOptions< DataT, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes<_Transform> = KeyOfRes + TransformData = DataT, + PickKeys extends KeysOf =KeysOf, > { server?: boolean lazy?: boolean - default?: () => DataT | Ref | null - transform?: Transform + default?: () => TransformData | Ref | null + transform?: _Transform, pick?: PickKeys watch?: MultiWatchSources immediate?: boolean @@ -64,33 +64,33 @@ const getDefault = () => null export function useAsyncData< DataT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes + TransformData = DataT, + PickKeys extends KeysOf = KeysOf > ( handler: (ctx?: NuxtApp) => Promise, - options?: AsyncDataOptions -): AsyncData, PickKeys>, DataE | null> + options?: AsyncDataOptions +): AsyncData, DataE | null> export function useAsyncData< DataT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes + TransformData = DataT, + PickKeys extends KeysOf = KeysOf > ( key: string, handler: (ctx?: NuxtApp) => Promise, - options?: AsyncDataOptions -): AsyncData, PickKeys>, DataE | null> + options?: AsyncDataOptions +): AsyncData, DataE | null> export function useAsyncData< DataT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes -> (...args: any[]): AsyncData, PickKeys>, DataE | null> { + TransformData = DataT, + 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') { @@ -122,7 +122,7 @@ export function useAsyncData< } } // TODO: Else, somehow check for conflicting keys with different defaults or fetcher - const asyncData = { ...nuxt._asyncData[key] } as AsyncData + const asyncData = { ...nuxt._asyncData[key] } as AsyncData asyncData.refresh = asyncData.execute = (opts = {}) => { if (nuxt._asyncDataPromises[key]) { @@ -146,15 +146,16 @@ 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 TransformData if (options.transform) { - result = options.transform(result) + result = options.transform(_result) } if (options.pick) { - result = pick(result as any, options.pick) as DataT + result = pick(result as any, options.pick) as TransformData } asyncData.data.value = result asyncData.error.value = null @@ -239,36 +240,36 @@ export function useAsyncData< 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, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes + TransformData = DataT, + PickKeys extends KeysOf = KeysOf > ( handler: (ctx?: NuxtApp) => Promise, - options?: Omit, 'lazy'> -): AsyncData, PickKeys>, DataE | null> + options?: Omit, 'lazy'> +): AsyncData, DataE | null> export function useLazyAsyncData< DataT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes + TransformData = DataT, + PickKeys extends KeysOf = KeysOf > ( key: string, handler: (ctx?: NuxtApp) => Promise, - options?: Omit, 'lazy'> -): AsyncData, PickKeys>, DataE | null> + options?: Omit, 'lazy'> +): AsyncData, DataE | null> export function useLazyAsyncData< DataT, DataE = Error, - Transform extends _Transform = _Transform, - PickKeys extends KeyOfRes = KeyOfRes -> (...args: any[]): AsyncData, PickKeys>, DataE | null> { + TransformData = DataT, + 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 844abcacad..29d26f6f11 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 @@ -17,11 +17,11 @@ type ComputedFetchOptions = _Transform, - PickKeys extends KeyOfRes = KeyOfRes, + TransformData = DataT, + 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 + TransformData = _ResT, + PickKeys extends KeysOf = KeysOf > ( request: Ref | ReqT | (() => ReqT), - opts?: UseFetchOptions<_ResT, Transform, PickKeys, ReqT, Method> -): AsyncData, PickKeys>, ErrorT | null> + opts?: UseFetchOptions<_ResT, TransformData, 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 + TransformData = _ResT, + PickKeys extends KeysOf = KeysOf > ( request: Ref | ReqT | (() => ReqT), - arg1?: string | UseFetchOptions<_ResT, Transform, PickKeys, ReqT, Method>, + arg1?: string | UseFetchOptions<_ResT, TransformData, 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, TransformData, 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, TransformData, 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 + TransformData = _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 + TransformData = _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 c1ba1b0df7..9a6818d1f4 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>() }) })