145 lines
5.0 KiB
Rust
145 lines
5.0 KiB
Rust
//! 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<Mutex<String>>;
|
|
pub(crate) fn status(path: &Path) -> Status {
|
|
static STATES: OnceLock<Mutex<BTreeMap<PathBuf, Status>>> = 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<dyn Layer>,
|
|
pub modules: RefCell<BTreeMap<String, String>>,
|
|
}
|
|
#[op2]
|
|
pub fn op_modules(state: &mut OpState, #[serde] modules: BTreeMap<String, Vec<String>>) {
|
|
let loader = state.borrow::<Rc<Loader>>();
|
|
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::<Status>().lock().unwrap() = value;
|
|
}
|
|
pub(super) fn transpile(name: &str, source: String) -> Result<String, JsErrorBox> {
|
|
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<ModuleSpecifier, JsErrorBox> {
|
|
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,
|
|
))
|
|
}))
|
|
}
|
|
}
|