jetcrab\api/
modules.rs

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        // Simple resolution logic - can be enhanced
90        let module_id = if specifier.starts_with('.') || specifier.starts_with('/') {
91            // Relative path
92            if let Some(from_path) = from {
93                format!("{}/{}", from_path, specifier)
94            } else {
95                specifier.to_string()
96            }
97        } else {
98            // Absolute module name
99            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        // Parse and execute the module
152        let module_value = self.execute_module(&module_info)?;
153
154        // Cache the result
155        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        // This would integrate with the engine to execute the module
165        // For now, return a placeholder
166        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}