diff --git a/eslint.config.js b/eslint.config.js index 25f88697..2af2f2e0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,6 +5,15 @@ import reactRefresh from "eslint-plugin-react-refresh"; import tseslint from "typescript-eslint"; import tailwindPlugin from "eslint-plugin-tailwindcss"; +const restrictedSyntax = { + reactQuery: { + useQuery: (v) => + `CallExpression[callee.name='useQuery'] > ObjectExpression:first-child > Property[key.name='${v}']`, + useQueries: (v) => + `CallExpression[callee.name='useQueries'] > ObjectExpression:first-child > Property[key.name='queries'] > ArrayExpression > ObjectExpression > Property[key.name='${v}']`, + }, +}; + export default tseslint.config( { ignores: ["dist"] }, { @@ -56,6 +65,47 @@ export default tseslint.config( ], }, ], + "no-restricted-syntax": [ + "error", + { + selector: [ + restrictedSyntax.reactQuery.useQuery("staleTime"), + restrictedSyntax.reactQuery.useQuery("gcTime"), + restrictedSyntax.reactQuery.useQueries("staleTime"), + restrictedSyntax.reactQuery.useQueries("gcTime"), + ].join(", "), + message: + "`staleTime` & `gcTime` should be managed via the `getQueryCacheConfig` util instead.", + }, + { + selector: [ + restrictedSyntax.reactQuery.useQuery("queryKey"), + restrictedSyntax.reactQuery.useQuery("queryFn"), + restrictedSyntax.reactQuery.useQueries("queryKey"), + restrictedSyntax.reactQuery.useQueries("queryFn"), + ].join(", "), + message: + "'queryKey' & 'queryFn' should be managed by openapi-ts react-query integration instead. This allows standardized management of query keys & cache invalidation.", + }, + { + selector: [ + restrictedSyntax.reactQuery.useQuery("refetchOnMount"), + restrictedSyntax.reactQuery.useQuery("refetchOnReconnect"), + restrictedSyntax.reactQuery.useQuery("refetchOnWindowFocus"), + restrictedSyntax.reactQuery.useQueries("refetchOnMount"), + restrictedSyntax.reactQuery.useQueries("refetchOnReconnect"), + restrictedSyntax.reactQuery.useQueries("refetchOnWindowFocus"), + ].join(", "), + message: + "`refetchOnMount`, `refetchOnReconnect` & `refetchOnWindowFocus` should be managed centrally in the react-query provider", + }, + { + selector: + "CallExpression > MemberExpression[property.name='invalidateQueries']", + message: + "Do not directly call `invalidateQueries`. Instead, use the `invalidateQueries` helper function.", + }, + ], "no-restricted-imports": [ "error", { diff --git a/src/App.tsx b/src/App.tsx index e03ecedf..254fa0d8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,12 @@ import { Header } from "./features/header/components/header"; import { PromptList } from "./components/PromptList"; -import { usePromptsData } from "./hooks/usePromptsData"; +import { useQueryGetWorkspaceMessages } from "./hooks/use-query-get-workspace-messages"; import { Sidebar } from "./components/Sidebar"; import { useSse } from "./hooks/useSse"; import Page from "./Page"; function App() { - const { data: prompts, isLoading } = usePromptsData(); + const { data: prompts, isLoading } = useQueryGetWorkspaceMessages(); useSse(); return ( diff --git a/src/components/react-query-provider.tsx b/src/components/react-query-provider.tsx index 97f9fd62..f4a7f3c7 100644 --- a/src/components/react-query-provider.tsx +++ b/src/components/react-query-provider.tsx @@ -42,7 +42,10 @@ export function QueryClientProvider({ children }: { children: ReactNode }) { new QueryClient({ defaultOptions: { queries: { - ...getQueryCacheConfig("short"), + ...getQueryCacheConfig("no-cache"), + refetchOnMount: true, + refetchOnReconnect: true, + refetchOnWindowFocus: true, }, }, }), @@ -65,6 +68,7 @@ export function QueryClientProvider({ children }: { children: ReactNode }) { setActiveWorkspaceName(newWorkspaceName); + // eslint-disable-next-line no-restricted-syntax void queryClient.invalidateQueries({ refetchType: "all", // Avoid a continuous loop diff --git a/src/features/alerts/hooks/use-query-get-workspace-alerts.ts b/src/features/alerts/hooks/use-query-get-workspace-alerts.ts index 1a2e9f8d..13cdca5c 100644 --- a/src/features/alerts/hooks/use-query-get-workspace-alerts.ts +++ b/src/features/alerts/hooks/use-query-get-workspace-alerts.ts @@ -4,6 +4,7 @@ import { } from "@/api/generated"; import { v1GetWorkspaceAlertsOptions } from "@/api/generated/@tanstack/react-query.gen"; import { useActiveWorkspaceName } from "@/features/workspace/hooks/use-active-workspace-name"; +import { getQueryCacheConfig } from "@/lib/react-query-utils"; import { useQuery } from "@tanstack/react-query"; export function useQueryGetWorkspaceAlerts({ @@ -33,9 +34,7 @@ export function useQueryGetWorkspaceAlerts({ ...rest } = useQuery({ ...v1GetWorkspaceAlertsOptions(options), - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: true, + ...getQueryCacheConfig("5s"), select, }); diff --git a/src/features/alerts/hooks/use-query-get-workspace-token-usage.ts b/src/features/alerts/hooks/use-query-get-workspace-token-usage.ts index 0b43972c..a06e8abd 100644 --- a/src/features/alerts/hooks/use-query-get-workspace-token-usage.ts +++ b/src/features/alerts/hooks/use-query-get-workspace-token-usage.ts @@ -23,9 +23,6 @@ export function useQueryGetWorkspaceTokenUsage< return useQuery({ ...v1GetWorkspaceTokenUsageOptions(options), - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: true, select, }); } diff --git a/src/features/header/components/header-status-menu.tsx b/src/features/header/components/header-status-menu.tsx index 781a46d4..769019eb 100644 --- a/src/features/header/components/header-status-menu.tsx +++ b/src/features/header/components/header-status-menu.tsx @@ -1,5 +1,4 @@ -import { useCodeGateStatus } from "../hooks/use-codegate-status"; -import { HealthStatus } from "../types"; +import { useQueriesCodegateStatus } from "../hooks/use-queries-codegate-status"; import { Button, DialogTrigger, @@ -40,7 +39,7 @@ type CodeGateHealthCheckStatus = | "error_checking_health"; function deriveOverallStatus( - data: ReturnType["data"], + data: ReturnType["data"], isPending: boolean, isError: boolean, ): CodeGateStatus { @@ -48,19 +47,19 @@ function deriveOverallStatus( if (isError) return "error_checking_status"; if ( - data?.health === HealthStatus.HEALTHY && + data?.health?.status === "healthy" && data.version?.error === null && data.version?.is_latest === false ) return "update_available"; - if (data?.health === HealthStatus.HEALTHY) return "healthy"; + if (data?.health?.status === "healthy") return "healthy"; return "unhealthy"; } function deriveVersionStatus( - data: ReturnType["data"], + data: ReturnType["data"], isPending: boolean, isError: boolean, ): CodeGateVersionStatus { @@ -72,14 +71,14 @@ function deriveVersionStatus( } function deriveHealthCheckStatus( - data: ReturnType["data"], + data: ReturnType["data"], isPending: boolean, isError: boolean, ): CodeGateHealthCheckStatus { if (isPending) return "loading"; if (isError) return "error_checking_health"; - if (data?.health == HealthStatus.HEALTHY) return "healthy"; + if (data?.health?.status === "healthy") return "healthy"; return "unhealthy"; } @@ -102,7 +101,7 @@ function getButtonText(status: CodeGateStatus): string { function getVersionText( status: CodeGateVersionStatus, - data: ReturnType["data"], + data: ReturnType["data"], ): ReactNode { switch (status) { case "error_checking_version": @@ -262,7 +261,7 @@ function StatusPopover({ }: { versionStatus: CodeGateVersionStatus; healthCheckStatus: CodeGateHealthCheckStatus; - data: ReturnType["data"]; + data: ReturnType["data"]; }) { return ( @@ -290,7 +289,7 @@ function StatusPopover({ } export function HeaderStatusMenu() { - const { data, isPending, isError } = useCodeGateStatus(); + const { data, isPending, isError } = useQueriesCodegateStatus(); const status = deriveOverallStatus(data, isPending, isError); const versionStatus = deriveVersionStatus(data, isPending, isError); diff --git a/src/features/header/hooks/use-codegate-status.ts b/src/features/header/hooks/use-codegate-status.ts deleted file mode 100644 index ca1d4161..00000000 --- a/src/features/header/hooks/use-codegate-status.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { queryOptions, useQuery } from "@tanstack/react-query"; - -import { healthCheckHealthGet, v1VersionCheck } from "@/api/generated"; -import { HealthStatus, VersionResponse } from "../types"; - -type HealthResponse = { status: "healthy" | unknown } | null; - -const getCodeGateHealth = async (): Promise => { - const data = (await healthCheckHealthGet()).data; - - if ((data as HealthResponse)?.status === "healthy") - return HealthStatus.HEALTHY; - if ((data as HealthResponse)?.status !== "healthy") - return HealthStatus.UNHEALTHY; - - return null; -}; - -const getVersion = async (): Promise => { - return ((await v1VersionCheck()).data as VersionResponse) ?? null; -}; - -export function getQueryOptionsCodeGateStatus() { - return queryOptions({ - queryFn: async () => { - const health = await getCodeGateHealth(); - const version = await getVersion(); - - return { - health, - version: version as VersionResponse | null, - }; - }, - queryKey: ["useHealthCheck"], - refetchInterval: 5_000, - staleTime: Infinity, - gcTime: Infinity, - refetchIntervalInBackground: true, - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: true, - retry: false, - }); -} - -export const useCodeGateStatus = () => - useQuery({ - ...getQueryOptionsCodeGateStatus(), - }); diff --git a/src/features/header/hooks/use-queries-codegate-status.ts b/src/features/header/hooks/use-queries-codegate-status.ts new file mode 100644 index 00000000..bffb4652 --- /dev/null +++ b/src/features/header/hooks/use-queries-codegate-status.ts @@ -0,0 +1,56 @@ +import { useQueries } from "@tanstack/react-query"; + +import { + HealthCheckHealthGetResponse, + V1VersionCheckResponse, +} from "@/api/generated"; +import { VersionResponse } from "../types"; +import { getQueryCacheConfig } from "@/lib/react-query-utils"; +import { + healthCheckHealthGetOptions, + v1VersionCheckOptions, +} from "@/api/generated/@tanstack/react-query.gen"; +import { QueryResult } from "@/types/react-query"; + +type UseQueryReturn = [ + QueryResult, + QueryResult, +]; + +const combine = (results: UseQueryReturn) => { + const [health, version] = results; + + return { + data: { + health: health.data as { status: "healthy" } | null, + version: version.data as VersionResponse | null, + }, + isError: results.some((r) => r.isError), + isPending: results.some((r) => r.isPending), + isFetching: results.some((r) => r.isFetching), + isLoading: results.some((r) => r.isLoading), + isRefetching: results.some((r) => r.isRefetching), + }; +}; + +export const useQueriesCodegateStatus = () => { + return useQueries({ + combine, + queries: [ + { + ...healthCheckHealthGetOptions(), + refetchInterval: 60_000, + refetchIntervalInBackground: true, + retry: false, + ...getQueryCacheConfig("indefinite"), + }, + { + ...v1VersionCheckOptions(), + refetchInterval: 60_000, + refetchIntervalInBackground: true, + retry: false, + ...getQueryCacheConfig("indefinite"), + }, + ], + }); +}; diff --git a/src/features/header/types.ts b/src/features/header/types.ts index 2fdb4c2b..89f7d0ea 100644 --- a/src/features/header/types.ts +++ b/src/features/header/types.ts @@ -1,8 +1,3 @@ -export enum HealthStatus { - HEALTHY = "Healthy", - UNHEALTHY = "Unhealthy", -} - export type VersionResponse = { current_version: string; latest_version: string; diff --git a/src/features/providers/hooks/use-invalidate-providers-queries.ts b/src/features/providers/hooks/use-invalidate-providers-queries.ts index 36aee59c..0b7ec2c1 100644 --- a/src/features/providers/hooks/use-invalidate-providers-queries.ts +++ b/src/features/providers/hooks/use-invalidate-providers-queries.ts @@ -1,15 +1,13 @@ import { v1ListProviderEndpointsQueryKey } from "@/api/generated/@tanstack/react-query.gen"; import { useQueryClient } from "@tanstack/react-query"; import { useCallback } from "react"; +import { invalidateQueries } from "../../../lib/react-query-utils"; export function useInvalidateProvidersQueries() { const queryClient = useQueryClient(); const invalidate = useCallback(async () => { - await queryClient.invalidateQueries({ - queryKey: v1ListProviderEndpointsQueryKey(), - refetchType: "all", - }); + invalidateQueries(queryClient, [v1ListProviderEndpointsQueryKey]); }, [queryClient]); return invalidate; diff --git a/src/features/providers/hooks/use-providers.ts b/src/features/providers/hooks/use-providers.ts index cf112f09..8da5a386 100644 --- a/src/features/providers/hooks/use-providers.ts +++ b/src/features/providers/hooks/use-providers.ts @@ -4,8 +4,5 @@ import { v1ListProviderEndpointsOptions } from "@/api/generated/@tanstack/react- export function useProviders() { return useQuery({ ...v1ListProviderEndpointsOptions(), - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: true, }); } diff --git a/src/features/workspace/components/workspace-custom-instructions.tsx b/src/features/workspace/components/workspace-custom-instructions.tsx index e571407a..80211c12 100644 --- a/src/features/workspace/components/workspace-custom-instructions.tsx +++ b/src/features/workspace/components/workspace-custom-instructions.tsx @@ -51,6 +51,7 @@ import { useMutationSetWorkspaceCustomInstructions } from "../hooks/use-mutation import Fuse from "fuse.js"; import systemPrompts from "../constants/built-in-system-prompts.json"; import { MessageTextSquare02, SearchMd } from "@untitled-ui/icons-react"; +import { invalidateQueries } from "@/lib/react-query-utils"; type DarkModeContextValue = { preference: "dark" | "light" | null; @@ -290,12 +291,10 @@ export function WorkspaceCustomInstructions({ mutateAsync( { ...options, body: { prompt: value } }, { - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: v1GetWorkspaceCustomInstructionsQueryKey(options), - refetchType: "all", - }); - }, + onSuccess: () => + invalidateQueries(queryClient, [ + v1GetWorkspaceCustomInstructionsQueryKey, + ]), }, ); }, diff --git a/src/features/workspace/components/workspaces-selection.tsx b/src/features/workspace/components/workspaces-selection.tsx index 4004743e..b49cad1c 100644 --- a/src/features/workspace/components/workspaces-selection.tsx +++ b/src/features/workspace/components/workspaces-selection.tsx @@ -37,7 +37,8 @@ export function WorkspacesSelection() { const handleWorkspaceClick = (name: string) => { activateWorkspace({ body: { name } }).then(() => { - queryClient.invalidateQueries({ refetchType: "all" }); + // eslint-disable-next-line no-restricted-syntax + queryClient.invalidateQueries({ refetchType: "all" }); // Global setting, refetch **everything** setIsOpen(false); }); }; diff --git a/src/features/workspace/hooks/use-active-workspaces.ts b/src/features/workspace/hooks/use-active-workspaces.ts index 3a088a31..9996d3f5 100644 --- a/src/features/workspace/hooks/use-active-workspaces.ts +++ b/src/features/workspace/hooks/use-active-workspaces.ts @@ -11,9 +11,6 @@ export function useActiveWorkspaces({ ...v1ListActiveWorkspacesOptions(), refetchInterval: 5_000, refetchIntervalInBackground: true, - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: true, retry: false, select, }); diff --git a/src/features/workspace/hooks/use-archived-workspaces.ts b/src/features/workspace/hooks/use-archived-workspaces.ts index 2cc2d3fe..cbd3b571 100644 --- a/src/features/workspace/hooks/use-archived-workspaces.ts +++ b/src/features/workspace/hooks/use-archived-workspaces.ts @@ -11,9 +11,6 @@ export function useArchivedWorkspaces({ ...v1ListArchivedWorkspacesOptions(), refetchInterval: 5000, refetchIntervalInBackground: true, - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: true, retry: false, select, }); diff --git a/src/features/workspace/hooks/use-invalidate-workspace-queries.ts b/src/features/workspace/hooks/use-invalidate-workspace-queries.ts index 3bb23afa..9597bb82 100644 --- a/src/features/workspace/hooks/use-invalidate-workspace-queries.ts +++ b/src/features/workspace/hooks/use-invalidate-workspace-queries.ts @@ -1,7 +1,8 @@ import { v1ListArchivedWorkspacesQueryKey, - v1ListWorkspacesOptions, + v1ListWorkspacesQueryKey, } from "@/api/generated/@tanstack/react-query.gen"; +import { invalidateQueries } from "@/lib/react-query-utils"; import { useQueryClient } from "@tanstack/react-query"; import { useCallback } from "react"; @@ -9,14 +10,10 @@ export function useInvalidateWorkspaceQueries() { const queryClient = useQueryClient(); const invalidate = useCallback(async () => { - await queryClient.invalidateQueries({ - queryKey: v1ListWorkspacesOptions(), - refetchType: "all", - }); - await queryClient.invalidateQueries({ - queryKey: v1ListArchivedWorkspacesQueryKey(), - refetchType: "all", - }); + invalidateQueries(queryClient, [ + v1ListWorkspacesQueryKey, + v1ListArchivedWorkspacesQueryKey, + ]); }, [queryClient]); return invalidate; diff --git a/src/features/workspace/hooks/use-list-workspaces.ts b/src/features/workspace/hooks/use-list-workspaces.ts index 47b7ffca..03afcf01 100644 --- a/src/features/workspace/hooks/use-list-workspaces.ts +++ b/src/features/workspace/hooks/use-list-workspaces.ts @@ -11,9 +11,6 @@ export function useListWorkspaces({ ...v1ListWorkspacesOptions(), refetchInterval: 5000, refetchIntervalInBackground: true, - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: true, retry: false, select, }); diff --git a/src/features/workspace/hooks/use-mutation-activate-workspace.ts b/src/features/workspace/hooks/use-mutation-activate-workspace.ts index 0e7ac68a..0903e20f 100644 --- a/src/features/workspace/hooks/use-mutation-activate-workspace.ts +++ b/src/features/workspace/hooks/use-mutation-activate-workspace.ts @@ -7,6 +7,7 @@ export function useMutationActivateWorkspace() { return useToastMutation({ ...v1ActivateWorkspaceMutation(), + // eslint-disable-next-line no-restricted-syntax onSuccess: () => queryClient.invalidateQueries({ refetchType: "all" }), // Global setting, refetch **everything** successMsg: (variables) => `Activated "${variables.body.name}" workspace`, }); diff --git a/src/features/workspace/hooks/use-mutation-set-workspace-custom-instructions.tsx b/src/features/workspace/hooks/use-mutation-set-workspace-custom-instructions.tsx index e1531c12..4bba5933 100644 --- a/src/features/workspace/hooks/use-mutation-set-workspace-custom-instructions.tsx +++ b/src/features/workspace/hooks/use-mutation-set-workspace-custom-instructions.tsx @@ -5,6 +5,7 @@ import { import { V1GetWorkspaceCustomInstructionsData } from "@/api/generated"; import { useToastMutation } from "@/hooks/use-toast-mutation"; import { useQueryClient } from "@tanstack/react-query"; +import { invalidateQueries } from "@/lib/react-query-utils"; export function useMutationSetWorkspaceCustomInstructions( options: V1GetWorkspaceCustomInstructionsData, @@ -14,10 +15,9 @@ export function useMutationSetWorkspaceCustomInstructions( return useToastMutation({ ...v1SetWorkspaceCustomInstructionsMutation(options), onSuccess: () => - queryClient.invalidateQueries({ - queryKey: v1GetWorkspaceCustomInstructionsQueryKey(options), - refetchType: "all", - }), + invalidateQueries(queryClient, [ + v1GetWorkspaceCustomInstructionsQueryKey, + ]), successMsg: "Successfully updated custom instructions", }); } diff --git a/src/features/workspace/hooks/use-query-list-all-workspaces.ts b/src/features/workspace/hooks/use-query-list-all-workspaces.ts index 875a0e6f..97c0a455 100644 --- a/src/features/workspace/hooks/use-query-list-all-workspaces.ts +++ b/src/features/workspace/hooks/use-query-list-all-workspaces.ts @@ -1,11 +1,4 @@ -import { - DefinedUseQueryResult, - QueryObserverLoadingErrorResult, - QueryObserverLoadingResult, - QueryObserverPendingResult, - QueryObserverRefetchErrorResult, - useQueries, -} from "@tanstack/react-query"; +import { useQueries } from "@tanstack/react-query"; import { v1ListArchivedWorkspacesOptions, v1ListWorkspacesOptions, @@ -14,20 +7,14 @@ import { V1ListArchivedWorkspacesResponse, V1ListWorkspacesResponse, } from "@/api/generated"; +import { QueryResult } from "@/types/react-query"; -type QueryResult = - | DefinedUseQueryResult - | QueryObserverLoadingErrorResult - | QueryObserverLoadingResult - | QueryObserverPendingResult - | QueryObserverRefetchErrorResult; - -type UseQueryDataReturn = [ +type UseQueryReturn = [ QueryResult, QueryResult, ]; -const combine = (results: UseQueryDataReturn) => { +const combine = (results: UseQueryReturn) => { const [workspaces, archivedWorkspaces] = results; const active = workspaces.data?.workspaces diff --git a/src/hooks/use-models-data.ts b/src/hooks/use-models-data.ts index c423147c..b9fc280b 100644 --- a/src/hooks/use-models-data.ts +++ b/src/hooks/use-models-data.ts @@ -4,8 +4,5 @@ import { v1ListAllModelsForAllProvidersOptions } from "@/api/generated/@tanstack export const useModelsData = () => { return useQuery({ ...v1ListAllModelsForAllProvidersOptions(), - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: true, }); }; diff --git a/src/hooks/usePromptsData.ts b/src/hooks/use-query-get-workspace-messages.ts similarity index 86% rename from src/hooks/usePromptsData.ts rename to src/hooks/use-query-get-workspace-messages.ts index 6978bf93..fbb7e2be 100644 --- a/src/hooks/usePromptsData.ts +++ b/src/hooks/use-query-get-workspace-messages.ts @@ -6,6 +6,7 @@ import { } from "@/api/generated"; import { v1GetWorkspaceMessagesOptions } from "@/api/generated/@tanstack/react-query.gen"; import { useActiveWorkspaceName } from "@/features/workspace/hooks/use-active-workspace-name"; +import { getQueryCacheConfig } from "@/lib/react-query-utils"; // NOTE: This needs to be a stable function reference to enable memo-isation of // the select operation on each React re-render. @@ -15,7 +16,7 @@ function select(data: V1GetWorkspaceMessagesResponse): Conversation[] { ); } -export const usePromptsData = () => { +export const useQueryGetWorkspaceMessages = () => { const { data: activeWorkspaceName } = useActiveWorkspaceName(); const options: V1GetWorkspaceMessagesData = { @@ -26,6 +27,7 @@ export const usePromptsData = () => { return useQuery({ ...v1GetWorkspaceMessagesOptions(options), + ...getQueryCacheConfig("5s"), select: select, }); }; diff --git a/src/lib/react-query-utils.ts b/src/lib/react-query-utils.ts index b677843f..9bfb1818 100644 --- a/src/lib/react-query-utils.ts +++ b/src/lib/react-query-utils.ts @@ -15,9 +15,12 @@ type QueryKey = [ ]; // A generic type that describes the possible permutations of openapi-ts -// react-query queryKey functions. The use of `any` is required to make it -// generic enough for use with any openapi-ts queryKey fn. The return type -// constraint is sufficiently strict that it is still safe to use. +// react-query queryKey functions. +// +// NOTE: The use of `any` is required to make it generic enough for use with any +// openapi-ts queryKey fn. The return type constraint is sufficiently strict +// that it is still safe to use. +// // eslint-disable-next-line @typescript-eslint/no-explicit-any type QueryKeyFn = (options: any) => QueryKey[0][]; @@ -34,10 +37,12 @@ const getQueryKeyFnId = ( export function invalidateQueries( queryClient: QueryClient, queryKeyFns: QueryKeyFn[], + options: { stale: boolean } = { stale: true }, ) { + // eslint-disable-next-line no-restricted-syntax return queryClient.invalidateQueries({ refetchType: "all", - stale: true, + stale: options.stale, predicate: ( query: Query, ) => { @@ -49,15 +54,15 @@ export function invalidateQueries( } export function getQueryCacheConfig( - lifetime: "dynamic" | "short" | "indefinite", + lifetime: "no-cache" | "5s" | "indefinite", ) { switch (lifetime) { - case "dynamic": + case "no-cache": return { staleTime: 0, } as const satisfies Pick; - case "short": + case "5s": return { staleTime: 5 * 1_000, } as const satisfies Pick; diff --git a/src/routes/__tests__/route-chat.test.tsx b/src/routes/__tests__/route-chat.test.tsx index f90f38ad..60eb927a 100644 --- a/src/routes/__tests__/route-chat.test.tsx +++ b/src/routes/__tests__/route-chat.test.tsx @@ -19,8 +19,9 @@ vi.mock("@/hooks/useCurrentPromptStore", () => ({ })), })); -vi.mock("@/hooks/usePromptsData", () => ({ - usePromptsData: vi.fn(() => ({ +// TODO: Replace this with MSW +vi.mock("@/hooks/use-query-get-workspace-messages", () => ({ + useQueryGetWorkspaceMessages: vi.fn(() => ({ data: [ { question_answers: [ diff --git a/src/routes/route-chat.tsx b/src/routes/route-chat.tsx index 605d137f..30f1b5f3 100644 --- a/src/routes/route-chat.tsx +++ b/src/routes/route-chat.tsx @@ -1,5 +1,5 @@ import { useParams } from "react-router-dom"; -import { usePromptsData } from "@/hooks/usePromptsData"; +import { useQueryGetWorkspaceMessages } from "@/hooks/use-query-get-workspace-messages"; import { parsingPromptText, sanitizeQuestionPrompt } from "@/lib/utils"; import { ChatMessageList } from "@/components/ui/chat/chat-message-list"; import { @@ -13,7 +13,7 @@ import { BreadcrumbHome } from "@/components/BreadcrumbHome"; export function RouteChat() { const { id } = useParams(); - const { data: prompts } = usePromptsData(); + const { data: prompts } = useQueryGetWorkspaceMessages(); const chat = prompts?.find((prompt) => prompt.chat_id === id); const title = diff --git a/src/types/react-query.ts b/src/types/react-query.ts new file mode 100644 index 00000000..1c98b479 --- /dev/null +++ b/src/types/react-query.ts @@ -0,0 +1,14 @@ +import { + DefinedUseQueryResult, + QueryObserverLoadingErrorResult, + QueryObserverLoadingResult, + QueryObserverPendingResult, + QueryObserverRefetchErrorResult, +} from "@tanstack/react-query"; + +export type QueryResult = + | DefinedUseQueryResult + | QueryObserverLoadingErrorResult + | QueryObserverLoadingResult + | QueryObserverPendingResult + | QueryObserverRefetchErrorResult;