From 7b6b677873b9361fe0386df4387fdedd54cabeeb Mon Sep 17 00:00:00 2001 From: benjamineckstein <13351939+benjamineckstein@users.noreply.github.com> Date: Fri, 5 Jun 2026 08:27:14 +0200 Subject: [PATCH] feat(openapi-react-query): generate useInfiniteQuery hooks for paginated endpoints (#189) Emits a useXxxInfinite hook alongside the regular useQuery hook whenever a list-level GET operation is detected as paginated. Fully additive and non-breaking: the regular hook is always generated unchanged. Detection priority: 1. x-infinite: true/false on the operation (explicit override). 2. Heuristic: list GET (no path params) with a recognised pagination query param name (page, cursor, offset, pageToken, after, before, next_page, nextPage, page_token). Detection runs on resolved $ref params. 3. infiniteQuery config field (boolean | 'auto', default 'auto') allows force-on, force-off, or heuristic mode. Generated hook shape: - Named use${funcName}Infinite (mirrors useSuspense* suffix pattern). - queryKey: [...resourceKeys.list(params), 'infinite'] to namespace away from regular list queries. - queryFn injects pageParam into the params object at the detected param name. - Provides initialPageParam: undefined and getNextPageParam: () => undefined as defaults; callers override both via the options argument. - options type: Omit, 'queryKey' | 'queryFn'> so callers can supply getNextPageParam and initialPageParam. Config: added infinite_query field to ReactQueryConfig with validation. Regenerated: packages/integration/generated/hooks.ts (gains useListTasksInfinite), and 6 showcase specs in examples/generated-rq/ that have pagination params (1password-connect, devto, openai, redocly-museum, resend, spotify). --- .../generated-rq/1password-connect/hooks.ts | 35 + examples/generated-rq/devto/hooks.ts | 438 +++++++++ examples/generated-rq/openai/hooks.ts | 907 ++++++++++++++++++ examples/generated-rq/redocly-museum/hooks.ts | 66 ++ examples/generated-rq/resend/hooks.ts | 438 +++++++++ examples/generated-rq/spotify/hooks.ts | 442 +++++++++ packages/integration/generated/hooks.ts | 35 + .../integration/src/consumer-simulation.ts | 15 +- .../__snapshots__/hooks.test.ts.snap | 190 +++- .../src/__tests__/hooks.test.ts | 518 +++++++++- packages/openapi-react-query/src/config.ts | 15 + packages/openapi-react-query/src/generator.ts | 1 + .../openapi-react-query/src/plugins/hooks.ts | 282 +++++- 13 files changed, 3371 insertions(+), 11 deletions(-) diff --git a/examples/generated-rq/1password-connect/hooks.ts b/examples/generated-rq/1password-connect/hooks.ts index c29c071..8a7d5d2 100644 --- a/examples/generated-rq/1password-connect/hooks.ts +++ b/examples/generated-rq/1password-connect/hooks.ts @@ -4,6 +4,10 @@ import { queryOptions, useQuery, type UseQueryOptions, + useInfiniteQuery, + type UseInfiniteQueryOptions, + type InfiniteData, + type QueryKey, useMutation, type UseMutationOptions, } from '@tanstack/react-query' @@ -273,6 +277,37 @@ export function useGetApiActivity( }) } +export function useGetApiActivityInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...activityKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getApiActivity({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetVaults( params?: Parameters[0], options?: Omit< diff --git a/examples/generated-rq/devto/hooks.ts b/examples/generated-rq/devto/hooks.ts index 54060c0..db6bd07 100644 --- a/examples/generated-rq/devto/hooks.ts +++ b/examples/generated-rq/devto/hooks.ts @@ -4,6 +4,10 @@ import { queryOptions, useQuery, type UseQueryOptions, + useInfiniteQuery, + type UseInfiniteQueryOptions, + type InfiniteData, + type QueryKey, useMutation, type UseMutationOptions, } from '@tanstack/react-query' @@ -786,6 +790,37 @@ export function useGetArticles( }) } +export function useGetArticlesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getArticles(params), 'infinite'], + queryFn: ({ pageParam }) => getArticles({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetLatestArticles( params?: Parameters[0], options?: Omit< @@ -802,6 +837,37 @@ export function useGetLatestArticles( }) } +export function useGetLatestArticlesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getLatestArticles(params), 'infinite'], + queryFn: ({ pageParam }) => getLatestArticles({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetArticleById( id: string | undefined | null, options?: Omit< @@ -853,6 +919,37 @@ export function useGetUserArticles( }) } +export function useGetUserArticlesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getUserArticles(params), 'infinite'], + queryFn: ({ pageParam }) => getUserArticles({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetUserPublishedArticles( params?: Parameters[0], options?: Omit< @@ -869,6 +966,37 @@ export function useGetUserPublishedArticles( }) } +export function useGetUserPublishedArticlesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getUserPublishedArticles(params), 'infinite'], + queryFn: ({ pageParam }) => getUserPublishedArticles({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetUserUnpublishedArticles( params?: Parameters[0], options?: Omit< @@ -885,6 +1013,37 @@ export function useGetUserUnpublishedArticles( }) } +export function useGetUserUnpublishedArticlesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getUserUnpublishedArticles(params), 'infinite'], + queryFn: ({ pageParam }) => getUserUnpublishedArticles({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetUserAllArticles( params?: Parameters[0], options?: Omit< @@ -901,6 +1060,37 @@ export function useGetUserAllArticles( }) } +export function useGetUserAllArticlesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getUserAllArticles(params), 'infinite'], + queryFn: ({ pageParam }) => getUserAllArticles({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetSegments( params?: Parameters[0], options?: Omit< @@ -1000,6 +1190,37 @@ export function useGetCommentsByArticleId( }) } +export function useGetCommentsByArticleIdInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getCommentsByArticleId(params), 'infinite'], + queryFn: ({ pageParam }) => getCommentsByArticleId({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetCommentById( id: string | undefined | null, options?: Omit< @@ -1048,6 +1269,37 @@ export function useGetFollowers( }) } +export function useGetFollowersInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getFollowers(params), 'infinite'], + queryFn: ({ pageParam }) => getFollowers({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetOrganization( username: string | undefined | null, options?: Omit< @@ -1117,6 +1369,37 @@ export function useGetOrganizations( }) } +export function useGetOrganizationsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getOrganizations(params), 'infinite'], + queryFn: ({ pageParam }) => getOrganizations({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetOrganizationById( id: string | undefined | null, options?: Omit< @@ -1182,6 +1465,37 @@ export function useGetPodcastEpisodes( }) } +export function useGetPodcastEpisodesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getPodcastEpisodes(params), 'infinite'], + queryFn: ({ pageParam }) => getPodcastEpisodes({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetProfileImage( username: string | undefined | null, options?: Omit< @@ -1215,6 +1529,37 @@ export function useGetReadinglist( }) } +export function useGetReadinglistInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getReadinglist(params), 'infinite'], + queryFn: ({ pageParam }) => getReadinglist({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetSurveys( params?: Parameters[0], options?: Omit< @@ -1231,6 +1576,37 @@ export function useGetSurveys( }) } +export function useGetSurveysInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getSurveys(params), 'infinite'], + queryFn: ({ pageParam }) => getSurveys({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetSurveyByIdOrSlug( idOrSlug: string | undefined | null, options?: Omit< @@ -1300,6 +1676,37 @@ export function useGetTags( }) } +export function useGetTagsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.getTags(params), 'infinite'], + queryFn: ({ pageParam }) => getTags({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetUserMe( options?: Omit< UseQueryOptions>, ApiError>, @@ -1348,6 +1755,37 @@ export function useVideos( }) } +export function useVideosInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeys.videos(params), 'infinite'], + queryFn: ({ pageParam }) => videos({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + // ── Mutations ──────────────────────────────────────────────── export function useCreateAgentSession( diff --git a/examples/generated-rq/openai/hooks.ts b/examples/generated-rq/openai/hooks.ts index 130deed..db5236a 100644 --- a/examples/generated-rq/openai/hooks.ts +++ b/examples/generated-rq/openai/hooks.ts @@ -4,6 +4,10 @@ import { queryOptions, useQuery, type UseQueryOptions, + useInfiniteQuery, + type UseInfiniteQueryOptions, + type InfiniteData, + type QueryKey, useMutation, type UseMutationOptions, } from '@tanstack/react-query' @@ -2203,6 +2207,38 @@ export function useListAssistants( }) } +/** @deprecated */ +export function useListAssistantsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...assistantKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listAssistants({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useGetAssistant( assistantId: string | undefined | null, @@ -2237,6 +2273,37 @@ export function useListVoiceConsents( }) } +export function useListVoiceConsentsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...audioKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listVoiceConsents({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetVoiceConsent( consentId: string | undefined | null, options?: Omit< @@ -2270,6 +2337,37 @@ export function useListBatches( }) } +export function useListBatchesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...batchKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listBatches({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useRetrieveBatch( batchId: string | undefined | null, options?: Omit< @@ -2303,6 +2401,37 @@ export function useListChatCompletions( }) } +export function useListChatCompletionsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...chatKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listChatCompletions({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetChatCompletion( completionId: string | undefined | null, options?: Omit< @@ -2354,6 +2483,37 @@ export function useListContainers( }) } +export function useListContainersInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...containerKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listContainers({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useRetrieveContainer( containerId: string | undefined | null, options?: Omit< @@ -2478,6 +2638,37 @@ export function useListEvals( }) } +export function useListEvalsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...evalKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listEvals({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetEval( evalId: string | undefined | null, options?: Omit< @@ -2585,6 +2776,37 @@ export function useListFiles( }) } +export function useListFilesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...fileKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listFiles({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useRetrieveFile( fileId: string | undefined | null, options?: Omit< @@ -2653,6 +2875,38 @@ export function useListPaginatedFineTuningJobs( }) } +export function useListPaginatedFineTuningJobsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...fineTuningKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => + listPaginatedFineTuningJobs({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useRetrieveFineTuningJob( fineTuningJobId: string | undefined | null, options?: Omit< @@ -2754,6 +3008,37 @@ export function useAdminApiKeysList( }) } +export function useAdminApiKeysListInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.adminApiKeysList(params), 'infinite'], + queryFn: ({ pageParam }) => adminApiKeysList({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useAdminApiKeysGet( keyId: string | undefined | null, options?: Omit< @@ -2787,6 +3072,37 @@ export function useListAuditLogs( }) } +export function useListAuditLogsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.listAuditLogs(params), 'infinite'], + queryFn: ({ pageParam }) => listAuditLogs({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useListOrganizationCertificates( params?: Parameters[0], options?: Omit< @@ -2803,6 +3119,38 @@ export function useListOrganizationCertificates( }) } +export function useListOrganizationCertificatesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.listOrganizationCertificates(params), 'infinite'], + queryFn: ({ pageParam }) => + listOrganizationCertificates({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetCertificate( certificateId: string | undefined | null, params?: Parameters[1], @@ -2837,6 +3185,37 @@ export function useUsageCosts( }) } +export function useUsageCostsInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageCosts(params), 'infinite'], + queryFn: ({ pageParam }) => usageCosts({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useListGroups( params?: Parameters[0], options?: Omit< @@ -2853,6 +3232,37 @@ export function useListGroups( }) } +export function useListGroupsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.listGroups(params), 'infinite'], + queryFn: ({ pageParam }) => listGroups({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useListGroupRoleAssignments( groupId: string | undefined | null, params?: Parameters[1], @@ -2905,6 +3315,37 @@ export function useListInvites( }) } +export function useListInvitesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.listInvites(params), 'infinite'], + queryFn: ({ pageParam }) => listInvites({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useRetrieveInvite( inviteId: string | undefined | null, options?: Omit< @@ -2938,6 +3379,37 @@ export function useListProjects( }) } +export function useListProjectsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.listProjects(params), 'infinite'], + queryFn: ({ pageParam }) => listProjects({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useRetrieveProject( projectId: string | undefined | null, options?: Omit< @@ -3133,6 +3605,37 @@ export function useListRoles( }) } +export function useListRolesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.listRoles(params), 'infinite'], + queryFn: ({ pageParam }) => listRoles({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useUsageAudioSpeeches( params: Parameters[0], options?: Omit< @@ -3149,6 +3652,37 @@ export function useUsageAudioSpeeches( }) } +export function useUsageAudioSpeechesInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageAudioSpeeches(params), 'infinite'], + queryFn: ({ pageParam }) => usageAudioSpeeches({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useUsageAudioTranscriptions( params: Parameters[0], options?: Omit< @@ -3165,6 +3699,37 @@ export function useUsageAudioTranscriptions( }) } +export function useUsageAudioTranscriptionsInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageAudioTranscriptions(params), 'infinite'], + queryFn: ({ pageParam }) => usageAudioTranscriptions({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useUsageCodeInterpreterSessions( params: Parameters[0], options?: Omit< @@ -3181,6 +3746,38 @@ export function useUsageCodeInterpreterSessions( }) } +export function useUsageCodeInterpreterSessionsInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageCodeInterpreterSessions(params), 'infinite'], + queryFn: ({ pageParam }) => + usageCodeInterpreterSessions({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useUsageCompletions( params: Parameters[0], options?: Omit< @@ -3197,6 +3794,37 @@ export function useUsageCompletions( }) } +export function useUsageCompletionsInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageCompletions(params), 'infinite'], + queryFn: ({ pageParam }) => usageCompletions({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useUsageEmbeddings( params: Parameters[0], options?: Omit< @@ -3213,6 +3841,37 @@ export function useUsageEmbeddings( }) } +export function useUsageEmbeddingsInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageEmbeddings(params), 'infinite'], + queryFn: ({ pageParam }) => usageEmbeddings({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useUsageImages( params: Parameters[0], options?: Omit< @@ -3229,6 +3888,37 @@ export function useUsageImages( }) } +export function useUsageImagesInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageImages(params), 'infinite'], + queryFn: ({ pageParam }) => usageImages({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useUsageModerations( params: Parameters[0], options?: Omit< @@ -3245,6 +3935,37 @@ export function useUsageModerations( }) } +export function useUsageModerationsInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageModerations(params), 'infinite'], + queryFn: ({ pageParam }) => usageModerations({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useUsageVectorStores( params: Parameters[0], options?: Omit< @@ -3261,6 +3982,37 @@ export function useUsageVectorStores( }) } +export function useUsageVectorStoresInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.usageVectorStores(params), 'infinite'], + queryFn: ({ pageParam }) => usageVectorStores({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useListUsers( params?: Parameters[0], options?: Omit< @@ -3277,6 +4029,37 @@ export function useListUsers( }) } +export function useListUsersInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...organizationKeys.listUsers(params), 'infinite'], + queryFn: ({ pageParam }) => listUsers({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useRetrieveUser( userId: string | undefined | null, options?: Omit< @@ -3548,6 +4331,37 @@ export function useListVectorStores( }) } +export function useListVectorStoresInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...vectorStoreKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listVectorStores({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetVectorStore( vectorStoreId: string | undefined | null, options?: Omit< @@ -3689,6 +4503,37 @@ export function useListVideos( }) } +export function useListVideosInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...videoKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listVideos({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetVideoCharacter( characterId: string | undefined | null, options?: Omit< @@ -3757,6 +4602,37 @@ export function useListSkills( }) } +export function useListSkillsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...skillKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listSkills({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetSkill( skillId: string | undefined | null, options?: Omit< @@ -3896,6 +4772,37 @@ export function useListThreadsMethod( }) } +export function useListThreadsMethodInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...chatkitKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listThreadsMethod({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + // ── Mutations ──────────────────────────────────────────────── /** @deprecated */ diff --git a/examples/generated-rq/redocly-museum/hooks.ts b/examples/generated-rq/redocly-museum/hooks.ts index 16bcd5f..fbe1e43 100644 --- a/examples/generated-rq/redocly-museum/hooks.ts +++ b/examples/generated-rq/redocly-museum/hooks.ts @@ -4,6 +4,10 @@ import { queryOptions, useQuery, type UseQueryOptions, + useInfiniteQuery, + type UseInfiniteQueryOptions, + type InfiniteData, + type QueryKey, useMutation, type UseMutationOptions, } from '@tanstack/react-query' @@ -123,6 +127,37 @@ export function useGetMuseumHours( }) } +export function useGetMuseumHoursInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...museumHourKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getMuseumHours({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useListSpecialEvents( params?: Parameters[0], options?: Omit< @@ -139,6 +174,37 @@ export function useListSpecialEvents( }) } +export function useListSpecialEventsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...specialEventKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listSpecialEvents({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetSpecialEvent( eventId: string | undefined | null, options?: Omit< diff --git a/examples/generated-rq/resend/hooks.ts b/examples/generated-rq/resend/hooks.ts index cfb2fb3..8d76210 100644 --- a/examples/generated-rq/resend/hooks.ts +++ b/examples/generated-rq/resend/hooks.ts @@ -4,6 +4,10 @@ import { queryOptions, useQuery, type UseQueryOptions, + useInfiniteQuery, + type UseInfiniteQueryOptions, + type InfiniteData, + type QueryKey, useMutation, type UseMutationOptions, } from '@tanstack/react-query' @@ -854,6 +858,37 @@ export function useGetEmails( }) } +export function useGetEmailsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...emailKeys.getEmails(params), 'infinite'], + queryFn: ({ pageParam }) => getEmails({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetEmailsByEmailId( emailId: string | undefined | null, options?: Omit< @@ -929,6 +964,37 @@ export function useGetEmailsReceiving( }) } +export function useGetEmailsReceivingInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...emailKeys.getEmailsReceiving(params), 'infinite'], + queryFn: ({ pageParam }) => getEmailsReceiving({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetEmailsReceivingByEmailId( emailId: string | undefined | null, options?: Omit< @@ -1007,6 +1073,37 @@ export function useGetDomains( }) } +export function useGetDomainsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...domainKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getDomains({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetDomainsByDomainId( domainId: string | undefined | null, options?: Omit< @@ -1040,6 +1137,37 @@ export function useGetApiKeys( }) } +export function useGetApiKeysInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...apiKeyKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getApiKeys({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetTemplates( params?: Parameters[0], options?: Omit< @@ -1056,6 +1184,37 @@ export function useGetTemplates( }) } +export function useGetTemplatesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...templateKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getTemplates({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetTemplatesById( id: string | undefined | null, options?: Omit< @@ -1123,6 +1282,37 @@ export function useGetContacts( }) } +export function useGetContactsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...contactKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getContacts({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetContactsById( id: string | undefined | null, options?: Omit< @@ -1156,6 +1346,37 @@ export function useGetBroadcasts( }) } +export function useGetBroadcastsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...broadcastKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getBroadcasts({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetBroadcastsById( id: string | undefined | null, options?: Omit< @@ -1189,6 +1410,37 @@ export function useGetWebhooks( }) } +export function useGetWebhooksInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...webhookKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getWebhooks({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetWebhooksByWebhookId( webhookId: string | undefined | null, options?: Omit< @@ -1222,6 +1474,37 @@ export function useGetSegments( }) } +export function useGetSegmentsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...segmentKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getSegments({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetSegmentsById( id: string | undefined | null, options?: Omit< @@ -1255,6 +1538,37 @@ export function useGetTopics( }) } +export function useGetTopicsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...topicKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getTopics({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetTopicsById( id: string | undefined | null, options?: Omit< @@ -1288,6 +1602,37 @@ export function useGetContactProperties( }) } +export function useGetContactPropertiesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...contactPropertyKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getContactProperties({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetContactPropertiesById( id: string | undefined | null, options?: Omit< @@ -1357,6 +1702,37 @@ export function useGetLogs( }) } +export function useGetLogsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...logKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getLogs({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetLogsByLogId( logId: string | undefined | null, options?: Omit< @@ -1390,6 +1766,37 @@ export function useGetAutomations( }) } +export function useGetAutomationsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...automationKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getAutomations({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetAutomationsByAutomationId( automationId: string | undefined | null, options?: Omit< @@ -1459,6 +1866,37 @@ export function useGetEvents( }) } +export function useGetEventsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...eventKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => getEvents({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetEventsByIdentifier( identifier: string | undefined | null, options?: Omit< diff --git a/examples/generated-rq/spotify/hooks.ts b/examples/generated-rq/spotify/hooks.ts index 784adae..bfedbca 100644 --- a/examples/generated-rq/spotify/hooks.ts +++ b/examples/generated-rq/spotify/hooks.ts @@ -4,6 +4,10 @@ import { queryOptions, useQuery, type UseQueryOptions, + useInfiniteQuery, + type UseInfiniteQueryOptions, + type InfiniteData, + type QueryKey, useMutation, type UseMutationOptions, } from '@tanstack/react-query' @@ -1614,6 +1618,37 @@ export function useGetUsersSavedAudiobooks( }) } +export function useGetUsersSavedAudiobooksInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getUsersSavedAudiobooks(params), 'infinite'], + queryFn: ({ pageParam }) => getUsersSavedAudiobooks({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useCheckUsersSavedAudiobooks( params: Parameters[0], @@ -1717,6 +1752,37 @@ export function useSearch( }) } +export function useSearchInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...searchKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => search({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetCurrentUsersProfile( options?: Omit< UseQueryOptions>, ApiError>, @@ -1803,6 +1869,38 @@ export function useGetAListOfCurrentUsersPlaylists( }) } +export function useGetAListOfCurrentUsersPlaylistsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getAListOfCurrentUsersPlaylists(params), 'infinite'], + queryFn: ({ pageParam }) => + getAListOfCurrentUsersPlaylists({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useCheckLibraryContains( params: Parameters[0], options?: Omit< @@ -1835,6 +1933,37 @@ export function useGetUsersSavedAlbums( }) } +export function useGetUsersSavedAlbumsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getUsersSavedAlbums(params), 'infinite'], + queryFn: ({ pageParam }) => getUsersSavedAlbums({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useCheckUsersSavedAlbums( params: Parameters[0], @@ -1868,6 +1997,37 @@ export function useGetUsersSavedTracks( }) } +export function useGetUsersSavedTracksInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getUsersSavedTracks(params), 'infinite'], + queryFn: ({ pageParam }) => getUsersSavedTracks({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useCheckUsersSavedTracks( params: Parameters[0], @@ -1901,6 +2061,37 @@ export function useGetUsersSavedEpisodes( }) } +export function useGetUsersSavedEpisodesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getUsersSavedEpisodes(params), 'infinite'], + queryFn: ({ pageParam }) => getUsersSavedEpisodes({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useCheckUsersSavedEpisodes( params: Parameters[0], @@ -1934,6 +2125,37 @@ export function useGetUsersSavedShows( }) } +export function useGetUsersSavedShowsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getUsersSavedShows(params), 'infinite'], + queryFn: ({ pageParam }) => getUsersSavedShows({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useCheckUsersSavedShows( params: Parameters[0], @@ -2005,6 +2227,38 @@ export function useGetFeaturedPlaylists( }) } +/** @deprecated */ +export function useGetFeaturedPlaylistsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...browseKeys.getFeaturedPlaylists(params), 'infinite'], + queryFn: ({ pageParam }) => getFeaturedPlaylists({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useGetCategories( params?: Parameters[0], @@ -2022,6 +2276,38 @@ export function useGetCategories( }) } +/** @deprecated */ +export function useGetCategoriesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...browseKeys.getCategories(params), 'infinite'], + queryFn: ({ pageParam }) => getCategories({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useGetACategory( categoryId: string | undefined | null, @@ -2094,6 +2380,38 @@ export function useGetNewReleases( }) } +/** @deprecated */ +export function useGetNewReleasesInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...browseKeys.getNewReleases(params), 'infinite'], + queryFn: ({ pageParam }) => getNewReleases({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetFollowed( params: Parameters[0], options?: Omit< @@ -2110,6 +2428,37 @@ export function useGetFollowed( }) } +export function useGetFollowedInfinite( + params: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getFollowed(params), 'infinite'], + queryFn: ({ pageParam }) => getFollowed({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + /** @deprecated */ export function useCheckCurrentUserFollows( params: Parameters[0], @@ -2300,6 +2649,37 @@ export function useGetRecentlyPlayed( }) } +export function useGetRecentlyPlayedInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getRecentlyPlayed(params), 'infinite'], + queryFn: ({ pageParam }) => getRecentlyPlayed({ ...params, after: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetQueue( options?: Omit< UseQueryOptions>, ApiError>, @@ -2347,6 +2727,37 @@ export function useGetUsersTopArtists( }) } +export function useGetUsersTopArtistsInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getUsersTopArtists(params), 'infinite'], + queryFn: ({ pageParam }) => getUsersTopArtists({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetUsersTopTracks( params?: Parameters[0], options?: Omit< @@ -2363,6 +2774,37 @@ export function useGetUsersTopTracks( }) } +export function useGetUsersTopTracksInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...meKeys.getUsersTopTracks(params), 'infinite'], + queryFn: ({ pageParam }) => getUsersTopTracks({ ...params, offset: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + // ── Mutations ──────────────────────────────────────────────── /** @deprecated */ diff --git a/packages/integration/generated/hooks.ts b/packages/integration/generated/hooks.ts index 833dc2e..225cb0a 100644 --- a/packages/integration/generated/hooks.ts +++ b/packages/integration/generated/hooks.ts @@ -4,6 +4,10 @@ import { queryOptions, useQuery, type UseQueryOptions, + useInfiniteQuery, + type UseInfiniteQueryOptions, + type InfiniteData, + type QueryKey, useMutation, type UseMutationOptions, } from '@tanstack/react-query' @@ -77,6 +81,37 @@ export function useListTasks( }) } +export function useListTasksInfinite( + params?: Parameters[0], + options?: Omit< + UseInfiniteQueryOptions< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >, + 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam' + > +) { + return useInfiniteQuery< + Awaited>, + ApiError, + InfiniteData>>, + QueryKey, + unknown + >({ + queryKey: [...taskKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listTasks({ ...params, page: pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 30000, + gcTime: 300000, + ...options, + }) +} + export function useGetTask( id: string | undefined | null, options?: Omit< diff --git a/packages/integration/src/consumer-simulation.ts b/packages/integration/src/consumer-simulation.ts index b040c39..b37a724 100644 --- a/packages/integration/src/consumer-simulation.ts +++ b/packages/integration/src/consumer-simulation.ts @@ -2,7 +2,7 @@ // tsc --noEmit exercises all these patterns on every `pnpm lint` run in CI. import type { Task, CreateTaskRequest } from '../generated/models.js' -import { useListTasks, useGetTask, useCreateTask } from '../generated/hooks.js' +import { useListTasks, useListTasksInfinite, useGetTask, useCreateTask } from '../generated/hooks.js' import { createServerClient } from '../generated/server.js' // Pattern 1: useQuery with no params (list hook) @@ -57,6 +57,17 @@ async function _serverClientUsage() { await api.deleteTask('some-id') } +// Pattern 7: useXxxInfinite — partial options object (only enabled); must not +// require getNextPageParam or initialPageParam at the call site (Fix #189-CR1). +function _useListTasksInfinitePartialOptions(enabled: boolean) { + return useListTasksInfinite({ status: 'pending' }, { enabled }) +} + +// Pattern 8: useXxxInfinite — no options argument at all; must typecheck. +function _useListTasksInfiniteNoOptions() { + return useListTasksInfinite() +} + // Ensure all _-prefixed functions are referenced so TypeScript doesn't elide them void _useListTasksNoParams void _useListTasksWithParams @@ -64,3 +75,5 @@ void _useGetTaskNullish void _useCreateTaskOnSuccess void _useCreateTaskOnError void _serverClientUsage +void _useListTasksInfinitePartialOptions +void _useListTasksInfiniteNoOptions diff --git a/packages/openapi-react-query/src/__tests__/__snapshots__/hooks.test.ts.snap b/packages/openapi-react-query/src/__tests__/__snapshots__/hooks.test.ts.snap index 9e9e6f2..7197723 100644 --- a/packages/openapi-react-query/src/__tests__/__snapshots__/hooks.test.ts.snap +++ b/packages/openapi-react-query/src/__tests__/__snapshots__/hooks.test.ts.snap @@ -1,9 +1,109 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`generateHooks — #189: infinite query generation > snapshot: paginated list spec generates correct infinite hook output 1`] = ` +"// This file is auto-generated by @codewithagents/openapi-react-query — do not edit + +import { queryOptions, useQuery, type UseQueryOptions, useInfiniteQuery, type UseInfiniteQueryOptions, type InfiniteData, type QueryKey, useMutation, type UseMutationOptions } from '@tanstack/react-query' +import { createPost, getPost, listPosts, type ApiError } from './client.js' + +// ── Query key factories ────────────────────────────────────── + +export const postKeys = { + all: () => ["posts"] as const, + list: (params?: Parameters[0]) => ["posts", 'list', params] as const, + detail: (id: string) => ["posts", id] as const, +} + +// ── Query options factories ────────────────────────────────── + +export function listPostsQueryOptions( + params?: Parameters[0], + options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, +) { + return queryOptions>, ApiError>({ + queryKey: postKeys.list(params), + queryFn: () => listPosts(params), + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + +export function getPostQueryOptions( + id: string, + options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, +) { + return queryOptions>, ApiError>({ + queryKey: postKeys.detail(id), + queryFn: () => getPost(id), + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + +// ── Queries ────────────────────────────────────────────────── + +export function useListPosts( + params?: Parameters[0], + options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, +) { + return useQuery>, ApiError>({ + queryKey: postKeys.list(params), + queryFn: () => listPosts(params), + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + +export function useListPostsInfinite( + params?: Parameters[0], + options?: Omit>, ApiError, InfiniteData>>, QueryKey, unknown>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>, +) { + return useInfiniteQuery>, ApiError, InfiniteData>>, QueryKey, unknown>({ + queryKey: [...postKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listPosts({ ...params, "cursor": pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + +export function useGetPost( + id: string | undefined | null, + options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, +) { + return useQuery>, ApiError>({ + queryKey: postKeys.detail(id!), + queryFn: () => getPost(id!), + staleTime: 0, + gcTime: 300000, + enabled: id != null && (options?.enabled ?? true), + ...options, + }) +} + +// ── Mutations ──────────────────────────────────────────────── + +export function useCreatePost( + options?: Omit>, ApiError, Parameters[0]>, 'mutationFn'>, +) { + return useMutation>, ApiError, Parameters[0]>({ + mutationFn: (vars) => createPost(vars), + ...options, + }) +} +" +`; + exports[`snapshot: full generated output > autoInvalidate: true — auto-invalidation wiring 1`] = ` "// This file is auto-generated by @codewithagents/openapi-react-query — do not edit -import { queryOptions, useQuery, type UseQueryOptions, useMutation, type UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { queryOptions, useQuery, type UseQueryOptions, useInfiniteQuery, type UseInfiniteQueryOptions, type InfiniteData, type QueryKey, useMutation, type UseMutationOptions, useQueryClient } from '@tanstack/react-query' import { createTask, deleteTask, getTask, listTasks, updateTask, type ApiError } from './client.js' // ── Query key factories ────────────────────────────────────── @@ -57,6 +157,22 @@ export function useListTasks( }) } +export function useListTasksInfinite( + params?: Parameters[0], + options?: Omit>, ApiError, InfiniteData>>, QueryKey, unknown>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>, +) { + return useInfiniteQuery>, ApiError, InfiniteData>>, QueryKey, unknown>({ + queryKey: [...taskKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listTasks({ ...params, "page": pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetTask( id: string | undefined | null, options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, @@ -122,7 +238,7 @@ export function useDeleteTask( exports[`snapshot: full generated output > autoInvalidate: true, suspense: true, overrides: { tasks: { staleTime: 60_000 } } — all features combined 1`] = ` "// This file is auto-generated by @codewithagents/openapi-react-query — do not edit -import { queryOptions, useQuery, type UseQueryOptions, useSuspenseQuery, type UseSuspenseQueryOptions, useMutation, type UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { queryOptions, useQuery, type UseQueryOptions, useSuspenseQuery, type UseSuspenseQueryOptions, useInfiniteQuery, type UseInfiniteQueryOptions, type InfiniteData, type QueryKey, useMutation, type UseMutationOptions, useQueryClient } from '@tanstack/react-query' import { createTask, deleteTask, getTask, listTasks, updateTask, type ApiError } from './client.js' // ── Query key factories ────────────────────────────────────── @@ -189,6 +305,22 @@ export function useSuspenseListTasks( }) } +export function useListTasksInfinite( + params?: Parameters[0], + options?: Omit>, ApiError, InfiniteData>>, QueryKey, unknown>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>, +) { + return useInfiniteQuery>, ApiError, InfiniteData>>, QueryKey, unknown>({ + queryKey: [...taskKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listTasks({ ...params, "page": pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 60000, + gcTime: 300000, + ...options, + }) +} + export function useGetTask( id: string | undefined | null, options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, @@ -267,7 +399,7 @@ export function useDeleteTask( exports[`snapshot: full generated output > default options (staleTime: 0, gcTime: 300_000) — baseline 1`] = ` "// This file is auto-generated by @codewithagents/openapi-react-query — do not edit -import { queryOptions, useQuery, type UseQueryOptions, useMutation, type UseMutationOptions } from '@tanstack/react-query' +import { queryOptions, useQuery, type UseQueryOptions, useInfiniteQuery, type UseInfiniteQueryOptions, type InfiniteData, type QueryKey, useMutation, type UseMutationOptions } from '@tanstack/react-query' import { createTask, deleteTask, getTask, listTasks, updateTask, type ApiError } from './client.js' // ── Query key factories ────────────────────────────────────── @@ -321,6 +453,22 @@ export function useListTasks( }) } +export function useListTasksInfinite( + params?: Parameters[0], + options?: Omit>, ApiError, InfiniteData>>, QueryKey, unknown>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>, +) { + return useInfiniteQuery>, ApiError, InfiniteData>>, QueryKey, unknown>({ + queryKey: [...taskKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listTasks({ ...params, "page": pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetTask( id: string | undefined | null, options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, @@ -369,7 +517,7 @@ export function useDeleteTask( exports[`snapshot: full generated output > overrides: { tasks: { staleTime: 60_000, gcTime: 600_000 } } — per-resource timing 1`] = ` "// This file is auto-generated by @codewithagents/openapi-react-query — do not edit -import { queryOptions, useQuery, type UseQueryOptions, useMutation, type UseMutationOptions } from '@tanstack/react-query' +import { queryOptions, useQuery, type UseQueryOptions, useInfiniteQuery, type UseInfiniteQueryOptions, type InfiniteData, type QueryKey, useMutation, type UseMutationOptions } from '@tanstack/react-query' import { createTask, deleteTask, getTask, listTasks, updateTask, type ApiError } from './client.js' // ── Query key factories ────────────────────────────────────── @@ -423,6 +571,22 @@ export function useListTasks( }) } +export function useListTasksInfinite( + params?: Parameters[0], + options?: Omit>, ApiError, InfiniteData>>, QueryKey, unknown>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>, +) { + return useInfiniteQuery>, ApiError, InfiniteData>>, QueryKey, unknown>({ + queryKey: [...taskKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listTasks({ ...params, "page": pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 60000, + gcTime: 600000, + ...options, + }) +} + export function useGetTask( id: string | undefined | null, options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, @@ -471,7 +635,7 @@ export function useDeleteTask( exports[`snapshot: full generated output > suspense: true — suspense hook variants 1`] = ` "// This file is auto-generated by @codewithagents/openapi-react-query — do not edit -import { queryOptions, useQuery, type UseQueryOptions, useSuspenseQuery, type UseSuspenseQueryOptions, useMutation, type UseMutationOptions } from '@tanstack/react-query' +import { queryOptions, useQuery, type UseQueryOptions, useSuspenseQuery, type UseSuspenseQueryOptions, useInfiniteQuery, type UseInfiniteQueryOptions, type InfiniteData, type QueryKey, useMutation, type UseMutationOptions } from '@tanstack/react-query' import { createTask, deleteTask, getTask, listTasks, updateTask, type ApiError } from './client.js' // ── Query key factories ────────────────────────────────────── @@ -538,6 +702,22 @@ export function useSuspenseListTasks( }) } +export function useListTasksInfinite( + params?: Parameters[0], + options?: Omit>, ApiError, InfiniteData>>, QueryKey, unknown>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>, +) { + return useInfiniteQuery>, ApiError, InfiniteData>>, QueryKey, unknown>({ + queryKey: [...taskKeys.list(params), 'infinite'], + queryFn: ({ pageParam }) => listTasks({ ...params, "page": pageParam as never }), + initialPageParam: undefined, + // Override getNextPageParam in options to enable actual pagination. + getNextPageParam: () => undefined, + staleTime: 0, + gcTime: 300000, + ...options, + }) +} + export function useGetTask( id: string | undefined | null, options?: Omit>, ApiError>, 'queryKey' | 'queryFn'>, diff --git a/packages/openapi-react-query/src/__tests__/hooks.test.ts b/packages/openapi-react-query/src/__tests__/hooks.test.ts index 2dfcb3e..6d7b738 100644 --- a/packages/openapi-react-query/src/__tests__/hooks.test.ts +++ b/packages/openapi-react-query/src/__tests__/hooks.test.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'node:fs' import { join } from 'node:path' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import type { OpenAPIV3_1 } from 'openapi-types' import { generateHooks } from '../plugins/hooks.js' @@ -2233,3 +2233,519 @@ describe('generateHooks — operationId collides with client-internal helper (#2 expect(hooksContent).not.toMatch(/import\s*\{[^}]*\bfetch\b(?!_)[^}]*\}/) }) }) + +// ── Feature #189: useInfiniteQuery hook generation ──────────────────────────── + +describe('generateHooks — #189: infinite query generation', () => { + // Base helper for building minimal list specs + const makeListSpec = ( + paramNames: string[], + xInfinite?: boolean + ): OpenAPIV3_1.Document => ({ + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items': { + get: { + operationId: 'listItems', + ...(xInfinite !== undefined ? { 'x-infinite': xInfinite } : {}), + parameters: paramNames.map((name) => ({ + name, + in: 'query' as const, + schema: { type: 'integer' as const }, + })), + responses: { '200': { description: 'ok' } }, + }, + }, + }, + }) + + // ── Heuristic detection: positive cases ───────────────────────────────────── + + it('detects pagination by "page" param name', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + it('detects pagination by "cursor" param name', () => { + const { content } = generateHooks(makeListSpec(['cursor']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + it('detects pagination by "offset" param name', () => { + const { content } = generateHooks(makeListSpec(['offset']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + it('detects pagination by "pageToken" param name', () => { + const { content } = generateHooks(makeListSpec(['pageToken']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + it('detects pagination by "after" param name', () => { + const { content } = generateHooks(makeListSpec(['after']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + it('detects pagination by "before" param name', () => { + const { content } = generateHooks(makeListSpec(['before']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + it('detects pagination by "next_page" param name', () => { + const { content } = generateHooks(makeListSpec(['next_page']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + it('detects pagination by "nextPage" param name', () => { + const { content } = generateHooks(makeListSpec(['nextPage']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + it('detects pagination by "page_token" param name', () => { + const { content } = generateHooks(makeListSpec(['page_token']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + }) + + // ── Heuristic detection: negative cases ───────────────────────────────────── + + it('does NOT detect pagination for unrelated param names (limit, size, sort)', () => { + const { content } = generateHooks(makeListSpec(['limit', 'size', 'sort']), { + staleTime: 0, + gcTime: 0, + }) + expect(content).not.toContain('useListItemsInfinite') + expect(content).not.toContain('useInfiniteQuery') + }) + + it('does NOT detect pagination for detail GET ops (with path params)', () => { + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items/{id}': { + get: { + operationId: 'getItem', + parameters: [ + { name: 'id', in: 'path', required: true, schema: { type: 'string' } }, + { name: 'page', in: 'query', schema: { type: 'integer' } }, + ], + responses: { '200': { description: 'ok' } }, + }, + }, + }, + } + const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 }) + expect(content).not.toContain('useGetItemInfinite') + expect(content).not.toContain('useInfiniteQuery') + }) + + it('does NOT generate infinite hooks for mutation ops', () => { + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items': { + post: { + operationId: 'createItem', + parameters: [{ name: 'page', in: 'query', schema: { type: 'integer' } }], + responses: { '201': { description: 'created' } }, + }, + }, + }, + } + const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 }) + expect(content).not.toContain('Infinite') + }) + + it('does NOT emit infinite hook when spec has no GET ops with pagination params', () => { + const { content } = generateHooks(makeListSpec(['filter', 'search']), { + staleTime: 0, + gcTime: 0, + }) + expect(content).not.toContain('useInfiniteQuery') + // Should not import InfiniteData or UseInfiniteQueryOptions + expect(content).not.toContain('InfiniteData') + }) + + // ── x-infinite extension ───────────────────────────────────────────────────── + + it('x-infinite: true forces pagination detection even without standard param name', () => { + const { content } = generateHooks(makeListSpec(['filter'], true), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItemsInfinite') + expect(content).toContain('useInfiniteQuery') + }) + + it('x-infinite: false suppresses infinite hook even when standard page param is present', () => { + const { content } = generateHooks(makeListSpec(['page'], false), { staleTime: 0, gcTime: 0 }) + expect(content).not.toContain('useListItemsInfinite') + expect(content).not.toContain('useInfiniteQuery') + }) + + // ── infiniteQuery config option ────────────────────────────────────────────── + + it('infiniteQuery: false suppresses all infinite hooks globally', () => { + const { content } = generateHooks(makeListSpec(['page']), { + staleTime: 0, + gcTime: 0, + infiniteQuery: false, + }) + expect(content).not.toContain('useListItemsInfinite') + expect(content).not.toContain('useInfiniteQuery') + }) + + it('infiniteQuery: true forces infinite hooks on all list GET ops regardless of param names', () => { + const { content } = generateHooks(makeListSpec(['filter', 'search']), { + staleTime: 0, + gcTime: 0, + infiniteQuery: true, + }) + expect(content).toContain('export function useListItemsInfinite') + expect(content).toContain('useInfiniteQuery') + }) + + it('infiniteQuery: auto (default) uses heuristic detection', () => { + const withPage = generateHooks(makeListSpec(['page']), { + staleTime: 0, + gcTime: 0, + infiniteQuery: 'auto', + }) + expect(withPage.content).toContain('export function useListItemsInfinite') + + const withoutPage = generateHooks(makeListSpec(['limit']), { + staleTime: 0, + gcTime: 0, + infiniteQuery: 'auto', + }) + expect(withoutPage.content).not.toContain('useListItemsInfinite') + }) + + // ── Naming convention ──────────────────────────────────────────────────────── + + it('hook name follows use${capitalize(funcName)}Infinite suffix pattern', () => { + const { content } = generateHooks(makeListSpec(['cursor']), { staleTime: 0, gcTime: 0 }) + // funcName is "listItems" → hook is "useListItemsInfinite" + expect(content).toContain('export function useListItemsInfinite') + // Must NOT use "useInfiniteListItems" (prefix pattern is wrong) + expect(content).not.toContain('export function useInfiniteListItems') + }) + + // ── Generated hook structure ───────────────────────────────────────────────── + + it('generated hook calls useInfiniteQuery (not useQuery)', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + const hookStart = content.indexOf('export function useListItemsInfinite') + const hookEnd = content.indexOf('\n}', hookStart) + 2 + const hookBody = content.slice(hookStart, hookEnd) + expect(hookBody).toContain('return useInfiniteQuery<') + expect(hookBody).not.toContain('useQuery<') + }) + + it('generated hook includes initialPageParam: undefined', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('initialPageParam: undefined,') + }) + + it('generated hook includes getNextPageParam placeholder', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('getNextPageParam: () => undefined,') + }) + + it('queryKey appends "infinite" segment to the list key', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + const hookStart = content.indexOf('export function useListItemsInfinite') + const hookEnd = content.indexOf('\n}', hookStart) + 2 + const hookBody = content.slice(hookStart, hookEnd) + expect(hookBody).toContain(`queryKey: [...itemKeys.list(params), 'infinite']`) + }) + + it('queryFn injects pageParam into params under the detected param name', () => { + const { content } = generateHooks(makeListSpec(['cursor']), { staleTime: 0, gcTime: 0 }) + const hookStart = content.indexOf('export function useListItemsInfinite') + const hookEnd = content.indexOf('\n}', hookStart) + 2 + const hookBody = content.slice(hookStart, hookEnd) + expect(hookBody).toContain('"cursor": pageParam as never') + }) + + it('...options spread is last so caller can override getNextPageParam and initialPageParam', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + const hookStart = content.indexOf('export function useListItemsInfinite') + const hookEnd = content.indexOf('\n}', hookStart) + 2 + const hookBody = content.slice(hookStart, hookEnd) + const getNextPageParamIdx = hookBody.indexOf('getNextPageParam:') + const spreadIdx = hookBody.indexOf('...options,') + expect(getNextPageParamIdx).toBeGreaterThan(-1) + expect(spreadIdx).toBeGreaterThan(getNextPageParamIdx) + }) + + it('options type omits queryKey, queryFn, getNextPageParam, initialPageParam so partial options like { enabled } typecheck', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + // Omitting getNextPageParam and initialPageParam from the options type makes them + // optional at the call site; the hook body provides defaults and ...options overrides. + expect(content).toContain(`Omit { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('export function useListItems(') + expect(content).toContain('export function useListItemsInfinite(') + }) + + it('regular useListItems hook is not affected when infinite is generated alongside', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + const regularHookStart = content.indexOf('export function useListItems(') + const regularHookEnd = content.indexOf('\n}', regularHookStart) + 2 + const regularHookBody = content.slice(regularHookStart, regularHookEnd) + // Regular hook must still use useQuery, not useInfiniteQuery + expect(regularHookBody).toContain('return useQuery<') + expect(regularHookBody).not.toContain('useInfiniteQuery') + }) + + // ── React Query imports ────────────────────────────────────────────────────── + + it('imports useInfiniteQuery, UseInfiniteQueryOptions, InfiniteData, QueryKey when paginated op exists', () => { + const { content } = generateHooks(makeListSpec(['page']), { staleTime: 0, gcTime: 0 }) + expect(content).toContain('useInfiniteQuery') + expect(content).toContain('type UseInfiniteQueryOptions') + expect(content).toContain('type InfiniteData') + expect(content).toContain('type QueryKey') + }) + + it('does NOT import infinite query types when no paginated op exists', () => { + const { content } = generateHooks(makeListSpec(['filter']), { staleTime: 0, gcTime: 0 }) + expect(content).not.toContain('UseInfiniteQueryOptions') + expect(content).not.toContain('InfiniteData') + }) + + // ── staleTime/gcTime ───────────────────────────────────────────────────────── + + it('infinite hook uses effective staleTime and gcTime', () => { + const { content } = generateHooks(makeListSpec(['page']), { + staleTime: 5000, + gcTime: 60000, + }) + const hookStart = content.indexOf('export function useListItemsInfinite') + const hookEnd = content.indexOf('\n}', hookStart) + 2 + const hookBody = content.slice(hookStart, hookEnd) + expect(hookBody).toContain('staleTime: 5000,') + expect(hookBody).toContain('gcTime: 60000,') + }) + + // ── Deprecated operation ───────────────────────────────────────────────────── + + it('deprecated GET with pagination emits @deprecated JSDoc on the infinite hook', () => { + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items': { + get: { + operationId: 'listItems', + deprecated: true, + parameters: [{ name: 'page', in: 'query', schema: { type: 'integer' } }], + responses: { '200': { description: 'ok' } }, + }, + }, + }, + } + const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 }) + const hookIdx = content.indexOf('export function useListItemsInfinite') + expect(hookIdx).toBeGreaterThan(-1) + const before = content.slice(0, hookIdx) + expect(before.slice(before.lastIndexOf('/**'))).toContain('@deprecated') + }) + + // ── $ref param resolution ──────────────────────────────────────────────────── + + it('detects pagination from $ref query params (resolves component $refs)', () => { + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items': { + get: { + operationId: 'listItems', + parameters: [ + { $ref: '#/components/parameters/PageParam' }, + { $ref: '#/components/parameters/LimitParam' }, + ], + responses: { '200': { description: 'ok' } }, + }, + }, + }, + components: { + parameters: { + PageParam: { + name: 'page', + in: 'query', + schema: { type: 'integer' }, + } as OpenAPIV3_1.ParameterObject, + LimitParam: { + name: 'limit', + in: 'query', + schema: { type: 'integer' }, + } as OpenAPIV3_1.ParameterObject, + }, + }, + } + const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 }) + // Should detect 'page' from $ref-resolved parameter + expect(content).toContain('export function useListItemsInfinite') + expect(content).toContain('useInfiniteQuery') + }) + + it('does NOT detect pagination from $ref params when resolved names are non-pagination', () => { + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items': { + get: { + operationId: 'listItems', + parameters: [ + { $ref: '#/components/parameters/FilterParam' }, + { $ref: '#/components/parameters/SortParam' }, + ], + responses: { '200': { description: 'ok' } }, + }, + }, + }, + components: { + parameters: { + FilterParam: { + name: 'filter', + in: 'query', + schema: { type: 'string' }, + } as OpenAPIV3_1.ParameterObject, + SortParam: { + name: 'sort', + in: 'query', + schema: { type: 'string' }, + } as OpenAPIV3_1.ParameterObject, + }, + }, + } + const { content } = generateHooks(spec, { staleTime: 0, gcTime: 0 }) + expect(content).not.toContain('useListItemsInfinite') + expect(content).not.toContain('useInfiniteQuery') + }) + + // ── Snapshot test ──────────────────────────────────────────────────────────── + + it('snapshot: paginated list spec generates correct infinite hook output', () => { + const paginatedSpec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Paginated API', version: '1.0.0' }, + paths: { + '/posts': { + get: { + operationId: 'listPosts', + parameters: [ + { name: 'cursor', in: 'query', schema: { type: 'string' } }, + { name: 'limit', in: 'query', schema: { type: 'integer' } }, + ], + responses: { '200': { description: 'ok' } }, + }, + post: { + operationId: 'createPost', + requestBody: { + required: true, + content: { 'application/json': { schema: { type: 'object' } } }, + }, + responses: { '201': { description: 'created' } }, + }, + }, + '/posts/{id}': { + get: { + operationId: 'getPost', + parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], + responses: { '200': { description: 'ok' } }, + }, + }, + }, + } + const { content } = generateHooks(paginatedSpec, { staleTime: 0, gcTime: 300000 }) + expect(content).toMatchSnapshot() + }) + + // ── x-infinite: true with zero query params (CR fix #2) ───────────────────── + + it('x-infinite: true on an operation with no query params emits a console.warn and skips the hook', () => { + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items': { + get: { + operationId: 'listItems', + 'x-infinite': true, + // No parameters at all + responses: { '200': { description: 'ok' } }, + }, + }, + }, + } + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined) + const { content } = generateHooks(spec as OpenAPIV3_1.Document, { staleTime: 0, gcTime: 0 }) + expect(warnSpy).toHaveBeenCalledOnce() + expect(warnSpy.mock.calls[0]![0]).toContain('x-infinite: true') + expect(warnSpy.mock.calls[0]![0]).toContain('listItems') + expect(warnSpy.mock.calls[0]![0]).toContain('no query params') + // No infinite hook emitted because there is no page param to inject + expect(content).not.toContain('useListItemsInfinite') + expect(content).not.toContain('useInfiniteQuery') + warnSpy.mockRestore() + }) + + it('x-infinite: true with query params emits the hook without warning', () => { + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items': { + get: { + operationId: 'listItems', + 'x-infinite': true, + parameters: [{ name: 'filter', in: 'query', schema: { type: 'string' } }], + responses: { '200': { description: 'ok' } }, + }, + }, + }, + } + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined) + const { content } = generateHooks(spec as OpenAPIV3_1.Document, { staleTime: 0, gcTime: 0 }) + expect(warnSpy).not.toHaveBeenCalled() + expect(content).toContain('useListItemsInfinite') + warnSpy.mockRestore() + }) + + // ── x-infinite: true on a detail endpoint (has path params) — extension wins ─ + + it('x-infinite: true on a detail endpoint (with path params) emits the infinite hook (extension wins)', () => { + // The heuristic would skip this (path params present), but the explicit + // x-infinite: true override takes priority and forces emission. + const spec: OpenAPIV3_1.Document = { + openapi: '3.1.0', + info: { title: 'Test', version: '1.0.0' }, + paths: { + '/items/{id}': { + get: { + operationId: 'getItem', + 'x-infinite': true, + parameters: [ + { name: 'id', in: 'path', required: true, schema: { type: 'string' } }, + { name: 'cursor', in: 'query', schema: { type: 'string' } }, + ], + responses: { '200': { description: 'ok' } }, + }, + }, + }, + } + const { content } = generateHooks(spec as OpenAPIV3_1.Document, { staleTime: 0, gcTime: 0 }) + // x-infinite: true overrides the "no path params" heuristic guard + expect(content).toContain('export function useGetItemInfinite') + }) +}) diff --git a/packages/openapi-react-query/src/config.ts b/packages/openapi-react-query/src/config.ts index 1a2a450..4925f29 100644 --- a/packages/openapi-react-query/src/config.ts +++ b/packages/openapi-react-query/src/config.ts @@ -22,6 +22,13 @@ export interface ReactQueryConfig { overrides?: Record /** When true, mutation hooks auto-invalidate related resource queries on success (default: false) */ auto_invalidate?: boolean + /** + * Controls infinite query hook generation. + * - 'auto' (default): emit useXxxInfinite when pagination params are detected. + * - true: emit useXxxInfinite for all list GET ops regardless of param names. + * - false: never emit useXxxInfinite hooks. + */ + infinite_query?: boolean | 'auto' } function validateTiming(resource: string, timing: unknown): void { @@ -54,11 +61,18 @@ function expectBoolean(raw: Record, key: string, message: strin if (raw[key] !== undefined && typeof raw[key] !== 'boolean') throw new Error(message) } +function validateInfiniteQuery(value: unknown): void { + if (value !== undefined && typeof value !== 'boolean' && value !== 'auto') { + throw new Error('"infinite_query" must be a boolean or "auto"') + } +} + function validateReactQueryFields(raw: Record): void { expectNumber(raw, 'stale_time', '"stale_time" must be a number (milliseconds)') expectNumber(raw, 'gc_time', '"gc_time" must be a number (milliseconds)') expectBoolean(raw, 'suspense', '"suspense" must be a boolean') expectBoolean(raw, 'auto_invalidate', '"auto_invalidate" must be a boolean') + validateInfiniteQuery(raw['infinite_query']) if (raw['overrides'] !== undefined) validateOverrides(raw['overrides']) } @@ -78,6 +92,7 @@ export async function loadConfig(cwd: string, configPath?: string): Promise | undefined, auto_invalidate: raw['auto_invalidate'] as boolean | undefined, + infinite_query: raw['infinite_query'] as boolean | 'auto' | undefined, } }, }) diff --git a/packages/openapi-react-query/src/generator.ts b/packages/openapi-react-query/src/generator.ts index cdf2cba..ec45b16 100644 --- a/packages/openapi-react-query/src/generator.ts +++ b/packages/openapi-react-query/src/generator.ts @@ -37,6 +37,7 @@ export async function generate(cwd: string, configPath?: string): Promise suspense: config.suspense, overrides: Object.keys(overrides).length > 0 ? overrides : undefined, autoInvalidate: config.auto_invalidate, + infiniteQuery: config.infinite_query, }), generateTestUtils(spec), ] diff --git a/packages/openapi-react-query/src/plugins/hooks.ts b/packages/openapi-react-query/src/plugins/hooks.ts index b2ea8a9..e380453 100644 --- a/packages/openapi-react-query/src/plugins/hooks.ts +++ b/packages/openapi-react-query/src/plugins/hooks.ts @@ -12,6 +12,7 @@ import { type OperationObject = OpenAPIV3_1.OperationObject type ReferenceObject = OpenAPIV3_1.ReferenceObject type PathItemObject = OpenAPIV3_1.PathItemObject +type ParameterObject = OpenAPIV3_1.ParameterObject export interface HookGenOptions { staleTime: number @@ -19,6 +20,13 @@ export interface HookGenOptions { suspense?: boolean overrides?: Record autoInvalidate?: boolean + /** + * Controls infinite query hook generation. + * - 'auto' (default): emit useXxxInfinite when pagination params are detected. + * - true: emit useXxxInfinite for all list GET ops regardless of param names. + * - false: never emit useXxxInfinite hooks. + */ + infiniteQuery?: boolean | 'auto' } // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -103,6 +111,149 @@ interface OperationMeta { hasQueryParams: boolean hasRequiredQueryParams: boolean deprecated: boolean + isPaginated: boolean + pageParamName: string | undefined +} + +// ── Pagination detection ─────────────────────────────────────────────────────── + +/** Param names that signal a paginated list endpoint. First match wins. */ +const PAGINATION_PARAM_NAMES = new Set([ + 'page', + 'cursor', + 'offset', + 'pageToken', + 'pagetoken', + 'after', + 'before', + 'next_page', + 'nextPage', + 'page_token', +]) + +// Intentional mirror of openapi-zod-ts client.ts resolveParamRef — kept local to +// avoid a circular dependency between the two packages. Component-ref resolution +// only; deep JSON-pointer refs are out of scope for pagination detection. +function resolveParamRef( + p: ParameterObject | ReferenceObject, + spec: OpenAPIV3_1.Document, + visited?: Set +): ParameterObject | null { + if (!isRef(p)) return p as ParameterObject + const ref = (p as ReferenceObject).$ref + const visitedSet = visited ?? new Set() + if (visitedSet.has(ref)) return null + visitedSet.add(ref) + + const match = /^#\/components\/parameters\/(.+)$/.exec(ref) + if (match !== null) { + const name = match[1]! + const params = spec.components?.parameters as + | Record + | undefined + const resolved = params?.[name] + if (resolved === undefined) return null + return resolveParamRef(resolved, spec, visitedSet) + } + return null +} + +/** + * Returns resolved query parameter names for an operation. + * Merges path-item and operation parameters, resolves $ref params, and + * returns the names of all query-in parameters. + */ +function getResolvedQueryParamNames( + pathItem: PathItemObject, + operation: OperationObject, + spec: OpenAPIV3_1.Document +): string[] { + const pathItemParams = (pathItem.parameters ?? []) as (ParameterObject | ReferenceObject)[] + const operationParams = (operation.parameters ?? []) as (ParameterObject | ReferenceObject)[] + const all = [...pathItemParams, ...operationParams] + const resolved = all + .map((p) => resolveParamRef(p, spec)) + .filter((p): p is ParameterObject => p !== null) + // Deduplicate by (name, in) — operation wins over path-item + const seen = new Map() + for (const p of resolved) { + seen.set(`${p.name}::${p.in}`, p) + } + return [...seen.values()] + .filter((p) => p.in === 'query') + .map((p) => p.name) +} + +const PAGINATED_FALSE = { isPaginated: false, pageParamName: undefined } as const + +/** + * Handles the explicit `x-infinite: true` case. + * Warns and returns a no-op when no query params exist to inject pageParam into. + */ +function detectPaginationXInfiniteTrue( + operation: OperationObject, + pathItem: PathItemObject, + spec: OpenAPIV3_1.Document +): { isPaginated: boolean; pageParamName: string | undefined } { + const queryNames = getResolvedQueryParamNames(pathItem, operation, spec) + const match = queryNames.find((n) => PAGINATION_PARAM_NAMES.has(n)) + const pageParam = match ?? queryNames[0] + if (pageParam === undefined) { + const opId = operation.operationId ?? '(unknown operationId)' + console.warn( + `[openapi-react-query] x-infinite: true on "${opId}" but no query params were found. ` + + `No useXxxInfinite hook will be emitted. ` + + `Add at least one query parameter to the operation or remove x-infinite.` + ) + return PAGINATED_FALSE + } + return { isPaginated: true, pageParamName: pageParam } +} + +/** + * Heuristic path: checks for pagination param names among resolved query params. + * Respects the `infiniteQuery` option (force-on / auto). + */ +function detectPaginationHeuristic( + pathItem: PathItemObject, + operation: OperationObject, + spec: OpenAPIV3_1.Document, + infiniteQuery: boolean | 'auto' +): { isPaginated: boolean; pageParamName: string | undefined } { + const queryNames = getResolvedQueryParamNames(pathItem, operation, spec) + const match = queryNames.find((n) => PAGINATION_PARAM_NAMES.has(n)) + if (infiniteQuery === true) { + return { isPaginated: true, pageParamName: match ?? queryNames[0] } + } + // Default 'auto': only emit when a recognized pagination param is present + return match !== undefined ? { isPaginated: true, pageParamName: match } : PAGINATED_FALSE +} + +/** + * Detects whether an operation should receive an infinite query hook. + * + * Priority order: + * 1. `x-infinite: true/false` on the operation (explicit opt-in/out). + * 2. Heuristic: list-level GET (no path params) with a recognized pagination + * query param name. + * 3. `infiniteQuery: true` forces detection for all list GET ops even without + * standard param names. + * + * Returns `{ isPaginated, pageParamName }`. + */ +function detectPagination( + operation: OperationObject, + pathItem: PathItemObject, + spec: OpenAPIV3_1.Document, + pathParams: string[], + infiniteQuery: boolean | 'auto' +): { isPaginated: boolean; pageParamName: string | undefined } { + const xInfinite = (operation as Record)['x-infinite'] + if (xInfinite === true) return detectPaginationXInfiniteTrue(operation, pathItem, spec) + if (xInfinite === false) return PAGINATED_FALSE + if (pathParams.length > 0) return PAGINATED_FALSE + if (infiniteQuery === false) return PAGINATED_FALSE + return detectPaginationHeuristic(pathItem, operation, spec, infiniteQuery) } // getBodyInfo gained the writable-variant redirect (#242); complexity is under @@ -452,6 +603,87 @@ function buildSuspenseQueryHook( return lines.join('\n') } +// ── Infinite query hook generation ──────────────────────────────────────────── + +/** + * Builds the query key call for the infinite hook. + * Reuses the existing list key entry with an appended 'infinite' segment so + * infinite queries are namespaced away from regular list queries. + */ +function buildInfiniteQueryKeyCall( + keyFactoryName: string, + keyEntry: KeyEntry, + hasQueryParams: boolean +): string { + const base = `${keyFactoryName}.${keyEntry.key}` + const listCall = hasQueryParams ? `${base}(params)` : `${base}()` + return `[...${listCall}, 'infinite']` +} + +/** + * Builds a `useXxxInfinite` hook for a paginated list operation. + * + * The hook: + * - Is named `use${capitalize(funcName)}Infinite` (mirrors useSuspense* suffix). + * - Accepts all params as the regular list hook (path params are none for list ops). + * - Injects `pageParam` from React Query into the client call. + * - Provides default `initialPageParam: undefined` and a placeholder + * `getNextPageParam: () => undefined`; callers override both via `options`. + */ +function buildInfiniteQueryHook( + op: OperationMeta, + keyFactoryName: string, + keyEntry: KeyEntry, + pageParamName: string, + staleTime: number, + gcTime: number +): string { + const lines: string[] = [] + const { funcName, hasQueryParams, hasRequiredQueryParams, deprecated } = op + const infiniteHookName = `use${capitalize(funcName)}Infinite` + + const sigParts: string[] = [] + if (hasQueryParams) { + const paramsToken = hasRequiredQueryParams ? 'params' : 'params?' + sigParts.push(`${paramsToken}: Parameters[0]`) + } + sigParts.push( + `options?: Omit>, ApiError, InfiniteData>>, QueryKey, unknown>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>` + ) + + const queryKeyCall = buildInfiniteQueryKeyCall(keyFactoryName, keyEntry, hasQueryParams) + + // Build queryFn: merges pageParam into the params object under the detected param name + let queryFnBody: string + if (hasQueryParams) { + queryFnBody = `({ pageParam }) => ${funcName}({ ...params, ${JSON.stringify(pageParamName)}: pageParam as never })` + } else { + queryFnBody = `({ pageParam }) => ${funcName}({ ${JSON.stringify(pageParamName)}: pageParam as never } as never)` + } + + if (deprecated) { + lines.push(`/** @deprecated */`) + } + lines.push(`export function ${infiniteHookName}(`) + lines.push(` ${sigParts.join(',\n ')},`) + lines.push(`) {`) + lines.push( + ` return useInfiniteQuery>, ApiError, InfiniteData>>, QueryKey, unknown>({` + ) + lines.push(` queryKey: ${queryKeyCall},`) + lines.push(` queryFn: ${queryFnBody},`) + lines.push(` initialPageParam: undefined,`) + lines.push(` // Override getNextPageParam in options to enable actual pagination.`) + lines.push(` getNextPageParam: () => undefined,`) + lines.push(` staleTime: ${staleTime},`) + lines.push(` gcTime: ${gcTime},`) + lines.push(` ...options,`) + lines.push(` })`) + lines.push(`}`) + + return lines.join('\n') +} + // ── Mutation hook generation ─────────────────────────────────────────────────── interface MutationInvalidateInfo { @@ -637,13 +869,19 @@ function buildMutationHook( // ── Main generator ───────────────────────────────────────────────────────────── +// Cognitive complexity is dominated by the two nested for-loops that existed +// before #189; the pagination detection call added here is a single ternary. +// fallow-ignore-next-line complexity /** * Walks all paths in the spec and produces a flat list of OperationMeta records. * * Name deduplication mirrors the client generator so that funcName values stay in * sync with client exports (e.g. getConfig -> getConfig_2, fetch -> fetch_2). */ -function collectOperations(spec: OpenAPIV3_1.Document): OperationMeta[] { +function collectOperations( + spec: OpenAPIV3_1.Document, + infiniteQuery: boolean | 'auto' = 'auto' +): OperationMeta[] { const paths = spec.paths as Record> | undefined // Build the writable-variant map so getBodyInfo can redirect request-body schemas @@ -690,6 +928,18 @@ function collectOperations(spec: OpenAPIV3_1.Document): OperationMeta[] { const hasRequiredQueryParams = hasRequiredParams const deprecated = operation.deprecated === true + // Pagination detection only applies to GET operations + const { isPaginated, pageParamName } = + method === 'get' + ? detectPagination( + operation, + pathItem as PathItemObject, + spec, + pathParams, + infiniteQuery + ) + : { isPaginated: false, pageParamName: undefined } + operations.push({ funcName, hookName, @@ -701,6 +951,8 @@ function collectOperations(spec: OpenAPIV3_1.Document): OperationMeta[] { hasQueryParams, hasRequiredQueryParams, deprecated, + isPaginated, + pageParamName, }) } } @@ -713,10 +965,11 @@ export function generateHooks( spec: OpenAPIV3_1.Document, options: HookGenOptions ): { filename: string; content: string } { - const { staleTime, gcTime, suspense = false, autoInvalidate = false } = options + const { staleTime, gcTime, suspense = false, autoInvalidate = false, infiniteQuery = 'auto' } = + options // Collect all operations (metadata extraction separated from string emission) - const operations = collectOperations(spec) + const operations = collectOperations(spec, infiniteQuery) // Separate GET vs mutation operations const getOps = operations.filter((op) => op.method === 'get') @@ -797,9 +1050,10 @@ export function generateHooks( keyFactoryBlocks.push(buildKeyFactory(resource, entries)) } - // Build queryOptions factories and query hooks (and suspense variants if enabled) + // Build queryOptions factories and query hooks (and suspense/infinite variants if enabled) const queryOptionsBlocks: string[] = [] const queryHookBlocks: string[] = [] + let needsUseInfiniteQuery = false for (const op of getOps) { const info = opToKeyInfo.get(op.funcName) if (info === undefined) continue @@ -819,6 +1073,19 @@ export function generateHooks( buildSuspenseQueryHook(op, factoryName, keyEntry, effectiveStaleTime, effectiveGcTime) ) } + if (op.isPaginated && op.pageParamName !== undefined) { + needsUseInfiniteQuery = true + queryHookBlocks.push( + buildInfiniteQueryHook( + op, + factoryName, + keyEntry, + op.pageParamName, + effectiveStaleTime, + effectiveGcTime + ) + ) + } } // Build resource → detailKeyName map for auto-invalidate @@ -858,6 +1125,13 @@ export function generateHooks( if (needsQueryOptions) rqImports.push('queryOptions') if (needsUseQuery) rqImports.push('useQuery', 'type UseQueryOptions') if (needsUseSuspenseQuery) rqImports.push('useSuspenseQuery', 'type UseSuspenseQueryOptions') + if (needsUseInfiniteQuery) + rqImports.push( + 'useInfiniteQuery', + 'type UseInfiniteQueryOptions', + 'type InfiniteData', + 'type QueryKey' + ) if (needsUseMutation) rqImports.push('useMutation', 'type UseMutationOptions') if (needsUseQueryClient) rqImports.push('useQueryClient')