Skip to content

Commit 2f43eae

Browse files
feat(database-ui): spread server routes and handlers out to separate files
1 parent c34ddaa commit 2f43eae

File tree

25 files changed

+580
-676
lines changed

25 files changed

+580
-676
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/Users/francis/code/millpointlabs/elide/elide/packages/cli/src/db-studio/api/package-lock.kdl
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "db-studio-api",
3+
"version": "1.0.0",
4+
"description": "Database Studio API Server",
5+
"main": "index.ts",
6+
"scripts": {},
7+
"dependencies": {
8+
"zod": "4"
9+
},
10+
"devDependencies": {
11+
"@elide-dev/types": "1.0.0-beta10"
12+
}
13+
}
150 Bytes
Binary file not shown.

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
*
1010
* NOTE: This module does NOT import "elide:sqlite" directly because Elide's
1111
* module loader can only handle that special protocol in the entry point file.
12-
* The Database class must be passed from index.tsx.
12+
* The Database class must be passed from index.ts.
1313
*/
1414

15-
import { Database, Statement } from "elide:sqlite";
15+
import type { Database, Statement } from "elide:sqlite";
1616

1717
/**
1818
* Log SQL queries to console
@@ -167,7 +167,6 @@ export function validateDatabase(db: Database): boolean {
167167
try {
168168
// Try a simple query to verify the database is valid
169169
const sql = "SELECT 1";
170-
logQuery(sql);
171170
const query = db.query(sql);
172171
query.get();
173172
return true;

packages/cli/src/db-studio/api/elide.pkl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ version = "1.0.0"
66
description = "Database Studio API Server"
77

88
entrypoint {
9-
"index.tsx"
9+
"index.ts"
1010
}
1111

1212
dependencies {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { ApiResponse, RouteContext, RouteHandler, DatabaseHandler } from "./types.ts";
2+
import { validateDatabaseIndex } from "../utils/validation.ts";
3+
4+
/**
5+
* Middleware that validates database index and provides database instance to handler
6+
*/
7+
export function withDatabase(handler: DatabaseHandler): RouteHandler {
8+
return async (params: Record<string, string>, context: RouteContext, body: string): Promise<ApiResponse> => {
9+
const result = validateDatabaseIndex(params.dbIndex, context.databases);
10+
if ("error" in result) return result.error;
11+
12+
const { database } = result;
13+
const db = new context.Database(database.path);
14+
15+
return handler(params, { ...context, database, db }, body);
16+
};
17+
}
18+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { ApiResponse } from "./types.ts";
2+
3+
/**
4+
* Create a JSON response
5+
*/
6+
export function jsonResponse(data: unknown, status: number = 200): ApiResponse {
7+
console.log("returning json response", data);
8+
return {
9+
status,
10+
headers: { "Content-Type": "application/json" },
11+
body: JSON.stringify(data),
12+
};
13+
}
14+
15+
/**
16+
* Create an error response
17+
*/
18+
export function errorResponse(message: string, status: number = 500): ApiResponse {
19+
return jsonResponse({ error: message }, status);
20+
}
21+
22+
/**
23+
* Handle database operation errors
24+
*/
25+
export function handleDatabaseError(err: unknown, operation: string): ApiResponse {
26+
const errorMessage = err instanceof Error ? err.message : "Unknown error";
27+
return errorResponse(`Failed to ${operation}: ${errorMessage}`, 500);
28+
}
29+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Match a route pattern against a path and extract parameters
3+
*/
4+
export function matchRoute(pattern: string, path: string): Record<string, string> | null {
5+
const patternParts = pattern.split("/").filter(p => p);
6+
const pathParts = path.split("/").filter(p => p);
7+
8+
if (patternParts.length !== pathParts.length) return null;
9+
10+
const params: Record<string, string> = {};
11+
12+
for (let i = 0; i < patternParts.length; i++) {
13+
const patternPart = patternParts[i];
14+
const pathPart = pathParts[i];
15+
16+
if (patternPart.startsWith(":")) {
17+
// Parameter segment
18+
const paramName = patternPart.slice(1);
19+
params[paramName] = pathPart;
20+
} else if (patternPart !== pathPart) {
21+
// Literal segment doesn't match
22+
return null;
23+
}
24+
}
25+
26+
return params;
27+
}
28+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { DiscoveredDatabase } from "../database.ts";
2+
import type { ApiResponse, RouteContext, DatabaseConstructor } from "./types.ts";
3+
import { matchRoute } from "./router.ts";
4+
import { routes } from "../routes/index.ts";
5+
import { errorResponse } from "./responses.ts";
6+
7+
/**
8+
* Handle API requests using Node.js primitives
9+
*/
10+
export async function handleApiRequest(
11+
url: string,
12+
method: string,
13+
body: string,
14+
databases: DiscoveredDatabase[],
15+
Database: DatabaseConstructor
16+
): Promise<ApiResponse> {
17+
// Parse URL path (remove query string if present)
18+
const path = url.split('?')[0];
19+
20+
try {
21+
// Try to match against registered routes
22+
const context: RouteContext = { databases, Database };
23+
24+
for (const route of routes) {
25+
if (route.method !== method) continue;
26+
27+
const params = matchRoute(route.pattern, path);
28+
if (params) {
29+
return await route.handler(params, context, body);
30+
}
31+
}
32+
33+
// No route matched - return 404
34+
return { status: 404, headers: {}, body: '' };
35+
36+
} catch (err) {
37+
const errorMessage = err instanceof Error ? err.message : "Unknown error";
38+
console.error("Error handling request:", err);
39+
return errorResponse(errorMessage, 500);
40+
}
41+
}
42+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { DiscoveredDatabase } from "../database.ts";
2+
import type { Database } from "elide:sqlite";
3+
4+
/**
5+
* Database constructor type
6+
*/
7+
export type DatabaseConstructor = typeof Database;
8+
9+
/**
10+
* API response structure
11+
*/
12+
export type ApiResponse = {
13+
status: number;
14+
headers: Record<string, string>;
15+
body: string;
16+
};
17+
18+
/**
19+
* Context passed to all route handlers
20+
*/
21+
export type RouteContext = {
22+
databases: DiscoveredDatabase[];
23+
Database: DatabaseConstructor;
24+
};
25+
26+
/**
27+
* Route handler function signature
28+
*/
29+
export type RouteHandler = (
30+
params: Record<string, string>,
31+
context: RouteContext,
32+
body: string
33+
) => Promise<ApiResponse>;
34+
35+
/**
36+
* Route definition
37+
*/
38+
export type Route = {
39+
method: string;
40+
pattern: string;
41+
handler: RouteHandler;
42+
};
43+
44+
/**
45+
* Extended context for database-specific handlers
46+
*/
47+
export type DatabaseHandlerContext = {
48+
database: DiscoveredDatabase;
49+
db: Database;
50+
databases: DiscoveredDatabase[];
51+
};
52+
53+
/**
54+
* Database-specific handler function signature
55+
*/
56+
export type DatabaseHandler = (
57+
params: Record<string, string>,
58+
context: DatabaseHandlerContext,
59+
body: string
60+
) => Promise<ApiResponse>;
61+

0 commit comments

Comments
 (0)