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 }),
};