Per ADR-014 (Open Source First), this package designates ioredis as the platform standard for Redis-backed sliding-window rate limiting and ships @dmwd-io/rate-limit as a thin re-export that makes the choice official and documents platform conventions (env var names, defaults). The value delivered is standards and drift prevention — not a custom abstraction layered on top of the library. No custom provider interface is built; ioredis’s own API is the interface.
Goals
Designate ioredis as the platform standard for Redis-backed rate limiting per ADR-014.
Re-export ioredis through @dmwd-io/rate-limit so teams have a single, blessed import path.
Document platform env var conventions (REDIS_URL) so connection configuration is consistent across services.
Define a standard error response shape (429 Too Many Requests) with Retry-After metadata.
Document recommended usage patterns for sliding-window rate limiting with ioredis Lua scripts.
Non-Goals
Building a custom provider interface or adapter layer — the library’s own API is the interface (per ADR-014).
SQLite storage adapter (not needed when Valkey is the platform cache default per ADR-027 §4).
Building rate-limit dashboard or analytics UI.
Providing distributed rate-limiting coordination beyond what ioredis supports natively.
Rate-limiting at the CDN or reverse-proxy layer (Cloudflare, nginx).
Scope
In Scope
Area
Description
Policy model
Typed RateLimitPolicy supporting fixed-window, sliding-window, and token-bucket
Rate limiter
RateLimiter class with check(key, policy) and consume(key, policy) methods
Storage adapter interface
RateLimitStore interface for pluggable storage backends
In-memory adapter
MemoryRateLimitStore for dev and testing
Error response
Standard RateLimitError type with status code, retryAfter, and limit metadata
HTTP helpers
Utility to set Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining headers
Middleware helper
Framework-agnostic middleware factory for common HTTP frameworks
Unit tests
Full coverage of all algorithms, store adapter, and error formatting
valkey-rate-limit-store.ts
Valkey/Redis storage adapter using ioredis; the default production store per ADR-027 §4
Documentation
Storybook MDX docs with usage examples
Out of Scope
Area
Reason
Redis adapter
Shipped as a separate package
SQLite adapter
Shipped as a separate package
Distributed coordination
Requires consensus protocol; out of scope for a library
Admin UI / dashboard
Backend concern
IP-based geo-blocking
Different concern; not rate-limiting
Users and Pain Points
User Groups
User
Description
Needs
Backend developers
Engineers implementing API rate limits
A declarative policy model and consistent evaluation API
Platform engineers
Engineers deploying and monitoring rate limits
Standard error responses and headers for observability
QA engineers
Testers verifying rate-limit behavior
An in-memory store that allows controlled testing of limit scenarios
Pain Points
User
Pain Point
Impact
Backend developers
Each service implements rate-limiting differently with ad-hoc counters
Inconsistent behavior; some endpoints unprotected
Backend developers
No standard error response for rate-limited requests
Clients cannot reliably detect or handle rate limits
Platform engineers
Rate-limit headers are missing or inconsistent across services
Monitoring and alerting cannot be standardized
QA engineers
Testing rate limits requires waiting for real time windows
Tests are slow or skip rate-limit verification
Definitions
Term
Definition
Rate-limit policy
A declarative configuration specifying the algorithm, window size, and maximum requests
Fixed window
Counts requests in discrete time windows (e.g., 100 requests per minute, resetting at minute boundaries)
Sliding window
Counts requests in a rolling window that moves with the current time
Token bucket
Allows bursts up to a capacity, refilling tokens at a steady rate
Storage adapter
An object implementing RateLimitStore that persists counters (in-memory, Redis, etc.)
Retry-After
HTTP header indicating how many seconds the client should wait before retrying
Current State
Existing Behavior
No shared rate-limiting library exists. Services implement rate limits using ad-hoc middleware or inline counter logic.
Current Limitations
No typed policy model; rate limits are configured with magic numbers in middleware.
No standard error response shape; some services return 429 with a plain text body, others return JSON.
No shared storage abstraction; some services use Redis directly, others use in-memory counters.
Rate-limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After) are inconsistently applied.
Existing Workarounds
Developers implement custom rate-limit middleware per service.
Tests manipulate time or use very high limits to avoid hitting rate limits during test execution.
Proposed Solution
Summary
@dmwd-io/rate-limit re-exports ioredis as the platform-standard Redis client for rate limiting. No custom interface is built — the library’s API is the interface. The package adds platform conventions on top: the REDIS_URL env var for connection configuration and documented patterns for PII-safe key construction.
import { Redis } from'@dmwd-io/rate-limit';
const client = newRedis(process.env.REDIS_URL);
Platform conventions documented by this package:
REDIS_URL — connection string for the Valkey/Redis instance (required in production).
Key construction pattern: rl:<service>:<userId> — no raw PII in keys.
Recommended Lua script pattern for atomic sliding-window increments.
No custom adapter or factory is shipped. Teams use ioredis directly via this re-export.
User Experience
Not applicable (library, no UI).
Developer Experience
Developers import from @dmwd-io/rate-limit, connect using REDIS_URL, and use ioredis directly for rate-limit counter operations. The package provides documented examples for sliding-window Lua scripts and platform key conventions rather than wrapping the library in a custom abstraction.
Common mistakes (e.g., using in-memory store in multi-instance production)
Dependencies
Dependency
Type
Owner
Status
Notes
TypeScript 5.x
Engineering
David Holmes
Ready
Build toolchain
Vitest
Engineering
David Holmes
Ready
Test runner
None (runtime)
—
—
Ready
Zero runtime dependencies
ADR-027
Architecture
Engineering
Ready
Designates Valkey as the platform cache/queue default (§4); ioredis is the recommended client
Risks and Tradeoffs
Risk / Tradeoff
Impact
Mitigation
In-memory store does not work in multi-instance deployments
Rate limits are per-instance, not global
Document this clearly; ship Redis adapter separately
Three algorithm options increase implementation and testing surface
More code to maintain
Each algorithm is independent; can defer sliding-window or token-bucket
Middleware factory may not fit all framework conventions
Some frameworks have different middleware signatures
Factory returns a generic function; provide framework-specific wrappers in docs
Automatic cleanup interval in MemoryRateLimitStore
Interval must be cleared on shutdown to avoid leaked timers
Provide a dispose() method; document cleanup in long-running processes
Open Questions
ID
Question
Owner
Status
Resolution
Q-001
Should the RateLimitStore interface support atomic check-and-consume in a single call for Redis?
David Holmes
Open
—
Q-002
Should we support composite keys (e.g., user:${id}:endpoint:${path}) with a helper function?
David Holmes
Open
—
Q-003
Should the middleware factory support per-route policy overrides?
David Holmes
Open
—
Acceptance Criteria
ID
Criteria
Related Requirement
AC-001
RateLimitPolicy type supports fixed-window, sliding-window, and token-bucket algorithms
FR-001
AC-002
RateLimiter.consume(key, policy) returns { allowed: false } after maxRequests calls within a window
FUNC-002
AC-003
RateLimiter.check(key, policy) does not consume a request
FUNC-001
AC-004
formatRateLimitHeaders(result) returns all four standard headers
FUNC-007
AC-005
MemoryRateLimitStore evicts expired entries on cleanup interval
FUNC-008
AC-006
Fixed-window counter resets at window boundary
FUNC-003
AC-007
All public types re-exported from package index
NFR-002
AC-008
Unit tests pass covering all three algorithms and edge cases (window boundary, burst, empty store)
FR-002
AC-009
Storybook MDX docs render without errors
DOC-001
LLM Handoff Instructions
Expected LLM Behavior
Follow the requirements and acceptance criteria in this document.
Do not expand scope beyond the In Scope section.
Respect the Out of Scope section.
Ask for clarification only when a requirement cannot be safely interpreted.
Create the package under the packages/ directory if it does not already exist.
Create a package.json listing ioredis as a peer dependency.
Create src/index.ts with export * from 'ioredis' as the re-export.
Document the REDIS_URL env var and platform key conventions in the package README and Storybook MDX docs.
Run pnpm typecheck before declaring the task complete.
LLM Should Not
Build a custom RateLimitStore interface or adapter layer — ioredis’s API is the interface (ADR-014).
Add new dependencies beyond ioredis without justification.
Change unrelated components.
Log request keys or user identifiers.
Decision Log
Date
Decision
Reason
Owner
2026-05-26
Support three algorithms (fixed-window, sliding-window, token-bucket)
Covers common use cases; each is independently useful
David Holmes
2026-05-26
Zero runtime dependencies
Library must be lightweight and deployable anywhere
David Holmes
2026-05-26
Async store interface even for in-memory adapter
Enables network-backed stores without interface changes
David Holmes
2026-05-26
Separate check and consume methods
Read-only preview is useful for UI hints and monitoring without affecting counters
David Holmes
2026-05-26
Valkey designated as the production rate-limit store
ADR-027 §4 designates Valkey (OSS Redis fork) as the platform cache default; ioredis is the recommended client
David Holmes
2026-06-02
Reframed per ADR-014 (Open Source First): adopt ioredis as thin re-export rather than building a custom provider interface. Library API is the interface.
ADR-014 prohibits custom abstraction layers over mature OSS libraries
David Holmes
Document History
Date
Author
Change
2026-05-26
David Holmes
Initial draft
2026-06-02
David Holmes
Reframed per ADR-014: ship as thin re-export of ioredis, drop custom interface.