UNPKG

@limitrate/cli

Version:

CLI dashboard for LimitRate rate limiting inspection

160 lines (157 loc) 4.05 kB
// src/storage.ts import Database from "better-sqlite3"; import { existsSync, mkdirSync } from "fs"; import { join } from "path"; var EventStorage = class { db; constructor(dbPath = ".limitrate/history.db") { const dir = join(process.cwd(), ".limitrate"); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } this.db = new Database(join(process.cwd(), dbPath)); this.initSchema(); this.cleanup(); } initSchema() { this.db.exec(` CREATE TABLE IF NOT EXISTS events ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, user TEXT NOT NULL, plan TEXT NOT NULL, endpoint TEXT NOT NULL, type TEXT NOT NULL, window TEXT, value REAL, threshold REAL, created_at INTEGER DEFAULT (strftime('%s', 'now')) ); CREATE INDEX IF NOT EXISTS idx_timestamp ON events(timestamp); CREATE INDEX IF NOT EXISTS idx_endpoint ON events(endpoint); CREATE INDEX IF NOT EXISTS idx_type ON events(type); CREATE INDEX IF NOT EXISTS idx_user ON events(user); CREATE INDEX IF NOT EXISTS idx_created_at ON events(created_at); `); } /** * Save an event to storage */ saveEvent(event) { try { const stmt = this.db.prepare(` INSERT INTO events (timestamp, user, plan, endpoint, type, window, value, threshold) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `); stmt.run( event.timestamp, event.user, event.plan, event.endpoint, event.type, event.window || null, event.value || null, event.threshold || null ); } catch (error) { console.error("[LimitRate CLI] Failed to save event:", error); } } /** * Get endpoint statistics */ getEndpointStats() { const stmt = this.db.prepare(` SELECT endpoint, COUNT(*) as totalHits, SUM(CASE WHEN type IN ('rate_exceeded', 'cost_exceeded', 'blocked') THEN 1 ELSE 0 END) as blocked, SUM(CASE WHEN type = 'slowdown_applied' THEN 1 ELSE 0 END) as slowdowns FROM events WHERE created_at > strftime('%s', 'now') - 172800 -- Last 48 hours GROUP BY endpoint ORDER BY totalHits DESC `); return stmt.all(); } /** * Get top offenders (users with most blocks) */ getTopOffenders(limit = 10) { const stmt = this.db.prepare(` SELECT user, plan, COUNT(*) as blocks FROM events WHERE type IN ('rate_exceeded', 'cost_exceeded', 'blocked') AND created_at > strftime('%s', 'now') - 3600 -- Last hour GROUP BY user, plan ORDER BY blocks DESC LIMIT ? `); return stmt.all(limit); } /** * Get recent events */ getRecentEvents(limit = 10) { const stmt = this.db.prepare(` SELECT * FROM events ORDER BY timestamp DESC LIMIT ? `); return stmt.all(limit); } /** * Clean up events older than 48 hours */ cleanup() { try { const stmt = this.db.prepare(` DELETE FROM events WHERE created_at < strftime('%s', 'now') - 172800 `); const result = stmt.run(); if (result.changes > 0) { console.log(`[LimitRate CLI] Cleaned up ${result.changes} old events`); } } catch (error) { console.error("[LimitRate CLI] Cleanup failed:", error); } } /** * Get total event count */ getEventCount() { const stmt = this.db.prepare(` SELECT COUNT(*) as count FROM events WHERE created_at > strftime('%s', 'now') - 172800 `); const result = stmt.get(); return result.count; } /** * Close database connection */ close() { this.db.close(); } }; var storageInstance = null; function getStorage() { if (!storageInstance) { storageInstance = new EventStorage(); } return storageInstance; } function saveEvent(event) { getStorage().saveEvent(event); } export { EventStorage, getStorage, saveEvent };