Skip to content

Commit c34ddaa

Browse files
feat(database-ui): setup elide.pkl manifest for api
1 parent 4d396c7 commit c34ddaa

File tree

10 files changed

+161
-48
lines changed

10 files changed

+161
-48
lines changed

packages/cli/src/db-studio/api/config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/// <reference path="../../../../../types/index.d.ts" />
2-
31
import type { DiscoveredDatabase } from "./database.ts";
42

53
/**

packages/cli/src/db-studio/api/database.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/// <reference path="../../../../../types/index.d.ts" />
21
/**
32
* Database API Layer
43
*
@@ -13,7 +12,16 @@
1312
* The Database class must be passed from index.tsx.
1413
*/
1514

16-
import type { Database, Statement } from "elide:sqlite";
15+
import { Database, Statement } from "elide:sqlite";
16+
17+
/**
18+
* Log SQL queries to console
19+
*/
20+
function logQuery(sql: string, params?: unknown[]): void {
21+
const timestamp = new Date().toISOString();
22+
const paramsStr = params && params.length > 0 ? ` [${params.join(", ")}]` : "";
23+
console.log(`[${timestamp}] SQL: ${sql}${paramsStr}`);
24+
}
1725

1826
export interface DiscoveredDatabase {
1927
path: string;
@@ -54,12 +62,16 @@ interface CountRow {
5462
* Get list of tables in a database
5563
*/
5664
export function getTables(db: Database): TableInfo[] {
57-
const query: Statement<TableNameRow> = db.query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
65+
const sql = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
66+
logQuery(sql);
67+
const query: Statement<TableNameRow> = db.query(sql);
5868
const results = query.all();
5969

6070
return results.map(({ name }) => {
6171
const tableName = name;
62-
const countQuery: Statement<CountRow> = db.query(`SELECT COUNT(*) as count FROM ${tableName}`);
72+
const countSql = `SELECT COUNT(*) as count FROM ${tableName}`;
73+
logQuery(countSql);
74+
const countQuery: Statement<CountRow> = db.query(countSql);
6375
const countResult = countQuery.get();
6476

6577
return {
@@ -80,16 +92,22 @@ interface ColumnNameRow {
8092
export function getTableData(db: Database, tableName: string, limit: number = 100, offset: number = 0): TableData {
8193

8294
// Get schema (column names)
83-
const schemaQuery: Statement<ColumnNameRow> = db.prepare(`SELECT name FROM pragma_table_info('${tableName}') ORDER BY cid`);
95+
const schemaSql = `SELECT name FROM pragma_table_info('${tableName}') ORDER BY cid`;
96+
logQuery(schemaSql);
97+
const schemaQuery: Statement<ColumnNameRow> = db.prepare(schemaSql);
8498
const schemaResults = schemaQuery.all();
8599
const columns = schemaResults.map((col) => col.name);
86100

87101
// Get data rows (unknown type since we don't know the schema)
88-
const dataQuery = db.query(`SELECT * FROM ${tableName} LIMIT ${limit} OFFSET ${offset}`);
102+
const dataSql = `SELECT * FROM ${tableName} LIMIT ${limit} OFFSET ${offset}`;
103+
logQuery(dataSql);
104+
const dataQuery = db.query(dataSql);
89105
const rows = dataQuery.all();
90106

91107
// Get total row count
92-
const countQuery: Statement<CountRow> = db.query(`SELECT COUNT(*) as count FROM ${tableName}`);
108+
const countSql = `SELECT COUNT(*) as count FROM ${tableName}`;
109+
logQuery(countSql);
110+
const countQuery: Statement<CountRow> = db.query(countSql);
93111
const countResult = countQuery.get();
94112
const totalRows = countResult?.count ?? 0;
95113

@@ -105,7 +123,9 @@ export function getTableData(db: Database, tableName: string, limit: number = 10
105123
* Get database metadata
106124
*/
107125
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'");
126+
const sql = "SELECT COUNT(*) as count FROM sqlite_master WHERE type='table'";
127+
logQuery(sql);
128+
const tablesQuery: Statement<CountRow> = db.query(sql);
109129
const tablesResult = tablesQuery.get();
110130

111131
// Extract name from path
@@ -125,6 +145,7 @@ export function getDatabaseInfo(db: Database, dbPath: string): DatabaseInfo {
125145
* Execute a raw SQL query (for future query editor feature)
126146
*/
127147
export function executeQuery(db: Database, sql: string, limit: number = 100): { columns: string[], rows: unknown[][] } {
148+
logQuery(sql);
128149
const query = db.query(sql);
129150
const results = query.all();
130151

@@ -145,7 +166,9 @@ export function executeQuery(db: Database, sql: string, limit: number = 100): {
145166
export function validateDatabase(db: Database): boolean {
146167
try {
147168
// Try a simple query to verify the database is valid
148-
const query = db.query("SELECT 1");
169+
const sql = "SELECT 1";
170+
logQuery(sql);
171+
const query = db.query(sql);
149172
query.get();
150173
return true;
151174
} catch (err) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
amends "elide:project.pkl"
2+
import "elide:JavaScript.pkl" as js
3+
4+
name = "db-studio-api"
5+
version = "1.0.0"
6+
description = "Database Studio API Server"
7+
8+
entrypoint {
9+
"index.tsx"
10+
}
11+
12+
dependencies {
13+
npm {
14+
packages {
15+
"zod@4"
16+
}
17+
18+
devPackages {
19+
"@elide-dev/[email protected]"
20+
}
21+
}
22+
}

packages/cli/src/db-studio/api/index.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/// <reference path="../../../../../types/index.d.ts" />
21

32
import { createServer } from "http";
43
import { Database } from "elide:sqlite";
@@ -43,15 +42,12 @@ const server = createServer(options, (req, res) => {
4342
const url = req.url || '/';
4443
const method = req.method || 'GET';
4544

46-
// Handle the API request
4745
const response = await handleApiRequest(url, method, body, databases, Database);
4846

49-
// Write response with Content-Length (byte length, not character length)
5047
res.writeHead(response.status, {
5148
...response.headers,
5249
'Content-Length': Buffer.byteLength(response.body, 'utf8')
5350
});
54-
console.log(response.body);
5551
res.end(response.body);
5652
} catch (err) {
5753
console.error("Error handling request:", err);
@@ -67,5 +63,5 @@ const server = createServer(options, (req, res) => {
6763

6864
// Start listening on configured port
6965
server.listen(port, () => {
70-
console.log(`Database Studio API started on http://localhost:${port} 🎉`);
66+
console.log(`Database Studio API started on http://localhost:${port} 🚀`);
7167
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
lockfile-version 1
2+
root{
3+
dependencies{
4+
zod ">=4.0.0 <5.0.0-0"
5+
}
6+
dev-dependencies{
7+
"@elide-dev/types" "1.0.0-beta10"
8+
}
9+
}
10+
pkg "@elide-dev/types"{
11+
version "1.0.0-beta10"
12+
resolved "https://registry.npmjs.org/@elide-dev/types/-/types-1.0.0-beta10.tgz"
13+
integrity "sha512-VAaiwprV0ekDHn0cXxn/GnvNohm2gLk0SOqymMCyWK6Tk0Q9rf0O4PCR4NGOFZf51zfghB1Pd204w72lhQbPhg=="
14+
dependencies{
15+
"@types/node" ">=0.0.0"
16+
}
17+
}
18+
pkg "@types/node"{
19+
version "24.10.1"
20+
resolved "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz"
21+
integrity "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="
22+
dependencies{
23+
undici-types ">=7.16.0 <7.17.0-0"
24+
}
25+
}
26+
pkg undici-types{
27+
version "7.16.0"
28+
resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz"
29+
integrity "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
30+
}
31+
pkg zod{
32+
version "4.1.12"
33+
resolved "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz"
34+
integrity "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="
35+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {z} from "zod";
2+
3+
const schema = z.object({
4+
name: z.string(),
5+
age: z.number(),
6+
});
7+
8+
const data = {
9+
name: "John",
10+
age: 30,
11+
};
12+
13+
const result = schema.parse(data);
14+
console.log(result);

packages/cli/src/db-studio/ui/src/lib/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,13 @@ export function formatDate(timestamp: number) {
2222
if (hours < 24) return `${hours}h ago`
2323
return `${days}d ago`
2424
}
25+
26+
export function formatRowCount(count: number): string {
27+
if (count < 1000) return count.toString()
28+
if (count < 1000000) {
29+
const k = count / 1000
30+
return k % 1 === 0 ? `${k}K` : `${k.toFixed(1)}K`
31+
}
32+
const m = count / 1000000
33+
return m % 1 === 0 ? `${m}M` : `${m.toFixed(1)}M`
34+
}

packages/cli/src/db-studio/ui/src/routes/Database.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useMemo, useState } from 'react'
33
import { TableProperties as TableIcon, Code2 } from 'lucide-react'
44
import { useDatabaseTables } from '../hooks/useDatabaseTables'
55
import { Button } from '@/components/ui/button'
6+
import { formatRowCount } from '../lib/utils'
67

78
export default function Database() {
89
const { dbIndex, tableName } = useParams()
@@ -50,7 +51,7 @@ export default function Database() {
5051
<div className="text-xs text-gray-500 uppercase mb-3 font-medium">
5152
{loading ? 'Loading…' : `${filteredTables.length} TABLES`}
5253
</div>
53-
{!loading && filteredTables.map(({ name }) => {
54+
{!loading && filteredTables.map(({ name, rowCount }) => {
5455
const isActive = decodeURIComponent(tableName || '') === name
5556
return (
5657
<Link
@@ -63,7 +64,8 @@ export default function Database() {
6364
aria-current={isActive ? 'page' : undefined}
6465
>
6566
<TableIcon className="w-4 h-4 shrink-0" />
66-
<span className="truncate">{name}</span>
67+
<span className="truncate flex-1">{name}</span>
68+
<span className="text-xs text-gray-500 shrink-0">{formatRowCount(rowCount)}</span>
6769
</Link>
6870
)
6971
})}

packages/cli/src/db-studio/ui/src/routes/Query.tsx

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ export default function Query() {
9797

9898
return (
9999
<div className="flex-1 p-0 overflow-auto font-mono flex flex-col">
100-
<div className="px-6 pt-6 pb-4 border-b border-gray-800">
101-
<div className="flex items-center justify-between mb-4">
100+
<div className="pt-6 border-b border-gray-800">
101+
<div className="flex items-center justify-between mb-4 px-6">
102102
<h2 className="text-2xl font-semibold tracking-tight">SQL Query Editor</h2>
103103
<div className="flex gap-2">
104104
<Button
@@ -135,7 +135,7 @@ export default function Query() {
135135
allowMultipleSelections: false,
136136
}}
137137
editable={!loading}
138-
className="w-full [&_.cm-editor]:bg-gray-900 [&_.cm-editor]:border-0 [&_.cm-editor]:rounded-none [&_.cm-scroller]:font-mono [&_.cm-content]:text-gray-200 [&_.cm-content]:text-sm [&_.cm-placeholder]:text-gray-600 [&_.cm-editor]:w-full [&_.cm-gutter]:bg-gray-900 [&_.cm-lineNumbers]:text-gray-500"
138+
className="w-full [&_.cm-editor]:bg-gray-900 [&_.cm-editor]:border-0 [&_.cm-editor]:rounded-none [&_.cm-scroller]:font-mono [&_.cm-content]:text-gray-200 [&_.cm-content]:text-sm [&_.cm-placeholder]:text-gray-600 [&_.cm-editor]:w-full [&_.cm-gutter]:bg-gray-900 [&_.cm-lineNumbers]:text-gray-500 [&_.cm-editor]:p-0 [&_.cm-scroller]:p-0 [&_.cm-content]:p-0"
139139
/>
140140
<div className="flex items-center gap-3 px-3 py-1.5 bg-gray-900 border-t border-gray-800">
141141
<div className="text-xs text-gray-400 font-mono">
@@ -180,29 +180,38 @@ export default function Query() {
180180
<UiTable className="w-full">
181181
<TableHeader>
182182
<TableRow className="bg-gray-900/50">
183-
{Object.keys(result.rows[0] as Record<string, unknown>).map((col) => (
184-
<TableHead
185-
key={col}
186-
className="text-left px-4 py-3 text-xs font-medium text-gray-400 tracking-wider border-b border-gray-800"
187-
>
188-
{col}
189-
</TableHead>
190-
))}
183+
{Object.keys(result.rows[0] as Record<string, unknown>).map((col, colIndex, cols) => {
184+
const isLastColumn = colIndex === cols.length - 1
185+
return (
186+
<TableHead
187+
key={col}
188+
className={`text-left px-4 py-3 text-xs font-medium text-gray-400 tracking-wider border-b border-gray-800 ${!isLastColumn ? 'border-r border-gray-800' : ''}`}
189+
>
190+
{col}
191+
</TableHead>
192+
)
193+
})}
191194
</TableRow>
192195
</TableHeader>
193196
<TableBody>
194-
{result.rows.map((row, i) => (
195-
<TableRow key={i} className="hover:bg-gray-900/30 transition-colors">
196-
{Object.values(row as Record<string, unknown>).map((cell, j) => (
197-
<TableCell
198-
key={j}
199-
className="px-4 py-3 text-sm text-gray-200 whitespace-nowrap"
200-
>
201-
{String(cell ?? '')}
202-
</TableCell>
203-
))}
204-
</TableRow>
205-
))}
197+
{result.rows.map((row, i) => {
198+
const rowValues = Object.values(row as Record<string, unknown>)
199+
return (
200+
<TableRow key={i} className="hover:bg-gray-900/30 transition-colors">
201+
{rowValues.map((cell, j) => {
202+
const isLastColumn = j === rowValues.length - 1
203+
return (
204+
<TableCell
205+
key={j}
206+
className={`px-4 py-3 text-sm text-gray-200 whitespace-nowrap ${!isLastColumn ? 'border-r border-gray-800' : ''}`}
207+
>
208+
{String(cell ?? '')}
209+
</TableCell>
210+
)
211+
})}
212+
</TableRow>
213+
)
214+
})}
206215
</TableBody>
207216
</UiTable>
208217
</div>

packages/cli/src/db-studio/ui/src/routes/Table.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ export default function TableView() {
3838
<UiTable className="w-full">
3939
<TableHeader>
4040
<TableRow className="bg-gray-900/50">
41-
{data.columns.map(col => {
41+
{data.columns.map((col, colIndex) => {
4242
const isKey = (data.primaryKeys?.includes(col)) || /(^id$|_id$)/i.test(col)
43+
const isLastColumn = colIndex === data.columns.length - 1
4344
return (
44-
<TableHead key={col} className="text-left px-4 py-3 text-xs font-medium text-gray-400 tracking-wider border-b border-gray-800">
45+
<TableHead key={col} className={`text-left px-4 py-3 text-xs font-medium text-gray-400 tracking-wider border-b border-gray-800 ${!isLastColumn ? 'border-r border-gray-800' : ''}`}>
4546
<span className="inline-flex items-center gap-1.5">
4647
{isKey && <KeyRound className="w-3.5 h-3.5 text-amber-300" />}
4748
<span>{col}</span>
@@ -54,11 +55,14 @@ export default function TableView() {
5455
<TableBody>
5556
{data.rows.map((row, i) => (
5657
<TableRow key={i} className="hover:bg-gray-900/30 transition-colors">
57-
{row.map((cell, j) => (
58-
<TableCell key={j} className="px-4 py-3 text-sm text-gray-200 whitespace-nowrap">
59-
{String(cell ?? '')}
60-
</TableCell>
61-
))}
58+
{row.map((cell, j) => {
59+
const isLastColumn = j === row.length - 1
60+
return (
61+
<TableCell key={j} className={`px-4 py-3 text-sm text-gray-200 whitespace-nowrap ${!isLastColumn ? 'border-r border-gray-800' : ''}`}>
62+
{String(cell ?? '')}
63+
</TableCell>
64+
)
65+
})}
6266
</TableRow>
6367
))}
6468
</TableBody>

0 commit comments

Comments
 (0)