te.js 2.1.0 → 2.1.2

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.
Files changed (70) hide show
  1. package/README.md +197 -196
  2. package/auto-docs/analysis/handler-analyzer.js +58 -58
  3. package/auto-docs/analysis/source-resolver.js +101 -101
  4. package/auto-docs/constants.js +37 -37
  5. package/auto-docs/docs-llm/index.js +7 -7
  6. package/auto-docs/docs-llm/prompts.js +222 -222
  7. package/auto-docs/docs-llm/provider.js +132 -132
  8. package/auto-docs/index.js +146 -146
  9. package/auto-docs/openapi/endpoint-processor.js +277 -277
  10. package/auto-docs/openapi/generator.js +107 -107
  11. package/auto-docs/openapi/level3.js +131 -131
  12. package/auto-docs/openapi/spec-builders.js +244 -244
  13. package/auto-docs/ui/docs-ui.js +186 -186
  14. package/auto-docs/utils/logger.js +17 -17
  15. package/auto-docs/utils/strip-usage.js +10 -10
  16. package/cli/docs-command.js +315 -315
  17. package/cli/fly-command.js +71 -71
  18. package/cli/index.js +56 -56
  19. package/cors/index.js +71 -0
  20. package/database/index.js +165 -165
  21. package/database/mongodb.js +146 -146
  22. package/database/redis.js +201 -201
  23. package/docs/README.md +36 -36
  24. package/docs/ammo.md +362 -362
  25. package/docs/api-reference.md +490 -490
  26. package/docs/auto-docs.md +216 -216
  27. package/docs/cli.md +152 -152
  28. package/docs/configuration.md +275 -275
  29. package/docs/database.md +390 -390
  30. package/docs/error-handling.md +438 -438
  31. package/docs/file-uploads.md +333 -333
  32. package/docs/getting-started.md +214 -214
  33. package/docs/middleware.md +355 -355
  34. package/docs/rate-limiting.md +393 -393
  35. package/docs/routing.md +302 -302
  36. package/lib/llm/client.js +73 -0
  37. package/lib/llm/index.js +7 -0
  38. package/lib/llm/parse.js +89 -0
  39. package/package.json +64 -62
  40. package/rate-limit/algorithms/fixed-window.js +141 -141
  41. package/rate-limit/algorithms/sliding-window.js +147 -147
  42. package/rate-limit/algorithms/token-bucket.js +115 -115
  43. package/rate-limit/base.js +165 -165
  44. package/rate-limit/index.js +147 -147
  45. package/rate-limit/storage/base.js +104 -104
  46. package/rate-limit/storage/memory.js +101 -101
  47. package/rate-limit/storage/redis.js +88 -88
  48. package/server/ammo/body-parser.js +220 -220
  49. package/server/ammo/dispatch-helper.js +103 -103
  50. package/server/ammo/enhancer.js +57 -57
  51. package/server/ammo.js +454 -415
  52. package/server/endpoint.js +97 -74
  53. package/server/error.js +9 -9
  54. package/server/errors/code-context.js +125 -125
  55. package/server/errors/llm-error-service.js +140 -140
  56. package/server/files/helper.js +33 -33
  57. package/server/files/uploader.js +143 -143
  58. package/server/handler.js +158 -119
  59. package/server/target.js +185 -175
  60. package/server/targets/middleware-validator.js +22 -22
  61. package/server/targets/path-validator.js +21 -21
  62. package/server/targets/registry.js +160 -160
  63. package/server/targets/shoot-validator.js +21 -21
  64. package/te.js +428 -402
  65. package/utils/auto-register.js +17 -17
  66. package/utils/configuration.js +64 -64
  67. package/utils/errors-llm-config.js +84 -84
  68. package/utils/request-logger.js +43 -43
  69. package/utils/status-codes.js +82 -82
  70. package/utils/tejas-entrypoint-html.js +18 -18
@@ -1,165 +1,165 @@
1
- import TejError from '../server/error.js';
2
- import MemoryStorage from './storage/memory.js';
3
- import RedisStorage from './storage/redis.js';
4
- import dbManager from '../database/index.js';
5
-
6
- /**
7
- * Base rate limiter class implementing common functionality for rate limiting algorithms
8
- *
9
- * @abstract
10
- * @class
11
- * @description
12
- * This is the base class for all rate limiting algorithms. It provides common configuration
13
- * options and storage handling, while allowing specific algorithms to implement their own logic.
14
- * Only one algorithm can be active per instance - the algorithm is determined by which options
15
- * object is provided (tokenBucketConfig, slidingWindowConfig, or fixedWindowConfig).
16
- *
17
- * @example
18
- * // Using with Redis storage and token bucket algorithm
19
- * const limiter = new TokenBucketRateLimiter({
20
- * maxRequests: 10,
21
- * timeWindowSeconds: 60,
22
- * store: 'redis',
23
- * tokenBucketConfig: {
24
- * refillRate: 0.5,
25
- * burstSize: 15
26
- * }
27
- * });
28
- */
29
- class RateLimiter {
30
- /**
31
- * Creates a new rate limiter instance
32
- *
33
- * @param {Object} options - Configuration options for the rate limiter
34
- * @param {number} [options.maxRequests=60] - Maximum number of requests allowed within the time window.
35
- * This is the default rate limit cap that applies across all algorithms.
36
- * For token bucket, this affects the default refill rate.
37
- * @param {number} [options.timeWindowSeconds=60] - Time window in seconds for rate limiting.
38
- * For fixed window, this is the window duration.
39
- * For sliding window, this is the total time span considered.
40
- * For token bucket, this affects the default refill rate calculation.
41
- * @param {string} [options.keyPrefix='rl:'] - Prefix for storage keys. Useful when implementing different rate limit
42
- * rules with different prefixes (e.g., 'rl:api:', 'rl:web:').
43
- * @param {string} [options.store='memory'] - Storage backend to use ('memory' or 'redis')
44
- * @param {Object} [options.tokenBucketConfig] - Token bucket algorithm specific options
45
- * @param {Object} [options.slidingWindowConfig] - Sliding window algorithm specific options
46
- * @param {Object} [options.fixedWindowConfig] - Fixed window algorithm specific options
47
- */
48
- constructor(options) {
49
- // Common options for all algorithms
50
- this.options = {
51
- maxRequests: 60, // Maximum number of requests
52
- timeWindowSeconds: 60, // Time window in seconds
53
- keyPrefix: 'rl:', // Key prefix for storage
54
- store: 'memory', // Default to memory storage
55
- ...options,
56
- };
57
-
58
- // Only one algorithm can be active per instance
59
- if (options?.tokenBucketConfig && options?.slidingWindowConfig) {
60
- throw new TejError(
61
- 400,
62
- 'Cannot use multiple rate limiting algorithms. Choose either tokenBucketConfig or slidingWindowConfig or fixedWindowConfig.',
63
- );
64
- }
65
-
66
- if (options?.tokenBucketConfig && options?.fixedWindowConfig) {
67
- throw new TejError(
68
- 500,
69
- 'Cannot use multiple rate limiting algorithms. Choose either tokenBucketConfig or slidingWindowConfig or fixedWindowConfig.',
70
- );
71
- }
72
-
73
- if (options?.slidingWindowConfig && options?.fixedWindowConfig) {
74
- throw new TejError(
75
- 500,
76
- 'Cannot use multiple rate limiting algorithms. Choose either tokenBucketConfig or slidingWindowConfig or fixedWindowConfig.',
77
- );
78
- }
79
-
80
- // Set default values for algorithm options if any are provided
81
- this.tokenBucketOptions = options?.tokenBucketConfig
82
- ? {
83
- refillRate: this.options.maxRequests / this.options.timeWindowSeconds, // Tokens per second
84
- burstSize: this.options.maxRequests, // Maximum token capacity
85
- ...options.tokenBucketConfig,
86
- }
87
- : null;
88
-
89
- this.slidingWindowOptions = options?.slidingWindowConfig
90
- ? {
91
- granularity: 1, // Time precision in seconds
92
- weights: { current: 1, previous: 0 }, // Weights for current and previous windows
93
- ...options.slidingWindowConfig,
94
- }
95
- : null;
96
-
97
- this.fixedWindowOptions = options?.fixedWindowConfig
98
- ? {
99
- strictWindow: false, // If true, windows align with clock
100
- ...options.fixedWindowConfig,
101
- }
102
- : null;
103
-
104
- // Initialize storage based on store type
105
- if (this.options.store === 'redis') {
106
- if (!dbManager.hasConnection('redis')) {
107
- throw new TejError(
108
- 500,
109
- 'Redis store selected but no Redis connection available. Call withRedis() first.',
110
- );
111
- }
112
- const redisClient = dbManager.getConnection('redis');
113
- this.storage = new RedisStorage(redisClient);
114
- } else {
115
- this.storage = new MemoryStorage();
116
- }
117
- }
118
-
119
- /**
120
- * Generate storage key for the rate limit identifier
121
- *
122
- * @param {string} identifier - Unique identifier for the rate limit (e.g. IP address, user ID)
123
- * @returns {string} The storage key with prefix
124
- */
125
- getKey(identifier) {
126
- return `${this.options.keyPrefix}${identifier}`;
127
- }
128
-
129
- /**
130
- * Abstract method for checking if request is allowed
131
- * Must be implemented by concrete rate limiter classes
132
- *
133
- * @abstract
134
- * @param {string} identifier - Unique identifier for the rate limit (e.g. IP address, user ID)
135
- * @returns {Promise<Object>} Rate limit check result
136
- * @returns {boolean} result.success - Whether the request is allowed
137
- * @returns {number} result.remainingRequests - Number of requests remaining in the window
138
- * @returns {number} result.resetTime - Unix timestamp when the rate limit resets
139
- * @throws {Error} If not implemented by child class
140
- */
141
- async consume(identifier) {
142
- throw new TejError(500, 'Not implemented');
143
- }
144
-
145
- /**
146
- * Get algorithm-specific options for the specified algorithm type
147
- *
148
- * @param {string} type - Algorithm type ('tokenBucketConfig', 'slidingWindowConfig', or 'fixedWindowConfig')
149
- * @returns {Object|null} The algorithm-specific options, or null if type not found
150
- */
151
- getAlgorithmOptions(type) {
152
- switch (type) {
153
- case 'token-bucket':
154
- return this.tokenBucketOptions;
155
- case 'sliding-window':
156
- return this.slidingWindowOptions;
157
- case 'fixed-window':
158
- return this.fixedWindowOptions;
159
- default:
160
- return null;
161
- }
162
- }
163
- }
164
-
165
- export default RateLimiter;
1
+ import TejError from '../server/error.js';
2
+ import MemoryStorage from './storage/memory.js';
3
+ import RedisStorage from './storage/redis.js';
4
+ import dbManager from '../database/index.js';
5
+
6
+ /**
7
+ * Base rate limiter class implementing common functionality for rate limiting algorithms
8
+ *
9
+ * @abstract
10
+ * @class
11
+ * @description
12
+ * This is the base class for all rate limiting algorithms. It provides common configuration
13
+ * options and storage handling, while allowing specific algorithms to implement their own logic.
14
+ * Only one algorithm can be active per instance - the algorithm is determined by which options
15
+ * object is provided (tokenBucketConfig, slidingWindowConfig, or fixedWindowConfig).
16
+ *
17
+ * @example
18
+ * // Using with Redis storage and token bucket algorithm
19
+ * const limiter = new TokenBucketRateLimiter({
20
+ * maxRequests: 10,
21
+ * timeWindowSeconds: 60,
22
+ * store: 'redis',
23
+ * tokenBucketConfig: {
24
+ * refillRate: 0.5,
25
+ * burstSize: 15
26
+ * }
27
+ * });
28
+ */
29
+ class RateLimiter {
30
+ /**
31
+ * Creates a new rate limiter instance
32
+ *
33
+ * @param {Object} options - Configuration options for the rate limiter
34
+ * @param {number} [options.maxRequests=60] - Maximum number of requests allowed within the time window.
35
+ * This is the default rate limit cap that applies across all algorithms.
36
+ * For token bucket, this affects the default refill rate.
37
+ * @param {number} [options.timeWindowSeconds=60] - Time window in seconds for rate limiting.
38
+ * For fixed window, this is the window duration.
39
+ * For sliding window, this is the total time span considered.
40
+ * For token bucket, this affects the default refill rate calculation.
41
+ * @param {string} [options.keyPrefix='rl:'] - Prefix for storage keys. Useful when implementing different rate limit
42
+ * rules with different prefixes (e.g., 'rl:api:', 'rl:web:').
43
+ * @param {string} [options.store='memory'] - Storage backend to use ('memory' or 'redis')
44
+ * @param {Object} [options.tokenBucketConfig] - Token bucket algorithm specific options
45
+ * @param {Object} [options.slidingWindowConfig] - Sliding window algorithm specific options
46
+ * @param {Object} [options.fixedWindowConfig] - Fixed window algorithm specific options
47
+ */
48
+ constructor(options) {
49
+ // Common options for all algorithms
50
+ this.options = {
51
+ maxRequests: 60, // Maximum number of requests
52
+ timeWindowSeconds: 60, // Time window in seconds
53
+ keyPrefix: 'rl:', // Key prefix for storage
54
+ store: 'memory', // Default to memory storage
55
+ ...options,
56
+ };
57
+
58
+ // Only one algorithm can be active per instance
59
+ if (options?.tokenBucketConfig && options?.slidingWindowConfig) {
60
+ throw new TejError(
61
+ 400,
62
+ 'Cannot use multiple rate limiting algorithms. Choose either tokenBucketConfig or slidingWindowConfig or fixedWindowConfig.',
63
+ );
64
+ }
65
+
66
+ if (options?.tokenBucketConfig && options?.fixedWindowConfig) {
67
+ throw new TejError(
68
+ 500,
69
+ 'Cannot use multiple rate limiting algorithms. Choose either tokenBucketConfig or slidingWindowConfig or fixedWindowConfig.',
70
+ );
71
+ }
72
+
73
+ if (options?.slidingWindowConfig && options?.fixedWindowConfig) {
74
+ throw new TejError(
75
+ 500,
76
+ 'Cannot use multiple rate limiting algorithms. Choose either tokenBucketConfig or slidingWindowConfig or fixedWindowConfig.',
77
+ );
78
+ }
79
+
80
+ // Set default values for algorithm options if any are provided
81
+ this.tokenBucketOptions = options?.tokenBucketConfig
82
+ ? {
83
+ refillRate: this.options.maxRequests / this.options.timeWindowSeconds, // Tokens per second
84
+ burstSize: this.options.maxRequests, // Maximum token capacity
85
+ ...options.tokenBucketConfig,
86
+ }
87
+ : null;
88
+
89
+ this.slidingWindowOptions = options?.slidingWindowConfig
90
+ ? {
91
+ granularity: 1, // Time precision in seconds
92
+ weights: { current: 1, previous: 0 }, // Weights for current and previous windows
93
+ ...options.slidingWindowConfig,
94
+ }
95
+ : null;
96
+
97
+ this.fixedWindowOptions = options?.fixedWindowConfig
98
+ ? {
99
+ strictWindow: false, // If true, windows align with clock
100
+ ...options.fixedWindowConfig,
101
+ }
102
+ : null;
103
+
104
+ // Initialize storage based on store type
105
+ if (this.options.store === 'redis') {
106
+ if (!dbManager.hasConnection('redis')) {
107
+ throw new TejError(
108
+ 500,
109
+ 'Redis store selected but no Redis connection available. Call withRedis() first.',
110
+ );
111
+ }
112
+ const redisClient = dbManager.getConnection('redis');
113
+ this.storage = new RedisStorage(redisClient);
114
+ } else {
115
+ this.storage = new MemoryStorage();
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Generate storage key for the rate limit identifier
121
+ *
122
+ * @param {string} identifier - Unique identifier for the rate limit (e.g. IP address, user ID)
123
+ * @returns {string} The storage key with prefix
124
+ */
125
+ getKey(identifier) {
126
+ return `${this.options.keyPrefix}${identifier}`;
127
+ }
128
+
129
+ /**
130
+ * Abstract method for checking if request is allowed
131
+ * Must be implemented by concrete rate limiter classes
132
+ *
133
+ * @abstract
134
+ * @param {string} identifier - Unique identifier for the rate limit (e.g. IP address, user ID)
135
+ * @returns {Promise<Object>} Rate limit check result
136
+ * @returns {boolean} result.success - Whether the request is allowed
137
+ * @returns {number} result.remainingRequests - Number of requests remaining in the window
138
+ * @returns {number} result.resetTime - Unix timestamp when the rate limit resets
139
+ * @throws {Error} If not implemented by child class
140
+ */
141
+ async consume(identifier) {
142
+ throw new TejError(500, 'Not implemented');
143
+ }
144
+
145
+ /**
146
+ * Get algorithm-specific options for the specified algorithm type
147
+ *
148
+ * @param {string} type - Algorithm type ('tokenBucketConfig', 'slidingWindowConfig', or 'fixedWindowConfig')
149
+ * @returns {Object|null} The algorithm-specific options, or null if type not found
150
+ */
151
+ getAlgorithmOptions(type) {
152
+ switch (type) {
153
+ case 'token-bucket':
154
+ return this.tokenBucketOptions;
155
+ case 'sliding-window':
156
+ return this.slidingWindowOptions;
157
+ case 'fixed-window':
158
+ return this.fixedWindowOptions;
159
+ default:
160
+ return null;
161
+ }
162
+ }
163
+ }
164
+
165
+ export default RateLimiter;
@@ -1,147 +1,147 @@
1
- import TejError from '../server/error.js';
2
- import FixedWindowRateLimiter from './algorithms/fixed-window.js';
3
- import SlidingWindowRateLimiter from './algorithms/sliding-window.js';
4
- import TokenBucketRateLimiter from './algorithms/token-bucket.js';
5
- import dbManager from '../database/index.js';
6
-
7
- /**
8
- * Creates a rate limiting middleware function with the specified algorithm and storage
9
- *
10
- * @param {Object} options - Configuration options for the rate limiter
11
- * @param {number} options.maxRequests - Maximum number of requests allowed within the time window
12
- * @param {number} options.timeWindowSeconds - Time window in seconds
13
- * @param {string} [options.algorithm='sliding-window'] - Rate limiting algorithm to use:
14
- * - 'token-bucket': Best for handling traffic bursts
15
- * - 'sliding-window': Best for smooth rate limiting
16
- * - 'fixed-window': Simplest approach
17
- * @param {string} [options.store='memory'] - Storage backend to use:
18
- * - 'memory': In-memory storage (default)
19
- * - 'redis': Redis-based storage (requires global Redis config)
20
- * @param {Object} [options.algorithmOptions] - Algorithm-specific options
21
- * @param {Function} [options.keyGenerator] - Optional function to generate unique identifiers
22
- * @param {Object} [options.headerFormat] - Rate limit header format configuration
23
- * @param {string} [options.headerFormat.type='standard'] - Type of headers to use:
24
- * - 'legacy': Use X-RateLimit-* headers
25
- * - 'standard': Use RateLimit-* headers (draft 6+)
26
- * - 'both': Use both legacy and standard headers
27
- * @param {boolean} [options.headerFormat.draft7=false] - Whether to include draft 7 policy header
28
- * @param {boolean} [options.headerFormat.draft8=false] - Whether to include draft 8 reset format
29
- * @param {Function} [options.onRateLimited] - Optional callback when rate limit is exceeded
30
- * @returns {Function} Middleware function for use with te.js
31
- */
32
- function rateLimiter(options) {
33
- const {
34
- algorithm = 'sliding-window',
35
- store = 'memory',
36
- keyGenerator = (ammo) => ammo.ip,
37
- headerFormat = { type: 'standard' },
38
- onRateLimited,
39
- ...limiterOptions
40
- } = options;
41
-
42
- // Check Redis connectivity if Redis store is selected
43
- if (store === 'redis' && !dbManager.hasConnection('redis', {})) {
44
- throw new TejError(
45
- 400,
46
- 'Redis store selected but no Redis connection found. Please use withRedis() before using withRateLimit()',
47
- );
48
- }
49
-
50
- // Map algorithm names to their config property names
51
- const configMap = {
52
- 'token-bucket': 'tokenBucketConfig',
53
- 'sliding-window': 'slidingWindowConfig',
54
- 'fixed-window': 'fixedWindowConfig',
55
- };
56
-
57
- const configKey = configMap[algorithm];
58
- if (!configKey) {
59
- throw new TejError(
60
- 400,
61
- `Invalid algorithm: ${algorithm}. Must be one of: ${Object.keys(configMap).join(', ')}`,
62
- );
63
- }
64
-
65
- // Create algorithm-specific config
66
- const limiterConfig = {
67
- maxRequests: limiterOptions.maxRequests,
68
- timeWindowSeconds: limiterOptions.timeWindowSeconds,
69
- [configKey]: limiterOptions.algorithmOptions || {},
70
- store, // Pass the store type to the limiter
71
- };
72
-
73
- // Create the appropriate limiter instance
74
- let limiter;
75
- switch (algorithm) {
76
- case 'token-bucket':
77
- limiter = new TokenBucketRateLimiter(limiterConfig);
78
- break;
79
- case 'sliding-window':
80
- limiter = new SlidingWindowRateLimiter(limiterConfig);
81
- break;
82
- case 'fixed-window':
83
- limiter = new FixedWindowRateLimiter(limiterConfig);
84
- break;
85
- default:
86
- throw new TejError(400, 'Invalid algorithm specified');
87
- }
88
-
89
- // Helper to set headers based on format
90
- const setRateLimitHeaders = (ammo, result) => {
91
- const { type = 'standard', draft7 = false, draft8 = false } = headerFormat;
92
- const useStandard = type === 'standard' || type === 'both';
93
- const useLegacy = type === 'legacy' || type === 'both';
94
-
95
- if (useStandard) {
96
- // Standard headers (draft 6+)
97
- ammo.res.setHeader('RateLimit-Limit', limiter.options.maxRequests);
98
- ammo.res.setHeader('RateLimit-Remaining', result.remainingRequests);
99
-
100
- // Draft 8 uses delta-seconds format
101
- if (draft8) {
102
- const resetDelta = result.resetTime - Math.floor(Date.now() / 1000);
103
- ammo.res.setHeader('RateLimit-Reset', resetDelta);
104
- } else {
105
- ammo.res.setHeader('RateLimit-Reset', result.resetTime);
106
- }
107
-
108
- // Draft 7 added optional policy information
109
- if (draft7) {
110
- const policy = `${limiter.options.maxRequests};w=${limiter.options.timeWindowSeconds}`;
111
- ammo.res.setHeader('RateLimit-Policy', policy);
112
- }
113
- }
114
-
115
- if (useLegacy) {
116
- // Legacy X- headers
117
- ammo.res.setHeader('X-RateLimit-Limit', limiter.options.maxRequests);
118
- ammo.res.setHeader('X-RateLimit-Remaining', result.remainingRequests);
119
- ammo.res.setHeader('X-RateLimit-Reset', result.resetTime);
120
- }
121
-
122
- // Always set Retry-After on 429 responses
123
- if (!result.success) {
124
- const retryAfter = result.resetTime - Math.floor(Date.now() / 1000);
125
- ammo.res.setHeader('Retry-After', retryAfter);
126
- }
127
- };
128
-
129
- // Return middleware function
130
- return async (ammo, next) => {
131
- const key = keyGenerator(ammo);
132
- const result = await limiter.consume(key);
133
-
134
- setRateLimitHeaders(ammo, result);
135
-
136
- if (!result.success) {
137
- if (onRateLimited) {
138
- return onRateLimited(ammo);
139
- }
140
- return ammo.throw(429, 'Too Many Requests');
141
- }
142
-
143
- await next();
144
- };
145
- }
146
-
147
- export default rateLimiter;
1
+ import TejError from '../server/error.js';
2
+ import FixedWindowRateLimiter from './algorithms/fixed-window.js';
3
+ import SlidingWindowRateLimiter from './algorithms/sliding-window.js';
4
+ import TokenBucketRateLimiter from './algorithms/token-bucket.js';
5
+ import dbManager from '../database/index.js';
6
+
7
+ /**
8
+ * Creates a rate limiting middleware function with the specified algorithm and storage
9
+ *
10
+ * @param {Object} options - Configuration options for the rate limiter
11
+ * @param {number} options.maxRequests - Maximum number of requests allowed within the time window
12
+ * @param {number} options.timeWindowSeconds - Time window in seconds
13
+ * @param {string} [options.algorithm='sliding-window'] - Rate limiting algorithm to use:
14
+ * - 'token-bucket': Best for handling traffic bursts
15
+ * - 'sliding-window': Best for smooth rate limiting
16
+ * - 'fixed-window': Simplest approach
17
+ * @param {string} [options.store='memory'] - Storage backend to use:
18
+ * - 'memory': In-memory storage (default)
19
+ * - 'redis': Redis-based storage (requires global Redis config)
20
+ * @param {Object} [options.algorithmOptions] - Algorithm-specific options
21
+ * @param {Function} [options.keyGenerator] - Optional function to generate unique identifiers
22
+ * @param {Object} [options.headerFormat] - Rate limit header format configuration
23
+ * @param {string} [options.headerFormat.type='standard'] - Type of headers to use:
24
+ * - 'legacy': Use X-RateLimit-* headers
25
+ * - 'standard': Use RateLimit-* headers (draft 6+)
26
+ * - 'both': Use both legacy and standard headers
27
+ * @param {boolean} [options.headerFormat.draft7=false] - Whether to include draft 7 policy header
28
+ * @param {boolean} [options.headerFormat.draft8=false] - Whether to include draft 8 reset format
29
+ * @param {Function} [options.onRateLimited] - Optional callback when rate limit is exceeded
30
+ * @returns {Function} Middleware function for use with te.js
31
+ */
32
+ function rateLimiter(options) {
33
+ const {
34
+ algorithm = 'sliding-window',
35
+ store = 'memory',
36
+ keyGenerator = (ammo) => ammo.ip,
37
+ headerFormat = { type: 'standard' },
38
+ onRateLimited,
39
+ ...limiterOptions
40
+ } = options;
41
+
42
+ // Check Redis connectivity if Redis store is selected
43
+ if (store === 'redis' && !dbManager.hasConnection('redis', {})) {
44
+ throw new TejError(
45
+ 400,
46
+ 'Redis store selected but no Redis connection found. Please use withRedis() before using withRateLimit()',
47
+ );
48
+ }
49
+
50
+ // Map algorithm names to their config property names
51
+ const configMap = {
52
+ 'token-bucket': 'tokenBucketConfig',
53
+ 'sliding-window': 'slidingWindowConfig',
54
+ 'fixed-window': 'fixedWindowConfig',
55
+ };
56
+
57
+ const configKey = configMap[algorithm];
58
+ if (!configKey) {
59
+ throw new TejError(
60
+ 400,
61
+ `Invalid algorithm: ${algorithm}. Must be one of: ${Object.keys(configMap).join(', ')}`,
62
+ );
63
+ }
64
+
65
+ // Create algorithm-specific config
66
+ const limiterConfig = {
67
+ maxRequests: limiterOptions.maxRequests,
68
+ timeWindowSeconds: limiterOptions.timeWindowSeconds,
69
+ [configKey]: limiterOptions.algorithmOptions || {},
70
+ store, // Pass the store type to the limiter
71
+ };
72
+
73
+ // Create the appropriate limiter instance
74
+ let limiter;
75
+ switch (algorithm) {
76
+ case 'token-bucket':
77
+ limiter = new TokenBucketRateLimiter(limiterConfig);
78
+ break;
79
+ case 'sliding-window':
80
+ limiter = new SlidingWindowRateLimiter(limiterConfig);
81
+ break;
82
+ case 'fixed-window':
83
+ limiter = new FixedWindowRateLimiter(limiterConfig);
84
+ break;
85
+ default:
86
+ throw new TejError(400, 'Invalid algorithm specified');
87
+ }
88
+
89
+ // Helper to set headers based on format
90
+ const setRateLimitHeaders = (ammo, result) => {
91
+ const { type = 'standard', draft7 = false, draft8 = false } = headerFormat;
92
+ const useStandard = type === 'standard' || type === 'both';
93
+ const useLegacy = type === 'legacy' || type === 'both';
94
+
95
+ if (useStandard) {
96
+ // Standard headers (draft 6+)
97
+ ammo.res.setHeader('RateLimit-Limit', limiter.options.maxRequests);
98
+ ammo.res.setHeader('RateLimit-Remaining', result.remainingRequests);
99
+
100
+ // Draft 8 uses delta-seconds format
101
+ if (draft8) {
102
+ const resetDelta = result.resetTime - Math.floor(Date.now() / 1000);
103
+ ammo.res.setHeader('RateLimit-Reset', resetDelta);
104
+ } else {
105
+ ammo.res.setHeader('RateLimit-Reset', result.resetTime);
106
+ }
107
+
108
+ // Draft 7 added optional policy information
109
+ if (draft7) {
110
+ const policy = `${limiter.options.maxRequests};w=${limiter.options.timeWindowSeconds}`;
111
+ ammo.res.setHeader('RateLimit-Policy', policy);
112
+ }
113
+ }
114
+
115
+ if (useLegacy) {
116
+ // Legacy X- headers
117
+ ammo.res.setHeader('X-RateLimit-Limit', limiter.options.maxRequests);
118
+ ammo.res.setHeader('X-RateLimit-Remaining', result.remainingRequests);
119
+ ammo.res.setHeader('X-RateLimit-Reset', result.resetTime);
120
+ }
121
+
122
+ // Always set Retry-After on 429 responses
123
+ if (!result.success) {
124
+ const retryAfter = result.resetTime - Math.floor(Date.now() / 1000);
125
+ ammo.res.setHeader('Retry-After', retryAfter);
126
+ }
127
+ };
128
+
129
+ // Return middleware function
130
+ return async (ammo, next) => {
131
+ const key = keyGenerator(ammo);
132
+ const result = await limiter.consume(key);
133
+
134
+ setRateLimitHeaders(ammo, result);
135
+
136
+ if (!result.success) {
137
+ if (onRateLimited) {
138
+ return onRateLimited(ammo);
139
+ }
140
+ return ammo.throw(429, 'Too Many Requests');
141
+ }
142
+
143
+ await next();
144
+ };
145
+ }
146
+
147
+ export default rateLimiter;