sequelae-mcp
Version:
Let Claude, Cursor, and other AI agents run real SQL queries on live Postgres databases. No more copy-pasting SQL, stale schema docs, or hallucinated DB adapters — just raw, real-time access. Now with MCP support!
118 lines • 4.25 kB
JavaScript
;
/**
* Simple rate limiter implementation using token bucket algorithm
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RateLimiter = void 0;
class RateLimiter {
constructor(options) {
this.options = options;
this.requests = new Map();
this.globalRequests = {
count: 0,
resetTime: Date.now() + this.options.windowMs,
};
}
/**
* Check if a request is allowed and update counters
* @param identifier - Unique identifier for the requester (e.g., connection ID)
* @param tool - Optional tool name for tool-specific limits
* @returns Object with allowed status and retry-after time if rate limited
*/
checkLimit(identifier, tool) {
const now = Date.now();
// Check tool-specific limit if applicable
if (tool && this.options.toolSpecificLimits?.[tool]) {
const toolLimit = this.options.toolSpecificLimits[tool];
const toolKey = `${identifier}:${tool}`;
const toolRecord = this.getOrCreateRecord(toolKey, now, toolLimit.windowMs);
if (toolRecord.count >= toolLimit.maxRequests) {
return {
allowed: false,
retryAfter: Math.ceil((toolRecord.resetTime - now) / 1000),
};
}
}
// Check global limit
const record = this.getOrCreateRecord(identifier, now, this.options.windowMs);
if (record.count >= this.options.maxRequests) {
return {
allowed: false,
retryAfter: Math.ceil((record.resetTime - now) / 1000),
};
}
// Increment counters
record.count++;
if (tool && this.options.toolSpecificLimits?.[tool]) {
const toolKey = `${identifier}:${tool}`;
const toolRecord = this.requests.get(toolKey);
if (toolRecord) {
toolRecord.count++;
}
}
return { allowed: true };
}
/**
* Get or create a request record
*/
getOrCreateRecord(key, now, windowMs) {
let record = this.requests.get(key);
if (!record || record.resetTime <= now) {
record = {
count: 0,
resetTime: now + windowMs,
};
this.requests.set(key, record);
}
return record;
}
/**
* Clean up expired records to prevent memory leaks
*/
cleanup() {
const now = Date.now();
for (const [key, record] of this.requests.entries()) {
if (record.resetTime <= now) {
this.requests.delete(key);
}
}
}
/**
* Get current usage statistics for a requester
*/
getUsage(identifier) {
const now = Date.now();
const globalRecord = this.requests.get(identifier);
const globalUsed = globalRecord && globalRecord.resetTime > now ? globalRecord.count : 0;
const globalResetIn = globalRecord
? Math.max(0, Math.ceil((globalRecord.resetTime - now) / 1000))
: 0;
const result = {
global: {
used: globalUsed,
limit: this.options.maxRequests,
resetIn: globalResetIn,
},
};
// Add tool-specific usage if configured
if (this.options.toolSpecificLimits) {
result.tools = {};
for (const [tool, limits] of Object.entries(this.options.toolSpecificLimits)) {
const toolKey = `${identifier}:${tool}`;
const toolRecord = this.requests.get(toolKey);
const toolUsed = toolRecord && toolRecord.resetTime > now ? toolRecord.count : 0;
const toolResetIn = toolRecord
? Math.max(0, Math.ceil((toolRecord.resetTime - now) / 1000))
: 0;
result.tools[tool] = {
used: toolUsed,
limit: limits.maxRequests,
resetIn: toolResetIn,
};
}
}
return result;
}
}
exports.RateLimiter = RateLimiter;
//# sourceMappingURL=rate-limiter.js.map