use std::cell::RefCell;
use anyhow::Result;
use js::prelude::*;
use js::{CatchResultExt, Ctx, Function, Module, Promise, async_with};
use web_time::Instant;
use super::modules::surrealdb::query::QueryContext;
use super::modules::{loader, resolver};
use super::{classes, fetch, globals, modules};
use crate::ctx::FrozenContext;
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::err::Error;
use crate::val::Value;
pub unsafe fn create_query_data<'a>(
context: &'a FrozenContext,
opt: &'a Options,
doc: Option<&'a CursorDoc>,
ctx: &Ctx<'_>,
) -> Result<(), js::Error> {
unsafe {
let ctx = Ctx::from_raw(ctx.as_raw());
ctx.store_userdata(QueryContext {
context,
opt,
doc,
pending: RefCell::new(None),
})
.expect("userdata shouldn't be in use");
Ok(())
}
}
pub async fn run(
context: &FrozenContext,
opt: &Options,
doc: Option<&CursorDoc>,
src: &str,
arg: Vec<Value>,
) -> Result<Value> {
if context.is_done(None).await? {
return Ok(Value::None);
}
let opt = opt.dive(4)?;
let instant_start = Instant::now();
let time_limit = context.config.scripting_max_time_limit;
let run = js::AsyncRuntime::new()
.map_err(|e| anyhow::anyhow!("Failed to create JavaScript runtime: {}", e))?;
run.set_max_stack_size(context.config.scripting_max_stack_size).await;
run.set_memory_limit(context.config.scripting_max_memory_limit).await;
let cancellation = context.cancellation();
let handler = Box::new(move || cancellation.is_done() || instant_start.elapsed() > time_limit);
run.set_interrupt_handler(Some(handler)).await;
let ctx = js::AsyncContext::full(&run)
.await
.map_err(|e| anyhow::anyhow!("Failed to create JavaScript context: {}", e))?;
run.set_loader(resolver(), loader()).await;
let src = format!(
"export default async function() {{ try {{ {src} }} catch(e) {{ return (e instanceof Error) ? e : new Error(e); }} }}"
);
async_with!(ctx => |ctx| {
let res = async {
let global = ctx.globals();
unsafe{ create_query_data(context, &opt, doc, &ctx) }?;
fetch::register(&ctx)?;
let (module, promise) = Module::evaluate_def::<modules::surrealdb::Package, _>(ctx.clone(), "surrealdb")?;
promise.finish::<()>()?;
global.set("surrealdb",
module.get::<_, js::Value>("default")?,
)?;
let console = globals::console::console(&ctx)?;
global.set("console", console)?;
classes::init(&ctx)?;
let (module, promise) = Module::declare(ctx.clone(),"script", src)?.eval()?;
promise.into_future::<()>().await?;
let fnc = module.get::<_, Function>("default")?;
let doc = doc.map(|v| v.doc.as_ref());
let promise = fnc.call::<_, Promise>((This(doc), Rest(arg)))?.into_future::<Value>();
promise.await
}.await;
res.catch(&ctx).map_err(Error::from)
})
.await.map_err(anyhow::Error::new)
}