dioxus_provider/
hooks.rs

1//! # Provider Hooks
2//!
3//! This module provides hooks for working with providers in Dioxus applications.
4//! It requires `dioxus_provider::global::init_global_providers()` to be called at application startup.
5//!
6//! ## Example
7//!
8//! ```rust
9//! use dioxus::prelude::*;
10//! use dioxus_provider::{prelude::*, global::init_global_providers};
11//!
12//! #[provider]
13//! async fn fetch_user(id: u32) -> Result<String, String> {
14//!     Ok(format!("User {}", id))
15//! }
16//!
17//! #[component]
18//! fn App() -> Element {
19//!     let user = use_provider(fetch_user(), (1,));
20//!     rsx! { div { "User: {user:?}" } }
21//! }
22//!
23//! fn main() {
24//!     init_global_providers();
25//!     launch(App);
26//! }
27//! ```
28
29use dioxus_lib::prelude::*;
30use std::{fmt::Debug, future::Future, hash::Hash, time::Duration};
31use tracing::debug;
32
33use crate::{
34    cache::{AsyncState, ProviderCache},
35    global::{get_global_cache, get_global_refresh_registry},
36    refresh::{RefreshRegistry, TaskType},
37};
38
39/// A unified trait for defining providers - async operations that return data
40///
41/// This trait supports both simple providers (no parameters) and parameterized providers.
42/// Use `Provider<()>` for simple providers and `Provider<ParamType>` for parameterized providers.
43///
44/// ## Features
45///
46/// - **Async Execution**: All providers are async by default
47/// - **Configurable Caching**: Optional cache expiration times
48/// - **Stale-While-Revalidate**: Serve stale data while revalidating in background
49/// - **Auto-Refresh**: Optional automatic refresh at intervals
50/// - **Auto-Dispose**: Automatic cleanup when providers are no longer used
51///
52/// ## Cross-Platform Compatibility
53///
54/// The Provider trait is designed to work across platforms using Dioxus's spawn system:
55/// - Uses `dioxus::spawn` for async execution (no Send + Sync required for most types)
56/// - Parameters may need Send + Sync if shared across contexts
57/// - Output and Error types only need Clone since they stay within Dioxus context
58///
59/// ## Example
60///
61/// ```rust,no_run
62/// use dioxus_provider::prelude::*;
63/// use std::time::Duration;
64///
65/// #[provider(stale_time = "1m", cache_expiration = "5m")]
66/// async fn data_provider() -> Result<String, String> {
67///     // Fetch data from API
68///     Ok("Hello, World!".to_string())
69/// }
70///
71/// #[component]
72/// fn Consumer() -> Element {
73///     let data = use_provider(data_provider(), ());
74///     // ...
75/// }
76/// ```
77pub trait Provider<Param = ()>: Clone + PartialEq + 'static
78where
79    Param: Clone + PartialEq + Hash + Debug + 'static,
80{
81    /// The type of data returned on success
82    type Output: Clone + PartialEq + Send + Sync + 'static;
83    /// The type of error returned on failure  
84    type Error: Clone + Send + Sync + 'static;
85
86    /// Execute the async operation
87    ///
88    /// This method performs the actual work of the provider, such as fetching data
89    /// from an API, reading from a database, or computing a value.
90    fn run(&self, param: Param) -> impl Future<Output = Result<Self::Output, Self::Error>>;
91
92    /// Get a unique identifier for this provider instance with the given parameters
93    ///
94    /// This ID is used for caching and invalidation. The default implementation
95    /// hashes the provider's type and parameters to generate a unique ID.
96    fn id(&self, param: &Param) -> String {
97        use std::hash::{Hash, Hasher};
98        use std::collections::hash_map::DefaultHasher;
99
100        let mut hasher = DefaultHasher::new();
101        std::any::TypeId::of::<Self>().hash(&mut hasher);
102        param.hash(&mut hasher);
103        format!("{:x}", hasher.finish())
104    }
105
106    /// Get the interval duration for automatic refresh (None means no interval)
107    ///
108    /// When set, the provider will automatically refresh its data at the specified
109    /// interval, even if no component is actively watching it.
110    fn interval(&self) -> Option<Duration> {
111        None
112    }
113
114    /// Get the cache expiration duration (None means no expiration)
115    ///
116    /// When set, cached data will be considered expired after this duration and
117    /// will be removed from the cache, forcing a fresh fetch on the next access.
118    fn cache_expiration(&self) -> Option<Duration> {
119        None
120    }
121
122    /// Get the stale time duration for stale-while-revalidate behavior (None means no SWR)
123    ///
124    /// When set, data older than this duration will be considered stale and will
125    /// trigger a background revalidation while still serving the stale data to the UI.
126    fn stale_time(&self) -> Option<Duration> {
127        None
128    }
129}
130
131/// Get the provider cache - requires global providers to be initialized
132fn get_provider_cache() -> ProviderCache {
133    get_global_cache().clone()
134}
135
136/// Get the refresh registry - requires global providers to be initialized
137fn get_refresh_registry() -> RefreshRegistry {
138    get_global_refresh_registry().clone()
139}
140
141/// Hook to access the provider cache for manual cache management
142///
143/// This hook provides direct access to the global provider cache for manual
144/// invalidation, clearing, and other cache operations.
145///
146/// ## Global Providers Required
147///
148/// You must call `init_global_providers()` at application startup before using any provider hooks.
149///
150/// ## Setup
151///
152/// ```rust,no_run
153/// use dioxus_provider::{prelude::*, global::init_global_providers};
154///
155/// fn main() {
156///     init_global_providers();
157///     dioxus::launch(App);
158/// }
159///
160/// #[component]
161/// fn App() -> Element {
162///     rsx! {
163///         MyComponent {}
164///     }
165/// }
166/// ```
167///
168/// ## Example
169///
170/// ```rust,no_run
171/// use dioxus::prelude::*;
172/// use dioxus_provider::prelude::*;
173///
174/// #[component]
175/// fn MyComponent() -> Element {
176///     let cache = use_provider_cache();
177///     
178///     // Manually invalidate a specific cache entry
179///     cache.invalidate("my_provider_key");
180///     
181///     rsx! {
182///         div { "Cache operations example" }
183///     }
184/// }
185/// ```
186pub fn use_provider_cache() -> ProviderCache {
187    get_provider_cache()
188}
189
190/// Hook to invalidate a specific provider cache entry
191///
192/// Returns a function that, when called, will invalidate the cache entry for the
193/// specified provider and parameters, and trigger a refresh of all components
194/// using that provider.
195///
196/// Requires global providers to be initialized with `init_global_providers()`.
197///
198/// ## Example
199///
200/// ```rust,no_run
201/// use dioxus::prelude::*;
202/// use dioxus_provider::prelude::*;
203///
204/// #[provider]
205/// async fn user_provider(id: u32) -> Result<String, String> {
206///     Ok(format!("User {}", id))
207/// }
208///
209/// #[component]
210/// fn MyComponent() -> Element {
211///     let invalidate_user = use_invalidate_provider(user_provider(), 1);
212///     
213///     rsx! {
214///         button {
215///             onclick: move |_| invalidate_user(),
216///             "Refresh User Data"
217///         }
218///     }
219/// }
220/// ```
221pub fn use_invalidate_provider<P, Param>(provider: P, param: Param) -> impl Fn() + Clone
222where
223    P: Provider<Param>,
224    Param: Clone + PartialEq + Hash + Debug + 'static,
225{
226    let cache = get_provider_cache();
227    let refresh_registry = get_refresh_registry();
228    let cache_key = provider.id(&param);
229
230    move || {
231        cache.invalidate(&cache_key);
232        refresh_registry.trigger_refresh(&cache_key);
233    }
234}
235
236/// Hook to clear the entire provider cache
237///
238/// Returns a function that, when called, will clear all cached provider data
239/// and trigger a refresh of all providers currently in use.
240///
241/// Requires global providers to be initialized with `init_global_providers()`.
242///
243/// ## Example
244///
245/// ```rust,no_run
246/// use dioxus::prelude::*;
247/// use dioxus_provider::prelude::*;
248///
249/// #[component]
250/// fn MyComponent() -> Element {
251///     let clear_cache = use_clear_provider_cache();
252///     
253///     rsx! {
254///         button {
255///             onclick: move |_| clear_cache(),
256///             "Clear All Cache"
257///         }
258///     }
259/// }
260/// ```
261pub fn use_clear_provider_cache() -> impl Fn() + Clone {
262    let cache = get_provider_cache();
263    let refresh_registry = get_refresh_registry();
264
265    move || {
266        cache.clear();
267        refresh_registry.clear_all();
268    }
269}
270
271/// Trait for unified provider usage - automatically handles providers with and without parameters
272///
273/// This trait is implemented for all Provider types and provides a unified interface
274/// for using providers regardless of whether they take parameters or not.
275pub trait UseProvider<Args> {
276    /// The type of data returned on success
277    type Output: Clone + PartialEq + Send + Sync + 'static;
278    /// The type of error returned on failure
279    type Error: Clone + Send + Sync + 'static;
280
281    /// Use the provider with the given arguments
282    fn use_provider(self, args: Args) -> Signal<AsyncState<Self::Output, Self::Error>>;
283}
284
285// Helper functions for common provider operations
286
287/// Performs SWR staleness checking and triggers background revalidation if needed
288fn check_and_handle_swr_core<P, Param>(
289    provider: &P,
290    param: &Param,
291    cache_key: &str,
292    cache: &ProviderCache,
293    refresh_registry: &RefreshRegistry,
294) where
295    P: Provider<Param> + Clone,
296    Param: Clone + PartialEq + Hash + Debug + Send + Sync + 'static,
297{
298    let stale_time = provider.stale_time();
299    let cache_expiration = provider.cache_expiration();
300
301    if let Some(stale_duration) = stale_time {
302        if let Ok(cache_lock) = cache.cache.lock() {
303            if let Some(entry) = cache_lock.get(cache_key) {
304                if entry.is_stale(stale_duration)
305                    && !entry.is_expired(cache_expiration.unwrap_or(Duration::from_secs(3600)))
306                    && !refresh_registry.is_revalidation_in_progress(cache_key)
307                {
308                    // Data is stale but not expired and no revalidation in progress - trigger background revalidation
309                    if refresh_registry.start_revalidation(cache_key) {
310                        debug!(
311                            "๐Ÿ”„ [SWR] Data is stale for key: {} - triggering background revalidation",
312                            cache_key
313                        );
314
315                        let cache = cache.clone();
316                        let cache_key_clone = cache_key.to_string();
317                        let provider = provider.clone();
318                        let param = param.clone();
319                        let refresh_registry_clone = refresh_registry.clone();
320
321                        spawn(async move {
322                            let result = provider.run(param).await;
323                            cache.set(cache_key_clone.clone(), result);
324
325                            // Mark revalidation as complete and trigger refresh
326                            refresh_registry_clone.complete_revalidation(&cache_key_clone);
327                            refresh_registry_clone.trigger_refresh(&cache_key_clone);
328                            debug!(
329                                "โœ… [SWR] Background revalidation completed for key: {}",
330                                cache_key_clone
331                            );
332                        });
333                    }
334                }
335            }
336        }
337    }
338}
339
340/// Sets up interval refresh task for a provider
341fn setup_interval_task_core<P, Param>(
342    provider: &P,
343    param: &Param,
344    cache_key: &str,
345    cache: &ProviderCache,
346    refresh_registry: &RefreshRegistry,
347) where
348    P: Provider<Param> + Clone + Send,
349    Param: Clone + PartialEq + Hash + Debug + Send + Sync + 'static,
350{
351    if let Some(interval) = provider.interval() {
352        let cache_clone = cache.clone();
353        let provider_clone = provider.clone();
354        let param_clone = param.clone();
355        let cache_key_clone = cache_key.to_string();
356        let refresh_registry_clone = refresh_registry.clone();
357
358        refresh_registry.start_interval_task(cache_key, interval, move || {
359            // Re-execute the provider and update cache in background
360            let cache_for_task = cache_clone.clone();
361            let provider_for_task = provider_clone.clone();
362            let param_for_task = param_clone.clone();
363            let cache_key_for_task = cache_key_clone.clone();
364            let refresh_registry_for_task = refresh_registry_clone.clone();
365
366            spawn(async move {
367                let result = provider_for_task.run(param_for_task).await;
368                cache_for_task.set(cache_key_for_task.clone(), result);
369
370                // Trigger refresh to mark reactive contexts as dirty and update UI
371                refresh_registry_for_task.trigger_refresh(&cache_key_for_task);
372            });
373        });
374    }
375}
376
377/// Sets up automatic cache expiration monitoring for providers
378fn setup_cache_expiration_task_core<P, Param>(
379    provider: &P,
380    _param: &Param,
381    cache_key: &str,
382    cache: &ProviderCache,
383    refresh_registry: &RefreshRegistry,
384) where
385    P: Provider<Param> + Clone + Send,
386    Param: Clone + PartialEq + Hash + Debug + Send + Sync + 'static,
387{
388    if let Some(expiration) = provider.cache_expiration() {
389        let cache_clone = cache.clone();
390        let cache_key_clone = cache_key.to_string();
391        let refresh_registry_clone = refresh_registry.clone();
392
393        refresh_registry.start_periodic_task(
394            cache_key,
395            TaskType::CacheExpiration,
396            expiration / 4, // Check every quarter of the expiration time
397            move || {
398                // Check if cache entry has expired
399                if let Ok(mut cache_lock) = cache_clone.cache.lock() {
400                    if let Some(entry) = cache_lock.get(&cache_key_clone) {
401                        if entry.is_expired(expiration) {
402                            debug!(
403                                "๐Ÿ—‘๏ธ [AUTO-EXPIRATION] Cache expired for key: {} - triggering reactive refresh",
404                                cache_key_clone
405                            );
406                            cache_lock.remove(&cache_key_clone);
407                            drop(cache_lock); // Release lock before triggering refresh
408                            
409                            // Trigger refresh to mark all reactive contexts as dirty
410                            refresh_registry_clone.trigger_refresh(&cache_key_clone);
411                        }
412                    }
413                }
414            },
415        );
416    }
417}
418
419/// Sets up automatic stale-checking task for SWR providers
420fn setup_stale_check_task_core<P, Param>(
421    provider: &P,
422    param: &Param,
423    cache_key: &str,
424    cache: &ProviderCache,
425    refresh_registry: &RefreshRegistry,
426) where
427    P: Provider<Param> + Clone + Send,
428    Param: Clone + PartialEq + Hash + Debug + Send + Sync + 'static,
429{
430    if let Some(stale_time) = provider.stale_time() {
431        let cache_clone = cache.clone();
432        let provider_clone = provider.clone();
433        let param_clone = param.clone();
434        let cache_key_clone = cache_key.to_string();
435        let refresh_registry_clone = refresh_registry.clone();
436
437        refresh_registry.start_stale_check_task(cache_key, stale_time, move || {
438            // Check if data is stale and trigger revalidation if needed
439            check_and_handle_swr_core(
440                &provider_clone,
441                &param_clone,
442                &cache_key_clone,
443                &cache_clone,
444                &refresh_registry_clone,
445            );
446        });
447    }
448}
449
450/// Core provider implementation that handles all the common logic
451fn use_provider_core<P, Param>(provider: P, param: Param) -> Signal<AsyncState<P::Output, P::Error>>
452where
453    P: Provider<Param> + Send + Clone,
454    Param: Clone + PartialEq + Hash + Debug + Send + Sync + 'static,
455{
456    let mut state = use_signal(|| AsyncState::Loading);
457    let cache = get_provider_cache();
458    let refresh_registry = get_refresh_registry();
459
460    let cache_key = provider.id(&param);
461    let cache_expiration = provider.cache_expiration();
462
463    // Setup intelligent cache management (replaces old auto-dispose system)
464    setup_intelligent_cache_management(&provider, &cache_key, &cache, &refresh_registry);
465
466    // Check cache expiration before the memo - this happens on every render
467    check_and_handle_cache_expiration(cache_expiration, &cache_key, &cache, &refresh_registry);
468
469    // SWR staleness checking - runs on every render to check for stale data
470    check_and_handle_swr_core(&provider, &param, &cache_key, &cache, &refresh_registry);
471
472    // Use memo with reactive dependencies to track changes automatically
473    let _execution_memo = use_memo(use_reactive!(|(provider, param)| {
474        let cache_key = provider.id(&param);
475
476        debug!(
477            "๐Ÿ”„ [USE_PROVIDER] Memo executing for key: {} with param: {:?}",
478            cache_key, param
479        );
480
481        // Subscribe to refresh events for this cache key if we have a reactive context
482        if let Some(reactive_context) = ReactiveContext::current() {
483            refresh_registry.subscribe_to_refresh(&cache_key, reactive_context);
484        }
485
486        // Read the current refresh count (this makes the memo reactive to changes)
487        let _current_refresh_count = refresh_registry.get_refresh_count(&cache_key);
488
489        // Set up cache expiration monitoring task
490        setup_cache_expiration_task_core(&provider, &param, &cache_key, &cache, &refresh_registry);
491
492        // Set up interval task if provider has interval configured
493        setup_interval_task_core(&provider, &param, &cache_key, &cache, &refresh_registry);
494
495        // Set up stale check task if provider has stale time configured
496        setup_stale_check_task_core(&provider, &param, &cache_key, &cache, &refresh_registry);
497
498        // Check cache for valid data
499        if let Some(cached_result) = cache.get::<Result<P::Output, P::Error>>(&cache_key) {
500            // Access tracking is automatically handled by cache.get() updating last_accessed time
501            debug!("๐Ÿ“Š [CACHE-HIT] Serving cached data for: {}", cache_key);
502
503            match cached_result {
504                Ok(data) => {
505                    let _ = spawn(async move {
506                        state.set(AsyncState::Success(data));
507                    });
508                }
509                Err(error) => {
510                    let _ = spawn(async move {
511                        state.set(AsyncState::Error(error));
512                    });
513                }
514            }
515            return;
516        }
517
518        // Cache miss - set loading and spawn async task
519        let _ = spawn(async move {
520            state.set(AsyncState::Loading);
521        });
522
523        let cache = cache.clone();
524        let cache_clone = cache.clone();
525        let cache_key_clone = cache_key.clone();
526        let provider = provider.clone();
527        let param = param.clone();
528        let mut state_for_async = state;
529
530        spawn(async move {
531            let result = provider.run(param).await;
532            cache_clone.set(cache_key_clone.clone(), result.clone());
533
534            // Access tracking is automatically handled when data is stored and accessed
535            debug!("๐Ÿ“Š [CACHE-STORE] Stored new data for: {}", cache_key_clone);
536
537            match result {
538                Ok(data) => state_for_async.set(AsyncState::Success(data)),
539                Err(error) => state_for_async.set(AsyncState::Error(error)),
540            }
541        });
542    }));
543
544    state
545}
546
547/// Implementation for providers with no parameters (simple providers)
548impl<P> UseProvider<()> for P
549where
550    P: Provider<()> + Send,
551{
552    type Output = P::Output;
553    type Error = P::Error;
554
555    fn use_provider(self, _args: ()) -> Signal<AsyncState<Self::Output, Self::Error>> {
556        use_provider_core(self, ())
557    }
558}
559
560/// Implementation for providers with parameters (parameterized providers)
561impl<P, Param> UseProvider<(Param,)> for P
562where
563    P: Provider<Param> + Send,
564    Param: Clone + PartialEq + Hash + Debug + Send + Sync + 'static,
565{
566    type Output = P::Output;
567    type Error = P::Error;
568
569    fn use_provider(self, args: (Param,)) -> Signal<AsyncState<Self::Output, Self::Error>> {
570        let param = args.0;
571        use_provider_core(self, param)
572    }
573}
574
575/// Implementation for providers with a single u32 parameter (without tuple wrapper)
576impl<P> UseProvider<u32> for P
577where
578    P: Provider<u32> + Send,
579{
580    type Output = P::Output;
581    type Error = P::Error;
582
583    fn use_provider(self, args: u32) -> Signal<AsyncState<Self::Output, Self::Error>> {
584        use_provider_core(self, args)
585    }
586}
587
588/// Implementation for providers with a single String parameter (without tuple wrapper)
589impl<P> UseProvider<String> for P
590where
591    P: Provider<String> + Send,
592{
593    type Output = P::Output;
594    type Error = P::Error;
595
596    fn use_provider(self, args: String) -> Signal<AsyncState<Self::Output, Self::Error>> {
597        use_provider_core(self, args)
598    }
599}
600
601/// Implementation for providers with a single i32 parameter (without tuple wrapper)
602impl<P> UseProvider<i32> for P
603where
604    P: Provider<i32> + Send,
605{
606    type Output = P::Output;
607    type Error = P::Error;
608
609    fn use_provider(self, args: i32) -> Signal<AsyncState<Self::Output, Self::Error>> {
610        use_provider_core(self, args)
611    }
612}
613
614/// Shared cache expiration logic
615fn check_and_handle_cache_expiration(
616    cache_expiration: Option<Duration>,
617    cache_key: &str,
618    cache: &ProviderCache,
619    refresh_registry: &RefreshRegistry,
620) {
621    if let Some(expiration) = cache_expiration {
622        if let Ok(mut cache_lock) = cache.cache.lock() {
623            if let Some(entry) = cache_lock.get(cache_key) {
624                if entry.is_expired(expiration) {
625                    debug!(
626                        "๐Ÿ—‘๏ธ [CACHE EXPIRATION] Removing expired cache entry for key: {}",
627                        cache_key
628                    );
629                    cache_lock.remove(cache_key);
630                    // Trigger a refresh to re-execute the provider
631                    refresh_registry.trigger_refresh(cache_key);
632                }
633            }
634        }
635    }
636}
637
638/// Unified hook for using any provider - automatically detects parameterized vs non-parameterized providers
639///
640/// This is the main hook for consuming providers in Dioxus components. It automatically
641/// handles both simple providers (no parameters) and parameterized providers, providing
642/// a consistent interface for all provider types.
643///
644/// ## Features
645///
646/// - **Automatic Caching**: Results are cached based on provider configuration
647/// - **Reactive Updates**: Components automatically re-render when data changes
648/// - **Loading States**: Provides loading, success, and error states
649/// - **Background Refresh**: Supports interval refresh and stale-while-revalidate
650/// - **Auto-Dispose**: Automatically cleans up unused providers
651///
652/// ## Usage
653///
654/// ```rust,no_run
655/// use dioxus::prelude::*;
656/// use dioxus_provider::prelude::*;
657///
658/// #[derive(Clone, PartialEq)]
659/// struct DataProvider;
660///
661/// impl Provider<()> for DataProvider {
662///     type Output = String;
663///     type Error = String;
664///
665///     async fn run(&self, _: ()) -> Result<Self::Output, Self::Error> {
666///         Ok("Hello World".to_string())
667///     }
668///
669///     fn id(&self, _: &()) -> String {
670///         "data".to_string()
671///     }
672/// }
673///
674/// #[component]
675/// fn MyComponent() -> Element {
676///     // Provider with no parameters
677///     let data = use_provider(DataProvider, ());
678///     
679///     match *data.read() {
680///         AsyncState::Loading => rsx! { div { "Loading..." } },
681///         AsyncState::Success(ref value) => rsx! { div { "{value}" } },
682///         AsyncState::Error(ref _err) => rsx! { div { "Error occurred" } },
683///     }
684/// }
685/// ```
686pub fn use_provider<P, Args>(provider: P, args: Args) -> Signal<AsyncState<P::Output, P::Error>>
687where
688    P: UseProvider<Args>,
689{
690    provider.use_provider(args)
691}
692
693/// Sets up intelligent cache management for a provider
694///
695/// This replaces the old component-unmount auto-dispose with a better system:
696/// 1. Access-time tracking for LRU management
697/// 2. Periodic cleanup of unused entries based on cache_expiration
698/// 3. Cache size limits with LRU eviction
699/// 4. Automatic background cleanup tasks
700fn setup_intelligent_cache_management<P, Param>(
701    provider: &P,
702    cache_key: &str,
703    cache: &ProviderCache,
704    refresh_registry: &RefreshRegistry,
705) where
706    P: Provider<Param> + Clone,
707    Param: Clone + PartialEq + Hash + Debug + Send + Sync + 'static,
708{
709    // Set up periodic cleanup task for this provider if cache_expiration is configured
710    if let Some(cache_expiration) = provider.cache_expiration() {
711        let cleanup_interval = std::cmp::max(
712            cache_expiration / 4, // Clean up 4x more frequently than expiration
713            Duration::from_secs(30), // But at least every 30 seconds
714        );
715        
716        let cache_clone = cache.clone();
717        let unused_threshold = cache_expiration * 2; // Remove entries unused for 2x expiration time
718        let cleanup_key = format!("{}_cleanup", cache_key);
719
720        refresh_registry.start_periodic_task(
721            &cleanup_key,
722            TaskType::CacheCleanup,
723            cleanup_interval,
724            move || {
725                // Remove entries that haven't been accessed recently
726                let removed = cache_clone.cleanup_unused_entries(unused_threshold);
727                if removed > 0 {
728                    debug!("๐Ÿงน [SMART-CLEANUP] Removed {} unused cache entries", removed);
729                }
730
731                // Enforce cache size limits (configurable - could be made dynamic)
732                const MAX_CACHE_SIZE: usize = 1000;
733                let evicted = cache_clone.evict_lru_entries(MAX_CACHE_SIZE);
734                if evicted > 0 {
735                    debug!("๐Ÿ—‘๏ธ [LRU-EVICT] Evicted {} entries due to cache size limit", evicted);
736                }
737            },
738        );
739
740        debug!("๐Ÿ“Š [SMART-CACHE] Intelligent cache management enabled for: {} (cleanup every {:?})", cache_key, cleanup_interval);
741    }
742}