Load world extensions from commit snapshots with last-good recovery
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
//! 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.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,
|
||||
))
|
||||
}))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user