import type Database from "better-sqlite3"; export interface SymbolRef { caller_name: string; caller_path: string; caller_line: number; is_server_action: boolean; depth: number; path_chain: string; } export function querySymbolRefs( db: Database.Database, symbolName: string, maxDepth = 10 ): SymbolRef[] { const sql = ` WITH RECURSIVE upstream(caller_id, caller_name, caller_path, caller_line, is_sa, depth, path_chain) AS ( SELECT c.caller_id, s.name, f.path, c.call_line, s.is_server_action, 0, s.name FROM calls c JOIN symbols s ON c.caller_id = s.id JOIN files f ON s.file_id = f.id WHERE c.callee_id = (SELECT id FROM symbols WHERE name = ? LIMIT 1) UNION SELECT c.caller_id, s.name, f.path, c.call_line, s.is_server_action, u.depth + 1, u.path_chain || ' → ' || s.name FROM upstream u JOIN calls c ON c.callee_id = u.caller_id JOIN symbols s ON c.caller_id = s.id JOIN files f ON s.file_id = f.id WHERE u.depth < ? ) SELECT caller_name, caller_path, caller_line, is_sa as is_server_action, depth, path_chain FROM upstream ORDER BY depth, caller_path`; return db.prepare(sql).all(symbolName, maxDepth) as SymbolRef[]; } export interface SymbolCall { callee_name: string; callee_path: string; callee_line: number; depth: number; path_chain: string; } export function querySymbolForwardCalls( db: Database.Database, symbolName: string, maxDepth = 10 ): SymbolCall[] { const sql = ` WITH RECURSIVE downstream(callee_id, callee_name, callee_path, callee_line, depth, path_chain) AS ( SELECT c.callee_id, s.name, f.path, c.call_line, 0, ? FROM calls c JOIN symbols s ON c.callee_id = s.id JOIN files f ON s.file_id = f.id WHERE c.caller_id = (SELECT id FROM symbols WHERE name = ? LIMIT 1) UNION SELECT c.callee_id, s.name, f.path, c.call_line, d.depth + 1, d.path_chain || ' → ' || s.name FROM downstream d JOIN calls c ON c.caller_id = d.callee_id JOIN symbols s ON c.callee_id = s.id JOIN files f ON s.file_id = f.id WHERE d.depth < ? ) SELECT callee_name, callee_path, callee_line, depth, path_chain FROM downstream ORDER BY depth, callee_path`; return db.prepare(sql).all(symbolName, symbolName, maxDepth) as SymbolCall[]; } export interface ModuleDep { target_module: string; dep_type: string; } export function queryModuleDeps( db: Database.Database, moduleName: string, _maxDepth = 10 ): ModuleDep[] { const sql = ` SELECT DISTINCT m2.name AS target_module, md.dep_type FROM module_deps md JOIN modules m ON md.source_module_id = m.id JOIN modules m2 ON md.target_module_id = m2.id WHERE m.name = ?`; return db.prepare(sql).all(moduleName) as ModuleDep[]; } export interface ModuleReverseDep { source_module: string; dep_type: string; } export function queryModuleReverseDeps( db: Database.Database, moduleName: string, _maxDepth = 10 ): ModuleReverseDep[] { const sql = ` SELECT DISTINCT m.name AS source_module, md.dep_type FROM module_deps md JOIN modules m ON md.source_module_id = m.id JOIN modules m2 ON md.target_module_id = m2.id WHERE m2.name = ?`; return db.prepare(sql).all(moduleName) as ModuleReverseDep[]; } export interface TechUsage { symbol_name: string; file_path: string; tag_name: string; } export function queryTechUsage( db: Database.Database, tagName: string ): TechUsage[] { const sql = ` SELECT s.name AS symbol_name, f.path AS file_path, t.name AS tag_name FROM symbols s JOIN symbol_tech_tags stt ON s.id = stt.symbol_id JOIN tech_tags t ON stt.tag_id = t.id JOIN files f ON s.file_id = f.id WHERE t.name = ?`; return db.prepare(sql).all(tagName) as TechUsage[]; } export interface Violations { long_files: { path: string; lines: number }[]; server_actions_without_permission: { name: string; path: string }[]; } export function queryViolations(db: Database.Database): Violations { const longFiles = db .prepare("SELECT path, lines FROM files WHERE lines > 800 ORDER BY lines DESC") .all() as { path: string; lines: number }[]; const serverActionsWithoutPerm = db .prepare( `SELECT s.name, f.path FROM symbols s JOIN files f ON s.file_id = f.id WHERE s.is_server_action = 1 AND NOT EXISTS ( SELECT 1 FROM calls c JOIN symbols cs ON c.callee_id = cs.id WHERE c.caller_id = s.id AND cs.name = 'requirePermission' )` ) .all() as { name: string; path: string }[]; return { long_files: longFiles, server_actions_without_permission: serverActionsWithoutPerm, }; }