powpow/proto/ratelimit

Sliding-window rate limiter built on powpow's event loop.

Supports multiple time windows per limiter — e.g. per-IP hourly + daily quotas checked atomically. The two-phase allow verifies every window before incrementing any, so a message rejected by the daily quota is not overcounted against the hourly.

Single-window (backward-compatible): let rl = newRateLimiter(loop, maxRequests = 100, windowMs = 60_000)

Multi-window: let rl = newMultiRateLimiter(loop, (100, 3_600_000), (1_000, 86_400_000))

Types

RateLimiter = ref object
  loop*: Loop
WindowLimit = tuple[maxRequests: int, windowMs: int]

Procs

proc allow(rl: RateLimiter; key: string): bool {....raises: [KeyError],
    tags: [TimeEffect], forbids: [].}
Check whether key is allowed across ALL configured windows. Returns true only when every non-unlimited window still has capacity; increments the count atomically on success. Returns true immediately for empty keys or when every rule is unlimited (maxRequests <= 0).
proc check(rl: RateLimiter; req: HttpRequest; res: HttpResponse): bool {.inline,
    ...raises: [KeyError, OSError, Exception], tags: [TimeEffect, RootEffect],
    forbids: [].}
proc close(rl: RateLimiter) {....raises: [], tags: [], forbids: [].}
Stop the cleanup timer and release the limiter's lock.
proc newMultiRateLimiter(loop: Loop; limits: openArray[WindowLimit];
                         enableCleanup = true): RateLimiter {....raises: [],
    tags: [TimeEffect], forbids: [].}
Create a sliding-window rate limiter with one or more time windows. Each limit is (maxRequests, windowMs) — entries with maxRequests <= 0 are treated as unlimited (always allowed, never counted).
proc newRateLimiter(loop: Loop; maxRequests: int; windowMs: int;
                    enableCleanup = true): RateLimiter {....raises: [],
    tags: [TimeEffect], forbids: [].}
Single-window convenience constructor (backward-compatible).