diff --git a/eslint.config.js b/eslint.config.js index d9a08b85eee..85033c6a871 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -61,4 +61,10 @@ export default [ }, settings: { vitest: { typecheck: true } }, }, + { + files: ['**/*.test-d.ts', '**/*.test-d.tsx'], + rules: { + 'vitest/expect-expect': 'off', + }, + }, ] diff --git a/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts b/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts index 4a7ce532bc2..5362c257e4b 100644 --- a/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts +++ b/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts @@ -38,24 +38,50 @@ describe('infiniteQueryOptions', () => { const options = infiniteQueryOptions({ queryKey: ['key'], queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, initialPageParam: 1, + mode: 'manual', }) - const { data } = injectInfiniteQuery(() => options) + const { data, fetchNextPage } = injectInfiniteQuery(() => options) // known issue: type of pageParams is unknown when returned from useInfiniteQuery expectTypeOf(data()).toEqualTypeOf< InfiniteData | undefined >() + fetchNextPage({ pageParam: 2 }) + + // @ts-expect-error pageParam is required in manual mode + fetchNextPage() + }) + + it('should preserve manual fetch method types', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + return pageParam * 5 + }, + initialPageParam: 1, + mode: 'manual', + }) + + const { fetchNextPage, fetchPreviousPage } = injectInfiniteQuery( + () => options, + ) + + fetchNextPage({ pageParam: 2 }) + fetchPreviousPage({ pageParam: 0 }) + + // @ts-expect-error pageParam is required in manual mode + fetchNextPage() }) it('should work when passed to fetchInfiniteQuery', async () => { const options = infiniteQueryOptions({ queryKey: ['key'], queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, initialPageParam: 1, + mode: 'manual', }) const data = await new QueryClient().fetchInfiniteQuery(options) @@ -155,6 +181,33 @@ describe('infiniteQueryOptions', () => { ) }) + it('should reject missing mode / getNextPageParam and reject getters in manual mode', () => { + // @ts-expect-error getNextPageParam is required unless mode is manual + infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + }) + + // @ts-expect-error getNextPageParam is not allowed in manual mode + infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + mode: 'manual', + getNextPageParam: () => 1, + }) + + // @ts-expect-error getPreviousPageParam is not allowed in manual mode + infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + mode: 'manual', + getPreviousPageParam: () => 0, + }) + }) + test('allow optional initialData function', () => { const initialData: { example: boolean } | undefined = { example: true } const queryOptions = infiniteQueryOptions({ diff --git a/packages/angular-query-experimental/src/__tests__/inject-infinite-query.test-d.ts b/packages/angular-query-experimental/src/__tests__/inject-infinite-query.test-d.ts index 7ec133adfb1..655dbec2cf2 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-infinite-query.test-d.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-infinite-query.test-d.ts @@ -39,4 +39,21 @@ describe('injectInfiniteQuery', () => { expectTypeOf(data).toEqualTypeOf>() } }) + + test('should require pageParam on manual fetch methods', () => { + const query = TestBed.runInInjectionContext(() => { + return injectInfiniteQuery(() => ({ + queryKey: ['infiniteQuery'], + queryFn: ({ pageParam }) => 'data on page ' + pageParam, + initialPageParam: 0, + mode: 'manual', + })) + }) + + query.fetchNextPage({ pageParam: 1 }) + query.fetchPreviousPage({ pageParam: 0 }) + + // @ts-expect-error pageParam is required in manual mode + query.fetchNextPage() + }) }) diff --git a/packages/angular-query-experimental/src/infinite-query-options.ts b/packages/angular-query-experimental/src/infinite-query-options.ts index fc18c0e94dc..0e766d1f5b7 100644 --- a/packages/angular-query-experimental/src/infinite-query-options.ts +++ b/packages/angular-query-experimental/src/infinite-query-options.ts @@ -2,13 +2,38 @@ import type { DataTag, DefaultError, InfiniteData, + InfiniteQueryMode, InitialDataFunction, NonUndefinedGuard, OmitKeyof, QueryKey, SkipToken, } from '@tanstack/query-core' -import type { CreateInfiniteQueryOptions } from './types' +import type { + CreateInfiniteQueryOptions, + CreateInfiniteQueryOptionsBase, +} from './types' + +type OptionalInitialData = { + initialData?: + | undefined + | NonUndefinedGuard> + | InitialDataFunction< + NonUndefinedGuard> + > +} + +type RequiredInitialData = { + initialData: + | NonUndefinedGuard> + | (() => NonUndefinedGuard>) + | undefined +} + +type WithoutSkipTokenQueryFn = + OmitKeyof & { + queryFn?: Exclude + } export type UndefinedInitialDataInfiniteOptions< TQueryFnData, @@ -16,20 +41,49 @@ export type UndefinedInitialDataInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = InfiniteQueryMode | undefined, > = CreateInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam -> & { - initialData?: - | undefined - | NonUndefinedGuard> - | InitialDataFunction< - NonUndefinedGuard> - > -} + TPageParam, + TMode +> & + OptionalInitialData + +export type ManualUndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = CreateInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode +> & + OptionalInitialData + +export type UnusedSkipTokenManualInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = WithoutSkipTokenQueryFn< + CreateInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + > +> export type UnusedSkipTokenInfiniteOptions< TQueryFnData, @@ -37,27 +91,33 @@ export type UnusedSkipTokenInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> = OmitKeyof< + TMode extends InfiniteQueryMode | undefined = InfiniteQueryMode | undefined, +> = WithoutSkipTokenQueryFn< CreateInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam - >, - 'queryFn' -> & { - queryFn?: Exclude< - CreateInfiniteQueryOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam - >['queryFn'], - SkipToken | undefined + TPageParam, + TMode > -} +> + +export type ManualDefinedInitialDataInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = CreateInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode +> & + RequiredInitialData export type DefinedInitialDataInfiniteOptions< TQueryFnData, @@ -65,26 +125,17 @@ export type DefinedInitialDataInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = InfiniteQueryMode | undefined, > = CreateInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam -> & { - initialData: - | NonUndefinedGuard> - | (() => NonUndefinedGuard>) - | undefined -} + TPageParam, + TMode +> & + RequiredInitialData -/** - * Allows to share and re-use infinite query options in a type-safe way. - * - * The `queryKey` will be tagged with the type from `queryFn`. - * @param options - The infinite query options to tag with the type from `queryFn`. - * @returns The tagged infinite query options. - */ export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, @@ -97,9 +148,34 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined >, ): DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined +> & { + queryKey: DataTag, TError> +} +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: ManualDefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): ManualDefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, @@ -108,14 +184,6 @@ export function infiniteQueryOptions< > & { queryKey: DataTag, TError> } - -/** - * Allows to share and re-use infinite query options in a type-safe way. - * - * The `queryKey` will be tagged with the type from `queryFn`. - * @param options - The infinite query options to tag with the type from `queryFn`. - * @returns The tagged infinite query options. - */ export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, @@ -128,9 +196,105 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined >, ): UnusedSkipTokenInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined +> & { + queryKey: DataTag, TError> +} +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UnusedSkipTokenManualInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): UnusedSkipTokenManualInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> & { + queryKey: DataTag, TError> +} +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >, +): UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined +> & { + queryKey: DataTag, TError> +} +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: ManualUndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): ManualUndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> & { + queryKey: DataTag, TError> +} +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): DefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, @@ -139,14 +303,6 @@ export function infiniteQueryOptions< > & { queryKey: DataTag, TError> } - -/** - * Allows to share and re-use infinite query options in a type-safe way. - * - * The `queryKey` will be tagged with the type from `queryFn`. - * @param options - The infinite query options to tag with the type from `queryFn`. - * @returns The tagged infinite query options. - */ export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, @@ -170,14 +326,6 @@ export function infiniteQueryOptions< > & { queryKey: DataTag, TError> } - -/** - * Allows to share and re-use infinite query options in a type-safe way. - * - * The `queryKey` will be tagged with the type from `queryFn`. - * @param options - The infinite query options to tag with the type from `queryFn`. - * @returns The tagged infinite query options. - */ export function infiniteQueryOptions(options: unknown) { return options } diff --git a/packages/angular-query-experimental/src/inject-infinite-query.ts b/packages/angular-query-experimental/src/inject-infinite-query.ts index ee6de032409..c1aec6f32c6 100644 --- a/packages/angular-query-experimental/src/inject-infinite-query.ts +++ b/packages/angular-query-experimental/src/inject-infinite-query.ts @@ -9,8 +9,8 @@ import { createBaseQuery } from './create-base-query' import type { DefaultError, InfiniteData, + InfiniteQueryMode, QueryKey, - QueryObserver, } from '@tanstack/query-core' import type { CreateInfiniteQueryOptions, @@ -19,6 +19,8 @@ import type { } from './types' import type { DefinedInitialDataInfiniteOptions, + ManualDefinedInitialDataInfiniteOptions, + ManualUndefinedInitialDataInfiniteOptions, UndefinedInitialDataInfiniteOptions, } from './infinite-query-options' @@ -34,9 +36,103 @@ export interface InjectInfiniteQueryOptions { /** * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a unique key. * Infinite queries can additively "load more" data onto an existing set of data or "infinite scroll" + * + * **Basic example** + * ```ts + * class ServiceOrComponent { + * query = injectInfiniteQuery(() => ({ + * queryKey: ['projects'], + * queryFn: ({ pageParam }) => + * this.#http.get('/api/projects?cursor=' + pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * })) + * } + * ``` + * + * Similar to `computed` from Angular, the function passed to `injectInfiniteQuery` will be run in the reactive context. + * In the example below, the query will be automatically enabled and executed when the filter signal changes + * to a truthy value. When the filter signal changes back to a falsy value, the query will be disabled. + * + * **Reactive example** + * ```ts + * class ServiceOrComponent { + * filter = signal('') + * + * projectsQuery = injectInfiniteQuery(() => ({ + * queryKey: ['projects', this.filter()], + * queryFn: ({ pageParam }) => fetchProjects(this.filter(), pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * enabled: !!this.filter(), + * })) + * } + * ``` * @param injectInfiniteQueryFn - A function that returns infinite query options. * @param options - Additional configuration. * @returns The infinite query result. + * @see https://tanstack.com/query/latest/docs/framework/angular/guides/infinite-queries + */ +export function injectInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + injectInfiniteQueryFn: () => ManualDefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, + options?: InjectInfiniteQueryOptions, +): DefinedCreateInfiniteQueryResult< + TData, + TError, + TPageParam, + InfiniteQueryMode +> +/** + * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a unique key. + * Infinite queries can additively "load more" data onto an existing set of data or "infinite scroll" + * + * **Basic example** + * ```ts + * class ServiceOrComponent { + * query = injectInfiniteQuery(() => ({ + * queryKey: ['projects'], + * queryFn: ({ pageParam }) => + * this.#http.get('/api/projects?cursor=' + pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * })) + * } + * ``` + * + * Similar to `computed` from Angular, the function passed to `injectInfiniteQuery` will be run in the reactive context. + * In the example below, the query will be automatically enabled and executed when the filter signal changes + * to a truthy value. When the filter signal changes back to a falsy value, the query will be disabled. + * + * **Reactive example** + * ```ts + * class ServiceOrComponent { + * filter = signal('') + * + * projectsQuery = injectInfiniteQuery(() => ({ + * queryKey: ['projects', this.filter()], + * queryFn: ({ pageParam }) => fetchProjects(this.filter(), pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * enabled: !!this.filter(), + * })) + * } + * ``` + * @param injectInfiniteQueryFn - A function that returns infinite query options. + * @param options - Additional configuration. + * @returns The infinite query result. + * @see https://tanstack.com/query/latest/docs/framework/angular/guides/infinite-queries */ export function injectInfiniteQuery< TQueryFnData, @@ -46,6 +142,63 @@ export function injectInfiniteQuery< TPageParam = unknown, >( injectInfiniteQueryFn: () => DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >, + options?: InjectInfiniteQueryOptions, +): DefinedCreateInfiniteQueryResult +/** + * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a unique key. + * Infinite queries can additively "load more" data onto an existing set of data or "infinite scroll" + * + * **Basic example** + * ```ts + * class ServiceOrComponent { + * query = injectInfiniteQuery(() => ({ + * queryKey: ['projects'], + * queryFn: ({ pageParam }) => + * this.#http.get('/api/projects?cursor=' + pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * })) + * } + * ``` + * + * Similar to `computed` from Angular, the function passed to `injectInfiniteQuery` will be run in the reactive context. + * In the example below, the query will be automatically enabled and executed when the filter signal changes + * to a truthy value. When the filter signal changes back to a falsy value, the query will be disabled. + * + * **Reactive example** + * ```ts + * class ServiceOrComponent { + * filter = signal('') + * + * projectsQuery = injectInfiniteQuery(() => ({ + * queryKey: ['projects', this.filter()], + * queryFn: ({ pageParam }) => fetchProjects(this.filter(), pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * enabled: !!this.filter(), + * })) + * } + * ``` + * @param injectInfiniteQueryFn - A function that returns infinite query options. + * @param options - Additional configuration. + * @returns The infinite query result. + * @see https://tanstack.com/query/latest/docs/framework/angular/guides/infinite-queries + */ +export function injectInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + injectInfiniteQueryFn: () => ManualUndefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, @@ -53,14 +206,46 @@ export function injectInfiniteQuery< TPageParam >, options?: InjectInfiniteQueryOptions, -): DefinedCreateInfiniteQueryResult - +): CreateInfiniteQueryResult /** * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a unique key. * Infinite queries can additively "load more" data onto an existing set of data or "infinite scroll" + * + * **Basic example** + * ```ts + * class ServiceOrComponent { + * query = injectInfiniteQuery(() => ({ + * queryKey: ['projects'], + * queryFn: ({ pageParam }) => + * this.#http.get('/api/projects?cursor=' + pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * })) + * } + * ``` + * + * Similar to `computed` from Angular, the function passed to `injectInfiniteQuery` will be run in the reactive context. + * In the example below, the query will be automatically enabled and executed when the filter signal changes + * to a truthy value. When the filter signal changes back to a falsy value, the query will be disabled. + * + * **Reactive example** + * ```ts + * class ServiceOrComponent { + * filter = signal('') + * + * projectsQuery = injectInfiniteQuery(() => ({ + * queryKey: ['projects', this.filter()], + * queryFn: ({ pageParam }) => fetchProjects(this.filter(), pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * enabled: !!this.filter(), + * })) + * } + * ``` * @param injectInfiniteQueryFn - A function that returns infinite query options. * @param options - Additional configuration. * @returns The infinite query result. + * @see https://tanstack.com/query/latest/docs/framework/angular/guides/infinite-queries */ export function injectInfiniteQuery< TQueryFnData, @@ -70,6 +255,63 @@ export function injectInfiniteQuery< TPageParam = unknown, >( injectInfiniteQueryFn: () => UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >, + options?: InjectInfiniteQueryOptions, +): CreateInfiniteQueryResult +/** + * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a unique key. + * Infinite queries can additively "load more" data onto an existing set of data or "infinite scroll" + * + * **Basic example** + * ```ts + * class ServiceOrComponent { + * query = injectInfiniteQuery(() => ({ + * queryKey: ['projects'], + * queryFn: ({ pageParam }) => + * this.#http.get('/api/projects?cursor=' + pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * })) + * } + * ``` + * + * Similar to `computed` from Angular, the function passed to `injectInfiniteQuery` will be run in the reactive context. + * In the example below, the query will be automatically enabled and executed when the filter signal changes + * to a truthy value. When the filter signal changes back to a falsy value, the query will be disabled. + * + * **Reactive example** + * ```ts + * class ServiceOrComponent { + * filter = signal('') + * + * projectsQuery = injectInfiniteQuery(() => ({ + * queryKey: ['projects', this.filter()], + * queryFn: ({ pageParam }) => fetchProjects(this.filter(), pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * enabled: !!this.filter(), + * })) + * } + * ``` + * @param injectInfiniteQueryFn - A function that returns infinite query options. + * @param options - Additional configuration. + * @returns The infinite query result. + * @see https://tanstack.com/query/latest/docs/framework/angular/guides/infinite-queries + */ +export function injectInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + injectInfiniteQueryFn: () => DefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, @@ -77,14 +319,102 @@ export function injectInfiniteQuery< TPageParam >, options?: InjectInfiniteQueryOptions, -): CreateInfiniteQueryResult - +): DefinedCreateInfiniteQueryResult /** * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a unique key. * Infinite queries can additively "load more" data onto an existing set of data or "infinite scroll" + * + * **Basic example** + * ```ts + * class ServiceOrComponent { + * query = injectInfiniteQuery(() => ({ + * queryKey: ['projects'], + * queryFn: ({ pageParam }) => + * this.#http.get('/api/projects?cursor=' + pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * })) + * } + * ``` + * + * Similar to `computed` from Angular, the function passed to `injectInfiniteQuery` will be run in the reactive context. + * In the example below, the query will be automatically enabled and executed when the filter signal changes + * to a truthy value. When the filter signal changes back to a falsy value, the query will be disabled. + * + * **Reactive example** + * ```ts + * class ServiceOrComponent { + * filter = signal('') + * + * projectsQuery = injectInfiniteQuery(() => ({ + * queryKey: ['projects', this.filter()], + * queryFn: ({ pageParam }) => fetchProjects(this.filter(), pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * enabled: !!this.filter(), + * })) + * } + * ``` * @param injectInfiniteQueryFn - A function that returns infinite query options. * @param options - Additional configuration. * @returns The infinite query result. + * @see https://tanstack.com/query/latest/docs/framework/angular/guides/infinite-queries + */ +export function injectInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + injectInfiniteQueryFn: () => UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, + options?: InjectInfiniteQueryOptions, +): CreateInfiniteQueryResult +/** + * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a unique key. + * Infinite queries can additively "load more" data onto an existing set of data or "infinite scroll" + * + * **Basic example** + * ```ts + * class ServiceOrComponent { + * query = injectInfiniteQuery(() => ({ + * queryKey: ['projects'], + * queryFn: ({ pageParam }) => + * this.#http.get('/api/projects?cursor=' + pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * })) + * } + * ``` + * + * Similar to `computed` from Angular, the function passed to `injectInfiniteQuery` will be run in the reactive context. + * In the example below, the query will be automatically enabled and executed when the filter signal changes + * to a truthy value. When the filter signal changes back to a falsy value, the query will be disabled. + * + * **Reactive example** + * ```ts + * class ServiceOrComponent { + * filter = signal('') + * + * projectsQuery = injectInfiniteQuery(() => ({ + * queryKey: ['projects', this.filter()], + * queryFn: ({ pageParam }) => fetchProjects(this.filter(), pageParam), + * initialPageParam: 0, + * getNextPageParam: (lastPage) => lastPage.nextCursor, + * enabled: !!this.filter(), + * })) + * } + * ``` + * @param injectInfiniteQueryFn - A function that returns infinite query options. + * @param options - Additional configuration. + * @returns The infinite query result. + * @see https://tanstack.com/query/latest/docs/framework/angular/guides/infinite-queries */ export function injectInfiniteQuery< TQueryFnData, @@ -101,7 +431,7 @@ export function injectInfiniteQuery< TPageParam >, options?: InjectInfiniteQueryOptions, -): CreateInfiniteQueryResult +): CreateInfiniteQueryResult /** * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a unique key. @@ -111,15 +441,12 @@ export function injectInfiniteQuery< * @returns The infinite query result. */ export function injectInfiniteQuery( - injectInfiniteQueryFn: () => CreateInfiniteQueryOptions, + injectInfiniteQueryFn: () => any, options?: InjectInfiniteQueryOptions, ) { !options?.injector && assertInInjectionContext(injectInfiniteQuery) - const injector = options?.injector ?? inject(Injector) - return runInInjectionContext(injector, () => - createBaseQuery( - injectInfiniteQueryFn, - InfiniteQueryObserver as typeof QueryObserver, - ), + + return runInInjectionContext(options?.injector ?? inject(Injector), () => + createBaseQuery(injectInfiniteQueryFn, InfiniteQueryObserver), ) } diff --git a/packages/angular-query-experimental/src/types.ts b/packages/angular-query-experimental/src/types.ts index d71bec248f7..f7eef6ad5f2 100644 --- a/packages/angular-query-experimental/src/types.ts +++ b/packages/angular-query-experimental/src/types.ts @@ -4,6 +4,8 @@ import type { DefaultError, DefinedInfiniteQueryObserverResult, DefinedQueryObserverResult, + DistributiveOmit, + InfiniteQueryMode, InfiniteQueryObserverOptions, InfiniteQueryObserverResult, MutateFunction, @@ -72,22 +74,40 @@ export interface BaseQueryNarrowing { > } -export interface CreateInfiniteQueryOptions< +export type CreateInfiniteQueryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> extends OmitKeyof< + TMode extends InfiniteQueryMode | undefined = InfiniteQueryMode | undefined, +> = CreateInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + TMode +> + +export type CreateInfiniteQueryOptionsBase< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, +> = DistributiveOmit< InfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode >, 'suspense' -> {} +> export type CreateBaseQueryResult< TData = unknown, @@ -111,15 +131,21 @@ export type DefinedCreateQueryResult< export type CreateInfiniteQueryResult< TData = unknown, TError = DefaultError, + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = BaseQueryNarrowing & - MapToSignals> + MapToSignals> export type DefinedCreateInfiniteQueryResult< TData = unknown, TError = DefaultError, + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, TDefinedInfiniteQueryObserver = DefinedInfiniteQueryObserverResult< TData, - TError + TError, + TPageParam, + TMode >, > = MapToSignals diff --git a/packages/preact-query/src/types.ts b/packages/preact-query/src/types.ts index c995a28b08e..7987211aaa3 100644 --- a/packages/preact-query/src/types.ts +++ b/packages/preact-query/src/types.ts @@ -99,13 +99,13 @@ export type AnyUseInfiniteQueryOptions = UseInfiniteQueryOptions< any, any > -export interface UseInfiniteQueryOptions< +export type UseInfiniteQueryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> extends OmitKeyof< +> = DistributiveOmit< InfiniteQueryObserverOptions< TQueryFnData, TError, @@ -114,7 +114,7 @@ export interface UseInfiniteQueryOptions< TPageParam >, 'suspense' -> { +> & { /** * Set this to `false` to unsubscribe this observer from updates to the query cache. * Defaults to `true`. @@ -124,16 +124,16 @@ export interface UseInfiniteQueryOptions< export type AnyUseSuspenseInfiniteQueryOptions = UseSuspenseInfiniteQueryOptions -export interface UseSuspenseInfiniteQueryOptions< +export type UseSuspenseInfiniteQueryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> extends OmitKeyof< +> = DistributiveOmit< UseInfiniteQueryOptions, 'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData' -> { +> & { queryFn?: Exclude< UseInfiniteQueryOptions< TQueryFnData, diff --git a/packages/query-core/src/__tests__/infiniteQueryBehavior.test.tsx b/packages/query-core/src/__tests__/infiniteQueryBehavior.test.tsx index db96ea17da6..d607e34e624 100644 --- a/packages/query-core/src/__tests__/infiniteQueryBehavior.test.tsx +++ b/packages/query-core/src/__tests__/infiniteQueryBehavior.test.tsx @@ -66,7 +66,12 @@ describe('InfiniteQueryBehavior', () => { }) let observerResult: - | InfiniteQueryObserverResult + | InfiniteQueryObserverResult< + InfiniteData, + Error, + number, + 'manual' + > | undefined const unsubscribe = observer.subscribe((result) => { @@ -195,7 +200,124 @@ describe('InfiniteQueryBehavior', () => { unsubscribe() }) - test('should support query cancellation', async () => { + test('InfiniteQueryBehavior should apply pageParam', async () => { + const key = queryKey() + + const queryFn = vi.fn().mockImplementation(({ pageParam }) => { + return pageParam + }) + + const observer = new InfiniteQueryObserver< + number, + Error, + InfiniteData, + typeof key, + number, + 'manual' + >(queryClient, { + queryKey: key, + queryFn, + mode: 'manual', + initialPageParam: 0, + }) + + let observerResult: + | InfiniteQueryObserverResult< + InfiniteData, + Error, + number, + 'manual' + > + | undefined + + const unsubscribe = observer.subscribe((result) => { + observerResult = result + }) + + // Wait for the first page to be fetched + await vi.waitFor(() => + expect(observerResult).toMatchObject({ + isFetching: false, + data: { pages: [0], pageParams: [0] }, + }), + ) + + queryFn.mockClear() + + // Fetch the next page using pageParam + await observer.fetchNextPage({ pageParam: 1 }) + + expect(queryFn).toHaveBeenNthCalledWith(1, { + queryKey: key, + pageParam: 1, + meta: undefined, + client: queryClient, + direction: 'forward', + signal: expect.anything(), + }) + + expect(observerResult).toMatchObject({ + isFetching: false, + data: { pages: [0, 1], pageParams: [0, 1] }, + }) + + queryFn.mockClear() + + // Fetch the previous page using pageParam + await observer.fetchPreviousPage({ pageParam: -1 }) + + expect(queryFn).toHaveBeenNthCalledWith(1, { + queryKey: key, + pageParam: -1, + meta: undefined, + client: queryClient, + direction: 'backward', + signal: expect.anything(), + }) + + expect(observerResult).toMatchObject({ + isFetching: false, + data: { pages: [-1, 0, 1], pageParams: [-1, 0, 1] }, + }) + + queryFn.mockClear() + + // Refetch pages: old manual page params should be used + await observer.refetch() + + expect(queryFn).toHaveBeenCalledTimes(3) + + expect(queryFn).toHaveBeenNthCalledWith(1, { + queryKey: key, + pageParam: -1, + meta: undefined, + client: queryClient, + direction: 'forward', + signal: expect.anything(), + }) + + expect(queryFn).toHaveBeenNthCalledWith(2, { + queryKey: key, + pageParam: 0, + meta: undefined, + client: queryClient, + direction: 'forward', + signal: expect.anything(), + }) + + expect(queryFn).toHaveBeenNthCalledWith(3, { + queryKey: key, + pageParam: 1, + meta: undefined, + client: queryClient, + direction: 'forward', + signal: expect.anything(), + }) + + unsubscribe() + }) + + test('InfiniteQueryBehavior should support query cancellation', async () => { const key = queryKey() let abortSignal: AbortSignal | null = null diff --git a/packages/query-core/src/__tests__/infiniteQueryObserver.test-d.tsx b/packages/query-core/src/__tests__/infiniteQueryObserver.test-d.tsx index 8e22fafde77..3b832065b3f 100644 --- a/packages/query-core/src/__tests__/infiniteQueryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/infiniteQueryObserver.test-d.tsx @@ -72,4 +72,90 @@ describe('InfiniteQueryObserver', () => { expectTypeOf(result.status).toEqualTypeOf<'success'>() } }) + + it('should not allow pageParam on fetchNextPage / fetchPreviousPage if getNextPageParam is defined', async () => { + const observer = new InfiniteQueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: ({ pageParam }) => String(pageParam), + initialPageParam: 1, + getNextPageParam: (page) => Number(page) + 1, + }) + + observer.fetchNextPage() + observer.fetchPreviousPage({ cancelRefetch: false }) + + // @ts-expect-error pageParam is not allowed in declarative mode + observer.fetchNextPage({ pageParam: 2 }) + + // @ts-expect-error pageParam is not allowed in declarative mode + observer.fetchPreviousPage({ pageParam: 0 }) + }) + + it('should require pageParam on fetchNextPage / fetchPreviousPage if getNextPageParam is missing', async () => { + const observer = new InfiniteQueryObserver< + string, + Error, + InfiniteData, + ReturnType, + number, + 'manual' + >(queryClient, { + queryKey: queryKey(), + queryFn: ({ pageParam }) => String(pageParam), + mode: 'manual', + initialPageParam: 1, + }) + + observer.fetchNextPage({ pageParam: 2 }) + observer.fetchPreviousPage({ pageParam: 0, cancelRefetch: false }) + + // @ts-expect-error pageParam is required in manual mode + observer.fetchNextPage() + + // @ts-expect-error pageParam is required in manual mode + observer.fetchPreviousPage() + }) + + it('should reject missing mode / getNextPageParam', () => { + // @ts-expect-error getNextPageParam is required unless mode is manual + new InfiniteQueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: ({ pageParam }) => String(pageParam), + initialPageParam: 1, + }) + }) + + it('should reject page param getters in manual mode', () => { + // @ts-expect-error getNextPageParam is not allowed in manual mode + new InfiniteQueryObserver< + string, + Error, + InfiniteData, + ReturnType, + number, + 'manual' + >(queryClient, { + queryKey: queryKey(), + queryFn: ({ pageParam }) => String(pageParam), + mode: 'manual', + initialPageParam: 1, + getNextPageParam: () => 2, + }) + + // @ts-expect-error getPreviousPageParam is not allowed in manual mode + new InfiniteQueryObserver< + string, + Error, + InfiniteData, + ReturnType, + number, + 'manual' + >(queryClient, { + queryKey: queryKey(), + queryFn: ({ pageParam }) => String(pageParam), + mode: 'manual', + initialPageParam: 1, + getPreviousPageParam: () => 0, + }) + }) }) diff --git a/packages/query-core/src/__tests__/queryClient.test-d.tsx b/packages/query-core/src/__tests__/queryClient.test-d.tsx index 8a3be1a9e23..249efab5772 100644 --- a/packages/query-core/src/__tests__/queryClient.test-d.tsx +++ b/packages/query-core/src/__tests__/queryClient.test-d.tsx @@ -171,26 +171,51 @@ describe('fetchInfiniteQuery', () => { }) it('should not allow passing getNextPageParam without pages', () => { - assertType>([ - { - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - initialPageParam: 1, - getNextPageParam: () => 1, - }, - ]) + void new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + getNextPageParam: () => 1, + }) }) it('should not allow passing pages without getNextPageParam', () => { - assertType>([ - // @ts-expect-error Property 'getNextPageParam' is missing - { - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - initialPageParam: 1, - pages: 5, - }, - ]) + // @ts-expect-error Property 'getNextPageParam' is missing + void new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + pages: 5, + }) + }) + + it('should allow manual mode without page param getters', () => { + void new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + mode: 'manual', + initialPageParam: 1, + }) + }) + + it('should not allow manual mode with page param getters or pages', () => { + // @ts-expect-error getNextPageParam is not allowed in manual mode + void new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + mode: 'manual', + initialPageParam: 1, + getNextPageParam: () => 1, + }) + + // @ts-expect-error pages are not allowed in manual mode + void new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + mode: 'manual', + initialPageParam: 1, + pages: 2, + }) }) }) diff --git a/packages/query-core/src/__tests__/queryClient.test.tsx b/packages/query-core/src/__tests__/queryClient.test.tsx index 2676717fb62..03f1ba853a5 100644 --- a/packages/query-core/src/__tests__/queryClient.test.tsx +++ b/packages/query-core/src/__tests__/queryClient.test.tsx @@ -810,7 +810,12 @@ describe('queryClient', () => { StrictData, StrictQueryKey, number - >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }), + >({ + queryKey: key, + queryFn: fetchFn, + initialPageParam: 0, + mode: 'manual', + }), ).resolves.toEqual(data) }) @@ -819,6 +824,7 @@ describe('queryClient', () => { const result = await queryClient.fetchInfiniteQuery({ queryKey: key, initialPageParam: 10, + mode: 'manual', queryFn: ({ pageParam }) => Number(pageParam), }) const result2 = queryClient.getQueryData(key) @@ -848,7 +854,12 @@ describe('queryClient', () => { StrictData, StrictQueryKey, number - >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }) + >({ + queryKey: key, + queryFn: fetchFn, + initialPageParam: 0, + mode: 'manual', + }) const result = queryClient.getQueryData(key) @@ -865,6 +876,7 @@ describe('queryClient', () => { queryKey: key, queryFn: ({ pageParam }) => Number(pageParam), initialPageParam: 10, + mode: 'manual', }) const result = queryClient.getQueryData(key) @@ -1005,7 +1017,7 @@ describe('queryClient', () => { await queryClient.cancelQueries() - // with previous data present, imperative fetch should resolve to that data after cancel + // with previous data present, manual fetch should resolve to that data after cancel await expect(pending).resolves.toBe('data') const state1 = queryClient.getQueryState(key1) @@ -1033,7 +1045,7 @@ describe('queryClient', () => { }) }) - test('should throw CancelledError for imperative methods when initial fetch is cancelled', async () => { + test('should throw CancelledError for manual methods when initial fetch is cancelled', async () => { const key = queryKey() const promise = queryClient.fetchQuery({ diff --git a/packages/query-core/src/infiniteQueryBehavior.ts b/packages/query-core/src/infiniteQueryBehavior.ts index af9c50e5503..b6578a2ca9c 100644 --- a/packages/query-core/src/infiniteQueryBehavior.ts +++ b/packages/query-core/src/infiniteQueryBehavior.ts @@ -4,22 +4,33 @@ import { addToStart, ensureQueryFn, } from './utils' -import type { QueryBehavior } from './query' import type { + FetchPageDirectionMode, InfiniteData, InfiniteQueryPageParamsOptions, OmitKeyof, QueryFunctionContext, QueryKey, } from './types' +import type { QueryBehavior } from './query' -export function infiniteQueryBehavior( +export function infiniteQueryBehavior< + TQueryFnData, + TError, + TData, + TPageParam, + TMode extends FetchPageDirectionMode = FetchPageDirectionMode, +>( pages?: number, ): QueryBehavior> { return { onFetch: (context, query) => { - const options = context.options as InfiniteQueryPageParamsOptions - const direction = context.fetchOptions?.meta?.fetchMore?.direction + const options = context.options as InfiniteQueryPageParamsOptions< + TQueryFnData, + TPageParam, + TMode + > + const fetchMore = context.fetchOptions?.meta?.fetchMore const oldPages = context.state.data?.pages || [] const oldPageParams = context.state.data?.pageParams || [] let result: InfiniteData = { pages: [], pageParams: [] } @@ -80,14 +91,17 @@ export function infiniteQueryBehavior( } // fetch next / previous page? - if (direction && oldPages.length) { - const previous = direction === 'backward' + if (fetchMore && oldPages.length) { + const previous = fetchMore.direction === 'backward' const pageParamFn = previous ? getPreviousPageParam : getNextPageParam const oldData = { pages: oldPages, pageParams: oldPageParams, } - const param = pageParamFn(options, oldData) + const param = + fetchMore.pageParam === undefined + ? pageParamFn(options, oldData) + : fetchMore.pageParam result = await fetchPage(oldData, param, previous) } else { @@ -96,8 +110,8 @@ export function infiniteQueryBehavior( // Fetch all pages do { const param = - currentPage === 0 - ? (oldPageParams[0] ?? options.initialPageParam) + currentPage === 0 || !options.getNextPageParam + ? (oldPageParams[currentPage] ?? options.initialPageParam) : getNextPageParam(options, result) if (currentPage > 0 && param == null) { break @@ -130,12 +144,12 @@ export function infiniteQueryBehavior( } function getNextPageParam( - options: InfiniteQueryPageParamsOptions, + options: InfiniteQueryPageParamsOptions, { pages, pageParams }: InfiniteData, ): unknown | undefined { const lastIndex = pages.length - 1 return pages.length > 0 - ? options.getNextPageParam( + ? options.getNextPageParam?.( pages[lastIndex], pages, pageParams[lastIndex], @@ -145,7 +159,7 @@ function getNextPageParam( } function getPreviousPageParam( - options: InfiniteQueryPageParamsOptions, + options: InfiniteQueryPageParamsOptions, { pages, pageParams }: InfiniteData, ): unknown | undefined { return pages.length > 0 @@ -157,7 +171,7 @@ function getPreviousPageParam( * Checks if there is a next page. */ export function hasNextPage( - options: InfiniteQueryPageParamsOptions, + options: InfiniteQueryPageParamsOptions, data?: InfiniteData, ): boolean { if (!data) return false @@ -168,9 +182,11 @@ export function hasNextPage( * Checks if there is a previous page. */ export function hasPreviousPage( - options: InfiniteQueryPageParamsOptions, + options: InfiniteQueryPageParamsOptions, data?: InfiniteData, ): boolean { - if (!data || !options.getPreviousPageParam) return false + if (!data) { + return false + } return getPreviousPageParam(options, data) != null } diff --git a/packages/query-core/src/infiniteQueryObserver.ts b/packages/query-core/src/infiniteQueryObserver.ts index 1499b138169..2443f4af15d 100644 --- a/packages/query-core/src/infiniteQueryObserver.ts +++ b/packages/query-core/src/infiniteQueryObserver.ts @@ -7,20 +7,28 @@ import { import type { Subscribable } from './subscribable' import type { DefaultError, - DefaultedInfiniteQueryObserverOptions, - FetchNextPageOptions, - FetchPreviousPageOptions, + DefaultedQueryObserverOptions, + FetchPageDirectionMode, InfiniteData, - InfiniteQueryObserverBaseResult, + InfiniteQueryFetchNextPageArgs, + InfiniteQueryFetchPreviousPageArgs, + InfiniteQueryMode, InfiniteQueryObserverOptions, + InfiniteQueryObserverOptionsBase, InfiniteQueryObserverResult, QueryKey, + QueryObserverOptions, } from './types' import type { QueryClient } from './queryClient' import type { Query } from './query' -type InfiniteQueryObserverListener = ( - result: InfiniteQueryObserverResult, +type InfiniteQueryObserverListener< + TData, + TError, + TPageParam, + TMode extends FetchPageDirectionMode, +> = ( + result: InfiniteQueryObserverResult, ) => void export class InfiniteQueryObserver< @@ -29,6 +37,7 @@ export class InfiniteQueryObserver< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, > extends QueryObserver< TQueryFnData, TError, @@ -38,7 +47,7 @@ export class InfiniteQueryObserver< > { // Type override subscribe!: Subscribable< - InfiniteQueryObserverListener + InfiniteQueryObserverListener >['subscribe'] // Type override @@ -50,7 +59,7 @@ export class InfiniteQueryObserver< InfiniteData, TQueryKey >['getCurrentResult'], - InfiniteQueryObserverResult + InfiniteQueryObserverResult > // Type override @@ -62,7 +71,7 @@ export class InfiniteQueryObserver< InfiniteData, TQueryKey >['fetch'], - Promise> + Promise> > constructor( @@ -72,7 +81,30 @@ export class InfiniteQueryObserver< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined + >, + ) + constructor( + client: QueryClient, + options: InfiniteQueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + >, + ) + constructor( + client: QueryClient, + options: InfiniteQueryObserverOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + TMode >, ) { super(client, options) @@ -85,12 +117,12 @@ export class InfiniteQueryObserver< } setOptions( - options: InfiniteQueryObserverOptions< + options: QueryObserverOptions< TQueryFnData, TError, TData, - TQueryKey, - TPageParam + InfiniteData, + TQueryKey >, ): void { super.setOptions({ @@ -100,39 +132,43 @@ export class InfiniteQueryObserver< } getOptimisticResult( - options: DefaultedInfiniteQueryObserverOptions< + options: DefaultedQueryObserverOptions< TQueryFnData, TError, TData, - TQueryKey, - TPageParam + InfiniteData, + TQueryKey >, - ): InfiniteQueryObserverResult { + ): InfiniteQueryObserverResult { options.behavior = infiniteQueryBehavior() return super.getOptimisticResult(options) as InfiniteQueryObserverResult< TData, - TError + TError, + TPageParam, + TMode > } fetchNextPage( - options?: FetchNextPageOptions, - ): Promise> { + ...args: InfiniteQueryFetchNextPageArgs + ): Promise> { + const { pageParam, ...options } = args[0] ?? ({} as any) return this.fetch({ ...options, meta: { - fetchMore: { direction: 'forward' }, + fetchMore: { direction: 'forward', pageParam }, }, }) } fetchPreviousPage( - options?: FetchPreviousPageOptions, - ): Promise> { + ...args: InfiniteQueryFetchPreviousPageArgs + ): Promise> { + const { pageParam, ...options } = args[0] ?? ({} as any) return this.fetch({ ...options, meta: { - fetchMore: { direction: 'backward' }, + fetchMore: { direction: 'backward', pageParam }, }, }) } @@ -144,14 +180,14 @@ export class InfiniteQueryObserver< InfiniteData, TQueryKey >, - options: InfiniteQueryObserverOptions< + options: QueryObserverOptions< TQueryFnData, TError, TData, - TQueryKey, - TPageParam + InfiniteData, + TQueryKey >, - ): InfiniteQueryObserverResult { + ): InfiniteQueryObserverResult { const { state } = query const parentResult = super.createResult(query, options) @@ -164,12 +200,12 @@ export class InfiniteQueryObserver< const isFetchPreviousPageError = isError && fetchDirection === 'backward' const isFetchingPreviousPage = isFetching && fetchDirection === 'backward' - const result: InfiniteQueryObserverBaseResult = { + const result = { ...parentResult, fetchNextPage: this.fetchNextPage, fetchPreviousPage: this.fetchPreviousPage, - hasNextPage: hasNextPage(options, state.data), - hasPreviousPage: hasPreviousPage(options, state.data), + hasNextPage: hasNextPage(options as any, state.data), + hasPreviousPage: hasPreviousPage(options as any, state.data), isFetchNextPageError, isFetchingNextPage, isFetchPreviousPageError, @@ -180,7 +216,12 @@ export class InfiniteQueryObserver< isRefetching && !isFetchingNextPage && !isFetchingPreviousPage, } - return result as InfiniteQueryObserverResult + return result as InfiniteQueryObserverResult< + TData, + TError, + TPageParam, + TMode + > } } diff --git a/packages/query-core/src/query.ts b/packages/query-core/src/query.ts index 7dfaa587721..80c441a46ee 100644 --- a/packages/query-core/src/query.ts +++ b/packages/query-core/src/query.ts @@ -90,7 +90,7 @@ export interface QueryBehavior< export type FetchDirection = 'forward' | 'backward' export interface FetchMeta { - fetchMore?: { direction: FetchDirection } + fetchMore?: { direction: FetchDirection; pageParam?: unknown } } export interface FetchOptions { diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 80cc36668aa..e3b22034b70 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -120,7 +120,7 @@ export class QueryClient { } /** - * Imperative (non-reactive) way to retrieve data for a QueryKey. + * Direct (non-reactive) way to retrieve data for a QueryKey. * Should only be used in callbacks or functions where reading the latest data is necessary, e.g. for optimistic updates. * * Hint: Do not use this function inside a component, because it won't receive updates. @@ -419,7 +419,9 @@ export class QueryClient { TPageParam >, ): Promise { - return this.fetchInfiniteQuery(options).then(noop).catch(noop) + return this.fetchInfiniteQuery(options as any) + .then(noop) + .catch(noop) } ensureInfiniteQueryData< diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 4f3f4caed20..32552343d7d 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -281,10 +281,13 @@ export interface InitialPageParam { initialPageParam: TPageParam } -export interface InfiniteQueryPageParamsOptions< +export type InfiniteQueryMode = 'manual' + +export interface InfiniteQueryPageParamsDeclarativeOptions< TQueryFnData = unknown, TPageParam = unknown, > extends InitialPageParam { + mode?: never /** * This function can be set to automatically get the previous cursor for infinite queries. * The result will also be used to determine the value of `hasPreviousPage`. @@ -297,6 +300,33 @@ export interface InfiniteQueryPageParamsOptions< getNextPageParam: GetNextPageParamFunction } +export interface InfiniteQueryPageParamsManualOptions< + TPageParam = unknown, +> extends InitialPageParam { + mode: InfiniteQueryMode + getPreviousPageParam?: never + getNextPageParam?: never +} + +export type InfiniteQueryPageParamsOptions< + TQueryFnData = unknown, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = FetchPageDirectionMode, +> = TMode extends FetchPageDirectionMode + ? TMode extends InfiniteQueryMode + ? InfiniteQueryPageParamsManualOptions + : InfiniteQueryPageParamsDeclarativeOptions + : never + +export type FetchPageDirectionMode = InfiniteQueryMode | undefined + +export interface ManualFetchPageOptions { + /** + * The page param to pass to the query function for this manual fetch. + */ + pageParam: TPageParam +} + export type ThrowOnError< TQueryFnData, TError, @@ -455,41 +485,78 @@ export type DefaultedQueryObserverOptions< 'throwOnError' | 'refetchOnReconnect' | 'queryHash' > -export interface InfiniteQueryObserverOptions< +export type InfiniteQueryObserverOptionsBase< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> - extends - QueryObserverOptions< + TMode extends FetchPageDirectionMode = undefined, +> = QueryObserverOptions< + TQueryFnData, + TError, + TData, + InfiniteData, + TQueryKey, + TPageParam +> & + InfiniteQueryPageParamsOptions + +export type InfiniteQueryObserverOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = FetchPageDirectionMode, +> = TMode extends FetchPageDirectionMode + ? InfiniteQueryObserverOptionsBase< TQueryFnData, TError, TData, - InfiniteData, TQueryKey, - TPageParam - >, - InfiniteQueryPageParamsOptions {} + TPageParam, + TMode + > + : never -export type DefaultedInfiniteQueryObserverOptions< +export type DefaultedInfiniteQueryObserverOptionsBase< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, > = WithRequired< - InfiniteQueryObserverOptions< + InfiniteQueryObserverOptionsBase< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode >, 'throwOnError' | 'refetchOnReconnect' | 'queryHash' > +export type DefaultedInfiniteQueryObserverOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = FetchPageDirectionMode, +> = TMode extends FetchPageDirectionMode + ? DefaultedInfiniteQueryObserverOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + TMode + > + : never + export interface FetchQueryOptions< TQueryFnData = unknown, TError = DefaultError, @@ -524,35 +591,93 @@ export interface EnsureQueryDataOptions< revalidateIfStale?: boolean } -export type EnsureInfiniteQueryDataOptions< +export type EnsureInfiniteQueryDataOptionsBase< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> = FetchInfiniteQueryOptions< + TMode extends FetchPageDirectionMode = undefined, +> = FetchInfiniteQueryOptionsBase< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode > & { revalidateIfStale?: boolean } -type FetchInfiniteQueryPages = +export type EnsureInfiniteQueryDataOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = FetchPageDirectionMode, +> = TMode extends FetchPageDirectionMode + ? EnsureInfiniteQueryDataOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + TMode + > + : never + +type FetchInfiniteQueryPagesDeclarative< + TQueryFnData = unknown, + TPageParam = unknown, +> = | { pages?: never } | { pages: number getNextPageParam: GetNextPageParamFunction } -export type FetchInfiniteQueryOptions< +interface FetchInfiniteQueryPageParamsDeclarativeOptions< + TQueryFnData = unknown, + TPageParam = unknown, +> extends InitialPageParam { + mode?: never + getPreviousPageParam?: GetPreviousPageParamFunction + getNextPageParam?: GetNextPageParamFunction +} + +type FetchInfiniteQueryPageParamsOptions< + TQueryFnData = unknown, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = FetchPageDirectionMode, +> = TMode extends FetchPageDirectionMode + ? TMode extends InfiniteQueryMode + ? InfiniteQueryPageParamsManualOptions + : FetchInfiniteQueryPageParamsDeclarativeOptions + : never + +type FetchInfiniteQueryPages< + TQueryFnData = unknown, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = FetchPageDirectionMode, +> = TMode extends FetchPageDirectionMode + ? TMode extends InfiniteQueryMode + ? { + mode: InfiniteQueryMode + pages?: never + getNextPageParam?: never + getPreviousPageParam?: never + } + : FetchInfiniteQueryPagesDeclarative + : never + +export type FetchInfiniteQueryOptionsBase< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, > = Omit< FetchQueryOptions< TQueryFnData, @@ -563,8 +688,26 @@ export type FetchInfiniteQueryOptions< >, 'initialPageParam' > & - InitialPageParam & - FetchInfiniteQueryPages + FetchInfiniteQueryPageParamsOptions & + FetchInfiniteQueryPages + +export type FetchInfiniteQueryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = FetchPageDirectionMode, +> = TMode extends FetchPageDirectionMode + ? FetchInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + TMode + > + : never export interface ResultOptions { throwOnError?: boolean @@ -618,6 +761,34 @@ export interface FetchPreviousPageOptions extends ResultOptions { cancelRefetch?: boolean } +export type InfiniteQueryFetchNextPageOptions< + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> = TMode extends InfiniteQueryMode + ? ManualFetchPageOptions & FetchNextPageOptions + : FetchNextPageOptions + +export type InfiniteQueryFetchPreviousPageOptions< + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> = TMode extends InfiniteQueryMode + ? ManualFetchPageOptions & FetchPreviousPageOptions + : FetchPreviousPageOptions + +export type InfiniteQueryFetchNextPageArgs< + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> = TMode extends InfiniteQueryMode + ? [options: InfiniteQueryFetchNextPageOptions] + : [options?: InfiniteQueryFetchNextPageOptions] + +export type InfiniteQueryFetchPreviousPageArgs< + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> = TMode extends InfiniteQueryMode + ? [options: InfiniteQueryFetchPreviousPageOptions] + : [options?: InfiniteQueryFetchPreviousPageOptions] + export type QueryStatus = 'pending' | 'error' | 'success' export type FetchStatus = 'fetching' | 'paused' | 'idle' @@ -910,19 +1081,21 @@ export type QueryObserverResult = export interface InfiniteQueryObserverBaseResult< TData = unknown, TError = DefaultError, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, > extends QueryObserverBaseResult { /** * This function allows you to fetch the next "page" of results. */ fetchNextPage: ( - options?: FetchNextPageOptions, - ) => Promise> + ...args: InfiniteQueryFetchNextPageArgs + ) => Promise> /** * This function allows you to fetch the previous "page" of results. */ fetchPreviousPage: ( - options?: FetchPreviousPageOptions, - ) => Promise> + ...args: InfiniteQueryFetchPreviousPageArgs + ) => Promise> /** * Will be `true` if there is a next page to be fetched (known via the `getNextPageParam` option). */ @@ -952,7 +1125,9 @@ export interface InfiniteQueryObserverBaseResult< export interface InfiniteQueryObserverPendingResult< TData = unknown, TError = DefaultError, -> extends InfiniteQueryObserverBaseResult { + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> extends InfiniteQueryObserverBaseResult { data: undefined error: null isError: false @@ -969,7 +1144,9 @@ export interface InfiniteQueryObserverPendingResult< export interface InfiniteQueryObserverLoadingResult< TData = unknown, TError = DefaultError, -> extends InfiniteQueryObserverBaseResult { + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> extends InfiniteQueryObserverBaseResult { data: undefined error: null isError: false @@ -987,7 +1164,9 @@ export interface InfiniteQueryObserverLoadingResult< export interface InfiniteQueryObserverLoadingErrorResult< TData = unknown, TError = DefaultError, -> extends InfiniteQueryObserverBaseResult { + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> extends InfiniteQueryObserverBaseResult { data: undefined error: TError isError: true @@ -1005,7 +1184,9 @@ export interface InfiniteQueryObserverLoadingErrorResult< export interface InfiniteQueryObserverRefetchErrorResult< TData = unknown, TError = DefaultError, -> extends InfiniteQueryObserverBaseResult { + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> extends InfiniteQueryObserverBaseResult { data: TData error: TError isError: true @@ -1021,7 +1202,9 @@ export interface InfiniteQueryObserverRefetchErrorResult< export interface InfiniteQueryObserverSuccessResult< TData = unknown, TError = DefaultError, -> extends InfiniteQueryObserverBaseResult { + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> extends InfiniteQueryObserverBaseResult { data: TData error: null isError: false @@ -1039,7 +1222,9 @@ export interface InfiniteQueryObserverSuccessResult< export interface InfiniteQueryObserverPlaceholderResult< TData = unknown, TError = DefaultError, -> extends InfiniteQueryObserverBaseResult { + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, +> extends InfiniteQueryObserverBaseResult { data: TData isError: false error: null @@ -1057,19 +1242,23 @@ export interface InfiniteQueryObserverPlaceholderResult< export type DefinedInfiniteQueryObserverResult< TData = unknown, TError = DefaultError, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, > = - | InfiniteQueryObserverRefetchErrorResult - | InfiniteQueryObserverSuccessResult + | InfiniteQueryObserverRefetchErrorResult + | InfiniteQueryObserverSuccessResult export type InfiniteQueryObserverResult< TData = unknown, TError = DefaultError, + TPageParam = unknown, + TMode extends FetchPageDirectionMode = undefined, > = - | DefinedInfiniteQueryObserverResult - | InfiniteQueryObserverLoadingErrorResult - | InfiniteQueryObserverLoadingResult - | InfiniteQueryObserverPendingResult - | InfiniteQueryObserverPlaceholderResult + | DefinedInfiniteQueryObserverResult + | InfiniteQueryObserverLoadingErrorResult + | InfiniteQueryObserverLoadingResult + | InfiniteQueryObserverPendingResult + | InfiniteQueryObserverPlaceholderResult export type MutationKey = Register extends { mutationKey: infer TMutationKey diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx index a1d97bf0927..afcd4db2720 100644 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx @@ -39,16 +39,21 @@ describe('infiniteQueryOptions', () => { const options = infiniteQueryOptions({ queryKey: ['key'], queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, initialPageParam: 1, + mode: 'manual', }) - const { data } = useInfiniteQuery(options) + const { data, fetchNextPage, fetchPreviousPage } = useInfiniteQuery(options) // known issue: type of pageParams is unknown when returned from useInfiniteQuery expectTypeOf(data).toEqualTypeOf< InfiniteData | undefined >() + fetchNextPage({ pageParam: 2 }) + fetchPreviousPage({ pageParam: 0 }) + + // @ts-expect-error pageParam is required in manual mode + fetchNextPage() }) it('should work when passed to useSuspenseInfiniteQuery', () => { const options = infiniteQueryOptions({ @@ -66,8 +71,8 @@ describe('infiniteQueryOptions', () => { const options = infiniteQueryOptions({ queryKey: ['key'], queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, initialPageParam: 1, + mode: 'manual', }) const data = await new QueryClient().fetchInfiniteQuery(options) @@ -153,6 +158,33 @@ describe('infiniteQueryOptions', () => { expectTypeOf(data).toEqualTypeOf>() }) + it('should reject missing mode / getNextPageParam and reject getters in manual mode', () => { + // @ts-expect-error getNextPageParam is required unless mode is manual + infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + }) + + // @ts-expect-error getNextPageParam is not allowed in manual mode + infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + mode: 'manual', + getNextPageParam: () => 1, + }) + + // @ts-expect-error getPreviousPageParam is not allowed in manual mode + infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + mode: 'manual', + getPreviousPageParam: () => 0, + }) + }) + test('should not be allowed to be passed to non-infinite query functions', () => { const queryClient = new QueryClient() const options = infiniteQueryOptions({ diff --git a/packages/react-query/src/__tests__/ssr.test.tsx b/packages/react-query/src/__tests__/ssr.test.tsx index 3cd16c7ee92..7c084a39df4 100644 --- a/packages/react-query/src/__tests__/ssr.test.tsx +++ b/packages/react-query/src/__tests__/ssr.test.tsx @@ -156,6 +156,7 @@ describe('Server Side Rendering', () => { queryKey: key, queryFn, initialPageParam: 0, + mode: 'manual', }) await vi.advanceTimersByTimeAsync(10) diff --git a/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx b/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx index a231d206008..d7defd44761 100644 --- a/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx +++ b/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx @@ -34,6 +34,7 @@ describe('pageParam', () => { expectTypeOf(pageParam).toEqualTypeOf() }, initialPageParam: 1, + mode: 'manual', }) }) @@ -45,8 +46,27 @@ describe('pageParam', () => { expectTypeOf(pageParam).toEqualTypeOf() }, initialPageParam: 1, + mode: 'manual', }) }) + + it('should require pageParam on manual fetch methods', () => { + const infiniteQuery = useInfiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + return pageParam * 5 + }, + initialPageParam: 1, + mode: 'manual', + }) + + infiniteQuery.fetchNextPage({ pageParam: 2 }) + infiniteQuery.fetchPreviousPage({ pageParam: 0 }) + + // @ts-expect-error pageParam is required in manual mode + infiniteQuery.fetchNextPage() + }) }) describe('select', () => { it('should still return paginated data if no select result', () => { @@ -116,6 +136,27 @@ describe('getNextPageParam / getPreviousPageParam', () => { InfiniteData | undefined >() }) + + it('should infer async object page types for getNextPageParam', () => { + useInfiniteQuery({ + queryKey: ['key'], + queryFn: async ({ pageParam = 0 }) => { + return { + nextCursor: pageParam + 1, + data: `page-${pageParam}`, + } + }, + initialPageParam: 0, + getNextPageParam: (lastPage) => { + expectTypeOf(lastPage).toEqualTypeOf<{ + nextCursor: number + data: string + }>() + return lastPage.nextCursor + }, + retry: false, + }) + }) }) describe('error booleans', () => { diff --git a/packages/react-query/src/infiniteQueryOptions.ts b/packages/react-query/src/infiniteQueryOptions.ts index 5e8c371a59f..df7fab28ad2 100644 --- a/packages/react-query/src/infiniteQueryOptions.ts +++ b/packages/react-query/src/infiniteQueryOptions.ts @@ -2,13 +2,47 @@ import type { DataTag, DefaultError, InfiniteData, + InfiniteQueryMode, InitialDataFunction, NonUndefinedGuard, OmitKeyof, QueryKey, SkipToken, } from '@tanstack/query-core' -import type { UseInfiniteQueryOptions } from './types' +import type { + UseInfiniteQueryOptions, + UseInfiniteQueryOptionsBase, +} from './types' + +type OptionalInitialData = { + initialData?: + | undefined + | NonUndefinedGuard> + | InitialDataFunction< + NonUndefinedGuard> + > +} + +type RequiredInitialData = { + initialData: + | NonUndefinedGuard> + | (() => NonUndefinedGuard>) + | undefined +} + +type WithoutSkipTokenQueryFn = + OmitKeyof & { + queryFn?: Exclude + } + +type TaggedInfiniteQueryOptions< + TOptions, + TQueryKey extends QueryKey, + TQueryFnData, + TError, +> = TOptions & { + queryKey: DataTag, TError> +} export type UndefinedInitialDataInfiniteOptions< TQueryFnData, @@ -16,20 +50,49 @@ export type UndefinedInitialDataInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = InfiniteQueryMode | undefined, > = UseInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam -> & { - initialData?: - | undefined - | NonUndefinedGuard> - | InitialDataFunction< - NonUndefinedGuard> - > -} + TPageParam, + TMode +> & + OptionalInitialData + +export type ManualUndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = UseInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode +> & + OptionalInitialData + +export type UnusedSkipTokenManualInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = WithoutSkipTokenQueryFn< + UseInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + > +> export type UnusedSkipTokenInfiniteOptions< TQueryFnData, @@ -37,21 +100,17 @@ export type UnusedSkipTokenInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> = OmitKeyof< - UseInfiniteQueryOptions, - 'queryFn' -> & { - queryFn?: Exclude< - UseInfiniteQueryOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam - >['queryFn'], - SkipToken | undefined + TMode extends InfiniteQueryMode | undefined = InfiniteQueryMode | undefined, +> = WithoutSkipTokenQueryFn< + UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + TMode > -} +> export type DefinedInitialDataInfiniteOptions< TQueryFnData, @@ -59,18 +118,32 @@ export type DefinedInitialDataInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = InfiniteQueryMode | undefined, > = UseInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam -> & { - initialData: - | NonUndefinedGuard> - | (() => NonUndefinedGuard>) - | undefined -} + TPageParam, + TMode +> & + RequiredInitialData + +export type ManualDefinedInitialDataInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = UseInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode +> & + RequiredInitialData export function infiniteQueryOptions< TQueryFnData, @@ -84,17 +157,41 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined >, ): DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined +> & + TaggedInfiniteQueryOptions<{}, TQueryKey, TQueryFnData, TError> + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: ManualDefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): ManualDefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, TQueryKey, TPageParam -> & { - queryKey: DataTag, TError> -} +> & + TaggedInfiniteQueryOptions<{}, TQueryKey, TQueryFnData, TError> export function infiniteQueryOptions< TQueryFnData, @@ -108,17 +205,112 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined >, ): UnusedSkipTokenInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined +> & + TaggedInfiniteQueryOptions<{}, TQueryKey, TQueryFnData, TError> + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UnusedSkipTokenManualInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): UnusedSkipTokenManualInfiniteOptions< TQueryFnData, TError, TData, TQueryKey, TPageParam -> & { - queryKey: DataTag, TError> -} +> & + TaggedInfiniteQueryOptions<{}, TQueryKey, TQueryFnData, TError> + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >, +): UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined +> & + TaggedInfiniteQueryOptions<{}, TQueryKey, TQueryFnData, TError> + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: ManualUndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): ManualUndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> & + TaggedInfiniteQueryOptions<{}, TQueryKey, TQueryFnData, TError> + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> & + TaggedInfiniteQueryOptions<{}, TQueryKey, TQueryFnData, TError> export function infiniteQueryOptions< TQueryFnData, @@ -140,9 +332,8 @@ export function infiniteQueryOptions< TData, TQueryKey, TPageParam -> & { - queryKey: DataTag, TError> -} +> & + TaggedInfiniteQueryOptions<{}, TQueryKey, TQueryFnData, TError> export function infiniteQueryOptions(options: unknown) { return options diff --git a/packages/react-query/src/types.ts b/packages/react-query/src/types.ts index 50df2d333f2..180e2af6e25 100644 --- a/packages/react-query/src/types.ts +++ b/packages/react-query/src/types.ts @@ -6,6 +6,7 @@ import type { DefinedQueryObserverResult, DistributiveOmit, FetchQueryOptions, + InfiniteQueryMode, InfiniteQueryObserverOptions, InfiniteQueryObserverResult, MutateFunction, @@ -98,54 +99,110 @@ export type AnyUseInfiniteQueryOptions = UseInfiniteQueryOptions< any, any, any, + any, any > -export interface UseInfiniteQueryOptions< +export type UseInfiniteQueryOptionsBase< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> extends OmitKeyof< + TMode extends InfiniteQueryMode | undefined = undefined, +> = DistributiveOmit< InfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode >, 'suspense' -> { - /** - * Set this to `false` to unsubscribe this observer from updates to the query cache. - * Defaults to `true`. - */ +> & { subscribed?: boolean } -export type AnyUseSuspenseInfiniteQueryOptions = - UseSuspenseInfiniteQueryOptions -export interface UseSuspenseInfiniteQueryOptions< +export type UseInfiniteQueryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> extends OmitKeyof< - UseInfiniteQueryOptions, - 'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData' -> { - queryFn?: Exclude< - UseInfiniteQueryOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam - >['queryFn'], - SkipToken - > -} + TMode extends InfiniteQueryMode | undefined = InfiniteQueryMode | undefined, +> = UseInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + TMode +> + +export type UseSuspenseInfiniteQueryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = + | (DistributiveOmit< + UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >, + 'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData' + > & { + /** + * Set this to `false` to unsubscribe this observer from updates to the query cache. + * Defaults to `true`. + */ + queryFn?: Exclude< + UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >['queryFn'], + SkipToken + > + }) + | (DistributiveOmit< + UseInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + >, + 'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData' + > & { + /** + * Set this to `false` to unsubscribe this observer from updates to the query cache. + * Defaults to `true`. + */ + queryFn?: Exclude< + UseInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + >['queryFn'], + SkipToken + > + }) + +export type AnyUseSuspenseInfiniteQueryOptions = + UseSuspenseInfiniteQueryOptions export type UseBaseQueryResult< TData = unknown, @@ -173,18 +230,24 @@ export type DefinedUseQueryResult< export type UseInfiniteQueryResult< TData = unknown, TError = DefaultError, -> = InfiniteQueryObserverResult + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, +> = InfiniteQueryObserverResult export type DefinedUseInfiniteQueryResult< TData = unknown, TError = DefaultError, -> = DefinedInfiniteQueryObserverResult + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, +> = DefinedInfiniteQueryObserverResult export type UseSuspenseInfiniteQueryResult< TData = unknown, TError = DefaultError, + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = OmitKeyof< - DefinedInfiniteQueryObserverResult, + DefinedInfiniteQueryObserverResult, 'isPlaceholderData' | 'promise' > diff --git a/packages/react-query/src/useInfiniteQuery.ts b/packages/react-query/src/useInfiniteQuery.ts index 32ebfb7673e..44dfa296b5d 100644 --- a/packages/react-query/src/useInfiniteQuery.ts +++ b/packages/react-query/src/useInfiniteQuery.ts @@ -4,6 +4,7 @@ import { useBaseQuery } from './useBaseQuery' import type { DefaultError, InfiniteData, + InfiniteQueryMode, QueryClient, QueryKey, QueryObserver, @@ -15,6 +16,8 @@ import type { } from './types' import type { DefinedInitialDataInfiniteOptions, + ManualDefinedInitialDataInfiniteOptions, + ManualUndefinedInitialDataInfiniteOptions, UndefinedInitialDataInfiniteOptions, } from './infiniteQueryOptions' @@ -26,6 +29,24 @@ export function useInfiniteQuery< TPageParam = unknown, >( options: DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >, + queryClient?: QueryClient, +): DefinedUseInfiniteQueryResult + +export function useInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: ManualDefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, @@ -33,7 +54,7 @@ export function useInfiniteQuery< TPageParam >, queryClient?: QueryClient, -): DefinedUseInfiniteQueryResult +): DefinedUseInfiniteQueryResult export function useInfiniteQuery< TQueryFnData, @@ -47,10 +68,11 @@ export function useInfiniteQuery< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined >, queryClient?: QueryClient, -): UseInfiniteQueryResult +): UseInfiniteQueryResult export function useInfiniteQuery< TQueryFnData, @@ -59,7 +81,7 @@ export function useInfiniteQuery< TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( - options: UseInfiniteQueryOptions< + options: ManualUndefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, @@ -67,7 +89,7 @@ export function useInfiniteQuery< TPageParam >, queryClient?: QueryClient, -): UseInfiniteQueryResult +): UseInfiniteQueryResult export function useInfiniteQuery( options: UseInfiniteQueryOptions, diff --git a/packages/solid-query/src/QueryClient.ts b/packages/solid-query/src/QueryClient.ts index 998c92b808e..aee191ecc37 100644 --- a/packages/solid-query/src/QueryClient.ts +++ b/packages/solid-query/src/QueryClient.ts @@ -2,6 +2,7 @@ import { QueryClient as QueryCoreClient } from '@tanstack/query-core' import type { DefaultOptions as CoreDefaultOptions, DefaultError, + InfiniteQueryMode, OmitKeyof, QueryClientConfig as QueryCoreClientConfig, InfiniteQueryObserverOptions as QueryCoreInfiniteQueryObserverOptions, @@ -39,22 +40,24 @@ export interface QueryObserverOptions< | ((oldData: TData | undefined, newData: TData) => TData) } -export interface InfiniteQueryObserverOptions< +export type InfiniteQueryObserverOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> extends OmitKeyof< + TMode extends InfiniteQueryMode | undefined = undefined, +> = OmitKeyof< QueryCoreInfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode >, 'structuralSharing' -> { +> & { /** * Set this to a reconciliation key to enable reconciliation between query results. * Set this to `false` to disable reconciliation between query results. diff --git a/packages/solid-query/src/__tests__/useQueryOptions.test-d.tsx b/packages/solid-query/src/__tests__/useQueryOptions.test-d.tsx index d0f4d60446d..cb378fd0ba4 100644 --- a/packages/solid-query/src/__tests__/useQueryOptions.test-d.tsx +++ b/packages/solid-query/src/__tests__/useQueryOptions.test-d.tsx @@ -72,4 +72,24 @@ describe('infiniteQueryOptions', () => { }> >() }) + + it('should preserve manual fetch method types', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + return pageParam * 5 + }, + initialPageParam: 1, + mode: 'manual', + }) + + const query = useInfiniteQuery(() => options) + + query.fetchNextPage({ pageParam: 2 }) + query.fetchPreviousPage({ pageParam: 0 }) + + // @ts-expect-error pageParam is required in manual mode + query.fetchNextPage() + }) }) diff --git a/packages/solid-query/src/infiniteQueryOptions.ts b/packages/solid-query/src/infiniteQueryOptions.ts index de4991d0b78..f188876a41b 100644 --- a/packages/solid-query/src/infiniteQueryOptions.ts +++ b/packages/solid-query/src/infiniteQueryOptions.ts @@ -2,6 +2,7 @@ import type { DataTag, DefaultError, InfiniteData, + InfiniteQueryMode, NonUndefinedGuard, QueryKey, } from '@tanstack/query-core' @@ -14,13 +15,15 @@ export type UndefinedInitialDataInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = Accessor< SolidInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode > & { initialData?: undefined } @@ -33,13 +36,15 @@ export type DefinedInitialDataInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = Accessor< SolidInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode > & { initialData: | NonUndefinedGuard> @@ -59,7 +64,8 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined > >, ): ReturnType< @@ -68,7 +74,66 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined + > +> & { + queryKey: DataTag> +} +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: ReturnType< + DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + > + >, +): ReturnType< + DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + > +> & { + queryKey: DataTag> +} +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: ReturnType< + UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + > + >, +): ReturnType< + UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined > > & { queryKey: DataTag> @@ -86,7 +151,8 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + InfiniteQueryMode > >, ): ReturnType< @@ -95,7 +161,8 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + InfiniteQueryMode > > & { queryKey: DataTag> diff --git a/packages/solid-query/src/types.ts b/packages/solid-query/src/types.ts index 697177772cd..383b807a3a6 100644 --- a/packages/solid-query/src/types.ts +++ b/packages/solid-query/src/types.ts @@ -4,6 +4,7 @@ import type { DefaultError, DefinedInfiniteQueryObserverResult, DefinedQueryObserverResult, + InfiniteQueryMode, InfiniteQueryObserverResult, MutateFunction, MutationObserverOptions, @@ -87,22 +88,24 @@ export type DefinedUseQueryResult< > = DefinedUseBaseQueryResult /* --- Create Infinite Queries Types --- */ -export interface SolidInfiniteQueryOptions< +export type SolidInfiniteQueryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, -> extends OmitKeyof< + TMode extends InfiniteQueryMode | undefined = undefined, +> = OmitKeyof< InfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode >, 'queryKey' | 'suspense' -> { +> & { queryKey: TQueryKey /** * Only applicable while rendering queries on the server with streaming. @@ -125,19 +128,31 @@ export type UseInfiniteQueryOptions< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = Accessor< - SolidInfiniteQueryOptions + SolidInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + TMode + > > export type UseInfiniteQueryResult< TData = unknown, TError = DefaultError, -> = InfiniteQueryObserverResult + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, +> = InfiniteQueryObserverResult export type DefinedUseInfiniteQueryResult< TData = unknown, TError = DefaultError, -> = DefinedInfiniteQueryObserverResult + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, +> = DefinedInfiniteQueryObserverResult /* --- Create Mutation Types --- */ export interface SolidMutationOptions< diff --git a/packages/solid-query/src/useInfiniteQuery.ts b/packages/solid-query/src/useInfiniteQuery.ts index 9dcff4b1855..1fce37c3fff 100644 --- a/packages/solid-query/src/useInfiniteQuery.ts +++ b/packages/solid-query/src/useInfiniteQuery.ts @@ -4,6 +4,7 @@ import { useBaseQuery } from './useBaseQuery' import type { DefaultError, InfiniteData, + InfiniteQueryMode, QueryKey, QueryObserver, } from '@tanstack/query-core' @@ -31,10 +32,28 @@ export function useInfiniteQuery< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined >, queryClient?: Accessor, -): DefinedUseInfiniteQueryResult +): DefinedUseInfiniteQueryResult +export function useInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + >, + queryClient?: Accessor, +): DefinedUseInfiniteQueryResult export function useInfiniteQuery< TQueryFnData, TError = DefaultError, @@ -47,11 +66,28 @@ export function useInfiniteQuery< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined >, queryClient?: Accessor, -): UseInfiniteQueryResult - +): UseInfiniteQueryResult +export function useInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + >, + queryClient?: Accessor, +): UseInfiniteQueryResult export function useInfiniteQuery< TQueryFnData, TError = DefaultError, @@ -67,10 +103,15 @@ export function useInfiniteQuery< TPageParam >, queryClient?: Accessor, -): UseInfiniteQueryResult { +): UseInfiniteQueryResult + +export function useInfiniteQuery( + options: any, + queryClient?: Accessor, +): any { return useBaseQuery( createMemo(() => options()), InfiniteQueryObserver as typeof QueryObserver, queryClient, - ) as UseInfiniteQueryResult + ) as UseInfiniteQueryResult } diff --git a/packages/svelte-query/src/createInfiniteQuery.ts b/packages/svelte-query/src/createInfiniteQuery.ts index e8fe7948909..b234a7f31fb 100644 --- a/packages/svelte-query/src/createInfiniteQuery.ts +++ b/packages/svelte-query/src/createInfiniteQuery.ts @@ -3,12 +3,14 @@ import { createBaseQuery } from './createBaseQuery.svelte.js' import type { DefaultError, InfiniteData, + InfiniteQueryMode, QueryClient, QueryKey, QueryObserver, } from '@tanstack/query-core' import type { Accessor, + CreateBaseQueryOptions, CreateInfiniteQueryOptions, CreateInfiniteQueryResult, } from './types.js' @@ -26,14 +28,40 @@ export function createInfiniteQuery< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined > >, queryClient?: Accessor, -): CreateInfiniteQueryResult { +): CreateInfiniteQueryResult +export function createInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: Accessor< + CreateInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + > + >, + queryClient?: Accessor, +): CreateInfiniteQueryResult +export function createInfiniteQuery( + options: any, + queryClient?: Accessor, +): any { return createBaseQuery( - options, + options as Accessor< + CreateBaseQueryOptions, any> + >, InfiniteQueryObserver as typeof QueryObserver, queryClient, - ) as CreateInfiniteQueryResult + ) as any } diff --git a/packages/svelte-query/src/infiniteQueryOptions.ts b/packages/svelte-query/src/infiniteQueryOptions.ts index 9702520ac22..c0255632e89 100644 --- a/packages/svelte-query/src/infiniteQueryOptions.ts +++ b/packages/svelte-query/src/infiniteQueryOptions.ts @@ -1,4 +1,9 @@ -import type { DefaultError, InfiniteData, QueryKey } from '@tanstack/query-core' +import type { + DefaultError, + InfiniteData, + InfiniteQueryMode, + QueryKey, +} from '@tanstack/query-core' import type { CreateInfiniteQueryOptions } from './types.js' export function infiniteQueryOptions< @@ -13,14 +18,40 @@ export function infiniteQueryOptions< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined >, ): CreateInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam -> { + TPageParam, + undefined +> +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: CreateInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + >, +): CreateInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode +> +export function infiniteQueryOptions(options: any): any { return options } diff --git a/packages/svelte-query/src/types.ts b/packages/svelte-query/src/types.ts index 9a64d10dc3f..e79f56001ae 100644 --- a/packages/svelte-query/src/types.ts +++ b/packages/svelte-query/src/types.ts @@ -2,6 +2,7 @@ import type { Snippet } from 'svelte' import type { DefaultError, DefinedQueryObserverResult, + InfiniteQueryMode, InfiniteQueryObserverOptions, InfiniteQueryObserverResult, MutateFunction, @@ -56,19 +57,23 @@ export type CreateInfiniteQueryOptions< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = InfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode > /** Result from createInfiniteQuery */ export type CreateInfiniteQueryResult< TData = unknown, TError = DefaultError, -> = InfiniteQueryObserverResult + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, +> = InfiniteQueryObserverResult /** Options for createBaseQuery with initialData */ export type DefinedCreateBaseQueryResult< diff --git a/packages/svelte-query/tests/infiniteQueryOptions.test-d.ts b/packages/svelte-query/tests/infiniteQueryOptions.test-d.ts index b92d8d7730e..84c741d612b 100644 --- a/packages/svelte-query/tests/infiniteQueryOptions.test-d.ts +++ b/packages/svelte-query/tests/infiniteQueryOptions.test-d.ts @@ -5,12 +5,12 @@ import type { InfiniteData } from '@tanstack/query-core' describe('queryOptions', () => { test('Should not allow excess properties', () => { + // @ts-expect-error this is a good error, because stallTime does not exist! infiniteQueryOptions({ queryKey: ['key'], queryFn: () => Promise.resolve('data'), getNextPageParam: () => 1, initialPageParam: 1, - // @ts-expect-error this is a good error, because stallTime does not exist! stallTime: 1000, }) }) @@ -56,4 +56,24 @@ describe('queryOptions', () => { expectTypeOf(data).toEqualTypeOf>() }) + + test('Should preserve manual fetch method types', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + return pageParam * 5 + }, + initialPageParam: 1, + mode: 'manual', + }) + + const query = createInfiniteQuery(() => options) + + query.fetchNextPage({ pageParam: 2 }) + query.fetchPreviousPage({ pageParam: 0 }) + + // @ts-expect-error pageParam is required in manual mode + query.fetchNextPage() + }) }) diff --git a/packages/vue-query/src/__tests__/infiniteQueryOptions.test-d.ts b/packages/vue-query/src/__tests__/infiniteQueryOptions.test-d.ts index 6413126ffd7..5c26f9d8a24 100644 --- a/packages/vue-query/src/__tests__/infiniteQueryOptions.test-d.ts +++ b/packages/vue-query/src/__tests__/infiniteQueryOptions.test-d.ts @@ -110,4 +110,24 @@ describe('infiniteQueryOptions', () => { InfiniteData | undefined >() }) + + it('should preserve manual fetch method types', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + return pageParam * 5 + }, + initialPageParam: 1, + mode: 'manual', + }) + + const query = reactive(useInfiniteQuery(options)) + + query.fetchNextPage({ pageParam: 2 }) + query.fetchPreviousPage({ pageParam: 0 }) + + // @ts-expect-error pageParam is required in manual mode + query.fetchNextPage() + }) }) diff --git a/packages/vue-query/src/infiniteQueryOptions.ts b/packages/vue-query/src/infiniteQueryOptions.ts index adab774aa10..5b210c1c951 100644 --- a/packages/vue-query/src/infiniteQueryOptions.ts +++ b/packages/vue-query/src/infiniteQueryOptions.ts @@ -2,6 +2,7 @@ import type { DataTag, DefaultError, InfiniteData, + InfiniteQueryMode, NonUndefinedGuard, QueryKey, } from '@tanstack/query-core' @@ -13,12 +14,14 @@ export type UndefinedInitialDataInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = UseInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode > & { initialData?: undefined } @@ -29,18 +32,123 @@ export type DefinedInitialDataInfiniteOptions< TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = UseInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode > & { initialData: | NonUndefinedGuard> | (() => NonUndefinedGuard>) } +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >, +): UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined +> & { + queryKey: DataTag, TError> +} +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + >, +): UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode +> & { + queryKey: DataTag, TError> +} + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + >, +): DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined +> & { + queryKey: DataTag, TError> +} + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + >, +): DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode +> & { + queryKey: DataTag, TError> +} + export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, diff --git a/packages/vue-query/src/queryClient.ts b/packages/vue-query/src/queryClient.ts index 7f9a0894bfe..2c02d047842 100644 --- a/packages/vue-query/src/queryClient.ts +++ b/packages/vue-query/src/queryClient.ts @@ -12,10 +12,12 @@ import type { DefaultOptions, EnsureQueryDataOptions, FetchInfiniteQueryOptions, + FetchInfiniteQueryOptionsBase, FetchQueryOptions, InferDataFromTag, InferErrorFromTag, InfiniteData, + InfiniteQueryMode, InvalidateOptions, InvalidateQueryFilters, MutationFilters, @@ -336,7 +338,24 @@ export class QueryClient extends QC { TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined + >, + ): Promise> + fetchInfiniteQuery< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + >( + options: FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode >, ): Promise> fetchInfiniteQuery< @@ -352,7 +371,8 @@ export class QueryClient extends QC { TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined > >, ): Promise> @@ -369,11 +389,31 @@ export class QueryClient extends QC { TError, TData, TQueryKey, - TPageParam + TPageParam, + InfiniteQueryMode + > + >, + ): Promise> + fetchInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + >( + options: MaybeRefDeep< + FetchInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined > >, - ): Promise> { - return super.fetchInfiniteQuery(cloneDeepUnref(options)) + ): Promise> + fetchInfiniteQuery(options: any): Promise { + return super.fetchInfiniteQuery(cloneDeepUnref(options) as any) } prefetchInfiniteQuery< @@ -388,7 +428,24 @@ export class QueryClient extends QC { TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined + >, + ): Promise + prefetchInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + >( + options: FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode >, ): Promise prefetchInfiniteQuery< @@ -404,7 +461,8 @@ export class QueryClient extends QC { TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined > >, ): Promise @@ -421,11 +479,31 @@ export class QueryClient extends QC { TError, TData, TQueryKey, - TPageParam + TPageParam, + InfiniteQueryMode > >, - ): Promise { - return super.prefetchInfiniteQuery(cloneDeepUnref(options)) + ): Promise + prefetchInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + >( + options: MaybeRefDeep< + FetchInfiniteQueryOptionsBase< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + undefined + > + >, + ): Promise + prefetchInfiniteQuery(options: any): Promise { + return super.prefetchInfiniteQuery(cloneDeepUnref(options) as any) } setDefaultOptions(options: MaybeRefDeep): void { diff --git a/packages/vue-query/src/useInfiniteQuery.ts b/packages/vue-query/src/useInfiniteQuery.ts index 107251df872..722808a32a1 100644 --- a/packages/vue-query/src/useInfiniteQuery.ts +++ b/packages/vue-query/src/useInfiniteQuery.ts @@ -7,7 +7,8 @@ import type { import type { DefaultError, InfiniteData, - InfiniteQueryObserverOptions, + InfiniteQueryMode, + InfiniteQueryObserverOptionsBase, InfiniteQueryObserverResult, QueryKey, QueryObserver, @@ -30,40 +31,49 @@ export type UseInfiniteQueryOptions< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, > = MaybeRef< { - [Property in keyof InfiniteQueryObserverOptions< + [Property in keyof InfiniteQueryObserverOptionsBase< TQueryFnData, TError, TData, TQueryKey, - TPageParam + TPageParam, + TMode >]: Property extends 'enabled' ? MaybeRefOrGetter< - InfiniteQueryObserverOptions< + InfiniteQueryObserverOptionsBase< TQueryFnData, TError, TData, DeepUnwrapRef, - TPageParam + TPageParam, + TMode >[Property] > : MaybeRefDeep< - InfiniteQueryObserverOptions< + InfiniteQueryObserverOptionsBase< TQueryFnData, TError, TData, DeepUnwrapRef, - TPageParam + TPageParam, + TMode >[Property] > } & ShallowOption > -export type UseInfiniteQueryReturnType = UseBaseQueryReturnType< +export type UseInfiniteQueryReturnType< TData, TError, - InfiniteQueryObserverResult + TPageParam = unknown, + TMode extends InfiniteQueryMode | undefined = undefined, +> = UseBaseQueryReturnType< + TData, + TError, + InfiniteQueryObserverResult > export function useInfiniteQuery< @@ -79,11 +89,31 @@ export function useInfiniteQuery< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined > >, queryClient?: QueryClient, -): UseInfiniteQueryReturnType +): UseInfiniteQueryReturnType +export function useInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: MaybeRefOrGetter< + DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + > + >, + queryClient?: QueryClient, +): UseInfiniteQueryReturnType export function useInfiniteQuery< TQueryFnData, @@ -98,11 +128,31 @@ export function useInfiniteQuery< TError, TData, TQueryKey, - TPageParam + TPageParam, + undefined > >, queryClient?: QueryClient, -): UseInfiniteQueryReturnType +): UseInfiniteQueryReturnType +export function useInfiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: MaybeRefOrGetter< + UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam, + InfiniteQueryMode + > + >, + queryClient?: QueryClient, +): UseInfiniteQueryReturnType export function useInfiniteQuery< TQueryFnData, @@ -117,10 +167,7 @@ export function useInfiniteQuery< queryClient?: QueryClient, ): UseInfiniteQueryReturnType -export function useInfiniteQuery( - options: MaybeRefOrGetter, - queryClient?: QueryClient, -) { +export function useInfiniteQuery(options: any, queryClient?: QueryClient): any { return useBaseQuery( InfiniteQueryObserver as typeof QueryObserver, options,