1+ "use client"
2+
3+ import { useCallback , useRef , useMemo } from 'react'
4+ import {
5+ SearchCacheEntry ,
6+ CacheOperations ,
7+ SearchResults ,
8+ SearchQuery ,
9+ SearchResultMetadata
10+ } from '../types/freelancer-search.types'
11+ import { SearchCache } from '../utils/search-performance'
12+ import { generateCacheKey } from '../utils/search-helpers'
13+
14+ interface UseSearchCacheOptions {
15+ maxSize ?: number
16+ defaultTTL ?: number
17+ enableCleanup ?: boolean
18+ cleanupInterval ?: number
19+ }
20+
21+ interface UseSearchCacheReturn extends CacheOperations {
22+ isEnabled : boolean
23+ enable : ( ) => void
24+ disable : ( ) => void
25+ getSize : ( ) => number
26+ exportCache : ( ) => Record < string , any >
27+ importCache : ( data : Record < string , any > ) => void
28+ }
29+
30+
31+ export function useSearchCache ( options : UseSearchCacheOptions = { } ) : UseSearchCacheReturn {
32+ const {
33+ maxSize = 500 ,
34+ defaultTTL = 300000 ,
35+ enableCleanup = true ,
36+ cleanupInterval = 60000
37+ } = options
38+
39+ const cacheRef = useRef < SearchCache > ( new SearchCache ( maxSize , defaultTTL ) )
40+ const cleanupIntervalRef = useRef < NodeJS . Timeout | null > ( null )
41+ const isEnabledRef = useRef < boolean > ( true )
42+
43+ const cache = cacheRef . current
44+
45+ const startCleanupInterval = useCallback ( ( ) => {
46+ if ( cleanupIntervalRef . current ) {
47+ clearInterval ( cleanupIntervalRef . current )
48+ }
49+
50+ if ( enableCleanup ) {
51+ cleanupIntervalRef . current = setInterval ( ( ) => {
52+ cache . cleanup ( )
53+ } , cleanupInterval )
54+ }
55+ } , [ cache , enableCleanup , cleanupInterval ] )
56+
57+ useMemo ( ( ) => {
58+ startCleanupInterval ( )
59+ return ( ) => {
60+ if ( cleanupIntervalRef . current ) {
61+ clearInterval ( cleanupIntervalRef . current )
62+ }
63+ }
64+ } , [ startCleanupInterval ] )
65+
66+
67+
68+
69+ const get = useCallback ( < T , > ( key : string ) : SearchCacheEntry < T > | null => {
70+ if ( ! isEnabledRef . current ) return null
71+ return cache . get < T > ( key )
72+ } , [ cache ] )
73+
74+
75+ const set = useCallback ( < T , > (
76+ key : string | SearchQuery ,
77+ data : T ,
78+ ttl ?: number ,
79+ tags : string [ ] = [ ]
80+ ) : void => {
81+ if ( ! isEnabledRef . current ) return
82+
83+ const cacheKey = typeof key === 'string' ? key : generateCacheKey ( key )
84+ cache . set ( cacheKey , data , ttl , tags )
85+ } , [ cache ] )
86+
87+
88+ const deleteEntry = useCallback ( ( key : string | SearchQuery ) : boolean => {
89+ const cacheKey = typeof key === 'string' ? key : generateCacheKey ( key )
90+ return cache . delete ( cacheKey )
91+ } , [ cache ] )
92+
93+
94+ const clear = useCallback ( ( ) : void => {
95+ cache . clear ( )
96+ } , [ cache ] )
97+
98+
99+ const invalidateByTag = useCallback ( ( tag : string ) : number => {
100+ return cache . invalidateByTag ( tag )
101+ } , [ cache ] )
102+
103+
104+ const cleanup = useCallback ( ( ) : number => {
105+ return cache . cleanup ( )
106+ } , [ cache ] )
107+
108+
109+ const stats = useCallback ( ( ) => {
110+ return cache . stats ( )
111+ } , [ cache ] )
112+
113+
114+ const enable = useCallback ( ( ) : void => {
115+ isEnabledRef . current = true
116+ } , [ ] )
117+
118+
119+ const disable = useCallback ( ( ) : void => {
120+ isEnabledRef . current = false
121+ } , [ ] )
122+
123+
124+ const getSize = useCallback ( ( ) : number => {
125+ return cache . stats ( ) . size
126+ } , [ cache ] )
127+
128+
129+ const exportCache = useCallback ( ( ) : Record < string , any > => {
130+ const exported : Record < string , any > = { }
131+
132+ for ( const [ key , entry ] of ( cache as any ) . cache . entries ( ) ) {
133+ if ( Date . now ( ) < entry . timestamp + entry . ttl ) {
134+ exported [ key ] = {
135+ data : entry . data ,
136+ timestamp : entry . timestamp ,
137+ ttl : entry . ttl ,
138+ tags : entry . tags ,
139+ version : entry . version
140+ }
141+ }
142+ }
143+
144+ return exported
145+ } , [ cache ] )
146+
147+
148+ const importCache = useCallback ( ( data : Record < string , any > ) : void => {
149+ for ( const [ key , entry ] of Object . entries ( data ) ) {
150+ if ( entry && typeof entry === 'object' && entry . data && entry . timestamp ) {
151+ const remainingTTL = ( entry . timestamp + entry . ttl ) - Date . now ( )
152+ if ( remainingTTL > 0 ) {
153+ cache . set ( key , entry . data , remainingTTL , entry . tags || [ ] )
154+ }
155+ }
156+ }
157+ } , [ cache ] )
158+
159+ const cacheOperations : CacheOperations = {
160+ get,
161+ set,
162+ delete : deleteEntry ,
163+ clear,
164+ invalidateByTag,
165+ cleanup,
166+ stats
167+ }
168+
169+ return {
170+ ...cacheOperations ,
171+ isEnabled : isEnabledRef . current ,
172+ enable,
173+ disable,
174+ getSize,
175+ exportCache,
176+ importCache
177+ }
178+ }
179+
180+ export function useSearchResultsCache ( options : UseSearchCacheOptions = { } ) {
181+ const cache = useSearchCache ( options )
182+
183+
184+ const cacheResults = useCallback ( (
185+ query : SearchQuery ,
186+ results : SearchResults ,
187+ metadata ?: Partial < SearchResultMetadata >
188+ ) : void => {
189+ const tags = [
190+ 'search_results' ,
191+ `page_${ query . page } ` ,
192+ `limit_${ query . limit } ` ,
193+ ...query . filters . skills . map ( skill => `skill_${ skill . name } ` ) ,
194+ query . filters . location ?. city ? `city_${ query . filters . location . city } ` : '' ,
195+ query . filters . priceRange ?. currency ? `currency_${ query . filters . priceRange . currency } ` : ''
196+ ] . filter ( Boolean )
197+
198+ cache . set (
199+ generateCacheKey ( query ) ,
200+ {
201+ ...results ,
202+ metadata : metadata ? { ...results . metadata , ...metadata } : results . metadata
203+ } ,
204+ undefined ,
205+ tags
206+ )
207+ } , [ cache ] )
208+
209+
210+ const getCachedResults = useCallback ( ( query : SearchQuery ) : SearchResults | null => {
211+ const cached = cache . get < SearchResults > ( generateCacheKey ( query ) )
212+ return cached ? cached . data : null
213+ } , [ cache ] )
214+
215+ const invalidateByFilter = useCallback ( ( filterType : string , filterValue ?: string ) : void => {
216+ const tag = filterValue ? `${ filterType } _${ filterValue } ` : filterType
217+ cache . invalidateByTag ( tag )
218+ } , [ cache ] )
219+
220+
221+ const prewarmCache = useCallback ( async (
222+ popularQueries : SearchQuery [ ] ,
223+ searchFunction : ( query : SearchQuery ) => Promise < SearchResults >
224+ ) : Promise < void > => {
225+ const prewarmPromises = popularQueries . map ( async ( query ) => {
226+ const existing = getCachedResults ( query )
227+ if ( ! existing ) {
228+ try {
229+ const results = await searchFunction ( query )
230+ //@ts -ignore
231+ cacheResults ( query , results , { prewarmed : true } )
232+ } catch ( error ) {
233+ console . warn ( 'Failed to prewarm cache for query:' , query , error )
234+ }
235+ }
236+ } )
237+
238+ await Promise . allSettled ( prewarmPromises )
239+ } , [ getCachedResults , cacheResults ] )
240+
241+
242+ const getCacheMetrics = useCallback ( ( ) => {
243+ const stats = cache . stats ( )
244+ return {
245+ ...stats ,
246+ effectiveness : stats . hitRate > 0.3 ? 'high' : stats . hitRate > 0.1 ? 'medium' : 'low' ,
247+ recommendation : stats . hitRate < 0.1
248+ ? 'Consider increasing TTL or adjusting cache strategy'
249+ : stats . hitRate > 0.7
250+ ? 'Cache is performing well'
251+ : 'Cache performance is acceptable'
252+ }
253+ } , [ cache ] )
254+
255+ return {
256+ ...cache ,
257+ cacheResults,
258+ getCachedResults,
259+ invalidateByFilter,
260+ prewarmCache,
261+ getCacheMetrics
262+ }
263+ }
0 commit comments