import { RateLimitInfo, Algorithm, RateLimitStore } from '@jfungus/ratelimit'; export { Algorithm, CheckRateLimitOptions, CheckRateLimitResult, MemoryStore, RateLimitInfo, RateLimitStore, StoreResult, checkRateLimit, createRateLimiter } from '@jfungus/ratelimit'; import { Env, Context, MiddlewareHandler } from 'hono'; /** * @jfungus/ratelimit-hono - Rate limiting middleware for Hono * * @module */ /** * Quota unit for IETF standard headers. * @see https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/ */ type QuotaUnit = 'requests' | 'content-bytes' | 'concurrent-requests'; /** * Header format options. * * ## "legacy" (default) * Common X-RateLimit-* headers used by GitHub, Twitter, and most APIs: * - `X-RateLimit-Limit`: max requests in window * - `X-RateLimit-Remaining`: remaining requests * - `X-RateLimit-Reset`: Unix timestamp (seconds) when window resets * * ## "draft-6" * IETF draft-06 format with individual RateLimit-* headers: * - `RateLimit-Policy`: policy description (e.g., `100;w=60`) * - `RateLimit-Limit`: max requests * - `RateLimit-Remaining`: remaining requests * - `RateLimit-Reset`: seconds until reset * * ## "draft-7" * IETF draft-07 format with combined RateLimit header: * - `RateLimit-Policy`: policy description * - `RateLimit`: combined (e.g., `limit=100, remaining=50, reset=30`) * * ## "standard" * Current IETF draft-08+ format with structured field values (RFC 9651): * - `RateLimit-Policy`: `"name";q=100;w=60` * - `RateLimit`: `"name";r=50;t=30` * * ## false * Disable all rate limit headers. * * @see https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/ */ type HeadersFormat = 'legacy' | 'draft-6' | 'draft-7' | 'standard' | false; /** * Store access interface exposed in context */ type RateLimitStoreAccess = { /** Get rate limit info for a key. Returns undefined if key doesn't exist. */ getKey: (key: string) => Promise<{ count: number; reset: number; } | undefined> | { count: number; reset: number; } | undefined; /** Reset rate limit for a key */ resetKey: (key: string) => void | Promise; /** Reset all rate limit entries (if supported by store) */ resetAll?: () => void | Promise; }; /** * Options for rate limit middleware */ type RateLimitOptions = { /** * Maximum requests allowed in the time window. * @default 100 */ limit?: number | ((c: Context) => number | Promise); /** * Time window in milliseconds. * @default 60000 (1 minute) */ windowMs?: number; /** * Rate limiting algorithm. * @default 'sliding-window' */ algorithm?: Algorithm; /** * Storage backend for rate limit state. * @default MemoryStore */ store?: RateLimitStore; /** * Generate unique key for each client. * @default IP address from headers */ keyGenerator?: (c: Context) => string | Promise; /** * Handler called when rate limit is exceeded. */ handler?: (c: Context, info: RateLimitInfo) => Response | Promise; /** * HTTP header format to use. * * - "legacy": X-RateLimit-* headers (GitHub/Twitter style, default) * - "draft-6": IETF draft-06 individual headers * - "draft-7": IETF draft-07 combined header * - "standard": IETF draft-08+ structured fields (current spec) * - false: Disable headers * * @default 'legacy' */ headers?: HeadersFormat; /** * Policy identifier for IETF headers (draft-6+). * Used in RateLimit and RateLimit-Policy headers. * @default 'default' */ identifier?: string; /** * Quota unit for IETF standard headers. * Only included in "standard" format when not "requests". * @default 'requests' */ quotaUnit?: QuotaUnit; /** * Skip rate limiting for certain requests. */ skip?: (c: Context) => boolean | Promise; /** * Don't count successful (2xx) requests against limit. * @default false */ skipSuccessfulRequests?: boolean; /** * Don't count failed (4xx, 5xx) requests against limit. * @default false */ skipFailedRequests?: boolean; /** * Callback when a request is rate limited. */ onRateLimited?: (c: Context, info: RateLimitInfo) => void | Promise; /** * Behavior when store operations fail. * * - 'allow': Allow the request through (fail-open, default) * - 'deny': Block the request with 500 error (fail-closed) * - Function: Custom handler returning true to allow, false to deny * * @default 'allow' */ onStoreError?: 'allow' | 'deny' | ((error: Error, c: Context) => boolean | Promise); /** * Dry-run mode: track rate limits but don't block requests. * Useful for monitoring and testing before enforcing limits. * Headers and callbacks are still set/called normally. * @default false */ dryRun?: boolean; }; /** * Cloudflare Rate Limiting binding interface */ type RateLimitBinding = { limit: (options: { key: string; }) => Promise<{ success: boolean; }>; }; /** * Options for Cloudflare Rate Limiting binding */ type CloudflareRateLimitOptions = { /** * Cloudflare Rate Limiting binding from env */ binding: RateLimitBinding | ((c: Context) => RateLimitBinding); /** * Generate unique key for each client. */ keyGenerator: (c: Context) => string | Promise; /** * Handler called when rate limit is exceeded. */ handler?: (c: Context) => Response | Promise; /** * Skip rate limiting for certain requests. */ skip?: (c: Context) => boolean | Promise; }; declare module 'hono' { interface ContextVariableMap { rateLimit?: RateLimitInfo; rateLimitStore?: RateLimitStoreAccess; } } /** * Shutdown the default memory store. * Call this during graceful shutdown to clean up timers. * * @example * ```ts * import { shutdownDefaultStore } from '@jfungus/ratelimit-hono' * * process.on('SIGTERM', () => { * shutdownDefaultStore() * process.exit(0) * }) * ``` */ declare function shutdownDefaultStore(): void; /** * Extract client IP address from request headers. * * Checks headers in order of reliability: * 1. `CF-Connecting-IP` - Cloudflare's true client IP * 2. `X-Real-IP` - Common proxy header (nginx) * 3. `X-Forwarded-For` - Standard proxy header (first IP only) * * @param c - Hono context * @returns Client IP address or 'unknown' if not found * * @warning These headers can be spoofed. Only trust them behind a reverse proxy. */ declare function getClientIP(c: Context): string; /** * Rate Limit Middleware for Hono. * * @param {RateLimitOptions} [options] - Configuration options * @returns {MiddlewareHandler} Middleware handler * * @example * ```ts * import { Hono } from 'hono' * import { rateLimiter } from '@jfungus/ratelimit-hono' * * const app = new Hono() * * // Basic usage - 60 requests per minute * app.use(rateLimiter()) * * // Custom configuration * app.use('/api/*', rateLimiter({ * limit: 100, * windowMs: 60 * 1000, * })) * ``` */ declare const rateLimiter: (options?: RateLimitOptions) => MiddlewareHandler; /** * Rate limiter using Cloudflare's built-in Rate Limiting binding. * * This uses Cloudflare's globally distributed rate limiting infrastructure, * which is ideal for high-traffic applications. * * @example * ```ts * import { cloudflareRateLimiter } from '@jfungus/ratelimit-hono' * * type Bindings = { RATE_LIMITER: RateLimitBinding } * * const app = new Hono<{ Bindings: Bindings }>() * * app.use(cloudflareRateLimiter({ * binding: (c) => c.env.RATE_LIMITER, * keyGenerator: (c) => c.req.header('cf-connecting-ip') ?? 'unknown', * })) * ``` */ declare const cloudflareRateLimiter: (options: CloudflareRateLimitOptions) => MiddlewareHandler; export { type CloudflareRateLimitOptions, type HeadersFormat, type QuotaUnit, type RateLimitBinding, type RateLimitOptions, type RateLimitStoreAccess, cloudflareRateLimiter, getClientIP, rateLimiter, shutdownDefaultStore };