import { keepPreviousData, skipToken, useQueries, useQuery, type QueryKey, type QueryClient, type UseQueryOptions, type UseQueryResult, } from "@tanstack/react-query"; type QueryFnOption = NonNullable< UseQueryOptions["queryFn"] >; type ReplicaQueryInput = Omit< UseQueryOptions, | "gcTime" | "initialData" | "refetchOnMount" | "refetchOnReconnect" | "refetchOnWindowFocus" | "staleTime" > & { pushEvent: string; }; type FetchQueryInput = Omit< UseQueryOptions, "initialData" | "placeholderData" | "queryFn" | "refetchOnMount" | "staleTime" > & { dataShape: "list" | "value"; queryFn: QueryFnOption; } & ( | { staleTimeMs: number; immutableWhen?: never } | { staleTimeMs?: never; immutableWhen: (data: TQueryFnData) => boolean } ); export function useReplicaQuery< TQueryFnData, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >(input: ReplicaQueryInput): UseQueryResult { return useQuery(replicaQueryOptions(input)); } export function useFetchQuery< TQueryFnData, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( input: FetchQueryInput, queryClient?: QueryClient, ): UseQueryResult { return useQuery(fetchQueryOptions(input), queryClient); } export function useFetchQueries( inputs: FetchQueryInput[], ): UseQueryResult[] { return useQueries({ queries: inputs.map((input) => fetchQueryOptions(input)) }); } function replicaQueryOptions< TQueryFnData, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( input: ReplicaQueryInput, ): UseQueryOptions { const { pushEvent, meta, ...options } = input; return { ...options, gcTime: Infinity, meta: { ...meta, serverDataPolicy: { class: "replica", pushEvent, }, }, queryFn: options.queryFn ?? skipToken, refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, staleTime: Infinity, }; } export function fetchQueryOptions< TQueryFnData, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( input: FetchQueryInput, ): UseQueryOptions { if (!input.immutableWhen && !Number.isFinite(input.staleTimeMs)) { throw new Error("Fetch queries must declare a finite staleTimeMs."); } const { dataShape, meta, staleTimeMs, immutableWhen, ...options } = input; return { ...options, ...(dataShape === "list" ? { placeholderData: keepPreviousData } : {}), meta: { ...meta, serverDataPolicy: { class: "fetch", dataShape, }, }, refetchOnMount: immutableWhen ? true : "always", staleTime: immutableWhen ? (query) => query.state.data !== undefined && immutableWhen(query.state.data) ? Infinity : 0 : staleTimeMs, }; }