aboutsummaryrefslogtreecommitdiffstats
path: root/packages/plugins/ratelimit-memory/src/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/plugins/ratelimit-memory/src/index.ts')
-rw-r--r--packages/plugins/ratelimit-memory/src/index.ts86
1 files changed, 86 insertions, 0 deletions
diff --git a/packages/plugins/ratelimit-memory/src/index.ts b/packages/plugins/ratelimit-memory/src/index.ts
new file mode 100644
index 00000000..c47889d3
--- /dev/null
+++ b/packages/plugins/ratelimit-memory/src/index.ts
@@ -0,0 +1,86 @@
+import type {
+ RateLimitClient,
+ RateLimitConfig,
+ RateLimitResult,
+} from "@karakeep/shared/ratelimiting";
+import { PluginProvider } from "@karakeep/shared/plugins";
+
+interface RateLimitEntry {
+ count: number;
+ resetTime: number;
+}
+
+export class RateLimiter implements RateLimitClient {
+ private store = new Map<string, RateLimitEntry>();
+ private cleanupProbability: number;
+
+ constructor(cleanupProbability = 0.01) {
+ // Probability of cleanup on each check (default 1%)
+ this.cleanupProbability = cleanupProbability;
+ }
+
+ private cleanupExpiredEntries() {
+ const now = Date.now();
+ for (const [key, entry] of this.store.entries()) {
+ if (now > entry.resetTime) {
+ this.store.delete(key);
+ }
+ }
+ }
+
+ checkRateLimit(config: RateLimitConfig, key: string): RateLimitResult {
+ if (!key) {
+ return { allowed: true };
+ }
+
+ // Probabilistic cleanup
+ if (Math.random() < this.cleanupProbability) {
+ this.cleanupExpiredEntries();
+ }
+
+ const rateLimitKey = `${config.name}:${key}`;
+ const now = Date.now();
+
+ let entry = this.store.get(rateLimitKey);
+
+ if (!entry || now > entry.resetTime) {
+ entry = {
+ count: 1,
+ resetTime: now + config.windowMs,
+ };
+ this.store.set(rateLimitKey, entry);
+ return { allowed: true };
+ }
+
+ if (entry.count >= config.maxRequests) {
+ const resetInSeconds = Math.ceil((entry.resetTime - now) / 1000);
+ return {
+ allowed: false,
+ resetInSeconds,
+ };
+ }
+
+ entry.count++;
+ return { allowed: true };
+ }
+
+ reset(config: RateLimitConfig, key: string) {
+ const rateLimitKey = `${config.name}:${key}`;
+ this.store.delete(rateLimitKey);
+ }
+
+ clear() {
+ this.store.clear();
+ }
+}
+
+export class RateLimitProvider implements PluginProvider<RateLimitClient> {
+ private client: RateLimiter | null = null;
+
+ async getClient(): Promise<RateLimitClient | null> {
+ if (!this.client) {
+ this.client = new RateLimiter();
+ }
+ return this.client;
+ }
+}