diff --git a/backend/migrations/20251211111933_grant_create_schema_custom_instance_user.down.sql b/backend/migrations/20251211111933_grant_create_schema_custom_instance_user.down.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/migrations/20251211111933_grant_create_schema_custom_instance_user.up.sql b/backend/migrations/20251211111933_grant_create_schema_custom_instance_user.up.sql new file mode 100644 index 0000000000..e3e062fbf0 --- /dev/null +++ b/backend/migrations/20251211111933_grant_create_schema_custom_instance_user.up.sql @@ -0,0 +1,38 @@ +-- Grant CREATE privilege on all databases where custom_instance_user has CONNECT +-- This allows custom_instance_user to create schemas in databases it can already access + +DO $$ +DECLARE + db_record RECORD; + grant_command TEXT; +BEGIN + -- Find all databases where custom_instance_user has CONNECT privilege + -- We check if the datacl array contains an entry for custom_instance_user with 'c' (CONNECT) privilege + FOR db_record IN + SELECT d.datname + FROM pg_database d + WHERE d.datname NOT IN ('template0', 'template1') -- Skip template databases + AND d.datallowconn = true -- Only consider databases that allow connections + AND d.datacl IS NOT NULL -- Has ACL entries + AND EXISTS ( + SELECT 1 + FROM unnest(d.datacl) AS acl_entry + WHERE acl_entry::text LIKE 'custom_instance_user=c/%' -- 'c' is the privilege code for CONNECT + ) + LOOP + BEGIN + -- Grant CREATE privilege on the database + EXECUTE format('GRANT CREATE ON DATABASE %I TO custom_instance_user', db_record.datname); + RAISE NOTICE 'Granted CREATE on database % to custom_instance_user', db_record.datname; + EXCEPTION + WHEN others THEN + RAISE NOTICE 'Failed to grant CREATE on database %: %', db_record.datname, SQLERRM; + END; + END LOOP; + RAISE NOTICE 'Completed granting CREATE privileges to custom_instance_user on all accessible databases'; +EXCEPTION + WHEN others THEN + RAISE NOTICE 'Error in custom_instance_user CREATE privilege migration: %', SQLERRM; + -- Continue without failing the migration +END +$$; diff --git a/backend/parsers/windmill-parser-py/src/asset_parser.rs b/backend/parsers/windmill-parser-py/src/asset_parser.rs index 1a3f13c42d..ac611acf82 100644 --- a/backend/parsers/windmill-parser-py/src/asset_parser.rs +++ b/backend/parsers/windmill-parser-py/src/asset_parser.rs @@ -15,7 +15,7 @@ pub fn parse_assets(input: &str) -> anyhow::Result> { ast.into_iter() .for_each(|stmt| assets_finder.visit_stmt(stmt)); - for (kind, path) in assets_finder.var_identifiers.into_values() { + for (kind, path, _) in assets_finder.var_identifiers.into_values() { // if a db = wmill.datatable() was never used (e.g db.query(...)), // we still want to register the asset as unknown access type if asset_was_used(&assets_finder.assets, (kind, &path)) == false { @@ -28,9 +28,11 @@ pub fn parse_assets(input: &str) -> anyhow::Result> { Ok(merge_assets(assets_finder.assets)) } +type VarAssetName = String; +type VarAssetSchema = Option; struct AssetsFinder { assets: Vec, - var_identifiers: HashMap, + var_identifiers: HashMap, } impl Visitor for AssetsFinder { @@ -44,7 +46,7 @@ impl Visitor for AssetsFinder { // if a db = wmill.datatable() or similar was removed, but never used (e.g db.query(...)), // we still want to register the asset as unknown access type match removed { - Some((kind, path)) => { + Some((kind, path, _)) => { if !asset_was_used(&self.assets, (kind, &path)) { self.assets .push(ParseAssetsResult { kind, access_type: None, path }); @@ -53,11 +55,11 @@ impl Visitor for AssetsFinder { None => {} } - if let Some((kind, name)) = self.extract_asset_from_call(&node.value) { + if let Some((kind, name, schema)) = self.extract_asset_from_call(&node.value) { // Track target variable let Ok(var_name) = expr_name.id.parse::(); self.var_identifiers - .insert(var_name, (kind.clone(), name.clone())); + .insert(var_name, (kind.clone(), name.clone(), schema.clone())); } } // Continue with generic visit to catch any other assets in the expression @@ -107,7 +109,10 @@ impl Visitor for AssetsFinder { impl AssetsFinder { /// Extract asset info from calls like wmill.datatable('name'), wmill.ducklake('name'), etc. - fn extract_asset_from_call(&self, expr: &Expr) -> Option<(AssetKind, String)> { + fn extract_asset_from_call( + &self, + expr: &Expr, + ) -> Option<(AssetKind, VarAssetName, VarAssetSchema)> { let call = expr.as_call_expr()?; // Check for wmill.datatable, wmill.ducklake pattern @@ -143,10 +148,21 @@ impl AssetsFinder { } else { None } - }) - .unwrap_or_else(|| "main".to_string()); + }); + let (name, schema) = match name { + None => ("main".to_string(), None), + Some(name) => { + if let Some((name, s)) = name.split_once(':') { + let schema = Some(s.to_string()); + let name = if name.is_empty() { "main" } else { name }; + (name.to_string(), schema) + } else { + (name, None) + } + } + }; - Some((kind, name)) + Some((kind, name, schema)) } fn visit_expr_call_inner(&mut self, node: &rustpython_ast::ExprCall) -> Result<(), ()> { @@ -177,7 +193,7 @@ impl AssetsFinder { if obj_name == "wmill" { // Continue - } else if let Some((kind, ref path)) = self.var_identifiers.get(&obj_name) { + } else if let Some((kind, ref path, ref schema)) = self.var_identifiers.get(&obj_name) { if ident == "query" { let expr_name = node.args.get(0).or_else(|| { node.keywords @@ -198,7 +214,19 @@ impl AssetsFinder { // We use the SQL parser to detect if it's a read or write query match windmill_parser_sql::parse_assets(&sql) { - Ok(sql_assets) => { + Ok(mut sql_assets) => { + if let Some(schema_name) = schema { + for asset in &mut sql_assets { + if asset.kind == *kind && asset.path.starts_with(path.as_str()) { + asset.path = format!( + "{}/{}.{}", + path, + schema_name, + &asset.path[path.len() + 1..] + ); + } + } + } self.assets.extend(sql_assets); } _ => {} @@ -401,4 +429,80 @@ def g(): ]) ); } + + #[test] + fn test_py_asset_parser_datatable_with_schema() { + let input = r#" +import wmill +def main(x: int): + db = wmill.datatable('dt:public') + return db.query('SELECT * FROM friends WHERE age = $1', x).fetch() +"#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::DataTable, + path: "dt/public.friends".to_string(), + access_type: Some(R) + },]) + ); + } + + #[test] + fn test_py_asset_parser_ducklake_with_schema() { + let input = r#" +import wmill +def main(): + db = wmill.ducklake('lake1:analytics') + return db.query('SELECT * FROM metrics').fetch() +"#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::Ducklake, + path: "lake1/analytics.metrics".to_string(), + access_type: Some(R) + },]) + ); + } + + #[test] + fn test_py_asset_parser_schema_with_write() { + let input = r#" +import wmill +def main(x: int): + db = wmill.datatable('dt:public') + db.query('INSERT INTO users VALUES ($1)', x).fetch() + return db.query('SELECT * FROM users').fetch() +"#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::DataTable, + path: "dt/public.users".to_string(), + access_type: Some(RW) + },]) + ); + } + + #[test] + fn test_py_asset_parser_unused_datatable_with_schema() { + let input = r#" +import wmill +def main(): + db = wmill.datatable('dt:public') +"#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::DataTable, + path: "dt".to_string(), + access_type: None + },]) + ); + } } diff --git a/backend/parsers/windmill-parser-sql/src/asset_parser.rs b/backend/parsers/windmill-parser-sql/src/asset_parser.rs index 5ab4bb6a9f..818a06f0ca 100644 --- a/backend/parsers/windmill-parser-sql/src/asset_parser.rs +++ b/backend/parsers/windmill-parser-sql/src/asset_parser.rs @@ -58,19 +58,28 @@ impl AssetCollector { // Or when we access 'b' and we did USE a; fn get_associated_asset_from_obj_name(&self, name: &ObjectName) -> Option { let access_type = self.current_access_type_stack.last().copied(); - if name.0.len() == 1 { - let ident = name.0.first()?.as_ident()?; - if ident.quote_style.is_some() { - return None; - } - let specific_table = &ident.value; + if let Some((kind, path)) = &self.currently_used_asset { // We don't want to infer that any simple identifier refers to an asset if // we are not in a known R/W context if access_type.is_none() { return None; } - if let Some((kind, path)) = &self.currently_used_asset { + if name.0.len() == 1 || name.0.len() == 2 { + if name + .0 + .iter() + .any(|id| id.as_ident().and_then(|id| id.quote_style).is_some()) + { + return None; + } + + let specific_table = &name + .0 + .iter() + .map(|id| id.as_ident().map(|id| id.value.clone())) + .collect::>>()? + .join("."); let path = format!("{}/{}", path, specific_table); return Some(ParseAssetsResult { kind: *kind, access_type, path }); } @@ -82,8 +91,12 @@ impl AssetCollector { } let ident = name.0.first()?.as_ident()?; let (kind, path) = self.var_identifiers.get(&ident.value)?; - let path = if name.0.len() == 2 { - let specific_table = &name.0.get(1)?.as_ident()?.value; + let path = if name.0.len() == 2 || name.0.len() == 3 { + let specific_table = &name.0[1..] + .iter() + .map(|id| id.as_ident().map(|id| id.value.clone())) + .collect::>>()? + .join("."); format!("{}/{}", path, specific_table) } else { path.clone() @@ -623,4 +636,41 @@ mod tests { },]) ); } + + #[test] + fn test_sql_asset_parser_table_with_schema() { + let input = r#" + ATTACH 'ducklake' AS dl; + UPDATE dl.sch.table1 SET id = NULL; + SELECT * FROM dl.sch.table1; + "#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::Ducklake, + path: "main/sch.table1".to_string(), + access_type: Some(RW) + },]) + ); + } + + #[test] + fn test_sql_asset_parser_table_with_schema_implicit() { + let input = r#" + ATTACH 'ducklake' AS dl; + USE dl; + UPDATE sch.table1 SET id = NULL; + SELECT * FROM sch.table1; + "#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::Ducklake, + path: "main/sch.table1".to_string(), + access_type: Some(RW) + },]) + ); + } } diff --git a/backend/parsers/windmill-parser-ts/src/asset_parser.rs b/backend/parsers/windmill-parser-ts/src/asset_parser.rs index c702d4259c..426fd8f8c0 100644 --- a/backend/parsers/windmill-parser-ts/src/asset_parser.rs +++ b/backend/parsers/windmill-parser-ts/src/asset_parser.rs @@ -40,6 +40,8 @@ pub fn parse_assets(code: &str) -> anyhow::Result> { Ok(merge_assets(assets_finder.assets)) } +type VarAssetName = String; +type VarAssetSchema = Option; struct AssetsFinder { assets: Vec, @@ -49,9 +51,55 @@ struct AssetsFinder { // The goal is to remember that the identifier "sql" corresponds to the datatable "main" // so that when we see a tagged template expression with tag "sql" we know which datatable it // corresponds to. This allows us to infer if a datatable is Read or Write based on the SQL query. - var_identifiers: HashMap, + var_identifiers: HashMap, } +/// Helper function to extract wmill.datatable() or wmill.ducklake() calls, +/// Returns (AssetKind, asset_name, optional_schema_name) +fn extract_wmill_datatable_call(expr: &Expr) -> Option<(AssetKind, String, Option)> { + if let Expr::Call(call_expr) = expr { + if let Some(Expr::Member(member)) = call_expr.callee.as_expr().map(AsRef::as_ref) { + // Check if object is "wmill" + let is_wmill = matches!( + member.obj.as_ref(), + Expr::Ident(ident) if ident.sym.as_str() == "wmill" + ); + + if is_wmill { + if let MemberProp::Ident(prop) = &member.prop { + // Get the asset name from first arg, default to "main" + let asset_name = call_expr + .args + .first() + .and_then(|arg| match arg.expr.as_ref() { + Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()), + _ => None, + }) + .unwrap_or_else(|| "main".to_string()); + + let (asset_name, schema_name) = asset_name.split_once(':').map_or_else( + || (asset_name.clone(), None), + |(name, schema)| { + ( + (if name.is_empty() { "main" } else { name }).to_string(), + Some(schema.to_string()), + ) + }, + ); + + let kind = match prop.sym.as_str() { + "datatable" => Some(AssetKind::DataTable), + "ducklake" => Some(AssetKind::Ducklake), + _ => None, + }; + + return kind.map(|k| (k, asset_name, schema_name)); + } + } + } + } + None +} impl Visit for AssetsFinder { // visit_call_expr will not recurse if it detects an asset, // so this will only be called when no further context was found @@ -77,6 +125,37 @@ impl Visit for AssetsFinder { } } + fn visit_assign_expr(&mut self, node: &swc_ecma_ast::AssignExpr) { + // Handle reassignments like: sql = wmill.datatable('main') + // Extract the variable name from the left side + let var_name = match &node.left { + swc_ecma_ast::AssignTarget::Simple(simple_target) => match simple_target { + swc_ecma_ast::SimpleAssignTarget::Ident(ident_binding) => { + ident_binding.id.sym.as_str().to_string() + } + _ => { + node.visit_children_with(self); + return; + } + }, + _ => { + node.visit_children_with(self); + return; + } + }; + + // Check if right side is a wmill.datatable() or wmill.ducklake() call + if let Some((kind, asset_name, schema)) = extract_wmill_datatable_call(node.right.as_ref()) + { + self.var_identifiers + .insert(var_name, (kind, asset_name, schema)); + return; + } + + // Default: visit children + node.visit_children_with(self); + } + fn visit_block_stmt(&mut self, node: &swc_ecma_ast::BlockStmt) { // Save current state before entering the block let saved_var_identifiers = self.var_identifiers.clone(); @@ -88,7 +167,7 @@ impl Visit for AssetsFinder { if saved_var_identifiers.contains_key(var) { continue; } - let (kind, ref path) = self.var_identifiers[var]; + let (kind, ref path, _) = self.var_identifiers[var]; if asset_was_used(&self.assets, (kind, path)) { continue; } @@ -111,43 +190,12 @@ impl Visit for AssetsFinder { }; // Check if init is a call to wmill.datatable(...) or wmill.ducklake(...) + // optionally with .schema() chained if let Some(init) = &node.init { - if let Expr::Call(call_expr) = init.as_ref() { - if let Some(Expr::Member(member)) = call_expr.callee.as_expr().map(AsRef::as_ref) { - // Check if object is "wmill" - let is_wmill = matches!( - member.obj.as_ref(), - Expr::Ident(ident) if ident.sym.as_str() == "wmill" - ); - - if is_wmill { - if let MemberProp::Ident(prop) = &member.prop { - // Get the asset name from first arg, default to "main" - let asset_name = call_expr - .args - .first() - .and_then(|arg| match arg.expr.as_ref() { - Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()), - _ => None, - }) - .unwrap_or_else(|| "main".to_string()); - - match prop.sym.as_str() { - "datatable" => { - self.var_identifiers - .insert(var_name, (AssetKind::DataTable, asset_name)); - return; - } - "ducklake" => { - self.var_identifiers - .insert(var_name, (AssetKind::Ducklake, asset_name)); - return; - } - _ => {} - } - } - } - } + if let Some((kind, asset_name, schema)) = extract_wmill_datatable_call(init.as_ref()) { + self.var_identifiers + .insert(var_name, (kind, asset_name, schema)); + return; } } @@ -166,9 +214,7 @@ impl Visit for AssetsFinder { }; // Check if it's a known identifier - let (kind, asset_name) = if let Some((kind, name)) = self.var_identifiers.get(tag_name) { - (*kind, name.clone()) - } else { + let Some((kind, asset_name, schema)) = self.var_identifiers.get(tag_name) else { node.visit_children_with(self); return; }; @@ -191,7 +237,19 @@ impl Visit for AssetsFinder { // We use the SQL parser to detect if it's a read or write query match windmill_parser_sql::parse_assets(&sql) { - Ok(sql_assets) => { + Ok(mut sql_assets) => { + if let Some(schema) = schema { + for asset in &mut sql_assets { + if asset.kind == *kind && asset.path.starts_with(asset_name) { + asset.path = format!( + "{}/{}.{}", + asset_name, + schema, + &asset.path[asset_name.len() + 1..] + ); + } + } + } self.assets.extend(sql_assets); } _ => {} @@ -394,4 +452,114 @@ mod tests { ]) ); } + + #[test] + fn test_ts_asset_parser_datatable_with_schema() { + let input = r#" + import * as wmill from "windmill-client" + export async function main(x: number) { + let sql = wmill.datatable(':myschema') + return await sql`SELECT * FROM friends WHERE age = ${x}`.fetch() + } + "#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::DataTable, + path: "main/myschema.friends".to_string(), + access_type: Some(R) + },]) + ); + } + + #[test] + fn test_ts_asset_parser_schema_with_write() { + let input = r#" + import * as wmill from "windmill-client" + export async function main(x: number) { + let sql = wmill.datatable('dt:public') + await sql`INSERT INTO users VALUES (${x})`.fetch() + return await sql`SELECT * FROM users`.fetch() + } + "#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::DataTable, + path: "dt/public.users".to_string(), + access_type: Some(RW) + },]) + ); + } + + #[test] + fn test_ts_asset_parser_unused_datatable_with_schema() { + let input = r#" + import * as wmill from "windmill-client" + export async function main() { + let sql = wmill.datatable('dt:myschema') + } + "#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::DataTable, + path: "dt".to_string(), + access_type: None + },]) + ); + } + + #[test] + fn test_ts_asset_parser_reassignment() { + let input = r#" + import * as wmill from "windmill-client" + export async function main(x: number) { + let sql; + sql = wmill.datatable('dt') + return await sql`SELECT * FROM users WHERE id = ${x}`.fetch() + } + "#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ParseAssetsResult { + kind: AssetKind::DataTable, + path: "dt/users".to_string(), + access_type: Some(R) + },]) + ); + } + + #[test] + fn test_ts_asset_parser_reassignment_with_schema() { + let input = r#" + import * as wmill from "windmill-client" + export async function main(x: number) { + let sql = wmill.datatable('dt') + await sql`INSERT INTO test VALUES ('')`.fetch() + sql = wmill.datatable('dt:private') + return await sql`SELECT * FROM users WHERE id = ${x}`.fetch() + } + "#; + let s = parse_assets(input); + assert_eq!( + s.map_err(|e| e.to_string()), + Ok(vec![ + ParseAssetsResult { + kind: AssetKind::DataTable, + path: "dt/private.users".to_string(), + access_type: Some(R) + }, + ParseAssetsResult { + kind: AssetKind::DataTable, + path: "dt/test".to_string(), + access_type: Some(W) + }, + ]) + ); + } } diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index 9fa240088e..540e4d6b0f 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -798,6 +798,7 @@ async fn setup_custom_instance_pg_database_inner( "GRANT CONNECT ON DATABASE \"{dbname}\" TO custom_instance_user; GRANT USAGE ON SCHEMA public TO custom_instance_user; GRANT CREATE ON SCHEMA public TO custom_instance_user; + GRANT CREATE ON DATABASE \"{dbname}\" TO custom_instance_user; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO custom_instance_user;" )) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 60a8671512..e54d63cd8c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -79,11 +79,11 @@ "windmill-parser-wasm-java": "1.510.1", "windmill-parser-wasm-nu": "1.510.1", "windmill-parser-wasm-php": "1.574.1", - "windmill-parser-wasm-py": "1.590.0", - "windmill-parser-wasm-regex": "1.590.0", + "windmill-parser-wasm-py": "1.592.1", + "windmill-parser-wasm-regex": "1.592.1", "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.558.1", - "windmill-parser-wasm-ts": "1.590.0", + "windmill-parser-wasm-ts": "1.592.1", "windmill-parser-wasm-yaml": "1.561.0", "windmill-sql-datatype-parser-wasm": "1.512.0", "windmill-utils-internal": "^1.3.1", @@ -263,7 +263,6 @@ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -279,7 +278,6 @@ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -1861,7 +1859,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -2371,6 +2368,7 @@ "integrity": "sha512-Jer+M7DgIwT5IHfTayb4Iw/fkkxWNmC/mqn/nMh9JrbPbkxmyabfLQnhJ+JDn5HK77f84j34lubO3iqFtYAfMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@floating-ui/core": "^1.3.1", "@floating-ui/dom": "^1.4.5", @@ -2569,6 +2567,7 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -2914,6 +2913,7 @@ "integrity": "sha512-7TSvMrCdmig5TMyYDW876C5FljhA0wlGixtvASCiqUqtLfmyEEpaysXjC7GhR5mWcGRrCGF+L2Bl1eEaW1wTCA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -2991,6 +2991,7 @@ "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", @@ -3497,8 +3498,7 @@ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/ms": { "version": "2.1.0", @@ -3511,8 +3511,7 @@ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/semver": { "version": "7.7.1", @@ -3582,6 +3581,7 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -3772,6 +3772,7 @@ "integrity": "sha512-94yVpDbb+ykiT7mK6ToonGnq2GIHEQGBTZTAzGxBGQXcVNCh54YKC2/WkfaDzxy0m6Kgw05kq3FYHKHu+wRdIA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/browser": "4.0.15", "@vitest/mocker": "4.0.15", @@ -4021,6 +4022,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4074,6 +4076,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4219,7 +4222,6 @@ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4247,7 +4249,6 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4340,8 +4341,7 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -4468,6 +4468,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -4665,7 +4666,6 @@ "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "camelcase": "^6.3.0", "map-obj": "^4.1.0", @@ -4685,7 +4685,6 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -4699,7 +4698,6 @@ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -4713,7 +4711,6 @@ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -4822,6 +4819,7 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -5062,7 +5060,6 @@ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", @@ -5127,7 +5124,6 @@ "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12 || >=16" } @@ -5393,6 +5389,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -5446,6 +5443,7 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -5486,7 +5484,6 @@ "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -5500,7 +5497,6 @@ "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "decamelize": "^1.1.0", "map-obj": "^1.0.0" @@ -5518,7 +5514,6 @@ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5529,7 +5524,6 @@ "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6015,7 +6009,6 @@ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -6103,6 +6096,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6649,7 +6643,6 @@ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4.9.1" } @@ -7135,7 +7128,6 @@ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "global-prefix": "^3.0.0" }, @@ -7149,7 +7141,6 @@ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", @@ -7165,7 +7156,6 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -7215,8 +7205,7 @@ "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/gopd": { "version": "1.2.0", @@ -7252,6 +7241,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -7310,7 +7300,6 @@ "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7532,7 +7521,6 @@ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -7546,7 +7534,6 @@ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7559,8 +7546,7 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/html-tags": { "version": "3.3.1", @@ -7568,7 +7554,6 @@ "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -7652,7 +7637,6 @@ "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7673,7 +7657,6 @@ "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7745,8 +7728,7 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -7851,7 +7833,6 @@ "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7862,7 +7843,6 @@ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7961,8 +7941,7 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -7998,8 +7977,7 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-refs": { "version": "3.0.15", @@ -8112,7 +8090,6 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8691,8 +8668,7 @@ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", @@ -8770,7 +8746,6 @@ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -8821,7 +8796,6 @@ "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -9062,7 +9036,6 @@ "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/minimist": "^1.2.2", "camelcase-keys": "^7.0.0", @@ -9090,7 +9063,6 @@ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -9784,7 +9756,6 @@ "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", @@ -9863,6 +9834,7 @@ "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-api/-/monaco-vscode-editor-api-21.6.0.tgz", "integrity": "sha512-YTxKRHe9d4TvyEzWIqLpJXLyZyO4xFlLgrkgHoWBpomm6gIuwaRJJRpapBZf24oG8AhkniZSPg2iv/84M+ho6g==", "license": "MIT", + "peer": true, "dependencies": { "@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common": "21.6.0", "@codingame/monaco-vscode-api": "21.6.0" @@ -10125,7 +10097,6 @@ "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "hosted-git-info": "^4.0.1", "is-core-module": "^2.5.0", @@ -10477,7 +10448,6 @@ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -10823,6 +10793,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11011,6 +10982,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "lilconfig": "^3.0.0", "yaml": "^2.3.4" @@ -11400,8 +11372,7 @@ "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/postcss-safe-parser": { "version": "6.0.0", @@ -11576,6 +11547,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11880,7 +11852,6 @@ "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^3.0.2", @@ -11900,7 +11871,6 @@ "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "find-up": "^5.0.0", "read-pkg": "^6.0.0", @@ -11919,7 +11889,6 @@ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -11933,7 +11902,6 @@ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -11976,7 +11944,6 @@ "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "indent-string": "^5.0.0", "strip-indent": "^4.0.0" @@ -12572,7 +12539,6 @@ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -12649,7 +12615,6 @@ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -12660,8 +12625,7 @@ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true, - "license": "CC-BY-3.0", - "peer": true + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", @@ -12669,7 +12633,6 @@ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -12680,8 +12643,7 @@ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, - "license": "CC0-1.0", - "peer": true + "license": "CC0-1.0" }, "node_modules/sprintf-js": { "version": "1.0.3", @@ -12775,7 +12737,6 @@ "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12801,8 +12762,7 @@ "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/style-to-object": { "version": "0.4.4", @@ -12851,7 +12811,6 @@ "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@csstools/css-parser-algorithms": "^2.3.1", "@csstools/css-tokenizer": "^2.2.0", @@ -12934,7 +12893,6 @@ } ], "license": "MIT-0", - "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -12948,7 +12906,6 @@ "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flat-cache": "^3.2.0" }, @@ -12961,8 +12918,7 @@ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/stylelint/node_modules/postcss-selector-parser": { "version": "6.1.2", @@ -12985,7 +12941,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -13120,7 +13075,6 @@ "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -13150,6 +13104,7 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.39.12.tgz", "integrity": "sha512-CEzwxFuEycokU8K8CE/OuwVbmei+ivu2HvBGYIdASfMa1hCRSNr4RRkzNSvbAvu6h+BOig2CsZTAEY+WKvwZpA==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -13245,21 +13200,6 @@ } } }, - "node_modules/svelte-check/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/svelte-eslint-parser": { "version": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", @@ -13462,8 +13402,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/svgo": { "version": "3.3.2", @@ -13514,7 +13453,6 @@ "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -13542,6 +13480,7 @@ "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -13784,6 +13723,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -13846,7 +13786,6 @@ "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13945,6 +13884,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14159,7 +14099,6 @@ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -14214,6 +14153,7 @@ "integrity": "sha512-8wKihlF6EDF8grimwd7GPOhLkQkSIgj6Hlcp0CXhtO3HAXeUUqhgZmJmn07OF8e4PbTusMX6Yxmy1BptVRZsdw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/runtime": "0.99.0", "fdir": "^6.5.0", @@ -14326,6 +14266,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14359,6 +14300,7 @@ "integrity": "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.15", "@vitest/mocker": "4.0.15", @@ -14691,14 +14633,14 @@ "integrity": "sha512-COyid6B1RYs+bpzUCInsA4HY/WZkpDLfkQ90+AqU/TVTpzYSbAC2JCbIwy0cRElBvlhI4bQ+9Wg6hSQKMpEkpA==" }, "node_modules/windmill-parser-wasm-py": { - "version": "1.590.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.590.0.tgz", - "integrity": "sha512-8TojFdxzoRYsx+UqYuhwE/rBZqH9OBs8FWe7A+GLaA3HtKe4NDb57mKpiXFPDnEYXaSF8gs8g+IvNrZJq4qhig==" + "version": "1.592.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.592.1.tgz", + "integrity": "sha512-gt5yTczwhko7jH0UP/MrjA0NDUfUPZju6M99JtA+o8W8J8SQhps4O3gJm1V6+6k9XKosTAv9RIoyDAp1M9GJAA==" }, "node_modules/windmill-parser-wasm-regex": { - "version": "1.590.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.590.0.tgz", - "integrity": "sha512-M+KqE6x66Utjp/V8XbHHXHtjzXYpkLcd3jINi1hohhYhNru950c68psyu8X7WmXZBnv+AL0NvcOsrBDHW5q0iQ==" + "version": "1.592.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.592.1.tgz", + "integrity": "sha512-zZzPeYpUUranHgXtXkjBbYGNxys+2p7ESR/NbygYkNSBQZI9g0Lsdri8kQNx8vML9OjryhJbeRUoAZaFimyW4w==" }, "node_modules/windmill-parser-wasm-ruby": { "version": "1.526.1", @@ -14711,9 +14653,9 @@ "integrity": "sha512-21S7lm1KF8zO1187rbq14hzPHII2RdM2+D44MoAh1F6VoaScj+Puq0z5B1O/hwn/95R/a9jBlL2D8jbkXtlD1A==" }, "node_modules/windmill-parser-wasm-ts": { - "version": "1.590.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.590.0.tgz", - "integrity": "sha512-OHeIkuYd7RiC946fSzb9HnHSGJzcFbtz57LfRCnRuF/ky7rq26Csr+4uArpTmtBxiyHMVR3U22+qiNe2TjYxJQ==" + "version": "1.592.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.592.1.tgz", + "integrity": "sha512-jwXVyp718PltzcbRdWksqge9sNDPnzBgaHL8hrCgJMCVfErVHjYk6lv056LNhrgu+hXie4oB0q8A+qs8D9KBdA==" }, "node_modules/windmill-parser-wasm-yaml": { "version": "1.561.0", @@ -14864,7 +14806,6 @@ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" @@ -14879,6 +14820,7 @@ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -15082,7 +15024,6 @@ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=10" } @@ -15102,6 +15043,7 @@ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==", "license": "MIT", + "peer": true, "dependencies": { "lib0": "^0.2.99" }, @@ -15144,6 +15086,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/package.json b/frontend/package.json index 4749d821df..c354632083 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -151,11 +151,11 @@ "windmill-parser-wasm-java": "1.510.1", "windmill-parser-wasm-nu": "1.510.1", "windmill-parser-wasm-php": "1.574.1", - "windmill-parser-wasm-py": "1.590.0", - "windmill-parser-wasm-regex": "1.590.0", + "windmill-parser-wasm-py": "1.592.1", + "windmill-parser-wasm-regex": "1.592.1", "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.558.1", - "windmill-parser-wasm-ts": "1.590.0", + "windmill-parser-wasm-ts": "1.592.1", "windmill-parser-wasm-yaml": "1.561.0", "windmill-sql-datatype-parser-wasm": "1.512.0", "windmill-utils-internal": "^1.3.1", @@ -548,4 +548,4 @@ "@rollup/rollup-linux-x64-gnu": "^4.35.0", "fsevents": "^2.3.3" } -} +} \ No newline at end of file diff --git a/frontend/src/lib/components/DBManager.svelte b/frontend/src/lib/components/DBManager.svelte index ca6de47906..75d81e130a 100644 --- a/frontend/src/lib/components/DBManager.svelte +++ b/frontend/src/lib/components/DBManager.svelte @@ -1,6 +1,6 @@
(open ? filterText : (valueEntry?.label ?? getLabel({ value }) ?? '')), - (v) => { - if (open) { - filterText = v - } - } - } - placeholder={loading && !value ? 'Loading...' : value ? valueEntry?.label : placeholder} + bind:value={() => (open ? filterText : inputText), (v) => open && (filterText = v)} + placeholder={loading && !value + ? 'Loading...' + : value && !showPlaceholderOnOpen + ? inputText + : placeholder} style={containerStyle} class={twMerge( inputBaseClass, diff --git a/frontend/src/lib/components/select/SelectDropdown.svelte b/frontend/src/lib/components/select/SelectDropdown.svelte index 60847d5c84..545d6f3197 100644 --- a/frontend/src/lib/components/select/SelectDropdown.svelte +++ b/frontend/src/lib/components/select/SelectDropdown.svelte @@ -4,6 +4,7 @@ import { untrack, type Snippet } from 'svelte' import type { ProcessedItem } from './utils.svelte' import { twMerge } from 'tailwind-merge' + import { PlusIcon } from 'lucide-svelte' let { processedItems: _processedItems, @@ -169,7 +170,7 @@ )} style="height: {uiState.visible ? dropdownPos.height : 0}px;" > -
+
{@render header?.()} {#if processedItems?.length === 0}
{noItemsMsg}
@@ -190,11 +191,10 @@