Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 210 additions & 75 deletions README.md

Large diffs are not rendered by default.

85 changes: 85 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"includes": [
"**",
"!**/dist",
"!**/node_modules",
"!**/target",
"!**/build",
"!**/.next",
"!**/*.css",
"!src-tauri/target"
]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "warn",
"noArrayIndexKey": "warn",
"noUnknownAtRules": "off"
},
"style": {
"noNonNullAssertion": "warn"
},
"a11y": {
"noStaticElementInteractions": "off",
"useKeyWithClickEvents": "off",
"noSvgWithoutTitle": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"jsxQuoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false
}
},
"json": {
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
},
"overrides": [
{
"includes": ["src/monaco/completion-provider/snippets.ts"],
"linter": {
"rules": {
"suspicious": {
"noTemplateCurlyInString": "off"
}
}
}
}
]
}
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri"
"tauri": "tauri",
"format": "biome format --write .",
"lint": "biome lint .",
"check": "biome check --write .",
"check:ci": "biome check ."
},
"dependencies": {
"@glideapps/glide-data-grid": "^6.0.3",
Expand Down Expand Up @@ -40,6 +44,7 @@
"zustand": "^5.0.11"
},
"devDependencies": {
"@biomejs/biome": "2.4.15",
"@tailwindcss/vite": "^4.1.13",
"@tauri-apps/cli": "^2",
"@types/leaflet": "^1.9.21",
Expand Down
209 changes: 209 additions & 0 deletions src-tauri/src/app_setup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
use std::{collections::BTreeMap, sync::Arc};
use tauri::Manager;
use tauri::menu::{AboutMetadata, MenuBuilder, SubmenuBuilder};
use tokio::sync::Mutex;

use crate::{AppState, LOCAL_DB_NAME, terminal, utils};

pub fn setup_app(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(desktop)]
if let Some(pubkey) = option_env!("TAURI_UPDATER_PUBLIC_KEY") {
app.handle()
.plugin(tauri_plugin_updater::Builder::new().pubkey(pubkey).build())?;
} else {
tracing::info!(
"Updater disabled because TAURI_UPDATER_PUBLIC_KEY was not set at build time"
);
}

let app_handle = app.handle().clone();

tauri::async_runtime::block_on(async move {
let db_path = if cfg!(debug_assertions) {
LOCAL_DB_NAME.to_string()
} else {
let app_dir = app_handle
.path()
.app_data_dir()
.expect("Failed to resolve app data directory");
std::fs::create_dir_all(&app_dir).ok();
app_dir.join(LOCAL_DB_NAME).to_string_lossy().to_string()
};

let db = libsql::Builder::new_local(&db_path)
.build()
.await
.expect("Failed to open local database");

let conn = db.connect().expect("Failed to create connection");
conn.execute(
"CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
driver TEXT NOT NULL DEFAULT 'PGSQL',
username TEXT NOT NULL DEFAULT '',
password TEXT NOT NULL DEFAULT '',
database TEXT NOT NULL DEFAULT '',
host TEXT NOT NULL DEFAULT '',
port TEXT NOT NULL DEFAULT '',
ssl TEXT NOT NULL DEFAULT 'false'
)",
(),
)
.await
.expect("Failed to create projects table");

conn.execute(
"CREATE TABLE IF NOT EXISTS queries (
id TEXT PRIMARY KEY,
sql TEXT NOT NULL DEFAULT ''
)",
(),
)
.await
.expect("Failed to create queries table");

conn.execute(
"CREATE TABLE IF NOT EXISTS workspaces (
name TEXT PRIMARY KEY,
tabs TEXT NOT NULL DEFAULT '[]'
)",
(),
)
.await
.expect("Failed to create workspaces table");

conn.execute(
"CREATE TABLE IF NOT EXISTS virtual_query_snapshots (
query_id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
sql TEXT NOT NULL,
columns_packed TEXT NOT NULL DEFAULT '',
total_rows INTEGER NOT NULL DEFAULT 0,
page_size INTEGER NOT NULL DEFAULT 0,
col_count INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL
)",
(),
)
.await
.expect("Failed to create virtual_query_snapshots table");

conn.execute(
"CREATE TABLE IF NOT EXISTS virtual_query_pages (
query_id TEXT NOT NULL,
page_index INTEGER NOT NULL,
packed_page TEXT NOT NULL DEFAULT '',
PRIMARY KEY (query_id, page_index)
)",
(),
)
.await
.expect("Failed to create virtual_query_pages table");

// Best-effort orphan cleanup in case app exited before tab-close cleanup.
conn.execute(
"DELETE FROM virtual_query_pages
WHERE query_id NOT IN (SELECT query_id FROM virtual_query_snapshots)",
(),
)
.await
.ok();

for col in [
"ssh_enabled",
"ssh_host",
"ssh_port",
"ssh_user",
"ssh_password",
"ssh_key_path",
] {
conn.execute(
&format!(
"ALTER TABLE projects ADD COLUMN {} TEXT NOT NULL DEFAULT ''",
col
),
(),
)
.await
.ok(); // Ignore "column already exists" errors
}

let state = AppState {
clients: Arc::new(Mutex::new(BTreeMap::new())),
meta_clients: Arc::new(Mutex::new(BTreeMap::new())),
cancel_tokens: Arc::new(Mutex::new(BTreeMap::new())),
client_ssl: Arc::new(Mutex::new(BTreeMap::new())),
local_db: db,
resource_monitor: Arc::new(Mutex::new(utils::ResourceMonitor::new())),
virtual_cache: Arc::new(Mutex::new(BTreeMap::new())),
notify_handles: Arc::new(Mutex::new(BTreeMap::new())),
ssh_tunnels: Arc::new(Mutex::new(BTreeMap::new())),
};
app_handle.manage(state);

let terminal_state = terminal::TerminalState {
sessions: Arc::new(Mutex::new(std::collections::HashMap::new())),
};
app_handle.manage(terminal_state);
});

let handle = app.handle();

let app_menu = SubmenuBuilder::new(handle, "RSQL")
.about(Some(AboutMetadata {
name: Some("RSQL".into()),
version: Some(env!("CARGO_PKG_VERSION").into()),
copyright: Some("\u{00a9} 2025 rust-dd".into()),
comments: Some(
"Modern SQL client for PostgreSQL.\nBuilt with Tauri, React, and Rust.".into(),
),
website: Some("https://github.com/rust-dd/rust-sql".into()),
website_label: Some("GitHub".into()),
..Default::default()
}))
.separator()
.services()
.separator()
.hide()
.hide_others()
.show_all()
.separator()
.quit()
.build()?;

let edit_menu = SubmenuBuilder::new(handle, "Edit")
.undo()
.redo()
.separator()
.cut()
.copy()
.paste()
.select_all()
.build()?;

let view_menu = SubmenuBuilder::new(handle, "View").fullscreen().build()?;

let window_menu = SubmenuBuilder::new(handle, "Window")
.minimize()
.maximize()
.separator()
.close_window()
.build()?;

let menu = MenuBuilder::new(handle)
.items(&[&app_menu, &edit_menu, &view_menu, &window_menu])
.build()?;

handle.set_menu(menu)?;

#[cfg(debug_assertions)]
{
let window = app
.get_webview_window("main")
.expect("main window not found");
window.open_devtools();
window.close_devtools();
}

Ok(())
}
2 changes: 1 addition & 1 deletion src-tauri/src/dbs/query.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::common::enums::AppError;
use crate::AppState;
use crate::common::enums::AppError;
use std::collections::BTreeMap;
use tauri::{AppHandle, Manager, Result, State};

Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/dbs/workspace.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::common::enums::AppError;
use crate::AppState;
use crate::common::enums::AppError;
use tauri::{Result, State};

#[tauri::command(rename_all = "snake_case")]
Expand Down
Loading
Loading