From bdb3e6a6b31b5fe5fcb63104604f4b92e3e65100 Mon Sep 17 00:00:00 2001
From: Yash
Date: Sat, 24 Jan 2026 23:35:42 +0530
Subject: [PATCH 1/3] feat: Introduce database connection management,
discovery, schema browsing, and query execution capabilities.
---
bridge/src/jsonRpcHandler.ts | 13 +
bridge/src/services/discoveryService.ts | 337 ++++++++++++++++++
src/components/home/AddConnectionDialog.tsx | 13 +-
.../home/DiscoveredDatabasesCard.tsx | 165 +++++++++
src/components/home/WelcomeView.tsx | 10 +-
src/components/home/index.ts | 1 +
src/components/home/types.ts | 4 +-
src/hooks/useDiscoveredDatabases.ts | 46 +++
src/pages/Index.tsx | 26 +-
src/services/bridgeApi.ts | 20 +-
src/types/database.ts | 12 +
11 files changed, 641 insertions(+), 6 deletions(-)
create mode 100644 bridge/src/services/discoveryService.ts
create mode 100644 src/components/home/DiscoveredDatabasesCard.tsx
create mode 100644 src/hooks/useDiscoveredDatabases.ts
diff --git a/bridge/src/jsonRpcHandler.ts b/bridge/src/jsonRpcHandler.ts
index 607879b..ba92907 100644
--- a/bridge/src/jsonRpcHandler.ts
+++ b/bridge/src/jsonRpcHandler.ts
@@ -7,6 +7,7 @@ import { DatabaseHandlers } from "./handlers/databaseHandlers";
import { SessionHandlers } from "./handlers/sessionHandlers";
import { StatsHandlers } from "./handlers/statsHandlers";
import { MigrationHandlers } from "./handlers/migrationHandlers";
+import { discoveryService } from "./services/discoveryService";
import { Logger } from "pino";
/**
@@ -164,6 +165,18 @@ export function registerDbHandlers(
statsHandlers.handleGetTotalStats(p, id)
);
+ // ==========================================
+ // DATABASE DISCOVERY HANDLERS
+ // ==========================================
+ rpcRegister("db.discover", async (_p, id) => {
+ try {
+ const discovered = await discoveryService.discoverLocalDatabases();
+ rpc.sendResponse(id, { ok: true, data: discovered });
+ } catch (error: any) {
+ rpc.sendError(id, { code: "DISCOVERY_ERROR", message: error.message });
+ }
+ });
+
logger?.info("All RPC handlers registered successfully");
}
diff --git a/bridge/src/services/discoveryService.ts b/bridge/src/services/discoveryService.ts
new file mode 100644
index 0000000..7045c97
--- /dev/null
+++ b/bridge/src/services/discoveryService.ts
@@ -0,0 +1,337 @@
+/**
+ * Database Discovery Service
+ *
+ * Automatically discovers locally running databases by scanning common ports
+ * and detecting Docker containers with database images.
+ */
+
+import { exec } from "child_process";
+import { promisify } from "util";
+import * as net from "net";
+
+const execAsync = promisify(exec);
+
+// Port configurations for each database type
+const DATABASE_PORTS: Record = {
+ postgresql: [5432, 5433, 5434],
+ mysql: [3306, 3307, 3308],
+ mariadb: [3306, 3307, 3308],
+};
+
+// Fun adjectives and nouns for generating database names
+const ADJECTIVES = [
+ "swift", "cosmic", "stellar", "nimble", "turbo", "quantum", "neon", "cyber",
+ "atomic", "hyper", "mega", "ultra", "blazing", "electric", "dynamic", "rapid",
+ "mighty", "brave", "clever", "noble", "fierce", "silent", "golden", "crystal",
+ "shadow", "frost", "thunder", "ember", "azure", "crimson", "violet", "lunar",
+];
+
+const NOUNS = [
+ "phoenix", "dragon", "falcon", "panther", "tiger", "wolf", "hawk", "eagle",
+ "lion", "bear", "shark", "cobra", "viper", "raven", "storm", "blaze",
+ "nova", "comet", "nebula", "galaxy", "cosmos", "orbit", "pulse", "flux",
+ "spark", "bolt", "wave", "surge", "core", "nexus", "vertex", "matrix",
+];
+
+export interface DiscoveredDatabase {
+ type: "postgresql" | "mysql" | "mariadb";
+ host: string;
+ port: number;
+ source: "local" | "docker";
+ containerName?: string;
+ suggestedName: string;
+ defaultUser: string;
+ defaultDatabase: string;
+ defaultPassword?: string; // Only available for Docker containers
+}
+
+export class DiscoveryService {
+ private connectionTimeout = 500; // ms to wait for port response
+
+ /**
+ * Generate a fun name for a database connection
+ */
+ generateFunName(type: string, port: number): string {
+ const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
+ const suffix = port !== this.getDefaultPort(type) ? `-${port}` : "";
+ return `${adjective}-${noun}${suffix}`;
+ }
+
+ /**
+ * Get the default port for a database type
+ */
+ private getDefaultPort(type: string): number {
+ switch (type) {
+ case "postgresql":
+ return 5432;
+ case "mysql":
+ case "mariadb":
+ return 3306;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Get default username for a database type
+ */
+ private getDefaultUser(type: string): string {
+ switch (type) {
+ case "postgresql":
+ return "postgres";
+ case "mysql":
+ case "mariadb":
+ return "root";
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Get default database name for a database type
+ */
+ private getDefaultDatabase(type: string): string {
+ switch (type) {
+ case "postgresql":
+ return "postgres";
+ case "mysql":
+ case "mariadb":
+ return "mysql";
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Check if a specific port is open on a host
+ */
+ private async isPortOpen(host: string, port: number): Promise {
+ return new Promise((resolve) => {
+ const socket = new net.Socket();
+
+ socket.setTimeout(this.connectionTimeout);
+
+ socket.on("connect", () => {
+ socket.destroy();
+ resolve(true);
+ });
+
+ socket.on("timeout", () => {
+ socket.destroy();
+ resolve(false);
+ });
+
+ socket.on("error", () => {
+ socket.destroy();
+ resolve(false);
+ });
+
+ socket.connect(port, host);
+ });
+ }
+
+ /**
+ * Scan localhost for open database ports
+ */
+ private async scanLocalPorts(): Promise {
+ const discovered: DiscoveredDatabase[] = [];
+ const hosts = ["127.0.0.1", "localhost"];
+ const scannedPorts = new Set();
+
+ for (const host of hosts) {
+ for (const [dbType, ports] of Object.entries(DATABASE_PORTS)) {
+ for (const port of ports) {
+ const key = `${port}`;
+ if (scannedPorts.has(key)) continue;
+
+ const isOpen = await this.isPortOpen(host, port);
+ if (isOpen) {
+ scannedPorts.add(key);
+
+ // Determine the actual type based on the port
+ let actualType: "postgresql" | "mysql" | "mariadb" = dbType as any;
+ if (port >= 5432 && port <= 5434) {
+ actualType = "postgresql";
+ } else if (port >= 3306 && port <= 3308) {
+ // Can't distinguish MySQL from MariaDB by port alone
+ actualType = "mysql";
+ }
+
+ discovered.push({
+ type: actualType,
+ host: "localhost",
+ port,
+ source: "local",
+ suggestedName: this.generateFunName(actualType, port),
+ defaultUser: this.getDefaultUser(actualType),
+ defaultDatabase: this.getDefaultDatabase(actualType),
+ });
+ }
+ }
+ }
+ }
+
+ return discovered;
+ }
+
+ /**
+ * Get environment variables from a Docker container
+ */
+ private async getContainerEnvVars(containerName: string): Promise
- Download |
+ Download |
Features |
Installation |
Contributing
@@ -103,7 +103,7 @@ Before building from source, ensure you have:
### From Releases (Recommended)
-Download the latest release for your platform from the [**Releases**](https://github.com/Yashh56/RelWave/releases) page:
+Download the latest release for your platform from the [**Releases**](https://github.com/Relwave/relwave-app/releases) page:
| Platform | File Type | Description |
|----------|----------|-------------|
@@ -116,7 +116,7 @@ Download the latest release for your platform from the [**Releases**](https://gi
1. Clone the repository:
```bash
-git clone https://github.com/Yashh56/RelWave.git
+git clone https://github.com/Relwave/relwave-app.git
cd RelWave
```
diff --git a/bridge/package.json b/bridge/package.json
index 047480c..61fe8fd 100644
--- a/bridge/package.json
+++ b/bridge/package.json
@@ -1,6 +1,6 @@
{
"name": "db-visualizer-bridge",
- "version": "0.1.0",
+ "version": "0.1.0-beta.5",
"type": "commonjs",
"main": "dist/index.cjs",
"scripts": {
diff --git a/package.json b/package.json
index 3671bb5..666dd72 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "relwave",
"private": true,
- "version": "0.1.0-beta.4",
+ "version": "0.1.0-beta.5",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 799b9c1..8e85649 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "RelWave",
- "version": "0.1.0-beta.4",
+ "version": "0.1.0-beta.5",
"identifier": "com.yashs.RelWave",
"build": {
"beforeDevCommand": "npm run dev",
@@ -48,7 +48,7 @@
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEZCODA3MzJBRUNGNDE3NUIKUldSYkYvVHNLbk9BKzlKTVVlU2F2NGJET0VFcGlvZXhWVEhpUmhXR0J0cVFtY25zTlNIRDFNa0MK",
"endpoints": [
- "https://github.com/Yashh56/RelWave/releases/latest/download/latest.json"
+ "https://github.com/Relwave/relwave-app/releases/latest/download/latest.json"
],
"windows": {
"installMode": "passive"