kumarabhirup 3dd23ba381
ironclaw: save WIP workspace, dench, and web app changes before upstream merge
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 18:18:15 -08:00

99 lines
2.6 KiB
TypeScript

import { safeResolvePath, duckdbQueryOnFile, resolveDuckdbBin } from "@/lib/workspace";
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
type TableInfo = {
table_name: string;
column_count: number;
estimated_row_count: number;
columns: Array<{
name: string;
type: string;
is_nullable: boolean;
}>;
};
/**
* GET /api/workspace/db/introspect?path=<relative-path>
*
* Introspects a DuckDB / SQLite / generic DB file using the duckdb CLI.
* Returns the list of tables with their columns and approximate row counts.
*/
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const relPath = searchParams.get("path");
if (!relPath) {
return Response.json(
{ error: "Missing required `path` query parameter" },
{ status: 400 },
);
}
const absPath = safeResolvePath(relPath);
if (!absPath) {
return Response.json(
{ error: "File not found or path traversal rejected" },
{ status: 404 },
);
}
// Check if DuckDB CLI binary is available
if (!resolveDuckdbBin()) {
return Response.json({ tables: [], path: relPath, duckdb_available: false });
}
// Get all user tables (skip internal DuckDB catalogs)
const rawTables = duckdbQueryOnFile<{
table_name: string;
table_type: string;
}>(
absPath,
"SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'main' ORDER BY table_name",
);
if (rawTables.length === 0) {
return Response.json({ tables: [], path: relPath });
}
const tables: TableInfo[] = [];
for (const t of rawTables) {
// Fetch columns for this table
const cols = duckdbQueryOnFile<{
column_name: string;
data_type: string;
is_nullable: string;
}>(
absPath,
`SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'main' AND table_name = '${t.table_name.replace(/'/g, "''")}' ORDER BY ordinal_position`,
);
// Get approximate row count
let rowCount = 0;
try {
const countResult = duckdbQueryOnFile<{ cnt: number }>(
absPath,
`SELECT count(*) as cnt FROM "${t.table_name.replace(/"/g, '""')}"`,
);
rowCount = countResult[0]?.cnt ?? 0;
} catch {
// skip if we can't count
}
tables.push({
table_name: t.table_name,
column_count: cols.length,
estimated_row_count: rowCount,
columns: cols.map((c) => ({
name: c.column_name,
type: c.data_type,
is_nullable: c.is_nullable === "YES",
})),
});
}
return Response.json({ tables, path: relPath });
}