Skip to content

Commit d90925d

Browse files
feat(database-ui): cleanup server.ts
1 parent 5281d6d commit d90925d

File tree

1 file changed

+102
-110
lines changed
  • packages/cli/src/projects/db-studio/api

1 file changed

+102
-110
lines changed

packages/cli/src/projects/db-studio/api/server.ts

Lines changed: 102 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,25 @@ import type { Database } from "elide:sqlite";
44
import type { DiscoveredDatabase } from "./database.ts";
55
import { getDatabaseInfo, getTables, getTableData } from "./database.ts";
66

7-
/**
8-
* HTTP Server Layer - JSON API Handler
9-
*
10-
* Provides RESTful JSON API endpoints for database operations.
11-
* Used by the imperative Node.js HTTP server in index.tsx.
12-
* The React UI is served separately via a static file server.
13-
*/
14-
157
type ApiResponse = {
168
status: number;
179
headers: Record<string, string>;
1810
body: string;
1911
};
2012

21-
/**
22-
* Get database by index from the discovered list
23-
*/
24-
function getDatabaseByIndex(databases: DiscoveredDatabase[], index: number): DiscoveredDatabase | null {
25-
if (index < 0 || index >= databases.length) return null;
26-
return databases[index];
27-
}
13+
type RouteContext = {
14+
databases: DiscoveredDatabase[];
15+
Database: typeof Database;
16+
};
17+
18+
type RouteHandler = (params: Record<string, string>, context: RouteContext) => Promise<ApiResponse>;
19+
20+
type Route = {
21+
method: string;
22+
pattern: string;
23+
handler: RouteHandler;
24+
};
2825

29-
/**
30-
* Helper to create JSON response
31-
*/
3226
function jsonResponse(data: unknown, status: number = 200): ApiResponse {
3327
return {
3428
status,
@@ -37,17 +31,27 @@ function jsonResponse(data: unknown, status: number = 200): ApiResponse {
3731
};
3832
}
3933

40-
/**
41-
* Helper to create error response
42-
*/
4334
function errorResponse(message: string, status: number = 500): ApiResponse {
4435
return jsonResponse({ error: message }, status);
4536
}
4637

47-
/**
48-
* Match a route pattern with path parameters
49-
* Returns null if no match, otherwise returns extracted parameters
50-
*/
38+
function validateAndGetDatabase(
39+
dbIndexStr: string,
40+
databases: DiscoveredDatabase[]
41+
): { database: DiscoveredDatabase } | { error: ApiResponse } {
42+
const dbIndex = parseInt(dbIndexStr, 10);
43+
44+
if (isNaN(dbIndex)) {
45+
return { error: errorResponse("Invalid database index", 400) };
46+
}
47+
48+
if (dbIndex < 0 || dbIndex >= databases.length) {
49+
return { error: errorResponse(`Database not found at index ${dbIndex}`, 404) };
50+
}
51+
52+
return { database: databases[dbIndex] };
53+
}
54+
5155
function matchRoute(pattern: string, path: string): Record<string, string> | null {
5256
const patternParts = pattern.split("/").filter(p => p);
5357
const pathParts = path.split("/").filter(p => p);
@@ -73,6 +77,70 @@ function matchRoute(pattern: string, path: string): Record<string, string> | nul
7377
return params;
7478
}
7579

80+
async function healthCheck(): Promise<ApiResponse> {
81+
return jsonResponse({ status: "ok" });
82+
}
83+
84+
async function listDatabases(_params: Record<string, string>, context: RouteContext): Promise<ApiResponse> {
85+
return jsonResponse({ databases: context.databases });
86+
}
87+
88+
async function getDatabaseInfoRoute(params: Record<string, string>, context: RouteContext): Promise<ApiResponse> {
89+
const result = validateAndGetDatabase(params.dbIndex, context.databases);
90+
if ("error" in result) return result.error;
91+
92+
const { database } = result;
93+
const db = new context.Database(database.path);
94+
const info = getDatabaseInfo(db, database.path);
95+
96+
const fullInfo = {
97+
...info,
98+
size: database.size,
99+
lastModified: database.lastModified,
100+
isLocal: database.isLocal,
101+
tableCount: info.tableCount,
102+
};
103+
104+
return jsonResponse(fullInfo);
105+
}
106+
107+
async function getTablesRoute(params: Record<string, string>, context: RouteContext): Promise<ApiResponse> {
108+
const result = validateAndGetDatabase(params.dbIndex, context.databases);
109+
if ("error" in result) return result.error;
110+
111+
const { database } = result;
112+
const db = new context.Database(database.path);
113+
const tables = getTables(db);
114+
115+
return jsonResponse({ tables });
116+
}
117+
118+
async function getTableDataRoute(params: Record<string, string>, context: RouteContext): Promise<ApiResponse> {
119+
const result = validateAndGetDatabase(params.dbIndex, context.databases);
120+
if ("error" in result) return result.error;
121+
122+
const tableName = params.tableName;
123+
if (!tableName) {
124+
return errorResponse("Table name is required", 400);
125+
}
126+
127+
const { database } = result;
128+
const db = new context.Database(database.path);
129+
const tableData = getTableData(db, tableName);
130+
131+
return jsonResponse(tableData);
132+
}
133+
134+
/**
135+
* Route Registry
136+
*/
137+
const routes: Route[] = [
138+
{ method: "GET", pattern: "/api/databases", handler: listDatabases },
139+
{ method: "GET", pattern: "/api/databases/:dbIndex", handler: getDatabaseInfoRoute },
140+
{ method: "GET", pattern: "/api/databases/:dbIndex/tables", handler: getTablesRoute },
141+
{ method: "GET", pattern: "/api/databases/:dbIndex/tables/:tableName", handler: getTableDataRoute },
142+
];
143+
76144
/**
77145
* Handle API requests using Node.js primitives
78146
*/
@@ -87,100 +155,24 @@ export async function handleApiRequest(
87155
const path = url.split('?')[0];
88156

89157
try {
90-
// ============================================================================
91-
// Route: GET /health
92-
// ============================================================================
158+
// Special case: health check endpoint (no params needed)
93159
if (method === "GET" && path === "/health") {
94-
return jsonResponse({ status: "ok" });
95-
}
96-
97-
// ============================================================================
98-
// Route: GET /api/databases
99-
// ============================================================================
100-
if (method === "GET" && path === "/api/databases") {
101-
return jsonResponse({ databases });
102-
}
103-
104-
// ============================================================================
105-
// Route: GET /api/databases/:dbIndex
106-
// ============================================================================
107-
const dbInfoMatch = matchRoute("/api/databases/:dbIndex", path);
108-
if (method === "GET" && dbInfoMatch) {
109-
const dbIndex = parseInt(dbInfoMatch.dbIndex, 10);
110-
111-
if (isNaN(dbIndex)) {
112-
return errorResponse("Invalid database index", 400);
113-
}
114-
115-
const database = getDatabaseByIndex(databases, dbIndex);
116-
if (!database) {
117-
return errorResponse(`Database not found at index ${dbIndex}`, 404);
118-
}
119-
120-
const db = new Database(database.path);
121-
const info = getDatabaseInfo(db, database.path);
122-
123-
const fullInfo = {
124-
...info,
125-
size: database.size,
126-
lastModified: database.lastModified,
127-
isLocal: database.isLocal,
128-
tableCount: info.tableCount,
129-
};
130-
131-
return jsonResponse(fullInfo);
160+
return healthCheck();
132161
}
133162

134-
// ============================================================================
135-
// Route: GET /api/databases/:dbIndex/tables
136-
// ============================================================================
137-
const tablesMatch = matchRoute("/api/databases/:dbIndex/tables", path);
138-
if (method === "GET" && tablesMatch) {
139-
const dbIndex = parseInt(tablesMatch.dbIndex, 10);
163+
// Try to match against registered routes
164+
const context: RouteContext = { databases, Database };
140165

141-
if (isNaN(dbIndex)) {
142-
return errorResponse("Invalid database index", 400);
143-
}
166+
for (const route of routes) {
167+
if (route.method !== method) continue;
144168

145-
const database = getDatabaseByIndex(databases, dbIndex);
146-
if (!database) {
147-
return errorResponse(`Database not found at index ${dbIndex}`, 404);
169+
const params = matchRoute(route.pattern, path);
170+
if (params) {
171+
return await route.handler(params, context);
148172
}
149-
150-
const db = new Database(database.path);
151-
const tables = getTables(db);
152-
return jsonResponse({ tables });
153-
}
154-
155-
// ============================================================================
156-
// Route: GET /api/databases/:dbIndex/tables/:tableName
157-
// ============================================================================
158-
const tableDataMatch = matchRoute("/api/databases/:dbIndex/tables/:tableName", path);
159-
if (method === "GET" && tableDataMatch) {
160-
const dbIndex = parseInt(tableDataMatch.dbIndex, 10);
161-
162-
if (isNaN(dbIndex)) {
163-
return errorResponse("Invalid database index", 400);
164-
}
165-
166-
const database = getDatabaseByIndex(databases, dbIndex);
167-
if (!database) {
168-
return errorResponse(`Database not found at index ${dbIndex}`, 404);
169-
}
170-
171-
const tableName = tableDataMatch.tableName;
172-
if (!tableName) {
173-
return errorResponse("Table name is required", 400);
174-
}
175-
176-
const db = new Database(database.path);
177-
const tableData = getTableData(db, tableName);
178-
return jsonResponse(tableData);
179173
}
180174

181-
// ============================================================================
182175
// No route matched - return 404
183-
// ============================================================================
184176
return { status: 404, headers: {}, body: '' };
185177

186178
} catch (err) {

0 commit comments

Comments
 (0)