dioxus_provider/global.rs
1//! # Global Provider Management
2//!
3//! This module provides global singletons for cache, disposal, and refresh management
4//! that operate at application scale rather than component lifecycle scale.
5
6use std::sync::OnceLock;
7
8use crate::{cache::ProviderCache, refresh::RefreshRegistry};
9
10/// Error type for global provider operations
11#[derive(Debug, thiserror::Error)]
12pub enum GlobalProviderError {
13 #[error("Global providers not initialized. Call init_global_providers() first.")]
14 NotInitialized,
15 #[error("Failed to initialize global providers: {0}")]
16 InitializationFailed(String),
17}
18
19/// Global singleton instance of the provider cache
20static GLOBAL_CACHE: OnceLock<ProviderCache> = OnceLock::new();
21
22/// Global singleton instance of the refresh registry
23static GLOBAL_REFRESH_REGISTRY: OnceLock<RefreshRegistry> = OnceLock::new();
24
25/// Configuration for initializing the global provider system
26#[derive(Default, Debug, Clone)]
27pub struct ProviderConfig {
28 enable_dependency_injection: bool,
29}
30
31impl ProviderConfig {
32 /// Create a new provider configuration with default settings
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 /// Enable dependency injection support
38 pub fn with_dependency_injection(mut self) -> Self {
39 self.enable_dependency_injection = true;
40 self
41 }
42
43 /// Initialize the global provider system with this configuration
44 pub fn init(self) -> Result<(), GlobalProviderError> {
45 // Initialize cache first
46 GLOBAL_CACHE.get_or_init(ProviderCache::new);
47
48 // Initialize refresh registry
49 let _refresh_registry = GLOBAL_REFRESH_REGISTRY.get_or_init(RefreshRegistry::new);
50
51 // Initialize dependency injection if enabled
52 if self.enable_dependency_injection {
53 crate::injection::ensure_dependency_injection_initialized();
54 }
55
56 Ok(())
57 }
58}
59
60/// Initialize the global provider system with all features enabled
61///
62/// This is the recommended way to initialize dioxus-provider. It sets up:
63/// - Global cache for provider results
64/// - Refresh registry for reactive updates
65/// - Dependency injection system
66///
67/// ## Example
68///
69/// ```rust,no_run
70/// use dioxus::prelude::*;
71/// use dioxus_provider::global::init;
72///
73/// fn main() {
74/// // Initialize global provider system
75/// init();
76///
77/// // Launch your app
78/// dioxus::launch(app);
79/// }
80///
81/// #[component]
82/// fn app() -> Element {
83/// rsx! {
84/// div { "Hello World!" }
85/// }
86/// }
87/// ```
88pub fn init() -> Result<(), GlobalProviderError> {
89 ProviderConfig::new().with_dependency_injection().init()
90}
91
92/// Initialize the global provider management system (without dependency injection)
93///
94/// This should be called once at the start of your application,
95/// typically in your main function or app initialization.
96///
97/// ## Example
98///
99/// ```rust,no_run
100/// use dioxus::prelude::*;
101/// use dioxus_provider::global::init_global_providers;
102///
103/// fn main() {
104/// // Initialize global provider system
105/// init_global_providers();
106///
107/// // Launch your app
108/// dioxus::launch(app);
109/// }
110///
111/// #[component]
112/// fn app() -> Element {
113/// rsx! {
114/// div { "Hello World!" }
115/// }
116/// }
117/// ```
118#[deprecated(
119 since = "0.1.0",
120 note = "Use init() or ProviderConfig::new().init() instead"
121)]
122pub fn init_global_providers() -> Result<(), GlobalProviderError> {
123 ProviderConfig::new().init()
124}
125
126/// Get the global provider cache instance
127///
128/// Returns the global cache that persists across the entire application lifecycle.
129/// This cache is shared by all providers regardless of component boundaries.
130///
131/// ## Errors
132///
133/// Returns `GlobalProviderError::NotInitialized` if `init_global_providers()` has not been called yet.
134pub fn get_global_cache() -> Result<&'static ProviderCache, GlobalProviderError> {
135 GLOBAL_CACHE
136 .get()
137 .ok_or(GlobalProviderError::NotInitialized)
138}
139
140/// Get the global refresh registry instance
141///
142/// Returns the global refresh registry that manages reactive updates and intervals
143/// across the entire application.
144///
145/// ## Errors
146///
147/// Returns `GlobalProviderError::NotInitialized` if `init_global_providers()` has not been called yet.
148pub fn get_global_refresh_registry() -> Result<&'static RefreshRegistry, GlobalProviderError> {
149 GLOBAL_REFRESH_REGISTRY
150 .get()
151 .ok_or(GlobalProviderError::NotInitialized)
152}
153
154/// Check if global providers have been initialized
155pub fn is_initialized() -> bool {
156 GLOBAL_CACHE.get().is_some() && GLOBAL_REFRESH_REGISTRY.get().is_some()
157}
158
159/// Ensure that global providers have been initialized
160///
161/// This helper function returns an error if the global providers have not been initialized yet.
162/// It's useful for providing better error messages in hooks and other functions that depend
163/// on the global provider system.
164///
165/// ## Example
166///
167/// ```rust,no_run
168/// use dioxus_provider::global::ensure_initialized;
169/// use dioxus_provider::errors::ProviderError;
170///
171/// fn my_hook() -> Result<(), ProviderError> {
172/// ensure_initialized()?;
173/// // ... rest of hook logic
174/// Ok(())
175/// }
176/// ```
177pub fn ensure_initialized() -> Result<(), crate::errors::ProviderError> {
178 if !is_initialized() {
179 return Err(crate::errors::ProviderError::Configuration(
180 "Global providers not initialized. Call init() at application startup.".to_string(),
181 ));
182 }
183 Ok(())
184}
185
186/// Reset global providers (mainly for testing)
187///
188/// This is primarily intended for testing scenarios where you need
189/// to reset the global state between tests.
190///
191/// ## Warning
192///
193/// This function is not thread-safe and should only be used in single-threaded
194/// test environments. Do not use this in production code.
195#[cfg(test)]
196pub fn reset_global_providers() {
197 // Note: OnceLock doesn't have a public reset method, so this is mainly
198 // for documentation. In real tests, you'd typically use a different
199 // approach or restart the test process.
200 panic!("Global provider reset is not currently supported. Restart the application.");
201}
202
203// Note: Deprecated functions get_global_cache_panic() and get_global_refresh_registry_panic()
204// have been removed in version 0.1.0. Use get_global_cache() and get_global_refresh_registry()
205// with proper error handling instead.
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_global_provider_initialization() {
213 // If already initialized, just test that we can get the instances
214 if is_initialized() {
215 let _cache = get_global_cache().unwrap();
216 let _refresh = get_global_refresh_registry().unwrap();
217 return;
218 }
219
220 // Test initialization from scratch
221 assert!(!is_initialized());
222
223 init_global_providers().unwrap();
224
225 assert!(is_initialized());
226
227 // Test that we can get all instances
228 let _cache = get_global_cache().unwrap();
229 let _refresh = get_global_refresh_registry().unwrap();
230 }
231
232 #[test]
233 fn test_error_when_not_initialized() {
234 // Check if already initialized - skip test if so
235 if is_initialized() {
236 return;
237 }
238
239 // This should return an error since we haven't called init_global_providers()
240 assert!(get_global_cache().is_err());
241 assert!(get_global_refresh_registry().is_err());
242 }
243
244 #[test]
245 fn test_get_functions_with_error_handling() {
246 // Test that get functions work with proper error handling
247 init().unwrap();
248
249 let _cache = get_global_cache().unwrap();
250 let _refresh = get_global_refresh_registry().unwrap();
251 }
252}