@periodic/titanium 1.0.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.
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rateLimit = rateLimit;
4
+ exports.createRateLimiter = createRateLimiter;
5
+ const limiter_1 = require("../core/limiter");
6
+ const ip_1 = require("../utils/ip");
7
+ /**
8
+ * Express rate limiting middleware factory
9
+ *
10
+ * Features:
11
+ * - Framework-agnostic core with Express adapter
12
+ * - Configurable identifier extraction (user ID, API key, IP, etc.)
13
+ * - Fail-open or fail-closed strategies
14
+ * - Standard HTTP rate limit headers
15
+ * - Skip function for conditional rate limiting
16
+ *
17
+ * @param options - Express rate limit configuration
18
+ * @returns Express middleware function
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { createClient } from 'redis';
23
+ * import { rateLimit } from '@periodic/titanium';
24
+ *
25
+ * const redis = createClient();
26
+ * await redis.connect();
27
+ *
28
+ * // Basic usage with IP-based rate limiting
29
+ * app.use(rateLimit({
30
+ * redis,
31
+ * limit: 100,
32
+ * window: 60,
33
+ * keyPrefix: 'api'
34
+ * }));
35
+ *
36
+ * // User-based rate limiting with JWT
37
+ * app.post('/api/resource',
38
+ * authMiddleware,
39
+ * rateLimit({
40
+ * redis,
41
+ * limit: 10,
42
+ * window: 60,
43
+ * keyPrefix: 'create-resource',
44
+ * identifier: (req) => req.user?.id?.toString() || null
45
+ * }),
46
+ * handler
47
+ * );
48
+ * ```
49
+ */
50
+ function rateLimit(options) {
51
+ // Validate required options
52
+ if (!options.redis) {
53
+ throw new Error("Redis client is required");
54
+ }
55
+ if (!options.limit || options.limit <= 0) {
56
+ throw new Error("Limit must be a positive number");
57
+ }
58
+ if (!options.window || options.window <= 0) {
59
+ throw new Error("Window must be a positive number");
60
+ }
61
+ if (!options.keyPrefix || options.keyPrefix.trim() === "") {
62
+ throw new Error("Key prefix is required");
63
+ }
64
+ // Extract options with defaults
65
+ const { redis, limit, window, keyPrefix, algorithm = "fixed-window", identifier, message = "Too many requests. Please try again later.", skip, failStrategy = "open", standardHeaders = true, logger, } = options;
66
+ // Create logger instance
67
+ const log = {
68
+ info: logger?.info || console.log,
69
+ warn: logger?.warn || console.warn,
70
+ error: logger?.error || console.error,
71
+ };
72
+ // Create core rate limiter
73
+ const limiter = new limiter_1.RateLimiter({
74
+ redis,
75
+ limit,
76
+ window,
77
+ keyPrefix,
78
+ algorithm,
79
+ logger: log,
80
+ });
81
+ // Return Express middleware
82
+ return async (req, res, next) => {
83
+ try {
84
+ // Check if rate limiting should be skipped
85
+ if (skip && skip(req)) {
86
+ return next();
87
+ }
88
+ // Extract identifier
89
+ let clientIdentifier;
90
+ if (identifier) {
91
+ const customId = identifier(req);
92
+ clientIdentifier = customId || (0, ip_1.getDefaultIdentifier)(req);
93
+ }
94
+ else {
95
+ clientIdentifier = (0, ip_1.getDefaultIdentifier)(req);
96
+ }
97
+ // Attempt rate limiting
98
+ let result;
99
+ try {
100
+ result = await limiter.limit(clientIdentifier);
101
+ }
102
+ catch (error) {
103
+ // Redis error - apply fail strategy
104
+ log.error?.("Rate limiter error:", error);
105
+ if (failStrategy === "closed") {
106
+ log.warn?.("Redis unavailable with fail-closed strategy - blocking request");
107
+ res.status(503).json({
108
+ error: "Service temporarily unavailable. Please try again later.",
109
+ });
110
+ return;
111
+ }
112
+ // Fail-open strategy
113
+ log.warn?.("Redis unavailable with fail-open strategy - allowing request");
114
+ return next();
115
+ }
116
+ // Set standard headers if enabled
117
+ if (standardHeaders) {
118
+ setRateLimitHeaders(res, {
119
+ limit: result.limit,
120
+ remaining: result.remaining,
121
+ reset: result.reset,
122
+ retryAfter: result.allowed ? undefined : result.ttl,
123
+ });
124
+ }
125
+ // Check if request is allowed
126
+ if (!result.allowed) {
127
+ log.warn?.(`Rate limit exceeded for identifier: ${clientIdentifier} (${keyPrefix})`);
128
+ res.status(429).json({
129
+ error: message,
130
+ retryAfter: result.ttl,
131
+ limit: result.limit,
132
+ remaining: result.remaining,
133
+ reset: result.reset,
134
+ });
135
+ return;
136
+ }
137
+ // Log warning when approaching limit (80% threshold)
138
+ if (result.remaining < result.limit * 0.2) {
139
+ log.info?.(`Rate limit warning for identifier: ${clientIdentifier} (${keyPrefix}) - ${result.remaining} remaining`);
140
+ }
141
+ next();
142
+ }
143
+ catch (error) {
144
+ // Unexpected error - apply fail strategy
145
+ log.error?.("Unexpected rate limit middleware error:", error);
146
+ if (failStrategy === "closed") {
147
+ res.status(503).json({
148
+ error: "Service temporarily unavailable. Please try again later.",
149
+ });
150
+ return;
151
+ }
152
+ // Fail-open
153
+ log.warn?.("Allowing request due to unexpected error (fail-open mode)");
154
+ next();
155
+ }
156
+ };
157
+ }
158
+ /**
159
+ * Set standard rate limit headers on response
160
+ */
161
+ function setRateLimitHeaders(res, info) {
162
+ res.setHeader("X-RateLimit-Limit", info.limit.toString());
163
+ res.setHeader("X-RateLimit-Remaining", info.remaining.toString());
164
+ res.setHeader("X-RateLimit-Reset", info.reset.toString());
165
+ if (info.retryAfter !== undefined) {
166
+ res.setHeader("Retry-After", info.retryAfter.toString());
167
+ }
168
+ }
169
+ /**
170
+ * Create a rate limiter instance for manual control
171
+ * Useful for custom implementations or non-Express frameworks
172
+ *
173
+ * @param options - Rate limiter configuration
174
+ * @returns RateLimiter instance
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * const limiter = createRateLimiter({
179
+ * redis,
180
+ * limit: 100,
181
+ * window: 60,
182
+ * keyPrefix: 'api'
183
+ * });
184
+ *
185
+ * const result = await limiter.limit('user-123');
186
+ * if (!result.allowed) {
187
+ * // Handle rate limit exceeded
188
+ * }
189
+ * ```
190
+ */
191
+ function createRateLimiter(options) {
192
+ return new limiter_1.RateLimiter(options);
193
+ }
194
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":";;AAgDA,8BAkJC;AAqCD,8CAOC;AA7OD,6CAA8C;AAE9C,oCAAmD;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,SAAgB,SAAS,CAAC,OAAgC;IACxD,4BAA4B;IAC5B,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5C,CAAC;IAED,gCAAgC;IAChC,MAAM,EACJ,KAAK,EACL,KAAK,EACL,MAAM,EACN,SAAS,EACT,SAAS,GAAG,cAAc,EAC1B,UAAU,EACV,OAAO,GAAG,4CAA4C,EACtD,IAAI,EACJ,YAAY,GAAG,MAAM,EACrB,eAAe,GAAG,IAAI,EACtB,MAAM,GACP,GAAG,OAAO,CAAC;IAEZ,yBAAyB;IACzB,MAAM,GAAG,GAAW;QAClB,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,OAAO,CAAC,GAAG;QACjC,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,OAAO,CAAC,IAAI;QAClC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,OAAO,CAAC,KAAK;KACtC,CAAC;IAEF,2BAA2B;IAC3B,MAAM,OAAO,GAAG,IAAI,qBAAW,CAAC;QAC9B,KAAK;QACL,KAAK;QACL,MAAM;QACN,SAAS;QACT,SAAS;QACT,MAAM,EAAE,GAAG;KACZ,CAAC,CAAC;IAEH,4BAA4B;IAC5B,OAAO,KAAK,EACV,GAAY,EACZ,GAAa,EACb,IAAkB,EACH,EAAE;QACjB,IAAI,CAAC;YACH,2CAA2C;YAC3C,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,IAAI,EAAE,CAAC;YAChB,CAAC;YAED,qBAAqB;YACrB,IAAI,gBAAwB,CAAC;YAC7B,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;gBACjC,gBAAgB,GAAG,QAAQ,IAAI,IAAA,yBAAoB,EAAC,GAAG,CAAC,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACN,gBAAgB,GAAG,IAAA,yBAAoB,EAAC,GAAG,CAAC,CAAC;YAC/C,CAAC;YAED,wBAAwB;YACxB,IAAI,MAAM,CAAC;YACX,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;YACjD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,oCAAoC;gBACpC,GAAG,CAAC,KAAK,EAAE,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;gBAE1C,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;oBAC9B,GAAG,CAAC,IAAI,EAAE,CACR,gEAAgE,CACjE,CAAC;oBACF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;wBACnB,KAAK,EAAE,0DAA0D;qBAClE,CAAC,CAAC;oBACH,OAAO;gBACT,CAAC;gBAED,qBAAqB;gBACrB,GAAG,CAAC,IAAI,EAAE,CACR,8DAA8D,CAC/D,CAAC;gBACF,OAAO,IAAI,EAAE,CAAC;YAChB,CAAC;YAED,kCAAkC;YAClC,IAAI,eAAe,EAAE,CAAC;gBACpB,mBAAmB,CAAC,GAAG,EAAE;oBACvB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG;iBACpD,CAAC,CAAC;YACL,CAAC;YAED,8BAA8B;YAC9B,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,GAAG,CAAC,IAAI,EAAE,CACR,uCAAuC,gBAAgB,KAAK,SAAS,GAAG,CACzE,CAAC;gBAEF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,KAAK,EAAE,OAAO;oBACd,UAAU,EAAE,MAAM,CAAC,GAAG;oBACtB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;iBACpB,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,qDAAqD;YACrD,IAAI,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC;gBAC1C,GAAG,CAAC,IAAI,EAAE,CACR,sCAAsC,gBAAgB,KAAK,SAAS,OAAO,MAAM,CAAC,SAAS,YAAY,CACxG,CAAC;YACJ,CAAC;YAED,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yCAAyC;YACzC,GAAG,CAAC,KAAK,EAAE,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAC;YAE9D,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;gBAC9B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,KAAK,EAAE,0DAA0D;iBAClE,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,YAAY;YACZ,GAAG,CAAC,IAAI,EAAE,CAAC,2DAA2D,CAAC,CAAC;YACxE,IAAI,EAAE,CAAC;QACT,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,GAAa,EAAE,IAAmB;IAC7D,GAAG,CAAC,SAAS,CAAC,mBAAmB,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1D,GAAG,CAAC,SAAS,CAAC,uBAAuB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;IAClE,GAAG,CAAC,SAAS,CAAC,mBAAmB,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE1D,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QAClC,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAgB,iBAAiB,CAC/B,OAGC;IAED,OAAO,IAAI,qBAAW,CAAC,OAAO,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { RateLimiterConfig, RateLimitResult } from "./types";
2
+ /**
3
+ * Core rate limiter implementation
4
+ * Framework-agnostic, pure Redis-based rate limiting
5
+ */
6
+ export declare class RateLimiter {
7
+ private redis;
8
+ private requestLimit;
9
+ private window;
10
+ private keyPrefix;
11
+ private algorithm;
12
+ private logger;
13
+ constructor(config: RateLimiterConfig);
14
+ /**
15
+ * Validate configuration options
16
+ */
17
+ private validateConfig;
18
+ /**
19
+ * Create logger with fallback to console
20
+ */
21
+ private createLogger;
22
+ /**
23
+ * Build Redis key for the identifier
24
+ */
25
+ private buildKey;
26
+ /**
27
+ * Check if Redis client is available
28
+ */
29
+ private isRedisAvailable;
30
+ /**
31
+ * Attempt to consume a request for the given identifier
32
+ * Returns rate limit information
33
+ *
34
+ * @param identifier - Unique identifier for the client (user ID, IP, API key, etc.)
35
+ * @returns Promise resolving to rate limit result
36
+ *
37
+ * @throws Error if Redis operations fail (caller should handle)
38
+ */
39
+ limit(identifier: string): Promise<RateLimitResult>;
40
+ /**
41
+ * Fixed window rate limiting implementation
42
+ * Uses true fixed window semantics with SET NX EX
43
+ */
44
+ private fixedWindowLimit;
45
+ /**
46
+ * Reset rate limit for a specific identifier
47
+ * Useful for testing or manual intervention
48
+ *
49
+ * @param identifier - Unique identifier to reset
50
+ * @returns Promise resolving to true if key was deleted, false otherwise
51
+ */
52
+ reset(identifier: string): Promise<boolean>;
53
+ /**
54
+ * Get current rate limit status for an identifier
55
+ *
56
+ * @param identifier - Unique identifier to check
57
+ * @returns Promise resolving to current count and TTL, or null if no limit exists
58
+ */
59
+ getStatus(identifier: string): Promise<{
60
+ current: number;
61
+ ttl: number;
62
+ } | null>;
63
+ }
64
+ //# sourceMappingURL=limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limiter.d.ts","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAEhF;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,iBAAiB;IAWrC;;OAEG;IACH,OAAO,CAAC,cAAc;IAkBtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAQpB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;;;;;;;OAQG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAkBzD;;;OAGG;YACW,gBAAgB;IAkC9B;;;;;;OAMG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBjD;;;;;OAKG;IACG,SAAS,CACb,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CA6BpD"}
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RateLimiter = void 0;
4
+ /**
5
+ * Core rate limiter implementation
6
+ * Framework-agnostic, pure Redis-based rate limiting
7
+ */
8
+ class RateLimiter {
9
+ constructor(config) {
10
+ this.validateConfig(config);
11
+ this.redis = config.redis;
12
+ this.requestLimit = config.limit;
13
+ this.window = config.window;
14
+ this.keyPrefix = config.keyPrefix;
15
+ this.algorithm = config.algorithm || "fixed-window";
16
+ this.logger = this.createLogger(config.logger);
17
+ }
18
+ /**
19
+ * Validate configuration options
20
+ */
21
+ validateConfig(config) {
22
+ if (!config.redis) {
23
+ throw new Error("Redis client is required");
24
+ }
25
+ if (!config.limit || config.limit <= 0) {
26
+ throw new Error("Limit must be a positive number");
27
+ }
28
+ if (!config.window || config.window <= 0) {
29
+ throw new Error("Window must be a positive number (in seconds)");
30
+ }
31
+ if (!config.keyPrefix || config.keyPrefix.trim() === "") {
32
+ throw new Error("Key prefix is required");
33
+ }
34
+ }
35
+ /**
36
+ * Create logger with fallback to console
37
+ */
38
+ createLogger(customLogger) {
39
+ return {
40
+ info: customLogger?.info || console.log,
41
+ warn: customLogger?.warn || console.warn,
42
+ error: customLogger?.error || console.error,
43
+ };
44
+ }
45
+ /**
46
+ * Build Redis key for the identifier
47
+ */
48
+ buildKey(identifier) {
49
+ return `ratelimit:${this.keyPrefix}:${identifier}`;
50
+ }
51
+ /**
52
+ * Check if Redis client is available
53
+ */
54
+ isRedisAvailable() {
55
+ return this.redis.isOpen && this.redis.isReady;
56
+ }
57
+ /**
58
+ * Attempt to consume a request for the given identifier
59
+ * Returns rate limit information
60
+ *
61
+ * @param identifier - Unique identifier for the client (user ID, IP, API key, etc.)
62
+ * @returns Promise resolving to rate limit result
63
+ *
64
+ * @throws Error if Redis operations fail (caller should handle)
65
+ */
66
+ async limit(identifier) {
67
+ if (!identifier || identifier.trim() === "") {
68
+ throw new Error("Identifier cannot be empty");
69
+ }
70
+ if (!this.isRedisAvailable()) {
71
+ throw new Error("Redis client is not available");
72
+ }
73
+ const key = this.buildKey(identifier);
74
+ if (this.algorithm === "fixed-window") {
75
+ return this.fixedWindowLimit(key);
76
+ }
77
+ throw new Error(`Unsupported algorithm: ${this.algorithm}`);
78
+ }
79
+ /**
80
+ * Fixed window rate limiting implementation
81
+ * Uses true fixed window semantics with SET NX EX
82
+ */
83
+ async fixedWindowLimit(key) {
84
+ const now = Date.now();
85
+ // Try to initialize the window if it doesn't exist
86
+ // SET key 0 EX window NX - Only set if key doesn't exist
87
+ const initialized = await this.redis.set(key, "0", {
88
+ EX: this.window,
89
+ NX: true,
90
+ });
91
+ // Atomically increment the counter
92
+ const currentCount = await this.redis.incr(key);
93
+ // Get TTL to calculate reset time
94
+ const ttl = await this.redis.ttl(key);
95
+ // Calculate reset timestamp
96
+ const resetTime = ttl > 0 ? now + ttl * 1000 : now + this.window * 1000;
97
+ const remaining = Math.max(0, this.requestLimit - currentCount);
98
+ const allowed = currentCount <= this.requestLimit;
99
+ this.logger.info?.(`Rate limit check: identifier=${key}, count=${currentCount}/${this.requestLimit}, allowed=${allowed}`);
100
+ return {
101
+ allowed,
102
+ limit: this.requestLimit,
103
+ remaining,
104
+ reset: Math.ceil(resetTime / 1000),
105
+ ttl: ttl > 0 ? ttl : this.window,
106
+ };
107
+ }
108
+ /**
109
+ * Reset rate limit for a specific identifier
110
+ * Useful for testing or manual intervention
111
+ *
112
+ * @param identifier - Unique identifier to reset
113
+ * @returns Promise resolving to true if key was deleted, false otherwise
114
+ */
115
+ async reset(identifier) {
116
+ if (!identifier || identifier.trim() === "") {
117
+ throw new Error("Identifier cannot be empty");
118
+ }
119
+ if (!this.isRedisAvailable()) {
120
+ throw new Error("Redis client is not available");
121
+ }
122
+ const key = this.buildKey(identifier);
123
+ const result = await this.redis.del(key);
124
+ this.logger.info?.(`Rate limit reset for: ${key}`);
125
+ return result > 0;
126
+ }
127
+ /**
128
+ * Get current rate limit status for an identifier
129
+ *
130
+ * @param identifier - Unique identifier to check
131
+ * @returns Promise resolving to current count and TTL, or null if no limit exists
132
+ */
133
+ async getStatus(identifier) {
134
+ if (!identifier || identifier.trim() === "") {
135
+ throw new Error("Identifier cannot be empty");
136
+ }
137
+ if (!this.isRedisAvailable()) {
138
+ throw new Error("Redis client is not available");
139
+ }
140
+ const key = this.buildKey(identifier);
141
+ // Use pipeline for atomic read
142
+ const pipeline = this.redis.multi();
143
+ pipeline.get(key);
144
+ pipeline.ttl(key);
145
+ const results = await pipeline.exec();
146
+ const currentValue = results?.[0];
147
+ const ttl = results?.[1] || -1;
148
+ if (!currentValue || ttl < 0) {
149
+ return null;
150
+ }
151
+ const current = parseInt(currentValue, 10);
152
+ return { current, ttl };
153
+ }
154
+ }
155
+ exports.RateLimiter = RateLimiter;
156
+ //# sourceMappingURL=limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limiter.js","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,145 @@
1
+ import { RedisClientType } from "redis";
2
+ import { Request } from "express";
3
+ /**
4
+ * Rate limiting algorithm types
5
+ * Currently only fixed-window is implemented
6
+ */
7
+ export type Algorithm = "fixed-window";
8
+ /**
9
+ * Strategy for handling failures when Redis is unavailable
10
+ * - 'open': Allow requests through (recommended for availability)
11
+ * - 'closed': Block all requests (recommended for strict security)
12
+ */
13
+ export type FailStrategy = "open" | "closed";
14
+ /**
15
+ * Logger interface for optional logging
16
+ * If not provided, console will be used as fallback
17
+ */
18
+ export interface Logger {
19
+ info?: (...args: any[]) => void;
20
+ warn?: (...args: any[]) => void;
21
+ error?: (...args: any[]) => void;
22
+ }
23
+ /**
24
+ * Result returned by the core rate limiter
25
+ */
26
+ export interface RateLimitResult {
27
+ /**
28
+ * Whether the request should be allowed
29
+ */
30
+ allowed: boolean;
31
+ /**
32
+ * Maximum number of requests allowed in the window
33
+ */
34
+ limit: number;
35
+ /**
36
+ * Number of requests remaining in current window
37
+ */
38
+ remaining: number;
39
+ /**
40
+ * Timestamp (in seconds) when the rate limit resets
41
+ */
42
+ reset: number;
43
+ /**
44
+ * Time to live in seconds for the current window
45
+ */
46
+ ttl: number;
47
+ }
48
+ /**
49
+ * Core rate limiter configuration
50
+ */
51
+ export interface RateLimiterConfig {
52
+ /**
53
+ * Redis client instance (must be connected)
54
+ */
55
+ redis: RedisClientType;
56
+ /**
57
+ * Maximum number of requests allowed in the time window
58
+ */
59
+ limit: number;
60
+ /**
61
+ * Time window in seconds
62
+ */
63
+ window: number;
64
+ /**
65
+ * Prefix for Redis keys (useful for different route groups)
66
+ */
67
+ keyPrefix: string;
68
+ /**
69
+ * Rate limiting algorithm
70
+ * @default 'fixed-window'
71
+ */
72
+ algorithm?: Algorithm;
73
+ /**
74
+ * Optional logger instance
75
+ * If not provided, falls back to console
76
+ */
77
+ logger?: Logger;
78
+ }
79
+ /**
80
+ * Express middleware configuration
81
+ */
82
+ export interface ExpressRateLimitOptions extends RateLimiterConfig {
83
+ /**
84
+ * Custom function to extract identifier from request
85
+ * If not provided, uses IP address from request
86
+ *
87
+ * @param req - Express request object
88
+ * @returns Unique identifier for the client, or null to use IP
89
+ *
90
+ * @example
91
+ * // Use user ID from JWT token
92
+ * identifier: (req) => req.user?.id?.toString() || null
93
+ *
94
+ * @example
95
+ * // Use API key from headers
96
+ * identifier: (req) => req.headers['x-api-key'] as string || null
97
+ */
98
+ identifier?: (req: Request) => string | null;
99
+ /**
100
+ * Custom error message when rate limit is exceeded
101
+ * @default 'Too many requests. Please try again later.'
102
+ */
103
+ message?: string;
104
+ /**
105
+ * Function to skip rate limiting for certain requests
106
+ *
107
+ * @param req - Express request object
108
+ * @returns true to skip rate limiting, false to apply it
109
+ *
110
+ * @example
111
+ * // Skip rate limiting for admin users
112
+ * skip: (req) => req.user?.isAdmin === true
113
+ *
114
+ * @example
115
+ * // Skip rate limiting for internal IPs
116
+ * skip: (req) => req.ip?.startsWith('192.168.')
117
+ */
118
+ skip?: (req: Request) => boolean;
119
+ /**
120
+ * Strategy for handling Redis failures
121
+ * - 'open': Allow requests when Redis is down (recommended)
122
+ * - 'closed': Block requests when Redis is down
123
+ * @default 'open'
124
+ */
125
+ failStrategy?: FailStrategy;
126
+ /**
127
+ * Whether to include standard rate limit headers in responses
128
+ * - X-RateLimit-Limit
129
+ * - X-RateLimit-Remaining
130
+ * - X-RateLimit-Reset
131
+ * - Retry-After (when limit exceeded)
132
+ * @default true
133
+ */
134
+ standardHeaders?: boolean;
135
+ }
136
+ /**
137
+ * Internal rate limit information for header setting
138
+ */
139
+ export interface RateLimitInfo {
140
+ limit: number;
141
+ remaining: number;
142
+ reset: number;
143
+ retryAfter?: number;
144
+ }
145
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC;AAEvC;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE7C;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAChC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC;IAEvB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IAEtB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,iBAAiB;IAChE;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;IAE7C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAEjC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAE5B;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @periodic/titanium
3
+ *
4
+ * Production-ready Redis-backed rate limiting middleware for Express
5
+ * with TypeScript support, fail-safe design, and flexible configuration.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export { rateLimit, createRateLimiter } from "./adapters/express";
10
+ export { RateLimiter } from "./core/limiter";
11
+ export type { Algorithm, FailStrategy, Logger, RateLimitResult, RateLimiterConfig, ExpressRateLimitOptions, RateLimitInfo, } from "./core/types";
12
+ export { extractClientIp, normalizeIp, getDefaultIdentifier } from "./utils/ip";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,YAAY,EACV,SAAS,EACT,YAAY,EACZ,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,GACd,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /**
3
+ * @periodic/titanium
4
+ *
5
+ * Production-ready Redis-backed rate limiting middleware for Express
6
+ * with TypeScript support, fail-safe design, and flexible configuration.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.getDefaultIdentifier = exports.normalizeIp = exports.extractClientIp = exports.RateLimiter = exports.createRateLimiter = exports.rateLimit = void 0;
12
+ // Export Express middleware
13
+ var express_1 = require("./adapters/express");
14
+ Object.defineProperty(exports, "rateLimit", { enumerable: true, get: function () { return express_1.rateLimit; } });
15
+ Object.defineProperty(exports, "createRateLimiter", { enumerable: true, get: function () { return express_1.createRateLimiter; } });
16
+ // Export core components for advanced usage
17
+ var limiter_1 = require("./core/limiter");
18
+ Object.defineProperty(exports, "RateLimiter", { enumerable: true, get: function () { return limiter_1.RateLimiter; } });
19
+ // Export utilities
20
+ var ip_1 = require("./utils/ip");
21
+ Object.defineProperty(exports, "extractClientIp", { enumerable: true, get: function () { return ip_1.extractClientIp; } });
22
+ Object.defineProperty(exports, "normalizeIp", { enumerable: true, get: function () { return ip_1.normalizeIp; } });
23
+ Object.defineProperty(exports, "getDefaultIdentifier", { enumerable: true, get: function () { return ip_1.getDefaultIdentifier; } });
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,4BAA4B;AAC5B,8CAAkE;AAAzD,oGAAA,SAAS,OAAA;AAAE,4GAAA,iBAAiB,OAAA;AAErC,4CAA4C;AAC5C,0CAA6C;AAApC,sGAAA,WAAW,OAAA;AAapB,mBAAmB;AACnB,iCAAgF;AAAvE,qGAAA,eAAe,OAAA;AAAE,iGAAA,WAAW,OAAA;AAAE,0GAAA,oBAAoB,OAAA"}
@@ -0,0 +1,31 @@
1
+ import { Request } from "express";
2
+ /**
3
+ * Extract client IP address from Express request
4
+ * Handles various proxy and load balancer scenarios
5
+ *
6
+ * Priority:
7
+ * 1. X-Forwarded-For header (first IP if multiple)
8
+ * 2. X-Real-IP header
9
+ * 3. Socket remote address
10
+ * 4. 'unknown' as fallback
11
+ *
12
+ * @param req - Express request object
13
+ * @returns IP address string
14
+ */
15
+ export declare function extractClientIp(req: Request): string;
16
+ /**
17
+ * Normalize IP address for consistent key generation
18
+ * Handles IPv6 to IPv4 mapping
19
+ *
20
+ * @param ip - IP address string
21
+ * @returns Normalized IP address
22
+ */
23
+ export declare function normalizeIp(ip: string): string;
24
+ /**
25
+ * Extract and normalize client identifier from request
26
+ *
27
+ * @param req - Express request object
28
+ * @returns Normalized IP address
29
+ */
30
+ export declare function getDefaultIdentifier(req: Request): string;
31
+ //# sourceMappingURL=ip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ip.d.ts","sourceRoot":"","sources":["../../src/utils/ip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CA0BpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAQ9C;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAGzD"}