1pub mod geospatial;
4pub mod storage;
5pub mod strategies;
6
7use crate::error::{PwaError, Result};
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use wasm_bindgen::JsCast;
11use wasm_bindgen_futures::JsFuture;
12use web_sys::{Cache, CacheStorage, Request, Response};
13
14pub use geospatial::GeospatialCache;
15pub use storage::CacheStorageManager;
16pub use strategies::{CacheStrategy, StrategyType};
17
18pub fn get_cache_storage() -> Result<CacheStorage> {
20 let window = web_sys::window()
21 .ok_or_else(|| PwaError::InvalidState("No window available".to_string()))?;
22
23 let caches = window
24 .caches()
25 .map_err(|_| PwaError::CacheOperation("CacheStorage not available".to_string()))?;
26
27 Ok(caches)
28}
29
30pub async fn open_cache(name: &str) -> Result<Cache> {
32 let caches = get_cache_storage()?;
33 let promise = caches.open(name);
34
35 let result = JsFuture::from(promise)
36 .await
37 .map_err(|e| PwaError::CacheOperation(format!("Cache open failed: {:?}", e)))?;
38
39 result
40 .dyn_into::<Cache>()
41 .map_err(|_| PwaError::CacheOperation("Invalid cache object".to_string()))
42}
43
44pub async fn delete_cache(name: &str) -> Result<bool> {
46 let caches = get_cache_storage()?;
47 let promise = caches.delete(name);
48
49 let result = JsFuture::from(promise)
50 .await
51 .map_err(|e| PwaError::CacheOperation(format!("Cache delete failed: {:?}", e)))?;
52
53 result
54 .as_bool()
55 .ok_or_else(|| PwaError::CacheOperation("Invalid delete result".to_string()))
56}
57
58pub async fn has_cache(name: &str) -> Result<bool> {
60 let caches = get_cache_storage()?;
61 let promise = caches.has(name);
62
63 let result = JsFuture::from(promise)
64 .await
65 .map_err(|e| PwaError::CacheOperation(format!("Cache has failed: {:?}", e)))?;
66
67 result
68 .as_bool()
69 .ok_or_else(|| PwaError::CacheOperation("Invalid has result".to_string()))
70}
71
72pub async fn get_cache_names() -> Result<Vec<String>> {
74 let caches = get_cache_storage()?;
75 let promise = caches.keys();
76
77 let result = JsFuture::from(promise)
78 .await
79 .map_err(|e| PwaError::CacheOperation(format!("Cache keys failed: {:?}", e)))?;
80
81 let array = js_sys::Array::from(&result);
82 let mut names = Vec::new();
83
84 for i in 0..array.length() {
85 if let Some(name) = array.get(i).as_string() {
86 names.push(name);
87 }
88 }
89
90 Ok(names)
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct CacheEntryMetadata {
96 pub cache_name: String,
98
99 pub url: String,
101
102 pub cached_at: DateTime<Utc>,
104
105 pub expires_at: Option<DateTime<Utc>>,
107
108 pub size: Option<usize>,
110
111 pub custom: Option<serde_json::Value>,
113}
114
115pub struct CacheManager {
117 cache_name: String,
118}
119
120impl CacheManager {
121 pub fn new(cache_name: impl Into<String>) -> Self {
123 Self {
124 cache_name: cache_name.into(),
125 }
126 }
127
128 pub async fn open(&self) -> Result<Cache> {
130 open_cache(&self.cache_name).await
131 }
132
133 pub async fn put(&self, request: &Request, response: &Response) -> Result<()> {
135 let cache = self.open().await?;
136 let promise = cache.put_with_request(request, response);
137
138 JsFuture::from(promise)
139 .await
140 .map_err(|e| PwaError::CacheOperation(format!("Cache put failed: {:?}", e)))?;
141
142 Ok(())
143 }
144
145 pub async fn add(&self, request: &Request) -> Result<()> {
147 let cache = self.open().await?;
148 let promise = cache.add_with_request(request);
149
150 JsFuture::from(promise)
151 .await
152 .map_err(|e| PwaError::CacheOperation(format!("Cache add failed: {:?}", e)))?;
153
154 Ok(())
155 }
156
157 pub async fn add_all(&self, requests: &[Request]) -> Result<()> {
159 let cache = self.open().await?;
160
161 let array = js_sys::Array::new();
162 for request in requests {
163 array.push(request);
164 }
165
166 let promise = cache.add_all_with_request_sequence(&array);
167
168 JsFuture::from(promise)
169 .await
170 .map_err(|e| PwaError::CacheOperation(format!("Cache add_all failed: {:?}", e)))?;
171
172 Ok(())
173 }
174
175 pub async fn match_request(&self, request: &Request) -> Result<Option<Response>> {
177 let cache = self.open().await?;
178 let promise = cache.match_with_request(request);
179
180 let result = JsFuture::from(promise)
181 .await
182 .map_err(|e| PwaError::CacheOperation(format!("Cache match failed: {:?}", e)))?;
183
184 if result.is_undefined() || result.is_null() {
185 Ok(None)
186 } else {
187 let response = result
188 .dyn_into::<Response>()
189 .map_err(|_| PwaError::CacheOperation("Invalid response object".to_string()))?;
190 Ok(Some(response))
191 }
192 }
193
194 pub async fn delete(&self, request: &Request) -> Result<bool> {
196 let cache = self.open().await?;
197 let promise = cache.delete_with_request(request);
198
199 let result = JsFuture::from(promise)
200 .await
201 .map_err(|e| PwaError::CacheOperation(format!("Cache delete failed: {:?}", e)))?;
202
203 result
204 .as_bool()
205 .ok_or_else(|| PwaError::CacheOperation("Invalid delete result".to_string()))
206 }
207
208 pub async fn keys(&self) -> Result<Vec<Request>> {
210 let cache = self.open().await?;
211 let promise = cache.keys();
212
213 let result = JsFuture::from(promise)
214 .await
215 .map_err(|e| PwaError::CacheOperation(format!("Cache keys failed: {:?}", e)))?;
216
217 let array = js_sys::Array::from(&result);
218 let mut requests = Vec::new();
219
220 for i in 0..array.length() {
221 if let Ok(request) = array.get(i).dyn_into::<Request>() {
222 requests.push(request);
223 }
224 }
225
226 Ok(requests)
227 }
228
229 pub async fn clear(&self) -> Result<()> {
231 let requests = self.keys().await?;
232
233 for request in requests {
234 self.delete(&request).await?;
235 }
236
237 Ok(())
238 }
239
240 pub async fn destroy(&self) -> Result<bool> {
242 delete_cache(&self.cache_name).await
243 }
244
245 pub fn name(&self) -> &str {
247 &self.cache_name
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn test_cache_manager_creation() {
257 let manager = CacheManager::new("test-cache");
258 assert_eq!(manager.name(), "test-cache");
259 }
260
261 #[test]
262 fn test_cache_entry_metadata() {
263 let metadata = CacheEntryMetadata {
264 cache_name: "test".to_string(),
265 url: "https://example.com".to_string(),
266 cached_at: Utc::now(),
267 expires_at: None,
268 size: Some(1024),
269 custom: None,
270 };
271
272 assert_eq!(metadata.cache_name, "test");
273 assert_eq!(metadata.size, Some(1024));
274 }
275}