import type { InfiniteData, QueryFunction, QueryKey } from "@tanstack/react-query" import type { Draft, ValidRecipeReturnType } from "immer" import { produce } from "immer" import { queryClient } from "./query-client" export type DefinedQuery = Readonly<{ key: TQueryKey fn: QueryFunction rootKey?: QueryKey cancel: (key?: (key: TQueryKey) => QueryKey) => Promise remove: (key?: (key: TQueryKey) => QueryKey) => Promise invalidate: (options?: { keyExtractor?: (key: TQueryKey) => QueryKey exact?: boolean }) => Promise invalidateRoot: () => void refetch: () => Promise prefetch: () => Promise setData: ( updater: (draft: Draft) => ValidRecipeReturnType>, ) => void setInfiniteData: ( updater: ( draft: Draft>, ) => ValidRecipeReturnType>>, ) => void getData: () => TData | undefined optimisticUpdate: ( updater: (draft: Draft) => ValidRecipeReturnType> | void, ) => Promise<{ previousData: Awaited | undefined restore: () => void invalidate: () => void }> optimisticInfiniteUpdate: ( updater: ( draft: Draft>, ) => ValidRecipeReturnType>>, ) => Promise<{ previousData: Awaited> | undefined restore: () => void invalidate: () => void }> }> export type DefinedQueryOptions = { // shouldPersist?: boolean; rootKey?: QueryKey onCancel?: () => void | Promise onInvalidate?: () => void | Promise onInvalidateRoot?: () => void onRefetch?: (data?: TData) => void onRefetchRoot?: () => void onOptimisticUpdate?: () => void onOptimisticUpdateRestore?: () => void } export function defineQuery< TQueryKey extends QueryKey, TQueryFn extends QueryFunction, TData = Awaited>, >( key: TQueryKey, fn: TQueryFn, options?: DefinedQueryOptions, ): DefinedQuery export function defineQuery< TQueryKey extends QueryKey, TQueryFn extends QueryFunction, TData = Awaited>, >(key: TQueryKey, fn: TQueryFn, options?: DefinedQueryOptions) { const queryDefine: DefinedQuery = { key, fn, rootKey: options?.rootKey, invalidateRoot: () => { if (options?.rootKey) { queryClient.invalidateQueries({ queryKey: options.rootKey, refetchType: "all", }) options?.onInvalidateRoot?.() } }, prefetch: async () => { await queryClient.prefetchQuery({ queryKey: key, queryFn: fn, }) return queryClient.getQueryData(key) }, cancel: async (keyExtactor) => { const queryKey = typeof keyExtactor === "function" ? keyExtactor(key) : key await queryClient.cancelQueries({ queryKey, }) options?.onCancel?.() }, remove: async (keyExtactor) => { const queryKey = typeof keyExtactor === "function" ? keyExtactor(key) : key queryClient.removeQueries({ queryKey }) }, invalidate: async (args) => { const { keyExtractor, exact } = args || {} const queryKey = typeof keyExtractor === "function" ? keyExtractor(key) : key await queryClient.invalidateQueries({ queryKey, refetchType: "all", exact, }) options?.onInvalidate?.() }, refetch: async () => { await queryClient.refetchQueries({ queryKey: key, }) options?.onRefetch?.() return queryClient.getQueryData(key) }, setData: (updater) => queryClient.setQueryData(key, (old) => { if (!old) return if (typeof updater !== "function") return old return produce(old, updater) }), setInfiniteData: (updater) => queryDefine.setData>(updater), getData: () => queryClient.getQueryData(key), optimisticUpdate: async ( updater: (draft: Draft) => ValidRecipeReturnType>, ) => { await queryClient.cancelQueries({ queryKey: key, }) const previousData = await queryClient.getQueryData(key) await queryClient.setQueryData(key, (old) => { if (!old) return if (typeof updater !== "function") return old return produce(old, updater) }) options?.onOptimisticUpdate?.() return { previousData, restore: () => { queryClient.setQueryData(key, previousData) options?.onOptimisticUpdateRestore?.() }, invalidate: () => { queryClient.invalidateQueries({ queryKey: key, refetchType: "all" }) options?.onInvalidate?.() }, } }, optimisticInfiniteUpdate(updater) { return queryDefine.optimisticUpdate>(updater) }, } return Object.freeze(queryDefine) }