deno_cache_dir/
lib.rs

1// Copyright 2018-2025 the Deno authors. MIT license.
2
3mod cache;
4mod common;
5mod deno_dir;
6#[cfg(feature = "file_fetcher")]
7pub mod file_fetcher;
8mod global;
9mod local;
10pub mod memory;
11pub mod npm;
12mod sync;
13
14/// Permissions used to save a file in the disk caches.
15pub const CACHE_PERM: u32 = 0o644;
16
17pub use cache::CacheEntry;
18pub use cache::CacheReadFileError;
19pub use cache::Checksum;
20pub use cache::ChecksumIntegrityError;
21pub use cache::GlobalOrLocalHttpCache;
22pub use cache::GlobalToLocalCopy;
23pub use cache::HttpCache;
24pub use cache::HttpCacheItemKey;
25pub use cache::HttpCacheRc;
26pub use cache::SerializedCachedUrlMetadata;
27pub use cache::url_to_filename;
28pub use common::HeadersMap;
29pub use deno_dir::DenoDirResolutionError;
30pub use deno_dir::ResolveDenoDirSys;
31pub use deno_dir::resolve_deno_dir;
32pub use global::GlobalHttpCache;
33pub use global::GlobalHttpCacheRc;
34pub use global::GlobalHttpCacheSys;
35pub use local::LocalHttpCache;
36pub use local::LocalHttpCacheRc;
37pub use local::LocalHttpCacheSys;
38pub use local::LocalLspHttpCache;
39
40#[cfg(feature = "wasm")]
41pub mod wasm {
42  use std::collections::HashMap;
43  use std::io::ErrorKind;
44  use std::path::PathBuf;
45
46  use js_sys::Object;
47  use js_sys::Reflect;
48  use js_sys::Uint8Array;
49  use sys_traits::EnvVar;
50  use sys_traits::impls::RealSys;
51  use sys_traits::impls::wasm_path_to_str;
52  use sys_traits::impls::wasm_string_to_path;
53  use url::Url;
54  use wasm_bindgen::prelude::*;
55
56  use crate::CacheReadFileError;
57  use crate::Checksum;
58  use crate::HttpCache;
59  use crate::cache::CacheEntry;
60  use crate::cache::GlobalToLocalCopy;
61  use crate::common::HeadersMap;
62  use crate::deno_dir;
63  use crate::sync::new_rc;
64
65  #[wasm_bindgen]
66  pub fn url_to_filename(url: &str) -> Result<String, JsValue> {
67    console_error_panic_hook::set_once();
68    let url = parse_url(url).map_err(as_js_error)?;
69    crate::cache::url_to_filename(&url)
70      .map(|s| s.to_string_lossy().to_string())
71      .map_err(as_js_error)
72  }
73
74  #[wasm_bindgen]
75  pub fn resolve_deno_dir(
76    maybe_custom_root: Option<String>,
77  ) -> Result<String, JsValue> {
78    console_error_panic_hook::set_once();
79    deno_dir::resolve_deno_dir(
80      &RealSys,
81      maybe_custom_root.map(wasm_string_to_path),
82    )
83    .map(|path| wasm_path_to_str(&path).into_owned())
84    .map_err(|e| JsValue::from(js_sys::Error::new(&e.to_string())))
85  }
86
87  #[wasm_bindgen]
88  pub struct GlobalHttpCache {
89    cache: crate::GlobalHttpCache<RealSys>,
90  }
91
92  #[wasm_bindgen]
93  impl GlobalHttpCache {
94    pub fn new(path: &str) -> Self {
95      Self {
96        cache: crate::GlobalHttpCache::new(RealSys, PathBuf::from(path)),
97      }
98    }
99
100    #[wasm_bindgen(js_name = getHeaders)]
101    pub fn get_headers(&self, url: &str) -> Result<JsValue, JsValue> {
102      get_headers(&self.cache, url)
103    }
104
105    pub fn get(
106      &self,
107      url: &str,
108      maybe_checksum: Option<String>,
109    ) -> Result<JsValue, JsValue> {
110      get_cache_entry(&self.cache, url, maybe_checksum.as_deref())
111    }
112
113    pub fn set(
114      &self,
115      url: &str,
116      headers: JsValue,
117      text: &[u8],
118    ) -> Result<(), JsValue> {
119      set(&self.cache, url, headers, text)
120    }
121  }
122
123  #[wasm_bindgen]
124  pub struct LocalHttpCache {
125    cache: crate::LocalHttpCache<RealSys>,
126  }
127
128  #[wasm_bindgen]
129  impl LocalHttpCache {
130    pub fn new(
131      local_path: String,
132      global_path: String,
133      allow_global_to_local_copy: bool,
134    ) -> Self {
135      console_error_panic_hook::set_once();
136      let global =
137        crate::GlobalHttpCache::new(RealSys, wasm_string_to_path(global_path));
138      let jsr_url = RealSys
139        .env_var("JSR_URL")
140        .ok()
141        .and_then(|url| {
142          // ensure there is a trailing slash for the directory
143          let registry_url = format!("{}/", url.trim_end_matches('/'));
144          Url::parse(&registry_url).ok()
145        })
146        .unwrap_or_else(|| Url::parse("https://jsr.io/").unwrap());
147      let local = crate::LocalHttpCache::new(
148        wasm_string_to_path(local_path),
149        new_rc(global),
150        if allow_global_to_local_copy {
151          GlobalToLocalCopy::Allow
152        } else {
153          GlobalToLocalCopy::Disallow
154        },
155        jsr_url,
156      );
157      Self { cache: local }
158    }
159
160    #[wasm_bindgen(js_name = getHeaders)]
161    pub fn get_headers(&self, url: &str) -> Result<JsValue, JsValue> {
162      get_headers(&self.cache, url)
163    }
164
165    pub fn get(
166      &self,
167      url: &str,
168      maybe_checksum: Option<String>,
169    ) -> Result<JsValue, JsValue> {
170      get_cache_entry(&self.cache, url, maybe_checksum.as_deref())
171    }
172
173    pub fn set(
174      &self,
175      url: &str,
176      headers: JsValue,
177      text: &[u8],
178    ) -> Result<(), JsValue> {
179      set(&self.cache, url, headers, text)
180    }
181  }
182
183  fn get_headers<Cache: HttpCache>(
184    cache: &Cache,
185    url: &str,
186  ) -> Result<JsValue, JsValue> {
187    fn inner<Cache: HttpCache>(
188      cache: &Cache,
189      url: &str,
190    ) -> std::io::Result<Option<HeadersMap>> {
191      let url = parse_url(url)?;
192      let key = cache.cache_item_key(&url)?;
193      cache.read_headers(&key)
194    }
195
196    inner(cache, url)
197      .map(|headers| match headers {
198        Some(headers) => serde_wasm_bindgen::to_value(&headers).unwrap(),
199        None => JsValue::undefined(),
200      })
201      .map_err(as_js_error)
202  }
203
204  fn get_cache_entry<Cache: HttpCache>(
205    cache: &Cache,
206    url: &str,
207    maybe_checksum: Option<&str>,
208  ) -> Result<JsValue, JsValue> {
209    fn inner<Cache: HttpCache>(
210      cache: &Cache,
211      url: &str,
212      maybe_checksum: Option<Checksum>,
213    ) -> std::io::Result<Option<CacheEntry>> {
214      let url = parse_url(url)?;
215      let key = cache.cache_item_key(&url)?;
216      match cache.get(&key, maybe_checksum) {
217        Ok(Some(entry)) => Ok(Some(entry)),
218        Ok(None) => Ok(None),
219        Err(err) => match err {
220          CacheReadFileError::Io(err) => Err(err),
221          CacheReadFileError::ChecksumIntegrity(err) => {
222            Err(std::io::Error::new(ErrorKind::InvalidData, err.to_string()))
223          }
224        },
225      }
226    }
227
228    inner(cache, url, maybe_checksum.map(Checksum::new))
229      .map(|text| match text {
230        Some(entry) => {
231          let content = {
232            let array = Uint8Array::new_with_length(entry.content.len() as u32);
233            array.copy_from(&entry.content);
234            JsValue::from(array)
235          };
236          let headers: JsValue = {
237            // make it an object instead of a Map
238            let headers_object = Object::new();
239            for (key, value) in &entry.metadata.headers {
240              Reflect::set(
241                &headers_object,
242                &JsValue::from_str(key),
243                &JsValue::from_str(value),
244              )
245              .unwrap();
246            }
247            JsValue::from(headers_object)
248          };
249          let obj = Object::new();
250          Reflect::set(&obj, &JsValue::from_str("content"), &content).unwrap();
251          Reflect::set(&obj, &JsValue::from_str("headers"), &headers).unwrap();
252          JsValue::from(obj)
253        }
254        None => JsValue::undefined(),
255      })
256      .map_err(as_js_error)
257  }
258
259  fn set<Cache: HttpCache>(
260    cache: &Cache,
261    url: &str,
262    headers: JsValue,
263    content: &[u8],
264  ) -> Result<(), JsValue> {
265    fn inner<Cache: HttpCache>(
266      cache: &Cache,
267      url: &str,
268      headers: JsValue,
269      content: &[u8],
270    ) -> std::io::Result<()> {
271      let url = parse_url(url)?;
272      let headers: HashMap<String, String> =
273        serde_wasm_bindgen::from_value(headers).map_err(|err| {
274          std::io::Error::new(ErrorKind::InvalidData, err.to_string())
275        })?;
276      cache.set(&url, headers, content)
277    }
278
279    inner(cache, url, headers, content).map_err(as_js_error)
280  }
281
282  fn parse_url(url: &str) -> std::io::Result<Url> {
283    Url::parse(url)
284      .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string()))
285  }
286
287  fn as_js_error(e: std::io::Error) -> JsValue {
288    JsValue::from(js_sys::Error::new(&e.to_string()))
289  }
290}