node_resolver/
cache.rs

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