halt-rate 0.1.1 → 0.2.0
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/README.md +39 -1
- package/dist/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/next.d.mts +1 -1
- package/dist/adapters/next.d.ts +1 -1
- package/dist/adapters/next.js +37 -5
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/next.mjs +37 -5
- package/dist/adapters/next.mjs.map +1 -1
- package/dist/index.d.mts +53 -3
- package/dist/index.d.ts +53 -3
- package/dist/index.js +305 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +304 -6
- package/dist/index.mjs.map +1 -1
- package/dist/{limiter-qGH_X_KH.d.mts → limiter-HCt8ADZ2.d.mts} +51 -2
- package/dist/{limiter-qGH_X_KH.d.ts → limiter-HCt8ADZ2.d.ts} +51 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
💾 **Multiple Storage Backends**
|
|
17
17
|
- In-Memory (development, single-threaded)
|
|
18
|
-
- Redis (production, distributed
|
|
18
|
+
- Redis (production, distributed, **atomic via Lua**, cluster-safe) ✅
|
|
19
19
|
- PostgreSQL (ACID, relational)
|
|
20
20
|
- MongoDB (document store, TTL indexes)
|
|
21
21
|
- DynamoDB (AWS serverless, auto-scaling)
|
|
@@ -55,6 +55,9 @@ pnpm add halt
|
|
|
55
55
|
### Optional Dependencies
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
+
# Redis support (recommended for production)
|
|
59
|
+
npm install ioredis
|
|
60
|
+
|
|
58
61
|
# PostgreSQL support
|
|
59
62
|
npm install pg
|
|
60
63
|
|
|
@@ -80,6 +83,41 @@ import { InMemoryStore } from 'halt';
|
|
|
80
83
|
const store = new InMemoryStore();
|
|
81
84
|
```
|
|
82
85
|
|
|
86
|
+
### Redis (Production, recommended)
|
|
87
|
+
|
|
88
|
+
The Redis store runs the full check-and-consume **inside Redis via a Lua script**,
|
|
89
|
+
so limits stay accurate under concurrent load from any number of app servers. You
|
|
90
|
+
inject your own client (ioredis or node-redis v4+), so Halt has no hard dependency
|
|
91
|
+
on a particular Redis library.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import Redis from 'ioredis';
|
|
95
|
+
import { RateLimiter, RedisStore, presets } from 'halt';
|
|
96
|
+
|
|
97
|
+
const store = new RedisStore({
|
|
98
|
+
client: new Redis(process.env.REDIS_URL),
|
|
99
|
+
// What to do if Redis is unreachable:
|
|
100
|
+
// 'open' (default) — allow the request (don't take down traffic)
|
|
101
|
+
// 'closed' — block the request (429)
|
|
102
|
+
failMode: 'open',
|
|
103
|
+
onError: (err) => console.error('redis rate-limit error', err),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const limiter = new RateLimiter({ store, policy: presets.PUBLIC_API });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Atomicity.** All four algorithms are implemented as single-key Lua scripts, so a
|
|
110
|
+
burst of concurrent requests can never over-admit past the limit. The script reads
|
|
111
|
+
the clock from the Redis server (`TIME`), so app-server clock skew doesn't matter.
|
|
112
|
+
|
|
113
|
+
**Redis Cluster.** Every script touches exactly one key, so it is cluster-safe out
|
|
114
|
+
of the box. To colocate related keys on the same slot, wrap the variable part of
|
|
115
|
+
your key in a hash tag, e.g. `halt:{user-123}:...`.
|
|
116
|
+
|
|
117
|
+
> JavaScript has no synchronous network I/O, so the TypeScript store is async-only
|
|
118
|
+
> (the limiter's `check()` is already async). A synchronous client is available in
|
|
119
|
+
> the Python package.
|
|
120
|
+
|
|
83
121
|
### PostgreSQL
|
|
84
122
|
|
|
85
123
|
```typescript
|
package/dist/adapters/next.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import {
|
|
2
|
+
import { c as RateLimiterOptions, P as Policy } from '../limiter-HCt8ADZ2.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Next.js adapter for Halt rate limiting.
|
package/dist/adapters/next.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import {
|
|
2
|
+
import { c as RateLimiterOptions, P as Policy } from '../limiter-HCt8ADZ2.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Next.js adapter for Halt rate limiting.
|
package/dist/adapters/next.js
CHANGED
|
@@ -362,6 +362,11 @@ var LeakyBucket = class {
|
|
|
362
362
|
}
|
|
363
363
|
};
|
|
364
364
|
|
|
365
|
+
// src/core/store.ts
|
|
366
|
+
function isAtomicStore(store) {
|
|
367
|
+
return typeof store === "object" && store !== null && typeof store.evaluate === "function";
|
|
368
|
+
}
|
|
369
|
+
|
|
365
370
|
// src/core/limiter.ts
|
|
366
371
|
var RateLimiter = class {
|
|
367
372
|
constructor(options) {
|
|
@@ -406,6 +411,33 @@ var RateLimiter = class {
|
|
|
406
411
|
return resp;
|
|
407
412
|
}
|
|
408
413
|
const storageKey = `halt:${policy.name}:${key}`;
|
|
414
|
+
if (isAtomicStore(this.store)) {
|
|
415
|
+
const span2 = this.otelTracer?.startSpan?.("halt.check", {
|
|
416
|
+
attributes: { policy: policy.name, key }
|
|
417
|
+
});
|
|
418
|
+
const decision2 = await this.store.evaluate({
|
|
419
|
+
key: storageKey,
|
|
420
|
+
algorithm: policy.algorithm,
|
|
421
|
+
limit: policy.limit,
|
|
422
|
+
window: policy.window,
|
|
423
|
+
burst: policy.burst,
|
|
424
|
+
cost: requestCost,
|
|
425
|
+
ttl: policy.window * 2
|
|
426
|
+
});
|
|
427
|
+
this.metricsRecorder?.(
|
|
428
|
+
"halt.request.checked",
|
|
429
|
+
{ policy: policy.name, allowed: String(decision2.allowed) },
|
|
430
|
+
1
|
|
431
|
+
);
|
|
432
|
+
this.metricsRecorder?.(
|
|
433
|
+
decision2.allowed ? "halt.request.allowed" : "halt.request.blocked",
|
|
434
|
+
{ policy: policy.name },
|
|
435
|
+
1
|
|
436
|
+
);
|
|
437
|
+
span2?.end?.();
|
|
438
|
+
return decision2;
|
|
439
|
+
}
|
|
440
|
+
const store = this.store;
|
|
409
441
|
let algorithm = this.algorithmCache.get(policy.name);
|
|
410
442
|
if (!algorithm) {
|
|
411
443
|
if (policy.algorithm === "token_bucket" /* TOKEN_BUCKET */) {
|
|
@@ -423,7 +455,7 @@ var RateLimiter = class {
|
|
|
423
455
|
this.algorithmCache.set(policy.name, algorithm);
|
|
424
456
|
}
|
|
425
457
|
const span = this.otelTracer?.startSpan?.("halt.check", { attributes: { policy: policy.name, key } });
|
|
426
|
-
const state =
|
|
458
|
+
const state = store.get(storageKey);
|
|
427
459
|
let decision;
|
|
428
460
|
if (algorithm instanceof TokenBucket) {
|
|
429
461
|
let tokens;
|
|
@@ -439,7 +471,7 @@ var RateLimiter = class {
|
|
|
439
471
|
const result = algorithm.checkAndConsume(tokens, lastRefill, requestCost);
|
|
440
472
|
decision = result.decision;
|
|
441
473
|
const ttl = policy.window * 2;
|
|
442
|
-
|
|
474
|
+
store.set(storageKey, { tokens: result.newTokens, lastRefill: result.newLastRefill }, ttl);
|
|
443
475
|
} else if (algorithm instanceof FixedWindow) {
|
|
444
476
|
let count;
|
|
445
477
|
let windowStart;
|
|
@@ -454,13 +486,13 @@ var RateLimiter = class {
|
|
|
454
486
|
const result = algorithm.checkAndConsume(count, windowStart, requestCost);
|
|
455
487
|
decision = result.decision;
|
|
456
488
|
const ttl = policy.window * 2;
|
|
457
|
-
|
|
489
|
+
store.set(storageKey, { count: result.newCount, windowStart: result.newWindowStart }, ttl);
|
|
458
490
|
} else if (algorithm instanceof SlidingWindow) {
|
|
459
491
|
const buckets = state || algorithm.initialState();
|
|
460
492
|
const result = algorithm.checkAndConsume(buckets, requestCost);
|
|
461
493
|
decision = result.decision;
|
|
462
494
|
const ttl = policy.window * 2;
|
|
463
|
-
|
|
495
|
+
store.set(storageKey, result.newBuckets, ttl);
|
|
464
496
|
} else if (algorithm instanceof LeakyBucket) {
|
|
465
497
|
let level;
|
|
466
498
|
let lastLeak;
|
|
@@ -475,7 +507,7 @@ var RateLimiter = class {
|
|
|
475
507
|
const result = algorithm.checkAndConsume(level, lastLeak, requestCost);
|
|
476
508
|
decision = result.decision;
|
|
477
509
|
const ttl = policy.window * 2;
|
|
478
|
-
|
|
510
|
+
store.set(storageKey, { level: result.newLevel, lastLeak: result.newLastLeak }, ttl);
|
|
479
511
|
} else {
|
|
480
512
|
throw new Error(`Algorithm ${typeof algorithm} not supported`);
|
|
481
513
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/policy.ts","../../src/core/extractors.ts","../../src/algorithms/token-bucket.ts","../../src/algorithms/fixed-window.ts","../../src/algorithms/sliding-window.ts","../../src/algorithms/leaky-bucket.ts","../../src/core/limiter.ts","../../src/core/decision.ts","../../src/adapters/next.ts"],"names":["ip","NextResponse"],"mappings":"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","file":"next.js","sourcesContent":["/**\n * Policy model for rate limiting configuration.\n */\n\nexport enum KeyStrategy {\n IP = 'ip',\n USER = 'user',\n API_KEY = 'api_key',\n COMPOSITE = 'composite',\n CUSTOM = 'custom',\n}\n\nexport enum Algorithm {\n TOKEN_BUCKET = 'token_bucket',\n FIXED_WINDOW = 'fixed_window',\n SLIDING_WINDOW = 'sliding_window',\n LEAKY_BUCKET = 'leaky_bucket',\n}\n\nexport interface Policy {\n /** Human-readable policy name */\n name: string;\n /** Maximum number of requests allowed */\n limit: number;\n /** Time window in seconds */\n window: number;\n /** Rate limiting algorithm to use */\n algorithm?: Algorithm;\n /** Strategy for extracting the rate limit key */\n keyStrategy?: KeyStrategy;\n /** Maximum burst size (for token bucket) */\n burst?: number;\n /** Cost per request (default: 1) */\n cost?: number;\n /** Duration to block after limit exceeded (seconds) */\n blockDuration?: number;\n /** Custom function to extract key from request */\n keyExtractor?: (request: any) => string | null;\n /** List of paths or IPs to exempt from rate limiting */\n exemptions?: string[];\n}\n\n/**\n * Validate and normalize policy configuration.\n */\nexport function normalizePolicy(policy: Policy): Required<Policy> {\n const normalized: Required<Policy> = {\n name: policy.name,\n limit: policy.limit,\n window: policy.window,\n algorithm: policy.algorithm ?? Algorithm.TOKEN_BUCKET,\n keyStrategy: policy.keyStrategy ?? KeyStrategy.IP,\n burst: policy.burst ?? Math.floor(policy.limit * 1.2),\n cost: policy.cost ?? 1,\n blockDuration: policy.blockDuration ?? undefined,\n keyExtractor: policy.keyExtractor ?? undefined,\n exemptions: policy.exemptions ?? [],\n } as Required<Policy>;\n\n // Validation\n if (normalized.limit <= 0) {\n throw new Error('limit must be positive');\n }\n\n if (normalized.window <= 0) {\n throw new Error('window must be positive');\n }\n\n if (normalized.cost <= 0) {\n throw new Error('cost must be positive');\n }\n\n if (normalized.burst < normalized.limit) {\n throw new Error('burst must be >= limit');\n }\n\n return normalized;\n}\n","/**\n * Utility functions for extracting rate limit keys from requests.\n */\n\n// Default health check paths\nexport const HEALTH_CHECK_PATHS = new Set([\n '/health',\n '/ping',\n '/ready',\n '/healthz',\n '/livez',\n]);\n\n// Private IP ranges (CIDR notation)\nconst PRIVATE_IP_PATTERNS = [\n /^127\\./,\n /^10\\./,\n /^172\\.(1[6-9]|2\\d|3[01])\\./,\n /^192\\.168\\./,\n /^::1$/,\n /^fc00:/,\n];\n\n/**\n * Extract client IP from request, respecting X-Forwarded-For if from trusted proxy.\n */\nexport function extractIp(request: any, trustedProxies: string[] = []): string | null {\n let clientIp: string | null = null;\n\n // Try different request object patterns\n if (request.socket?.remoteAddress) {\n // Node.js/Express\n clientIp = request.socket.remoteAddress;\n } else if (request.ip) {\n // Express with trust proxy\n clientIp = request.ip;\n } else if (request.connection?.remoteAddress) {\n // Older Node.js\n clientIp = request.connection.remoteAddress;\n }\n\n if (!clientIp) {\n return null;\n }\n\n // Clean IPv6-mapped IPv4 addresses\n clientIp = clientIp.replace(/^::ffff:/, '');\n\n // Check if we should trust X-Forwarded-For\n if (trustedProxies.length > 0 && isTrustedProxy(clientIp, trustedProxies)) {\n const forwarded = getForwardedFor(request);\n if (forwarded) {\n return forwarded;\n }\n }\n\n return clientIp;\n}\n\n/**\n * Extract first IP from X-Forwarded-For header.\n */\nfunction getForwardedFor(request: any): string | null {\n const headers = request.headers || {};\n const forwarded = headers['x-forwarded-for'] || headers['X-Forwarded-For'];\n\n if (forwarded) {\n // Take the first IP (client IP)\n return forwarded.split(',')[0].trim();\n }\n\n return null;\n}\n\n/**\n * Check if IP is in trusted proxy list.\n */\nfunction isTrustedProxy(ip: string, trustedProxies: string[]): boolean {\n return trustedProxies.some((proxy) => {\n if (proxy.includes('/')) {\n // CIDR notation - simplified check\n return ip.startsWith(proxy.split('/')[0].split('.').slice(0, -1).join('.'));\n }\n return ip === proxy;\n });\n}\n\n/**\n * Check if IP is in private range.\n */\nexport function isPrivateIp(ip: string): boolean {\n return PRIVATE_IP_PATTERNS.some((pattern) => pattern.test(ip));\n}\n\n/**\n * Check if path is a health check endpoint.\n */\nexport function isHealthCheck(path: string): boolean {\n return HEALTH_CHECK_PATHS.has(path);\n}\n\n/**\n * Extract user ID from request.\n */\nexport function extractUserId(request: any): string | null {\n // Try common patterns\n if (request.user?.id) {\n return String(request.user.id);\n }\n\n if (request.userId) {\n return String(request.userId);\n }\n\n return null;\n}\n\n/**\n * Extract API key from request headers.\n */\nexport function extractApiKey(request: any): string | null {\n const headers = request.headers || {};\n\n // Try common header names\n const apiKey =\n headers['x-api-key'] ||\n headers['X-API-Key'] ||\n headers['authorization'] ||\n headers['Authorization'];\n\n if (apiKey) {\n // Handle Bearer tokens\n if (apiKey.startsWith('Bearer ')) {\n return apiKey.substring(7);\n }\n return apiKey;\n }\n\n return null;\n}\n\n/**\n * Extract path from request.\n */\nexport function extractPath(request: any): string | null {\n // Next.js\n if (request.nextUrl?.pathname) {\n return request.nextUrl.pathname;\n }\n\n // Express\n if (request.path) {\n return request.path;\n }\n\n if (request.url) {\n try {\n const url = new URL(request.url, 'http://localhost');\n return url.pathname;\n } catch {\n return request.url;\n }\n }\n\n return null;\n}\n","/**\n * Token bucket rate limiting algorithm.\n */\n\nimport { Decision } from '../core/decision';\n\nexport interface TokenBucketState {\n tokens: number;\n lastRefill: number;\n}\n\nexport class TokenBucket {\n private capacity: number;\n private rate: number;\n private window: number;\n\n constructor(capacity: number, rate: number, window: number) {\n this.capacity = capacity;\n this.rate = rate / window; // tokens per second\n this.window = window;\n }\n\n /**\n * Check if request is allowed and consume tokens.\n */\n checkAndConsume(\n currentTokens: number,\n lastRefill: number,\n cost: number,\n now: number = Date.now() / 1000\n ): { decision: Decision; newTokens: number; newLastRefill: number } {\n // Refill tokens based on elapsed time\n const elapsed = now - lastRefill;\n const refillAmount = elapsed * this.rate;\n const newTokens = Math.min(this.capacity, currentTokens + refillAmount);\n\n // Calculate reset time (when bucket will be full)\n const tokensNeeded = this.capacity - newTokens;\n const resetAt = Math.floor(now + tokensNeeded / this.rate);\n\n // Check if we have enough tokens\n if (newTokens >= cost) {\n // Consume tokens\n const tokensAfterConsume = newTokens - cost;\n const remaining = Math.floor(tokensAfterConsume);\n\n return {\n decision: {\n allowed: true,\n limit: Math.floor(this.rate * this.window),\n remaining,\n resetAt,\n },\n newTokens: tokensAfterConsume,\n newLastRefill: now,\n };\n } else {\n // Not enough tokens\n const tokensDeficit = cost - newTokens;\n const retryAfter = Math.floor(tokensDeficit / this.rate) + 1;\n\n return {\n decision: {\n allowed: false,\n limit: Math.floor(this.rate * this.window),\n remaining: 0,\n resetAt,\n retryAfter,\n },\n newTokens,\n newLastRefill: lastRefill, // Don't update last_refill on rejection\n };\n }\n }\n\n /**\n * Get initial state for a new key.\n */\n initialState(now: number = Date.now() / 1000): TokenBucketState {\n return {\n tokens: this.capacity,\n lastRefill: now,\n };\n }\n}\n","/**\n * Fixed window rate limiting algorithm.\n */\n\nimport { Decision } from '../core/decision';\n\nexport interface FixedWindowState {\n count: number;\n windowStart: number;\n}\n\nexport class FixedWindow {\n private limit: number;\n private window: number;\n\n constructor(limit: number, window: number) {\n this.limit = limit;\n this.window = window;\n }\n\n /**\n * Check if request is allowed and increment counter.\n */\n checkAndConsume(\n currentCount: number,\n windowStart: number,\n cost: number,\n now: number = Date.now() / 1000\n ): { decision: Decision; newCount: number; newWindowStart: number } {\n // Check if we're in a new window\n const timeSinceStart = now - windowStart;\n if (timeSinceStart >= this.window) {\n // New window - reset counter\n currentCount = 0;\n windowStart = now;\n }\n\n // Calculate reset time (end of current window)\n const resetAt = Math.floor(windowStart + this.window);\n\n // Check if we have capacity\n if (currentCount + cost <= this.limit) {\n // Allow request\n const newCount = currentCount + cost;\n const remaining = this.limit - newCount;\n\n return {\n decision: {\n allowed: true,\n limit: this.limit,\n remaining,\n resetAt,\n },\n newCount,\n newWindowStart: windowStart,\n };\n } else {\n // Block request\n const retryAfter = Math.floor(resetAt - now) + 1;\n\n return {\n decision: {\n allowed: false,\n limit: this.limit,\n remaining: 0,\n resetAt,\n retryAfter,\n },\n newCount: currentCount,\n newWindowStart: windowStart,\n };\n }\n }\n\n /**\n * Get initial state for a new key.\n */\n initialState(now: number = Date.now() / 1000): FixedWindowState {\n return {\n count: 0,\n windowStart: now,\n };\n }\n}\n","/**\n * Sliding window rate limiting algorithm.\n */\n\nimport { Decision } from '../core/decision';\n\nexport type SlidingWindowState = Record<number, number>;\n\nexport class SlidingWindow {\n private limit: number;\n private window: number;\n private precision: number;\n private bucketSize: number;\n\n constructor(limit: number, window: number, precision: number = 10) {\n this.limit = limit;\n this.window = window;\n this.precision = precision;\n this.bucketSize = window / precision;\n }\n\n /**\n * Check if request is allowed and update buckets.\n */\n checkAndConsume(\n buckets: SlidingWindowState,\n cost: number,\n now: number = Date.now() / 1000\n ): { decision: Decision; newBuckets: SlidingWindowState } {\n // Calculate current bucket\n const currentBucket = Math.floor(now / this.bucketSize);\n\n // Remove expired buckets (older than window)\n const cutoffBucket = currentBucket - this.precision;\n const newBuckets: SlidingWindowState = {};\n\n for (const [bucketIdStr, count] of Object.entries(buckets)) {\n const bucketId = parseInt(bucketIdStr, 10);\n if (bucketId > cutoffBucket) {\n newBuckets[bucketId] = count;\n }\n }\n\n // Count requests in sliding window\n const totalCount = Object.values(newBuckets).reduce((sum, count) => sum + count, 0);\n\n // Calculate reset time (when oldest bucket expires)\n const bucketIds = Object.keys(newBuckets).map((id) => parseInt(id, 10));\n const oldestBucket = bucketIds.length > 0 ? Math.min(...bucketIds) : currentBucket;\n const resetAt = Math.floor((oldestBucket + this.precision + 1) * this.bucketSize);\n\n // Check if we have capacity\n if (totalCount + cost <= this.limit) {\n // Allow request\n newBuckets[currentBucket] = (newBuckets[currentBucket] || 0) + cost;\n const remaining = this.limit - (totalCount + cost);\n\n return {\n decision: {\n allowed: true,\n limit: this.limit,\n remaining,\n resetAt,\n },\n newBuckets,\n };\n } else {\n // Block request\n const retryAfter = Math.floor(this.bucketSize) + 1;\n\n return {\n decision: {\n allowed: false,\n limit: this.limit,\n remaining: 0,\n resetAt,\n retryAfter,\n },\n newBuckets,\n };\n }\n }\n\n /**\n * Get initial state for a new key.\n */\n initialState(): SlidingWindowState {\n return {};\n }\n}\n","/**\n * Leaky bucket rate limiting algorithm.\n */\n\nimport { Decision } from '../core/decision';\n\nexport interface LeakyBucketState {\n level: number;\n lastLeak: number;\n}\n\nexport class LeakyBucket {\n private capacity: number;\n private leakRate: number;\n private window: number;\n\n constructor(capacity: number, leakRate: number, window: number) {\n this.capacity = capacity;\n this.leakRate = leakRate; // requests per second\n this.window = window;\n }\n\n /**\n * Check if request is allowed and update bucket level.\n */\n checkAndConsume(\n currentLevel: number,\n lastLeak: number,\n cost: number,\n now: number = Date.now() / 1000\n ): { decision: Decision; newLevel: number; newLastLeak: number } {\n // Calculate how much has leaked since last check\n const elapsed = now - lastLeak;\n const leaked = elapsed * this.leakRate;\n\n // Update bucket level (can't go below 0)\n let newLevel = Math.max(0, currentLevel - leaked);\n\n // Calculate when bucket will be empty\n let resetAt: number;\n if (newLevel > 0) {\n const timeToEmpty = newLevel / this.leakRate;\n resetAt = Math.floor(now + timeToEmpty);\n } else {\n resetAt = Math.floor(now);\n }\n\n // Check if we have capacity\n if (newLevel + cost <= this.capacity) {\n // Allow request - add to bucket\n newLevel += cost;\n const remaining = Math.floor(this.capacity - newLevel);\n\n return {\n decision: {\n allowed: true,\n limit: this.capacity,\n remaining,\n resetAt,\n },\n newLevel,\n newLastLeak: now,\n };\n } else {\n // Block request - bucket is full\n // Calculate when there will be enough capacity\n const spaceNeeded = newLevel + cost - this.capacity;\n const retryAfter = Math.floor(spaceNeeded / this.leakRate) + 1;\n\n return {\n decision: {\n allowed: false,\n limit: this.capacity,\n remaining: 0,\n resetAt,\n retryAfter,\n },\n newLevel,\n newLastLeak: now,\n };\n }\n }\n\n /**\n * Get initial state for a new key.\n */\n initialState(now: number = Date.now() / 1000): LeakyBucketState {\n return {\n level: 0,\n lastLeak: now,\n };\n }\n}\n","/**\n * Main rate limiter implementation.\n */\n\nimport { Decision } from './decision';\nimport { Policy, KeyStrategy, Algorithm, normalizePolicy } from './policy';\nimport {\n extractIp,\n extractUserId,\n extractApiKey,\n extractPath,\n isHealthCheck,\n isPrivateIp,\n} from './extractors';\nimport { TokenBucket } from '../algorithms/token-bucket';\nimport { FixedWindow } from '../algorithms/fixed-window';\nimport { SlidingWindow } from '../algorithms/sliding-window';\nimport { LeakyBucket } from '../algorithms/leaky-bucket';\nimport { Store } from '../stores/memory';\n\ntype AlgorithmInstance = TokenBucket | FixedWindow | SlidingWindow | LeakyBucket;\n\nexport interface RateLimiterOptions {\n store: Store;\n /** Either a static policy or a resolver returning a policy per-request */\n policy: Policy | ((request: any) => Policy | Promise<Policy>);\n trustedProxies?: string[];\n exemptPrivateIps?: boolean;\n /** Optional OpenTelemetry-like tracer (object with startSpan) */\n otelTracer?: any;\n /** Optional metrics recorder: (name, tags?, value?) => void */\n metricsRecorder?: (name: string, tags?: Record<string, string>, value?: number) => void;\n}\n\nexport class RateLimiter {\n private store: Store;\n private policyOrResolver: Policy | ((request: any) => Policy | Promise<Policy>);\n private trustedProxies: string[];\n private exemptPrivateIps: boolean;\n private algorithmCache: Map<string, AlgorithmInstance>;\n private otelTracer?: any;\n private metricsRecorder?: (name: string, tags?: Record<string, string>, value?: number) => void;\n\n constructor(options: RateLimiterOptions) {\n this.store = options.store;\n this.policyOrResolver = options.policy;\n this.trustedProxies = options.trustedProxies ?? [];\n this.exemptPrivateIps = options.exemptPrivateIps ?? true;\n this.algorithmCache = new Map();\n this.otelTracer = options.otelTracer;\n this.metricsRecorder = options.metricsRecorder;\n }\n\n /**\n * Check if request is allowed under rate limit.\n */\n /**\n * Check if request is allowed under rate limit.\n * This method is async because policy resolution may be async (e.g. DB lookup).\n */\n async check(request: any, cost?: number): Promise<Decision> {\n // Resolve policy (static or per-request)\n const resolved =\n typeof this.policyOrResolver === 'function'\n ? await (this.policyOrResolver as (r: any) => Policy | Promise<Policy>)(request)\n : (this.policyOrResolver as Policy);\n\n const policy = normalizePolicy(resolved);\n const requestCost = cost ?? policy.cost;\n\n // Quick exemptions check (policy-aware)\n if (this.isExempt(request, policy)) {\n const resp: Decision = {\n allowed: true,\n limit: policy.limit,\n remaining: policy.limit,\n resetAt: Math.floor(Date.now() / 1000 + policy.window),\n };\n this.metricsRecorder?.('halt.request.exempt', { policy: policy.name }, 1);\n return resp;\n }\n\n // Extract key\n const key = this.extractKey(request, policy);\n if (!key) {\n const resp: Decision = {\n allowed: true,\n limit: policy.limit,\n remaining: policy.limit,\n resetAt: Math.floor(Date.now() / 1000 + policy.window),\n };\n this.metricsRecorder?.('halt.request.no_key', { policy: policy.name }, 1);\n return resp;\n }\n\n const storageKey = `halt:${policy.name}:${key}`;\n\n // Get or create algorithm instance for this policy\n let algorithm = this.algorithmCache.get(policy.name);\n if (!algorithm) {\n if (policy.algorithm === Algorithm.TOKEN_BUCKET) {\n algorithm = new TokenBucket(policy.burst, policy.limit, policy.window);\n } else if (policy.algorithm === Algorithm.FIXED_WINDOW) {\n algorithm = new FixedWindow(policy.limit, policy.window);\n } else if (policy.algorithm === Algorithm.SLIDING_WINDOW) {\n algorithm = new SlidingWindow(policy.limit, policy.window);\n } else if (policy.algorithm === Algorithm.LEAKY_BUCKET) {\n const leakRate = policy.limit / policy.window;\n algorithm = new LeakyBucket(policy.burst, leakRate, policy.window);\n } else {\n throw new Error(`Algorithm ${policy.algorithm} not implemented`);\n }\n this.algorithmCache.set(policy.name, algorithm);\n }\n\n // Instrumentation: start a span if tracer available\n const span = this.otelTracer?.startSpan?.('halt.check', { attributes: { policy: policy.name, key } });\n\n const state = this.store.get(storageKey);\n let decision: Decision;\n\n if (algorithm instanceof TokenBucket) {\n let tokens: number;\n let lastRefill: number;\n\n if (!state) {\n const initialState = algorithm.initialState();\n tokens = initialState.tokens;\n lastRefill = initialState.lastRefill;\n } else {\n tokens = state.tokens;\n lastRefill = state.lastRefill;\n }\n\n const result = algorithm.checkAndConsume(tokens, lastRefill, requestCost);\n decision = result.decision;\n\n const ttl = policy.window * 2;\n this.store.set(storageKey, { tokens: result.newTokens, lastRefill: result.newLastRefill }, ttl);\n } else if (algorithm instanceof FixedWindow) {\n let count: number;\n let windowStart: number;\n\n if (!state) {\n const initialState = algorithm.initialState();\n count = initialState.count;\n windowStart = initialState.windowStart;\n } else {\n count = state.count;\n windowStart = state.windowStart;\n }\n\n const result = algorithm.checkAndConsume(count, windowStart, requestCost);\n decision = result.decision;\n\n const ttl = policy.window * 2;\n this.store.set(storageKey, { count: result.newCount, windowStart: result.newWindowStart }, ttl);\n } else if (algorithm instanceof SlidingWindow) {\n const buckets = state || algorithm.initialState();\n const result = algorithm.checkAndConsume(buckets, requestCost);\n decision = result.decision;\n\n const ttl = policy.window * 2;\n this.store.set(storageKey, result.newBuckets, ttl);\n } else if (algorithm instanceof LeakyBucket) {\n let level: number;\n let lastLeak: number;\n\n if (!state) {\n const initialState = algorithm.initialState();\n level = initialState.level;\n lastLeak = initialState.lastLeak;\n } else {\n level = state.level;\n lastLeak = state.lastLeak;\n }\n\n const result = algorithm.checkAndConsume(level, lastLeak, requestCost);\n decision = result.decision;\n\n const ttl = policy.window * 2;\n this.store.set(storageKey, { level: result.newLevel, lastLeak: result.newLastLeak }, ttl);\n } else {\n throw new Error(`Algorithm ${typeof algorithm} not supported`);\n }\n\n // Metrics and telemetry\n this.metricsRecorder?.('halt.request.checked', { policy: policy.name, allowed: String(decision.allowed) }, 1);\n if (decision.allowed) {\n this.metricsRecorder?.('halt.request.allowed', { policy: policy.name }, 1);\n } else {\n this.metricsRecorder?.('halt.request.blocked', { policy: policy.name }, 1);\n }\n\n span?.end?.();\n\n return decision;\n }\n\n /**\n * Extract rate limit key from request based on policy strategy.\n */\n private extractKey(request: any, policy: Policy): string | null {\n // Use custom extractor if provided\n if (policy.keyExtractor) {\n return policy.keyExtractor(request);\n }\n\n // Use built-in strategies\n if (policy.keyStrategy === KeyStrategy.IP) {\n return extractIp(request, this.trustedProxies);\n }\n\n if (policy.keyStrategy === KeyStrategy.USER) {\n return extractUserId(request);\n }\n\n if (policy.keyStrategy === KeyStrategy.API_KEY) {\n return extractApiKey(request);\n }\n\n if (policy.keyStrategy === KeyStrategy.COMPOSITE) {\n // Composite: user:ip or api_key:ip\n const user = extractUserId(request);\n const apiKey = extractApiKey(request);\n const ip = extractIp(request, this.trustedProxies);\n\n if (user && ip) {\n return `${user}:${ip}`;\n } else if (apiKey && ip) {\n return `${apiKey}:${ip}`;\n } else if (user) {\n return user;\n } else if (apiKey) {\n return apiKey;\n } else {\n return ip;\n }\n }\n\n return null;\n }\n\n /**\n * Check if request is exempt from rate limiting.\n */\n private isExempt(request: any, policy: Policy): boolean {\n // Check health check paths\n const path = extractPath(request);\n if (path && isHealthCheck(path)) {\n return true;\n }\n\n // Check custom exemptions\n if (path && (policy.exemptions ?? []).includes(path)) {\n return true;\n }\n\n // Check private IPs\n if (this.exemptPrivateIps) {\n const ip = extractIp(request, this.trustedProxies);\n if (ip && isPrivateIp(ip)) {\n return true;\n }\n }\n\n // Check IP exemptions\n const ip = extractIp(request, this.trustedProxies);\n if (ip && (policy.exemptions ?? []).includes(ip)) {\n return true;\n }\n\n return false;\n }\n}\n","/**\n * Decision model for rate limiting results.\n */\n\nexport interface Decision {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Maximum number of requests allowed in the window */\n limit: number;\n /** Number of requests remaining in the current window */\n remaining: number;\n /** Unix timestamp when the limit resets */\n resetAt: number;\n /** Seconds to wait before retrying (only set when blocked) */\n retryAfter?: number;\n}\n\n/**\n * Convert decision to standard rate limit headers.\n */\nexport function toHeaders(decision: Decision): Record<string, string> {\n const headers: Record<string, string> = {\n 'RateLimit-Limit': String(decision.limit),\n 'RateLimit-Remaining': String(Math.max(0, decision.remaining)),\n 'RateLimit-Reset': String(decision.resetAt),\n };\n\n if (!decision.allowed && decision.retryAfter !== undefined) {\n headers['Retry-After'] = String(decision.retryAfter);\n }\n\n return headers;\n}\n","/**\n * Next.js adapter for Halt rate limiting.\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { RateLimiter, RateLimiterOptions } from '../core/limiter';\nimport { toHeaders } from '../core/decision';\nimport { Policy } from '../core/policy';\n\n/**\n * Create Next.js middleware for rate limiting.\n */\nexport function haltMiddleware(options: RateLimiterOptions) {\n const limiter = new RateLimiter(options);\n\n return async (request: NextRequest) => {\n const decision = await limiter.check(request);\n\n if (decision.allowed) {\n // Allow request with rate limit headers\n const response = NextResponse.next();\n const headers = toHeaders(decision);\n for (const [key, value] of Object.entries(headers)) {\n response.headers.set(key, value);\n }\n return response;\n } else {\n // Block request\n const headers = toHeaders(decision);\n return NextResponse.json(\n {\n error: 'rate_limit_exceeded',\n message: 'Too many requests. Please try again later.',\n retryAfter: decision.retryAfter,\n },\n {\n status: 429,\n headers,\n }\n );\n }\n };\n}\n\n/**\n * Wrapper for Next.js route handlers to add rate limiting.\n */\nexport function withHalt<T extends (...args: any[]) => Promise<Response>>(\n handler: T,\n options: RateLimiterOptions\n): T {\n const limiter = new RateLimiter(options);\n\n return (async (...args: any[]) => {\n const request = args[0] as NextRequest;\n const decision = await limiter.check(request);\n\n if (decision.allowed) {\n // Call the original handler\n const response = await handler(...args);\n\n // Add rate limit headers\n const headers = toHeaders(decision);\n for (const [key, value] of Object.entries(headers)) {\n response.headers.set(key, value);\n }\n\n return response;\n } else {\n // Block request\n const headers = toHeaders(decision);\n return NextResponse.json(\n {\n error: 'rate_limit_exceeded',\n message: 'Too many requests. Please try again later.',\n retryAfter: decision.retryAfter,\n },\n {\n status: 429,\n headers,\n }\n );\n }\n }) as T;\n}\n\n/**\n * Simple wrapper that accepts just a policy for convenience.\n */\nexport function withPolicy<T extends (...args: any[]) => Promise<Response>>(\n handler: T,\n policy: Policy,\n store: RateLimiterOptions['store']\n): T {\n return withHalt(handler, { store, policy });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/policy.ts","../../src/core/extractors.ts","../../src/algorithms/token-bucket.ts","../../src/algorithms/fixed-window.ts","../../src/algorithms/sliding-window.ts","../../src/algorithms/leaky-bucket.ts","../../src/core/store.ts","../../src/core/limiter.ts","../../src/core/decision.ts","../../src/adapters/next.ts"],"names":["span","decision","ip","NextResponse"],"mappings":"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","file":"next.js","sourcesContent":["/**\n * Policy model for rate limiting configuration.\n */\n\nexport enum KeyStrategy {\n IP = 'ip',\n USER = 'user',\n API_KEY = 'api_key',\n COMPOSITE = 'composite',\n CUSTOM = 'custom',\n}\n\nexport enum Algorithm {\n TOKEN_BUCKET = 'token_bucket',\n FIXED_WINDOW = 'fixed_window',\n SLIDING_WINDOW = 'sliding_window',\n LEAKY_BUCKET = 'leaky_bucket',\n}\n\nexport interface Policy {\n /** Human-readable policy name */\n name: string;\n /** Maximum number of requests allowed */\n limit: number;\n /** Time window in seconds */\n window: number;\n /** Rate limiting algorithm to use */\n algorithm?: Algorithm;\n /** Strategy for extracting the rate limit key */\n keyStrategy?: KeyStrategy;\n /** Maximum burst size (for token bucket) */\n burst?: number;\n /** Cost per request (default: 1) */\n cost?: number;\n /** Duration to block after limit exceeded (seconds) */\n blockDuration?: number;\n /** Custom function to extract key from request */\n keyExtractor?: (request: any) => string | null;\n /** List of paths or IPs to exempt from rate limiting */\n exemptions?: string[];\n}\n\n/**\n * Validate and normalize policy configuration.\n */\nexport function normalizePolicy(policy: Policy): Required<Policy> {\n const normalized: Required<Policy> = {\n name: policy.name,\n limit: policy.limit,\n window: policy.window,\n algorithm: policy.algorithm ?? Algorithm.TOKEN_BUCKET,\n keyStrategy: policy.keyStrategy ?? KeyStrategy.IP,\n burst: policy.burst ?? Math.floor(policy.limit * 1.2),\n cost: policy.cost ?? 1,\n blockDuration: policy.blockDuration ?? undefined,\n keyExtractor: policy.keyExtractor ?? undefined,\n exemptions: policy.exemptions ?? [],\n } as Required<Policy>;\n\n // Validation\n if (normalized.limit <= 0) {\n throw new Error('limit must be positive');\n }\n\n if (normalized.window <= 0) {\n throw new Error('window must be positive');\n }\n\n if (normalized.cost <= 0) {\n throw new Error('cost must be positive');\n }\n\n if (normalized.burst < normalized.limit) {\n throw new Error('burst must be >= limit');\n }\n\n return normalized;\n}\n","/**\n * Utility functions for extracting rate limit keys from requests.\n */\n\n// Default health check paths\nexport const HEALTH_CHECK_PATHS = new Set([\n '/health',\n '/ping',\n '/ready',\n '/healthz',\n '/livez',\n]);\n\n// Private IP ranges (CIDR notation)\nconst PRIVATE_IP_PATTERNS = [\n /^127\\./,\n /^10\\./,\n /^172\\.(1[6-9]|2\\d|3[01])\\./,\n /^192\\.168\\./,\n /^::1$/,\n /^fc00:/,\n];\n\n/**\n * Extract client IP from request, respecting X-Forwarded-For if from trusted proxy.\n */\nexport function extractIp(request: any, trustedProxies: string[] = []): string | null {\n let clientIp: string | null = null;\n\n // Try different request object patterns\n if (request.socket?.remoteAddress) {\n // Node.js/Express\n clientIp = request.socket.remoteAddress;\n } else if (request.ip) {\n // Express with trust proxy\n clientIp = request.ip;\n } else if (request.connection?.remoteAddress) {\n // Older Node.js\n clientIp = request.connection.remoteAddress;\n }\n\n if (!clientIp) {\n return null;\n }\n\n // Clean IPv6-mapped IPv4 addresses\n clientIp = clientIp.replace(/^::ffff:/, '');\n\n // Check if we should trust X-Forwarded-For\n if (trustedProxies.length > 0 && isTrustedProxy(clientIp, trustedProxies)) {\n const forwarded = getForwardedFor(request);\n if (forwarded) {\n return forwarded;\n }\n }\n\n return clientIp;\n}\n\n/**\n * Extract first IP from X-Forwarded-For header.\n */\nfunction getForwardedFor(request: any): string | null {\n const headers = request.headers || {};\n const forwarded = headers['x-forwarded-for'] || headers['X-Forwarded-For'];\n\n if (forwarded) {\n // Take the first IP (client IP)\n return forwarded.split(',')[0].trim();\n }\n\n return null;\n}\n\n/**\n * Check if IP is in trusted proxy list.\n */\nfunction isTrustedProxy(ip: string, trustedProxies: string[]): boolean {\n return trustedProxies.some((proxy) => {\n if (proxy.includes('/')) {\n // CIDR notation - simplified check\n return ip.startsWith(proxy.split('/')[0].split('.').slice(0, -1).join('.'));\n }\n return ip === proxy;\n });\n}\n\n/**\n * Check if IP is in private range.\n */\nexport function isPrivateIp(ip: string): boolean {\n return PRIVATE_IP_PATTERNS.some((pattern) => pattern.test(ip));\n}\n\n/**\n * Check if path is a health check endpoint.\n */\nexport function isHealthCheck(path: string): boolean {\n return HEALTH_CHECK_PATHS.has(path);\n}\n\n/**\n * Extract user ID from request.\n */\nexport function extractUserId(request: any): string | null {\n // Try common patterns\n if (request.user?.id) {\n return String(request.user.id);\n }\n\n if (request.userId) {\n return String(request.userId);\n }\n\n return null;\n}\n\n/**\n * Extract API key from request headers.\n */\nexport function extractApiKey(request: any): string | null {\n const headers = request.headers || {};\n\n // Try common header names\n const apiKey =\n headers['x-api-key'] ||\n headers['X-API-Key'] ||\n headers['authorization'] ||\n headers['Authorization'];\n\n if (apiKey) {\n // Handle Bearer tokens\n if (apiKey.startsWith('Bearer ')) {\n return apiKey.substring(7);\n }\n return apiKey;\n }\n\n return null;\n}\n\n/**\n * Extract path from request.\n */\nexport function extractPath(request: any): string | null {\n // Next.js\n if (request.nextUrl?.pathname) {\n return request.nextUrl.pathname;\n }\n\n // Express\n if (request.path) {\n return request.path;\n }\n\n if (request.url) {\n try {\n const url = new URL(request.url, 'http://localhost');\n return url.pathname;\n } catch {\n return request.url;\n }\n }\n\n return null;\n}\n","/**\n * Token bucket rate limiting algorithm.\n */\n\nimport { Decision } from '../core/decision';\n\nexport interface TokenBucketState {\n tokens: number;\n lastRefill: number;\n}\n\nexport class TokenBucket {\n private capacity: number;\n private rate: number;\n private window: number;\n\n constructor(capacity: number, rate: number, window: number) {\n this.capacity = capacity;\n this.rate = rate / window; // tokens per second\n this.window = window;\n }\n\n /**\n * Check if request is allowed and consume tokens.\n */\n checkAndConsume(\n currentTokens: number,\n lastRefill: number,\n cost: number,\n now: number = Date.now() / 1000\n ): { decision: Decision; newTokens: number; newLastRefill: number } {\n // Refill tokens based on elapsed time\n const elapsed = now - lastRefill;\n const refillAmount = elapsed * this.rate;\n const newTokens = Math.min(this.capacity, currentTokens + refillAmount);\n\n // Calculate reset time (when bucket will be full)\n const tokensNeeded = this.capacity - newTokens;\n const resetAt = Math.floor(now + tokensNeeded / this.rate);\n\n // Check if we have enough tokens\n if (newTokens >= cost) {\n // Consume tokens\n const tokensAfterConsume = newTokens - cost;\n const remaining = Math.floor(tokensAfterConsume);\n\n return {\n decision: {\n allowed: true,\n limit: Math.floor(this.rate * this.window),\n remaining,\n resetAt,\n },\n newTokens: tokensAfterConsume,\n newLastRefill: now,\n };\n } else {\n // Not enough tokens\n const tokensDeficit = cost - newTokens;\n const retryAfter = Math.floor(tokensDeficit / this.rate) + 1;\n\n return {\n decision: {\n allowed: false,\n limit: Math.floor(this.rate * this.window),\n remaining: 0,\n resetAt,\n retryAfter,\n },\n newTokens,\n newLastRefill: lastRefill, // Don't update last_refill on rejection\n };\n }\n }\n\n /**\n * Get initial state for a new key.\n */\n initialState(now: number = Date.now() / 1000): TokenBucketState {\n return {\n tokens: this.capacity,\n lastRefill: now,\n };\n }\n}\n","/**\n * Fixed window rate limiting algorithm.\n */\n\nimport { Decision } from '../core/decision';\n\nexport interface FixedWindowState {\n count: number;\n windowStart: number;\n}\n\nexport class FixedWindow {\n private limit: number;\n private window: number;\n\n constructor(limit: number, window: number) {\n this.limit = limit;\n this.window = window;\n }\n\n /**\n * Check if request is allowed and increment counter.\n */\n checkAndConsume(\n currentCount: number,\n windowStart: number,\n cost: number,\n now: number = Date.now() / 1000\n ): { decision: Decision; newCount: number; newWindowStart: number } {\n // Check if we're in a new window\n const timeSinceStart = now - windowStart;\n if (timeSinceStart >= this.window) {\n // New window - reset counter\n currentCount = 0;\n windowStart = now;\n }\n\n // Calculate reset time (end of current window)\n const resetAt = Math.floor(windowStart + this.window);\n\n // Check if we have capacity\n if (currentCount + cost <= this.limit) {\n // Allow request\n const newCount = currentCount + cost;\n const remaining = this.limit - newCount;\n\n return {\n decision: {\n allowed: true,\n limit: this.limit,\n remaining,\n resetAt,\n },\n newCount,\n newWindowStart: windowStart,\n };\n } else {\n // Block request\n const retryAfter = Math.floor(resetAt - now) + 1;\n\n return {\n decision: {\n allowed: false,\n limit: this.limit,\n remaining: 0,\n resetAt,\n retryAfter,\n },\n newCount: currentCount,\n newWindowStart: windowStart,\n };\n }\n }\n\n /**\n * Get initial state for a new key.\n */\n initialState(now: number = Date.now() / 1000): FixedWindowState {\n return {\n count: 0,\n windowStart: now,\n };\n }\n}\n","/**\n * Sliding window rate limiting algorithm.\n */\n\nimport { Decision } from '../core/decision';\n\nexport type SlidingWindowState = Record<number, number>;\n\nexport class SlidingWindow {\n private limit: number;\n private window: number;\n private precision: number;\n private bucketSize: number;\n\n constructor(limit: number, window: number, precision: number = 10) {\n this.limit = limit;\n this.window = window;\n this.precision = precision;\n this.bucketSize = window / precision;\n }\n\n /**\n * Check if request is allowed and update buckets.\n */\n checkAndConsume(\n buckets: SlidingWindowState,\n cost: number,\n now: number = Date.now() / 1000\n ): { decision: Decision; newBuckets: SlidingWindowState } {\n // Calculate current bucket\n const currentBucket = Math.floor(now / this.bucketSize);\n\n // Remove expired buckets (older than window)\n const cutoffBucket = currentBucket - this.precision;\n const newBuckets: SlidingWindowState = {};\n\n for (const [bucketIdStr, count] of Object.entries(buckets)) {\n const bucketId = parseInt(bucketIdStr, 10);\n if (bucketId > cutoffBucket) {\n newBuckets[bucketId] = count;\n }\n }\n\n // Count requests in sliding window\n const totalCount = Object.values(newBuckets).reduce((sum, count) => sum + count, 0);\n\n // Calculate reset time (when oldest bucket expires)\n const bucketIds = Object.keys(newBuckets).map((id) => parseInt(id, 10));\n const oldestBucket = bucketIds.length > 0 ? Math.min(...bucketIds) : currentBucket;\n const resetAt = Math.floor((oldestBucket + this.precision + 1) * this.bucketSize);\n\n // Check if we have capacity\n if (totalCount + cost <= this.limit) {\n // Allow request\n newBuckets[currentBucket] = (newBuckets[currentBucket] || 0) + cost;\n const remaining = this.limit - (totalCount + cost);\n\n return {\n decision: {\n allowed: true,\n limit: this.limit,\n remaining,\n resetAt,\n },\n newBuckets,\n };\n } else {\n // Block request\n const retryAfter = Math.floor(this.bucketSize) + 1;\n\n return {\n decision: {\n allowed: false,\n limit: this.limit,\n remaining: 0,\n resetAt,\n retryAfter,\n },\n newBuckets,\n };\n }\n }\n\n /**\n * Get initial state for a new key.\n */\n initialState(): SlidingWindowState {\n return {};\n }\n}\n","/**\n * Leaky bucket rate limiting algorithm.\n */\n\nimport { Decision } from '../core/decision';\n\nexport interface LeakyBucketState {\n level: number;\n lastLeak: number;\n}\n\nexport class LeakyBucket {\n private capacity: number;\n private leakRate: number;\n private window: number;\n\n constructor(capacity: number, leakRate: number, window: number) {\n this.capacity = capacity;\n this.leakRate = leakRate; // requests per second\n this.window = window;\n }\n\n /**\n * Check if request is allowed and update bucket level.\n */\n checkAndConsume(\n currentLevel: number,\n lastLeak: number,\n cost: number,\n now: number = Date.now() / 1000\n ): { decision: Decision; newLevel: number; newLastLeak: number } {\n // Calculate how much has leaked since last check\n const elapsed = now - lastLeak;\n const leaked = elapsed * this.leakRate;\n\n // Update bucket level (can't go below 0)\n let newLevel = Math.max(0, currentLevel - leaked);\n\n // Calculate when bucket will be empty\n let resetAt: number;\n if (newLevel > 0) {\n const timeToEmpty = newLevel / this.leakRate;\n resetAt = Math.floor(now + timeToEmpty);\n } else {\n resetAt = Math.floor(now);\n }\n\n // Check if we have capacity\n if (newLevel + cost <= this.capacity) {\n // Allow request - add to bucket\n newLevel += cost;\n const remaining = Math.floor(this.capacity - newLevel);\n\n return {\n decision: {\n allowed: true,\n limit: this.capacity,\n remaining,\n resetAt,\n },\n newLevel,\n newLastLeak: now,\n };\n } else {\n // Block request - bucket is full\n // Calculate when there will be enough capacity\n const spaceNeeded = newLevel + cost - this.capacity;\n const retryAfter = Math.floor(spaceNeeded / this.leakRate) + 1;\n\n return {\n decision: {\n allowed: false,\n limit: this.capacity,\n remaining: 0,\n resetAt,\n retryAfter,\n },\n newLevel,\n newLastLeak: now,\n };\n }\n }\n\n /**\n * Get initial state for a new key.\n */\n initialState(now: number = Date.now() / 1000): LeakyBucketState {\n return {\n level: 0,\n lastLeak: now,\n };\n }\n}\n","/**\n * Atomic-evaluation protocol.\n *\n * Simple stores (e.g. InMemoryStore) expose get/set and let the limiter run the\n * algorithm in-process. Distributed stores that can compute the decision\n * atomically (e.g. RedisStore via Lua) instead expose `evaluate()`: the limiter\n * hands them the resolved policy params + storage key and gets back a finished\n * Decision. The limiter prefers `evaluate()` when present (see core/limiter.ts).\n */\n\nimport { Decision } from './decision';\nimport { Algorithm } from './policy';\n\n/** Everything an atomic store needs to compute a decision for one request. */\nexport interface EvaluateInput {\n /** Full storage key, already namespaced (e.g. `halt:public_api:1.2.3.4`). */\n key: string;\n /** Algorithm to apply. */\n algorithm: Algorithm;\n /** Requests permitted per window. */\n limit: number;\n /** Window length in seconds. */\n window: number;\n /** Bucket capacity (token/leaky bucket). */\n burst: number;\n /** Cost this request consumes. */\n cost: number;\n /** Seconds to keep the key alive in the backing store. */\n ttl: number;\n}\n\n/** A store that computes the rate-limit decision atomically on its own. */\nexport interface AtomicStore {\n evaluate(input: EvaluateInput): Promise<Decision>;\n}\n\n/** True if the store implements the atomic-evaluation protocol. */\nexport function isAtomicStore(store: unknown): store is AtomicStore {\n return (\n typeof store === 'object' &&\n store !== null &&\n typeof (store as AtomicStore).evaluate === 'function'\n );\n}\n\n/**\n * Minimal structural type for a Redis client (satisfied by ioredis and\n * node-redis v4+). Declared here so Halt has no hard dependency on any concrete\n * Redis package — the user injects their own client.\n */\nexport interface RedisClientLike {\n eval(script: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;\n evalsha?(sha: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;\n script?(subcommand: 'LOAD', script: string): Promise<unknown>;\n}\n","/**\n * Main rate limiter implementation.\n */\n\nimport { Decision } from './decision';\nimport { Policy, KeyStrategy, Algorithm, normalizePolicy } from './policy';\nimport {\n extractIp,\n extractUserId,\n extractApiKey,\n extractPath,\n isHealthCheck,\n isPrivateIp,\n} from './extractors';\nimport { TokenBucket } from '../algorithms/token-bucket';\nimport { FixedWindow } from '../algorithms/fixed-window';\nimport { SlidingWindow } from '../algorithms/sliding-window';\nimport { LeakyBucket } from '../algorithms/leaky-bucket';\nimport { Store } from '../stores/memory';\nimport { AtomicStore, isAtomicStore } from './store';\n\ntype AlgorithmInstance = TokenBucket | FixedWindow | SlidingWindow | LeakyBucket;\n\nexport interface RateLimiterOptions {\n /**\n * Storage backend. Either a simple KV store (InMemoryStore — the limiter runs\n * the algorithm in-process) or an atomic store (RedisStore — the store\n * computes the decision atomically via Lua).\n */\n store: Store | AtomicStore;\n /** Either a static policy or a resolver returning a policy per-request */\n policy: Policy | ((request: any) => Policy | Promise<Policy>);\n trustedProxies?: string[];\n exemptPrivateIps?: boolean;\n /** Optional OpenTelemetry-like tracer (object with startSpan) */\n otelTracer?: any;\n /** Optional metrics recorder: (name, tags?, value?) => void */\n metricsRecorder?: (name: string, tags?: Record<string, string>, value?: number) => void;\n}\n\nexport class RateLimiter {\n private store: Store | AtomicStore;\n private policyOrResolver: Policy | ((request: any) => Policy | Promise<Policy>);\n private trustedProxies: string[];\n private exemptPrivateIps: boolean;\n private algorithmCache: Map<string, AlgorithmInstance>;\n private otelTracer?: any;\n private metricsRecorder?: (name: string, tags?: Record<string, string>, value?: number) => void;\n\n constructor(options: RateLimiterOptions) {\n this.store = options.store;\n this.policyOrResolver = options.policy;\n this.trustedProxies = options.trustedProxies ?? [];\n this.exemptPrivateIps = options.exemptPrivateIps ?? true;\n this.algorithmCache = new Map();\n this.otelTracer = options.otelTracer;\n this.metricsRecorder = options.metricsRecorder;\n }\n\n /**\n * Check if request is allowed under rate limit.\n */\n /**\n * Check if request is allowed under rate limit.\n * This method is async because policy resolution may be async (e.g. DB lookup).\n */\n async check(request: any, cost?: number): Promise<Decision> {\n // Resolve policy (static or per-request)\n const resolved =\n typeof this.policyOrResolver === 'function'\n ? await (this.policyOrResolver as (r: any) => Policy | Promise<Policy>)(request)\n : (this.policyOrResolver as Policy);\n\n const policy = normalizePolicy(resolved);\n const requestCost = cost ?? policy.cost;\n\n // Quick exemptions check (policy-aware)\n if (this.isExempt(request, policy)) {\n const resp: Decision = {\n allowed: true,\n limit: policy.limit,\n remaining: policy.limit,\n resetAt: Math.floor(Date.now() / 1000 + policy.window),\n };\n this.metricsRecorder?.('halt.request.exempt', { policy: policy.name }, 1);\n return resp;\n }\n\n // Extract key\n const key = this.extractKey(request, policy);\n if (!key) {\n const resp: Decision = {\n allowed: true,\n limit: policy.limit,\n remaining: policy.limit,\n resetAt: Math.floor(Date.now() / 1000 + policy.window),\n };\n this.metricsRecorder?.('halt.request.no_key', { policy: policy.name }, 1);\n return resp;\n }\n\n const storageKey = `halt:${policy.name}:${key}`;\n\n // Atomic stores (e.g. Redis) compute the whole decision themselves.\n if (isAtomicStore(this.store)) {\n const span = this.otelTracer?.startSpan?.('halt.check', {\n attributes: { policy: policy.name, key },\n });\n const decision = await this.store.evaluate({\n key: storageKey,\n algorithm: policy.algorithm,\n limit: policy.limit,\n window: policy.window,\n burst: policy.burst,\n cost: requestCost,\n ttl: policy.window * 2,\n });\n this.metricsRecorder?.(\n 'halt.request.checked',\n { policy: policy.name, allowed: String(decision.allowed) },\n 1\n );\n this.metricsRecorder?.(\n decision.allowed ? 'halt.request.allowed' : 'halt.request.blocked',\n { policy: policy.name },\n 1\n );\n span?.end?.();\n return decision;\n }\n\n const store = this.store as Store;\n\n // Get or create algorithm instance for this policy\n let algorithm = this.algorithmCache.get(policy.name);\n if (!algorithm) {\n if (policy.algorithm === Algorithm.TOKEN_BUCKET) {\n algorithm = new TokenBucket(policy.burst, policy.limit, policy.window);\n } else if (policy.algorithm === Algorithm.FIXED_WINDOW) {\n algorithm = new FixedWindow(policy.limit, policy.window);\n } else if (policy.algorithm === Algorithm.SLIDING_WINDOW) {\n algorithm = new SlidingWindow(policy.limit, policy.window);\n } else if (policy.algorithm === Algorithm.LEAKY_BUCKET) {\n const leakRate = policy.limit / policy.window;\n algorithm = new LeakyBucket(policy.burst, leakRate, policy.window);\n } else {\n throw new Error(`Algorithm ${policy.algorithm} not implemented`);\n }\n this.algorithmCache.set(policy.name, algorithm);\n }\n\n // Instrumentation: start a span if tracer available\n const span = this.otelTracer?.startSpan?.('halt.check', { attributes: { policy: policy.name, key } });\n\n const state = store.get(storageKey);\n let decision: Decision;\n\n if (algorithm instanceof TokenBucket) {\n let tokens: number;\n let lastRefill: number;\n\n if (!state) {\n const initialState = algorithm.initialState();\n tokens = initialState.tokens;\n lastRefill = initialState.lastRefill;\n } else {\n tokens = state.tokens;\n lastRefill = state.lastRefill;\n }\n\n const result = algorithm.checkAndConsume(tokens, lastRefill, requestCost);\n decision = result.decision;\n\n const ttl = policy.window * 2;\n store.set(storageKey, { tokens: result.newTokens, lastRefill: result.newLastRefill }, ttl);\n } else if (algorithm instanceof FixedWindow) {\n let count: number;\n let windowStart: number;\n\n if (!state) {\n const initialState = algorithm.initialState();\n count = initialState.count;\n windowStart = initialState.windowStart;\n } else {\n count = state.count;\n windowStart = state.windowStart;\n }\n\n const result = algorithm.checkAndConsume(count, windowStart, requestCost);\n decision = result.decision;\n\n const ttl = policy.window * 2;\n store.set(storageKey, { count: result.newCount, windowStart: result.newWindowStart }, ttl);\n } else if (algorithm instanceof SlidingWindow) {\n const buckets = state || algorithm.initialState();\n const result = algorithm.checkAndConsume(buckets, requestCost);\n decision = result.decision;\n\n const ttl = policy.window * 2;\n store.set(storageKey, result.newBuckets, ttl);\n } else if (algorithm instanceof LeakyBucket) {\n let level: number;\n let lastLeak: number;\n\n if (!state) {\n const initialState = algorithm.initialState();\n level = initialState.level;\n lastLeak = initialState.lastLeak;\n } else {\n level = state.level;\n lastLeak = state.lastLeak;\n }\n\n const result = algorithm.checkAndConsume(level, lastLeak, requestCost);\n decision = result.decision;\n\n const ttl = policy.window * 2;\n store.set(storageKey, { level: result.newLevel, lastLeak: result.newLastLeak }, ttl);\n } else {\n throw new Error(`Algorithm ${typeof algorithm} not supported`);\n }\n\n // Metrics and telemetry\n this.metricsRecorder?.('halt.request.checked', { policy: policy.name, allowed: String(decision.allowed) }, 1);\n if (decision.allowed) {\n this.metricsRecorder?.('halt.request.allowed', { policy: policy.name }, 1);\n } else {\n this.metricsRecorder?.('halt.request.blocked', { policy: policy.name }, 1);\n }\n\n span?.end?.();\n\n return decision;\n }\n\n /**\n * Extract rate limit key from request based on policy strategy.\n */\n private extractKey(request: any, policy: Policy): string | null {\n // Use custom extractor if provided\n if (policy.keyExtractor) {\n return policy.keyExtractor(request);\n }\n\n // Use built-in strategies\n if (policy.keyStrategy === KeyStrategy.IP) {\n return extractIp(request, this.trustedProxies);\n }\n\n if (policy.keyStrategy === KeyStrategy.USER) {\n return extractUserId(request);\n }\n\n if (policy.keyStrategy === KeyStrategy.API_KEY) {\n return extractApiKey(request);\n }\n\n if (policy.keyStrategy === KeyStrategy.COMPOSITE) {\n // Composite: user:ip or api_key:ip\n const user = extractUserId(request);\n const apiKey = extractApiKey(request);\n const ip = extractIp(request, this.trustedProxies);\n\n if (user && ip) {\n return `${user}:${ip}`;\n } else if (apiKey && ip) {\n return `${apiKey}:${ip}`;\n } else if (user) {\n return user;\n } else if (apiKey) {\n return apiKey;\n } else {\n return ip;\n }\n }\n\n return null;\n }\n\n /**\n * Check if request is exempt from rate limiting.\n */\n private isExempt(request: any, policy: Policy): boolean {\n // Check health check paths\n const path = extractPath(request);\n if (path && isHealthCheck(path)) {\n return true;\n }\n\n // Check custom exemptions\n if (path && (policy.exemptions ?? []).includes(path)) {\n return true;\n }\n\n // Check private IPs\n if (this.exemptPrivateIps) {\n const ip = extractIp(request, this.trustedProxies);\n if (ip && isPrivateIp(ip)) {\n return true;\n }\n }\n\n // Check IP exemptions\n const ip = extractIp(request, this.trustedProxies);\n if (ip && (policy.exemptions ?? []).includes(ip)) {\n return true;\n }\n\n return false;\n }\n}\n","/**\n * Decision model for rate limiting results.\n */\n\nexport interface Decision {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Maximum number of requests allowed in the window */\n limit: number;\n /** Number of requests remaining in the current window */\n remaining: number;\n /** Unix timestamp when the limit resets */\n resetAt: number;\n /** Seconds to wait before retrying (only set when blocked) */\n retryAfter?: number;\n}\n\n/**\n * Convert decision to standard rate limit headers.\n */\nexport function toHeaders(decision: Decision): Record<string, string> {\n const headers: Record<string, string> = {\n 'RateLimit-Limit': String(decision.limit),\n 'RateLimit-Remaining': String(Math.max(0, decision.remaining)),\n 'RateLimit-Reset': String(decision.resetAt),\n };\n\n if (!decision.allowed && decision.retryAfter !== undefined) {\n headers['Retry-After'] = String(decision.retryAfter);\n }\n\n return headers;\n}\n","/**\n * Next.js adapter for Halt rate limiting.\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { RateLimiter, RateLimiterOptions } from '../core/limiter';\nimport { toHeaders } from '../core/decision';\nimport { Policy } from '../core/policy';\n\n/**\n * Create Next.js middleware for rate limiting.\n */\nexport function haltMiddleware(options: RateLimiterOptions) {\n const limiter = new RateLimiter(options);\n\n return async (request: NextRequest) => {\n const decision = await limiter.check(request);\n\n if (decision.allowed) {\n // Allow request with rate limit headers\n const response = NextResponse.next();\n const headers = toHeaders(decision);\n for (const [key, value] of Object.entries(headers)) {\n response.headers.set(key, value);\n }\n return response;\n } else {\n // Block request\n const headers = toHeaders(decision);\n return NextResponse.json(\n {\n error: 'rate_limit_exceeded',\n message: 'Too many requests. Please try again later.',\n retryAfter: decision.retryAfter,\n },\n {\n status: 429,\n headers,\n }\n );\n }\n };\n}\n\n/**\n * Wrapper for Next.js route handlers to add rate limiting.\n */\nexport function withHalt<T extends (...args: any[]) => Promise<Response>>(\n handler: T,\n options: RateLimiterOptions\n): T {\n const limiter = new RateLimiter(options);\n\n return (async (...args: any[]) => {\n const request = args[0] as NextRequest;\n const decision = await limiter.check(request);\n\n if (decision.allowed) {\n // Call the original handler\n const response = await handler(...args);\n\n // Add rate limit headers\n const headers = toHeaders(decision);\n for (const [key, value] of Object.entries(headers)) {\n response.headers.set(key, value);\n }\n\n return response;\n } else {\n // Block request\n const headers = toHeaders(decision);\n return NextResponse.json(\n {\n error: 'rate_limit_exceeded',\n message: 'Too many requests. Please try again later.',\n retryAfter: decision.retryAfter,\n },\n {\n status: 429,\n headers,\n }\n );\n }\n }) as T;\n}\n\n/**\n * Simple wrapper that accepts just a policy for convenience.\n */\nexport function withPolicy<T extends (...args: any[]) => Promise<Response>>(\n handler: T,\n policy: Policy,\n store: RateLimiterOptions['store']\n): T {\n return withHalt(handler, { store, policy });\n}\n"]}
|
package/dist/adapters/next.mjs
CHANGED
|
@@ -360,6 +360,11 @@ var LeakyBucket = class {
|
|
|
360
360
|
}
|
|
361
361
|
};
|
|
362
362
|
|
|
363
|
+
// src/core/store.ts
|
|
364
|
+
function isAtomicStore(store) {
|
|
365
|
+
return typeof store === "object" && store !== null && typeof store.evaluate === "function";
|
|
366
|
+
}
|
|
367
|
+
|
|
363
368
|
// src/core/limiter.ts
|
|
364
369
|
var RateLimiter = class {
|
|
365
370
|
constructor(options) {
|
|
@@ -404,6 +409,33 @@ var RateLimiter = class {
|
|
|
404
409
|
return resp;
|
|
405
410
|
}
|
|
406
411
|
const storageKey = `halt:${policy.name}:${key}`;
|
|
412
|
+
if (isAtomicStore(this.store)) {
|
|
413
|
+
const span2 = this.otelTracer?.startSpan?.("halt.check", {
|
|
414
|
+
attributes: { policy: policy.name, key }
|
|
415
|
+
});
|
|
416
|
+
const decision2 = await this.store.evaluate({
|
|
417
|
+
key: storageKey,
|
|
418
|
+
algorithm: policy.algorithm,
|
|
419
|
+
limit: policy.limit,
|
|
420
|
+
window: policy.window,
|
|
421
|
+
burst: policy.burst,
|
|
422
|
+
cost: requestCost,
|
|
423
|
+
ttl: policy.window * 2
|
|
424
|
+
});
|
|
425
|
+
this.metricsRecorder?.(
|
|
426
|
+
"halt.request.checked",
|
|
427
|
+
{ policy: policy.name, allowed: String(decision2.allowed) },
|
|
428
|
+
1
|
|
429
|
+
);
|
|
430
|
+
this.metricsRecorder?.(
|
|
431
|
+
decision2.allowed ? "halt.request.allowed" : "halt.request.blocked",
|
|
432
|
+
{ policy: policy.name },
|
|
433
|
+
1
|
|
434
|
+
);
|
|
435
|
+
span2?.end?.();
|
|
436
|
+
return decision2;
|
|
437
|
+
}
|
|
438
|
+
const store = this.store;
|
|
407
439
|
let algorithm = this.algorithmCache.get(policy.name);
|
|
408
440
|
if (!algorithm) {
|
|
409
441
|
if (policy.algorithm === "token_bucket" /* TOKEN_BUCKET */) {
|
|
@@ -421,7 +453,7 @@ var RateLimiter = class {
|
|
|
421
453
|
this.algorithmCache.set(policy.name, algorithm);
|
|
422
454
|
}
|
|
423
455
|
const span = this.otelTracer?.startSpan?.("halt.check", { attributes: { policy: policy.name, key } });
|
|
424
|
-
const state =
|
|
456
|
+
const state = store.get(storageKey);
|
|
425
457
|
let decision;
|
|
426
458
|
if (algorithm instanceof TokenBucket) {
|
|
427
459
|
let tokens;
|
|
@@ -437,7 +469,7 @@ var RateLimiter = class {
|
|
|
437
469
|
const result = algorithm.checkAndConsume(tokens, lastRefill, requestCost);
|
|
438
470
|
decision = result.decision;
|
|
439
471
|
const ttl = policy.window * 2;
|
|
440
|
-
|
|
472
|
+
store.set(storageKey, { tokens: result.newTokens, lastRefill: result.newLastRefill }, ttl);
|
|
441
473
|
} else if (algorithm instanceof FixedWindow) {
|
|
442
474
|
let count;
|
|
443
475
|
let windowStart;
|
|
@@ -452,13 +484,13 @@ var RateLimiter = class {
|
|
|
452
484
|
const result = algorithm.checkAndConsume(count, windowStart, requestCost);
|
|
453
485
|
decision = result.decision;
|
|
454
486
|
const ttl = policy.window * 2;
|
|
455
|
-
|
|
487
|
+
store.set(storageKey, { count: result.newCount, windowStart: result.newWindowStart }, ttl);
|
|
456
488
|
} else if (algorithm instanceof SlidingWindow) {
|
|
457
489
|
const buckets = state || algorithm.initialState();
|
|
458
490
|
const result = algorithm.checkAndConsume(buckets, requestCost);
|
|
459
491
|
decision = result.decision;
|
|
460
492
|
const ttl = policy.window * 2;
|
|
461
|
-
|
|
493
|
+
store.set(storageKey, result.newBuckets, ttl);
|
|
462
494
|
} else if (algorithm instanceof LeakyBucket) {
|
|
463
495
|
let level;
|
|
464
496
|
let lastLeak;
|
|
@@ -473,7 +505,7 @@ var RateLimiter = class {
|
|
|
473
505
|
const result = algorithm.checkAndConsume(level, lastLeak, requestCost);
|
|
474
506
|
decision = result.decision;
|
|
475
507
|
const ttl = policy.window * 2;
|
|
476
|
-
|
|
508
|
+
store.set(storageKey, { level: result.newLevel, lastLeak: result.newLastLeak }, ttl);
|
|
477
509
|
} else {
|
|
478
510
|
throw new Error(`Algorithm ${typeof algorithm} not supported`);
|
|
479
511
|
}
|