### Example IP Resolution Scenarios Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Provides examples of how Bun's requestIP() method might resolve IP addresses in different scenarios, including direct connections and behind reverse proxies. This helps understand the potential outcomes of the defaultKeyGenerator. ```typescript // Direct connection // requestIP() → { address: '203.0.113.1', port: 12345 } // Result: '203.0.113.1' // Behind reverse proxy (if configured properly) // X-Forwarded-For: 198.51.100.1, 203.0.113.5 // requestIP() → might respect the header and return 198.51.100.1 // Result: '198.51.100.1' // Unavailable (edge case) // requestIP() → null // Result: '' (empty string, warning logged) ``` -------------------------------- ### Install Elysia Rate Limit Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/README.md Install the elysia-rate-limit package using Bun. ```bash bun add elysia-rate-limit ``` -------------------------------- ### init Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/DefaultContext.md Initializes the context with configuration options. This method is called automatically by the `rateLimit` plugin during setup and should not be called manually. ```APIDOC ## init(options: Omit): void ### Description Initializes the context with configuration options. This method is called automatically by the `rateLimit` plugin during setup and should not be called manually. It logs initialization details, sets up the internal LRU cache, and stores the duration value. ### Parameters #### Path Parameters * **options** (`Omit`) - Required - The plugin configuration options (everything except the context option). Used to extract the duration setting for logging and initialization. ``` -------------------------------- ### Run Benchmark Script Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/BENCHMARK.md Execute the benchmark script using Bun to measure performance. Ensure you have Bun installed. ```bash bun run benchmark/index.ts ``` -------------------------------- ### Configuration Error Messages Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/README.md Displays example error messages for invalid duration or max limit configurations. ```text Invalid duration resolved: X. Duration must be a positive number. Invalid max limit resolved: X. Max must be a positive number. ``` -------------------------------- ### Custom Redis Context Implementation Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/configuration.md An example structure for a custom context implementation using Redis. This demonstrates how to integrate external storage for rate limiting. ```typescript import type { Context } from 'elysia-rate-limit' import type { Options } from 'elysia-rate-limit' class RedisContext implements Context { private redis: any // Your Redis client init(options: Omit) { // Initialize with options } async increment(key: string, duration?: number, requestTime?: number) { const count = await this.redis.incr(key) const reset = await this.redis.ttl(key) return { count, nextReset: new Date(), start: requestTime || Date.now() } } async decrement(key: string) { await this.redis.decr(key) } async reset(key?: string) { if (key) await this.redis.del(key) else await this.redis.flushdb() } async kill() { await this.redis.flushdb() } } ``` -------------------------------- ### Static Duration and Max Configuration Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Example of setting static values for duration and maximum requests. These are resolved once when the plugin is registered. ```typescript duration: 60000 // Resolved once at plugin registration max: 100 ``` -------------------------------- ### Example Debug Output Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/index.md View sample output from the debug logs, illustrating rate limiting decisions, key generation, and token issuance. This helps in understanding the plugin's internal state. ```text elysia-rate-limit:generator clientAddress: 192.168.1.1 +0ms elysia-rate-limit:context:abc123 issued new token for 192.168.1.1 (expire in 60 seconds) +2ms elysia-rate-limit:plugin clientKey 192.168.1.1 passed through with 1/10 request used (resetting in 60 seconds) +1ms ``` -------------------------------- ### Dynamic Duration and Max Configuration Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Example of using functions to dynamically resolve duration and maximum requests per incoming request. These functions are evaluated for each request. ```typescript duration: (key, request) => { ... } // Resolved per request max: (key, request) => { ... } ``` -------------------------------- ### Unit Testing Context Initialization Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Demonstrates how to unit test the rate limit context by initializing it with default options and then calling `increment` and `decrement` methods. Asserts the expected count and start times for rate limiting logic. ```typescript const context = new DefaultContext() context.init(defaultOptions) const result1 = await context.increment('key1', 60000, 1000) assert(result1.count === 1) assert(result1.start === 1000) const result2 = await context.increment('key1', 60000, 1010) assert(result2.count === 2) await context.decrement('key1') const result3 = await context.increment('key1', 60000, 1020) assert(result3.count === 2) // Starts at 2 again ``` -------------------------------- ### Configure Dynamic Maximum Requests Based on Request Headers Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/README.md Dynamically set the maximum number of requests based on request headers. This example allows more requests for premium users. ```typescript new Elysia().use( rateLimit({ max: (key, request) => { // Give premium users higher limits const isPremium = request.headers.get('X-User-Tier') === 'premium' return isPremium ? 1000 : 100 }, }) ) ``` -------------------------------- ### Global Scoping Example Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Demonstrates global scoping where all Elysia instances share the same rate limit context and counts. Use this when a single rate limit policy should apply across multiple API instances. ```typescript const api1 = new Elysia().use(rateLimit()) const api2 = new Elysia().use(rateLimit()) const app = new Elysia().use(api1).use(api2) // All instances share: // - Same Context instance // - Same rate limit counts ``` -------------------------------- ### Example: Failed Request Refund Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/errors.md Demonstrates how failed requests are handled when `countFailedRequest` is set to false. In this scenario, errors do not count towards the rate limit and are refunded. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' const app = new Elysia() .use(rateLimit({ max: 3, countFailedRequest: false, })) .get('/api', () => { throw new Error('Something failed') }) // Client makes 3 requests: // Request 1: Count = 1, then error occurs, refunded → Count = 0 // Request 2: Count = 1, then error occurs, refunded → Count = 0 // Request 3: Count = 1, then error occurs, refunded → Count = 0 // Client can keep making requests despite failures ``` -------------------------------- ### InMemoryContext implementation Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Context.md A simple in-memory context implementation for Elysia Rate Limit. It uses a Map to store rate limiting data. No external setup is needed. ```typescript import type { Context } from 'elysia-rate-limit' import type { Options } from 'elysia-rate-limit' class InMemoryContext implements Context { private store = new Map() init(options: Omit) { // No setup needed for in-memory storage } increment(key: string, duration?: number, requestTime?: number) { const now = requestTime ?? Date.now() const effectiveDuration = duration ?? 60000 let item = this.store.get(key) if (!item || item.nextReset.getTime() <= now) { item = { count: 1, nextReset: new Date(now + effectiveDuration), start: now, } } else { item.count++ } this.store.set(key, item) return item } decrement(key: string) { const item = this.store.get(key) if (item && item.count > 0) { item.count-- } } reset(key?: string) { if (typeof key === 'string') { this.store.delete(key) } else { this.store.clear() } } kill() { this.store.clear() } } ``` -------------------------------- ### Configure Dynamic Duration Based on Request Headers Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/README.md Dynamically set the rate limiting duration based on request headers. This example provides a shorter window for premium users. ```typescript new Elysia().use( rateLimit({ duration: (key, request) => { // Give premium users a shorter window const isPremium = request.headers.get('X-User-Tier') === 'premium' return isPremium ? 10_000 : 60_000 }, }) ) ``` -------------------------------- ### Custom Key Generator using IP Hash Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/types.md Example of a custom generator that hashes the client's IP address for rate limiting. It utilizes the derived plugin context to access the IP. ```typescript // Hash-based generator using derived plugin context const ipHashGenerator: Generator<{ ip: string }> = (request, server, { ip }) => { return Bun.hash(ip).toString() } ``` -------------------------------- ### Global Rate Limiting (Single Instance) Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/index.md Apply rate limiting globally to all requests in a single Elysia instance. This is the simplest setup for basic protection. ```typescript const app = new Elysia() .use(rateLimit()) // Global, shared context .listen(3000) ``` -------------------------------- ### Custom Cloudflare IP Generator Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Provides an example of a custom key generator designed for environments behind Cloudflare. It prioritizes the 'CF-Connecting-IP' header and falls back to the default IP detection if the header is not present. ```typescript import type { Generator } from 'elysia-rate-limit' const cloudflareGenerator: Generator = (request, server) => { // Cloudflare passes original IP in CF-Connecting-IP header return request.headers.get('CF-Connecting-IP') ?? server?.requestIP(request)?.address ?? '' } ``` -------------------------------- ### Custom Key Generator by User ID Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/types.md Example of a custom generator that uses the 'X-User-ID' header to generate a rate limiting key. Defaults to 'anonymous' if the header is not present. ```typescript import type { Generator } from 'elysia-rate-limit' // Rate limit by user ID from header const userIdGenerator: Generator = (request) => { return request.headers.get('X-User-ID') || 'anonymous' } ``` -------------------------------- ### Default Generator Usage in Elysia Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Demonstrates how the defaultKeyGenerator is used automatically when the rateLimit plugin is applied without a custom generator. This is the standard setup for IP-based rate limiting. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' const app = new Elysia() .use(rateLimit()) // Uses defaultKeyGenerator internally .get('/', () => 'hello') // Request from 192.168.1.1: // Key = '192.168.1.1' ``` -------------------------------- ### Scoped Scoping Example Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Illustrates scoped scoping where each Elysia instance maintains its own rate limit context and counts. This is useful when independent rate limiting is required for different API instances. ```typescript const api1 = new Elysia().use(rateLimit({ scoping: 'scoped' })) const api2 = new Elysia().use(rateLimit({ scoping: 'scoped' })) const app = new Elysia().use(api1).use(api2) // Each instance has: // - Separate Context instance // - Independent rate limit counts ``` -------------------------------- ### Handling Dynamic Configuration Function Throwing Error Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/errors.md Illustrates a case where a custom dynamic configuration function (duration) throws an exception during execution, for example, due to a database connection failure. The plugin's behavior depends on the errorResponse configuration. ```typescript const app = new Elysia() .use(rateLimit({ duration: async (key, request) => { // BUG: Database connection fails const tier = await getFromDB(key) // throws return tier === 'premium' ? 120000 : 60000 }, })) // Request will fail when the promise rejects ``` -------------------------------- ### init(options: Omit): void Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Context.md Initializes the storage backend with plugin configuration. Called once during plugin registration. ```APIDOC ## init ### Description Called once during plugin registration. Use this to initialize your storage backend with the plugin configuration. The `options` parameter contains: - `duration`: Static number or dynamic function - `max`: Static number or dynamic function - `errorResponse`: How to respond when limit exceeded - `scoping`: 'global' or 'scoped' - `countFailedRequest`: boolean - `generator`: Key generation function - `skip`: Request skip function - `injectServer`: Optional server injection function - `headers`: Whether to set rate limit headers ### Parameters #### Path Parameters * None #### Query Parameters * None #### Request Body * None ### Method * None (This is a method of the Context interface) ### Endpoint * None (This is a method of the Context interface) ### Returns `void` ### Code Example ```typescript import type { Context } from 'elysia-rate-limit' import type { Options } from 'elysia-rate-limit' class CustomContext implements Context { private options!: Omit init(options: Omit) { this.options = options // Initialize your backend here console.log(`Initialized with duration: ${typeof options.duration === 'function' ? 'dynamic' : options.duration}`) } // ... other methods } ``` ``` -------------------------------- ### Increment Return Type Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/types.md Specifies the structure of the object returned by the `increment` method, detailing request count, reset time, and window start. ```typescript { count: number // Total requests in this window (1-indexed) nextReset: Date // When this window expires and count resets to 0 start: number // Unix timestamp when this window started } ``` -------------------------------- ### Initialize Custom Context Storage Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Context.md Implement the init method to set up your custom storage backend with plugin configuration. This is called once during plugin registration. ```typescript import type { Context } from 'elysia-rate-limit' import type { Options } from 'elysia-rate-limit' class CustomContext implements Context { private options!: Omit init(options: Omit) { this.options = options // Initialize your backend here console.log(`Initialized with duration: ${typeof options.duration === 'function' ? 'dynamic' : options.duration}`) } // ... other methods } ``` -------------------------------- ### Benchmark Results Summary Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/BENCHMARK.md This section displays the raw benchmark data, including average time per iteration, p75/p99 percentiles, and data transfer sizes for different rate limiting configurations. It also includes a visual representation and a summary of performance differences. ```shell clk: ~4.20 GHz cpu: Apple M4 runtime: bun 1.3.11 (arm64-darwin) benchmark avg (min … max) p75 / p99 (min … top 1%) ------------------------------------------- ------------------------------- Without Rate Limit 399.66 ns/iter 375.00 ns █▅ (208.00 ns … 263.08 µs) 1.79 µs ██ ( 0.00 b … 48.00 kb) 193.32 b ▁██▇▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ With Rate Limit (Default) 1.99 µs/iter 1.92 µs █ (1.29 µs … 565.92 µs) 7.00 µs ██ ( 0.00 b … 128.00 kb) 223.04 b ▂██▆▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ With Rate Limit (Custom) 2.03 µs/iter 2.00 µs █▄ (1.38 µs … 480.67 µs) 6.67 µs ██ ( 0.00 b … 512.00 kb) 379.38 b ▂███▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ┌ ┐ ╷┬ ╷ Without Rate Limit ├│────────┤ ╵┴ ╵ ╷ ┌──┬ With Rate Limit (Default) ├─┤ │────────────────────────────────┤ ╵ └──┴ ╷ ┌─┬ ╷ With Rate Limit (Custom) ├─┤ │───────────────┤ ╵ └─┴ ╵ └ ┘ 208.00 ns 3.60 µs 7.00 µs summary Without Rate Limit 4.98x faster than With Rate Limit (Default) 5.08x faster than With Rate Limit (Custom) ``` -------------------------------- ### Custom Key Generator by API Key Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/types.md Example of a custom generator that uses the 'Authorization' header to generate a rate limiting key. It extracts the token after 'Bearer '. ```typescript // Rate limit by API key const apiKeyGenerator: Generator = (request) => { return request.headers.get('Authorization')?.replace('Bearer ', '') || '' } ``` -------------------------------- ### Static Analysis Optimization (Good Practice) Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Demonstrates the recommended way to access request context within `onBeforeHandle` to avoid unnecessary body parsing by Elysia. This preserves performance by deferring body parsing until explicitly needed. ```typescript // ✓ Good: Elysia doesn't know we need body plugin.onBeforeHandle( async function rateLimit({ set, request, cookie }) { const context = arguments[0] // Get full context } ) ``` -------------------------------- ### Project File Sizes Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/MANIFEST.md Provides an overview of the uncompressed markdown file sizes for the project's documentation. ```text Total: ~140 KB (uncompressed markdown) - README.md: 14 KB - ARCHITECTURE.md: 21 KB - configuration.md: 21 KB - api-reference/: ~50 KB - Other docs: ~34 KB ``` -------------------------------- ### Initialize DefaultContext with Elysia Plugin Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/DefaultContext.md Demonstrates how to initialize DefaultContext and integrate it with the Elysia rateLimit plugin. The 'init' method is called internally by the plugin. ```typescript import { DefaultContext, rateLimit } from 'elysia-rate-limit' const context = new DefaultContext(10000) const app = new Elysia().use(rateLimit({ context })) // init is called internally by the plugin ``` -------------------------------- ### Warning Log: Missing Server or Request Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Shows the console warning message logged when the defaultKeyGenerator fails because the 'server' or 'request' parameter is undefined. This indicates a potential issue with how the plugin is initialized or called. ```text [elysia-rate-limit] failed to determine client address (reason: server or request is undefined) ``` -------------------------------- ### Options Interface Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/types.md The `Options` interface contains all configuration parameters for the rate limit plugin. All fields are required in the interface definition, but the `rateLimit()` function accepts `Partial` and fills in defaults. ```APIDOC ## Options Interface ### Description The `Options` interface contains all configuration parameters for the rate limit plugin. All fields are required in the interface definition, but the `rateLimit()` function accepts `Partial` and fills in defaults. ### Fields - **`duration`** (number | function) - Optional - Default: `60000` - Time window in milliseconds. Can be static or dynamic based on key and request. - **`max`** (number | function) - Optional - Default: `10` - Maximum requests per window. Can be static or dynamic. - **`errorResponse`** (string | Response | Error) - Optional - Default: `'rate-limit reached'` - Response when limit exceeded. String → 429 text response; Response → sent as-is; Error → thrown. - **`scoping`** ('global' | 'scoped') - Optional - Default: `'global'` - Whether to apply to all Elysia instances ('global') or just the current one ('scoped'). - **`countFailedRequest`** (boolean) - Optional - Default: `false` - Whether failed requests count toward the limit. If false, count is refunded on error. - **`generator`** (Generator) - Optional - Default: `defaultKeyGenerator` - Function to generate unique client identifier from request. - **`context`** (Context) - Optional - Default: `new DefaultContext()` - Storage backend for request counting. Can be custom implementation. - **`skip`** (function) - Optional - Default: `() => false` - Function to skip rate limiting for specific requests. Returns true to skip. - **`injectServer`** (() => Server | null) - Optional - Default: `undefined` - Optional function to inject server instance. Rarely needed. - **`headers`** (boolean) - Optional - Default: `true` - Whether to add RateLimit-* headers to responses. ``` -------------------------------- ### Using Custom RedisContext with Elysia Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Context.md Demonstrates how to integrate a custom RedisContext with Elysia Rate Limit. Ensure your Redis client is properly initialized and passed to the context. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' const app = new Elysia() .use(rateLimit({ context: new RedisContext(redisClient), })) .get('/', () => 'hello') .listen(3000) ``` -------------------------------- ### Comparing defaultKeyGenerator with defaultOptions Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Shows how to verify if the default key generator is the one used by default in the rate limit options. This is useful for understanding the plugin's configuration. ```typescript import { rateLimit, defaultOptions } from 'elysia-rate-limit' console.log(defaultOptions.generator === defaultKeyGenerator) // true (for comparison) ``` -------------------------------- ### Testing defaultKeyGenerator with Mock Objects Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Illustrates how to test the defaultKeyGenerator function by providing mock Request and Server objects. This allows for isolated testing of the IP extraction logic. ```typescript import { defaultKeyGenerator } from 'elysia-rate-limit' import type { Generator } from 'elysia-rate-limit' // Simulate with a mock request and server const mockRequest = new Request('http://localhost/test') const mockServer = { requestIP: () => ({ address: '192.168.1.100', port: 54321 }), } const key = await defaultKeyGenerator(mockRequest, mockServer) console.log(key) // Output: '192.168.1.100' ``` -------------------------------- ### Warning Log: requestIP() Returns Null Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Displays the console warning for when Bun's requestIP() method returns null, causing the defaultKeyGenerator to fail. This might suggest a Bun version compatibility issue. ```text [elysia-rate-limit] failed to determine client address (reason: .requestIP() returns null) ``` -------------------------------- ### Handling Invalid Max Value Configuration Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/errors.md Shows an example of an invalid max value configuration where the dynamic max function returns a string instead of a number. This results in a configuration error with a specific error message. ```typescript const app = new Elysia() .use(rateLimit({ max: (key, request) => { // BUG: Returns string instead of number return '100' as any }, })) // Request will fail with "Invalid max limit resolved: 100. Max must be a positive number." ``` -------------------------------- ### Elysia Rate Limit Module Structure Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Visualizes the directory structure and main files of the elysia-rate-limit plugin. ```text src/ ├── index.ts ← Public API exports ├── @types/ │ ├── Context.ts ← Storage interface │ ├── Generator.ts ← Key generation type │ ├── Options.ts ← Configuration interface │ └── Server.ts ← Bun server types ├── constants/ │ └── defaultOptions.ts ← Default config values └── services/ ├── plugin.ts ← Core middleware logic ├── defaultContext.ts ← LRU cache implementation ├── defaultKeyGenerator.ts ← IP extraction └── logger.ts ← Debug logging ``` -------------------------------- ### Using Custom Generators with Dynamic Max Limits Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Generator.md Shows how to integrate a custom generator with Elysia Rate Limit and define dynamic maximum limits based on the generated key. This allows for tiered rate limiting. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' import type { Generator } from 'elysia-rate-limit' const customGenerator: Generator = (request) => { return request.headers.get('X-API-Key') ?? 'free-tier' } const app = new Elysia() .use(rateLimit({ generator: customGenerator, max: (key, request) => { // Different limits based on generated key return key.startsWith('key-') ? 1000 : 100 }, })) .get('/', () => 'hello') .listen(3000) ``` -------------------------------- ### kill(): MaybePromise Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Context.md Terminates the storage backend, releasing any resources. ```APIDOC ## kill ### Description Terminates the storage backend, releasing any resources it may be holding. This is typically called when the server is shutting down. ### Parameters #### Path Parameters * None #### Query Parameters * None #### Request Body * None ### Method * None (This is a method of the Context interface) ### Endpoint * None (This is a method of the Context interface) ### Returns `MaybePromise` — Completes when the backend is terminated. ``` -------------------------------- ### increment(key: string, duration?: number, requestTime?: number): MaybePromise<{ count: number; nextReset: Date; start: number }> Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Context.md Increments the request count for a given key. Creates a new window if the key doesn't exist or the previous window has expired. ```APIDOC ## increment ### Description Increments the request count for a given key. Creates a new window if the key doesn't exist or the previous window has expired. Handles both synchronous and asynchronous operations. ### Parameters #### Path Parameters * None #### Query Parameters * None #### Request Body * None ### Method * None (This is a method of the Context interface) ### Endpoint * None (This is a method of the Context interface) ### Parameters #### Path Parameters * None #### Query Parameters * None #### Request Body * None ### Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | key | `string` | Yes | — | Client identifier (e.g., IP address, user ID). | | duration | `number` | No | — | Window duration in milliseconds. Required when plugin options.duration is a function. | | requestTime | `number` | No | `Date.now()` | Current Unix timestamp. Used for testing; production uses current time. | ### Returns An object with: | Field | Type | Description | |-------|------|-------------| | count | `number` | Total requests made by this client in the current window (1-indexed). | | nextReset | `Date` | When the current window expires and count resets. | | start | `number` | Unix timestamp when the current window started. | ### Behavior - Creates a new window if the key doesn't exist - Creates a new window if the previous window has expired - Increments count if within the same window - Handles both sync and async operation (MaybePromise) ### Code Example ```typescript class InMemoryContext implements Context { private store = new Map() increment(key: string, duration?: number, requestTime?: number) { const now = requestTime ?? Date.now() const item = this.store.get(key) if (!item || item.nextReset.getTime() <= now) { // New window const newItem = { count: 1, nextReset: new Date(now + (duration ?? 60000)), start: now, } this.store.set(key, newItem) return newItem } else { // Increment existing item.count++ return item } } // ... other methods } ``` ``` -------------------------------- ### Deterministic vs. Non-Deterministic Keys Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Generator.md Illustrates the difference between a non-deterministic generator that produces different keys for the same client and a deterministic one that consistently generates the same key. Use deterministic generators for reliable rate limiting. ```typescript // WRONG: Non-deterministic const badGenerator: Generator = (request) => { return Math.random().toString() // Different key each request! } // RIGHT: Deterministic const goodGenerator: Generator = (request, server) => { return server?.requestIP(request)?.address ?? 'unknown' } ``` -------------------------------- ### Enable Rate Limit Headers Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/configuration.md Configure the Elysia Rate Limit plugin to include rate limit information headers in successful responses. These headers provide details on the limit, remaining requests, and reset time. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' const app = new Elysia() .use(rateLimit({ headers: true })) // Responses include RateLimit-* headers ``` -------------------------------- ### Integration Testing Rate Limiting Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Shows how to integration test the rate limit plugin by creating an Elysia app, applying the rate limit middleware, and then simulating rapid requests. Verifies that the correct status codes (200 and 429) are returned. ```typescript const app = new Elysia() .use(rateLimit({ duration: 100, max: 2 })) .get('/', () => 'hello') // Make 3 requests rapidly const r1 = await app.handle(new Request('http://localhost/')) assert(r1.status === 200) const r2 = await app.handle(new Request('http://localhost/')) assert(r2.status === 200) const r3 = await app.handle(new Request('http://localhost/')) assert(r3.status === 429) // Limited ``` -------------------------------- ### Warning Log: requestIP() Address Undefined Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Illustrates the console warning when requestIP() returns an object but lacks an 'address' property. This scenario also leads to the defaultKeyGenerator returning an empty string. ```text [elysia-rate-limit] failed to determine client address (reason: .requestIP()?.address returns undefined) ``` -------------------------------- ### Options Interface Definition Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/types.md Defines all configuration parameters for the rate limit plugin. The `rateLimit()` function accepts `Partial` with defaults applied. ```typescript interface Options { duration: number | ((key: string, request: ExtendedRequest) => number | Promise) max: number | ((key: string, request: ExtendedRequest) => number | Promise) errorResponse: string | Response | Error scoping: 'global' | 'scoped' countFailedRequest: boolean generator: Generator context: Context skip: (req: ExtendedRequest, key?: string) => boolean | Promise injectServer?: () => Server | null headers: boolean } ``` -------------------------------- ### Testing Custom Generators Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Generator.md Demonstrates how to test custom generator functions using mock requests. It verifies that the generator returns the expected key for different header conditions. ```typescript import type { Generator } from 'elysia-rate-limit' const myGenerator: Generator = (request, server) => { return request.headers.get('X-User-ID') ?? 'unknown' } // Test with mock request const mockRequest = new Request('http://localhost/test', { headers: { 'X-User-ID': 'user-123', }, }) as any const key = await myGenerator(mockRequest, null) console.assert(key === 'user-123', 'Should return user ID') // Test without header const mockRequest2 = new Request('http://localhost/test') as any const key2 = await myGenerator(mockRequest2, null) console.assert(key2 === 'unknown', 'Should return unknown') ``` -------------------------------- ### injectServer Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/README.md A function to inject the server instance into the plugin, useful for default key generators in detached Elysia instances. Use as a last resort due to potential performance impact. ```APIDOC ## injectServer ### Description Allows injecting the Elysia server instance into the plugin. This is primarily useful for scenarios requiring default key generators within detached Elysia instances. ### Signature `() => Server` ### Default `undefined` ### Usage This function should be used as a last resort. Defining this option can introduce an extra function call, potentially impacting performance. Refer to the example [here](./example/multiInstanceInjected.ts) for usage. ``` -------------------------------- ### Context Interface for Storage Backends Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/README.md Defines the interface for custom storage backends used by the rate limiting plugin. It specifies methods for initializing, incrementing, decrementing, resetting, and killing storage. ```typescript interface Context { init(options: Omit): void increment(key: string, duration?: number, requestTime?: number): MaybePromise<{ count: number; nextReset: Date; start: number }> decrement(key: string): MaybePromise reset(key?: string): MaybePromise kill(): MaybePromise } ``` -------------------------------- ### Redis-Based Rate Limiting with Custom Context Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/README.md Implement a custom Context for Redis-based rate limiting. This requires creating a class that adheres to the Context interface and interacts with your Redis instance. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' import type { Context } from 'elysia-rate-limit' class RedisContext implements Context { // ... implementation using Redis async increment(key: string, duration?: number, requestTime?: number): Promise<{ count: number; nextReset: Date; start: number }> { // Placeholder for Redis increment logic return { count: 1, nextReset: new Date(), start: Date.now() }; } async decrement(key: string): Promise { /* ... */ } async reset(key?: string): Promise { /* ... */ } async kill(): Promise { /* ... */ } init(options: any): void { /* ... */ } } const app = new Elysia() .use(rateLimit({ context: new RedisContext() })) app.listen(3000) ``` -------------------------------- ### Plugin Seed Implementation Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Shows how a unique seed is generated for the plugin to prevent collisions, especially when duration and max values are dynamic. This ensures proper isolation of plugin instances. ```typescript const seedValue = `${maxSeed}:${durationSeed}${scopeSeed}${randomSeed}` const plugin = new Elysia({ name: 'elysia-rate-limit', seed: seedValue, // Unique seed prevents collision }) ``` -------------------------------- ### DefaultContext Constructor Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/DefaultContext.md Creates a new DefaultContext instance. The context uses an LRU cache to store request tracking data. Each client key is tracked independently. ```APIDOC ## new DefaultContext(maxSize?: number) ### Description Creates a new DefaultContext instance. The context uses an LRU cache from the `@alloc/quick-lru` package to store request tracking data. Each client key is tracked independently. ### Parameters #### Path Parameters * **maxSize** (`number`) - Optional - Maximum number of client keys to store in the LRU cache. Oldest entries are evicted when the limit is reached. Defaults to `5000`. ``` -------------------------------- ### Skip Function Optimization (Good Practice) Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Shows an efficient skip function that only takes the `request` parameter. This defers the key generation logic until the `skip` function returns false, optimizing performance by avoiding unnecessary computations. ```typescript // ✓ Good: Generator only called if skip returns false const skip = (request) => request.url.includes('/health') ``` -------------------------------- ### Inject Server Instance for Detached Elysia Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/configuration.md Provide a server instance to the rate limit plugin using 'injectServer' when using detached Elysia instances. This is necessary for instances that do not automatically have a server reference. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' let server: any const options = { injectServer: () => server, } const api = new Elysia() .use(rateLimit(options)) .get('/users', () => ({ users: [] })) const app = new Elysia() .use(api) .listen(3000, (s) => { server = s.server }) ``` -------------------------------- ### Generator Using Derived Plugin Context (elysia-ip) Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Generator.md Generates a rate limit key using the IP address obtained from the elysia-ip plugin. This requires the elysia-ip plugin to be used before the rateLimit plugin. ```typescript import type { Generator } from 'elysia-rate-limit' import { ip } from 'elysia-ip' const ipPluginGenerator: Generator<{ ip: string }> = (request, server, { ip }) => { // Use IP from the elysia-ip plugin return Bun.hash(ip).toString() } const app = new Elysia() .use(ip()) .use(rateLimit({ generator: ipPluginGenerator, })) ``` -------------------------------- ### Custom Duration and Max Requests for Rate Limiting Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/rateLimit.md Configures a rate limit with a 1-minute window and a maximum of 100 requests per window. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' const app = new Elysia() .use(rateLimit({ duration: 60000, // 1 minute window max: 100, // 100 requests per window })) .get('/', () => 'hello') .listen(3000) ``` -------------------------------- ### Custom Context Implementation for Rate Limiting Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/rateLimit.md Uses a custom LRU cache context with a maximum of 10,000 entries, overriding the default 5,000. ```typescript import { Elysia } from 'elysia' import { rateLimit, DefaultContext } from 'elysia-rate-limit' const app = new Elysia() .use(rateLimit({ // Use LRU cache with 10,000 max entries instead of default 5,000 context: new DefaultContext(10000), })) .get('/', () => 'hello') .listen(3000) ``` -------------------------------- ### Basic Rate Limiting (Per IP) Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/README.md Use this snippet for simple rate limiting based on the client's IP address. It defaults to 10 requests per 60 seconds. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' const app = new Elysia() .use(rateLimit()) // 10 requests per 60 seconds, by IP .get('/', () => 'hello') .listen(3000) ``` -------------------------------- ### DefaultContext Constructor Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/DefaultContext.md Instantiates a new DefaultContext. Optionally specify the maximum number of client keys to store in the LRU cache. ```typescript new DefaultContext(maxSize?: number) ``` -------------------------------- ### RedisContext implementation Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/Context.md An asynchronous context implementation using Redis for Elysia Rate Limit. It handles incrementing, decrementing, resetting, and killing operations with persistence. ```typescript import type { Context } from 'elysia-rate-limit' import type { Options } from 'elysia-rate-limit' class RedisContext implements Context { private redis: any // Your Redis client init(options: Omit) { // Store options if needed for logging, etc. } async increment(key: string, duration?: number, requestTime?: number) { const now = requestTime ?? Date.now() const effectiveDuration = duration ?? 60000 // Try to get existing count const existingData = await this.redis.get(`rate:${key}`) if (!existingData) { // New window const nextReset = new Date(now + effectiveDuration) await this.redis.set(`rate:${key}`, JSON.stringify({ count: 1, start: now, resetTime: nextReset.getTime(), }), 'EX', Math.ceil(effectiveDuration / 1000)) return { count: 1, nextReset, start: now, } } // Increment existing const data = JSON.parse(existingData) if (data.resetTime <= now) { // Window expired, create new one const nextReset = new Date(now + effectiveDuration) await this.redis.set(`rate:${key}`, JSON.stringify({ count: 1, start: now, resetTime: nextReset.getTime(), }), 'EX', Math.ceil(effectiveDuration / 1000)) return { count: 1, nextReset, start: now, } } // Still in window data.count++ await this.redis.set(`rate:${key}`, JSON.stringify(data)) return { count: data.count, nextReset: new Date(data.resetTime), start: data.start, } } async decrement(key: string) { const data = await this.redis.get(`rate:${key}`) if (data) { const parsed = JSON.parse(data) if (parsed.count > 0) { parsed.count-- await this.redis.set(`rate:${key}`, JSON.stringify(parsed)) } } } async reset(key?: string) { if (typeof key === 'string') { await this.redis.del(`rate:${key}`) } else { const keys = await this.redis.keys('rate:*') if (keys.length > 0) { await this.redis.del(...keys) } } } async kill() { const keys = await this.redis.keys('rate:*') if (keys.length > 0) { await this.redis.del(...keys) } } } ``` -------------------------------- ### Debug Namespaces Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/index.md Understand the different namespaces available for debugging the Elysia Rate Limit plugin. These namespaces provide granular control over the logs you receive. ```text - elysia-rate-limit:plugin — Rate limit decisions - elysia-rate-limit:generator — Key generation - elysia-rate-limit:context:* — Storage operations ``` -------------------------------- ### Warning Log: Unknown IP Extraction Reason Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/api-reference/defaultKeyGenerator.md Shows the generic console warning logged when the defaultKeyGenerator encounters an unexpected error during IP address extraction. This fallback warning helps in debugging unforeseen issues. ```text [elysia-rate-limit] failed to determine client address (reason: unknown) ``` -------------------------------- ### Dynamic Max Requests Configuration Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/configuration.md Allows the maximum number of requests per window to be determined dynamically based on request headers or other factors. This enables tiered rate limiting for different user subscriptions. ```typescript import { Elysia } from 'elysia' import { rateLimit } from 'elysia-rate-limit' const app = new Elysia() .use(rateLimit({ max: (key, request) => { const tier = request.headers.get('X-Subscription-Tier') switch (tier) { case 'enterprise': return 10000 case 'pro': return 1000 case 'basic': return 100 default: return 10 } }, })) ``` -------------------------------- ### Static Analysis Optimization (Bad Practice) Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Illustrates an inefficient approach where destructuring the `body` parameter in `onBeforeHandle` causes Elysia to parse the request body for all routes, even if not required by the rate limiter. Avoid this to maintain performance. ```typescript // ✗ Bad: Elysia will parse body for all routes plugin.onBeforeHandle( async function rateLimit({ body, ...rest }) { // Body destructured // Now ALL requests have body parsed } ) ``` -------------------------------- ### Scoping Options for Multiple Instances Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/README.md Defines how rate limits are applied across multiple instances, either globally or scoped per instance. ```typescript // Share limits across instances (default) scoping: 'global' ``` ```typescript // Isolate limits per instance scoping: 'scoped' ``` -------------------------------- ### headers Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/README.md Controls whether the plugin automatically sets RateLimit-* headers in the response. Defaults to true. ```APIDOC ## headers ### Description Determines if the plugin automatically adds `RateLimit-*` headers to the response. ### Type `boolean` ### Default `true` ### Usage Set to `false` to disable automatic header setting. ``` -------------------------------- ### Configure Dynamic Duration Based on Asynchronous Data Fetch Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/README.md Dynamically set the rate limiting duration by fetching user tier information asynchronously from a database. ```typescript new Elysia().use( rateLimit({ duration: async (key, request) => { // Fetch user tier from database const userTier = await getUserTier(key) return userTier === 'premium' ? 10_000 : 60_000 }, }) ) ``` -------------------------------- ### Configure Static Duration for Rate Limiting Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/README.md Set a fixed duration for the rate limiting window in milliseconds. ```typescript new Elysia().use( rateLimit({ duration: 60_000, // 1-minute window }) ) ``` -------------------------------- ### High-Water Mark Logic for Dynamic Durations Source: https://github.com/rayriffy/elysia-rate-limit/blob/main/_autodocs/ARCHITECTURE.md Illustrates how the next reset time is extended when a request arrives with a longer duration than the current window. ```typescript Item created at t=0 with duration=60s → nextReset = 60s Request at t=10 with duration=120s → nextReset extended to 120s ```