halt-rate 0.1.1 → 0.3.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 +28 -885
- 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 +60 -6
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/next.mjs +60 -6
- package/dist/adapters/next.mjs.map +1 -1
- package/dist/index.d.mts +176 -3
- package/dist/index.d.ts +176 -3
- package/dist/index.js +867 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +850 -7
- package/dist/index.mjs.map +1 -1
- package/dist/limiter-DqVVE0Kl.d.mts +310 -0
- package/dist/limiter-DqVVE0Kl.d.ts +310 -0
- package/package.json +17 -4
- package/dist/limiter-qGH_X_KH.d.mts +0 -128
- package/dist/limiter-qGH_X_KH.d.ts +0 -128
package/dist/adapters/next.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import {
|
|
2
|
+
import { p as RateLimiterOptions, P as Policy } from '../limiter-DqVVE0Kl.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 { p as RateLimiterOptions, P as Policy } from '../limiter-DqVVE0Kl.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Next.js adapter for Halt rate limiting.
|
package/dist/adapters/next.js
CHANGED
|
@@ -16,7 +16,8 @@ function normalizePolicy(policy) {
|
|
|
16
16
|
cost: policy.cost ?? 1,
|
|
17
17
|
blockDuration: policy.blockDuration ?? void 0,
|
|
18
18
|
keyExtractor: policy.keyExtractor ?? void 0,
|
|
19
|
-
exemptions: policy.exemptions ?? []
|
|
19
|
+
exemptions: policy.exemptions ?? [],
|
|
20
|
+
plan: policy.plan ?? void 0
|
|
20
21
|
};
|
|
21
22
|
if (normalized.limit <= 0) {
|
|
22
23
|
throw new Error("limit must be positive");
|
|
@@ -362,6 +363,11 @@ var LeakyBucket = class {
|
|
|
362
363
|
}
|
|
363
364
|
};
|
|
364
365
|
|
|
366
|
+
// src/core/store.ts
|
|
367
|
+
function isAtomicStore(store) {
|
|
368
|
+
return typeof store === "object" && store !== null && typeof store.evaluate === "function";
|
|
369
|
+
}
|
|
370
|
+
|
|
365
371
|
// src/core/limiter.ts
|
|
366
372
|
var RateLimiter = class {
|
|
367
373
|
constructor(options) {
|
|
@@ -372,6 +378,25 @@ var RateLimiter = class {
|
|
|
372
378
|
this.algorithmCache = /* @__PURE__ */ new Map();
|
|
373
379
|
this.otelTracer = options.otelTracer;
|
|
374
380
|
this.metricsRecorder = options.metricsRecorder;
|
|
381
|
+
this.telemetry = options.telemetry;
|
|
382
|
+
}
|
|
383
|
+
/** Fan a finished decision out to the telemetry hooks with rich metadata. */
|
|
384
|
+
emitTelemetry(request, key, decision, policy, cost) {
|
|
385
|
+
if (!this.telemetry) return;
|
|
386
|
+
const metadata = {
|
|
387
|
+
policy: policy.name,
|
|
388
|
+
algorithm: policy.algorithm,
|
|
389
|
+
keyStrategy: policy.keyStrategy,
|
|
390
|
+
endpoint: extractPath(request) ?? void 0,
|
|
391
|
+
cost,
|
|
392
|
+
plan: policy.plan
|
|
393
|
+
};
|
|
394
|
+
this.telemetry.onCheck?.(key, decision, metadata);
|
|
395
|
+
if (decision.allowed) {
|
|
396
|
+
this.telemetry.onAllowed?.(key, decision, metadata);
|
|
397
|
+
} else {
|
|
398
|
+
this.telemetry.onBlocked?.(key, decision, metadata);
|
|
399
|
+
}
|
|
375
400
|
}
|
|
376
401
|
/**
|
|
377
402
|
* Check if request is allowed under rate limit.
|
|
@@ -406,6 +431,34 @@ var RateLimiter = class {
|
|
|
406
431
|
return resp;
|
|
407
432
|
}
|
|
408
433
|
const storageKey = `halt:${policy.name}:${key}`;
|
|
434
|
+
if (isAtomicStore(this.store)) {
|
|
435
|
+
const span2 = this.otelTracer?.startSpan?.("halt.check", {
|
|
436
|
+
attributes: { policy: policy.name, key }
|
|
437
|
+
});
|
|
438
|
+
const decision2 = await this.store.evaluate({
|
|
439
|
+
key: storageKey,
|
|
440
|
+
algorithm: policy.algorithm,
|
|
441
|
+
limit: policy.limit,
|
|
442
|
+
window: policy.window,
|
|
443
|
+
burst: policy.burst,
|
|
444
|
+
cost: requestCost,
|
|
445
|
+
ttl: policy.window * 2
|
|
446
|
+
});
|
|
447
|
+
this.metricsRecorder?.(
|
|
448
|
+
"halt.request.checked",
|
|
449
|
+
{ policy: policy.name, allowed: String(decision2.allowed) },
|
|
450
|
+
1
|
|
451
|
+
);
|
|
452
|
+
this.metricsRecorder?.(
|
|
453
|
+
decision2.allowed ? "halt.request.allowed" : "halt.request.blocked",
|
|
454
|
+
{ policy: policy.name },
|
|
455
|
+
1
|
|
456
|
+
);
|
|
457
|
+
span2?.end?.();
|
|
458
|
+
this.emitTelemetry(request, key, decision2, policy, requestCost);
|
|
459
|
+
return decision2;
|
|
460
|
+
}
|
|
461
|
+
const store = this.store;
|
|
409
462
|
let algorithm = this.algorithmCache.get(policy.name);
|
|
410
463
|
if (!algorithm) {
|
|
411
464
|
if (policy.algorithm === "token_bucket" /* TOKEN_BUCKET */) {
|
|
@@ -423,7 +476,7 @@ var RateLimiter = class {
|
|
|
423
476
|
this.algorithmCache.set(policy.name, algorithm);
|
|
424
477
|
}
|
|
425
478
|
const span = this.otelTracer?.startSpan?.("halt.check", { attributes: { policy: policy.name, key } });
|
|
426
|
-
const state =
|
|
479
|
+
const state = store.get(storageKey);
|
|
427
480
|
let decision;
|
|
428
481
|
if (algorithm instanceof TokenBucket) {
|
|
429
482
|
let tokens;
|
|
@@ -439,7 +492,7 @@ var RateLimiter = class {
|
|
|
439
492
|
const result = algorithm.checkAndConsume(tokens, lastRefill, requestCost);
|
|
440
493
|
decision = result.decision;
|
|
441
494
|
const ttl = policy.window * 2;
|
|
442
|
-
|
|
495
|
+
store.set(storageKey, { tokens: result.newTokens, lastRefill: result.newLastRefill }, ttl);
|
|
443
496
|
} else if (algorithm instanceof FixedWindow) {
|
|
444
497
|
let count;
|
|
445
498
|
let windowStart;
|
|
@@ -454,13 +507,13 @@ var RateLimiter = class {
|
|
|
454
507
|
const result = algorithm.checkAndConsume(count, windowStart, requestCost);
|
|
455
508
|
decision = result.decision;
|
|
456
509
|
const ttl = policy.window * 2;
|
|
457
|
-
|
|
510
|
+
store.set(storageKey, { count: result.newCount, windowStart: result.newWindowStart }, ttl);
|
|
458
511
|
} else if (algorithm instanceof SlidingWindow) {
|
|
459
512
|
const buckets = state || algorithm.initialState();
|
|
460
513
|
const result = algorithm.checkAndConsume(buckets, requestCost);
|
|
461
514
|
decision = result.decision;
|
|
462
515
|
const ttl = policy.window * 2;
|
|
463
|
-
|
|
516
|
+
store.set(storageKey, result.newBuckets, ttl);
|
|
464
517
|
} else if (algorithm instanceof LeakyBucket) {
|
|
465
518
|
let level;
|
|
466
519
|
let lastLeak;
|
|
@@ -475,7 +528,7 @@ var RateLimiter = class {
|
|
|
475
528
|
const result = algorithm.checkAndConsume(level, lastLeak, requestCost);
|
|
476
529
|
decision = result.decision;
|
|
477
530
|
const ttl = policy.window * 2;
|
|
478
|
-
|
|
531
|
+
store.set(storageKey, { level: result.newLevel, lastLeak: result.newLastLeak }, ttl);
|
|
479
532
|
} else {
|
|
480
533
|
throw new Error(`Algorithm ${typeof algorithm} not supported`);
|
|
481
534
|
}
|
|
@@ -486,6 +539,7 @@ var RateLimiter = class {
|
|
|
486
539
|
this.metricsRecorder?.("halt.request.blocked", { policy: policy.name }, 1);
|
|
487
540
|
}
|
|
488
541
|
span?.end?.();
|
|
542
|
+
this.emitTelemetry(request, key, decision, policy, requestCost);
|
|
489
543
|
return decision;
|
|
490
544
|
}
|
|
491
545
|
/**
|
|
@@ -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 /** Optional plan/tier label for observability tagging (e.g. \"pro\"). */\n plan?: 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 plan: policy.plan ?? undefined,\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';\nimport { TelemetryHooks } from './telemetry';\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 * Optional high-level observability hooks (StatsCollector, OpenTelemetryMetrics,\n * LoggingTelemetry, or a CompositeTelemetry of several). Called on every\n * rate-limited check with rich metadata (policy, algorithm, endpoint, cost, plan).\n */\n telemetry?: TelemetryHooks;\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 private telemetry?: TelemetryHooks;\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 this.telemetry = options.telemetry;\n }\n\n /** Fan a finished decision out to the telemetry hooks with rich metadata. */\n private emitTelemetry(\n request: any,\n key: string,\n decision: Decision,\n policy: Required<Policy>,\n cost: number\n ): void {\n if (!this.telemetry) return;\n const metadata: Record<string, any> = {\n policy: policy.name,\n algorithm: policy.algorithm,\n keyStrategy: policy.keyStrategy,\n endpoint: extractPath(request) ?? undefined,\n cost,\n plan: policy.plan,\n };\n this.telemetry.onCheck?.(key, decision, metadata);\n if (decision.allowed) {\n this.telemetry.onAllowed?.(key, decision, metadata);\n } else {\n this.telemetry.onBlocked?.(key, decision, metadata);\n }\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 this.emitTelemetry(request, key, decision, policy, requestCost);\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 this.emitTelemetry(request, key, decision, policy, requestCost);\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
|
@@ -14,7 +14,8 @@ function normalizePolicy(policy) {
|
|
|
14
14
|
cost: policy.cost ?? 1,
|
|
15
15
|
blockDuration: policy.blockDuration ?? void 0,
|
|
16
16
|
keyExtractor: policy.keyExtractor ?? void 0,
|
|
17
|
-
exemptions: policy.exemptions ?? []
|
|
17
|
+
exemptions: policy.exemptions ?? [],
|
|
18
|
+
plan: policy.plan ?? void 0
|
|
18
19
|
};
|
|
19
20
|
if (normalized.limit <= 0) {
|
|
20
21
|
throw new Error("limit must be positive");
|
|
@@ -360,6 +361,11 @@ var LeakyBucket = class {
|
|
|
360
361
|
}
|
|
361
362
|
};
|
|
362
363
|
|
|
364
|
+
// src/core/store.ts
|
|
365
|
+
function isAtomicStore(store) {
|
|
366
|
+
return typeof store === "object" && store !== null && typeof store.evaluate === "function";
|
|
367
|
+
}
|
|
368
|
+
|
|
363
369
|
// src/core/limiter.ts
|
|
364
370
|
var RateLimiter = class {
|
|
365
371
|
constructor(options) {
|
|
@@ -370,6 +376,25 @@ var RateLimiter = class {
|
|
|
370
376
|
this.algorithmCache = /* @__PURE__ */ new Map();
|
|
371
377
|
this.otelTracer = options.otelTracer;
|
|
372
378
|
this.metricsRecorder = options.metricsRecorder;
|
|
379
|
+
this.telemetry = options.telemetry;
|
|
380
|
+
}
|
|
381
|
+
/** Fan a finished decision out to the telemetry hooks with rich metadata. */
|
|
382
|
+
emitTelemetry(request, key, decision, policy, cost) {
|
|
383
|
+
if (!this.telemetry) return;
|
|
384
|
+
const metadata = {
|
|
385
|
+
policy: policy.name,
|
|
386
|
+
algorithm: policy.algorithm,
|
|
387
|
+
keyStrategy: policy.keyStrategy,
|
|
388
|
+
endpoint: extractPath(request) ?? void 0,
|
|
389
|
+
cost,
|
|
390
|
+
plan: policy.plan
|
|
391
|
+
};
|
|
392
|
+
this.telemetry.onCheck?.(key, decision, metadata);
|
|
393
|
+
if (decision.allowed) {
|
|
394
|
+
this.telemetry.onAllowed?.(key, decision, metadata);
|
|
395
|
+
} else {
|
|
396
|
+
this.telemetry.onBlocked?.(key, decision, metadata);
|
|
397
|
+
}
|
|
373
398
|
}
|
|
374
399
|
/**
|
|
375
400
|
* Check if request is allowed under rate limit.
|
|
@@ -404,6 +429,34 @@ var RateLimiter = class {
|
|
|
404
429
|
return resp;
|
|
405
430
|
}
|
|
406
431
|
const storageKey = `halt:${policy.name}:${key}`;
|
|
432
|
+
if (isAtomicStore(this.store)) {
|
|
433
|
+
const span2 = this.otelTracer?.startSpan?.("halt.check", {
|
|
434
|
+
attributes: { policy: policy.name, key }
|
|
435
|
+
});
|
|
436
|
+
const decision2 = await this.store.evaluate({
|
|
437
|
+
key: storageKey,
|
|
438
|
+
algorithm: policy.algorithm,
|
|
439
|
+
limit: policy.limit,
|
|
440
|
+
window: policy.window,
|
|
441
|
+
burst: policy.burst,
|
|
442
|
+
cost: requestCost,
|
|
443
|
+
ttl: policy.window * 2
|
|
444
|
+
});
|
|
445
|
+
this.metricsRecorder?.(
|
|
446
|
+
"halt.request.checked",
|
|
447
|
+
{ policy: policy.name, allowed: String(decision2.allowed) },
|
|
448
|
+
1
|
|
449
|
+
);
|
|
450
|
+
this.metricsRecorder?.(
|
|
451
|
+
decision2.allowed ? "halt.request.allowed" : "halt.request.blocked",
|
|
452
|
+
{ policy: policy.name },
|
|
453
|
+
1
|
|
454
|
+
);
|
|
455
|
+
span2?.end?.();
|
|
456
|
+
this.emitTelemetry(request, key, decision2, policy, requestCost);
|
|
457
|
+
return decision2;
|
|
458
|
+
}
|
|
459
|
+
const store = this.store;
|
|
407
460
|
let algorithm = this.algorithmCache.get(policy.name);
|
|
408
461
|
if (!algorithm) {
|
|
409
462
|
if (policy.algorithm === "token_bucket" /* TOKEN_BUCKET */) {
|
|
@@ -421,7 +474,7 @@ var RateLimiter = class {
|
|
|
421
474
|
this.algorithmCache.set(policy.name, algorithm);
|
|
422
475
|
}
|
|
423
476
|
const span = this.otelTracer?.startSpan?.("halt.check", { attributes: { policy: policy.name, key } });
|
|
424
|
-
const state =
|
|
477
|
+
const state = store.get(storageKey);
|
|
425
478
|
let decision;
|
|
426
479
|
if (algorithm instanceof TokenBucket) {
|
|
427
480
|
let tokens;
|
|
@@ -437,7 +490,7 @@ var RateLimiter = class {
|
|
|
437
490
|
const result = algorithm.checkAndConsume(tokens, lastRefill, requestCost);
|
|
438
491
|
decision = result.decision;
|
|
439
492
|
const ttl = policy.window * 2;
|
|
440
|
-
|
|
493
|
+
store.set(storageKey, { tokens: result.newTokens, lastRefill: result.newLastRefill }, ttl);
|
|
441
494
|
} else if (algorithm instanceof FixedWindow) {
|
|
442
495
|
let count;
|
|
443
496
|
let windowStart;
|
|
@@ -452,13 +505,13 @@ var RateLimiter = class {
|
|
|
452
505
|
const result = algorithm.checkAndConsume(count, windowStart, requestCost);
|
|
453
506
|
decision = result.decision;
|
|
454
507
|
const ttl = policy.window * 2;
|
|
455
|
-
|
|
508
|
+
store.set(storageKey, { count: result.newCount, windowStart: result.newWindowStart }, ttl);
|
|
456
509
|
} else if (algorithm instanceof SlidingWindow) {
|
|
457
510
|
const buckets = state || algorithm.initialState();
|
|
458
511
|
const result = algorithm.checkAndConsume(buckets, requestCost);
|
|
459
512
|
decision = result.decision;
|
|
460
513
|
const ttl = policy.window * 2;
|
|
461
|
-
|
|
514
|
+
store.set(storageKey, result.newBuckets, ttl);
|
|
462
515
|
} else if (algorithm instanceof LeakyBucket) {
|
|
463
516
|
let level;
|
|
464
517
|
let lastLeak;
|
|
@@ -473,7 +526,7 @@ var RateLimiter = class {
|
|
|
473
526
|
const result = algorithm.checkAndConsume(level, lastLeak, requestCost);
|
|
474
527
|
decision = result.decision;
|
|
475
528
|
const ttl = policy.window * 2;
|
|
476
|
-
|
|
529
|
+
store.set(storageKey, { level: result.newLevel, lastLeak: result.newLastLeak }, ttl);
|
|
477
530
|
} else {
|
|
478
531
|
throw new Error(`Algorithm ${typeof algorithm} not supported`);
|
|
479
532
|
}
|
|
@@ -484,6 +537,7 @@ var RateLimiter = class {
|
|
|
484
537
|
this.metricsRecorder?.("halt.request.blocked", { policy: policy.name }, 1);
|
|
485
538
|
}
|
|
486
539
|
span?.end?.();
|
|
540
|
+
this.emitTelemetry(request, key, decision, policy, requestCost);
|
|
487
541
|
return decision;
|
|
488
542
|
}
|
|
489
543
|
/**
|