#![allow(static_mut_refs)]
use anyhow::{Result, anyhow, bail};
pub use config::Config;
use javy::quickjs::{self, Ctx, Error as JSError, Function, Module, Value};
use javy::{Runtime, from_js_error};
use std::cell::OnceCell;
use std::str;
pub use javy;
mod config;
mod javy_plugin;
mod namespace;
#[cfg(all(target_family = "wasm", target_os = "wasi", target_env = "p1"))]
mod wasi_p1;
const FUNCTION_MODULE_NAME: &str = "function.mjs";
thread_local! {
static COMPILE_SRC_RET_AREA: OnceCell<[u32; 2]> = const { OnceCell::new() }
}
static mut RUNTIME: OnceCell<Runtime> = OnceCell::new();
static mut EVENT_LOOP_ENABLED: bool = false;
static EVENT_LOOP_ERR: &str = r#"
Pending jobs in the event queue.
Scheduling events is not supported when the
event-loop runtime config is not enabled.
"#;
pub fn initialize_runtime<F, G>(config: F, modify_runtime: G) -> Result<()>
where
F: FnOnce() -> Config,
G: FnOnce(Runtime) -> Runtime,
{
let config = config();
let runtime = Runtime::new(config.runtime_config)?;
let runtime = modify_runtime(runtime);
unsafe {
RUNTIME.take(); RUNTIME
.set(runtime)
.map_err(|_| anyhow!("Could not pre-initialize javy::Runtime"))
.unwrap();
EVENT_LOOP_ENABLED = config.event_loop;
};
Ok(())
}
pub fn compile_src(js_src: &[u8]) -> Result<Vec<u8>> {
let runtime = unsafe { RUNTIME.get() }.ok_or_else(|| {
anyhow!("Javy runtime not initialized. Ensure `javy init-plugin` has been invoked.")
})?;
runtime.compile_to_bytecode(FUNCTION_MODULE_NAME, &String::from_utf8_lossy(js_src))
}
pub fn invoke(bytecode: &[u8], fn_name: Option<&str>) -> Result<()> {
let runtime = unsafe { RUNTIME.get() }.ok_or_else(|| {
anyhow!("Javy runtime not initialized. Ensure `javy init-plugin` has been invoked.")
})?;
runtime
.context()
.with(|this| {
let module = unsafe { Module::load(this.clone(), bytecode)? };
let (module, promise) = module.eval()?;
handle_maybe_promise(this.clone(), promise.into())?;
if let Some(fn_name) = fn_name {
let fun: Function = module.get(fn_name)?;
let value = fun.call(())?;
handle_maybe_promise(this.clone(), value)?
}
Ok(())
})
.map_err(|e| runtime.context().with(|cx| from_js_error(cx.clone(), e)))
.and_then(|_: ()| ensure_pending_jobs(runtime))
}
fn handle_maybe_promise(this: Ctx, value: Value) -> quickjs::Result<()> {
match value.as_promise() {
Some(promise) => {
if unsafe { EVENT_LOOP_ENABLED } {
let resolved = promise.finish::<Value>();
if let Err(JSError::WouldBlock) = resolved {
Ok(())
} else {
resolved.map(|_| ())
}
} else {
match promise.result() {
None => Err(javy::to_js_error(this, anyhow!(EVENT_LOOP_ERR))),
Some(r) => r,
}
}
}
None => Ok(()),
}
}
fn ensure_pending_jobs(rt: &Runtime) -> Result<()> {
if unsafe { EVENT_LOOP_ENABLED } {
rt.resolve_pending_jobs()
} else if rt.has_pending_jobs() {
bail!(EVENT_LOOP_ERR);
} else {
Ok(())
}
}