limitforge 0.0.0-stage → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +461 -3
- package/index.d.ts +192 -0
- package/package.json +81 -4
- package/src/benchmark/benchmarkRunner.js +235 -0
- package/src/index.js +30 -0
- package/src/limiter/rateLimiter.js +670 -0
- package/src/resilience/circuitBreaker.js +216 -0
- package/src/resilience/errors.js +29 -0
- package/src/resilience/timeoutGuard.js +24 -0
- package/src/scripts/fixedWindow.lua +10 -0
- package/src/scripts/slidingWindow.lua +26 -0
- package/src/scripts/tokenBucket.lua +66 -0
- package/src/stores/memoryStore.js +238 -0
- package/src/stores/redisStore.js +213 -0
- package/src/stores/resilientStore.js +121 -0
- package/src/telemetry/metricsCollector.js +212 -0
- package/src/telemetry/openTelemetryBridge.js +133 -0
- package/src/telemetry/prometheusExporter.js +131 -0
- package/src/telemetry/telemetryEvents.js +58 -0
- package/src/utils/keyBuilder.js +41 -0
- package/src/utils/routeNormalizer.js +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anivesh Sikarwar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,461 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# LimitForge
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/limitforge)
|
|
4
|
+
[](https://github.com/Anivesh91/LimitForge/actions)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
Production-ready distributed rate limiting and resilience middleware for Express.js supporting **Full Observability (Prometheus, Grafana, OpenTelemetry)**, **High-Precision Performance Benchmarks**, **Production Resilience & Store Fault Tolerance**, **Circuit Breaker & Dual-Store Failover**, **Token Bucket + Controlled Burst**, **Rolling Sliding Window**, and **Fixed Window** algorithms, **Custom Client Identity** (API Key, User ID, Multi-Tenant), and **Dynamic Tier-Based Policies**.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install limitforge
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import express from 'express';
|
|
20
|
+
import { createClient } from 'redis';
|
|
21
|
+
import {
|
|
22
|
+
rateLimiter,
|
|
23
|
+
RedisStore,
|
|
24
|
+
ResilientStore,
|
|
25
|
+
MemoryStore,
|
|
26
|
+
createPrometheusExporter,
|
|
27
|
+
OpenTelemetryBridge
|
|
28
|
+
} from 'limitforge';
|
|
29
|
+
|
|
30
|
+
const app = express();
|
|
31
|
+
const redisClient = createClient({
|
|
32
|
+
url: process.env.REDIS_URL || 'redis://localhost:6379'
|
|
33
|
+
});
|
|
34
|
+
redisClient.on('error', (error) => console.error('Redis client error:', error));
|
|
35
|
+
await redisClient.connect();
|
|
36
|
+
const loginHandler = (req, res) => res.json({ success: true });
|
|
37
|
+
|
|
38
|
+
// 1. Resilient Distributed Rate Limiter with In-Memory Fallback & Telemetry
|
|
39
|
+
const generalApiLimiter = rateLimiter({
|
|
40
|
+
store: new RedisStore({ client: redisClient }),
|
|
41
|
+
fallbackStore: true, // Automatically wraps in ResilientStore with MemoryStore fallback
|
|
42
|
+
timeoutMs: 250, // Store operation timeout guard in milliseconds
|
|
43
|
+
failureThreshold: 5, // Trip circuit breaker to OPEN after 5 consecutive failures
|
|
44
|
+
resetTimeoutMs: 10_000, // Cooldown window before lazy HALF_OPEN canary probe
|
|
45
|
+
limit: 100,
|
|
46
|
+
windowMs: 60_000,
|
|
47
|
+
metrics: true, // Enable Prometheus telemetry collection
|
|
48
|
+
openTelemetry: true // Attach attributes & events to active OpenTelemetry span
|
|
49
|
+
});
|
|
50
|
+
app.use('/api', (req, res, next) => {
|
|
51
|
+
if (req.path === '/burst') return next();
|
|
52
|
+
return generalApiLimiter(req, res, next);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 2. Token Bucket + Controlled Burst: Allows 20-request bursts, sustained 5 req/sec
|
|
56
|
+
app.use(
|
|
57
|
+
'/api/burst',
|
|
58
|
+
rateLimiter({
|
|
59
|
+
algorithm: 'token-bucket',
|
|
60
|
+
capacity: 20, // Maximum burst capacity
|
|
61
|
+
refillRate: 5, // Sustained replenishment rate
|
|
62
|
+
refillIntervalMs: 1000, // Refill interval (5 tokens per 1000ms)
|
|
63
|
+
keyGenerator: (req) => req.headers['x-api-key']
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// 3. Rolling Sliding Window: Strictest boundary enforcement for auth
|
|
68
|
+
app.post(
|
|
69
|
+
'/auth/login',
|
|
70
|
+
rateLimiter({
|
|
71
|
+
algorithm: 'sliding-window',
|
|
72
|
+
limit: 5,
|
|
73
|
+
windowMs: 60_000
|
|
74
|
+
}),
|
|
75
|
+
loginHandler
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// 4. Native Prometheus Scrape Endpoint (Zero External Dependencies)
|
|
79
|
+
app.get('/metrics', createPrometheusExporter());
|
|
80
|
+
|
|
81
|
+
app.listen(3000, () => console.log('LimitForge listening on http://localhost:3000'));
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Start Redis before running this example. Once the server is running, stopping Redis demonstrates the configured fallback behavior; start it again to observe recovery.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## What's New in LimitForge v7 (Observability & Performance)
|
|
89
|
+
|
|
90
|
+
LimitForge v7 answers the core production question: **"What is LimitForge doing in production, and how can developers observe its health and performance?"**
|
|
91
|
+
|
|
92
|
+
* **Zero-Dependency Native Prometheus Exposition (`/metrics`)**:
|
|
93
|
+
- Exposes metrics adhering strictly to the Prometheus text exposition format (v0.0.4) without requiring `prom-client` or any third-party runtime package.
|
|
94
|
+
- Mount via `app.get('/metrics', createPrometheusExporter())` or serialize programmatically with `formatPrometheusMetrics(snapshot)`.
|
|
95
|
+
* **In-Memory Metrics Engine (`MetricsCollector`)**:
|
|
96
|
+
- Tracks throughput outcomes (`smartrate_requests_total` with `outcome="allowed"|"blocked"`), store latencies (`smartrate_store_duration_seconds`), store error counts (`smartrate_store_errors_total`), and circuit breaker state gauge (`smartrate_circuit_breaker_state`).
|
|
97
|
+
* **Strict Route Cardinality Guard (`normalizeRoute`, `sanitizePath`)**:
|
|
98
|
+
- Automatically collapses dynamic entity IDs (`/users/42` or `/orders/550e8400...`) to `/users/:id` to protect Prometheus TSDB from label explosion. User IDs and IPs are strictly excluded from metric labels.
|
|
99
|
+
* **Failure-Isolated Telemetry Boundary**:
|
|
100
|
+
- Telemetry operations run inside safe error boundaries. A telemetry error or full metrics buffer **never** disrupts client HTTP requests.
|
|
101
|
+
* **Duck-Typed OpenTelemetry Tracing Bridge (`OpenTelemetryBridge`)**:
|
|
102
|
+
- Automatically enriches active request spans with rate limiting attributes (`smartrate.outcome`, `smartrate.algorithm`, `smartrate.route`, `smartrate.remaining`, `smartrate.reset`) and emits `smartrate.rate_limit_exceeded` span events without making `@opentelemetry/api` a mandatory dependency.
|
|
103
|
+
* **Production Grafana Dashboard Asset**:
|
|
104
|
+
- Pre-built, importable Grafana dashboard JSON (`assets/dashboards/smartrate-grafana-dashboard.json`) featuring live throughput, block rate ratio gauge, circuit breaker state, and latency percentiles.
|
|
105
|
+
* **Production Alertmanager Rules**:
|
|
106
|
+
- Standard Prometheus alerting rules (`assets/alerts/prometheus-rules.yml`) for High Block Rate (> 25%), Store Outages, Circuit Breaker trips, and High Store Latency (p95 > 50ms).
|
|
107
|
+
* **High-Precision Performance Benchmarking CLI (`npm run benchmark`)**:
|
|
108
|
+
- Measures realistic overhead using `process.hrtime.bigint()` across 6 scenarios with 1,000 warmups + 10,000 requests each.
|
|
109
|
+
- **Empirical result:** In-memory evaluation adds only **~2.3 to 3.7 microseconds** (`< 0.004 ms`) at p50. Real microseconds measured, zero false "zero overhead" marketing claims.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## What's New in LimitForge v6
|
|
114
|
+
|
|
115
|
+
LimitForge v6 solves the most critical operational failure mode of distributed rate limiting: **What happens when Redis or the central store becomes unavailable, slow, or network-partitioned?**
|
|
116
|
+
|
|
117
|
+
* **Store Timeout Guard (`timeoutMs`, `withTimeout`, `StoreTimeoutError`)**:
|
|
118
|
+
- Enforces bounded operation latency on all store calls.
|
|
119
|
+
- Prevents slow Redis instances or stalled network sockets from exhausting Node.js event-loop queues or cascading into HTTP 504 gateway timeouts.
|
|
120
|
+
- If a store call exceeds `timeoutMs` (default 250ms), a `StoreTimeoutError` is raised promptly, triggering the configured failure policy.
|
|
121
|
+
* **Store Failure Policies (`onStoreError`)**:
|
|
122
|
+
- `next(err)` (default): When omitted, store errors bubble to the Express error pipeline (`next(storeError)`).
|
|
123
|
+
- `'fail-open'`: Requests bypass rate limiting cleanly during catastrophic failures, prioritizing maximum service availability (`next()`). Sets `RateLimit-Degraded: true`.
|
|
124
|
+
- `'fail-closed'`: Requests are rejected with `HTTP 503 Service Unavailable` and dynamic `Retry-After`, protecting upstream infrastructure under heavy load. Sets `RateLimit-Degraded: true`.
|
|
125
|
+
- Custom function `(err, req, res, next)`: Allows custom fallback logging, error reporting, or alternate routing.
|
|
126
|
+
* **Circuit Breaker Engine (`CircuitBreaker`, `CIRCUIT_STATE`)**:
|
|
127
|
+
- Implements a resilient 3-state finite state machine (`CLOSED`, `OPEN`, `HALF_OPEN`).
|
|
128
|
+
- **Lazy On-Demand Transitions**: State changes from `OPEN` to `HALF_OPEN` do not use background timers or unreferenced intervals. Transitions are evaluated lazily on the next incoming request once cooldown has elapsed.
|
|
129
|
+
- **Zero Store Hammering**: When in `OPEN` state, store calls are 100% bypassed (`CircuitBreakerOpenError`), preventing thundering herd problems against recovering databases.
|
|
130
|
+
- **Single-Flight Canary Probe**: In `HALF_OPEN`, exactly one canary probe is dispatched to test store health. Concurrent incoming requests are safely diverted to the fallback store.
|
|
131
|
+
- **Precise Failure Classification**: The breaker only records infrastructure failures (network timeouts, socket drops, store crashes). Rate limit rejections (`429`), invalid options, and application errors never trip the breaker.
|
|
132
|
+
* **Dual-Store Resilience Engine (`ResilientStore`)**:
|
|
133
|
+
- Combines a primary distributed store (e.g. `RedisStore`) with a fast local fallback store (e.g. `MemoryStore`).
|
|
134
|
+
- Provides the shorthand configuration `fallbackStore: true` in `rateLimiter(...)`.
|
|
135
|
+
- While degraded, the fallback store **strictly enforces local rate limits**, preventing unmetered traffic surges from crashing backend applications.
|
|
136
|
+
* **Architectural Boundaries Strictly Maintained**:
|
|
137
|
+
- **No State Sync / Backfill**: When Redis recovers, local memory counters are **NOT** synchronized or backfilled into Redis. Local state expires naturally according to TTL.
|
|
138
|
+
- **Per-Process Degradation Semantics**: In a multi-node cluster, degraded memory limits apply per process ($N \times \text{limit}$).
|
|
139
|
+
* **Observability & Degraded Header Tagging**:
|
|
140
|
+
- Attaches `RateLimit-Degraded: true` HTTP response header whenever fallback is engaged.
|
|
141
|
+
- Exposes `req.rateLimit` metadata object with `{ degraded, fallbackUsed, store, primaryError }` for application logging and OpenTelemetry/APM spans.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Resilience Architecture
|
|
146
|
+
|
|
147
|
+
```mermaid
|
|
148
|
+
flowchart TD
|
|
149
|
+
Req([Incoming HTTP Request]) --> Limiter[LimitForge Middleware]
|
|
150
|
+
Limiter --> BreakerCheck{Circuit Breaker State?}
|
|
151
|
+
|
|
152
|
+
BreakerCheck -->|"CLOSED (Normal)"| RunPrimary[Call primaryStore with timeoutMs Guard]
|
|
153
|
+
RunPrimary --> PrimarySuccess{Success?}
|
|
154
|
+
PrimarySuccess -->|"Yes"| NormalResp[Set RateLimit-* Headers<br/>degraded: false<br/>next()]
|
|
155
|
+
PrimarySuccess -->|"Timeout / Error"| RecordFail[Record Failure in Breaker]
|
|
156
|
+
RecordFail --> FallbackHop[Failover to fallbackStore]
|
|
157
|
+
|
|
158
|
+
BreakerCheck -->|"OPEN (Tripped)"| SkipPrimary[Bypass primaryStore<br/>Zero Network Hammering]
|
|
159
|
+
SkipPrimary --> FallbackHop
|
|
160
|
+
|
|
161
|
+
BreakerCheck -->|"HALF_OPEN (Cooldown Passed)"| ProbeInFlight{Canary Probe In-Flight?}
|
|
162
|
+
ProbeInFlight -->|"No (Canary Flight)"| RunCanary[Dispatch Single-Flight Canary to primaryStore]
|
|
163
|
+
ProbeInFlight -->|"Yes (Concurrent)"| FallbackHop
|
|
164
|
+
|
|
165
|
+
RunCanary --> CanaryResult{Canary Succeeded?}
|
|
166
|
+
CanaryResult -->|"Yes"| CloseBreaker[Transition to CLOSED<br/>Resume primaryStore]
|
|
167
|
+
CanaryResult -->|"No"| ReopenBreaker[Transition to OPEN<br/>Reset Cooldown]
|
|
168
|
+
CloseBreaker --> NormalResp
|
|
169
|
+
ReopenBreaker --> FallbackHop
|
|
170
|
+
|
|
171
|
+
FallbackHop --> RunFallback[Execute fallbackStore.consume]
|
|
172
|
+
RunFallback --> FallbackQuota{Local Quota Exceeded?}
|
|
173
|
+
FallbackQuota -->|"No"| AllowDegraded[HTTP 200 OK<br/>RateLimit-Degraded: true]
|
|
174
|
+
FallbackQuota -->|"Yes"| BlockDegraded[HTTP 429 Too Many Requests<br/>RateLimit-Degraded: true]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Circuit Breaker State Transitions
|
|
180
|
+
|
|
181
|
+
```mermaid
|
|
182
|
+
stateDiagram-v2
|
|
183
|
+
[*] --> CLOSED : Initial State
|
|
184
|
+
|
|
185
|
+
CLOSED --> OPEN : consecutiveFailures >= failureThreshold<br/>(Store Error or Timeout)
|
|
186
|
+
CLOSED --> CLOSED : Success (resets failure count)
|
|
187
|
+
|
|
188
|
+
OPEN --> HALF_OPEN : Cooldown elapsed (resetTimeoutMs)<br/>+ Next request arrives (Lazy evaluation)
|
|
189
|
+
OPEN --> OPEN : Fast-fail with CircuitBreakerOpenError<br/>(Divert to fallbackStore)
|
|
190
|
+
|
|
191
|
+
HALF_OPEN --> CLOSED : Canary probe succeeds<br/>(Full recovery)
|
|
192
|
+
HALF_OPEN --> OPEN : Canary probe fails<br/>(Reopen circuit)
|
|
193
|
+
HALF_OPEN --> HALF_OPEN : Concurrent requests divert to fallback
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Algorithm Comparison Matrix
|
|
199
|
+
|
|
200
|
+
| Dimension | Fixed Window (`'fixed-window'`) | Sliding Window (`'sliding-window'`) | Token Bucket (`'token-bucket'`) |
|
|
201
|
+
| :--- | :--- | :--- | :--- |
|
|
202
|
+
| **Boundary Burst Prevention** | ❌ Prone to $2 \times N$ bursts at boundaries | ✅ **Strictly eliminated** in rolling window | ✅ **Controlled burst** up to `capacity`, sustained by `refillRate` |
|
|
203
|
+
| **Traffic Shaping** | Hard reset each window | Rolling window cutoff | **Continuous smooth refill** over time |
|
|
204
|
+
| **Refill Schedule** | Window duration (`windowMs`) | Rolling interval (`windowMs`) | **Custom interval** (`refillIntervalMs`, default 1s) |
|
|
205
|
+
| **Memory Complexity** | **$O(1)$** per key (counter + timestamp) | **$O(N)$** per key ($N$ timestamps in ZSET) | **$O(1)$** per key (tokens + lastRefill hash) |
|
|
206
|
+
| **HTTP Reset Semantics** | Window expiry boundary | Oldest rolling timestamp eviction | **Option C**: Time to full burst (allowed) or next request (429) |
|
|
207
|
+
| **Redis Data Structure** | String counter (`INCR`) | Sorted Set (`ZSET`) | Hash (`HMGET` / `HSET`) + Atomic Lua |
|
|
208
|
+
| **Fault Tolerance** | Supported via `ResilientStore` | Supported via `ResilientStore` | Supported via `ResilientStore` |
|
|
209
|
+
| **Recommended Use Case** | Coarse public DDoS prevention | High-security auth / login endpoints | **SaaS APIs, bursty frontends, background webhook queues** |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Configuration Reference
|
|
214
|
+
|
|
215
|
+
### Resilience & Store Options (v6)
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
app.use(
|
|
219
|
+
rateLimiter({
|
|
220
|
+
// Store configuration
|
|
221
|
+
store: redisStore, // Primary store (defaults to new MemoryStore())
|
|
222
|
+
fallbackStore: true, // Set true to auto-wrap with MemoryStore fallback, or pass custom store
|
|
223
|
+
|
|
224
|
+
// Timeout Guard
|
|
225
|
+
timeoutMs: 250, // Max store execution time (default: 250ms when using fallbackStore/ResilientStore; optional on direct stores)
|
|
226
|
+
|
|
227
|
+
// Store Failure Strategy (when no fallbackStore is configured)
|
|
228
|
+
onStoreError: 'fail-open', // 'fail-open', 'fail-closed', 'error', or custom fn (defaults to error bubbling next(err))
|
|
229
|
+
|
|
230
|
+
// Circuit Breaker Options
|
|
231
|
+
circuitBreaker: true, // Enable breaker (default: true when options configured)
|
|
232
|
+
breakerId: 'main-api', // Optional identifier for metric isolation (smartrate_circuit_breaker_state{breaker="main-api"})
|
|
233
|
+
failureThreshold: 5, // Consecutive errors to trip breaker (default: 5)
|
|
234
|
+
resetTimeoutMs: 10_000, // Cooldown duration before canary probe (default: 10,000ms)
|
|
235
|
+
successThreshold: 1, // Consecutive canary successes to close breaker (default: 1)
|
|
236
|
+
|
|
237
|
+
// Algorithm, Identity & Security
|
|
238
|
+
algorithm: 'token-bucket', // 'token-bucket' | 'sliding-window' | 'fixed-window'
|
|
239
|
+
capacity: 20,
|
|
240
|
+
refillRate: 5,
|
|
241
|
+
refillIntervalMs: 1000,
|
|
242
|
+
keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
|
|
243
|
+
hashClientIdentifier: true, // Set true to SHA-256 hash raw API keys/tokens before storage in Redis (default: false)
|
|
244
|
+
|
|
245
|
+
// Observability (v7)
|
|
246
|
+
metrics: true, // Register with defaultMetricsCollector (Prometheus)
|
|
247
|
+
openTelemetry: true // Auto-attach attributes & events to active OpenTelemetry span
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Response Headers Reference
|
|
253
|
+
|
|
254
|
+
| Header | Example | Description |
|
|
255
|
+
| :--- | :--- | :--- |
|
|
256
|
+
| `RateLimit-Limit` | `100` | Quota boundary for the active algorithm |
|
|
257
|
+
| `RateLimit-Remaining` | `94` | Tokens or requests remaining in current window |
|
|
258
|
+
| `RateLimit-Reset` | `12` | Seconds until quota replenishment or next retry eligibility |
|
|
259
|
+
| `Retry-After` | `30` | *(HTTP 429 & 503 only)* Seconds client must sleep before retrying |
|
|
260
|
+
| `RateLimit-Degraded` | `true` | Indicates a store failure or timeout. A configured fallback store enforces its own quota; with `fail-open` and no fallback, the request proceeds without rate limiting. This header does not by itself mean fallback quota enforcement is active. |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Demos & Interactive Scripts
|
|
265
|
+
|
|
266
|
+
### 1. Production Observability Stack (Docker Compose + Grafana + Prometheus) (v7)
|
|
267
|
+
```bash
|
|
268
|
+
# Launch the full containerized stack:
|
|
269
|
+
docker-compose -f examples/observability-stack/docker-compose.yml up -d
|
|
270
|
+
|
|
271
|
+
# Run the realistic multi-stage traffic generator:
|
|
272
|
+
node examples/observability-stack/simulate-traffic.js
|
|
273
|
+
```
|
|
274
|
+
Automated end-to-end observability stack featuring:
|
|
275
|
+
- **LimitForge Express App**: `http://localhost:3000` with ResilientStore and `/metrics`.
|
|
276
|
+
- **Prometheus Scraper**: `http://localhost:9090` evaluating production alerting rules.
|
|
277
|
+
- **Grafana Dashboard**: `http://localhost:3001` (admin / admin) with live throughput, block rate, latency, and breaker state.
|
|
278
|
+
- **Live Chaos Injection**: Real-time store failure (`POST /api/chaos/break-store`), degraded memory fallback, circuit tripping, and canary probe recovery (`POST /api/chaos/heal-store`).
|
|
279
|
+
|
|
280
|
+
### 2. Standalone Observability Express Demo (v7)
|
|
281
|
+
```bash
|
|
282
|
+
npm run demo:observability
|
|
283
|
+
# Or: node examples/express-demo/observability-demo.js
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 3. Production Outage & Resilience Demo (v6)
|
|
287
|
+
```bash
|
|
288
|
+
npm run demo:resilience
|
|
289
|
+
# Or: node examples/express-demo/resilience-demo.js
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 4. Token Bucket & Burst Control Demo (v5)
|
|
293
|
+
```bash
|
|
294
|
+
npm run demo:burst
|
|
295
|
+
# Or: node examples/express-demo/burst-demo.js
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 5. SaaS Multi-Tier Dynamic Policy Demo (v4)
|
|
299
|
+
```bash
|
|
300
|
+
npm run demo:saas
|
|
301
|
+
# Or: node examples/express-demo/saas-demo.js
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 6. Fixed vs. Sliding Window Comparison Demo (v3)
|
|
305
|
+
```bash
|
|
306
|
+
npm run demo:sliding
|
|
307
|
+
# Or: node examples/express-demo/sliding-demo.js
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 7. Distributed Multi-Instance Redis Demo (v2)
|
|
311
|
+
```bash
|
|
312
|
+
npm run demo:multi
|
|
313
|
+
# Or: node examples/express-demo/multi-instance.js
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Verification & Test Catalog (253 Tests)
|
|
319
|
+
|
|
320
|
+
Run the full automated test suite:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
npm test
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```text
|
|
327
|
+
ℹ tests 253
|
|
328
|
+
ℹ suites 98
|
|
329
|
+
ℹ pass 253
|
|
330
|
+
ℹ fail 0
|
|
331
|
+
ℹ duration_ms ~3400ms
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Test Suites Breakdown (253 Tests across 21 Test Files)
|
|
335
|
+
|
|
336
|
+
1. **`tests/telemetry.collector.test.js`** (34 tests) — **[v7]**
|
|
337
|
+
- Route normalization & cardinality guard (`UUID`, `ULID`, `CUID`, numeric, and hex hash sanitization).
|
|
338
|
+
- Monotonic counters, instantaneous gauges, cumulative histograms.
|
|
339
|
+
- RateLimiter telemetry integration and outcome labels (`allowed`, `blocked`).
|
|
340
|
+
- Degraded requests, fallback error labeling (`store="redis"` on fallback failover).
|
|
341
|
+
- Multi-breaker metric isolation with `breakerId` (`smartrate_circuit_breaker_state{breaker="auth"}`).
|
|
342
|
+
- Circuit breaker `stateChange` listener deduplication (zero leaks across 15+ limiters).
|
|
343
|
+
- `maxSeries` memory bound and strict series cap enforcement.
|
|
344
|
+
2. **`tests/telemetry.otel.test.js`** (15 tests) — **[v7]**
|
|
345
|
+
- `OpenTelemetryBridge` unit semantics and duck-typed active span extraction.
|
|
346
|
+
- Standard OpenTelemetry semantic conventions (`ratelimit.*`).
|
|
347
|
+
- Child span creation via `startSpan('smartrate.consume')` and automatic lifecycle cleanup in `finally`.
|
|
348
|
+
- ResilientStore failure and exception recording on spans.
|
|
349
|
+
- Verification of production Alertmanager rules (`assets/alerts/prometheus-rules.yml`) and Grafana dashboard (`assets/dashboards/smartrate-grafana-dashboard.json`).
|
|
350
|
+
- Observability stack configuration files verification.
|
|
351
|
+
3. **`tests/telemetry.prometheus.test.js`** (13 tests) — **[v7]**
|
|
352
|
+
- Native Prometheus text exposition serializer (`formatPrometheusMetrics`).
|
|
353
|
+
- Standard HELP and TYPE comments, escaping backslashes and double quotes.
|
|
354
|
+
- Cumulative histogram buckets (`le`, `+Inf`, `_sum`, `_count`).
|
|
355
|
+
- Express `/metrics` exporter middleware (`createPrometheusExporter`).
|
|
356
|
+
4. **`tests/keyGenerator.test.js`** (14 tests) — **[v4/v7]**
|
|
357
|
+
- Identity extraction (API Key, User ID, Multi-Tenant composite keys).
|
|
358
|
+
- `MAX_IDENTIFIER_LENGTH = 256` clamp protection against memory bloat.
|
|
359
|
+
- Optional cryptographic SHA-256 hashing via `hashClientIdentifier: true` and custom hashing functions.
|
|
360
|
+
- Collision-free hashing for pathological client identifiers.
|
|
361
|
+
- Secret token masking in Redis rate limit storage keys.
|
|
362
|
+
5. **`tests/resilience.resilientStore.test.js`** (25 tests) — **[v6]**
|
|
363
|
+
- Dual-Store `ResilientStore` constructor and option validation.
|
|
364
|
+
- Happy-path primary execution without fallback invocation.
|
|
365
|
+
- Transparent failover on infrastructure errors and timeout guard triggers.
|
|
366
|
+
- Circuit breaker tripping to `OPEN` and zero primary store hammering.
|
|
367
|
+
- Safe local in-memory quota degradation and 429 rejection on exhausted local quota.
|
|
368
|
+
- Lazy `HALF_OPEN` transition and single-flight canary probe execution.
|
|
369
|
+
- Concurrent request diversion during in-flight canary probe.
|
|
370
|
+
- Seamless traffic recovery to `CLOSED` upon primary healing.
|
|
371
|
+
- Express integration, automatic `RateLimit-Degraded: true` header, and `req.rateLimit` metadata.
|
|
372
|
+
6. **`tests/resilience.circuitBreaker.test.js`** (25 tests) — **[v6]**
|
|
373
|
+
- CircuitBreaker finite state machine (`CLOSED`, `OPEN`, `HALF_OPEN`).
|
|
374
|
+
- Lazy on-demand cooldown evaluation without background timers.
|
|
375
|
+
- Single-flight canary token validation and generation management.
|
|
376
|
+
- Failure classification isolation (network/store errors count; 429 quota exhaustion and route errors do not).
|
|
377
|
+
- Dynamic `Retry-After` derivation on fail-closed 503 responses based on remaining cooldown.
|
|
378
|
+
7. **`tests/resilience.timeout.test.js`** (18 tests) — **[v6]**
|
|
379
|
+
- `withTimeout` Promise wrapper with active timer cleanup.
|
|
380
|
+
- `StoreTimeoutError` instantiation and inheritance.
|
|
381
|
+
- Middleware `timeoutMs` option validation.
|
|
382
|
+
- `fail-open` strategy: graceful bypass with `RateLimit-Degraded: true` and `next()`.
|
|
383
|
+
- `fail-closed` strategy: HTTP 503 rejection with `RateLimit-Degraded` and dynamic `Retry-After`.
|
|
384
|
+
- Custom `onStoreError` callback execution and legacy backward-compatible bubbling.
|
|
385
|
+
8. **`tests/tokenBucket.recovery.test.js`** (11 tests) — **[v5]**
|
|
386
|
+
- Option C `RateLimit-Reset` semantics in MemoryStore and RedisStore.
|
|
387
|
+
- Multi-instance distributed Token Bucket across separate client and store instances.
|
|
388
|
+
- Full HTTP recovery cycle: `200 OK` (burst) $\to$ `429 Too Many Requests` $\to$ wait `Retry-After` $\to$ `200 OK`.
|
|
389
|
+
- 50-request parallel burst concurrency verification under RedisStore.
|
|
390
|
+
9. **`tests/tokenBucket.memory.test.js`** (17 tests) — **[v4/v5]**
|
|
391
|
+
- Fail-fast parameter validation for `capacity`, `refillRate`, `refillIntervalMs`.
|
|
392
|
+
- Derived omitted `refillRate` using configured refill intervals.
|
|
393
|
+
- Continuous in-memory refill math and capacity ceiling clamping.
|
|
394
|
+
- Mathematical zero-leak memory sweeper eviction.
|
|
395
|
+
10. **`tests/tokenBucket.redis.test.js`** (10 tests) — **[v4/v5]**
|
|
396
|
+
- Distributed Redis Token Bucket mechanics and continuous Lua refill math.
|
|
397
|
+
- Safe sliding activity TTL for storage cleanup.
|
|
398
|
+
- 50-request parallel concurrency stress test (zero race conditions).
|
|
399
|
+
11. **`tests/rateLimiter.test.js`** (14 tests) — **[v1]**
|
|
400
|
+
- Option validation, Fixed Window enforcement, IP/route/method isolation.
|
|
401
|
+
12. **`tests/slidingWindow.memory.test.js`** (10 tests) — **[v3]**
|
|
402
|
+
- In-memory rolling queue mechanics, half-open interval boundaries.
|
|
403
|
+
13. **`tests/redisStore.test.js`** (9 tests) — **[v2]**
|
|
404
|
+
- RedisStore client injection, eval vs sendCommand fallback.
|
|
405
|
+
14. **`tests/dynamicPolicy.test.js`** (7 tests) — **[v4]**
|
|
406
|
+
- Dynamic per-request capacity, refillRate, cost functions.
|
|
407
|
+
15. **`tests/benchmark.test.js`** (6 tests) — **[v7]**
|
|
408
|
+
- Overhead microbenchmarks across scenarios (no-op, MemoryStore, RedisStore, ResilientStore).
|
|
409
|
+
- Zero warmup handling and benchmark reporting.
|
|
410
|
+
16. **`tests/distributed.test.js`** (5 tests) — **[v3]**
|
|
411
|
+
- Cross-instance shared state verification across Express instances.
|
|
412
|
+
17. **`tests/slidingWindow.redis.test.js`** (5 tests) — **[v3]**
|
|
413
|
+
- Redis Sorted Set (ZSET) atomic Lua script execution.
|
|
414
|
+
18. **`tests/memoryStore.test.js`** (5 tests) — **[v1]**
|
|
415
|
+
- Memory store unit tests and cleanup sweepers.
|
|
416
|
+
19. **`tests/concurrency.test.js`** (4 tests) — **[v3]**
|
|
417
|
+
- Concurrency tests for Fixed Window and Sliding Window.
|
|
418
|
+
20. **`tests/redis.integration.test.js`** (3 tests) — **[v2]**
|
|
419
|
+
- Redis fixed window TTL and expiration.
|
|
420
|
+
21. **`tests/boundaryBurst.test.js`** (3 tests) — **[v3]**
|
|
421
|
+
- Boundary-burst verification (fixed vs sliding window).
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Roadmap & Version History
|
|
426
|
+
|
|
427
|
+
* **v1.0.0**: In-memory Fixed Window rate limiter for Express.js.
|
|
428
|
+
* **v2.0.0**: Pluggable storage architecture, distributed `RedisStore`, and atomic Lua script execution.
|
|
429
|
+
* **v3.0.0**: Rolling Sliding Window (`algorithm: 'sliding-window'`) with Redis Sorted Sets (ZSET) and boundary-burst elimination.
|
|
430
|
+
* **v4.0.0**: Custom client identity via `keyGenerator(req)`, dynamic tier-based policies, and weighted request costs.
|
|
431
|
+
* **v5.0.0**: Token Bucket + Controlled Burst rate limiting (`algorithm: 'token-bucket'`), sub-second continuous refill, Option C `RateLimit-Reset`, and HTTP client recovery flow.
|
|
432
|
+
* **v6.0.0**: Store Timeout Guard (`timeoutMs`, `withTimeout`), configurable failure policies (`onStoreError`), Circuit Breaker Engine (`CLOSED`, `OPEN`, `HALF_OPEN`), Dual-store resilience wrapper (`ResilientStore`), and local quota enforcement during degradation.
|
|
433
|
+
* **v7.0.0 (Released & Hardened)**:
|
|
434
|
+
- **Zero-Dependency Native Prometheus Exposition (`/metrics`)**: Serializes counters, gauges, and cumulative histograms strictly adhering to standard Prometheus format without third-party dependencies.
|
|
435
|
+
- **In-Memory Metrics Engine (`MetricsCollector`)**: High-throughput thread-safe aggregation with strict `maxSeries` memory bound and `_overflow` route aggregation.
|
|
436
|
+
- **Cardinality Guard & Dynamic Route Sanitization**: Automatically collapses numeric, UUID, ULID, and CUID identifiers into `:id` segments. User IDs and IPs are strictly excluded from metric labels.
|
|
437
|
+
- **Circuit Breaker Isolation & Listener Safety**: Supports `breakerId` / `id` for isolated gauge metrics (`smartrate_circuit_breaker_state{breaker="..."}`) and deduplicates listeners via WeakMap to prevent EventEmitter leaks.
|
|
438
|
+
- **ResilientStore Outage Observability**: Automatically increments `smartrate_store_errors_total{store="redis"}` and records span exceptions when primary store operations fail or time out during fallback.
|
|
439
|
+
- **OpenTelemetry Standard Semantic Conventions**: Enriches spans with `ratelimit.allowed`, `ratelimit.remaining`, `ratelimit.reset`, `ratelimit.algorithm`, `ratelimit.store`, and `ratelimit.degraded`. Creates child spans via `startSpan('smartrate.consume')` when tracer is configured.
|
|
440
|
+
- **Production Grafana & Prometheus Assets**: Full Alertmanager rules (`assets/alerts/prometheus-rules.yml`) and pre-built Grafana dashboard (`assets/dashboards/smartrate-grafana-dashboard.json`).
|
|
441
|
+
- **Production Observability Stack**: Complete Docker Compose setup with Prometheus, Grafana, Redis, and LimitForge, paired with an automated multi-stage traffic simulator (`examples/observability-stack/`).
|
|
442
|
+
- **Security & Identity Hardening**: Defensive clamping of client identifiers to 256 characters (`MAX_IDENTIFIER_LENGTH = 256`) and optional cryptographic SHA-256 hashing (`hashClientIdentifier: true`) to mask raw API tokens in Redis storage.
|
|
443
|
+
- **Nanosecond Benchmarks**: Realistic, empirical overhead measurement (`npm run benchmark`) using `process.hrtime.bigint()`.
|
|
444
|
+
* **v8.0.0 (Release & Production Hardening)**:
|
|
445
|
+
- **Official NPM Package Name**: Formally released as `limitforge` with clean packaging metadata and homepage.
|
|
446
|
+
- **Comprehensive TypeScript Definitions (`index.d.ts`)**: Full type definitions covering all options, algorithms, stores, circuit breakers, and telemetry exports.
|
|
447
|
+
- **Production Tarball Whitelist (`files`)**: Clean packaging including only runtime code (`src/`, `index.d.ts`, `README.md`, `LICENSE`), eliminating internal tests and dev assets.
|
|
448
|
+
- **Official MIT License**: Added root `LICENSE` file with official copyright.
|
|
449
|
+
- **Production Proxy Configuration**: Enabled `trust proxy` in demo app for accurate client IP resolution behind Render, Railway, Cloudflare, and AWS ALB.
|
|
450
|
+
- **Smart Redis-Backed Demo**: Live Express demo auto-detects `REDIS_URL` for distributed rate limiting with graceful fallback to `MemoryStore` and `/api/status` healthcheck.
|
|
451
|
+
- **Deployment Security Hardening**: Bound Redis to `127.0.0.1:6379`, parameterized Grafana admin secrets, and authenticated chaos failure injection endpoints.
|
|
452
|
+
- **GitHub Actions CI/CD Pipeline**: Multi-version test matrix (Node.js 18, 20, 22) with live Redis service container.
|
|
453
|
+
* **v8.0.1 (Repository & Branding Synchronization)**:
|
|
454
|
+
- **Synchronized Repository URLs**: Updated official repository, issues, and homepage metadata to `LimitForge`.
|
|
455
|
+
- **Documentation & Demo Alignment**: Fully unified all examples, observability dashboards, and benchmark banners under the LimitForge identity.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## License
|
|
460
|
+
|
|
461
|
+
MIT © [Anivesh](https://github.com/Anivesh91)
|