From c7ba32f3bd5b7386833d404ba95aea2faeb5b799 Mon Sep 17 00:00:00 2001 From: bkellam Date: Tue, 18 Nov 2025 13:31:51 -0800 Subject: [PATCH] add client side caching --- .../app/[domain]/search/useStreamedSearch.ts | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/web/src/app/[domain]/search/useStreamedSearch.ts b/packages/web/src/app/[domain]/search/useStreamedSearch.ts index 1e8a9375..861288a1 100644 --- a/packages/web/src/app/[domain]/search/useStreamedSearch.ts +++ b/packages/web/src/app/[domain]/search/useStreamedSearch.ts @@ -3,6 +3,31 @@ import { RepositoryInfo, SearchRequest, SearchResponse, SearchResultFile } from '@/features/search/types'; import { useState, useCallback, useRef, useEffect } from 'react'; +interface CacheEntry { + files: SearchResultFile[]; + repoInfo: Record; + numMatches: number; + durationMs: number; + timestamp: number; +} + +const searchCache = new Map(); +const CACHE_TTL = 5 * 60 * 1000; + +const createCacheKey = (params: SearchRequest): string => { + return JSON.stringify({ + query: params.query, + matches: params.matches, + contextLines: params.contextLines, + whole: params.whole, + isRegexEnabled: params.isRegexEnabled, + isCaseSensitivityEnabled: params.isCaseSensitivityEnabled, + }); +}; + +const isCacheValid = (entry: CacheEntry): boolean => { + return Date.now() - entry.timestamp < CACHE_TTL; +}; export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegexEnabled, isCaseSensitivityEnabled }: SearchRequest) => { @@ -44,6 +69,30 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex } abortControllerRef.current = new AbortController(); + const cacheKey = createCacheKey({ + query, + matches, + contextLines, + whole, + isRegexEnabled, + isCaseSensitivityEnabled, + }); + + // Check if we have a valid cached result. If so, use it. + const cachedEntry = searchCache.get(cacheKey); + if (cachedEntry && isCacheValid(cachedEntry)) { + console.debug('Using cached search results'); + setState({ + isStreaming: false, + error: null, + files: cachedEntry.files, + repoInfo: cachedEntry.repoInfo, + durationMs: cachedEntry.durationMs, + numMatches: cachedEntry.numMatches, + }); + return; + } + setState({ isStreaming: true, error: null, @@ -153,10 +202,19 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex } finally { const endTime = performance.now(); const durationMs = endTime - startTime; - setState(prev => ({ - ...prev, - durationMs, - })); + setState(prev => { + searchCache.set(cacheKey, { + files: prev.files, + repoInfo: prev.repoInfo, + numMatches: prev.numMatches, + durationMs, + timestamp: Date.now(), + }); + return { + ...prev, + durationMs, + } + }); } }