File size: 3,323 Bytes
42ae0b9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | /**
* 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 }),
};
|