ewdlop's picture
App.tsx
42ae0b9
/**
* SQL Analyzer REST API client
* Plain fetch() β€” no tRPC, no Node.js dependency.
* Base URL is determined by the VITE_API_BASE env var (defaults to "").
* In production the React bundle is served by FastAPI itself, so "" works.
* In dev, Vite proxies /api/* to http://localhost:7860.
*/
const BASE = (import.meta.env.VITE_API_BASE as string | undefined) ?? "";
async function post<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`API error ${res.status}: ${text.slice(0, 400)}`);
}
return res.json() as Promise<T>;
}
async function get<T>(path: string): Promise<T> {
const res = await fetch(`${BASE}${path}`);
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`API error ${res.status}: ${text.slice(0, 400)}`);
}
return res.json() as Promise<T>;
}
// ── Types ──────────────────────────────────────────────────────────────────
export interface LintViolation {
line_no: number;
line_pos: number;
code: string;
description: string;
name: string;
warning: boolean;
fixable: boolean;
}
export interface LintResult {
dialect: string;
violations: LintViolation[];
passed: boolean;
stats: { total: number; errors: number; warnings: number; fixable: number };
}
export interface AstNode {
id: string;
type: string;
name: string;
raw: string | null;
start_line: number | null;
start_pos: number | null;
end_line: number | null;
end_pos: number | null;
is_leaf: boolean;
children: AstNode[];
}
export interface ParseResult {
dialect: string;
tree: AstNode;
token_count: number;
depth: number;
}
export interface FormatResult {
dialect: string;
original: string;
formatted: string;
changed: boolean;
fixes_applied: number;
}
export interface InjectionPattern {
pattern_id: string;
risk_level: "critical" | "high" | "medium" | "low";
category: string;
description: string;
detail: string;
offending_token: string | null;
line_no: number | null;
line_pos: number | null;
recommendation: string;
}
export interface InjectionResult {
dialect: string;
safe: boolean;
risk_score: number;
patterns: InjectionPattern[];
summary: string;
}
export interface HealthResult {
status: string;
version: string;
dialects: string[];
}
// ── API calls ──────────────────────────────────────────────────────────────
export const api = {
health: () => get<HealthResult>("/api/health"),
lint: (sql: string, dialect: string) => post<LintResult>("/api/lint", { sql, dialect }),
parse: (sql: string, dialect: string) => post<ParseResult>("/api/parse", { sql, dialect }),
format: (sql: string, dialect: string) => post<FormatResult>("/api/format", { sql, dialect }),
inject: (sql: string, dialect: string) => post<InjectionResult>("/api/inject", { sql, dialect }),
};