diff --git a/CHANGELOG.md b/CHANGELOG.md index fc4167a..8d93c45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to QueryDen are documented here. This project adheres to [Se ## [Unreleased] +### Security +- **Dependency advisories cleared.** rustls 0.23.46, rustls-webpki 0.103.16 and webpki-roots 1.0.11 (TLS downgrade, handshake and distrusted-intermediate fixes); quinn-proto, crossbeam-epoch, quick-xml (via plist), tokio. The app's own `zip` moves 2 → 9 (symlink, permission and SUID handling in `extract()`, used for CLI-tool downloads) with only the deflate codec, dropping the bzip2/lzma C libraries. Frontend: Vite, PostCSS, nanoid and Babel patched; Vitest 3 → 4 (Tinypool RCE, dev-only); Monaco 0.55 → 0.57 (bundled DOMPurify). Docs site: in-range Astro dependency fixes. +- **Not yet fixable here:** `zip` 4.x inside `tauri-plugin-updater` (no patched 4.x; update archives are signature-verified before extraction), `rsa` in sqlx's MySQL driver (no fixed release; QueryDen only encrypts with the server's public key), `rkyv` (in the lockfile, not compiled in), and Astro ≥ 7.2.8 for the docs site (needs Node ≥ 22.23 on the build host). + ### Query console (all engines) - **Each editor tab is its own database session.** Statements used to run on whichever pooled connection was free, so `BEGIN; …; COMMIT` across runs autocommitted the middle statements and left a connection idle in transaction, and `SET`, `SET ROLE`, temp tables, prepared statements and cursors vanished between runs. Tabs now pin a dedicated connection (`plugin:sql|run`); the toolbar transaction buttons and grid edits use the same session. Idle sessions dropped by a NAT/pooler reconnect automatically with a notice. - **Rows *and* affected counts for every statement.** Execution no longer guesses SELECT-vs-execute from keywords: data-modifying CTEs, `DELETE … RETURNING`, `CALL`, `FETCH` and `SELECT fn()` all return what they produce. Empty results keep their column headers; duplicate column names (`SELECT a.id, b.id`) both survive. @@ -35,7 +39,7 @@ All notable changes to QueryDen are documented here. This project adheres to [Se - **Exports:** RFC 4180 CSV, escaped XML/HTML, quoted SQL, `IS NULL` in generated WHERE clauses, and a native **Excel `.xlsx`** export (bold frozen header, typed cells). The "Exported …" toast disappears after a moment instead of staying on screen. - **AI assistant:** the model list is fetched live from OpenAI, Anthropic, Gemini or the local server; calls go through the backend (the CSP blocked them before); the API key is sealed with AES-256-GCM instead of stored in plain text. - **Data safety:** a connection/vault/settings/history file that fails to load is never overwritten by an empty list, and every storage write is atomic (temp file + fsync + rename, created `0600`). -- **SSH:** key authentication passed the private key as the public key (always failed); `~` in key paths; Test SSH accepts hostnames; tunnel errors are reported immediately instead of a 30 s pool timeout. +- **SSH:** key authentication passed the private key as the public key (always failed); `~` in key paths; Test SSH accepts hostnames; tunnel errors are reported immediately instead of a 30 s pool timeout, and retrying right after a failed tunnel no longer gets handed the dead tunnel. - **Platform:** storage/SSH/system commands no longer block the UI thread; Windows IPC allowed in the CSP; macOS Cmd+Option shortcuts and F1 help; psql console font fallbacks; image "Open" works; app icons fill their canvas; Linux releases build on Ubuntu 22.04 (glibc 2.35) and `.deb` installs get their own updater entry. ### Added diff --git a/package-lock.json b/package-lock.json index 23e797e..0acb6a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "queryden", - "version": "1.0.23", + "version": "1.0.25", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "queryden", - "version": "1.0.23", + "version": "1.0.25", "license": "MIT", "dependencies": { "@glideapps/glide-data-grid": "^6.0.0", @@ -24,7 +24,7 @@ "clsx": "^2.1.1", "dagre": "^0.8.5", "lucide-react": "^0.469.0", - "monaco-editor": "^0.55.0", + "monaco-editor": "^0.57.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.6.0", @@ -45,16 +45,16 @@ "tailwindcss": "^4.0.0", "typescript": "~5.8.3", "vite": "^6.0.0", - "vitest": "^3.2.4" + "vitest": "^4.1.12" } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -63,29 +63,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -102,13 +102,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -118,13 +118,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -134,36 +134,36 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -182,52 +182,52 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz", + "integrity": "sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.11.tgz", + "integrity": "sha512-OZ6+oaK7GMyg3IfCpj8JOmn8n+w2rJbDT0LLfVDTLb1ZFeKDh3AiNELgCMUw175IJHSkLl74hG0M0VLsvx+gag==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.11" }, "bin": { "parser": "bin/babel-parser.js" @@ -326,31 +326,31 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -358,13 +358,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz", + "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.11", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1889,6 +1889,13 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", @@ -2702,39 +2709,40 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.12.tgz", + "integrity": "sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==", "dev": true, "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@vitest/spy": "4.1.12", + "@vitest/utils": "4.1.12", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.12.tgz", + "integrity": "sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.1.12", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -2746,42 +2754,42 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.12.tgz", + "integrity": "sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.12.tgz", + "integrity": "sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.1.12", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.12.tgz", + "integrity": "sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.1.12", + "@vitest/utils": "4.1.12", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { @@ -2789,28 +2797,25 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.12.tgz", + "integrity": "sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.12.tgz", + "integrity": "sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.1.12", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2935,9 +2940,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.29", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", - "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "version": "2.11.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.33.tgz", + "integrity": "sha512-yiVKDX+qCMRKBE084lXvq9EcwFUhKZ2nU8TK1EQQuOgPNR/lmbGxXOeDdlGUqC2Zz11Cs9rk1d/8NfgBmlkt0w==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -2947,18 +2952,18 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.29.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.4.tgz", + "integrity": "sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==", "funding": [ { "type": "opencollective", @@ -2975,11 +2980,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.29", + "caniuse-lite": "^1.0.30001816", + "electron-to-chromium": "^1.5.459", + "node-releases": "^2.0.59", + "update-browserslist-db": "^1.3.4" }, "bin": { "browserslist": "cli.js" @@ -2988,20 +2993,10 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001792", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", - "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "version": "1.0.30001819", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001819.tgz", + "integrity": "sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==", "funding": [ { "type": "opencollective", @@ -3035,18 +3030,11 @@ } }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } @@ -3091,16 +3079,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/classcat": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", @@ -3296,16 +3274,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/deepmerge": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", @@ -3360,18 +3328,18 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.3.tgz", - "integrity": "sha512-VVwJidIJcp1hpg2OMXML3ZVRPYSZiq4aX7qBh83BSIpOaRDqI+qxhXjjIWnpzkOXhmp0L81lnoME1mnCc9H48A==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz", + "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "node_modules/electron-to-chromium": { - "version": "1.5.353", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", - "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", + "version": "1.5.464", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.464.tgz", + "integrity": "sha512-nRa65aFge40viPAKxMKULIY8tcXKwKboJWNn4RoKwyWXJ6SP7BnrDGKUTzc64818jIciH0PK7LSF8QzZes19GA==", "license": "ISC" }, "node_modules/enhanced-resolve": { @@ -3389,9 +3357,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, "license": "MIT" }, @@ -4072,13 +4040,6 @@ "loose-envify": "cli.js" } }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4979,12 +4940,12 @@ } }, "node_modules/monaco-editor": { - "version": "0.55.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", - "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.57.0.tgz", + "integrity": "sha512-5BkI9KGoqrNvBGUe15/QlZq3OooZ8WLg1AxTpaqHRCP3HNpzPPZKE2EDz8M7c+VRmCeUw1Brp4cx/PWm3kI/5A==", "license": "MIT", "dependencies": { - "dompurify": "3.2.7", + "dompurify": "3.4.15", "marked": "14.0.0" } }, @@ -5013,9 +4974,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -5054,10 +5015,13 @@ } }, "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", - "license": "MIT" + "version": "2.0.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.60.tgz", + "integrity": "sha512-OmO8+GM1JUZqXET720VrJCwkjUCXmCMQm0Z3UDDRbfNLHS4NEAvY60j5jiFv0tkKw2PNNlqOLhahVmwJIOLDIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -5081,6 +5045,20 @@ "node": ">=0.10.0" } }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5152,16 +5130,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5182,9 +5150,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "dev": true, "funding": [ { @@ -5202,9 +5170,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -5605,9 +5573,9 @@ "license": "ISC" }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -5651,9 +5619,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", "dev": true, "license": "MIT" }, @@ -5671,26 +5639,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -5748,11 +5696,14 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.16", @@ -5771,30 +5722,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { @@ -5941,9 +5872,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "funding": [ { "type": "opencollective", @@ -6051,9 +5982,9 @@ } }, "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.5.tgz", + "integrity": "sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg==", "dev": true, "license": "MIT", "dependencies": { @@ -6125,29 +6056,6 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6164,65 +6072,79 @@ } }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.12.tgz", + "integrity": "sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==", "dev": true, "license": "MIT", "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@vitest/expect": "4.1.12", + "@vitest/mocker": "4.1.12", + "@vitest/pretty-format": "4.1.12", + "@vitest/runner": "4.1.12", + "@vitest/snapshot": "4.1.12", + "@vitest/spy": "4.1.12", + "@vitest/utils": "4.1.12", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.12", + "@vitest/browser-preview": "4.1.12", + "@vitest/browser-webdriverio": "^4.1.11", + "@vitest/coverage-istanbul": "4.1.12", + "@vitest/coverage-v8": "4.1.12", + "@vitest/ui": "4.1.12", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { "optional": true }, "@vitest/ui": { @@ -6233,6 +6155,9 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, diff --git a/package.json b/package.json index abcded0..324cb7c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "clsx": "^2.1.1", "dagre": "^0.8.5", "lucide-react": "^0.469.0", - "monaco-editor": "^0.55.0", + "monaco-editor": "^0.57.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.6.0", @@ -68,7 +68,7 @@ "tailwindcss": "^4.0.0", "typescript": "~5.8.3", "vite": "^6.0.0", - "vitest": "^3.2.4" + "vitest": "^4.1.12" }, "overrides": { "dompurify": "^3.4.3" diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index be15775..0696e05 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" @@ -313,6 +313,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" @@ -504,25 +510,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bzip2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" -dependencies = [ - "bzip2-sys", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "cairo-rs" version = "0.18.5" @@ -597,8 +584,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -641,6 +626,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.44" @@ -696,12 +692,6 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - [[package]] name = "convert_case" version = "0.4.0" @@ -821,9 +811,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] @@ -973,12 +963,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "deflate64" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" - [[package]] name = "der" version = "0.7.10" @@ -1086,7 +1070,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1293,7 +1277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1309,11 +1293,10 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -1378,6 +1361,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1706,11 +1690,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] @@ -1720,10 +1702,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -2073,7 +2058,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.7", + "webpki-roots 1.0.11", ] [[package]] @@ -2111,7 +2096,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.57.0", + "windows-core 0.61.2", ] [[package]] @@ -2427,16 +2412,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "js-sys" version = "0.3.97" @@ -2656,27 +2631,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lzma-rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" -dependencies = [ - "byteorder", - "crc", -] - -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "mac" version = "0.1.1" @@ -2810,7 +2764,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2843,6 +2797,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -2864,7 +2830,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3302,16 +3268,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac", -] - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3552,11 +3508,11 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.9.0" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" +checksum = "327a4cb2ad49dc48adfdc9da89e22c64d488b508c67a044d71ba729e4521a285" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "indexmap 2.14.0", "quick-xml", "serde", @@ -3778,14 +3734,14 @@ dependencies = [ "tracing-subscriber", "url", "which", - "zip 2.4.2", + "zip 9.1.1", ] [[package]] name = "quick-xml" -version = "0.39.2" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" dependencies = [ "memchr", ] @@ -3812,14 +3768,15 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" dependencies = [ "bytes", - "getrandom 0.3.4", + "getrandom 0.4.2", "lru-slab", - "rand 0.9.4", + "rand 0.10.3", + "rand_pcg 0.10.2", "ring", "rustc-hash", "rustls", @@ -3883,7 +3840,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", - "rand_pcg", + "rand_pcg 0.2.1", ] [[package]] @@ -3899,12 +3856,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -3927,16 +3885,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -3957,12 +3905,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_hc" @@ -3982,6 +3927,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -4130,7 +4084,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.7", + "webpki-roots 1.0.11", ] [[package]] @@ -4301,14 +4255,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "once_cell", "ring", @@ -4358,7 +4312,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4369,9 +4323,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "ring", "rustls-pki-types", @@ -4844,7 +4798,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4897,9 +4851,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ "lock_api", ] @@ -5730,7 +5684,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5861,9 +5815,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -6154,7 +6108,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6163,6 +6117,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-path" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" + [[package]] name = "typeid" version = "1.0.3" @@ -6183,7 +6143,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6611,9 +6571,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" dependencies = [ "rustls-pki-types", ] @@ -6624,14 +6584,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.7", + "webpki-roots 1.0.11", ] [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" dependencies = [ "rustls-pki-types", ] @@ -6716,7 +6676,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -7440,15 +7400,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - [[package]] name = "yoke" version = "0.8.2" @@ -7579,20 +7530,6 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] [[package]] name = "zerotrie" @@ -7629,46 +7566,39 @@ dependencies = [ [[package]] name = "zip" -version = "2.4.2" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" dependencies = [ - "aes", "arbitrary", - "bzip2", - "constant_time_eq", "crc32fast", - "crossbeam-utils", - "deflate64", - "displaydoc", - "flate2", - "getrandom 0.3.4", - "hmac", "indexmap 2.14.0", - "lzma-rs", "memchr", - "pbkdf2", - "sha1", - "thiserror 2.0.18", - "time", - "xz2", - "zeroize", - "zopfli", - "zstd", ] [[package]] name = "zip" -version = "4.6.1" +version = "9.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +checksum = "518b847fd864440c379748ad684c449f293af68978a485a4ff16f7c901c1f7c4" dependencies = [ - "arbitrary", "crc32fast", + "flate2", + "getrandom 0.4.2", "indexmap 2.14.0", "memchr", + "nix", + "typed-path", + "windows-sys 0.61.2", + "zopfli", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.21" @@ -7687,34 +7617,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "zvariant" version = "5.10.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index bfd6d4f..abccd01 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -40,7 +40,7 @@ keyring = { version = "3" } ssh2 = "0.9" tokio = { version = "1", features = ["process", "io-util", "rt-multi-thread", "fs", "rt"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } -zip = "2" +zip = { version = "9", default-features = false, features = ["deflate"] } flate2 = "1" tar = "0.4" which = "7" diff --git a/src-tauri/src/export.rs b/src-tauri/src/export.rs index 8b49d77..3597c65 100644 --- a/src-tauri/src/export.rs +++ b/src-tauri/src/export.rs @@ -287,7 +287,7 @@ mod tests { } let mut archive = zip::ZipArchive::new(std::fs::File::open(&path).unwrap()).unwrap(); - let names: Vec = (0..archive.len()).map(|i| archive.by_index(i).unwrap().name().to_string()).collect(); + let names: Vec = archive.file_names().map(|n| n.unwrap().into_owned()).collect(); for part in ["[Content_Types].xml", "_rels/.rels", "xl/workbook.xml", "xl/_rels/workbook.xml.rels", "xl/styles.xml", "xl/worksheets/sheet1.xml"] { assert!(names.contains(&part.to_string()), "missing {part}"); } diff --git a/src-tauri/src/ssh.rs b/src-tauri/src/ssh.rs index e3726c5..ad87b9a 100644 --- a/src-tauri/src/ssh.rs +++ b/src-tauri/src/ssh.rs @@ -101,6 +101,16 @@ fn get_tunnels() -> &'static Mutex> { TUNNELS.get_or_init(|| Mutex::new(HashMap::new())) } +/// Drop `connection_id`'s registry entry only while it is still this tunnel: +/// a newer tunnel for the same connection may have replaced it. +fn unregister_tunnel(connection_id: &str, stop_signal: &Arc>) { + if let Ok(mut map) = get_tunnels().lock() { + if map.get(connection_id).is_some_and(|t| Arc::ptr_eq(&t.stop_signal, stop_signal)) { + map.remove(connection_id); + } + } +} + fn find_free_port() -> Result { let listener = TcpListener::bind("127.0.0.1:0").map_err(|e| e.to_string())?; let port = listener.local_addr().map_err(|e| e.to_string())?.port(); @@ -168,9 +178,7 @@ pub fn create_ssh_tunnel( let _ = ready_tx.send(Err(e.clone())); eprintln!("SSH tunnel error: {}", e); } - if let Ok(mut map) = get_tunnels().lock() { - map.remove(&conn_id_for_thread); - } + unregister_tunnel(&conn_id_for_thread, &stop_signal_clone); }); map.insert( @@ -190,14 +198,17 @@ pub fn create_ssh_tunnel( // 30 s later as an opaque database "pool timed out". match ready_rx.recv_timeout(Duration::from_secs(45)) { Ok(Ok(())) => Ok(tunnel_info), - Ok(Err(e)) => Err(e), + // Unregister here rather than leave it to the exiting thread: until + // then the dead entry would be handed to a retry as a live tunnel. + Ok(Err(e)) => { + unregister_tunnel(&connection_id, &stop_signal); + Err(e) + } Err(_) => { if let Ok(mut stop) = stop_signal.lock() { *stop = true; } - if let Ok(mut map) = get_tunnels().lock() { - map.remove(&connection_id); - } + unregister_tunnel(&connection_id, &stop_signal); Err(format!("SSH tunnel to {} did not come up within 45 seconds", tunnel_info.remote_host)) } } @@ -576,8 +587,28 @@ mod tests { .unwrap_err(); assert!(err.contains("Failed to connect to SSH server"), "{err}"); assert!(started.elapsed() < Duration::from_secs(10)); - // Failed tunnels don't linger in the registry. + // Failed tunnels don't linger in the registry, so a retry fails + // again instead of being handed the dead tunnel's port. assert!(get_tunnel_status("qd-test-refused".into()).unwrap().is_none()); + let retry = create_ssh_tunnel( + "qd-test-refused".into(), "127.0.0.1".into(), closed, "u".into(), + Some("p".into()), None, None, "db".into(), 5432, + ); + assert!(retry.is_err(), "retry returned a dead tunnel: {retry:?}"); + } + + #[test] + fn an_old_tunnel_never_unregisters_its_replacement() { + let old_signal = Arc::new(Mutex::new(false)); + let new_signal = Arc::new(Mutex::new(false)); + get_tunnels().lock().unwrap().insert( + "qd-test-replaced".into(), + SshTunnel { local_port: 1, remote_host: "db".into(), remote_port: 5432, stop_signal: new_signal.clone() }, + ); + unregister_tunnel("qd-test-replaced", &old_signal); + assert!(get_tunnel_status("qd-test-replaced".into()).unwrap().is_some()); + unregister_tunnel("qd-test-replaced", &new_signal); + assert!(get_tunnel_status("qd-test-replaced".into()).unwrap().is_none()); } #[test] diff --git a/src/components/editor/monacoSetup.test.ts b/src/components/editor/monacoSetup.test.ts new file mode 100644 index 0000000..acc93ef --- /dev/null +++ b/src/components/editor/monacoSetup.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from "vitest"; +import editorMainSource from "monaco-editor/editor/editor.main.js?raw"; +import registerAllSource from "monaco-editor/features/register.all.js?raw"; +import setupSource from "./monacoSetup.ts?raw"; + +// monacoSetup.ts rebuilds Monaco's `editor.main.js` without its languages. +// Monaco 0.57 moved the suggest widget and document semantic tokens out of +// the shared feature list; missing either one silently turns off completion +// or table/alias colouring. This fails when an upgrade adds a contribution +// that the slim entry doesn't import. + +/** Relative imports of an `esm/vs` file, as paths relative to `esm/vs`. */ +function importsOf(source: string, fileDir: string): string[] { + return [...source.matchAll(/^(?:import|export)\s[^'"]*?['"]([^'"]+)['"]/gm)] + .map((m) => m[1]) + .filter((spec) => spec.startsWith(".")) + .map((spec) => normalize(`${fileDir}/${spec}`)); +} + +function normalize(p: string): string { + const out: string[] = []; + for (const part of p.split("/")) { + if (part === "" || part === ".") continue; + if (part === ".." && out.length > 0 && out[out.length - 1] !== "..") out.pop(); + else out.push(part); + } + return out.join("/"); +} + +describe("slim Monaco entry", () => { + it("imports every editor contribution that editor.main.js does, minus languages", () => { + const slim = new Set( + [...setupSource.matchAll(/^import\s(?:[^'"]*from\s)?["']monaco-editor\/([^"'?]+)["']/gm)].map((m) => m[1]), + ); + expect(slim).toContain("features/register.all.js"); + expect(slim).toContain("editor/editor.api.js"); + + const covered = new Set([...slim, ...importsOf(registerAllSource, "features")]); + const missing = importsOf(editorMainSource, "editor").filter( + (p) => !p.startsWith("languages/") && !p.startsWith("../") && !p.endsWith(".css") && !covered.has(p), + ); + expect(missing).toEqual([]); + }); +}); diff --git a/src/components/editor/monacoSetup.ts b/src/components/editor/monacoSetup.ts index f0411f6..fdb0429 100644 --- a/src/components/editor/monacoSetup.ts +++ b/src/components/editor/monacoSetup.ts @@ -1,22 +1,32 @@ import { loader } from "@monaco-editor/react"; -import EditorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; +import EditorWorker from "monaco-editor/editor/editor.worker?worker"; // Slim Monaco entry: the core editor + standalone runtime, with **no** built-in -// language modes or language workers. The default `monaco-editor` entry pulls -// in ~80 basic-languages contributions plus four language workers (JSON, TS, -// CSS, HTML) that we never use — QueryDen only edits SQL. +// language modes or language workers. The default `monaco-editor` entry +// (`editor/editor.main.js`) pulls in ~80 basic-languages contributions plus +// four language workers (JSON, TS, CSS, HTML) that we never use — QueryDen +// only edits SQL. // -// Using `edcore.main.js` strips: -// • all 80+ `basic-languages/*` Monarch tokenizers -// • the JSON / TypeScript / CSS / HTML language services (and their workers) +// This is `editor.main.js` without its language imports: the shared editor +// features, the contributions `editor.main.js` adds on top of them (the +// suggest widget and document semantic tokens among them — without those, +// completion and table/alias colouring silently stop), and the API. +// `monacoSetup.test.ts` fails if a Monaco upgrade adds one we don't import. // // We then register only QueryDen's own SQL tokenizer (`sqlLanguage.ts`) for // the query editor, Compare dialog, and Definition viewer. SQL autocomplete // and hover providers are registered dynamically in QueryEditor at mount time. -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-expect-error - no .d.ts shipped for this internal path; we re-type via -// the main `monaco-editor` types below. -import * as monacoRuntime from "monaco-editor/esm/vs/editor/edcore.main.js"; +import "monaco-editor/features/register.all.js"; +import "monaco-editor/editor/browser/coreCommands.js"; +import "monaco-editor/editor/contrib/caretOperations/browser/caretOperations.js"; +import "monaco-editor/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.js"; +import "monaco-editor/editor/contrib/find/browser/findController.js"; +import "monaco-editor/editor/contrib/gotoSymbol/browser/goToCommands.js"; +import "monaco-editor/editor/contrib/gotoError/browser/markerSelectionStatus.js"; +import "monaco-editor/editor/contrib/semanticTokens/browser/documentSemanticTokens.js"; +import "monaco-editor/editor/contrib/suggest/browser/suggestController.js"; +import "monaco-editor/editor/common/standaloneStrings.js"; +import * as monacoRuntime from "monaco-editor/editor/editor.api.js"; import { registerSqlLanguage } from "./sqlLanguage"; // Borrow the namespace types from the main entry without dragging its runtime. @@ -32,8 +42,8 @@ registerSqlLanguage(monaco); // the `worker-src 'self' blob:` directive in tauri.conf.json. See #102. // // Only the base editor worker is wired here — the JSON/TS/CSS/HTML language -// workers are intentionally absent because `edcore.main.js` doesn't load -// those language services. +// workers are intentionally absent because this entry doesn't load those +// language services. self.MonacoEnvironment = { getWorker: () => new EditorWorker(), }; diff --git a/src/components/editor/sqlCompletionAdapter.test.ts b/src/components/editor/sqlCompletionAdapter.test.ts index 7c347c4..9745157 100644 --- a/src/components/editor/sqlCompletionAdapter.test.ts +++ b/src/components/editor/sqlCompletionAdapter.test.ts @@ -2,9 +2,9 @@ import { describe, it, expect, vi } from "vitest"; // Monaco's own word finder and suggest-list filter, so these tests see what // the suggest widget would show, not just what the provider returned. // @ts-expect-error - internal Monaco path, no .d.ts shipped -import { ensureValidWordDefinition, getWordAtText } from "monaco-editor/esm/vs/editor/common/core/wordHelper.js"; +import { ensureValidWordDefinition, getWordAtText } from "monaco-editor/editor/common/core/wordHelper.js"; // @ts-expect-error - internal Monaco path, no .d.ts shipped -import { fuzzyScore } from "monaco-editor/esm/vs/base/common/filters.js"; +import { fuzzyScore } from "monaco-editor/base/common/filters.js"; import { provideSqlCompletions, statementBoundsAt, type CompletionModelLike, type MonacoSuggestion } from "./sqlCompletionAdapter"; import { sqlLanguageConfiguration } from "./sqlLanguage"; import type { CompletionSchemaLike } from "./completionContext"; diff --git a/src/components/editor/sqlLanguage.test.ts b/src/components/editor/sqlLanguage.test.ts index 76d6001..e6db38c 100644 --- a/src/components/editor/sqlLanguage.test.ts +++ b/src/components/editor/sqlLanguage.test.ts @@ -2,9 +2,9 @@ import { describe, it, expect } from "vitest"; // Monaco's own Monarch compiler + lexer, run headless with stub services so // the grammar is tested exactly as the editor will execute it. // @ts-expect-error - internal Monaco path, no .d.ts shipped -import { compile } from "monaco-editor/esm/vs/editor/standalone/common/monarch/monarchCompile.js"; +import { compile } from "monaco-editor/editor/standalone/common/monarch/monarchCompile.js"; // @ts-expect-error - internal Monaco path, no .d.ts shipped -import { MonarchTokenizer } from "monaco-editor/esm/vs/editor/standalone/common/monarch/monarchLexer.js"; +import { MonarchTokenizer } from "monaco-editor/editor/standalone/common/monarch/monarchLexer.js"; import { encodeSemanticTokens, sqlMonarchLanguage } from "./sqlLanguage"; import { classifySqlSpans } from "./completionContext"; diff --git a/website/package-lock.json b/website/package-lock.json index 10587ac..d4b8968 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -2133,16 +2133,16 @@ } }, "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" }, "funding": { "url": "https://github.com/sponsors/fb55" @@ -2162,9 +2162,9 @@ } }, "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -2268,9 +2268,9 @@ } }, "node_modules/devalue": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", - "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.4.tgz", + "integrity": "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==", "license": "MIT" }, "node_modules/devlop": { @@ -2949,9 +2949,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", "license": "BSD-2-Clause" }, "node_modules/inline-style-parser": { @@ -3089,9 +3089,19 @@ } }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4208,9 +4218,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "funding": [ { "type": "github", @@ -4471,9 +4481,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "funding": [ { "type": "opencollective", @@ -4490,9 +4500,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4919,9 +4929,9 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", - "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -5029,9 +5039,9 @@ } }, "node_modules/smol-toml": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", - "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", + "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -5050,9 +5060,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5107,18 +5117,18 @@ } }, "node_modules/svgo": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", - "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz", + "integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==", "license": "MIT", "dependencies": { "commander": "^11.1.0", - "css-select": "^5.1.0", + "css-select": "^6.0.0", "css-tree": "^3.0.1", - "css-what": "^6.1.0", + "css-what": "^7.0.0", "csso": "^5.0.5", "picocolors": "^1.1.1", - "sax": "^1.5.0" + "sax": "1.6.1" }, "bin": { "svgo": "bin/svgo.js" @@ -5527,12 +5537,12 @@ } }, "node_modules/vite": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", - "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz", + "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==", "license": "MIT", "dependencies": { - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -5666,9 +5676,9 @@ } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/website/src/content/docs/ai/byo-key.mdx b/website/src/content/docs/ai/byo-key.mdx index f796d72..43d3a16 100644 --- a/website/src/content/docs/ai/byo-key.mdx +++ b/website/src/content/docs/ai/byo-key.mdx @@ -10,14 +10,14 @@ QueryDen does not run an inference server. When you use the AI features, the cal ## Configuring a provider -**Settings → AI → Add provider**. Pick one of: +Open **Settings → AI Assistant**, turn it on, and pick a provider: - **OpenAI** — paste an `sk-...` key. - **Anthropic** — paste an `sk-ant-...` key. - **Google (Gemini)** — paste an `AIza...` key. - **Local** — Ollama (`http://localhost:11434` by default) or any OpenAI-compatible server such as LM Studio (`http://localhost:1234`). No key needed. -The key is stored in `settings.json` sealed with AES-256-GCM, using the same machine-locked key as the credential vault — never in plain text. Requests to the provider are made by QueryDen's backend, so the key never passes through the web view; it leaves your machine only in the request to the provider. +The key is sealed with AES-256-GCM, using the same machine-locked key as the credential vault — never stored in plain text. Requests to the provider are made by QueryDen's backend, so the key never passes through the web view; it leaves your machine only in the request to the provider. ## Where the AI is actually used diff --git a/website/src/content/docs/editor/code-intelligence.mdx b/website/src/content/docs/editor/code-intelligence.mdx new file mode 100644 index 0000000..4cb5daf --- /dev/null +++ b/website/src/content/docs/editor/code-intelligence.mdx @@ -0,0 +1,56 @@ +--- +title: Completion and navigation +description: Context-aware autocomplete, parameter hints, hover info, Ctrl+click definitions, and DataGrip-style highlighting. +section: editor +order: 12 +updated: 2026-10-26 +--- + +import Callout from '../../../components/Callout.astro'; + +The editor reads your schema — tables, views, columns with their types, functions, user-defined types and foreign keys — and uses it everywhere: suggestions, hints, hovers and colours. It is loaded when you open a database and refreshed with **Refresh Schema** on the database's right-click menu. + +## Autocomplete + +What's offered depends on where the cursor is in the statement: + +| Where | Suggestions | +|-------|-------------| +| `SELECT ▮`, `WHERE ▮`, `ON ▮`, `SET ▮`, `ORDER BY ▮` | Columns of the tables the statement uses — even when `FROM` comes after the cursor — in table order, with their types. Then functions, then keywords. | +| `FROM ▮`, `JOIN ▮`, `UPDATE ▮`, `INTO ▮` | Tables, views and schemas. After `JOIN`, ready-made joins along foreign keys (`orders o ON o.user_id = u.id`). | +| `alias.▮` / `table.▮` | That table's columns. | +| `schema.▮` | That schema's tables, views and functions. | +| `::▮`, `CAST(x AS ▮` | Data types, including your own enums, domains and composites. | +| After a complete table reference | The clauses that can follow (`WHERE`, `JOIN`, `GROUP BY`, …). | + +Identifiers that need quoting are inserted quoted (`"createdAt"`). Choosing a function inserts `name()` and opens its parameter hints. + +**Which schema?** A tab completes against **its own** connection and database — the one shown in the tab header — even when the sidebar has another selected. A tab on a server that isn't open in the sidebar loads its schema in the background. + +## Parameter hints + +Type `my_function(` and a hint lists every overload with argument names, types, return type and the function's comment, highlighting the argument you're on. `Ctrl+Shift+Space` brings it back. Calls to routines with named parameters also show the names inline, before each argument (`p_env: 'prod'`). Built-in functions are covered for PostgreSQL, MySQL and SQLite. + +## Hover + +- A **table or view** shows its columns and types. +- A **function** shows its signatures and description. +- A **column** shows its type and table. + +## Ctrl+click definitions + +`Ctrl+Click` (macOS: `Cmd+Click`) a name to see its definition, built by the server's own deparsers — the same source `psql \d` and `pg_dump` use: + +- **Functions and procedures**: every overload's `CREATE OR REPLACE FUNCTION`, including set-returning functions called in `FROM`. +- **Views**: the view definition. +- **Tables**: exact column types (`varchar(80)`, `numeric(12,2)`, identity columns), constraints, indexes and comments. + +A schema-qualified name is looked up exactly; an unqualified one follows the `search_path`, and if it isn't there, any schema that has it. + +## Highlighting + +Colours follow DataGrip's Darcula and IntelliJ Light schemes: keywords, functions, types, strings, numbers and comments from a grammar that understands each dialect (dollar-quoted bodies, `E''` strings, nested comments, backticks, `::type`), plus colours that need context — table names, schema names, and aliases in italics. + + +Completion needs the schema: open a database in the explorer (or wait for a background load on a tab that targets another server), and use **Refresh Schema** after DDL changes. Keywords and built-in functions are always offered, even before a schema is loaded. + diff --git a/website/src/content/docs/editor/exporting-results.mdx b/website/src/content/docs/editor/exporting-results.mdx new file mode 100644 index 0000000..e96243c --- /dev/null +++ b/website/src/content/docs/editor/exporting-results.mdx @@ -0,0 +1,30 @@ +--- +title: Exporting results +description: Save a result set as Excel, CSV, JSON, XML, HTML or SQL, or turn selected rows into INSERT, UPDATE and DELETE statements. +section: editor +order: 70 +updated: 2026-10-26 +--- + +Every result grid — in a query tab and in Multi Query — has an **Export data** menu above it. Pick a format, choose where to save, and a confirmation appears briefly in the corner. + +## Formats + +| Format | Details | +|--------|---------| +| **Excel (.xlsx)** | A real Excel workbook, written natively — opens in Excel, LibreOffice and Google Sheets. Bold, frozen header row, columns sized to their content, numbers and booleans stored as numbers and booleans (large integers and high-precision decimals stay exact as text). The sheet is named after the table. | +| **CSV** | RFC 4180: values with commas, quotes or line breaks are quoted, quotes doubled, CRLF line endings. | +| **JSON** | An array of row objects, pretty-printed. | +| **XML** | One `` element per row, column names made into valid XML element names, values escaped. | +| **HTML** | A `` with escaped values — paste into a document or email. | +| **SQL INSERT** | One `INSERT` per row, with identifiers and values quoted for the connection's dialect. | + +Choose which formats appear in the menu under **Settings → Import/Export**. + +## Generate SQL from rows + +Select rows in the grid, right-click → **Generate SQL** → **SQL INSERTs**, **SQL UPDATEs** or **SQL DELETEs**. UPDATE and DELETE statements identify each row by its primary key and use `IS NULL` where a key value is NULL, so they match exactly the rows you selected. The SQL goes to the clipboard. + +## Copy + +`Ctrl+C` copies the selected cells as tab-separated text, ready to paste into a spreadsheet. diff --git a/website/src/content/docs/editor/keyboard-shortcuts.mdx b/website/src/content/docs/editor/keyboard-shortcuts.mdx index 978886c..ce63e04 100644 --- a/website/src/content/docs/editor/keyboard-shortcuts.mdx +++ b/website/src/content/docs/editor/keyboard-shortcuts.mdx @@ -3,10 +3,10 @@ title: Keyboard shortcuts description: Default keymap. Edit the JSON in Settings to customize per feature. section: editor order: 10 -updated: 2026-05-22 +updated: 2026-10-26 --- -QueryDen is keyboard-first. The defaults below are wired in `src/App.tsx` (global) and the per-feature stores. Bindings can be remapped per feature in **Settings → Keymap**. +QueryDen is keyboard-first. The global shortcuts below are wired in `src/utils/globalShortcuts.ts`; inside the editor, QueryDen adds its run and intention keys on top of Monaco's (VS Code's) standard bindings. On macOS, read `Ctrl` as `Cmd`. ## Global @@ -19,23 +19,26 @@ QueryDen is keyboard-first. The defaults below are wired in `src/App.tsx` (globa | `Ctrl+Shift+L` | Format SQL | | `Ctrl+Shift+F` | Toggle global search bar | | `Ctrl+Shift+E` | Toggle Database Explorer pane | +| `Ctrl+PageUp` / `Ctrl+PageDown` | Previous / next tab | | `Ctrl+Alt+S` | Open settings | -| `Ctrl+H` | Open help | +| `F1` or `Ctrl+H` | Open help (outside the editor — inside it, Monaco uses `F1` for its command palette and `Ctrl+H` for find & replace) | ## In the editor | Shortcut | Action | |----------|--------| -| `Ctrl+Click` on an identifier | Jump to definition | -| Hover an identifier | Live `CREATE TABLE` tooltip with columns | +| `Ctrl+Click` on an identifier | Open its definition — table, view, or function with every overload | +| Hover an identifier | Columns and types of a table, signature of a function, type of a column | | `Alt+Enter` | Intention actions (JetBrains-style refactor popup) | | `Ctrl+Space` | Trigger autocomplete | +| `Ctrl+Shift+Space` | Show parameter hints for the call under the cursor | | `Ctrl+/` | Toggle line comment | -| `Ctrl+D` | Duplicate line / add selection to next occurrence | -| `Ctrl+Y` | Delete line | -| `Ctrl+W` | Expand selection | -| `Ctrl+Shift+W` | Shrink selection | -| `Alt+Shift+Up/Down` | Move line up/down | +| `Ctrl+D` | Add selection to next occurrence (multi-cursor) | +| `Ctrl+Shift+K` | Delete line | +| `Alt+Up` / `Alt+Down` | Move line up / down | +| `Shift+Alt+Up` / `Shift+Alt+Down` | Copy line up / down | +| `Shift+Alt+Right` / `Shift+Alt+Left` | Expand / shrink selection | +| `Ctrl+F` | Find | ## In the results grid @@ -48,10 +51,10 @@ QueryDen is keyboard-first. The defaults below are wired in `src/App.tsx` (globa ## Keymap presets -The default "DataGrip" preset only lists bindings the app actually wires. DataGrip-flavored shortcuts whose handlers don't exist yet (global search, in-app console, go-to-definition keybinding, rename, structure view, tab cycling, local/clipboard history) are intentionally omitted — they'll come back as each feature lands. Previously [issue #11](https://github.com/openidle-dev/queryden/issues/11) tracked the fact that the preset advertised these as available when pressing them did nothing. +**Settings → Keymap** lists the default bindings and the "DataGrip" preset. DataGrip-flavored shortcuts whose handlers don't exist yet are intentionally omitted — they'll come back as each feature lands. Previously [issue #11](https://github.com/openidle-dev/queryden/issues/11) tracked the fact that the preset advertised these as available when pressing them did nothing. > Shortcuts that conflict with operating system behavior (such as Ctrl+W closing the window, Ctrl+\ sending SIGQUIT on Linux, or F4 being captured by macOS) are avoided. The keymap uses Ctrl+Shift+E for the Database Explorer to prevent these conflicts. ## Customizing -Open **Settings → Keymap** to remap any per-feature binding. The keymap is stored encrypted alongside your connections, so it follows your master password — not your operating system's preferences. +Edits in **Settings → Keymap** are saved to `keymaps.json` in the app data folder (plain JSON — it holds no secrets) and shown in toolbar tooltips. Applying remapped keys to the editor itself isn't wired yet; until it is, the bindings above are the ones that fire. diff --git a/website/src/content/docs/editor/local-history.mdx b/website/src/content/docs/editor/local-history.mdx index eaa8395..a0392a6 100644 --- a/website/src/content/docs/editor/local-history.mdx +++ b/website/src/content/docs/editor/local-history.mdx @@ -3,7 +3,7 @@ title: Local history description: Silent line-level snapshots of every editor tab. Locally, never synced. section: editor order: 30 -updated: 2026-05-13 +updated: 2026-10-26 --- QueryDen takes a snapshot of every editor tab every five seconds. The snapshots are stored encrypted in your vault, never leave your machine, and never expire. @@ -17,13 +17,13 @@ QueryDen takes a snapshot of every editor tab every five seconds. The snapshots ## What you do with it -`Ctrl+Alt+H` (or **Tab → History** in the toolbar) opens the local-history panel for the current tab. The panel shows every snapshot as a timestamped row. Click any row to see a **line-level diff** against the current buffer. Hit **Revert** to swap the current buffer for the snapshot. +Right-click in the editor and choose **Local History | Show History** to open the local-history panel for the current tab. The panel shows every snapshot as a timestamped row. Click any row to see a **line-level diff** against the current buffer. Hit **Revert** to swap the current buffer for the snapshot. > Reverting does not destroy the current buffer — it gets snapshotted first. So "revert" is one undoable step, not a one-way trip. ## Where it lives on disk -`~/.local/share/com.queryden.app/local-history.json` on Linux, equivalent paths on macOS and Windows. The file is encrypted alongside your other vault data — see [Vault setup](/docs/getting-started/vault-setup). +`~/.local/share/com.queryden.app/local-history.json` on Linux, equivalent paths on macOS and Windows. The file is encrypted alongside your other vault data — see [The vault](/docs/getting-started/vault-setup). ## When to use it diff --git a/website/src/content/docs/editor/psql-console.mdx b/website/src/content/docs/editor/psql-console.mdx new file mode 100644 index 0000000..3330298 --- /dev/null +++ b/website/src/content/docs/editor/psql-console.mdx @@ -0,0 +1,53 @@ +--- +title: psql console +description: Run psql commands against any PostgreSQL connection from inside QueryDen, with meta-commands, expanded display and \watch. +section: editor +order: 60 +updated: 2026-10-26 +--- + +import Callout from '../../../components/Callout.astro'; + +The psql console runs your installed `psql` against a connection, in a tab, with the connection's credentials and SSH tunnel already applied. It's there for the things only psql does — `\d`, `\l`, `\df`, expanded display, `\watch` — next to the SQL editor. + +## Open it + +Right-click a database in the Database Explorer → **Open PSQL (Console)**. It's available for PostgreSQL, Supabase and CockroachDB connections, and always opens on the server and database you clicked. + +Type a command and press `Enter` (`Shift+Enter` for a new line). `↑` / `↓` recall earlier commands. + +## Commands + +| Input | What happens | +|-------|--------------| +| Any SQL | Runs and prints a psql-style table. NULL prints empty, line breaks inside values print as `↵`. Several statements separated by `;` run one after another. | +| `\dt`, `\d table`, `\l`, `\dn`, `\df`, `\du` … | Passed to psql and printed exactly as psql prints them. | +| `\c dbname` | Switches the console to another database on the same server (after checking it can connect). | +| `\x`, `\x on`, `\x off` | Toggles expanded display for the following commands. | +| `\watch 5` | Re-runs the last query every 5 seconds until you press **Stop**. | + +Server `NOTICE` and `WARNING` messages are printed with the output. + + +The console starts a fresh `psql -c` for every command, so session state — `SET`, `BEGIN`, temporary tables — doesn't carry from one command to the next. For session work, use a regular editor tab: each one [keeps its own session](/docs/editor/statement-aware-run#each-tab-is-its-own-session). + + +## Which psql is used + +QueryDen looks for `psql` in this order and uses the first it finds: + +1. Your `PATH`. +2. The standard install folders, even when they aren't on `PATH`: + - **Windows** — `Program Files\PostgreSQL\\bin` (EDB installer), Scoop. + - **macOS** — Homebrew (`postgresql@` and keg-only `libpq`), Postgres.app, `/Library/PostgreSQL` (EDB), MacPorts. + - **Linux** — `/usr/lib/postgresql//bin` (Debian/Ubuntu), `/usr/pgsql-` (PGDG RPMs), `/usr/local/pgsql/bin`, Linuxbrew, Snap. + +When several versions are installed, the newest wins. A newer psql talks to older servers, so one recent client covers all your connections. **Settings → CLI Tools** shows which binary was found, where, and its version; press **Refresh** after installing one. + +If none is found, the console shows install instructions for your OS with a **Check Again** button — no restart needed. + +## Safe by default + +- The password is handed to psql through its environment, never on the command line where other processes could see it. +- Your `~/.psqlrc` is skipped, so personal settings like `\timing` can't change what QueryDen reads back. +- Connections time out after 15 seconds instead of hanging on an unreachable host. diff --git a/website/src/content/docs/editor/saved-queries.mdx b/website/src/content/docs/editor/saved-queries.mdx new file mode 100644 index 0000000..c13a7b8 --- /dev/null +++ b/website/src/content/docs/editor/saved-queries.mdx @@ -0,0 +1,31 @@ +--- +title: Saved queries and Multi Query +description: Keep a library of queries per connection, and run one query across many databases and servers at once. +section: editor +order: 80 +updated: 2026-10-26 +--- + +## Saving a query + +`Ctrl+S` saves the tab's SQL to your library, filed under the tab's own connection and database. Saving under a name that already exists **on the same connection** updates it; a query with the same name on another connection is a separate entry and is left alone. + +Saved queries are stored encrypted with the rest of your data — see [The vault](/docs/getting-started/vault-setup). + +## Opening saved queries + +The **Files** panel in the sidebar lists your saved queries with a search box: + +- **Click** a query to open it in a new tab — on the connection and database it was saved for. +- **▶** opens it and runs it straight away. +- **Delete** asks for confirmation first. + +## Multi Query + +The **Multi-Query** toolbar button runs one query against many targets in a single go — every tenant database, every environment, every server: + +1. Pick targets: individual databases, whole connections, or **Select All** for every database of every connection. +2. Write the query, or load one from the same **Saved Queries** list. +3. Run. Targets execute in parallel (four at a time), each through its own SSH tunnel and credentials, and each result appears under its target with its status, row count and time. + +Results can be [exported](/docs/editor/exporting-results) per target, including to Excel. diff --git a/website/src/content/docs/engines/postgres.mdx b/website/src/content/docs/engines/postgres.mdx index 83ad60c..9291da6 100644 --- a/website/src/content/docs/engines/postgres.mdx +++ b/website/src/content/docs/engines/postgres.mdx @@ -3,11 +3,13 @@ title: PostgreSQL description: Native driver via the patched tauri-plugin-sql. INTERVAL, OID, and array types supported. section: engines order: 10 -updated: 2026-05-13 +updated: 2026-10-26 --- PostgreSQL is QueryDen's first-class engine. The driver is the upstream `tauri-plugin-sql` with a local patch that adds support for several types upstream doesn't emit cleanly: `INTERVAL`, `OID`, multidimensional arrays, and a few `bytea` edge cases. +The patch also gives every editor tab its own session, so `BEGIN`/`COMMIT`, `SET`, temporary tables and prepared statements carry over between runs in that tab, and values come back exactly as `psql` prints them — `numeric` with every digit, `interval`, `money`, `regclass`, arrays, `infinity`, `timestamptz` in your local time zone. See [Statement-aware run](/docs/editor/statement-aware-run#each-tab-is-its-own-session). + ## Connection The standard PG connection fields apply: @@ -16,20 +18,21 @@ The standard PG connection fields apply: - **Port** — default `5432` - **Database** — the database name - **User / Password** — stored in the encrypted vault -- **SSL** — required / preferred / disabled. Required on managed PG (Supabase, Cloud SQL, RDS) by default. -- **SSH tunnel** — optional. Real `ssh2`-backed tunneling; no `ssh` subprocess. +- **TLS** — used automatically whenever the server offers it, as on Supabase, Cloud SQL and RDS. +- **SSH tunnel** — optional. Real `ssh2`-backed tunneling; no `ssh` subprocess. Password or private-key authentication (`~/` paths work). ## Cockroach + Supabase Neither is a separate backend in QueryDen — both speak the PostgreSQL wire protocol and connect through the same driver as Postgres. -- **CockroachDB** — supported through the PostgreSQL wire protocol. Pick the PostgreSQL engine when adding the connection; CockroachDB-specific behaviours (e.g. its dialect quirks) are handled by the server, not by a separate client driver. +- **CockroachDB** — supported through the PostgreSQL wire protocol. Pick **CockroachDB** in the connection dialog (it uses the PostgreSQL driver underneath, with CockroachDB's default port 26257); CockroachDB-specific behaviours (e.g. its dialect quirks) are handled by the server, not by a separate client driver. - **Supabase** — PostgreSQL plus a connection-string helper UI for Supabase's hosted format. The helper generates the right host/port/SSL fields from a Supabase project URL; the driver underneath is the PostgreSQL one. ## Coming soon - Full type catalog table — every PG type with how QueryDen renders it in the grid. - Logical replication quickstart. -- `psql` integration deep-dive (the version-matched embedded client). + +For psql's own commands (`\d`, `\l`, `\watch`, expanded display), use the [psql console](/docs/editor/psql-console). If a specific type or feature is missing, open an issue with a minimal reproduction. diff --git a/website/src/content/docs/getting-started/first-query.mdx b/website/src/content/docs/getting-started/first-query.mdx index 81dfe75..9fca8e0 100644 --- a/website/src/content/docs/getting-started/first-query.mdx +++ b/website/src/content/docs/getting-started/first-query.mdx @@ -3,16 +3,18 @@ title: Run your first query description: Add a connection, open a tab, run a statement. Three keystrokes. section: getting-started order: 30 -updated: 2026-05-13 +updated: 2026-10-26 --- ## 1. Add a connection -`New connection → pick engine → fill the host / port / user / database`. Optional SSH tunnel for jump-host setups. Connections are stored encrypted and locked to this machine (see [Vault setup](/docs/getting-started/vault-setup)). +`New connection → pick engine → fill the host / port / user / database`. Optional SSH tunnel for jump-host setups. Connections are stored encrypted and locked to this machine (see [The vault](/docs/getting-started/vault-setup)). + +Double-click the connection in the Database Explorer to connect and expand it, then double-click a database to open its schemas. Each connected server keeps its own tree. | Engine | Notes | |--------|-------| -| PostgreSQL | Native driver. Patched `tauri-plugin-sql` adds support for `INTERVAL`, `OID`, and array types. | +| PostgreSQL | Native driver (patched `tauri-plugin-sql`). Each tab is its own session, and values print exactly as `psql` shows them — `numeric`, `interval`, arrays, `infinity` included. | | MySQL / MariaDB | One driver. MariaDB is supported through the MySQL wire protocol — the same driver as MySQL. Standard types + JSON. | | SQLite | File-path connection. Multiple DB files per project. | | CockroachDB | Supported through the PostgreSQL wire protocol — connect with the PostgreSQL driver. No separate backend. | @@ -20,7 +22,7 @@ updated: 2026-05-13 ## 2. Open a tab -`Ctrl+T` opens a new query tab in the current connection. Tabs persist across app restarts — locally, never synced. +`Ctrl+N` opens a new query tab in the current connection. Tabs persist across app restarts — locally, never synced. ## 3. Write and run @@ -37,16 +39,18 @@ Hit **`Ctrl+Enter`** to run. QueryDen parses the buffer and runs only the **statement under your cursor** — not the whole file. If you have 30 statements in the tab, the cursor decides which one fires. This is the single most-loved shortcut in the editor. -To run several statements, select the range first, then `Ctrl+Enter`. +To run several statements, select the range first, then `Ctrl+Enter`. `Ctrl+Shift+Enter` runs every statement in the tab. ## 4. What you see next - A **results grid** appears below the editor with one row per matched record. Glide Data Grid; scrolls smoothly at 100k+ rows. -- A **status line** at the bottom: row count, execution time, whether the query plan was cached. -- If you ran `EXPLAIN ANALYZE`, the plan tree renders as a [visual viewer](/docs/editor/explain) — hot nodes highlighted, cost percentages shown, one-click "suggest index" via your configured AI provider. +- A **status line** at the bottom: row count, execution time, and whether a transaction is open in this tab. +- `RAISE NOTICE` output and other server messages under **Messages → Server output**. +- If you ran `EXPLAIN ANALYZE` (or opened the **Optimizer**), the plan tree renders as a visual viewer — hot nodes highlighted, cost percentages shown, one-click "suggest index" via your configured AI provider. ## What to learn next - [Editor keyboard shortcuts](/docs/editor/keyboard-shortcuts) — the full keymap. +- [Completion and navigation](/docs/editor/code-intelligence) — what autocomplete offers, parameter hints, Ctrl+click definitions. - [Statement-aware run](/docs/editor/statement-aware-run) — how cursor-driven execution works under the hood. - [Local history](/docs/editor/local-history) — every five seconds, every tab, locally. diff --git a/website/src/content/docs/getting-started/install.mdx b/website/src/content/docs/getting-started/install.mdx index 51a278c..39967f1 100644 --- a/website/src/content/docs/getting-started/install.mdx +++ b/website/src/content/docs/getting-started/install.mdx @@ -3,21 +3,19 @@ title: Install description: Native installers for macOS, Windows, and Linux. ~11 MB across platforms. section: getting-started order: 10 -updated: 2026-05-13 +updated: 2026-10-26 --- -QueryDen ships native installers built by GitHub Actions from the public source tree. Pick your OS: +QueryDen ships native installers built by GitHub Actions from the public source tree. Pick your OS on the [download page](/download) or the [GitHub release](https://github.com/openidle-dev/queryden/releases/latest): | Platform | File | Size | |----------|------|------| -| macOS (Apple Silicon) | `QueryDen_1.0.11_aarch64.dmg` | ~11 MB | -| Windows (x86_64) | `QueryDen_1.0.11_x64-setup.exe` | ~8 MB | -| Linux (.deb) | `QueryDen_1.0.11_amd64.deb` | ~11 MB | -| Linux (AppImage) | `QueryDen_1.0.11_amd64.AppImage` | ~84 MB | +| macOS (Intel + Apple Silicon) | `QueryDen__universal.dmg` | ~20 MB | +| Windows (x86_64) | `QueryDen__x64-setup.exe` | ~6 MB | +| Linux (.deb) | `QueryDen__amd64.deb` | ~9 MB | +| Linux (AppImage) | `QueryDen__amd64.AppImage` | ~80 MB | -The AppImage bundles its own runtime, which is why it's larger. Use the `.deb` if your distribution supports it. - -[Download page →](/download) +The macOS build is a single universal binary, so the same `.dmg` runs on Intel and Apple Silicon Macs (releases up to 1.0.23 shipped an Apple Silicon-only `_aarch64.dmg`). The AppImage bundles its own runtime, which is why it's larger; use the `.deb` if your distribution supports it. ## Verify the download @@ -25,22 +23,28 @@ Every installer ships with a matching `.sha256` next to it on the GitHub release ```powershell # Windows (PowerShell) -Get-FileHash QueryDen_1.0.11_x64-setup.exe -Algorithm SHA256 +Get-FileHash QueryDen__x64-setup.exe -Algorithm SHA256 ``` ```bash # macOS / Linux -shasum -a 256 QueryDen_1.0.11_aarch64.dmg -sha256sum QueryDen_1.0.11_amd64.deb +shasum -a 256 QueryDen__universal.dmg +sha256sum QueryDen__amd64.deb ``` If the hashes don't match, don't run the installer — open an issue. ## System requirements -- **macOS** 11 Big Sur or newer. Apple Silicon only (no Intel build right now). -- **Windows** 10 1809 or newer. x86_64. WebView2 runtime is auto-installed by the setup .exe. -- **Linux** glibc 2.35+ (Ubuntu 22.04+, Debian 12+). `webkit2gtk` 4.1. +- **macOS** 11 Big Sur or newer, Intel or Apple Silicon. +- **Windows** 10 1809 or newer, x86_64. The WebView2 runtime is installed by the setup `.exe` if it's missing. +- **Linux** glibc 2.35+ (Ubuntu 22.04+, Debian 12+, Fedora 36+) and `webkit2gtk` 4.1. + +Optional: a PostgreSQL client (`psql`) for the [psql console](/docs/editor/psql-console). QueryDen finds it on your `PATH` or in the standard install folders. + +## Updates + +Installed builds update themselves: QueryDen checks the signed release feed and offers the update in-app. To get fixes before they're tagged, see the [beta channel](/docs/getting-started/beta-channel). ## Build from source diff --git a/website/src/content/docs/getting-started/vault-setup.mdx b/website/src/content/docs/getting-started/vault-setup.mdx index 84b80fe..17a3fae 100644 --- a/website/src/content/docs/getting-started/vault-setup.mdx +++ b/website/src/content/docs/getting-started/vault-setup.mdx @@ -1,40 +1,44 @@ --- -title: Set up the vault -description: On first launch you create a master password. The vault is encrypted and machine-bound. +title: The vault +description: How QueryDen stores connections and credentials — encrypted at rest, locked to this machine, no setup step. section: getting-started order: 20 -updated: 2026-05-13 +updated: 2026-10-26 --- -On first launch, QueryDen asks you to create a **master password** for the encrypted vault. The vault stores every connection string, every saved query, and every local-history snapshot. +import Callout from '../../../components/Callout.astro'; -> The master password never leaves your machine. It is mixed with a machine fingerprint to derive the AES-256-GCM encryption key. The key never leaves your machine either. +There is nothing to set up. The first time QueryDen saves a connection it creates its encryption key, and from then on everything sensitive is written encrypted and locked to this computer. -## What you choose +## What's protected -- A passphrase you'll remember. A long one is better than a clever short one — Argon2id is memory-hard, so the slow KDF is doing the heavy lifting either way. -- That's it. There is no recovery flow. If you forget it, the vault is unreadable. +Every secret QueryDen keeps — connection passwords, SSH passwords and key passphrases, credential profiles, your AI provider key — plus your saved queries, query history and local history is encrypted with **AES-256-GCM** before it touches disk. Nothing sensitive is ever written in plain text. -## What QueryDen does behind the scenes +The data lives in QueryDen's app data folder: `~/.local/share/com.queryden.app/` on Linux, `~/Library/Application Support/com.queryden.app/` on macOS and `%APPDATA%\com.queryden.app\` on Windows. -1. Reads a machine fingerprint — `/etc/machine-id` on Linux, `IOPlatformUUID` on macOS, `CSP UUID` on Windows. -2. Loads (or creates) the OS-keychain entry for the master key. -3. Derives the AES-256-GCM key from your passphrase + the machine fingerprint via Argon2id. -4. Writes the encrypted vault file to the Tauri app data directory. +## How the key is made -Full layer-by-layer walkthrough lives on [the security page](/security#vault). +1. On first use QueryDen generates a random 256-bit key, stored in your user profile with owner-only access. +2. It reads this machine's hardware identity, so the key only works on this computer. +3. Both, plus a random per-vault salt, go through **Argon2id** — a memory-hard key-derivation function — to produce the encryption key. -## Brute-force lockout +Every write is atomic (temporary file, flush, rename), so a crash or power loss mid-save can't leave a half-written file. -After 5 failed unlock attempts, the vault locks for an exponentially increasing duration. State persists across app restarts via the encrypted state file, so a relaunch doesn't reset the counter. + +Copy the folder to another computer — a backup, a synced drive — and it won't decrypt there, even with everything else intact. On your own machine the vault is protected by your OS account, so keep full-disk encryption and a screen lock turned on. An optional master password and OS-keychain storage are on the [roadmap](/roadmap). + -## Moving machines +## Credential profiles -By design, the vault file does **not** load on a different computer — even with the correct password. The machine fingerprint changes; the derived key changes; the MAC fails. +**Settings → Credential Vault** holds named username/password pairs. Pick a profile in a connection instead of typing the password there, and changing the profile updates every connection that uses it. -If you want to move connections between machines: +## Moving to a new machine -1. Export the connection list (plaintext JSON of the structure you choose — passwords are not exported by default). -2. Set the same passwords on the new machine after import. +By design the files don't decrypt on another computer, even if you copy the whole folder. To move your connections: -A future release will add an explicit "move vault" flow. +1. **Export** them with the **Import / Export Connections** button in the Database Explorer toolbar. You choose whether to include passwords; the export is a plain JSON file, so treat one with passwords like a password. +2. **Import** the file on the new machine. QueryDen re-encrypts everything with that machine's key. + +If a file fails to decrypt (for example after copying the folder between machines), QueryDen shows a banner and refuses to save over it, so nothing on disk is lost while you sort it out. + +Full details: [Vault internals](/docs/security/vault-internals). diff --git a/website/src/content/docs/security/vault-internals.mdx b/website/src/content/docs/security/vault-internals.mdx index 936f850..0eb8060 100644 --- a/website/src/content/docs/security/vault-internals.mdx +++ b/website/src/content/docs/security/vault-internals.mdx @@ -1,58 +1,53 @@ --- title: Vault internals -description: AES-256-GCM, Argon2id, machine binding, OS keychain. The full layer cake. +description: AES-256-GCM, Argon2id, machine binding, crash-safe writes. The full layer cake. section: security order: 10 -updated: 2026-05-13 +updated: 2026-10-26 --- -The encrypted vault file is what makes QueryDen safe to trust with production credentials. This page walks the layers; for the byte-by-byte hex view, see [the Security page](/security#vault). +The encrypted vault is what makes QueryDen safe to trust with production credentials. This page walks the layers; for the overview, see [the Security page](/security#vault). -## The encrypted file +## Each encrypted value -Path on disk: +Every secret is sealed individually with **AES-256-GCM** and stored as base64 of: -- **Linux** — `~/.local/share/com.queryden.app/vault.json` -- **macOS** — `~/Library/Application Support/com.queryden.app/vault.json` -- **Windows** — `%APPDATA%\com.queryden.app\vault.json` +| Part | Size | Purpose | +|------|------|---------| +| Nonce | 12 B | Random, freshly generated for every write | +| Ciphertext | variable | The encrypted value | +| Tag | 16 B | GCM authentication tag — any modification makes decryption fail | -The file is binary despite the `.json` suffix. The layout is: +Saved queries, query history and local history are sealed the same way as whole documents. -| Region | Size | Purpose | -|--------|------|---------| -| Magic + version | 6 B | File-format header, version-tagged | -| Salt | 16 B | Argon2id salt — currently static, [issue #14](https://github.com/openidle-dev/queryden/issues/14) tracks per-vault randomization | -| Nonce | 12 B | AES-GCM nonce, regenerated on each write | -| Ciphertext | variable | The encrypted vault body | -| MAC | 16 B | AES-GCM authentication tag | +## The key -## The encryption pipeline - -1. You type a master password. -2. QueryDen reads the **machine fingerprint** for your OS: +1. On first use QueryDen generates a **random 256-bit key** from the operating system's secure random source and keeps it in your user profile with owner-only access. +2. It reads the **machine identity**: - Linux: `/etc/machine-id` - macOS: `IOPlatformUUID` - - Windows: `CSP UUID` -3. The OS keychain entry for the master key is loaded (or created): - - macOS Keychain (Secure Enclave-bound when available) - - Windows Credential Manager (DPAPI) - - Linux `libsecret` / GNOME Keyring -4. The three inputs feed Argon2id, which derives the AES-256-GCM key. -5. The key encrypts the vault payload. The nonce + ciphertext + MAC are written atomically. + - Windows: the system UUID (`Win32_ComputerSystemProduct`) +3. A **random 16-byte salt** is generated per vault ([#14](https://github.com/openidle-dev/queryden/issues/14)). +4. All three go through **Argon2id** (19 MiB memory, 2 passes — the OWASP-recommended baseline) to derive the AES-256-GCM key. The derived key is cached in memory for the session so the slow derivation runs once. + +If any input differs on read — another machine, another install — the authentication tag doesn't verify and the data refuses to decrypt. + +## Writes are crash-safe -If any single input is wrong on read — wrong password, wrong machine, missing OS keychain entry — the MAC validation fails and the file refuses to decrypt. +Every file is written to a temporary file, flushed to disk and renamed over the original, so a crash or power loss leaves either the old file or the new one — never a torn mix. If a file can't be decrypted, QueryDen tells you and refuses to save over it, so a failed read never turns into lost data. -## Brute-force lockout +## Roadmap -Five failed unlock attempts in a row trip the lockout. The lockout duration doubles each retry. State persists across app restarts via the encrypted state file, so a relaunch doesn't reset the counter. +- An optional **master password** mixed into the key derivation. +- Storing the per-install key in the **OS keychain** (macOS Keychain, Windows Credential Manager, Secret Service on Linux). ## What we don't defend against - A rooted / compromised OS. If an attacker controls your kernel, no userland app survives. -- A keylogger between your keyboard and your master password. +- Malware running as your own user account while you're logged in. - A network attacker between you and a database that lacks TLS. Use SSL or the SSH tunnel — that's why they're there. - You pasting a connection string in Slack. We can't fix that. ## Verify everything -The encryption code is roughly 300 lines of Rust at [`src-tauri/src/storage.rs`](https://github.com/openidle-dev/queryden/blob/main/src-tauri/src/storage.rs). The CI pipeline that builds the binaries lives in the same repo. There is no closed-source layer. +QueryDen is open source, and the CI pipeline that builds the binaries runs in the same public repository. There is no closed-source layer. diff --git a/website/src/content/docs/troubleshooting/common-errors.mdx b/website/src/content/docs/troubleshooting/common-errors.mdx index 86efcba..588448a 100644 --- a/website/src/content/docs/troubleshooting/common-errors.mdx +++ b/website/src/content/docs/troubleshooting/common-errors.mdx @@ -3,7 +3,7 @@ title: Common errors description: A growing list of error messages with diagnoses and fixes. section: troubleshooting order: 10 -updated: 2026-05-13 +updated: 2026-10-26 --- If your error isn't here yet, open an issue with the exact message, the engine, and your OS. The list grows by reproduction. @@ -20,7 +20,11 @@ Wrong password, or the database isn't configured to accept password auth from yo ### `server version mismatch` (psql / pg_dump) -QueryDen handles this for you — the embedded CLI auto-downloads the matching client version. If you see this from a system-installed `psql`, you're not using QueryDen's bundled one. Run via **Tab → CLI** instead. +The [psql console](/docs/editor/psql-console) uses the newest `psql` installed on your machine. A newer client talks to older servers, so install a client at least as new as your newest server; **Settings → CLI Tools** shows which one QueryDen found. + +### `psql not found` + +Install the PostgreSQL client (the dialog lists the command for your OS) and press **Check Again** — no restart needed. QueryDen also finds `psql` in the standard install folders when they aren't on your `PATH`. ## Vault @@ -28,15 +32,19 @@ QueryDen handles this for you — the embedded CLI auto-downloads the matching c By design. The vault is bound to the machine fingerprint. Copying `vault.json` to a different computer makes it unreadable, even with the right password. See [Vault internals](/docs/security/vault-internals). -### `5 failed unlock attempts — locked for N minutes` +### `Stored connections or credentials could not be read` -Brute-force lockout tripped. Wait it out. There is no override — if you're locked out of your own data, you'll need to wait for the cooldown. +A data file failed to decrypt — usually because the app data folder was copied from another machine. QueryDen leaves the file untouched and won't save over it. Restore the original folder, or move your connections with **Import / Export Connections** instead (see [The vault](/docs/getting-started/vault-setup#moving-to-a-new-machine)). ## Editor ### `Ctrl+D toggles the explorer when I wanted multi-cursor` -Fixed in [#13](https://github.com/openidle-dev/queryden/issues/13). The Database Explorer toggle moved to `Ctrl+\` (matching the VS Code / DataGrip sidebar-toggle convention), leaving `Ctrl+D` to Monaco's built-in "add selection to next occurrence" multi-cursor. +Fixed in [#13](https://github.com/openidle-dev/queryden/issues/13). The Database Explorer toggle moved to `Ctrl+Shift+E`, leaving `Ctrl+D` to Monaco's built-in "add selection to next occurrence" multi-cursor. + +### No autocomplete suggestions + +Completion uses the schema of the tab's connection and database. Open the database in the explorer (tabs on other servers load theirs in the background) and use **Refresh Schema** after DDL changes. See [Completion and navigation](/docs/editor/code-intelligence). ## Performance diff --git a/website/src/lib/site.ts b/website/src/lib/site.ts index 5f1ece1..08be6c5 100644 --- a/website/src/lib/site.ts +++ b/website/src/lib/site.ts @@ -136,6 +136,7 @@ function classifyAsset(name: string, sizeBytes: number): ReleaseAsset | null { if (name === 'latest.json' || name.endsWith('.app.tar.gz')) return null; const sizeMB = Math.round((sizeBytes / 1024 / 1024) * 10) / 10; + if (/_universal\.dmg$/i.test(name)) return { os: 'macOS', arch: 'Intel + Apple Silicon', file: name, sizeMB, primary: true }; if (/_aarch64\.dmg$/i.test(name)) return { os: 'macOS', arch: 'Apple Silicon', file: name, sizeMB, primary: true }; if (/_x64-setup\.exe$/i.test(name)) return { os: 'Windows', arch: 'x86_64', file: name, sizeMB, primary: true }; if (/_amd64\.deb$/i.test(name)) return { os: 'Linux', arch: 'x86_64 · .deb', file: name, sizeMB, primary: true }; diff --git a/website/src/pages/download.astro b/website/src/pages/download.astro index 01c7f86..05fb246 100644 --- a/website/src/pages/download.astro +++ b/website/src/pages/download.astro @@ -18,9 +18,9 @@ const verifyCmds = [ ]; const requirements = [ - { title: 'macOS', items: ['macOS 11 Big Sur or newer', 'Apple Silicon only (no Intel build right now)', 'Universal WKWebView (system)'] }, + { title: 'macOS', items: ['macOS 11 Big Sur or newer', 'Intel and Apple Silicon (universal build)', 'Universal WKWebView (system)'] }, { title: 'Windows', items: ['Windows 10 1809 or newer', 'x86_64', 'WebView2 runtime (auto-installed)'] }, - { title: 'Linux', items: ['glibc 2.31+ (Debian 11 / Ubuntu 22.04+)', 'webkit2gtk 4.1', 'libsecret / GNOME Keyring for the vault'] }, + { title: 'Linux', items: ['glibc 2.35+ (Ubuntu 22.04+ / Debian 12+)', 'webkit2gtk 4.1'] }, ]; --- diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 645fc2a..055b1b8 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -23,9 +23,8 @@ const ROWS: Array<{ label: string; values: Array; wedge: boole { label: 'Native (no Chromium bundled)', values: [true, false, true, false, false, false], wedge: true }, { label: 'Installer size', values: ['~11 MB', '~250 MB', '~50 MB', '~700 MB', '~200 MB', '~200 MB'], wedge: true }, { label: 'Machine-locked vault', values: [true, false, false, false, false, false], wedge: true }, - { label: 'Brute-force lockout', values: [true, false, false, false, false, false], wedge: true }, { label: 'Local history · diffs', values: [true, 'Yes', 'Yes', 'Yes', 'Yes', 'Limited'], wedge: false }, - { label: 'Integrated psql · per-version', values: [true, false, false, false, false, 'bundled'], wedge: true }, + { label: 'Integrated psql console', values: [true, false, false, false, false, 'bundled'], wedge: true }, { label: 'AI assistant · BYO key', values: ['OpenAI · Anthropic · Google · Ollama', 'Yes (paid)', false, 'Paid', 'Yes (BYO)', false], wedge: true }, { label: 'Visual EXPLAIN ANALYZE', values: [true, 'Yes', false, 'Yes', 'Yes', 'Yes'], wedge: false }, { label: 'SSH tunneling', values: [true, true, true, true, true, true], wedge: false }, @@ -475,18 +474,18 @@ const FOOTPRINT = [

Pick the engines, run once, get merged rows tagged with their source.

- +
Embedded CLI
-

psql, but the right version.

+

psql, one right-click away.

-

Auto-downloads the matching server-version client. No "version mismatch" surprises.

+

Uses the newest psql on your machine — found even when it isn't on PATH — with the connection's credentials and SSH tunnel already applied.

@@ -535,22 +534,22 @@ const FOOTPRINT = [
Layer

AES-256-GCM

-

Authenticated encryption for every connection, every saved query, every vault entry. Tampering invalidates the MAC; the file refuses to decrypt.

+

Authenticated encryption for every stored secret, saved query and history entry. Tampering invalidates the tag; the data refuses to decrypt.

Layer

Argon2id KDF

-

Master password → key via a memory-hard KDF, making brute-force orders of magnitude slower than a GPU farm.

+

Per-install secret + machine identity + per-vault salt → key via a memory-hard KDF, making brute-force orders of magnitude slower than a GPU farm.

Layer

Machine fingerprint

-

Mixed into the KDF. Linux: /etc/machine-id. macOS: IOPlatformUUID. Windows: CSP UUID. Change machines, the vault refuses to load.

+

Mixed into the KDF. Linux: /etc/machine-id. macOS: IOPlatformUUID. Windows: the system UUID. Change machines, the vault refuses to load.

Layer
-

OS keychain

-

Master key in macOS Keychain (Secure Enclave-bound), Windows Credential Manager (DPAPI), or libsecret on Linux.

+

Per-install key

+

256 random bits from the operating system's secure random source, generated on first launch and readable only by your user account.

@@ -559,8 +558,8 @@ const FOOTPRINT = [
Deep dive

See the bytes of an encrypted vault.

- The Security page walks the full file format — magic header, Argon2id salt, - GCM nonce, ciphertext, MAC — annotated against the source. Plus the threat + The Security page walks the encryption layers — Argon2id, per-vault salt, + GCM nonce, ciphertext, authentication tag. Plus the threat model: what we defend against, and what we don't.

Open the threat model → @@ -648,11 +647,11 @@ const FOOTPRINT = [
  • - Machine-locked vault, brute-force lockout + Machine-locked vault
  • - Integrated psql (per Postgres version) + Integrated psql console
  • diff --git a/website/src/pages/roadmap.astro b/website/src/pages/roadmap.astro index 6a839a4..a775876 100644 --- a/website/src/pages/roadmap.astro +++ b/website/src/pages/roadmap.astro @@ -3,11 +3,11 @@ import Layout from '../layouts/Layout.astro'; import SignatureMesh from '../components/SignatureMesh.astro'; import { SITE, RELEASE } from '../lib/site'; -// TODO: fill these in. The structure is informed by Zed ("Currently / Next"), -// Warp ("team committed / community-eligible"), and Astro (issues are the -// source of truth — the website is a thin curated view). Each item should -// link to a GitHub issue when one exists; the issue is what's real, this page -// is a summary. +// The structure is informed by Zed ("Currently / Next"), Warp ("team +// committed / community-eligible"), and Astro (issues are the source of +// truth — the website is a thin curated view). Each item should link to a +// GitHub issue when one exists; the issue is what's real, this page is a +// summary. When an issue closes, move its item to SHIPPED. // // Status rule (keep it minimal): only "in progress" items get a badge. // Everything else is implicitly planned. Don't invent dates or version pins. @@ -28,6 +28,8 @@ interface Horizon { items: Item[]; } +const issueLink = (n: number) => `#${n}`; + const HORIZONS: Horizon[] = [ { id: 'now', @@ -35,15 +37,27 @@ const HORIZONS: Horizon[] = [ lede: 'Active focus — the work most likely to land in the next release.', items: [ { - title: 'macOS in-app updater', - body: 'pick_asset misses the .dmg and install_update has no darwin branch — Mac users are stuck on whatever they first installed. Fixing the asset selection and install path.', - issue: 8, + title: 'Editor sessions and code intelligence', + body: 'Every tab becomes its own database session, so transactions and SET carry between runs. Context-aware completion, parameter hints with argument names, Ctrl+click definitions, DataGrip-style colours, and exact psql-style values for every type.', + status: 'in-progress', + }, + { + title: 'AI assistant on your own key', + body: 'Real calls to OpenAI, Anthropic, Gemini or a local server, with the model list fetched live from the provider and the key sealed at rest.', + issue: 10, + status: 'in-progress', + }, + { + title: 'CLI tools management', + body: 'Settings → CLI Tools shows which psql / mysql / mongosh was found, where and which version — including installs that aren\'t on PATH.', + issue: 39, status: 'in-progress', }, { - title: 'Connection dialog cleanup', - body: 'Surface all required settings clearly and collapse the "coming soon" provider list behind a Show all toggle, so the dialog stops looking like every backend is half-finished.', - issue: 44, + title: 'Dependency security sweep', + body: 'Patched TLS, archive-extraction and build-tool dependencies across the app and the docs site.', + issue: 185, + status: 'in-progress', }, ], }, @@ -53,29 +67,32 @@ const HORIZONS: Horizon[] = [ lede: 'Queued for after the current batch. Order is approximate; scope may move.', items: [ { - title: 'SSH tunnel reliability', - body: 'A reproducible "tunnel won\'t connect" report. Needs a deeper look at the ssh2-based tunnel registry and clearer error surfacing.', - issue: 46, + title: 'Refresh the database list', + body: 'Pick up databases created or dropped on the server without reconnecting.', + issue: 237, }, { - title: 'psql console correctness', - body: 'Auto-appended LIMIT after a trailing semicolon produces a syntax error; the Extended-display shortcut (\\x) doesn\'t fire. Two small fixes that make the embedded psql usable.', - issue: 38, + title: 'Master password and OS keychain', + body: 'An optional master password mixed into the vault key, and the per-install key kept in the macOS Keychain, Windows Credential Manager or Secret Service.', }, { - title: 'Type-correct grid rendering', - body: 'BIGINT precision past JS safe-integer range, and date / timestamp detection by column type instead of column name.', - issue: 41, + title: 'SSH hardening', + body: 'Host-key verification against known_hosts, and ed25519 / OpenSSH-format keys on Windows.', }, { - title: 'CLI tools management', - body: 'PostgreSQL CLI auto-download is broken end-to-end; users have no UI to inspect, switch, or repair downloaded CLIs.', - issue: 40, + title: 'One design system everywhere', + body: `Consistent primitives and palette across the app, the results grid included (${issueLink(151)}, ${issueLink(152)}, ${issueLink(187)}, ${issueLink(192)}).`, + issue: 149, }, { - title: 'Keymap audit', - body: 'Ctrl+Shift+F is double-bound, and ~14 shortcuts in the DataGrip preset are advertised but never wired. (Ctrl+D resolved in #13.)', - issue: 11, + title: 'Testing infrastructure', + body: 'Component tests and Playwright end-to-end runs on top of the unit and live-database suites.', + issue: 24, + }, + { + title: 'Toolchain upgrades', + body: 'CI on the Node 24 runtime, and the docs site on Astro 7.', + issue: 186, }, ], }, @@ -85,15 +102,45 @@ const HORIZONS: Horizon[] = [ lede: 'Direction, not commitment — these are problems we want to solve, not specs we\'ve written.', items: [ { - title: 'Universal macOS build', - body: 'Ship universal-apple-darwin so the same DMG runs on Intel and Apple Silicon Macs.', - issue: 7, + title: 'Install a previous version', + body: 'Roll back to an earlier release from inside the app.', + issue: 91, + status: 'exploring', + }, + { + title: 'Signed and notarized builds', + body: 'Code-signed Windows installers and notarized macOS builds, so neither OS warns on first launch.', + status: 'exploring', + }, + { + title: 'Smaller, focused modules', + body: `Split the largest components and contexts into focused pieces (${issueLink(1)}–${issueLink(6)}) and add ESLint (${issueLink(33)}).`, + issue: 2, + status: 'exploring', + }, + { + title: 'More engines', + body: 'SQL Server, Oracle and DB2 — listed as coming soon in the connection dialog.', status: 'exploring', }, ], }, ]; +// Closed issues that used to sit on this page. +const SHIPPED: Item[] = [ + { title: 'Universal macOS build — Intel and Apple Silicon in one .dmg', body: '', issue: 7 }, + { title: 'In-app updater on macOS', body: '', issue: 8 }, + { title: 'Keymap preset only advertises shortcuts that work', body: '', issue: 11 }, + { title: 'Per-vault random Argon2 salt', body: '', issue: 14 }, + { title: 'psql console: auto-LIMIT and \\x fixes', body: '', issue: 38 }, + { title: 'psql found from system installs instead of a broken auto-download', body: '', issue: 40 }, + { title: 'BIGINT values beyond JavaScript\'s safe range stay exact', body: '', issue: 41 }, + { title: 'Connection dialog shows every setting clearly', body: '', issue: 44 }, + { title: 'SSH tunnel reliability', body: '', issue: 46 }, + { title: 'Beta update channel', body: '', issue: 105 }, +]; + const STATUS_LABEL: Record = { 'in-progress': 'In progress', 'planned': 'Planned', @@ -163,6 +210,16 @@ const issueUrl = (n: number) => `${SITE.repo}/issues/${n}`; Latest release: {RELEASE.tag}{' '} on {RELEASE.publishedAt}. The changelog has the version-by-version history.

    +
      + {SHIPPED.map((it) => ( +
    • + {it.issue !== undefined && ( + #{it.issue} + )} + {it.title} +
    • + ))} +

    Disagree with the priorities?

    @@ -261,6 +318,15 @@ const issueUrl = (n: number) => `${SITE.repo}/issues/${n}`; } .rm-desc a:hover { color: var(--accent); border-bottom-color: var(--accent); } + .rm-desc b { color: var(--ink-1); font-weight: 500; } + + .rm-shipped { list-style: none; padding: 0; margin: 18px 0 0; display: grid; gap: 8px; } + .rm-shipped li { + display: grid; grid-template-columns: 52px 1fr; gap: 12px; align-items: baseline; + color: var(--ink-2); font-size: 14px; line-height: 1.5; + } + .rm-shipped .rm-issue { font-family: var(--mono); font-size: 12px; justify-self: start; } + /* Tail callouts */ .rm-tail { border-top: 1px solid var(--line); } .rm-tail-grid { diff --git a/website/src/pages/security.astro b/website/src/pages/security.astro index eb6a6e1..a3162d0 100644 --- a/website/src/pages/security.astro +++ b/website/src/pages/security.astro @@ -14,32 +14,30 @@ const principles = [ }, { title: 'You can audit every line', - body: 'The source is on GitHub under MIT. The encryption code is ~300 lines of Rust in `src-tauri/src/storage.rs`. The CI pipeline that builds the binaries is in the same repo. There is no closed-source layer.', + body: 'The source is on GitHub under MIT. The CI pipeline that builds the binaries is in the same repo. There is no closed-source layer.', }, ]; const threats = [ { actor: 'Someone copies your QueryDen vault file off your laptop', - defense: 'The vault is encrypted with a key derived from your master password + a machine fingerprint. Without the original machine, the file does not decrypt — even with the correct password.' }, - { actor: 'Malware on your laptop tries to brute-force the vault', - defense: 'After 5 failed unlock attempts the vault locks for an exponentially increasing duration. Argon2id key derivation makes each attempt slow on purpose.' }, + defense: 'The vault is encrypted with a key bound to this machine\'s hardware identity. Copied to any other computer, the file does not decrypt.' }, + { actor: 'Someone tries to brute-force a copied vault file', + defense: 'There is no short password to guess: the key comes from a random 256-bit secret plus this machine\'s identity, stretched through memory-hard Argon2id.' }, { actor: 'You lose the laptop', - defense: 'The vault file is encrypted at rest. The master key in your OS keychain is hardware-bound (Secure Enclave on macOS, DPAPI on Windows, libsecret on Linux). A wiped login means the vault becomes permanently unreadable.' }, + defense: 'Every secret is encrypted at rest. Pair it with full-disk encryption (FileVault, BitLocker, LUKS) and a lost laptop gives up nothing.' }, { actor: 'A QueryDen update ships a backdoor', defense: 'Every release is built by GitHub Actions from a public tag. The SHA-256 of each installer is published. Reproduce the build yourself from the same commit and compare — the hash must match.' }, { actor: 'You connect to a malicious database server', defense: 'QueryDen is the client. The server can return malicious result rows, but the result grid renders them as text. We do not eval, we do not exec, we do not auto-run a follow-up query based on a result.' }, ]; -// Illustrative bytes from a QueryDen vault file. Layout reflects the real -// AES-256-GCM file format: magic+version, salt, nonce, ciphertext body, MAC. +// Illustrative bytes of one encrypted value. Layout reflects the real +// AES-256-GCM layout: nonce, ciphertext body, authentication tag. // Bytes themselves are random — this is a visual, not a live decryption. -type Region = 'magic' | 'salt' | 'nonce' | 'cipher' | 'tag'; +type Region = 'nonce' | 'cipher' | 'tag'; const VAULT_BYTES: { hex: string; region: Region }[] = (() => { const make = (s: string, region: Region) => s.split(' ').map(hex => ({ hex, region })); return [ - ...make('51 44 56 4e 5f 02', 'magic'), - ...make('e7 3a 9c 1f b8 4d 02 91 75 c0 ae 6b 32 d8 49 a7', 'salt'), ...make('c4 8e 1a 7f 5d b2 09 33 e6 4c 91 a8', 'nonce'), ...make('6f 92 b1 04 d7 8a 3e cc f1 27 5b 84 a0 19 6d e3 48 0c b7 95 22 fe d4 7a 91 c8 0b 64 e7 a2', 'cipher'), ...make('5f b8 14 6a 92 f3 0d 4e 27 c5 91 a8 6f 14 d3 02', 'tag'), @@ -47,11 +45,11 @@ const VAULT_BYTES: { hex: string; region: Region }[] = (() => { })(); const layers = [ - { name: 'AES-256-GCM', detail: 'Authenticated encryption for every connection, every saved query, every vault entry. Tampering invalidates the MAC; the file refuses to decrypt.' }, - { name: 'Argon2id (memory-hard KDF)', detail: 'Master password → encryption key. Memory-hard by design, making brute-force orders of magnitude slower than a GPU farm.' }, + { name: 'AES-256-GCM', detail: 'Authenticated encryption for every stored secret, saved query and history entry. Tampering invalidates the tag; the data refuses to decrypt.' }, + { name: 'Argon2id (memory-hard KDF)', detail: 'Per-install secret + machine identity + per-vault salt → encryption key. Memory-hard by design, making brute-force orders of magnitude slower than a GPU farm.' }, { name: 'Machine fingerprint', detail: 'Linux: /etc/machine-id. macOS: IOPlatformUUID. Windows: Win32_ComputerSystemProduct.UUID. Mixed into the KDF — change machines, the vault refuses to load.' }, - { name: 'OS keychain master key', detail: 'macOS Keychain (Secure Enclave-bound) · Windows Credential Manager (DPAPI) · libsecret on Linux. Falls back to a file-based key only if the OS keyring is unavailable.' }, - { name: 'Brute-force lockout', detail: '5 failed unlocks → exponential backoff. Implemented in atomic counters in-process; the lock persists across app restarts via the encrypted state file.' }, + { name: 'Per-install random key', detail: '256 bits from the operating system\'s secure random source, generated on first launch and readable only by your user account.' }, + { name: 'Crash-safe writes', detail: 'Temporary file, flush, atomic rename — a crash never leaves a torn file, and a file that fails to decrypt is never overwritten.' }, { name: 'CSP locked to self', detail: "Tauri's default-src policy disables remote script loading. The renderer cannot fetch arbitrary code from the internet, even if you find an XSS vector." }, ]; --- @@ -117,7 +115,7 @@ const layers = [
    -
    ▲  Disk · vault.json bytes
    +
    ▲  Disk · one encrypted value
    ))}
    -

    - Source: src-tauri/src/storage.rs -

    @@ -177,7 +168,7 @@ const layers = [
    What we do not defend against
    • A rooted/compromised OS. If an attacker controls your kernel they control your memory; no userland app can recover from this.
    • -
    • A keylogger that captures your master password at the keyboard.
    • +
    • Malware running as your own user account while you're logged in.
    • A network attacker between you and a database that lacks TLS. Use SSL/TLS or our SSH tunnel — that's what they're for.
    • You sharing a connection string in Slack. We can't fix that.
    @@ -190,10 +181,10 @@ const layers = [
    04 · Verify everything

    Don't trust this page. Trust the code.