use anyhow::Result;
use deno_core::{
include_js_files, resolve_path, serde_json::json, Extension, JsRuntime, ModuleLoader,
ModuleSpecifier, OpDecl, RuntimeOptions,
};
use deno_fetch::FetchPermissions;
use deno_web::{BlobStore, TimersPermission};
use deno_websocket::WebSocketPermissions;
use mashin_core::sdk::ResourceAction;
pub use mashin_core::{
mashin_dir::MashinDir, BackendState, Config, ExecutedResource, ExecutedResources, HeadersMap,
HttpCache, HttpClient, MashinBuilder, MashinEngine, ProgressManager, RuntimeCommand,
};
use std::{
cell::RefCell,
env::current_dir,
ops::{Deref, DerefMut},
path::Path,
rc::Rc,
str::FromStr,
};
mod builtin;
#[macro_export]
macro_rules! log {
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
log::$level!(
target: "mashin::runtime",
$patter $(, $values)*
)
};
}
#[macro_export]
macro_rules! js_log {
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
log::$level!(
target: "mashin::js",
$patter $(, $values)*
)
};
}
struct AllowAllPermissions;
impl FetchPermissions for AllowAllPermissions {
fn check_net_url(&mut self, _url: &deno_core::url::Url, _api_name: &str) -> Result<()> {
Ok(())
}
fn check_read(&mut self, _path: &Path, _api_name: &str) -> Result<()> {
Ok(())
}
}
impl TimersPermission for AllowAllPermissions {
fn allow_hrtime(&mut self) -> bool {
true
}
fn check_unstable(&self, _state: &deno_core::OpState, _api_name: &'static str) {
}
}
impl WebSocketPermissions for AllowAllPermissions {
fn check_net_url(
&mut self,
_url: &deno_core::url::Url,
_api_name: &str,
) -> std::result::Result<(), deno_core::error::AnyError> {
Ok(())
}
}
pub struct Runtime<T: Config> {
runtime: JsRuntime,
main_module: String,
engine: Rc<MashinEngine<T>>,
raw_args: Vec<String>,
}
pub struct RuntimeResult {
pub executed_resources: Rc<RefCell<ExecutedResources>>,
}
impl<T: Config> Deref for Runtime<T> {
type Target = JsRuntime;
fn deref(&self) -> &Self::Target {
&self.runtime
}
}
impl<T: Config> DerefMut for Runtime<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.runtime
}
}
impl<T: Config> Runtime<T> {
pub fn new(
main_module: &str,
mashin_engine: Rc<MashinEngine<T>>,
module_loader: Rc<dyn ModuleLoader>,
raw_args: Vec<String>,
) -> Result<Self> {
let isolated_engine = mashin_engine.clone();
let extension = Extension::builder("mashin_core")
.esm(include_js_files!(
mashin_core dir "js",
"01_errors.js",
"06_util.js",
"30_os.js",
"40_ffi.js",
"98_global_scope.js",
"99_main.js",
))
.ops(stdlib::<T>())
.state(move |state| {
state.put(isolated_engine);
state.put(AllowAllPermissions {});
})
.build();
let runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![
deno_console::deno_console::init_ops_and_esm(),
deno_webidl::deno_webidl::init_ops_and_esm(),
deno_url::deno_url::init_ops_and_esm(),
deno_web::deno_web::init_ops_and_esm::<AllowAllPermissions>(
BlobStore::default(),
None,
),
deno_fetch::deno_fetch::init_ops_and_esm::<AllowAllPermissions>(
deno_fetch::Options {
user_agent: format!("mashin_core/{}", env!("CARGO_PKG_VERSION")),
..Default::default()
},
),
extension,
],
module_loader: Some(module_loader),
..Default::default()
});
let mut runtime =
Self { engine: mashin_engine, main_module: main_module.to_string(), runtime, raw_args };
runtime.bootstrap()?;
Ok(runtime)
}
pub async fn prepare(&mut self) -> Result<u64> {
self.run_main_module().await?;
let rc_op_state = self.runtime.op_state();
let op_state = rc_op_state.borrow();
let engine = op_state.borrow::<Rc<MashinEngine<T>>>();
Ok(engine.resources_count())
}
pub async fn run(&mut self) -> Result<RuntimeResult> {
self.run_main_module().await?;
let rc_op_state = self.runtime.op_state();
let op_state = rc_op_state.borrow();
let engine = op_state.borrow::<Rc<MashinEngine<T>>>();
let executed_resources_rc = &engine.executed_resources;
let all_resources_in_state = engine.state_handler.borrow().resources()?;
let mut executed_resources = executed_resources_rc.borrow_mut();
for urn in &all_resources_in_state {
if !executed_resources.contains_key(urn) {
executed_resources.insert(
urn,
ExecutedResource {
diff: None,
provider: urn.as_provider()?,
required_change: Some(ResourceAction::Delete),
},
);
}
}
Ok(RuntimeResult { executed_resources: executed_resources_rc.clone() })
}
fn bootstrap(&mut self) -> Result<()> {
self.runtime.execute_script(
"file://__bootstrap.js",
format!(
r#"globalThis.bootstrap.mainRuntime({})"#,
json!({
"isFirstRun": self.engine.command == RuntimeCommand::Read,
"args": self.raw_args,
"target": env!("TARGET")
})
),
)?;
Ok(())
}
async fn run_main_module(&mut self) -> Result<()> {
let main_module_path = &self.main_module;
let main_module = if main_module_path.starts_with("https") {
ModuleSpecifier::from_str(main_module_path)?
} else {
resolve_path(&self.main_module, current_dir()?.as_path())?
};
if self.engine.command == RuntimeCommand::Prepare {
log::info!(" Fetching dependencies");
}
let mod_id = self.runtime.load_main_module(&main_module, None).await?;
let result_main = self.runtime.mod_evaluate(mod_id);
self.runtime.run_event_loop(false).await?;
result_main.await?
}
}
fn stdlib<T: Config>() -> Vec<OpDecl> {
let mut ops = vec![];
ops.extend(builtin::mashin_core_client::op_decls::<T>());
ops
}