// --------------------------------------------------------------------------- // Query History — automatisches Tracking aller ausgefuehrten Queries // --------------------------------------------------------------------------- export interface HistoryEntry { id: string; sql: string; executedAt: string; executionTimeMs?: number; rowCount?: number; isFavorite: boolean; name?: string; } const STORAGE_KEY = 'logfire-query-history'; const MAX_ENTRIES = 200; export class HistoryManager { private entries = new Map(); private listeners = new Set<() => void>(); constructor() { this.load(); } // --------------------------------------------------------------------------- // Persistence // --------------------------------------------------------------------------- private load(): void { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return; try { const arr: HistoryEntry[] = JSON.parse(raw); for (const e of arr) this.entries.set(e.id, e); } catch { /* ignore */ } } private save(): void { localStorage.setItem( STORAGE_KEY, JSON.stringify(Array.from(this.entries.values())), ); for (const fn of this.listeners) fn(); } onChange(fn: () => void): () => void { this.listeners.add(fn); return () => this.listeners.delete(fn); } // --------------------------------------------------------------------------- // CRUD // --------------------------------------------------------------------------- addEntry(sql: string, executionTimeMs?: number, rowCount?: number): HistoryEntry { // Deduplizieren: gleiche SQL aktualisiert vorhandenen Eintrag for (const entry of this.entries.values()) { if (entry.sql === sql) { entry.executedAt = new Date().toISOString(); entry.executionTimeMs = executionTimeMs; entry.rowCount = rowCount; this.save(); return entry; } } const entry: HistoryEntry = { id: `h-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, sql, executedAt: new Date().toISOString(), executionTimeMs, rowCount, isFavorite: false, }; this.entries.set(entry.id, entry); // Limit: aelteste nicht-favorisierte loeschen if (this.entries.size > MAX_ENTRIES) { const sorted = this.getAll(); for (const e of sorted.slice(MAX_ENTRIES)) { if (!e.isFavorite) this.entries.delete(e.id); } } this.save(); return entry; } getAll(searchTerm?: string): HistoryEntry[] { let results = Array.from(this.entries.values()); if (searchTerm) { const term = searchTerm.toLowerCase(); results = results.filter( e => e.sql.toLowerCase().includes(term) || e.name?.toLowerCase().includes(term), ); } return results.sort( (a, b) => new Date(b.executedAt).getTime() - new Date(a.executedAt).getTime(), ); } toggleFavorite(id: string): void { const e = this.entries.get(id); if (e) { e.isFavorite = !e.isFavorite; this.save(); } } setName(id: string, name: string): void { const e = this.entries.get(id); if (e) { e.name = name || undefined; this.save(); } } delete(id: string): void { this.entries.delete(id); this.save(); } clear(): void { for (const [id, entry] of this.entries) { if (!entry.isFavorite) this.entries.delete(id); } this.save(); } }