feat: add graphql support (#2014)

* feat: add graphql support

* fix: use custom editor for viewing graphql schema

* fix: graphql parser cargo version

* fix: add graphql where missing
This commit is contained in:
HugoCasa
2023-08-09 16:19:57 +02:00
committed by GitHub
parent c3615765c3
commit f1a0070b71
49 changed files with 857 additions and 151 deletions
+13
View File
@@ -7265,6 +7265,17 @@ dependencies = [
"windmill-parser",
]
[[package]]
name = "windmill-parser-graphql"
version = "1.143.0"
dependencies = [
"anyhow",
"lazy_static",
"regex",
"serde_json",
"windmill-parser",
]
[[package]]
name = "windmill-parser-py"
version = "1.143.0"
@@ -7331,6 +7342,7 @@ dependencies = [
"windmill-parser",
"windmill-parser-bash",
"windmill-parser-go",
"windmill-parser-graphql",
"windmill-parser-py",
"windmill-parser-sql",
"windmill-parser-ts",
@@ -7413,6 +7425,7 @@ dependencies = [
"windmill-parser",
"windmill-parser-bash",
"windmill-parser-go",
"windmill-parser-graphql",
"windmill-parser-py",
"windmill-parser-py-imports",
"windmill-parser-sql",
+1
View File
@@ -85,6 +85,7 @@ windmill-parser-py-imports = { path = "./parsers/windmill-parser-py-imports" }
windmill-parser-go = { path = "./parsers/windmill-parser-go" }
windmill-parser-bash = { path = "./parsers/windmill-parser-bash" }
windmill-parser-sql = { path = "./parsers/windmill-parser-sql" }
windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
axum = { version = "^0", features = ["headers"] }
headers = "^0"
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'graphql';
@@ -0,0 +1,16 @@
[package]
name = "windmill-parser-graphql"
version.workspace = true
edition.workspace = true
authors.workspace = true
[lib]
name = "windmill_parser_graphql"
path = "./src/lib.rs"
[dependencies]
windmill-parser.workspace = true
anyhow.workspace = true
regex.workspace = true
lazy_static.workspace = true
serde_json.workspace = true
@@ -0,0 +1,111 @@
#![allow(non_snake_case)] // TODO: switch to parse_* function naming
use anyhow::anyhow;
use regex::Regex;
use serde_json::json;
use windmill_parser::{Arg, MainArgSignature, Typ};
pub fn parse_graphql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_graphql_file(&code)?;
if let Some(x) = parsed {
let args = x;
Ok(MainArgSignature { star_args: false, star_kwargs: false, args })
} else {
Err(anyhow!("Error parsing sql".to_string()))
}
}
lazy_static::lazy_static! {
static ref RE_ARG_GRAPHQL: Regex = Regex::new(r#"\$(\w+)\s*:\s*(?:(\w+)!?|\[(\w+)!?\])!?\s*(?:=\s*(\w+)\s*)?"#).unwrap();
static ref RE_ARG_GRAPHQL_ARRAY: Regex = Regex::new(r#"^\[(\w+)!?\]!?$"#).unwrap();
}
fn parse_graphql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
let mut args: Vec<Arg> = vec![];
for cap in RE_ARG_GRAPHQL.captures_iter(code) {
let name = cap.get(1).map(|x| x.as_str().to_string()).unwrap();
let mut typ = cap.get(2).map(|x| x.as_str().to_string());
let parsed_typ = if typ.is_none() {
let inner_typ = cap.get(3).map(|x| x.as_str().to_string());
typ = inner_typ.clone().map(|x| format!("[{}]", x.to_string()));
Typ::List(Box::new(parse_graphql_typ(inner_typ.unwrap().as_str())))
} else {
parse_graphql_typ(typ.clone().unwrap().as_str())
};
let default = cap.get(4).map(|x| x.as_str().to_string());
let has_default = default.is_some();
let parsed_default = default.and_then(|x| match parsed_typ {
Typ::Int => x.parse::<i64>().ok().map(|x| json!(x)),
Typ::Float => x.parse::<f64>().ok().map(|x| json!(x)),
_ => Some(json!(x)),
});
args.push(Arg {
name,
typ: parsed_typ,
default: parsed_default,
otyp: Some(typ.unwrap()),
has_default,
});
}
Ok(Some(args))
}
pub fn parse_graphql_typ(typ: &str) -> Typ {
match typ {
"String" | "ID" => Typ::Str(None),
"Int" => Typ::Int,
"Boolean" => Typ::Bool,
"Float" => Typ::Float,
_ => Typ::Object(vec![]),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_graphql_sig() -> anyhow::Result<()> {
let code = r#"
query($s: String, $arr: [String]) {
books {
title
}
}
"#;
//println!("{}", serde_json::to_string()?);
assert_eq!(
parse_graphql_sig(code)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![
Arg {
otyp: Some("String".to_string()),
name: "s".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
},
Arg {
otyp: Some("[String]".to_string()),
name: "arr".to_string(),
typ: Typ::List(Box::new(Typ::Str(None))),
default: None,
has_default: false
},
]
}
);
Ok(())
}
}
@@ -21,6 +21,7 @@ windmill-parser-bash.workspace = true
windmill-parser-sql.workspace = true
windmill-parser-py.workspace = true
windmill-parser-ts.workspace = true
windmill-parser-graphql.workspace = true
wasm-bindgen.workspace = true
serde_json.workspace = true
getrandom = { workspace = true, features = ["js"] }
@@ -3,7 +3,7 @@
"collaborators": [
"Ruben Fiszel <ruben@windmill.dev>"
],
"version": "1.138.1",
"version": "1.141.0",
"files": [
"windmill_parser_wasm_bg.wasm",
"windmill_parser_wasm.js",
@@ -40,6 +40,11 @@ export function parse_bigquery(code: string): string;
* @returns {string}
*/
export function parse_snowflake(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_graphql(code: string): string;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
@@ -53,6 +58,7 @@ export interface InitOutput {
readonly parse_mysql: (a: number, b: number, c: number) => void;
readonly parse_bigquery: (a: number, b: number, c: number) => void;
readonly parse_snowflake: (a: number, b: number, c: number) => void;
readonly parse_graphql: (a: number, b: number, c: number) => void;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
@@ -381,6 +381,29 @@ export function parse_snowflake(code) {
}
}
/**
* @param {string} code
* @returns {string}
*/
export function parse_graphql(code) {
let deferred2_0;
let deferred2_1;
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
wasm.parse_graphql(retptr, ptr0, len0);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
deferred2_0 = r0;
deferred2_1 = r1;
return getStringFromWasm0(r0, r1);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}
function handleError(f, args) {
try {
return f.apply(this, args);
@@ -423,7 +446,7 @@ async function __wbg_load(module, imports) {
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_eval_d972bbef37d2cd5a = function(arg0, arg1) {
imports.wbg.__wbg_eval_9331e2bd3095f3d6 = function(arg0, arg1) {
const ret = eval(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
@@ -9,6 +9,7 @@ export function parse_sql(a: number, b: number, c: number): void;
export function parse_mysql(a: number, b: number, c: number): void;
export function parse_bigquery(a: number, b: number, c: number): void;
export function parse_snowflake(a: number, b: number, c: number): void;
export function parse_graphql(a: number, b: number, c: number): void;
export function __wbindgen_malloc(a: number, b: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number;
export function __wbindgen_add_to_stack_pointer(a: number): number;
@@ -49,3 +49,8 @@ pub fn parse_bigquery(code: &str) -> String {
pub fn parse_snowflake(code: &str) -> String {
wrap_sig(windmill_parser_sql::parse_snowflake_sig(code))
}
#[wasm_bindgen]
pub fn parse_graphql(code: &str) -> String {
wrap_sig(windmill_parser_graphql::parse_graphql_sig(code))
}
+1
View File
@@ -1478,6 +1478,7 @@ async fn tarball_workspace(
ScriptLang::Mysql => "my.sql",
ScriptLang::Bigquery => "bq.sql",
ScriptLang::Snowflake => "sf.sql",
ScriptLang::Graphql => "gql",
ScriptLang::Nativets => "fetch.ts",
ScriptLang::Bun => "bun.ts",
};
+2
View File
@@ -35,6 +35,7 @@ pub enum ScriptLang {
Mysql,
Bigquery,
Snowflake,
Graphql,
}
impl ScriptLang {
@@ -50,6 +51,7 @@ impl ScriptLang {
ScriptLang::Mysql => "mysql",
ScriptLang::Bigquery => "bigquery",
ScriptLang::Snowflake => "snowflake",
ScriptLang::Graphql => "graphql",
}
}
}
+1
View File
@@ -76,6 +76,7 @@ lazy_static::lazy_static! {
"postgresql".to_string(),
"bigquery".to_string(),
"snowflake".to_string(),
"graphql".to_string(),
"dependency".to_string(),
"flow".to_string(),
"hub".to_string(),
+1
View File
@@ -30,6 +30,7 @@ windmill-parser-py.workspace = true
windmill-parser-py-imports.workspace = true
windmill-parser-bash.workspace = true
windmill-parser-sql.workspace = true
windmill-parser-graphql.workspace = true
sqlx.workspace = true
uuid.workspace = true
tracing.workspace = true
@@ -6,7 +6,7 @@ use windmill_queue::HTTP_CLIENT;
use serde::Deserialize;
use crate::{get_content, transform_json_value, AuthedClient, JobCompleted};
use crate::{transform_json_value, AuthedClient, JobCompleted};
use gcp_auth::{AuthenticationManager, CustomServiceAccount};
@@ -54,7 +54,7 @@ struct BigqueryError {
pub async fn do_bigquery(
job: QueuedJob,
client: &AuthedClient,
db: &sqlx::Pool<sqlx::Postgres>,
query: &str,
) -> windmill_common::error::Result<JobCompleted> {
let args = if let Some(args) = &job.args {
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
@@ -94,8 +94,6 @@ pub async fn do_bigquery(
let mut statement_values: Vec<Value> = vec![];
let query = get_content(&job, db).await?;
let sig = parse_bigquery_sig(&query)
.map_err(|x| Error::ExecutionErr(x.to_string()))?
.args;
@@ -0,0 +1,89 @@
use serde_json::{json, Value};
use windmill_common::error::Error;
use windmill_common::jobs::QueuedJob;
use windmill_queue::HTTP_CLIENT;
use serde::Deserialize;
use crate::{transform_json_value, AuthedClient, JobCompleted};
#[derive(Deserialize)]
struct GraphqlDatabase {
bearer_token: Option<String>,
base_url: String,
}
#[derive(Deserialize)]
struct GraphqlResponse {
data: Option<Value>,
errors: Option<Vec<GraphqlError>>,
}
#[derive(Deserialize)]
struct GraphqlError {
message: String,
}
pub async fn do_graphql(
job: QueuedJob,
client: &AuthedClient,
query: &str,
) -> windmill_common::error::Result<JobCompleted> {
let args = if let Some(args) = &job.args {
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
} else {
None
};
let graphql_args: serde_json::Value = serde_json::from_value(args.unwrap_or_else(|| json!({})))
.map_err(|e| Error::ExecutionErr(e.to_string()))?;
let database = serde_json::from_value::<GraphqlDatabase>(
graphql_args.get("database").unwrap_or(&json!({})).clone(),
)
.map_err(|e: serde_json::Error| Error::ExecutionErr(e.to_string()))?;
let args = &job
.args
.clone()
.unwrap_or_else(|| json!({}))
.as_object()
.map(|x| x.to_owned())
.unwrap_or_else(|| json!({}).as_object().unwrap().to_owned());
let mut request = HTTP_CLIENT.post(database.base_url).json(&json!({
"query": query,
"variables": args
}));
if let Some(token) = &database.bearer_token {
request = request.bearer_auth(token.as_str());
}
let response = request
.send()
.await
.map_err(|e| Error::ExecutionErr(e.to_string()))?;
let result = response
.json::<GraphqlResponse>()
.await
.map_err(|e| Error::ExecutionErr(e.to_string()))?;
if let Some(errors) = result.errors {
return Err(Error::ExecutionErr(
errors
.into_iter()
.map(|x| x.message)
.collect::<Vec<_>>()
.join("\n"),
));
}
// And then check that we got back the same string we sent over.
return Ok(JobCompleted {
job: job,
result: result.data.unwrap_or(json!({})),
logs: "".to_string(),
success: true,
});
}
+1
View File
@@ -6,6 +6,7 @@ mod snowflake_executor;
mod common;
mod global_cache;
mod go_executor;
mod graphql_executor;
mod js_eval;
mod mysql_executor;
mod pg_executor;
@@ -12,7 +12,7 @@ use windmill_queue::HTTP_CLIENT;
use serde::{Deserialize, Serialize};
use crate::{get_content, transform_json_value, AuthedClient, JobCompleted};
use crate::{transform_json_value, AuthedClient, JobCompleted};
#[derive(Serialize)]
struct Claims {
@@ -63,7 +63,7 @@ struct SnowflakeError {
pub async fn do_snowflake(
job: QueuedJob,
client: &AuthedClient,
db: &sqlx::Pool<sqlx::Postgres>,
query: &str,
) -> windmill_common::error::Result<JobCompleted> {
let args = if let Some(args) = &job.args {
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
@@ -114,8 +114,6 @@ pub async fn do_snowflake(
.map(|x| x.to_owned())
.unwrap_or_else(|| json!({}).as_object().unwrap().to_owned());
let query = get_content(&job, db).await?;
let mut bindings = serde_json::Map::new();
let sig = parse_snowflake_sig(&query)
.map_err(|x| Error::ExecutionErr(x.to_string()))?
+8 -3
View File
@@ -66,7 +66,7 @@ use windmill_queue::{add_completed_job, add_completed_job_error,IDLE_WORKERS};
use crate::{
worker_flow::{
handle_flow, update_flow_status_after_job_completion, update_flow_status_in_progress,
}, python_executor::{create_dependencies_dir, pip_compile, handle_python_job, handle_python_reqs}, common::{read_result, set_logs}, go_executor::{handle_go_job, install_go_dependencies}, js_eval::{transpile_ts, eval_fetch_timeout}, pg_executor::do_postgresql, mysql_executor::do_mysql,
}, python_executor::{create_dependencies_dir, pip_compile, handle_python_job, handle_python_reqs}, common::{read_result, set_logs}, go_executor::{handle_go_job, install_go_dependencies}, js_eval::{transpile_ts, eval_fetch_timeout}, pg_executor::do_postgresql, mysql_executor::do_mysql, graphql_executor::do_graphql,
};
#[cfg(feature = "enterprise")]
@@ -361,6 +361,7 @@ pub async fn run_worker<R: rsmq_async::RsmqConnection + Send + Sync + Clone + 's
Some(ScriptLang::Mysql),
Some(ScriptLang::Bigquery),
Some(ScriptLang::Snowflake),
Some(ScriptLang::Graphql),
Some(ScriptLang::Bun)];
let worker_execution_duration: HashMap<_, _> = all_langs.clone().into_iter().map(|x| (x.clone(), prometheus::register_histogram!(
@@ -1343,7 +1344,7 @@ async fn handle_code_execution_job(
#[cfg(feature = "enterprise")]
{
let jc = do_bigquery(job.clone(), &client.get_authed().await, &db).await?;
let jc = do_bigquery(job.clone(), &client.get_authed().await, &inner_content).await?;
return Ok(jc.result)
}
} else if language == Some(ScriptLang::Snowflake) {
@@ -1354,9 +1355,12 @@ async fn handle_code_execution_job(
#[cfg(feature = "enterprise")]
{
let jc = do_snowflake(job.clone(), &client.get_authed().await, &db).await?;
let jc = do_snowflake(job.clone(), &client.get_authed().await, &inner_content).await?;
return Ok(jc.result)
}
} else if language == Some(ScriptLang::Graphql) {
let jc = do_graphql(job.clone(), &client.get_authed().await, &inner_content).await?;
return Ok(jc.result)
} else if language == Some(ScriptLang::Nativets) {
logs.push_str("\n--- FETCH TS EXECUTION ---\n");
let code = format!("const BASE_URL = '{base_internal_url}';\nconst WM_TOKEN = '{}';\n{}", &client.get_token().await, inner_content);
@@ -2244,6 +2248,7 @@ async fn capture_dependency_job(
ScriptLang::Mysql => Ok("".to_owned()),
ScriptLang::Bigquery => Ok("".to_owned()),
ScriptLang::Snowflake => Ok("".to_owned()),
ScriptLang::Graphql => Ok("".to_owned()),
ScriptLang::Bash => Ok("".to_owned()),
ScriptLang::Nativets => Ok("".to_owned()),
+2
View File
@@ -64,6 +64,8 @@ async function dev(opts: GlobalOptions & { filter?: string }) {
: splitted.length > 2 && splitted[splitted.length - 2] == "sf"
? "snowflake"
: "postgresql"
: ext == "gql"
? "graphql"
: "unknown";
currentLastEdit = {
content,
+1
View File
@@ -137,6 +137,7 @@ function ZipFSElement(zip: JSZip, useYaml: boolean): DynFSElement {
else if (language == "mysql") ext = "my.sql";
else if (language == "bigquery") ext = "bq.sql";
else if (language == "snowflake") ext = "sf.sql";
else if (language == "graphql") ext = "gql";
else if (language == "bun") ext = "bun.ts";
else if (language == "nativets") ext = "native.ts";
+113 -10
View File
@@ -26,9 +26,11 @@
"diff": "^5.1.0",
"esm-env": "^1.0.0",
"fast-equals": "^5.0.1",
"graphql": "^16.7.1",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"lucide-svelte": "^0.246.0",
"monaco-graphql": "^1.3.0",
"monaco-languageclient": "~6.0.3",
"openai": "^4.0.0-beta.4",
"quill": "^1.3.7",
@@ -41,7 +43,7 @@
"svelte-timezone-picker": "^2.0.3",
"tailwind-merge": "^1.13.2",
"vscode-ws-jsonrpc": "3.0.0",
"windmill-parser-wasm": "^1.138.1",
"windmill-parser-wasm": "^1.141.0",
"y-monaco": "^0.1.4",
"y-websocket": "^1.5.0",
"yjs": "^13.6.7"
@@ -99,6 +101,7 @@
"tslib": "^2.6.0",
"typescript": "^5.1.3",
"vite": "^4.4.7",
"vite-plugin-monaco-editor": "^1.1.0",
"yootils": "^0.3.1"
},
"peerDependencies": {
@@ -4342,6 +4345,29 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
"node_modules/graphql": {
"version": "16.7.1",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz",
"integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==",
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/graphql-language-service": {
"version": "5.1.7",
"resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.7.tgz",
"integrity": "sha512-xkawYMJeoNYGhT+SpSH3c2qf6HpGHQ/duDmrseVHBpVCrXAiGnliXGSCC4jyMGgZQ05GytsZ12p0nUo7s6lSSw==",
"dependencies": {
"nullthrows": "^1.0.0",
"vscode-languageserver-types": "^3.17.1"
},
"bin": {
"graphql": "dist/temp-bin.js"
},
"peerDependencies": {
"graphql": "^15.5.0 || ^16.0.0"
}
},
"node_modules/hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
@@ -6315,6 +6341,20 @@
"integrity": "sha512-zhbZ2Nx93tLR8aJmL2zI1mhJpsl87HMebNBM6R8z4pLfs8pj604pIVIVwyF1TivcfNtIPpMXL+nb3DsBmE/x6Q==",
"dev": true
},
"node_modules/monaco-graphql": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/monaco-graphql/-/monaco-graphql-1.3.0.tgz",
"integrity": "sha512-Ilz7kUKYfO5KGwandjR+eQbN+fj1fQY9OECIQlzgeWOkLXMZqXTsxf01iQfVw08mMsBz7qa6fZKsESouDcBN2g==",
"dependencies": {
"graphql-language-service": "^5.1.7",
"picomatch-browser": "^2.2.6"
},
"peerDependencies": {
"graphql": "^15.5.0 || ^16.0.0",
"monaco-editor": ">= 0.20.0 < 1",
"prettier": "^2.8.0 || ^3.0.0"
}
},
"node_modules/monaco-languageclient": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-6.0.3.tgz",
@@ -6564,6 +6604,11 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/nullthrows": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -6911,6 +6956,17 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/picomatch-browser": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/picomatch-browser/-/picomatch-browser-2.2.6.tgz",
"integrity": "sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w==",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -7549,7 +7605,6 @@
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
@@ -9616,6 +9671,15 @@
}
}
},
"node_modules/vite-plugin-monaco-editor": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
"integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
"dev": true,
"peerDependencies": {
"monaco-editor": ">=0.33.0"
}
},
"node_modules/vitefu": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz",
@@ -9777,9 +9841,9 @@
}
},
"node_modules/windmill-parser-wasm": {
"version": "1.138.1",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.138.1.tgz",
"integrity": "sha512-KGbiKKk7i8xRV+kcGXNRChIVXRQRAQ6eAGVJo7c2qjq+Sft3cGteWbkgeh8gjU5+KdmnP866jGwsRtw+zW2+5A=="
"version": "1.141.0",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.141.0.tgz",
"integrity": "sha512-2U4wAyKOMa7YTcWGUSCCqhNEfI+dVF9JvlXzPneOUO9nD7zm5uXRB+xaJLHKtYB5gmzLz1cQElp/FpbJaLLZsw=="
},
"node_modules/wordwrap": {
"version": "1.0.0",
@@ -13005,6 +13069,20 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
"graphql": {
"version": "16.7.1",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz",
"integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg=="
},
"graphql-language-service": {
"version": "5.1.7",
"resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.7.tgz",
"integrity": "sha512-xkawYMJeoNYGhT+SpSH3c2qf6HpGHQ/duDmrseVHBpVCrXAiGnliXGSCC4jyMGgZQ05GytsZ12p0nUo7s6lSSw==",
"requires": {
"nullthrows": "^1.0.0",
"vscode-languageserver-types": "^3.17.1"
}
},
"hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
@@ -14357,6 +14435,15 @@
}
}
},
"monaco-graphql": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/monaco-graphql/-/monaco-graphql-1.3.0.tgz",
"integrity": "sha512-Ilz7kUKYfO5KGwandjR+eQbN+fj1fQY9OECIQlzgeWOkLXMZqXTsxf01iQfVw08mMsBz7qa6fZKsESouDcBN2g==",
"requires": {
"graphql-language-service": "^5.1.7",
"picomatch-browser": "^2.2.6"
}
},
"monaco-languageclient": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-6.0.3.tgz",
@@ -14524,6 +14611,11 @@
"boolbase": "^1.0.0"
}
},
"nullthrows": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -14794,6 +14886,11 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
"picomatch-browser": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/picomatch-browser/-/picomatch-browser-2.2.6.tgz",
"integrity": "sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w=="
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -15183,8 +15280,7 @@
"prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="
},
"prettier-plugin-svelte": {
"version": "2.10.1",
@@ -16641,6 +16737,13 @@
"rollup": "^3.25.2"
}
},
"vite-plugin-monaco-editor": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
"integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
"dev": true,
"requires": {}
},
"vitefu": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz",
@@ -16766,9 +16869,9 @@
}
},
"windmill-parser-wasm": {
"version": "1.138.1",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.138.1.tgz",
"integrity": "sha512-KGbiKKk7i8xRV+kcGXNRChIVXRQRAQ6eAGVJo7c2qjq+Sft3cGteWbkgeh8gjU5+KdmnP866jGwsRtw+zW2+5A=="
"version": "1.141.0",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.141.0.tgz",
"integrity": "sha512-2U4wAyKOMa7YTcWGUSCCqhNEfI+dVF9JvlXzPneOUO9nD7zm5uXRB+xaJLHKtYB5gmzLz1cQElp/FpbJaLLZsw=="
},
"wordwrap": {
"version": "1.0.0",
+4 -1
View File
@@ -68,6 +68,7 @@
"tslib": "^2.6.0",
"typescript": "^5.1.3",
"vite": "^4.4.7",
"vite-plugin-monaco-editor": "^1.1.0",
"yootils": "^0.3.1"
},
"type": "module",
@@ -89,9 +90,11 @@
"diff": "^5.1.0",
"esm-env": "^1.0.0",
"fast-equals": "^5.0.1",
"graphql": "^16.7.1",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"lucide-svelte": "^0.246.0",
"monaco-graphql": "^1.3.0",
"monaco-languageclient": "~6.0.3",
"openai": "^4.0.0-beta.4",
"quill": "^1.3.7",
@@ -104,7 +107,7 @@
"svelte-timezone-picker": "^2.0.3",
"tailwind-merge": "^1.13.2",
"vscode-ws-jsonrpc": "3.0.0",
"windmill-parser-wasm": "^1.138.1",
"windmill-parser-wasm": "^1.141.0",
"y-monaco": "^0.1.4",
"y-websocket": "^1.5.0",
"yjs": "^13.6.7"
@@ -1,6 +1,6 @@
<script lang="ts">
import { JobService, Preview } from '$lib/gen'
import { dbSchema, dbSchemaPublicOnly, workspaceStore, type DBSchema } from '$lib/stores'
import { dbSchema, workspaceStore, type DBSchema, type GraphqlSchema } from '$lib/stores'
import { onDestroy } from 'svelte'
import Button from './common/button/Button.svelte'
import Drawer from './common/drawer/Drawer.svelte'
@@ -9,6 +9,8 @@
import { tryEvery } from '$lib/utils'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import { buildClientSchema, printSchema } from 'graphql'
import GraphqlSchemaViewer from './GraphqlSchemaViewer.svelte'
export let resourceType: string | undefined
export let resourcePath: String | undefined = undefined
@@ -104,6 +106,27 @@ export async function main(args: any) {
}, {});
}
return data;
}`,
graphql: `import { getIntrospectionQuery } from "npm:graphql@16.7.1";
export async function main(args: any) {
const headers: { [key: string]: string } = {
"Content-Type": "application/json",
};
if (args.bearer_token) {
headers["authorization"] = "Bearer " + args.bearer_token;
}
const response = await fetch(args.base_url, {
method: "POST",
headers,
body: JSON.stringify({
query: getIntrospectionQuery(),
}),
});
if (!response.ok) {
throw new Error("Could not query schema");
}
const schema = (await response.json()).data;
return schema;
}`
}
@@ -131,7 +154,23 @@ export async function main(args: any) {
if (!testResult.success) {
console.error(testResult.result?.['error']?.['message'])
} else {
dbSchema.set(testResult.result)
if (resourceType === 'postgresql') {
dbSchema.set({
lang: 'postgresql',
schema: testResult.result,
publicOnly: true
})
} else if (resourceType === 'mysql') {
dbSchema.set({
lang: 'mysql',
schema: testResult.result
})
} else if (resourceType === 'graphql') {
dbSchema.set({
lang: 'graphql',
schema: testResult.result
})
}
}
},
timeoutCode: async () => {
@@ -153,25 +192,24 @@ export async function main(args: any) {
})
}
function formatSchema(
schema: DBSchema,
resourceType: string | undefined,
dbSchemaPublicOnly: boolean
) {
if (resourceType === 'postgresql' && dbSchemaPublicOnly) {
return schema.public || schema
} else if (resourceType === 'mysql' && Object.keys(schema).length === 1) {
return schema[Object.keys(schema)[0]]
function formatSchema(dbSchema: DBSchema) {
if (dbSchema.lang === 'postgresql' && dbSchema.publicOnly) {
return dbSchema.schema.public || dbSchema
} else if (dbSchema.lang === 'mysql' && Object.keys(dbSchema.schema).length === 1) {
return dbSchema.schema[Object.keys(dbSchema.schema)[0]]
} else {
return schema
return dbSchema.schema
}
}
$: resourcePath && ['postgresql', 'mysql'].includes(resourceType || '') && getSchema()
function formatGraphqlSchema(dbSchema: GraphqlSchema) {
return printSchema(buildClientSchema(dbSchema.schema))
}
$: resourcePath && ['postgresql', 'mysql', 'graphql'].includes(resourceType || '') && getSchema()
function clearSchema() {
dbSchema.set(undefined)
dbSchemaPublicOnly.set(true)
}
$: !resourcePath && $dbSchema && clearSchema()
@@ -192,13 +230,17 @@ export async function main(args: any) {
</Button>
<Drawer bind:this={drawer} size="800px">
<DrawerContent title="DB Schema Explorer" on:close={drawer.closeDrawer}>
{#if resourceType === 'postgresql'}
<ToggleButtonGroup class="mb-4" bind:selected={$dbSchemaPublicOnly}>
{#if $dbSchema.lang === 'postgresql'}
<ToggleButtonGroup class="mb-4" bind:selected={$dbSchema.publicOnly}>
<ToggleButton value={true} label="Public" />
<ToggleButton value={false} label="All" />
</ToggleButtonGroup>
{/if}
<ObjectViewer json={formatSchema($dbSchema, resourceType, $dbSchemaPublicOnly)} pureViewer />
{#if $dbSchema.lang === 'graphql'}
<GraphqlSchemaViewer code={formatGraphqlSchema($dbSchema)} class="h-full" />
{:else}
<ObjectViewer json={formatSchema($dbSchema)} pureViewer />
{/if}
</DrawerContent>
</Drawer>
{/if}
+93 -77
View File
@@ -21,6 +21,7 @@
import 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution'
import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'
import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution'
import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution'
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'
import { MonacoLanguageClient, initServices } from 'monaco-languageclient'
import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'
@@ -41,6 +42,8 @@
import { workspaceStore } from '$lib/stores'
import { UserService } from '$lib/gen'
import type { Text } from 'yjs'
import { initializeMode } from 'monaco-graphql/esm/initializeMode'
import type { MonacoGraphQLAPI } from 'monaco-graphql/esm/api'
let divEl: HTMLDivElement | null = null
let editor: meditor.IStandaloneCodeEditor
@@ -96,6 +99,7 @@
let nbWsAttempt = 0
let disposeMethod: () => void | undefined
const dispatch = createEventDispatcher()
let graphqlService: MonacoGraphQLAPI | undefined = undefined
const uri =
lang == 'typescript'
@@ -196,105 +200,116 @@
let command: Disposable | undefined = undefined
let dbSchemaCompletor: Disposable | undefined = undefined
$: $dbSchema && addDBSchemaCompletions()
$: !$dbSchema && dbSchemaCompletor && dbSchemaCompletor.dispose()
let sqlSchemaCompletor: Disposable | undefined = undefined
$: $dbSchema && ['sql', 'graphql'].includes(lang) && addDBSchemaCompletions()
$: (!$dbSchema || lang !== 'sql') && sqlSchemaCompletor && sqlSchemaCompletor.dispose()
$: (!$dbSchema || lang !== 'graphql') && graphqlService && graphqlService.setSchemaConfig([])
function addDBSchemaCompletions() {
if (dbSchemaCompletor) {
dbSchemaCompletor.dispose()
const { lang: schemaLang, schema } = $dbSchema || {}
if (!schemaLang || !schema) {
return
}
dbSchemaCompletor = languages.registerCompletionItemProvider('sql', {
triggerCharacters: ['.', ' ', '('],
provideCompletionItems: function (model, position) {
const textUntilPosition = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
})
const word = model.getWordUntilPosition(position)
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
if (schemaLang === 'graphql') {
graphqlService ||= initializeMode()
graphqlService?.setSchemaConfig([
{
uri: 'my-schema.graphql',
introspectionJSON: schema
}
if (!$dbSchema) {
return { suggestions: [] }
}
let suggestions: languages.CompletionItem[] = []
const noneMatch = textUntilPosition.match(/(?:add|create table)\s/i)
if (noneMatch) {
return {
suggestions
}
}
for (const schemaKey in $dbSchema) {
suggestions.push({
label: schemaKey,
detail: 'schema',
kind: languages.CompletionItemKind.Function,
insertText: schemaKey,
range: range,
sortText: 'z'
])
} else if (schemaLang === 'mysql' || schemaLang === 'postgresql') {
if (sqlSchemaCompletor) {
sqlSchemaCompletor.dispose()
}
sqlSchemaCompletor = languages.registerCompletionItemProvider('sql', {
triggerCharacters: ['.', ' ', '('],
provideCompletionItems: function (model, position) {
const textUntilPosition = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
})
for (const tableKey in $dbSchema[schemaKey]) {
const word = model.getWordUntilPosition(position)
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
}
let suggestions: languages.CompletionItem[] = []
const noneMatch = textUntilPosition.match(/(?:add|create table)\s/i)
if (noneMatch) {
return {
suggestions
}
}
for (const schemaKey in schema) {
suggestions.push({
label: tableKey,
detail: `table (${schemaKey})`,
label: schemaKey,
detail: 'schema',
kind: languages.CompletionItemKind.Function,
insertText: tableKey,
insertText: schemaKey,
range: range,
sortText: 'y'
sortText: 'z'
})
const noColsMatch = textUntilPosition.match(
/(?:from|insert into|update|table)\s(?![\s\S]*(\b(where|order by|group by|values|set|column)\b|\())/i
)
for (const tableKey in schema[schemaKey]) {
suggestions.push({
label: tableKey,
detail: `table (${schemaKey})`,
kind: languages.CompletionItemKind.Function,
insertText: tableKey,
range: range,
sortText: 'y'
})
if (!noColsMatch) {
for (const columnKey in $dbSchema[schemaKey][tableKey]) {
suggestions.push({
label: columnKey,
detail: `${$dbSchema[schemaKey][tableKey][columnKey]['type']} (${schemaKey}.${tableKey})`,
kind: languages.CompletionItemKind.Function,
insertText: columnKey,
range: range,
sortText: 'x'
})
const noColsMatch = textUntilPosition.match(
/(?:from|insert into|update|table)\s(?![\s\S]*(\b(where|order by|group by|values|set|column)\b|\())/i
)
if (!noColsMatch) {
for (const columnKey in schema[schemaKey][tableKey]) {
suggestions.push({
label: columnKey,
detail: `${schema[schemaKey][tableKey][columnKey]['type']} (${schemaKey}.${tableKey})`,
kind: languages.CompletionItemKind.Function,
insertText: columnKey,
range: range,
sortText: 'x'
})
}
}
if (textUntilPosition.match(new RegExp(`${tableKey}.$`, 'i'))) {
suggestions = suggestions.filter((x) =>
x.detail?.includes(`(${schemaKey}.${tableKey})`)
)
return {
suggestions
}
}
}
if (textUntilPosition.match(new RegExp(`${tableKey}.$`, 'i'))) {
suggestions = suggestions.filter((x) =>
x.detail?.includes(`(${schemaKey}.${tableKey})`)
)
if (textUntilPosition.match(new RegExp(`${schemaKey}.$`, 'i'))) {
suggestions = suggestions.filter((x) => x.detail === `table (${schemaKey})`)
return {
suggestions
}
}
}
if (textUntilPosition.match(new RegExp(`${schemaKey}.$`, 'i'))) {
suggestions = suggestions.filter((x) => x.detail === `table (${schemaKey})`)
return {
suggestions
}
return {
suggestions
}
}
return {
suggestions
}
}
})
})
}
}
const outputChannel = {
@@ -815,6 +830,7 @@
onDestroy(() => {
disposeMethod && disposeMethod()
websocketInterval && clearInterval(websocketInterval)
sqlSchemaCompletor && sqlSchemaCompletor.dispose()
})
</script>
@@ -0,0 +1,40 @@
<script lang="ts">
import { BROWSER } from 'esm-env'
import 'monaco-editor/esm/vs/editor/edcore.main'
import { editor as meditor } from 'monaco-editor/esm/vs/editor/editor.api'
import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution'
import { onDestroy, onMount } from 'svelte'
let divEl: HTMLDivElement | null = null
let editor: meditor.IStandaloneCodeEditor
export let code: string = ''
async function loadMonaco() {
editor = meditor.create(divEl as HTMLDivElement, {
value: code,
language: 'graphql',
readOnly: true,
automaticLayout: true,
scrollBeyondLastLine: false,
lineNumbers: 'off',
minimap: { enabled: false }
})
}
onMount(async () => {
if (BROWSER) {
await loadMonaco()
}
})
onDestroy(() => {
try {
editor && editor.dispose()
} catch (err) {}
})
</script>
<div bind:this={divEl} class="{$$props.class ?? ''} editor" />
@@ -9,7 +9,7 @@
import AppConnect from './AppConnect.svelte'
import { Button } from './common'
import ResourceEditor from './ResourceEditor.svelte'
import DbSchemaExplorer from './DBSchemaExplorer.svelte'
import DBSchemaExplorer from './DBSchemaExplorer.svelte'
import DarkModeObserver from './DarkModeObserver.svelte'
const dispatch = createEventDispatcher()
@@ -143,5 +143,5 @@
<Icon scale={0.8} data={faRotateRight} />
</Button>
</div>
<DbSchemaExplorer {resourceType} resourcePath={value} />
<DBSchemaExplorer {resourceType} resourcePath={value} />
</div>
@@ -4,6 +4,7 @@
import { onDestroy, tick } from 'svelte'
import type { Preview } from '$lib/gen/models/Preview'
import { createEventDispatcher } from 'svelte'
import type { SupportedLanguage } from '$lib/common'
export let isLoading = false
export let job: { completed: boolean; result: any; id: string } | undefined = undefined
@@ -84,7 +85,7 @@
export async function runPreview(
path: string | undefined,
code: string,
lang: 'deno' | 'go' | 'python3' | 'bash' | 'nativets',
lang: SupportedLanguage,
args: Record<string, any>,
tag: string | undefined
): Promise<string> {
@@ -71,6 +71,7 @@
langs.push(['MySQL', Script.language.MYSQL])
langs.push(['BigQuery', Script.language.BIGQUERY])
langs.push(['Snowflake', Script.language.SNOWFLAKE])
langs.push(['GraphQL', Script.language.GRAPHQL])
if (SCRIPT_SHOW_GO) {
langs.push(['Go', Script.language.GO])
}
@@ -15,6 +15,7 @@
import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'
import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'
import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution'
import 'monaco-editor/esm/vs/language/json/monaco.contribution'
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'
@@ -53,6 +53,10 @@ export async function main(database: any) {
snowflake: {
code: `select 1`,
lang: 'snowflake'
},
graphql: {
code: '{ __typename }',
lang: 'graphql'
}
}
@@ -103,6 +103,7 @@
['mysql', 'MySQL'],
['bigquery', 'BigQuery'],
['snowflake', 'Snowflake'],
['graphql', 'GraphQL'],
['bun', 'TypeScript (Bun)']
] as [Script.language, string][]
</script>
@@ -74,6 +74,13 @@ export function buildWorkerDefinition(
return buildWorker(workerOverrideGlobals, label, 'cssWorker', 'CSS Worker')
case 'json':
return buildWorker(workerOverrideGlobals, label, 'jsonWorker', 'JSON Worker')
case 'graphql':
const workerFilename = `graphql.worker.bundle.js`
const workerPathLocal = `${workerOverrideGlobals.workerPath}/${workerFilename}`
const workerUrl = new URL(workerPathLocal, workerOverrideGlobals.basePath)
return new Worker(workerUrl.href, {
name: label
})
default:
return buildWorker(workerOverrideGlobals, label, 'editorWorker', 'Editor Worker')
}
@@ -6,7 +6,7 @@
import { sendUserToast } from '$lib/toast'
import type Editor from '../Editor.svelte'
import { faCheck, faClose, faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons'
import { dbSchema, dbSchemaPublicOnly, existsOpenaiResourcePath } from '$lib/stores'
import { dbSchema, existsOpenaiResourcePath } from '$lib/stores'
import type DiffEditor from '../DiffEditor.svelte'
import { scriptLangToEditorLang } from '$lib/scripts'
import Popover from '../Popover.svelte'
@@ -38,8 +38,7 @@
language: lang,
code: editor?.getCode() || '',
error,
dbSchema: $dbSchema,
dbSchemaPublicOnly: $dbSchemaPublicOnly
dbSchema: $dbSchema
})
generatedCode = result.code
explanation = result.explanation
@@ -8,7 +8,7 @@
import { faCheck, faClose, faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons'
import Popup from '../common/popup/Popup.svelte'
import { Icon } from 'svelte-awesome'
import { dbSchema, dbSchemaPublicOnly, existsOpenaiResourcePath } from '$lib/stores'
import { dbSchema, existsOpenaiResourcePath } from '$lib/stores'
import type DiffEditor from '../DiffEditor.svelte'
import { scriptLangToEditorLang } from '$lib/scripts'
import type { Selection } from 'monaco-editor/esm/vs/editor/editor.api'
@@ -46,16 +46,14 @@
language: lang,
description: funcDesc,
selectedCode,
dbSchema: $dbSchema,
dbSchemaPublicOnly: $dbSchemaPublicOnly
dbSchema: $dbSchema
})
generatedCode = originalCode.replace(selectedCode, result.code + '\n')
} else {
const result = await generateScript({
language: lang,
description: funcDesc,
dbSchema: $dbSchema,
dbSchemaPublicOnly: $dbSchemaPublicOnly
dbSchema: $dbSchema
})
generatedCode = result.code
}
@@ -229,8 +227,8 @@
In order to better generate the script, we pass the selected DB schema to GPT-4.
</Tooltip>
</p>
{#if lang === 'postgresql'}
<ToggleButtonGroup class="w-auto shrink-0" bind:selected={$dbSchemaPublicOnly}>
{#if $dbSchema.lang === 'postgresql'}
<ToggleButtonGroup class="w-auto shrink-0" bind:selected={$dbSchema.publicOnly}>
<ToggleButton value={true} label="Public schema" />
<ToggleButton value={false} label="All schemas" />
</ToggleButtonGroup>
+13 -9
View File
@@ -41,7 +41,6 @@ workspaceStore.subscribe(async (value) => {
interface BaseOptions {
language: Script.language | 'frontend'
dbSchema: DBSchema | undefined
dbSchemaPublicOnly: boolean
}
interface ScriptGenerationOptions extends BaseOptions {
@@ -71,19 +70,24 @@ async function addResourceTypes(scriptOptions: BaseOptions, workspace: string, p
}
function addDBSChema(scriptOptions: BaseOptions, prompt: string) {
if (['mysql', 'postgresql'].includes(scriptOptions.language) && scriptOptions.dbSchema) {
const { dbSchema, dbSchemaPublicOnly, language } = scriptOptions
const { dbSchema, language } = scriptOptions
if (
dbSchema &&
['postgresql', 'mysql'].includes(language) && // make sure we are using a SQL language
(dbSchema.lang === 'postgresql' || dbSchema.lang === 'mysql') // make sure we have a SQL schema
) {
const { schema, lang } = dbSchema
let smallerSchema: {
[schemaKey: string]: {
[tableKey: string]: Array<[string, string, boolean, string?]>
}
} = {}
for (const schemaKey in dbSchema) {
for (const schemaKey in schema) {
smallerSchema[schemaKey] = {}
for (const tableKey in dbSchema[schemaKey]) {
for (const tableKey in schema[schemaKey]) {
smallerSchema[schemaKey][tableKey] = []
for (const colKey in dbSchema[schemaKey][tableKey]) {
const col = dbSchema[schemaKey][tableKey][colKey]
for (const colKey in schema[schemaKey][tableKey]) {
const col = schema[schemaKey][tableKey][colKey]
const p: [string, string, boolean, string?] = [colKey, col.type, col.required]
if (col.default) {
p.push(col.default)
@@ -98,9 +102,9 @@ function addDBSChema(scriptOptions: BaseOptions, prompt: string) {
| {
[tableKey: string]: Array<[string, string, boolean, string?]>
} = smallerSchema
if (language === 'postgresql' && dbSchemaPublicOnly) {
if (lang === 'postgresql' && dbSchema.publicOnly) {
finalSchema = smallerSchema.public || smallerSchema
} else if (language === 'mysql' && Object.keys(smallerSchema).length === 1) {
} else if (lang === 'mysql' && Object.keys(smallerSchema).length === 1) {
finalSchema = smallerSchema[Object.keys(smallerSchema)[0]]
}
prompt =
@@ -12,6 +12,7 @@
import PowershellIcon from '$lib/components/icons/PowershellIcon.svelte'
import BigQueryIcon from '$lib/components/icons/BigQueryIcon.svelte'
import SnowflakeIcon from '$lib/components/icons/SnowflakeIcon.svelte'
import GraphqlIcon from '$lib/components/icons/GraphqlIcon.svelte'
export let lang:
| SupportedLanguage
@@ -32,8 +33,10 @@
[Script.language.GO]: 'Go',
[Script.language.BASH]: 'Bash',
[Script.language.NATIVETS]: 'HTTP',
// [Script.language.GRAPHQL]: 'HTTP',
[Script.language.POSTGRESQL]: 'Postgresql'
[Script.language.GRAPHQL]: 'GraphQL',
[Script.language.POSTGRESQL]: 'Postgresql',
[Script.language.BIGQUERY]: 'BigQuery',
[Script.language.SNOWFLAKE]: 'Snowflake'
}
const langToComponent: Record<
@@ -56,7 +59,7 @@
powershell: PowershellIcon,
postgresql: PostgresIcon,
nativets: RestIcon,
graphql: RestIcon
graphql: GraphqlIcon
}
</script>
@@ -188,6 +188,18 @@
}}
/>
<FlowScriptPicker
label="GraphQL"
lang={Script.language.GRAPHQL}
on:click={() => {
dispatch('new', {
language: RawScript.language.GRAPHQL,
kind,
subkind: 'flow'
})
}}
/>
<FlowScriptPicker
label={`Docker`}
lang="docker"
@@ -0,0 +1,138 @@
<script lang="ts">
export let height = '24px'
export let width = '24px'
</script>
<svg
version="1.1"
id="GraphQL_Logo"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 400 400"
enable-background="new 0 0 400 400"
xml:space="preserve"
{width}
{height}
>
<g>
<g>
<g>
<rect
x="122"
y="-0.4"
transform="matrix(-0.866 -0.5 0.5 -0.866 163.3196 363.3136)"
fill="#E535AB"
width="16.6"
height="320.3"
/>
</g>
</g>
<g>
<g>
<rect x="39.8" y="272.2" fill="#E535AB" width="320.3" height="16.6" />
</g>
</g>
<g>
<g>
<rect
x="37.9"
y="312.2"
transform="matrix(-0.866 -0.5 0.5 -0.866 83.0693 663.3409)"
fill="#E535AB"
width="185"
height="16.6"
/>
</g>
</g>
<g>
<g>
<rect
x="177.1"
y="71.1"
transform="matrix(-0.866 -0.5 0.5 -0.866 463.3409 283.0693)"
fill="#E535AB"
width="185"
height="16.6"
/>
</g>
</g>
<g>
<g>
<rect
x="122.1"
y="-13"
transform="matrix(-0.5 -0.866 0.866 -0.5 126.7903 232.1221)"
fill="#E535AB"
width="16.6"
height="185"
/>
</g>
</g>
<g>
<g>
<rect
x="109.6"
y="151.6"
transform="matrix(-0.5 -0.866 0.866 -0.5 266.0828 473.3766)"
fill="#E535AB"
width="320.3"
height="16.6"
/>
</g>
</g>
<g>
<g>
<rect x="52.5" y="107.5" fill="#E535AB" width="16.6" height="185" />
</g>
</g>
<g>
<g>
<rect x="330.9" y="107.5" fill="#E535AB" width="16.6" height="185" />
</g>
</g>
<g>
<g>
<rect
x="262.4"
y="240.1"
transform="matrix(-0.5 -0.866 0.866 -0.5 126.7953 714.2875)"
fill="#E535AB"
width="14.5"
height="160.9"
/>
</g>
</g>
<path
fill="#E535AB"
d="M369.5,297.9c-9.6,16.7-31,22.4-47.7,12.8c-16.7-9.6-22.4-31-12.8-47.7c9.6-16.7,31-22.4,47.7-12.8
C373.5,259.9,379.2,281.2,369.5,297.9"
/>
<path
fill="#E535AB"
d="M90.9,137c-9.6,16.7-31,22.4-47.7,12.8c-16.7-9.6-22.4-31-12.8-47.7c9.6-16.7,31-22.4,47.7-12.8
C94.8,99,100.5,120.3,90.9,137"
/>
<path
fill="#E535AB"
d="M30.5,297.9c-9.6-16.7-3.9-38,12.8-47.7c16.7-9.6,38-3.9,47.7,12.8c9.6,16.7,3.9,38-12.8,47.7
C61.4,320.3,40.1,314.6,30.5,297.9"
/>
<path
fill="#E535AB"
d="M309.1,137c-9.6-16.7-3.9-38,12.8-47.7c16.7-9.6,38-3.9,47.7,12.8c9.6,16.7,3.9,38-12.8,47.7
C340.1,159.4,318.7,153.7,309.1,137"
/>
<path
fill="#E535AB"
d="M200,395.8c-19.3,0-34.9-15.6-34.9-34.9c0-19.3,15.6-34.9,34.9-34.9c19.3,0,34.9,15.6,34.9,34.9
C234.9,380.1,219.3,395.8,200,395.8"
/>
<path
fill="#E535AB"
d="M200,74c-19.3,0-34.9-15.6-34.9-34.9c0-19.3,15.6-34.9,34.9-34.9c19.3,0,34.9,15.6,34.9,34.9
C234.9,58.4,219.3,74,200,74"
/>
</g>
</svg>
+2
View File
@@ -63,6 +63,8 @@ export function langToExt(lang: string): string {
return 'ts'
case 'nativets':
return 'ts'
case 'graphql':
return 'gql'
default:
return 'unknown'
+8 -1
View File
@@ -11,7 +11,8 @@ import init, {
parse_sql,
parse_mysql,
parse_bigquery,
parse_snowflake
parse_snowflake,
parse_graphql
} from 'windmill-parser-wasm'
import wasmUrl from 'windmill-parser-wasm/windmill_parser_wasm_bg.wasm?url'
import { workspaceStore } from './stores.js'
@@ -63,6 +64,12 @@ export async function inferArgs(
{ name: 'database', typ: { resource: 'snowflake' } },
...inferedSchema.args
]
} else if (language == 'graphql') {
inferedSchema = JSON.parse(parse_graphql(code))
inferedSchema.args = [
{ name: 'database', typ: { resource: 'graphql' } },
...inferedSchema.args
]
} else if (language == 'go') {
inferedSchema = JSON.parse(parse_go(code))
} else if (language == 'bash') {
+14 -1
View File
@@ -138,7 +138,8 @@ INSERT INTO demo VALUES (?, ?)
export const BIGQUERY_INIT_CODE = `-- @name1 (string) = default arg
-- @name2 (integer)
INSERT INTO \`demodb.demo\` VALUES (@name1, @name2)
-- @name3 (string[])
INSERT INTO \`demodb.demo\` VALUES (@name1, @name2, @name3)
`
export const SNOWFLAKE_INIT_CODE = `-- ? name1 (varchar) = default arg
@@ -146,6 +147,15 @@ export const SNOWFLAKE_INIT_CODE = `-- ? name1 (varchar) = default arg
INSERT INTO demo VALUES (?, ?)
`
export const GRAPHQL_INIT_CODE = `query($name1: String, $name2: Int, $name3: [String]) {
demo(name1: $name1, name2: $name2, name3: $name3) {
name1,
name2,
name3
}
}
`
export const FETCH_INIT_CODE = `export async function main(
url: string | undefined,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' = 'GET',
@@ -270,6 +280,7 @@ const ALL_INITIAL_CODE = [
MYSQL_INIT_CODE,
BIGQUERY_INIT_CODE,
SNOWFLAKE_INIT_CODE,
GRAPHQL_INIT_CODE,
DENO_INIT_CODE_TRIGGER,
DENO_INIT_CODE_CLEAR,
PYTHON_INIT_CODE_CLEAR,
@@ -344,6 +355,8 @@ export function initialCode(
return BIGQUERY_INIT_CODE
} else if (language == 'snowflake') {
return SNOWFLAKE_INIT_CODE
} else if (language == 'graphql') {
return GRAPHQL_INIT_CODE
} else if (language == 'bun') {
if (subkind === 'flow') {
return BUN_INIT_CODE_CLEAR
+1 -1
View File
@@ -18,7 +18,7 @@ export function scriptLangToEditorLang(lang: Script.language) {
return 'sql'
} else if (lang == 'bigquery') {
return 'sql'
} else if (lang == "snowflake") {
} else if (lang == 'snowflake') {
return 'sql'
} else if (lang == 'python3') {
return 'python'
+21 -3
View File
@@ -2,6 +2,7 @@ import { BROWSER } from 'esm-env'
import { derived, type Readable, writable } from 'svelte/store'
import type { UserWorkspaceList } from '$lib/gen/models/UserWorkspaceList.js'
import type { TokenResponse } from './gen'
import type { IntrospectionQuery } from 'graphql'
export interface UserExt {
email: string
@@ -66,7 +67,8 @@ export const hubScripts = writable<
| undefined
>(undefined)
export const existsOpenaiResourcePath = writable<boolean>(false)
export type DBSchema = {
type SQLBaseSchema = {
[schemaKey: string]: {
[tableKey: string]: {
[columnKey: string]: {
@@ -78,9 +80,25 @@ export type DBSchema = {
}
}
export const dbSchema = writable<DBSchema | undefined>(undefined)
export interface MysqlSchema {
lang: 'mysql'
schema: SQLBaseSchema
}
export const dbSchemaPublicOnly = writable<boolean>(true)
export interface PostgresqlSchema {
lang: 'postgresql'
schema: SQLBaseSchema
publicOnly: boolean
}
export interface GraphqlSchema {
lang: 'graphql'
schema: IntrospectionQuery
}
export type DBSchema = MysqlSchema | PostgresqlSchema | GraphqlSchema
export const dbSchema = writable<DBSchema | undefined>(undefined)
export function switchWorkspace(workspace: string | undefined) {
localStorage.removeItem('flow')
@@ -291,7 +291,7 @@
<Badge color="blue">{job.job_kind}</Badge>
</div>
{/if}
{#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'nativets', 'bash', 'other', 'dependency'].includes(job.tag)}
{#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'graphql', 'nativets', 'bash', 'other', 'dependency'].includes(job.tag)}
<div>
<Badge color="indigo">Worker group: {job.tag}</Badge>
</div>
+15 -1
View File
@@ -2,6 +2,7 @@ import { sveltekit } from '@sveltejs/kit/vite'
import { readFileSync } from 'fs'
import { fileURLToPath } from 'url'
import ViteYaml from '@modyfi/vite-plugin-yaml'
import monacoEditorPlugin from 'vite-plugin-monaco-editor'
const file = fileURLToPath(new URL('package.json', import.meta.url))
const json = readFileSync(file, 'utf8')
@@ -32,7 +33,20 @@ const config = {
preview: {
port: 3000
},
plugins: [sveltekit(), ViteYaml()],
plugins: [
sveltekit(),
ViteYaml(),
monacoEditorPlugin.default({
publicPath: 'workers',
languageWorkers: [],
customWorkers: [
{
label: 'graphql',
entry: 'monaco-graphql/esm/graphql.worker'
}
]
})
],
define: {
__pkg__: version
},