Switch from Boa to QuickJS for JavaScript runtime

This commit is contained in:
Tobie Morgan Hitchcock 2022-07-06 10:17:16 +01:00
parent 6ef120f7ce
commit 338bf3e142
11 changed files with 534 additions and 483 deletions

331
Cargo.lock generated
View file

@ -8,17 +8,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.7",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
@ -286,70 +275,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "boa_engine"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d42e34ff7c02423cb5248bc67021f13f2bb7aec22de7ab351a6a4e31ec6d9"
dependencies = [
"bitflags",
"boa_gc",
"boa_interner",
"boa_profiler",
"boa_unicode",
"chrono",
"dyn-clone",
"fast-float",
"gc",
"indexmap",
"num-bigint",
"num-integer",
"num-traits",
"once_cell",
"rand 0.8.5",
"regress",
"rustc-hash",
"ryu-js",
"serde",
"serde_json",
"tap",
"unicode-normalization",
]
[[package]]
name = "boa_gc"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a1167c469e96484d0f1d73d2a1ae5e44983aa49a32630d76f5a555116b18204"
dependencies = [
"gc",
]
[[package]]
name = "boa_interner"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8a4c15b4b0e86daa063c7123da11f8e41ecdc91fc086d1fd8898670b2f40e"
dependencies = [
"gc",
"string-interner",
]
[[package]]
name = "boa_profiler"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28bd5912d57793f8fd312ce63eaedd34fa6ff5fe759be508d3d2455905b0880a"
[[package]]
name = "boa_unicode"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90b9a73ca3f2bdd98e3e132e3c93684d9ab682ed5a3cad8be6b87c38d7f349e6"
dependencies = [
"unicode-general-category",
]
[[package]]
name = "boringssl-src"
version = "0.2.0"
@ -601,6 +526,41 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "derive-new"
version = "0.5.9"
@ -648,12 +608,6 @@ dependencies = [
"urlencoding",
]
[[package]]
name = "dyn-clone"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28"
[[package]]
name = "echodb"
version = "0.3.0"
@ -708,12 +662,6 @@ dependencies = [
"rand 0.7.3",
]
[[package]]
name = "fast-float"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
[[package]]
name = "fastrand"
version = "1.7.0"
@ -764,6 +712,19 @@ dependencies = [
"num-traits",
]
[[package]]
name = "flume"
version = "0.10.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ceeb589a3157cac0ab8cc585feb749bd2cea5cb55a6ee802ad72d9fd38303da"
dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"pin-project",
"spin 0.9.3",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -922,27 +883,6 @@ dependencies = [
"thread_local",
]
[[package]]
name = "gc"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3edaac0f5832202ebc99520cb77c932248010c4645d20be1dc62d6579f5b3752"
dependencies = [
"gc_derive",
]
[[package]]
name = "gc_derive"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60df8444f094ff7885631d80e78eb7d88c3c2361a98daaabb06256e4500db941"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "generic-array"
version = "0.14.5"
@ -1008,8 +948,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@ -1102,9 +1044,6 @@ name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "headers"
@ -1248,6 +1187,12 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.2.3"
@ -1538,6 +1483,15 @@ dependencies = [
"rand 0.8.5",
]
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
"getrandom 0.2.7",
]
[[package]]
name = "native-tls"
version = "0.2.10"
@ -1600,7 +1554,6 @@ dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
@ -1860,6 +1813,40 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-crate"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
dependencies = [
"thiserror",
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.39"
@ -2147,13 +2134,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
[[package]]
name = "regress"
version = "0.4.1"
name = "relative-path"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a92ff21fe8026ce3f2627faaf43606f0b67b014dbc9ccf027181a804f75d92e"
dependencies = [
"memchr",
]
checksum = "b4e112eddc95bbf25365df3b5414354ad2fe7ee465eddb9965a515faf8c3b6d9"
[[package]]
name = "remove_dir_all"
@ -2280,6 +2264,58 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5864e7ef1a6b7bcf1d6ca3f655e65e724ed3b52546a0d0a663c991522f552ea"
[[package]]
name = "rquickjs"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989910af47b3f21c95205a1e1974bfb0f460e5492f469631297cc44c3340967e"
dependencies = [
"rquickjs-core",
"rquickjs-macro",
]
[[package]]
name = "rquickjs-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78ca2361848dddf910c27296d98bd62be9467d1d1d703eeead8c732adbd4e32"
dependencies = [
"async-task",
"chrono",
"flume",
"futures-lite",
"pin-project-lite",
"relative-path",
"rquickjs-sys",
]
[[package]]
name = "rquickjs-macro"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61e2079f08cea21359618d33146cee860fc362857b8b16357c8c444ca3ae6104"
dependencies = [
"darling",
"fnv",
"ident_case",
"indexmap",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"rquickjs-core",
"syn",
]
[[package]]
name = "rquickjs-sys"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "263a9a14fc68f2f3403c67e94e5def69af60afbcf7914d2a7fb8f9e17027fa25"
dependencies = [
"cc",
]
[[package]]
name = "rstar"
version = "0.9.3"
@ -2334,12 +2370,6 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "ryu-js"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f"
[[package]]
name = "safemem"
version = "0.3.3"
@ -2623,17 +2653,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "string-interner"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e2531d8525b29b514d25e275a43581320d587b86db302b9a7e464bac579648"
dependencies = [
"cfg-if",
"hashbrown",
"serde",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -2683,14 +2702,12 @@ dependencies = [
"async-executor",
"async-recursion",
"bigdecimal",
"boa_engine",
"chrono",
"deunicode",
"dmp",
"echodb",
"futures 0.3.21",
"fuzzy-matcher",
"gc",
"geo",
"indxdb",
"lexical-sort",
@ -2703,6 +2720,7 @@ dependencies = [
"rand 0.8.5",
"regex",
"rmp-serde",
"rquickjs",
"scrypt",
"serde",
"sha-1 0.10.0",
@ -2739,24 +2757,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.3.0"
@ -3047,6 +3047,15 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.1"
@ -3140,12 +3149,6 @@ version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-general-category"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
[[package]]
name = "unicode-ident"
version = "1.0.1"
@ -3167,12 +3170,6 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-xid"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]]
name = "untrusted"
version = "0.7.1"

View file

@ -12,13 +12,12 @@ kv-tikv = ["tikv"]
kv-echodb = ["echodb"]
kv-indxdb = ["indxdb"]
kv-yokudb = []
scripting = ["boa", "gc"]
scripting = ["js", "executor"]
[dependencies]
argon2 = "0.4.1"
async-recursion = "1.0.0"
bigdecimal = { version = "0.3.0", features = ["serde", "string-only"] }
boa = { version = "0.15.0", package = "boa_engine", optional = true }
channel = { version = "1.6.1", package = "async-channel" }
chrono = { version = "0.4.19", features = ["serde"] }
derive = { version = "0.3.0", package = "surrealdb-derive" }
@ -28,9 +27,9 @@ echodb = { version = "0.3.0", optional = true }
executor = { version = "1.4.1", package = "async-executor", optional = true }
futures = "0.3.21"
fuzzy-matcher = "0.3.7"
gc = { version = "0.4.1", optional = true }
geo = { version = "0.22.0", features = ["use-serde"] }
indxdb = { version = "0.2.0", optional = true }
js = { version = "0.1.6", package = "rquickjs", features = ["chrono", "classes", "futures", "macro", "properties", "parallel"], optional = true }
lexical-sort = "0.3.1"
log = "0.4.17"
md-5 = "0.10.1"

View file

@ -1,313 +0,0 @@
#![cfg(feature = "scripting")]
use crate::ctx::Context;
use crate::err::Error;
use crate::sql::array::Array;
use crate::sql::datetime::Datetime;
use crate::sql::duration::Duration;
use crate::sql::number::Number;
use crate::sql::object::Object;
use crate::sql::thing::Thing;
use crate::sql::value::Value;
use bigdecimal::ToPrimitive;
use boa::builtins::date::Date;
use boa::class::Class;
use boa::class::ClassBuilder;
use boa::object::JsArray;
use boa::object::JsObject;
use boa::object::ObjectData;
use boa::object::ObjectKind;
use boa::property::Attribute;
use boa::Context as Boa;
use boa::JsResult;
use boa::JsString;
use boa::JsValue;
use chrono::Datelike;
use chrono::Timelike;
use gc::Finalize;
use gc::Trace;
#[derive(Debug, Trace, Finalize)]
pub struct JsDuration {
value: String,
}
impl Class for JsDuration {
const NAME: &'static str = "Duration";
const LENGTH: usize = 1;
fn constructor(_this: &JsValue, args: &[JsValue], ctx: &mut Boa) -> JsResult<Self> {
Ok(JsDuration {
value: args.get(0).cloned().unwrap_or_default().to_string(ctx)?.to_string(),
})
}
fn init(class: &mut ClassBuilder) -> JsResult<()> {
class.method("value", 0, |this, _, _| {
if let Some(v) = this.as_object() {
if let Some(v) = v.downcast_ref::<JsDuration>() {
return Ok(v.value.clone().into());
}
}
Ok(JsValue::Undefined)
});
class.method("toString", 0, |this, _, _| {
if let Some(v) = this.as_object() {
if let Some(v) = v.downcast_ref::<JsDuration>() {
return Ok(v.value.clone().into());
}
}
Ok(JsValue::Undefined)
});
Ok(())
}
}
#[derive(Debug, Trace, Finalize)]
pub struct JsRecord {
tb: String,
id: String,
}
impl Class for JsRecord {
const NAME: &'static str = "Record";
const LENGTH: usize = 2;
fn constructor(_this: &JsValue, args: &[JsValue], ctx: &mut Boa) -> JsResult<Self> {
Ok(JsRecord {
tb: args.get(0).cloned().unwrap_or_default().to_string(ctx)?.to_string(),
id: args.get(1).cloned().unwrap_or_default().to_string(ctx)?.to_string(),
})
}
fn init(class: &mut ClassBuilder) -> JsResult<()> {
class.method("tb", 0, |this, _, _| {
if let Some(v) = this.as_object() {
if let Some(v) = v.downcast_ref::<JsRecord>() {
return Ok(v.tb.clone().into());
}
}
Ok(JsValue::Undefined)
});
class.method("id", 0, |this, _, _| {
if let Some(v) = this.as_object() {
if let Some(v) = v.downcast_ref::<JsRecord>() {
return Ok(v.id.clone().into());
}
}
Ok(JsValue::Undefined)
});
class.method("toString", 0, |this, _, _| {
if let Some(v) = this.as_object() {
if let Some(v) = v.downcast_ref::<JsRecord>() {
return Ok(format!("{}:{}", v.tb, v.id).into());
}
}
Ok(JsValue::Undefined)
});
Ok(())
}
}
pub async fn run(
ctx: &Context<'_>,
src: &str,
arg: Vec<Value>,
doc: Option<&Value>,
) -> Result<Value, Error> {
// Check the context
let _ = ctx.check()?;
// Create an execution context
let mut ctx = Boa::default();
// Convert the arguments to JavaScript
let args = JsValue::from(Value::from(arg));
// Convert the current document to JavaScript
let this = doc.map_or(JsValue::Undefined, JsValue::from);
// Create the main function structure
let src = format!("(function() {{ {} }}).apply(self, args)", src);
// Register the current document as a global object
ctx.register_global_property("self", this, Attribute::default());
// Register the current document as a global object
ctx.register_global_property("args", args, Attribute::default());
// Register the JsDuration type as a global class
ctx.register_global_class::<JsDuration>().unwrap();
// Register the JsRecord type as a global class
ctx.register_global_class::<JsRecord>().unwrap();
// Attempt to execute the script
match ctx.eval(src.as_bytes()) {
// The script executed successfully
Ok(v) => Ok(v.into()),
// There was an error running the script
Err(e) => Err(Error::InvalidScript {
message: e.display().to_string(),
}),
}
}
impl From<&Datetime> for Date {
fn from(v: &Datetime) -> Self {
let mut obj = Self::default();
obj.set_components(
true,
Some(v.year() as f64),
Some(v.month0() as f64),
Some(v.day() as f64),
Some(v.hour() as f64),
Some(v.minute() as f64),
Some(v.second() as f64),
Some((v.nanosecond() / 1_000_000) as f64),
);
obj
}
}
impl From<Value> for JsValue {
fn from(v: Value) -> Self {
JsValue::from(&v)
}
}
impl From<&Value> for JsValue {
fn from(v: &Value) -> Self {
match v {
Value::Null => JsValue::Null,
Value::Void => JsValue::Undefined,
Value::None => JsValue::Undefined,
Value::True => JsValue::Boolean(true),
Value::False => JsValue::Boolean(false),
Value::Strand(v) => JsValue::String(v.as_str().into()),
Value::Number(Number::Int(v)) => JsValue::Integer(*v as i32),
Value::Number(Number::Float(v)) => JsValue::Rational(*v as f64),
Value::Number(Number::Decimal(v)) => match v.is_integer() {
true => JsValue::BigInt(v.to_i64().unwrap_or_default().into()),
false => JsValue::Rational(v.to_f64().unwrap_or_default()),
},
Value::Datetime(v) => JsValue::from(JsObject::from_proto_and_data(
Boa::default().intrinsics().constructors().date().prototype(),
ObjectData::date(v.into()),
)),
Value::Duration(v) => JsValue::from(JsObject::from_proto_and_data(
Boa::default().intrinsics().constructors().object().prototype(),
ObjectData::native_object(Box::new(JsDuration {
value: v.to_string(),
})),
)),
Value::Thing(v) => JsValue::from(JsObject::from_proto_and_data(
Boa::default().intrinsics().constructors().object().prototype(),
ObjectData::native_object(Box::new(JsRecord {
tb: v.tb.to_string(),
id: v.id.to_string(),
})),
)),
Value::Array(v) => {
let ctx = &mut Boa::default();
let arr = JsArray::new(ctx);
for v in v.iter() {
arr.push(JsValue::from(v), ctx).unwrap();
}
JsValue::from(arr)
}
Value::Object(v) => {
let ctx = &mut Boa::default();
let obj = JsObject::default();
for (k, v) in v.iter() {
let k = JsString::from(k.as_str());
let v = JsValue::from(v);
obj.set(k, v, true, ctx).unwrap();
}
JsValue::from(obj)
}
_ => JsValue::Null,
}
}
}
impl From<JsValue> for Value {
fn from(v: JsValue) -> Self {
Value::from(&v)
}
}
impl From<&JsValue> for Value {
fn from(v: &JsValue) -> Self {
match v {
JsValue::Null => Value::Null,
JsValue::Undefined => Value::None,
JsValue::Boolean(v) => Value::from(*v),
JsValue::String(v) => Value::from(v.as_str()),
JsValue::Integer(v) => Value::from(Number::Int(*v as i64)),
JsValue::Rational(v) => Value::from(Number::Float(*v as f64)),
JsValue::BigInt(v) => Value::from(Number::from(v.clone().to_string())),
JsValue::Object(v) => {
// Check to see if this object is a duration
if v.is::<JsDuration>() {
if let Some(v) = v.downcast_ref::<JsDuration>() {
let v = v.value.clone();
return Duration::from(v).into();
}
}
// Check to see if this object is a record
if v.is::<JsRecord>() {
if let Some(v) = v.downcast_ref::<JsRecord>() {
let v = (v.tb.clone(), v.id.clone());
return Thing::from(v).into();
}
}
// Check to see if this object is a date
if let Some(v) = v.borrow().as_date() {
if let Some(v) = v.to_utc() {
return Datetime::from(v).into();
}
}
// Get a borrowed reference to the object
let o = v.borrow();
// Check to see if this is a normal type
match o.kind() {
// This object is a Javascript Array
ObjectKind::Array => {
let mut x = Array::default();
let ctx = &mut Boa::default();
let len = v.get("length", ctx).unwrap().to_u32(ctx).unwrap();
for i in 0..len {
let v = o.properties().get(&i.into()).unwrap();
if let Some(v) = v.value() {
let v = Value::from(v);
x.push(v);
}
}
x.into()
}
// This object is a Javascript Object
ObjectKind::Ordinary => {
let mut x = Object::default();
for (k, v) in o.properties().iter() {
if let Some(v) = v.value() {
let k = k.to_string();
let v = Value::from(v);
x.insert(k, v);
}
}
x.into()
}
// This object is a Javascript Map
ObjectKind::Map(v) => {
let mut x = Object::default();
for (k, v) in v.iter() {
let k = Value::from(k).as_string();
let v = Value::from(v);
x.insert(k, v);
}
x.into()
}
// This object is a Javascript Set
ObjectKind::Set(v) => {
let mut x = Array::default();
for v in v.iter() {
let v = Value::from(v);
x.push(v);
}
x.into()
}
_ => Value::Null,
}
}
_ => Value::Null,
}
}
}

View file

@ -0,0 +1,93 @@
#[js::bind(object, public)]
#[quickjs(bare)]
pub mod duration {
#[derive(Clone)]
#[quickjs(class)]
pub struct Duration {
#[quickjs(hide)]
pub(crate) value: String,
}
impl Duration {
#[quickjs(constructor)]
pub fn new(value: String) -> Self {
Self {
value,
}
}
#[quickjs(rename = "toString")]
pub fn output(&self) -> String {
self.value.to_owned()
}
#[quickjs(get)]
pub fn value(&self) -> &str {
&self.value
}
}
}
#[js::bind(object, public)]
#[quickjs(bare)]
pub mod uuid {
#[derive(Clone)]
#[quickjs(class)]
pub struct Uuid {
#[quickjs(hide)]
pub(crate) value: String,
}
impl Uuid {
#[quickjs(constructor)]
pub fn new(value: String) -> Self {
Self {
value,
}
}
#[quickjs(rename = "toString")]
pub fn output(&self) -> String {
self.value.to_owned()
}
#[quickjs(get)]
pub fn value(&self) -> &str {
&self.value
}
}
}
#[js::bind(object, public)]
#[quickjs(bare)]
pub mod record {
#[derive(Clone)]
#[quickjs(class)]
pub struct Record {
#[quickjs(hide)]
pub(crate) tb: String,
#[quickjs(hide)]
pub(crate) id: String,
}
impl Record {
#[quickjs(constructor)]
pub fn new(tb: String, id: String) -> Self {
Self {
tb,
id,
}
}
#[quickjs(rename = "toString")]
pub fn output(&self) -> String {
format!("{}:{}", self.tb, self.id)
}
#[quickjs(get)]
pub fn tb(&self) -> &str {
&self.tb
}
#[quickjs(get)]
pub fn id(&self) -> &str {
&self.id
}
}
}

View file

@ -0,0 +1,19 @@
use std::future::Future;
#[derive(Default)]
pub struct Executor<'a> {
exe: executor::Executor<'a>,
}
impl<'a> Executor<'a> {
pub async fn run<T>(&self, future: impl Future<Output = T>) -> T {
self.exe.run(future).await
}
}
impl<'a> js::ExecutorSpawner for &Executor<'a> {
type JoinHandle = executor::Task<()>;
fn spawn_executor(self, task: js::Executor) -> Self::JoinHandle {
self.exe.spawn(task)
}
}

View file

@ -0,0 +1,92 @@
use super::classes;
use crate::sql::array::Array;
use crate::sql::datetime::Datetime;
use crate::sql::duration::Duration;
use crate::sql::object::Object;
use crate::sql::thing::Thing;
use crate::sql::uuid::Uuid;
use crate::sql::value::Value;
use chrono::{TimeZone, Utc};
use js::Ctx;
use js::Error;
use js::FromAtom;
use js::FromJs;
impl<'js> FromJs<'js> for Value {
fn from_js(ctx: Ctx<'js>, val: js::Value<'js>) -> Result<Self, Error> {
match val {
val if val.type_name() == "null" => Ok(Value::Null),
val if val.type_name() == "undefined" => Ok(Value::None),
val if val.is_bool() => Ok(val.as_bool().unwrap().into()),
val if val.is_string() => match val.into_string().unwrap().to_string() {
Ok(v) => Ok(Value::from(v)),
Err(e) => Err(e),
},
val if val.is_int() => Ok(val.as_int().unwrap().into()),
val if val.is_float() => Ok(val.as_float().unwrap().into()),
val if val.is_array() => {
let v = val.as_array().unwrap();
let mut x = Array::with_capacity(v.len());
for i in v.iter() {
let v = i?;
let v = Value::from_js(ctx, v)?;
x.push(v);
}
Ok(x.into())
}
val if val.is_object() => {
// Extract the value as an object
let v = val.into_object().unwrap();
// Check to see if this object is a duration
if (&v).instance_of::<classes::duration::Duration>() {
let v = v.into_instance::<classes::duration::Duration>().unwrap();
let v: &classes::duration::Duration = v.as_ref();
let v = v.value.clone();
return Ok(Duration::from(v).into());
}
// Check to see if this object is a record
if (&v).instance_of::<classes::record::Record>() {
let v = v.into_instance::<classes::record::Record>().unwrap();
let v: &classes::record::Record = v.as_ref();
let v = (v.tb.clone(), v.id.clone());
return Ok(Thing::from(v).into());
}
// Check to see if this object is a uuid
if (&v).instance_of::<classes::uuid::Uuid>() {
let v = v.into_instance::<classes::uuid::Uuid>().unwrap();
let v: &classes::uuid::Uuid = v.as_ref();
let v = v.value.clone();
return Ok(Uuid::from(v).into());
}
// Check to see if this object is a date
let date: js::Object = ctx.globals().get("Date")?;
if (&v).is_instance_of(&date) {
let f: js::Function = v.get("getTime")?;
let m: i64 = f.call((js::This(v),))?;
let d = Utc.timestamp_millis(m);
return Ok(Datetime::from(d).into());
}
// Check to see if this object is an array
if let Some(v) = v.as_array() {
let mut x = Array::with_capacity(v.len());
for i in v.iter() {
let v = i?;
let v = Value::from_js(ctx, v)?;
x.push(v);
}
return Ok(x.into());
}
// This object is a normal object
let mut x = Object::default();
for i in v.props() {
let (k, v) = i?;
let k = String::from_atom(k)?;
let v = Value::from_js(ctx, v)?;
x.insert(k, v);
}
Ok(x.into())
}
_ => Ok(Value::None),
}
}
}

View file

@ -0,0 +1,78 @@
use super::classes;
use crate::sql::number::Number;
use crate::sql::value::Value;
use bigdecimal::ToPrimitive;
use js::Array;
use js::Class;
use js::Ctx;
use js::Error;
use js::IntoJs;
use js::Null;
use js::Object;
use js::Undefined;
impl<'js> IntoJs<'js> for Value {
fn into_js(self, ctx: Ctx<'js>) -> Result<js::Value<'js>, Error> {
(&self).into_js(ctx)
}
}
impl<'js> IntoJs<'js> for &Value {
fn into_js(self, ctx: Ctx<'js>) -> Result<js::Value<'js>, Error> {
match self {
Value::Null => Null.into_js(ctx),
Value::Void => Undefined.into_js(ctx),
Value::None => Undefined.into_js(ctx),
Value::True => Ok(js::Value::new_bool(ctx, true)),
Value::False => Ok(js::Value::new_bool(ctx, false)),
Value::Strand(v) => js::String::from_str(ctx, v)?.into_js(ctx),
Value::Number(Number::Int(v)) => Ok(js::Value::new_int(ctx, *v as i32)),
Value::Number(Number::Float(v)) => Ok(js::Value::new_float(ctx, *v as f64)),
Value::Number(Number::Decimal(v)) => match v.is_integer() {
true => Ok(js::Value::new_int(ctx, v.to_i32().unwrap_or_default())),
false => Ok(js::Value::new_float(ctx, v.to_f64().unwrap_or_default())),
},
Value::Datetime(v) => {
let date: js::Function = ctx.globals().get("Date")?;
date.construct((v.0.timestamp_millis(),))
}
Value::Duration(v) => Ok(Class::<classes::duration::Duration>::instance(
ctx,
classes::duration::Duration {
value: v.to_raw(),
},
)?
.into_value()),
Value::Thing(v) => Ok(Class::<classes::record::Record>::instance(
ctx,
classes::record::Record {
tb: v.tb.to_owned(),
id: v.id.to_raw(),
},
)?
.into_value()),
Value::Uuid(v) => Ok(Class::<classes::uuid::Uuid>::instance(
ctx,
classes::uuid::Uuid {
value: v.to_raw(),
},
)?
.into_value()),
Value::Array(v) => {
let x = Array::new(ctx)?;
for (i, v) in v.iter().enumerate() {
x.set(i, v)?;
}
x.into_js(ctx)
}
Value::Object(v) => {
let x = Object::new(ctx)?;
for (k, v) in v.iter() {
x.set(k, v)?;
}
x.into_js(ctx)
}
_ => Undefined.into_js(ctx),
}
}
}

View file

@ -0,0 +1,65 @@
use super::classes;
use super::executor::Executor;
use crate::ctx::Context;
use crate::err::Error;
use crate::sql::value::Value;
use js::Promise;
pub async fn run(
ctx: &Context<'_>,
src: &str,
arg: Vec<Value>,
doc: Option<&Value>,
) -> Result<Value, Error> {
// Check the context
let _ = ctx.check()?;
// Create a new agent
let exe = Executor::default();
// Create an JavaScript context
let run = js::Runtime::new().unwrap();
// Create an execution context
let ctx = js::Context::full(&run).unwrap();
// Enable async code in the runtime
run.spawn_executor(&exe).detach();
// Convert the arguments to JavaScript
let args = Value::from(arg);
// Convert the current document to JavaScript
let this = doc.map_or(&Value::None, |v| v);
// Create the main function structure
let src = format!("(async function() {{ {} }}).apply(self, args)", src);
// Attempt to execute the script
let res: Result<Promise<Value>, js::Error> = ctx.with(|ctx| {
// Get the context global object
let global = ctx.globals();
// Register the Duration type as a global class
global.init_def::<classes::Duration>().unwrap();
// Register the Record type as a global class
global.init_def::<classes::Record>().unwrap();
// Register the Uuid type as a global class
global.init_def::<classes::Uuid>().unwrap();
// Register the document as a global object
global.prop("self", this).unwrap();
// Register the args as a global object
global.prop("args", args).unwrap();
// Attempt to execute the script
ctx.eval(src)
});
// Return the script result
let res = match res {
// The script executed successfully
Ok(v) => match exe.run(v).await {
// The promise fulfilled successfully
Ok(v) => Ok(v),
// There was an error awaiting the promise
Err(e) => Err(Error::InvalidScript {
message: e.to_string(),
}),
},
// There was an error running the script
Err(e) => Err(Error::InvalidScript {
message: e.to_string(),
}),
};
// Return the result
res
}

View file

@ -0,0 +1,9 @@
#![cfg(feature = "scripting")]
pub use main::run;
mod classes;
mod executor;
mod from;
mod into;
mod main;

View file

@ -53,6 +53,12 @@ impl Deref for Duration {
}
}
impl Duration {
pub fn to_raw(&self) -> String {
self.to_string()
}
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Split up the duration

View file

@ -49,6 +49,12 @@ impl Id {
pub fn rand() -> Id {
Id::String(nanoid!(20, &ID_CHARS))
}
pub fn to_raw(&self) -> String {
match self {
Id::Number(v) => v.to_string(),
Id::String(v) => v.to_string(),
}
}
}
impl fmt::Display for Id {