node_resolver/
cache.rs

1// Copyright 2018-2026 the Deno authors. MIT license.
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::path::Path;
6use std::path::PathBuf;
7
8use sys_traits::BaseFsCanonicalize;
9use sys_traits::BaseFsOpen;
10use sys_traits::BaseFsRead;
11use sys_traits::BaseFsReadDir;
12use sys_traits::FileType;
13use sys_traits::FsCanonicalize;
14use sys_traits::FsMetadata;
15use sys_traits::FsMetadataValue;
16use sys_traits::FsOpen;
17use sys_traits::FsRead;
18use sys_traits::FsReadDir;
19
20pub trait NodeResolutionCache:
21  std::fmt::Debug + deno_maybe_sync::MaybeSend + deno_maybe_sync::MaybeSync
22{
23  fn get_canonicalized(
24    &self,
25    path: &Path,
26  ) -> Option<Result<PathBuf, std::io::Error>>;
27  fn set_canonicalized(&self, from: PathBuf, to: &std::io::Result<PathBuf>);
28  fn get_file_type(&self, path: &Path) -> Option<Option<FileType>>;
29  fn set_file_type(&self, path: PathBuf, value: Option<FileType>);
30}
31
32thread_local! {
33  static CANONICALIZED_CACHE: RefCell<HashMap<PathBuf, Option<PathBuf>>> = RefCell::new(HashMap::new());
34  static FILE_TYPE_CACHE: RefCell<HashMap<PathBuf, Option<FileType>>> = RefCell::new(HashMap::new());
35}
36
37// We use thread local caches here because it's just more convenient
38// and easily allows workers to have separate caches.
39#[derive(Debug)]
40pub struct NodeResolutionThreadLocalCache;
41
42impl NodeResolutionThreadLocalCache {
43  pub fn clear() {
44    CANONICALIZED_CACHE.with_borrow_mut(|cache| cache.clear());
45    FILE_TYPE_CACHE.with_borrow_mut(|cache| cache.clear());
46  }
47}
48
49impl NodeResolutionCache for NodeResolutionThreadLocalCache {
50  fn get_canonicalized(
51    &self,
52    path: &Path,
53  ) -> Option<Result<PathBuf, std::io::Error>> {
54    CANONICALIZED_CACHE.with_borrow(|cache| {
55      let item = cache.get(path)?;
56      Some(match item {
57        Some(value) => Ok(value.clone()),
58        None => Err(std::io::Error::new(
59          std::io::ErrorKind::NotFound,
60          "Not found.",
61        )),
62      })
63    })
64  }
65
66  fn set_canonicalized(&self, from: PathBuf, to: &std::io::Result<PathBuf>) {
67    CANONICALIZED_CACHE.with_borrow_mut(|cache| match to {
68      Ok(to) => {
69        cache.insert(from, Some(to.clone()));
70      }
71      Err(err) => {
72        if err.kind() == std::io::ErrorKind::NotFound {
73          cache.insert(from, None);
74        }
75      }
76    });
77  }
78
79  fn get_file_type(&self, path: &Path) -> Option<Option<FileType>> {
80    FILE_TYPE_CACHE.with_borrow(|cache| cache.get(path).cloned())
81  }
82
83  fn set_file_type(&self, path: PathBuf, value: Option<FileType>) {
84    FILE_TYPE_CACHE.with_borrow_mut(|cache| {
85      cache.insert(path, value);
86    })
87  }
88}
89
90#[allow(clippy::disallowed_types)]
91pub type NodeResolutionCacheRc =
92  deno_maybe_sync::MaybeArc<dyn NodeResolutionCache>;
93
94#[derive(Debug, Default)]
95pub struct NodeResolutionSys<TSys> {
96  sys: TSys,
97  cache: Option<NodeResolutionCacheRc>,
98}
99
100impl<TSys: Clone> Clone for NodeResolutionSys<TSys> {
101  fn clone(&self) -> Self {
102    Self {
103      sys: self.sys.clone(),
104      cache: self.cache.clone(),
105    }
106  }
107}
108
109impl<TSys: FsMetadata> NodeResolutionSys<TSys> {
110  pub fn new(sys: TSys, store: Option<NodeResolutionCacheRc>) -> Self {
111    Self { sys, cache: store }
112  }
113
114  pub fn is_file(&self, path: &Path) -> bool {
115    match self.get_file_type(path) {
116      Ok(file_type) => file_type.is_file(),
117      Err(_) => false,
118    }
119  }
120
121  pub fn is_dir(&self, path: &Path) -> bool {
122    match self.get_file_type(path) {
123      Ok(file_type) => file_type.is_dir(),
124      Err(_) => false,
125    }
126  }
127
128  pub fn exists_(&self, path: &Path) -> bool {
129    self.get_file_type(path).is_ok()
130  }
131
132  pub fn get_file_type(&self, path: &Path) -> std::io::Result<FileType> {
133    {
134      if let Some(maybe_value) =
135        self.cache.as_ref().and_then(|c| c.get_file_type(path))
136      {
137        return match maybe_value {
138          Some(value) => Ok(value),
139          None => Err(std::io::Error::new(
140            std::io::ErrorKind::NotFound,
141            "Not found.",
142          )),
143        };
144      }
145    }
146    match self.sys.fs_metadata(path) {
147      Ok(metadata) => {
148        if let Some(cache) = &self.cache {
149          cache.set_file_type(path.to_path_buf(), Some(metadata.file_type()));
150        }
151        Ok(metadata.file_type())
152      }
153      Err(err) => {
154        if let Some(cache) = &self.cache {
155          cache.set_file_type(path.to_path_buf(), None);
156        }
157        Err(err)
158      }
159    }
160  }
161}
162
163impl<TSys: FsCanonicalize> BaseFsCanonicalize for NodeResolutionSys<TSys> {
164  fn base_fs_canonicalize(&self, from: &Path) -> std::io::Result<PathBuf> {
165    if let Some(cache) = &self.cache
166      && let Some(result) = cache.get_canonicalized(from)
167    {
168      return result;
169    }
170    let result = self.sys.base_fs_canonicalize(from);
171    if let Some(cache) = &self.cache {
172      cache.set_canonicalized(from.to_path_buf(), &result);
173    }
174    result
175  }
176}
177
178impl<TSys: FsReadDir> BaseFsReadDir for NodeResolutionSys<TSys> {
179  type ReadDirEntry = TSys::ReadDirEntry;
180
181  #[inline(always)]
182  fn base_fs_read_dir(
183    &self,
184    path: &Path,
185  ) -> std::io::Result<
186    Box<dyn Iterator<Item = std::io::Result<Self::ReadDirEntry>>>,
187  > {
188    self.sys.base_fs_read_dir(path)
189  }
190}
191
192impl<TSys: FsRead> BaseFsRead for NodeResolutionSys<TSys> {
193  #[inline(always)]
194  fn base_fs_read(
195    &self,
196    path: &Path,
197  ) -> std::io::Result<std::borrow::Cow<'static, [u8]>> {
198    self.sys.base_fs_read(path)
199  }
200}
201
202impl<TSys: FsOpen> BaseFsOpen for NodeResolutionSys<TSys> {
203  type File = TSys::File;
204
205  fn base_fs_open(
206    &self,
207    path: &Path,
208    flags: &sys_traits::OpenOptions,
209  ) -> std::io::Result<Self::File> {
210    self.sys.base_fs_open(path, flags)
211  }
212}