mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Add a custom RateLimiter implementation
This commit is contained in:
parent
b8d288868d
commit
12de164a4f
3 changed files with 99 additions and 0 deletions
77
src/utils/ratelimiter/MemoryRateLimiter.ts
Normal file
77
src/utils/ratelimiter/MemoryRateLimiter.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { RateLimitError } from './RateLimitError.ts';
|
||||
import { RateLimiter, RateLimiterClient } from './types.ts';
|
||||
|
||||
interface MemoryRateLimiterOpts {
|
||||
limit: number;
|
||||
window: number;
|
||||
}
|
||||
|
||||
export class MemoryRateLimiter implements RateLimiter {
|
||||
private iid: number;
|
||||
|
||||
private previous = new Map<string, RateLimiterClient>();
|
||||
private current = new Map<string, RateLimiterClient>();
|
||||
|
||||
constructor(private opts: MemoryRateLimiterOpts) {
|
||||
this.iid = setInterval(() => {
|
||||
this.previous = this.current;
|
||||
this.current = new Map();
|
||||
}, opts.window);
|
||||
}
|
||||
|
||||
get limit(): number {
|
||||
return this.opts.limit;
|
||||
}
|
||||
|
||||
get window(): number {
|
||||
return this.opts.window;
|
||||
}
|
||||
|
||||
client(key: string): RateLimiterClient {
|
||||
const curr = this.current.get(key);
|
||||
const prev = this.previous.get(key);
|
||||
|
||||
if (curr) {
|
||||
return curr;
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
this.current.set(key, prev);
|
||||
this.previous.delete(key);
|
||||
return prev;
|
||||
}
|
||||
|
||||
const next = new MemoryRateLimiterClient(this);
|
||||
this.current.set(key, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
[Symbol.dispose](): void {
|
||||
clearInterval(this.iid);
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryRateLimiterClient implements RateLimiterClient {
|
||||
private _hits: number = 0;
|
||||
readonly resetAt: Date;
|
||||
|
||||
constructor(private limiter: MemoryRateLimiter) {
|
||||
this.resetAt = new Date(Date.now() + limiter.window);
|
||||
}
|
||||
|
||||
get hits(): number {
|
||||
return this._hits;
|
||||
}
|
||||
|
||||
get remaining(): number {
|
||||
return this.limiter.limit - this.hits;
|
||||
}
|
||||
|
||||
hit(n: number = 1): void {
|
||||
this._hits += n;
|
||||
|
||||
if (this.remaining < 0) {
|
||||
throw new RateLimitError(this.limiter, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/utils/ratelimiter/RateLimitError.ts
Normal file
10
src/utils/ratelimiter/RateLimitError.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { RateLimiter, RateLimiterClient } from './types.ts';
|
||||
|
||||
export class RateLimitError extends Error {
|
||||
constructor(
|
||||
readonly limiter: RateLimiter,
|
||||
readonly client: RateLimiterClient,
|
||||
) {
|
||||
super('Rate limit exceeded');
|
||||
}
|
||||
}
|
||||
12
src/utils/ratelimiter/types.ts
Normal file
12
src/utils/ratelimiter/types.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export interface RateLimiter extends Disposable {
|
||||
readonly limit: number;
|
||||
readonly window: number;
|
||||
client(key: string): RateLimiterClient;
|
||||
}
|
||||
|
||||
export interface RateLimiterClient {
|
||||
readonly hits: number;
|
||||
readonly resetAt: Date;
|
||||
readonly remaining: number;
|
||||
hit(n?: number): void;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue