@limitrate/cli
Version:
CLI dashboard for LimitRate rate limiting inspection
160 lines (157 loc) • 4.05 kB
JavaScript
// 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
};