use super::ScriptingBackend;
use crate::core::{Error, Result, Value};
#[cfg(feature = "js")]
use deno_runtime::deno_core::{serde_v8, v8, JsRuntime, RuntimeOptions};
#[cfg(feature = "js")]
pub struct DenoBackend {
}
#[cfg(feature = "js")]
impl DenoBackend {
pub fn new() -> Self {
Self {}
}
}
impl Default for DenoBackend {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "js")]
fn create_secure_runtime_options() -> RuntimeOptions {
let mut options = RuntimeOptions::default();
options.extensions.retain(|ext| {
let name = &ext.name;
!name.contains("deno_kv") && !name.contains("deno_fs") && !name.contains("deno_process") && !name.contains("deno_ffi") && !name.contains("deno_napi") && !name.contains("deno_cron") && !name.contains("deno_os") && !name.contains("deno_webgpu") && !name.contains("deno_canvas") && !name.contains("deno_webstorage") && !name.contains("deno_node") });
options
}
#[cfg(feature = "js")]
impl ScriptingBackend for DenoBackend {
fn name(&self) -> &'static str {
"deno"
}
fn supported_languages(&self) -> &[&'static str] {
&["deno", "javascript", "js", "typescript", "ts"]
}
fn execute(&self, code: &str, args: &[Value], param_names: &[&str]) -> Result<Value> {
let mut runtime = JsRuntime::new(create_secure_runtime_options());
let mut args_array = Vec::new();
for arg in args {
let js_value = match arg {
Value::Null(_) => serde_json::Value::Null,
Value::Integer(i) => serde_json::json!(i),
Value::Float(f) => serde_json::json!(f),
Value::Text(s) => serde_json::json!(s.as_ref()),
Value::Boolean(b) => serde_json::json!(b),
Value::Timestamp(ts) => serde_json::json!(ts.to_rfc3339()),
Value::Json(j) => serde_json::from_str(j).unwrap_or(serde_json::Value::Null),
};
args_array.push(js_value);
}
let args_array_json = serde_json::to_string(&args_array)
.map_err(|e| Error::internal(format!("Failed to serialize arguments array: {}", e)))?;
let set_args_script = format!("globalThis.arguments = {};", args_array_json);
runtime
.execute_script("<set_args>", set_args_script)
.map_err(|e| Error::internal(format!("Failed to set arguments array: {}", e)))?;
for (i, arg) in args.iter().enumerate() {
let param_name = param_names[i];
let js_value = match arg {
Value::Null(_) => serde_json::Value::Null,
Value::Integer(i) => serde_json::json!(i),
Value::Float(f) => serde_json::json!(f),
Value::Text(s) => serde_json::json!(s.as_ref()),
Value::Boolean(b) => serde_json::json!(b),
Value::Timestamp(ts) => serde_json::json!(ts.to_rfc3339()),
Value::Json(j) => serde_json::from_str(j).unwrap_or(serde_json::Value::Null),
};
let set_script = format!("globalThis.{} = {};", param_name, js_value);
runtime
.execute_script("<set_arg>", set_script)
.map_err(|e| {
Error::internal(format!("Failed to set argument {}: {}", param_name, e))
})?;
}
let script = format!(
"
globalThis.__user_function = function() {{
{}
}};
globalThis.__user_function.apply(null, globalThis.arguments)
",
code
);
let result = runtime
.execute_script("<user_function>", script)
.map_err(|e| Error::internal(format!("Function execution failed: {}", e)))?;
let scope = &mut runtime.handle_scope();
let local = v8::Local::new(scope, result);
match serde_v8::from_v8::<serde_json::Value>(scope, local) {
Ok(json_value) => match json_value {
serde_json::Value::String(s) => Ok(Value::Text(s.into())),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(Value::Integer(i))
} else if let Some(f) = n.as_f64() {
Ok(Value::Float(f))
} else {
Ok(Value::Float(0.0)) }
}
serde_json::Value::Bool(b) => Ok(Value::Boolean(b)),
serde_json::Value::Null => Ok(Value::Null(crate::core::types::DataType::Null)),
_ => Ok(Value::Json(json_value.to_string().into())),
},
Err(_) => {
if local.is_string() {
let string = serde_v8::from_v8::<String>(scope, local).map_err(|e| {
Error::internal(format!("Failed to deserialize string result: {}", e))
})?;
Ok(Value::Text(string.into()))
} else {
Err(Error::internal("Failed to deserialize function result"))
}
}
}
}
fn validate_code(&self, _code: &str) -> Result<()> {
let _runtime = JsRuntime::new(create_secure_runtime_options());
Ok(())
}
}
#[cfg(not(feature = "deno"))]
pub struct DenoBackend;
#[cfg(not(feature = "deno"))]
impl DenoBackend {
pub fn new() -> Self {
Self
}
}
#[cfg(not(feature = "deno"))]
impl ScriptingBackend for DenoBackend {
fn name(&self) -> &'static str {
"deno"
}
fn supported_languages(&self) -> &[&'static str] {
&["deno", "javascript", "js", "typescript", "ts"]
}
fn execute(&self, _code: &str, _args: &[Value], _param_names: &[&str]) -> Result<Value> {
Err(Error::internal(
"Deno backend not enabled. Use --features deno to enable JavaScript/TypeScript support",
))
}
fn validate_code(&self, _code: &str) -> Result<()> {
Err(Error::internal(
"Deno backend not enabled. Use --features deno to enable JavaScript/TypeScript support",
))
}
}