//! Loads committed world modules and shares the bundle's package instances and status. use deno_core::{ ModuleLoadOptions, ModuleLoadReferrer, ModuleLoadResponse, ModuleLoader, ModuleSource, ModuleSourceCode, ModuleSpecifier, ModuleType, OpState, ResolutionKind, op2, }; use deno_error::JsErrorBox; use sausage_vfs::{Layer, Open}; use std::{ cell::RefCell, collections::BTreeMap, path::{Path, PathBuf}, rc::Rc, sync::{Arc, Mutex, OnceLock}, }; pub(crate) type Status = Arc>; pub(crate) fn status(path: &Path) -> Status { static STATES: OnceLock>> = OnceLock::new(); STATES .get_or_init(Default::default) .lock() .unwrap() .entry(path.to_owned()) .or_insert_with(|| { Arc::new(Mutex::new( "{\"good\":null,\"current\":null,\"error\":null}".into(), )) }) .clone() } pub(super) struct Loader { pub root: Arc, pub modules: RefCell>, } #[op2] pub fn op_modules(state: &mut OpState, #[serde] modules: BTreeMap>) { let loader = state.borrow::>(); for (name, exports) in modules { let quoted = serde_json::to_string(&name).unwrap(); let code = exports.into_iter().enumerate().map(|(index,key)| format!("const e{index} = globalThis.__sausage.modules[{quoted}].{key}; export {{ e{index} as {key} }};\n")).collect(); loader .modules .borrow_mut() .insert(format!("sausage:{name}"), code); } } #[op2(fast)] pub fn op_extension_status(state: &mut OpState, #[string] value: String) { *state.borrow::().lock().unwrap() = value; } pub(super) fn transpile(name: &str, source: String) -> Result { if !name.ends_with(".ts") { return Ok(source); } let parsed = deno_ast::parse_module(deno_ast::ParseParams { specifier: ModuleSpecifier::parse(name).map_err(|e| JsErrorBox::generic(e.to_string()))?, text: source.into(), media_type: deno_ast::MediaType::TypeScript, capture_tokens: false, scope_analysis: false, maybe_syntax: None, }) .map_err(|e| JsErrorBox::generic(e.to_string()))?; Ok(parsed .transpile( &Default::default(), &Default::default(), &deno_ast::EmitOptions { source_map: deno_ast::SourceMapOption::None, ..Default::default() }, ) .map_err(|e| JsErrorBox::generic(e.to_string()))? .into_source() .text) } impl ModuleLoader for Loader { fn resolve( &self, specifier: &str, referrer: &str, _: ResolutionKind, ) -> Result { if specifier == "yaml" || specifier.starts_with("@earendil-works/") { return ModuleSpecifier::parse(&format!("sausage:{specifier}")) .map_err(|e| JsErrorBox::generic(e.to_string())); } deno_core::resolve_import(specifier, referrer) .map_err(|e| JsErrorBox::generic(e.to_string())) } fn load( &self, specifier: &ModuleSpecifier, _: Option<&ModuleLoadReferrer>, _: ModuleLoadOptions, ) -> ModuleLoadResponse { if let Some(source) = self.modules.borrow().get(specifier.as_str()) { return ModuleLoadResponse::Sync(Ok(ModuleSource::new( ModuleType::JavaScript, ModuleSourceCode::String(source.clone().into()), specifier, None, ))); } let root = self.root.clone(); let specifier = specifier.clone(); ModuleLoadResponse::Async(Box::pin(async move { let work = async { let path = specifier .to_file_path() .map_err(|_| JsErrorBox::generic(format!("Unknown module {specifier}")))?; let file = root .open( path.to_str().unwrap(), Open { follow: true, ..Default::default() }, ) .await .map_err(|e| JsErrorBox::generic(e.to_string()))?; let size = file .stat() .await .map_err(|e| JsErrorBox::generic(e.to_string()))? .size; let source = String::from_utf8( file.read(0, size as usize) .await .map_err(|e| JsErrorBox::generic(e.to_string()))?, ) .map_err(|e| JsErrorBox::generic(e.to_string()))?; transpile(specifier.as_str(), source) } .await?; Ok(ModuleSource::new( ModuleType::JavaScript, ModuleSourceCode::String(work.into()), &specifier, None, )) })) } }