1use crate::api::error::ApiError;
2use crate::vm::value::Value;
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
7pub struct ModuleInfo {
8 pub id: String,
9 pub path: Option<PathBuf>,
10 pub source: String,
11 pub exports: HashMap<String, Value>,
12 pub dependencies: Vec<String>,
13 pub is_loaded: bool,
14}
15
16impl ModuleInfo {
17 pub fn new(id: String, source: String) -> Self {
18 Self {
19 id,
20 path: None,
21 source,
22 exports: HashMap::new(),
23 dependencies: Vec::new(),
24 is_loaded: false,
25 }
26 }
27
28 pub fn with_path(mut self, path: PathBuf) -> Self {
29 self.path = Some(path);
30 self
31 }
32
33 pub fn add_export(&mut self, name: String, value: Value) {
34 self.exports.insert(name, value);
35 }
36
37 pub fn get_export(&self, name: &str) -> Option<&Value> {
38 self.exports.get(name)
39 }
40
41 pub fn add_dependency(&mut self, module_id: String) {
42 if !self.dependencies.contains(&module_id) {
43 self.dependencies.push(module_id);
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
49pub struct ModuleResolution {
50 pub module_id: String,
51 pub absolute_path: Option<PathBuf>,
52 pub is_external: bool,
53}
54
55pub trait ModuleProvider: Send + Sync {
56 fn resolve_module(
57 &self,
58 specifier: &str,
59 from: Option<&str>,
60 ) -> Result<ModuleResolution, ApiError>;
61 fn load_module(&self, resolution: &ModuleResolution) -> Result<String, ApiError>;
62 fn get_module_info(&self, module_id: &str) -> Option<&ModuleInfo>;
63}
64
65pub struct FileSystemModuleProvider {
66 base_path: PathBuf,
67 modules: HashMap<String, ModuleInfo>,
68}
69
70impl FileSystemModuleProvider {
71 pub fn new(base_path: PathBuf) -> Self {
72 Self {
73 base_path,
74 modules: HashMap::new(),
75 }
76 }
77
78 pub fn add_module(&mut self, module: ModuleInfo) {
79 self.modules.insert(module.id.clone(), module);
80 }
81}
82
83impl ModuleProvider for FileSystemModuleProvider {
84 fn resolve_module(
85 &self,
86 specifier: &str,
87 from: Option<&str>,
88 ) -> Result<ModuleResolution, ApiError> {
89 let module_id = if specifier.starts_with('.') || specifier.starts_with('/') {
91 if let Some(from_path) = from {
93 format!("{}/{}", from_path, specifier)
94 } else {
95 specifier.to_string()
96 }
97 } else {
98 specifier.to_string()
100 };
101
102 Ok(ModuleResolution {
103 module_id,
104 absolute_path: None,
105 is_external: false,
106 })
107 }
108
109 fn load_module(&self, resolution: &ModuleResolution) -> Result<String, ApiError> {
110 if let Some(module) = self.modules.get(&resolution.module_id) {
111 Ok(module.source.clone())
112 } else {
113 Err(ApiError::ResourceError {
114 resource: resolution.module_id.clone(),
115 message: "Module not found".to_string(),
116 position: None,
117 })
118 }
119 }
120
121 fn get_module_info(&self, module_id: &str) -> Option<&ModuleInfo> {
122 self.modules.get(module_id)
123 }
124}
125
126pub struct ModuleLoader {
127 provider: Box<dyn ModuleProvider>,
128 loaded_modules: HashMap<String, ModuleInfo>,
129 module_cache: HashMap<String, Value>,
130}
131
132impl ModuleLoader {
133 pub fn new(provider: Box<dyn ModuleProvider>) -> Self {
134 Self {
135 provider,
136 loaded_modules: HashMap::new(),
137 module_cache: HashMap::new(),
138 }
139 }
140
141 pub fn load_module(&mut self, specifier: &str, from: Option<&str>) -> Result<Value, ApiError> {
142 let resolution = self.provider.resolve_module(specifier, from)?;
143
144 if let Some(cached) = self.module_cache.get(&resolution.module_id) {
145 return Ok(cached.clone());
146 }
147
148 let source = self.provider.load_module(&resolution)?;
149 let module_info = ModuleInfo::new(resolution.module_id.clone(), source);
150
151 let module_value = self.execute_module(&module_info)?;
153
154 let module_id = resolution.module_id.clone();
156 self.module_cache.insert(module_id, module_value.clone());
157 self.loaded_modules
158 .insert(resolution.module_id, module_info);
159
160 Ok(module_value)
161 }
162
163 fn execute_module(&self, _module_info: &ModuleInfo) -> Result<Value, ApiError> {
164 Ok(Value::Object(crate::vm::handle::create_object_handle(0)))
167 }
168
169 pub fn get_loaded_modules(&self) -> &HashMap<String, ModuleInfo> {
170 &self.loaded_modules
171 }
172
173 pub fn clear_cache(&mut self) {
174 self.module_cache.clear();
175 }
176}
177
178pub struct ModuleRegistry {
179 loaders: HashMap<String, Box<dyn ModuleProvider>>,
180 default_loader: Option<Box<dyn ModuleProvider>>,
181}
182
183impl ModuleRegistry {
184 pub fn new() -> Self {
185 Self {
186 loaders: HashMap::new(),
187 default_loader: None,
188 }
189 }
190
191 pub fn register_provider(&mut self, scheme: String, provider: Box<dyn ModuleProvider>) {
192 self.loaders.insert(scheme, provider);
193 }
194
195 pub fn set_default_provider(&mut self, provider: Box<dyn ModuleProvider>) {
196 self.default_loader = Some(provider);
197 }
198
199 pub fn get_provider(&self, scheme: &str) -> Option<&dyn ModuleProvider> {
200 self.loaders.get(scheme).map(|p| p.as_ref())
201 }
202
203 pub fn get_default_provider(&self) -> Option<&dyn ModuleProvider> {
204 self.default_loader.as_ref().map(|p| p.as_ref())
205 }
206}
207
208impl Default for ModuleRegistry {
209 fn default() -> Self {
210 Self::new()
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use std::path::PathBuf;
218
219 #[test]
220 fn test_module_info() {
221 let mut module = ModuleInfo::new("test".to_string(), "console.log('hello')".to_string());
222 module.add_export("default".to_string(), Value::String("test".to_string()));
223
224 assert_eq!(module.exports.len(), 1);
225 assert!(module.get_export("default").is_some());
226 }
227
228 #[test]
229 fn test_file_system_provider() {
230 let mut provider = FileSystemModuleProvider::new(PathBuf::from("/tmp"));
231 let module = ModuleInfo::new("test".to_string(), "export default 'hello'".to_string());
232 provider.add_module(module);
233
234 let resolution = provider.resolve_module("test", None).unwrap();
235 assert_eq!(resolution.module_id, "test");
236 }
237
238 #[test]
239 fn test_module_registry() {
240 let mut registry = ModuleRegistry::new();
241 let provider = Box::new(FileSystemModuleProvider::new(PathBuf::from("/tmp")));
242
243 registry.register_provider("file".to_string(), provider);
244 assert!(registry.get_provider("file").is_some());
245 }
246}