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(¶m);
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 ¶m_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(¶m);
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, ¶m, &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(¶m);
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, ¶m, &cache_key, &cache, &refresh_registry);
491
492 // Set up interval task if provider has interval configured
493 setup_interval_task_core(&provider, ¶m, &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, ¶m, &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}