Skip to content

Commit 4d396c7

Browse files
feat(database-ui): move db-studio src out of samples
1 parent 916c5d7 commit 4d396c7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+318
-13
lines changed

packages/cli/build.gradle.kts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2407,7 +2407,7 @@ tasks {
24072407
val allSamples = layout.projectDirectory.dir("src/projects")
24082408
.asFile
24092409
.listFiles()
2410-
.filter { it.isDirectory() }
2410+
.filter { it.isDirectory() && it.name != "db-studio" }
24112411
.map { it.toPath() to it.name }
24122412

24132413
val builtSamples = layout.buildDirectory.dir("packed-samples")
@@ -2429,10 +2429,29 @@ tasks {
24292429
dependsOn(allSamplePackTasks)
24302430
}
24312431

2432+
val prepareDbStudioResources by registering(Copy::class) {
2433+
group = "build"
2434+
description = "Prepare Database Studio resources for embedding in CLI"
2435+
2436+
// Copy API files
2437+
from(layout.projectDirectory.dir("src/db-studio/api")) {
2438+
into("api")
2439+
exclude("config.ts") // Generated at runtime with injected config
2440+
}
2441+
2442+
// Copy built UI (dist/ folder only, not source or node_modules)
2443+
from(layout.projectDirectory.dir("src/db-studio/ui/dist")) {
2444+
into("ui")
2445+
}
2446+
2447+
into(layout.buildDirectory.dir("resources/main/META-INF/elide/db-studio"))
2448+
}
2449+
24322450
processResources {
24332451
dependsOn(
24342452
":packages:graalvm:buildRustNativesForHostDebug",
24352453
prepKotlinResources,
2454+
prepareDbStudioResources,
24362455
packSamples,
24372456
allSamplePackTasks,
24382457
)
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/// <reference path="../../../../../types/index.d.ts" />
2+
/**
3+
* Database API Layer
4+
*
5+
* Provides abstraction over database operations for the DB Studio.
6+
* This layer isolates all database-specific logic, making it easy to:
7+
* - Swap SQLite for other databases (PostgreSQL, MySQL, etc.)
8+
* - Add caching, connection pooling, or other optimizations
9+
* - Centralize error handling and validation
10+
*
11+
* NOTE: This module does NOT import "elide:sqlite" directly because Elide's
12+
* module loader can only handle that special protocol in the entry point file.
13+
* The Database class must be passed from index.tsx.
14+
*/
15+
16+
import type { Database, Statement } from "elide:sqlite";
17+
18+
export interface DiscoveredDatabase {
19+
path: string;
20+
name: string;
21+
size: number;
22+
lastModified: number;
23+
}
24+
25+
export interface TableInfo {
26+
name: string;
27+
rowCount: number;
28+
}
29+
30+
export type TableData = {
31+
name: string;
32+
columns: string[];
33+
rows: unknown[][];
34+
totalRows: number;
35+
};
36+
37+
export interface DatabaseInfo {
38+
path: string;
39+
name: string;
40+
size: number;
41+
lastModified: number;
42+
tableCount: number;
43+
}
44+
45+
interface TableNameRow {
46+
name: string;
47+
}
48+
49+
interface CountRow {
50+
count: number;
51+
}
52+
53+
/**
54+
* Get list of tables in a database
55+
*/
56+
export function getTables(db: Database): TableInfo[] {
57+
const query: Statement<TableNameRow> = db.query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
58+
const results = query.all();
59+
60+
return results.map(({ name }) => {
61+
const tableName = name;
62+
const countQuery: Statement<CountRow> = db.query(`SELECT COUNT(*) as count FROM ${tableName}`);
63+
const countResult = countQuery.get();
64+
65+
return {
66+
name: tableName,
67+
rowCount: countResult?.count ?? 0,
68+
};
69+
});
70+
}
71+
72+
73+
interface ColumnNameRow {
74+
name: string;
75+
}
76+
77+
/**
78+
* Get table data with schema and rows
79+
*/
80+
export function getTableData(db: Database, tableName: string, limit: number = 100, offset: number = 0): TableData {
81+
82+
// Get schema (column names)
83+
const schemaQuery: Statement<ColumnNameRow> = db.prepare(`SELECT name FROM pragma_table_info('${tableName}') ORDER BY cid`);
84+
const schemaResults = schemaQuery.all();
85+
const columns = schemaResults.map((col) => col.name);
86+
87+
// Get data rows (unknown type since we don't know the schema)
88+
const dataQuery = db.query(`SELECT * FROM ${tableName} LIMIT ${limit} OFFSET ${offset}`);
89+
const rows = dataQuery.all();
90+
91+
// Get total row count
92+
const countQuery: Statement<CountRow> = db.query(`SELECT COUNT(*) as count FROM ${tableName}`);
93+
const countResult = countQuery.get();
94+
const totalRows = countResult?.count ?? 0;
95+
96+
return {
97+
name: tableName,
98+
columns,
99+
rows: rows.map((row: unknown) => columns.map(col => (row as Record<string, unknown>)[col])),
100+
totalRows,
101+
};
102+
}
103+
104+
/**
105+
* Get database metadata
106+
*/
107+
export function getDatabaseInfo(db: Database, dbPath: string): DatabaseInfo {
108+
const tablesQuery: Statement<CountRow> = db.query("SELECT COUNT(*) as count FROM sqlite_master WHERE type='table'");
109+
const tablesResult = tablesQuery.get();
110+
111+
// Extract name from path
112+
const pathParts = dbPath.split('/');
113+
const name = pathParts[pathParts.length - 1];
114+
115+
return {
116+
path: dbPath,
117+
name,
118+
size: 0, // Will be populated by calling code if available
119+
lastModified: 0, // Will be populated by calling code if available
120+
tableCount: tablesResult?.count ?? 0,
121+
};
122+
}
123+
124+
/**
125+
* Execute a raw SQL query (for future query editor feature)
126+
*/
127+
export function executeQuery(db: Database, sql: string, limit: number = 100): { columns: string[], rows: unknown[][] } {
128+
const query = db.query(sql);
129+
const results = query.all();
130+
131+
if (results.length === 0) {
132+
return { columns: [], rows: [] };
133+
}
134+
135+
const firstRow = results[0] as Record<string, unknown>;
136+
const columns = Object.keys(firstRow);
137+
const rows = results.map((row: unknown) => columns.map(col => (row as Record<string, unknown>)[col]));
138+
139+
return { columns, rows };
140+
}
141+
142+
/**
143+
* Validate that a database path is accessible
144+
*/
145+
export function validateDatabase(db: Database): boolean {
146+
try {
147+
// Try a simple query to verify the database is valid
148+
const query = db.query("SELECT 1");
149+
query.get();
150+
return true;
151+
} catch (err) {
152+
return false;
153+
}
154+
}

0 commit comments

Comments
 (0)