diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 0d44987be3..c07ff6af9b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -91,6 +91,7 @@ embedding = ["windmill-api/embedding"] parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet", "dep:object_store"] prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus", "dep:prometheus"] flow_testing = ["windmill-worker/flow_testing"] +quickjs = ["windmill-worker/quickjs", "windmill-api/quickjs"] openidconnect = ["windmill-api/openidconnect", "windmill-common/openidconnect"] cloud = ["windmill-queue/cloud", "windmill-worker/cloud", "windmill-common/cloud", "windmill-api/cloud"] jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"] @@ -145,7 +146,8 @@ oss_core = [ "embedding", "parquet", "openidconnect", "license", "http_trigger", "zip", "oauth2", "postgres_trigger", "mqtt_trigger", "websocket", "smtp", "native_trigger", - "static_frontend", "mcp", "bedrock", "inline_preview" + "static_frontend", "mcp", "bedrock", "inline_preview", + "quickjs" ] ce_core = ["oss_core", "private"] ee_core = [ diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index 8e3c7d2818..bbf1492d84 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" [features] default = [] private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"] -enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise"] +enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"] stripe = [] inline_preview = ["dep:windmill-worker"] agent_worker_server = ["dep:windmill-worker"] @@ -42,6 +42,7 @@ mcp = ["dep:windmill-mcp", "windmill-mcp/server", "windmill-mcp/auth", "windmill bedrock = ["dep:aws-sdk-bedrock", "dep:aws-sdk-bedrockruntime", "windmill-common/bedrock", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-types"] python = ["windmill-dep-map/python", "dep:windmill-parser-py", "dep:windmill-parser-py-imports", "windmill-api-scripts/python", "windmill-trigger/python", "windmill-common/python"] no_auth = ["windmill-api-auth/no_auth", "windmill-store/no_auth", "windmill-api-users/no_auth"] +quickjs = ["windmill-jseval/quickjs"] [dependencies] windmill-mcp = { workspace = true, optional = true } @@ -64,7 +65,7 @@ windmill-parser-py = { workspace = true, optional = true } windmill-parser-py-imports = { workspace = true, optional = true } windmill-git-sync.workspace = true windmill-indexer = { workspace = true, optional = true } -windmill-autoscaling.workspace = true +windmill-autoscaling = { workspace = true, optional = true } windmill-worker = { workspace = true, optional = true } windmill-dep-map.workspace = true tokio.workspace = true diff --git a/backend/windmill-jseval/Cargo.toml b/backend/windmill-jseval/Cargo.toml index 076f1329f3..b2f7217404 100644 --- a/backend/windmill-jseval/Cargo.toml +++ b/backend/windmill-jseval/Cargo.toml @@ -10,10 +10,11 @@ path = "src/lib.rs" [features] default = [] +quickjs = ["dep:rquickjs"] [dependencies] windmill-common = { workspace = true, default-features = false } -rquickjs.workspace = true +rquickjs = { workspace = true, optional = true } serde_json.workspace = true tokio.workspace = true tracing.workspace = true diff --git a/backend/windmill-jseval/src/lib.rs b/backend/windmill-jseval/src/lib.rs index 34fdb62ee5..92024395c5 100644 --- a/backend/windmill-jseval/src/lib.rs +++ b/backend/windmill-jseval/src/lib.rs @@ -21,6 +21,7 @@ use std::sync::Arc; use lazy_static::lazy_static; use regex::Regex; +#[cfg(feature = "quickjs")] use rquickjs::{ async_with, prelude::{Async, Func, MutFn}, @@ -63,8 +64,9 @@ lazy_static! { .unwrap(); } -// ── Shared helper functions ─────────────────────────────────────────── +// ── Shared helper functions (quickjs-only) ────────────────────────── +#[cfg(feature = "quickjs")] pub fn replace_with_await(expr: String, fn_name: &str) -> String { let sep = format!("{}(", fn_name); let mut split = expr.split(&sep); @@ -75,10 +77,12 @@ pub fn replace_with_await(expr: String, fn_name: &str) -> String { s } +#[cfg(feature = "quickjs")] pub fn replace_with_await_result(expr: String) -> String { RE.replace_all(&expr, "(await $r)").to_string() } +#[cfg(feature = "quickjs")] fn add_closing_bracket(s: &str) -> String { let mut s = s.to_string(); let mut level = 1; @@ -194,23 +198,20 @@ pub async fn handle_full_regex( return None; } +#[cfg(feature = "quickjs")] use windmill_common::utils::unsafe_raw; // ── QuickJS evaluation ─────────────────────────────────────────────── +#[cfg(feature = "quickjs")] + /// Shared state for async operations within QuickJS #[derive(Clone)] struct AsyncOpState { client: AuthedClient, } -/// Evaluates a JavaScript expression using QuickJS runtime. -/// -/// This function provides flow expression evaluation using QuickJS -/// instead of deno_core/V8 for significantly faster startup times. -/// -/// Unlike deno_core, this uses true async Rust callbacks for `variable()`, -/// `resource()`, and `results.xxx` access - no pre-fetching required. +#[cfg(feature = "quickjs")] pub async fn eval_timeout_quickjs( expr: String, transform_context: HashMap>>, @@ -301,9 +302,10 @@ pub async fn eval_timeout_quickjs( })?? } -/// Memory limit for QuickJS runtime (32MB). +#[cfg(feature = "quickjs")] const QUICKJS_MEMORY_LIMIT: usize = 32 * 1024 * 1024; +#[cfg(feature = "quickjs")] async fn eval_quickjs_inner( expr: &str, transform_context: HashMap>>, @@ -441,7 +443,7 @@ async fn eval_quickjs_inner( .await } -/// Set up async variable() and resource() functions using true Rust async callbacks. +#[cfg(feature = "quickjs")] fn setup_async_ops<'js>( ctx: &rquickjs::Ctx<'js>, globals: &Object<'js>, @@ -509,7 +511,7 @@ fn setup_async_ops<'js>( Ok(()) } -/// Set up stub functions that throw errors when no client is available +#[cfg(feature = "quickjs")] fn setup_stub_functions<'js>( ctx: &rquickjs::Ctx<'js>, _globals: &Object<'js>, @@ -531,7 +533,7 @@ fn setup_stub_functions<'js>( Ok(()) } -/// Set up the `results` Proxy object with dynamic access to step results. +#[cfg(feature = "quickjs")] fn setup_results_proxy<'js>( ctx: &rquickjs::Ctx<'js>, globals: &Object<'js>, @@ -657,7 +659,7 @@ fn setup_results_proxy<'js>( Ok(()) } -/// Convert a serde_json::Value to a QuickJS Value +#[cfg(feature = "quickjs")] fn json_to_js<'js>( ctx: &rquickjs::Ctx<'js>, val: &serde_json::Value, @@ -696,6 +698,7 @@ fn json_to_js<'js>( } } +#[cfg(feature = "quickjs")] fn should_add_return_quickjs(expr: &str) -> bool { let trimmed = expr.trim(); @@ -741,6 +744,7 @@ fn should_add_return_quickjs(expr: &str) -> bool { true } +#[cfg(feature = "quickjs")] fn contains_semicolon_outside_strings(expr: &str) -> bool { let mut in_single_quote = false; let mut in_double_quote = false; @@ -769,14 +773,14 @@ fn contains_semicolon_outside_strings(expr: &str) -> bool { false } +#[cfg(feature = "quickjs")] fn quickjs_error_to_anyhow(err: rquickjs::CaughtError<'_>) -> anyhow::Error { anyhow::anyhow!("QuickJS evaluation error: {}", err) } // ── eval_simple_js for windmill-api batch rerun ────────────────────── -/// Evaluate a JS expression with named JSON globals in scope. -/// Used by windmill-api for batch rerun arg transforms. +#[cfg(feature = "quickjs")] pub async fn eval_simple_js( expr: String, globals: HashMap, @@ -819,9 +823,32 @@ pub async fn eval_simple_js( })?? } +// ── Fallback stubs when quickjs is disabled ────────────────────────── + +#[cfg(not(feature = "quickjs"))] +pub async fn eval_timeout_quickjs( + _expr: String, + _transform_context: HashMap>>, + _flow_input: Option>>>, + _flow_env: Option<&HashMap>>, + _authed_client: Option<&windmill_common::client::AuthedClient>, + _by_id: Option<&IdContext>, + _ctx: Option>, +) -> anyhow::Result> { + anyhow::bail!("quickjs feature is not enabled") +} + +#[cfg(not(feature = "quickjs"))] +pub async fn eval_simple_js( + _expr: String, + _globals: HashMap, +) -> anyhow::Result> { + anyhow::bail!("quickjs feature is not enabled") +} + // ── Tests ──────────────────────────────────────────────────────────── -#[cfg(test)] +#[cfg(all(test, feature = "quickjs"))] mod tests { use super::*; use serde_json::json; diff --git a/backend/windmill-worker/Cargo.toml b/backend/windmill-worker/Cargo.toml index 619d092c7e..d95321dd77 100644 --- a/backend/windmill-worker/Cargo.toml +++ b/backend/windmill-worker/Cargo.toml @@ -13,7 +13,7 @@ default = [] private = [] mcp = ["dep:windmill-mcp"] prometheus = ["dep:prometheus", "windmill-common/prometheus"] -enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "dep:pem", "dep:tokio-util"] +enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "dep:pem", "dep:tokio-util", "dep:opentelemetry-proto", "dep:prost", "dep:hudsucker", "dep:rcgen", "dep:hyper-http-proxy", "dep:hyper-tls", "dep:hyper-util"] mssql = ["dep:tiberius"] mssql-kerberos = ["mssql", "tiberius/integrated-auth-gssapi"] # Linux/Unix integrated auth mssql-winauth = ["mssql", "tiberius/winauth"] # Windows integrated auth @@ -37,6 +37,7 @@ nu = ["dep:windmill-parser-nu"] java = ["dep:windmill-parser-java"] ruby = ["dep:windmill-parser-ruby"] duckdb = ["dep:libloading"] +quickjs = ["windmill-jseval/quickjs"] bedrock = ["dep:aws-sdk-bedrockruntime", "windmill-common/bedrock", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-types"] [dependencies] @@ -127,18 +128,18 @@ process-wrap.workspace = true async-once-cell.workspace = true libloading = { workspace = true, optional = true } -opentelemetry-proto.workspace = true +opentelemetry-proto = { workspace = true, optional = true } opentelemetry = { workspace = true, optional = true } tracing-opentelemetry = { workspace = true, optional = true } -prost.workspace = true +prost = { workspace = true, optional = true } axum.workspace = true bollard = { workspace = true, optional = true } oracle = { workspace = true, optional = true } -hudsucker.workspace = true -hyper-http-proxy.workspace = true -hyper-tls.workspace = true -hyper-util.workspace = true -rcgen.workspace = true +hudsucker = { workspace = true, optional = true } +hyper-http-proxy = { workspace = true, optional = true } +hyper-tls = { workspace = true, optional = true } +hyper-util = { workspace = true, optional = true } +rcgen = { workspace = true, optional = true } [build-dependencies] libffi-sys = { workspace = true, optional = true }