useCachedFileLinkSearch.ts2.8 KBView on GitHub
import { useCallback } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';

import { useTRPC } from '@/providers/query-provider';
import type { FileLinkSuggestionItem } from '@/modules/documents/file-link/FileLinkSuggestion';

/**
 * Cached `[[` file-link search, shared by the document editor and the chat composer.
 *
 * Mounting this hook pre-warms the "recent files" list (empty query) into React Query
 * with an infinite `gcTime`, so the popover paints instantly on first `[[` instead of
 * waiting on a network round-trip. Typed searches check the search cache, then filter
 * the warm recent list locally (prefetching fresh results in the background) before
 * ever hitting the server — mirroring the `@` conversation mention provider.
 */
export function useCachedFileLinkSearch(): (query: string) => Promise<FileLinkSuggestionItem[]> {
  const trpc = useTRPC();
  const queryClient = useQueryClient();

  // Pre-warm the recent-files cache on mount so the first `[[` is instant.
  useQuery(
    trpc.files.searchForLink.queryOptions(
      { query: '', limit: 30 },
      { staleTime: 5 * 60 * 1000, gcTime: Infinity },
    ),
  );

  return useCallback(
    async (query: string): Promise<FileLinkSuggestionItem[]> => {
      const trimmed = query.trim();
      const recentOptions = trpc.files.searchForLink.queryOptions(
        { query: '', limit: 30 },
        { staleTime: 5 * 60 * 1000, gcTime: Infinity },
      );

      if (!trimmed) {
        const cachedRecent = queryClient.getQueryData<FileLinkSuggestionItem[]>(
          recentOptions.queryKey,
        );
        if (cachedRecent) return cachedRecent;
        return await queryClient.fetchQuery(recentOptions);
      }

      const searchOptions = trpc.files.searchForLink.queryOptions(
        { query: trimmed, limit: 8 },
        { staleTime: 30_000 },
      );
      const cachedSearch = queryClient.getQueryData<FileLinkSuggestionItem[]>(
        searchOptions.queryKey,
      );
      if (cachedSearch) return cachedSearch;

      const cachedRecent = queryClient.getQueryData<FileLinkSuggestionItem[]>(
        recentOptions.queryKey,
      );
      if (cachedRecent && cachedRecent.length > 0) {
        const q = trimmed.toLowerCase();
        const filteredRecent = cachedRecent.filter((item) => {
          return (
            item.label.toLowerCase().includes(q) ||
            (item.path ?? '').toLowerCase().includes(q) ||
            item.documentType.toLowerCase().includes(q) ||
            (item.description?.toLowerCase().includes(q) ?? false)
          );
        });
        void queryClient.prefetchQuery(searchOptions);
        if (filteredRecent.length > 0) return filteredRecent.slice(0, 8);
      }

      return await queryClient.fetchQuery(searchOptions);
    },
    [queryClient, trpc],
  );
}