@developers-joyride/rate-limiter
Version:
A flexible rate limiting library with TypeScript support, Express middleware, and NestJS guard/interceptor capabilities
129 lines (128 loc) • 4.64 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MongoDBCacheProvider = void 0;
const mongoose_1 = __importDefault(require("mongoose"));
const rate_limit_model_1 = require("../models/rate-limit.model");
class MongoDBCacheProvider {
constructor(config) {
this.isConnected = false;
if (config.type !== "mongodb") {
throw new Error('MongoDBCacheProvider requires type to be "mongodb"');
}
if (!config.mongoUrl) {
throw new Error("MongoDB connection URL is required");
}
this.config = {
type: "mongodb",
mongoUrl: config.mongoUrl,
collectionName: config.collectionName || "rateLimitingLogs",
redisUrl: "",
redisKeyPrefix: "rate_limit:",
};
}
async initialize() {
if (this.isConnected) {
return;
}
try {
await mongoose_1.default.connect(this.config.mongoUrl);
this.isConnected = true;
// Set up TTL index for automatic cleanup
const collection = mongoose_1.default.connection.collection(this.config.collectionName);
await collection.createIndex({ createdAt: 1 }, { expireAfterSeconds: 86400 } // 24 hours default TTL
);
console.log("MongoDB cache provider initialized successfully");
}
catch (error) {
console.error("Failed to initialize MongoDB cache provider:", error);
throw error;
}
}
async checkLimit(key, maxRequests, windowMs) {
await this.ensureConnection();
const now = new Date();
const resetTime = new Date(now.getTime() + windowMs * 1000);
try {
// Find existing rate limit record
let rateLimitLog = await rate_limit_model_1.RateLimitLog.findOne({ key });
if (!rateLimitLog) {
// Create new rate limit record
rateLimitLog = new rate_limit_model_1.RateLimitLog({
key,
count: 1,
resetTime,
createdAt: now,
});
await rateLimitLog.save();
}
else {
// Check if the window has expired
if (rateLimitLog.resetTime <= now) {
// Reset the counter for new window
rateLimitLog.count = 1;
rateLimitLog.resetTime = resetTime;
rateLimitLog.createdAt = now;
}
else {
// Increment the counter
rateLimitLog.count += 1;
}
await rateLimitLog.save();
}
const allowed = rateLimitLog.count <= maxRequests;
const remaining = Math.max(0, maxRequests - rateLimitLog.count);
return {
allowed,
current: rateLimitLog.count,
limit: maxRequests,
windowMs,
resetTime: rateLimitLog.resetTime.toISOString(),
remaining,
};
}
catch (error) {
console.error("Error checking rate limit in MongoDB:", error);
// In case of error, allow the request to prevent blocking
return {
allowed: true,
current: 0,
limit: maxRequests,
windowMs,
resetTime: resetTime.toISOString(),
remaining: maxRequests,
};
}
}
async resetLimit(key) {
await this.ensureConnection();
await rate_limit_model_1.RateLimitLog.deleteOne({ key });
}
async getLimitInfo(key) {
await this.ensureConnection();
const rateLimitLog = await rate_limit_model_1.RateLimitLog.findOne({ key });
if (!rateLimitLog) {
return null;
}
return {
key: rateLimitLog.key,
count: rateLimitLog.count,
resetTime: rateLimitLog.resetTime,
createdAt: rateLimitLog.createdAt,
};
}
async ensureConnection() {
if (!this.isConnected) {
await this.initialize();
}
}
async close() {
if (this.isConnected) {
await mongoose_1.default.connection.close();
this.isConnected = false;
}
}
}
exports.MongoDBCacheProvider = MongoDBCacheProvider;