@asenajs/ergenecore 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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +636 -0
  3. package/dist/index.d.ts +24 -0
  4. package/dist/index.js +25 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/Ergenecore.d.ts +598 -0
  7. package/dist/lib/Ergenecore.js +1406 -0
  8. package/dist/lib/Ergenecore.js.map +1 -0
  9. package/dist/lib/ErgenecoreContextWrapper.d.ts +190 -0
  10. package/dist/lib/ErgenecoreContextWrapper.js +387 -0
  11. package/dist/lib/ErgenecoreContextWrapper.js.map +1 -0
  12. package/dist/lib/ErgenecoreWebsocketAdapter.d.ts +68 -0
  13. package/dist/lib/ErgenecoreWebsocketAdapter.js +233 -0
  14. package/dist/lib/ErgenecoreWebsocketAdapter.js.map +1 -0
  15. package/dist/lib/defaults/ConfigService.d.ts +39 -0
  16. package/dist/lib/defaults/ConfigService.js +30 -0
  17. package/dist/lib/defaults/ConfigService.js.map +1 -0
  18. package/dist/lib/defaults/MiddlewareService.d.ts +39 -0
  19. package/dist/lib/defaults/MiddlewareService.js +30 -0
  20. package/dist/lib/defaults/MiddlewareService.js.map +1 -0
  21. package/dist/lib/defaults/StaticServeService.d.ts +24 -0
  22. package/dist/lib/defaults/StaticServeService.js +23 -0
  23. package/dist/lib/defaults/StaticServeService.js.map +1 -0
  24. package/dist/lib/defaults/ValidationService.d.ts +35 -0
  25. package/dist/lib/defaults/ValidationService.js +34 -0
  26. package/dist/lib/defaults/ValidationService.js.map +1 -0
  27. package/dist/lib/defaults/index.d.ts +25 -0
  28. package/dist/lib/defaults/index.js +26 -0
  29. package/dist/lib/defaults/index.js.map +1 -0
  30. package/dist/lib/errors.d.ts +84 -0
  31. package/dist/lib/errors.js +108 -0
  32. package/dist/lib/errors.js.map +1 -0
  33. package/dist/lib/index.d.ts +36 -0
  34. package/dist/lib/index.js +37 -0
  35. package/dist/lib/index.js.map +1 -0
  36. package/dist/lib/middlewares/CorsMiddleware.d.ts +147 -0
  37. package/dist/lib/middlewares/CorsMiddleware.js +163 -0
  38. package/dist/lib/middlewares/CorsMiddleware.js.map +1 -0
  39. package/dist/lib/middlewares/RateLimiterMiddleware.d.ts +330 -0
  40. package/dist/lib/middlewares/RateLimiterMiddleware.js +300 -0
  41. package/dist/lib/middlewares/RateLimiterMiddleware.js.map +1 -0
  42. package/dist/lib/types/Handler.d.ts +65 -0
  43. package/dist/lib/types/Handler.js +10 -0
  44. package/dist/lib/types/Handler.js.map +1 -0
  45. package/dist/lib/types/StaticServeExtras.d.ts +29 -0
  46. package/dist/lib/types/StaticServeExtras.js +2 -0
  47. package/dist/lib/types/StaticServeExtras.js.map +1 -0
  48. package/dist/lib/types/Validation.d.ts +7 -0
  49. package/dist/lib/types/Validation.js +2 -0
  50. package/dist/lib/types/Validation.js.map +1 -0
  51. package/dist/lib/types/index.d.ts +24 -0
  52. package/dist/lib/types/index.js +25 -0
  53. package/dist/lib/types/index.js.map +1 -0
  54. package/dist/lib/utils/factory.d.ts +121 -0
  55. package/dist/lib/utils/factory.js +144 -0
  56. package/dist/lib/utils/factory.js.map +1 -0
  57. package/package.json +47 -0
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Rate Limiter Middleware for Ergenecore
3
+ *
4
+ * Implements Token Bucket algorithm for rate limiting:
5
+ * - Token bucket per client (identified by IP or custom key)
6
+ * - Configurable capacity and refill rate
7
+ * - Instance-based storage (each middleware instance has its own buckets)
8
+ * - Zero external dependencies (uses Bun's native APIs)
9
+ * - Asena DI container compatible
10
+ *
11
+ * @module defaults/RateLimiterMiddleware
12
+ *
13
+ * **Usage in Asena Application:**
14
+ * ```typescript
15
+ * import { Middleware } from '@asenajs/asena/server';
16
+ * import { RateLimiterMiddleware } from '@asenajs/ergenecore';
17
+ *
18
+ * // Create custom rate limiter for your needs
19
+ * @Middleware()
20
+ * export class ApiRateLimiter extends RateLimiterMiddleware {
21
+ * constructor() {
22
+ * super({
23
+ * capacity: 100,
24
+ * refillRate: 100 / 60, // 100 requests per minute
25
+ * });
26
+ * }
27
+ * }
28
+ *
29
+ * // Use in controller (class reference, not instance)
30
+ * @Controller({ middlewares: [ApiRateLimiter] })
31
+ * export class ApiController { ... }
32
+ *
33
+ * // Or use on specific route
34
+ * @Middleware()
35
+ * export class StrictRateLimiter extends RateLimiterMiddleware {
36
+ * constructor() {
37
+ * super({
38
+ * capacity: 5,
39
+ * refillRate: 5 / 60, // 5 requests per minute
40
+ * message: 'Too many login attempts. Please try again later.',
41
+ * });
42
+ * }
43
+ * }
44
+ *
45
+ * @Post({ path: '/login', middlewares: [StrictRateLimiter] })
46
+ * public login() { ... }
47
+ * ```
48
+ */
49
+ import type { Context } from '../ErgenecoreContextWrapper';
50
+ import { MiddlewareService } from '../defaults/MiddlewareService';
51
+ /**
52
+ * Token bucket for a single client
53
+ */
54
+ interface Bucket {
55
+ /**
56
+ * Current number of tokens available
57
+ */
58
+ tokens: number;
59
+ /**
60
+ * Last refill timestamp (milliseconds)
61
+ */
62
+ lastRefill: number;
63
+ }
64
+ /**
65
+ * Rate limiter configuration options
66
+ */
67
+ export interface RateLimiterOptions {
68
+ /**
69
+ * Maximum token capacity (burst size)
70
+ *
71
+ * This defines how many requests can be made in a burst.
72
+ * Example: capacity = 100 means 100 requests can be made instantly if bucket is full.
73
+ *
74
+ * @default 100
75
+ */
76
+ capacity?: number;
77
+ /**
78
+ * Token refill rate (tokens per second)
79
+ *
80
+ * This defines the sustained request rate.
81
+ * Example: refillRate = 10 means 10 requests per second sustained rate.
82
+ *
83
+ * Common patterns:
84
+ * - 10 req/s → refillRate: 10
85
+ * - 100 req/min → refillRate: 100/60 ≈ 1.67
86
+ * - 1000 req/hour → refillRate: 1000/3600 ≈ 0.28
87
+ *
88
+ * @default 10
89
+ */
90
+ refillRate?: number;
91
+ /**
92
+ * Custom key generator function
93
+ *
94
+ * By default, uses IP address from X-Forwarded-For header or remote address.
95
+ * You can customize this to use user ID, API key, or any other identifier.
96
+ *
97
+ * @default (ctx) => ctx.req.headers.get('x-forwarded-for') || 'unknown'
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * // Rate limit by user ID
102
+ * keyGenerator: (ctx) => ctx.state.user?.id || 'anonymous'
103
+ *
104
+ * // Rate limit by API key
105
+ * keyGenerator: (ctx) => ctx.req.headers.get('x-api-key') || 'unknown'
106
+ * ```
107
+ */
108
+ keyGenerator?: (context: Context) => string;
109
+ /**
110
+ * Custom message for rate limit exceeded response
111
+ *
112
+ * @default 'Rate limit exceeded. Please try again later.'
113
+ */
114
+ message?: string;
115
+ /**
116
+ * HTTP status code for rate limit exceeded response
117
+ *
118
+ * @default 429
119
+ */
120
+ statusCode?: number;
121
+ /**
122
+ * Token cost per request
123
+ *
124
+ * Advanced feature: Different endpoints can cost different amounts of tokens.
125
+ *
126
+ * @default 1
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * // Expensive operations cost more tokens
131
+ * cost: (ctx) => ctx.req.url.includes('/search') ? 5 : 1
132
+ * ```
133
+ */
134
+ cost?: number | ((context: Context) => number);
135
+ /**
136
+ * Skip rate limiting based on context
137
+ *
138
+ * Useful for whitelisting certain IPs, paths, or authenticated users.
139
+ *
140
+ * @default undefined
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * // Skip rate limiting for health check
145
+ * skip: (ctx) => ctx.req.url === '/health'
146
+ *
147
+ * // Skip for admin users
148
+ * skip: (ctx) => ctx.state.user?.role === 'admin'
149
+ * ```
150
+ */
151
+ skip?: (context: Context) => boolean;
152
+ /**
153
+ * Bucket cleanup interval in milliseconds
154
+ *
155
+ * Periodically removes inactive buckets to prevent memory leaks.
156
+ * Set to 0 to disable automatic cleanup.
157
+ *
158
+ * @default 60000 (1 minute)
159
+ */
160
+ cleanupInterval?: number;
161
+ /**
162
+ * Bucket TTL (time to live) in milliseconds
163
+ *
164
+ * Buckets that haven't been accessed for this duration will be removed during cleanup.
165
+ *
166
+ * @default 600000 (10 minutes)
167
+ */
168
+ bucketTTL?: number;
169
+ }
170
+ /**
171
+ * Rate Limiter Middleware
172
+ *
173
+ * Implements Token Bucket algorithm for controlling request rate.
174
+ * Each middleware instance maintains its own bucket storage (instance-based strategy).
175
+ *
176
+ * **Token Bucket Algorithm:**
177
+ * 1. Each client has a bucket with a maximum capacity of tokens
178
+ * 2. Tokens refill at a constant rate (refillRate per second)
179
+ * 3. Each request consumes tokens (default: 1 token)
180
+ * 4. If bucket has insufficient tokens → 429 Too Many Requests
181
+ * 5. Bucket capacity never exceeds maximum (tokens don't accumulate indefinitely)
182
+ *
183
+ * **Performance Optimizations:**
184
+ * - O(1) bucket lookup (Map)
185
+ * - O(1) token calculation (pure math, no arrays)
186
+ * - Lazy token refill (calculated on-demand, not via setInterval)
187
+ * - Memory-efficient bucket structure (2 numbers per client)
188
+ * - Automatic cleanup of inactive buckets
189
+ *
190
+ * **Instance-based Strategy:**
191
+ * Each RateLimiterMiddleware instance has its own bucket Map.
192
+ * This enables route-specific rate limiting:
193
+ * - GlobalRateLimiter → 1000 req/min for all routes
194
+ * - ApiRateLimiter → 100 req/min for /api/* routes
195
+ * - StrictRateLimiter → 5 req/min for /login route
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * // Global rate limiter (1000 req/min)
200
+ * @Middleware()
201
+ * export class GlobalRateLimiter extends RateLimiterMiddleware {
202
+ * constructor() {
203
+ * super({ capacity: 1000, refillRate: 1000/60 });
204
+ * }
205
+ * }
206
+ *
207
+ * // Strict rate limiter for auth endpoints (5 req/min)
208
+ * @Middleware()
209
+ * export class StrictRateLimiter extends RateLimiterMiddleware {
210
+ * constructor() {
211
+ * super({ capacity: 5, refillRate: 5/60 });
212
+ * }
213
+ * }
214
+ * ```
215
+ */
216
+ export declare class RateLimiterMiddleware extends MiddlewareService {
217
+ /**
218
+ * Instance-specific bucket storage
219
+ * Key: Client identifier (IP, user ID, etc.)
220
+ * Value: Token bucket state
221
+ */
222
+ private readonly buckets;
223
+ /**
224
+ * Maximum token capacity
225
+ */
226
+ private readonly capacity;
227
+ /**
228
+ * Token refill rate (tokens per second)
229
+ */
230
+ private readonly refillRate;
231
+ /**
232
+ * Client key generator function
233
+ */
234
+ private readonly keyGenerator;
235
+ /**
236
+ * Rate limit exceeded message
237
+ */
238
+ private readonly message;
239
+ /**
240
+ * Rate limit exceeded status code
241
+ */
242
+ private readonly statusCode;
243
+ /**
244
+ * Token cost per request
245
+ */
246
+ private readonly cost;
247
+ /**
248
+ * Skip predicate function
249
+ */
250
+ private readonly skip?;
251
+ /**
252
+ * Cleanup interval timer ID
253
+ */
254
+ private cleanupTimer?;
255
+ /**
256
+ * Bucket TTL in milliseconds
257
+ */
258
+ private readonly bucketTTL;
259
+ /**
260
+ * Creates a new rate limiter middleware instance
261
+ *
262
+ * @param options - Rate limiter configuration options
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * @Middleware()
267
+ * export class CustomRateLimiter extends RateLimiterMiddleware {
268
+ * constructor() {
269
+ * super({
270
+ * capacity: 100,
271
+ * refillRate: 10,
272
+ * keyGenerator: (ctx) => ctx.state.user?.id || 'anonymous',
273
+ * message: 'Çok fazla istek gönderdiniz. Lütfen bekleyin.',
274
+ * skip: (ctx) => ctx.req.url === '/health'
275
+ * });
276
+ * }
277
+ * }
278
+ * ```
279
+ */
280
+ constructor(options?: RateLimiterOptions);
281
+ /**
282
+ * Rate limiter middleware handler
283
+ *
284
+ * Execution flow:
285
+ * 1. Check if request should be skipped
286
+ * 2. Generate client key (IP, user ID, etc.)
287
+ * 3. Get or create bucket for client
288
+ * 4. Refill tokens based on elapsed time
289
+ * 5. Check if sufficient tokens available
290
+ * 6. Consume tokens and continue, or return 429
291
+ *
292
+ * @param context - Ergenecore context wrapper
293
+ * @param next - Function to call next middleware or handler
294
+ * @returns Response for rate limited requests, or result of next() for allowed requests
295
+ */
296
+ handle(context: Context, next: () => Promise<void>): Promise<any>;
297
+ /**
298
+ * Cleanup resources (stop cleanup timer)
299
+ *
300
+ * Call this when shutting down the application.
301
+ */
302
+ destroy(): void;
303
+ /**
304
+ * Get current bucket state for a client (for testing/debugging)
305
+ *
306
+ * @param key - Client identifier
307
+ * @returns Bucket state or undefined
308
+ */
309
+ getBucketState(key: string): Bucket | undefined;
310
+ /**
311
+ * Clear all buckets (for testing)
312
+ */
313
+ clearBuckets(): void;
314
+ /**
315
+ * Default key generator - uses X-Forwarded-For or falls back to 'unknown'
316
+ *
317
+ * @param context - Ergenecore context
318
+ * @returns Client identifier
319
+ */
320
+ private defaultKeyGenerator;
321
+ /**
322
+ * Setup automatic cleanup of inactive buckets
323
+ *
324
+ * Prevents memory leaks by removing buckets that haven't been accessed recently.
325
+ *
326
+ * @param interval - Cleanup interval in milliseconds
327
+ */
328
+ private setupCleanup;
329
+ }
330
+ export {};
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Rate Limiter Middleware for Ergenecore
3
+ *
4
+ * Implements Token Bucket algorithm for rate limiting:
5
+ * - Token bucket per client (identified by IP or custom key)
6
+ * - Configurable capacity and refill rate
7
+ * - Instance-based storage (each middleware instance has its own buckets)
8
+ * - Zero external dependencies (uses Bun's native APIs)
9
+ * - Asena DI container compatible
10
+ *
11
+ * @module defaults/RateLimiterMiddleware
12
+ *
13
+ * **Usage in Asena Application:**
14
+ * ```typescript
15
+ * import { Middleware } from '@asenajs/asena/server';
16
+ * import { RateLimiterMiddleware } from '@asenajs/ergenecore';
17
+ *
18
+ * // Create custom rate limiter for your needs
19
+ * @Middleware()
20
+ * export class ApiRateLimiter extends RateLimiterMiddleware {
21
+ * constructor() {
22
+ * super({
23
+ * capacity: 100,
24
+ * refillRate: 100 / 60, // 100 requests per minute
25
+ * });
26
+ * }
27
+ * }
28
+ *
29
+ * // Use in controller (class reference, not instance)
30
+ * @Controller({ middlewares: [ApiRateLimiter] })
31
+ * export class ApiController { ... }
32
+ *
33
+ * // Or use on specific route
34
+ * @Middleware()
35
+ * export class StrictRateLimiter extends RateLimiterMiddleware {
36
+ * constructor() {
37
+ * super({
38
+ * capacity: 5,
39
+ * refillRate: 5 / 60, // 5 requests per minute
40
+ * message: 'Too many login attempts. Please try again later.',
41
+ * });
42
+ * }
43
+ * }
44
+ *
45
+ * @Post({ path: '/login', middlewares: [StrictRateLimiter] })
46
+ * public login() { ... }
47
+ * ```
48
+ */
49
+ import { MiddlewareService } from '../defaults/MiddlewareService';
50
+ /**
51
+ * Rate Limiter Middleware
52
+ *
53
+ * Implements Token Bucket algorithm for controlling request rate.
54
+ * Each middleware instance maintains its own bucket storage (instance-based strategy).
55
+ *
56
+ * **Token Bucket Algorithm:**
57
+ * 1. Each client has a bucket with a maximum capacity of tokens
58
+ * 2. Tokens refill at a constant rate (refillRate per second)
59
+ * 3. Each request consumes tokens (default: 1 token)
60
+ * 4. If bucket has insufficient tokens → 429 Too Many Requests
61
+ * 5. Bucket capacity never exceeds maximum (tokens don't accumulate indefinitely)
62
+ *
63
+ * **Performance Optimizations:**
64
+ * - O(1) bucket lookup (Map)
65
+ * - O(1) token calculation (pure math, no arrays)
66
+ * - Lazy token refill (calculated on-demand, not via setInterval)
67
+ * - Memory-efficient bucket structure (2 numbers per client)
68
+ * - Automatic cleanup of inactive buckets
69
+ *
70
+ * **Instance-based Strategy:**
71
+ * Each RateLimiterMiddleware instance has its own bucket Map.
72
+ * This enables route-specific rate limiting:
73
+ * - GlobalRateLimiter → 1000 req/min for all routes
74
+ * - ApiRateLimiter → 100 req/min for /api/* routes
75
+ * - StrictRateLimiter → 5 req/min for /login route
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * // Global rate limiter (1000 req/min)
80
+ * @Middleware()
81
+ * export class GlobalRateLimiter extends RateLimiterMiddleware {
82
+ * constructor() {
83
+ * super({ capacity: 1000, refillRate: 1000/60 });
84
+ * }
85
+ * }
86
+ *
87
+ * // Strict rate limiter for auth endpoints (5 req/min)
88
+ * @Middleware()
89
+ * export class StrictRateLimiter extends RateLimiterMiddleware {
90
+ * constructor() {
91
+ * super({ capacity: 5, refillRate: 5/60 });
92
+ * }
93
+ * }
94
+ * ```
95
+ */
96
+ export class RateLimiterMiddleware extends MiddlewareService {
97
+ /**
98
+ * Instance-specific bucket storage
99
+ * Key: Client identifier (IP, user ID, etc.)
100
+ * Value: Token bucket state
101
+ */
102
+ buckets = new Map();
103
+ /**
104
+ * Maximum token capacity
105
+ */
106
+ capacity;
107
+ /**
108
+ * Token refill rate (tokens per second)
109
+ */
110
+ refillRate;
111
+ /**
112
+ * Client key generator function
113
+ */
114
+ keyGenerator;
115
+ /**
116
+ * Rate limit exceeded message
117
+ */
118
+ message;
119
+ /**
120
+ * Rate limit exceeded status code
121
+ */
122
+ statusCode;
123
+ /**
124
+ * Token cost per request
125
+ */
126
+ cost;
127
+ /**
128
+ * Skip predicate function
129
+ */
130
+ skip;
131
+ /**
132
+ * Cleanup interval timer ID
133
+ */
134
+ cleanupTimer;
135
+ /**
136
+ * Bucket TTL in milliseconds
137
+ */
138
+ bucketTTL;
139
+ /**
140
+ * Creates a new rate limiter middleware instance
141
+ *
142
+ * @param options - Rate limiter configuration options
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * @Middleware()
147
+ * export class CustomRateLimiter extends RateLimiterMiddleware {
148
+ * constructor() {
149
+ * super({
150
+ * capacity: 100,
151
+ * refillRate: 10,
152
+ * keyGenerator: (ctx) => ctx.state.user?.id || 'anonymous',
153
+ * message: 'Çok fazla istek gönderdiniz. Lütfen bekleyin.',
154
+ * skip: (ctx) => ctx.req.url === '/health'
155
+ * });
156
+ * }
157
+ * }
158
+ * ```
159
+ */
160
+ constructor(options = {}) {
161
+ super();
162
+ // Pre-process and store configuration
163
+ this.capacity = options.capacity ?? 100;
164
+ this.refillRate = options.refillRate ?? 10;
165
+ this.keyGenerator = options.keyGenerator ?? this.defaultKeyGenerator;
166
+ this.message = options.message ?? 'Rate limit exceeded. Please try again later.';
167
+ this.statusCode = options.statusCode ?? 429;
168
+ this.cost = options.cost ?? 1;
169
+ this.skip = options.skip;
170
+ this.bucketTTL = options.bucketTTL ?? 600000; // 10 minutes
171
+ // Setup automatic cleanup
172
+ const cleanupInterval = options.cleanupInterval ?? 60000; // 1 minute
173
+ if (cleanupInterval > 0) {
174
+ this.setupCleanup(cleanupInterval);
175
+ }
176
+ }
177
+ /**
178
+ * Rate limiter middleware handler
179
+ *
180
+ * Execution flow:
181
+ * 1. Check if request should be skipped
182
+ * 2. Generate client key (IP, user ID, etc.)
183
+ * 3. Get or create bucket for client
184
+ * 4. Refill tokens based on elapsed time
185
+ * 5. Check if sufficient tokens available
186
+ * 6. Consume tokens and continue, or return 429
187
+ *
188
+ * @param context - Ergenecore context wrapper
189
+ * @param next - Function to call next middleware or handler
190
+ * @returns Response for rate limited requests, or result of next() for allowed requests
191
+ */
192
+ async handle(context, next) {
193
+ // Check skip predicate
194
+ if (this.skip && this.skip(context)) {
195
+ return await next();
196
+ }
197
+ // Generate client key
198
+ const key = this.keyGenerator(context);
199
+ // Get or create bucket
200
+ let bucket = this.buckets.get(key);
201
+ if (!bucket) {
202
+ bucket = {
203
+ tokens: this.capacity,
204
+ lastRefill: Date.now(),
205
+ };
206
+ this.buckets.set(key, bucket);
207
+ }
208
+ // Refill tokens based on elapsed time
209
+ const now = Date.now();
210
+ const elapsedSeconds = (now - bucket.lastRefill) / 1000;
211
+ const tokensToAdd = elapsedSeconds * this.refillRate;
212
+ // Update bucket state
213
+ bucket.tokens = Math.min(bucket.tokens + tokensToAdd, this.capacity);
214
+ bucket.lastRefill = now;
215
+ // Calculate cost for this request
216
+ const requestCost = typeof this.cost === 'function' ? this.cost(context) : this.cost;
217
+ // Check if sufficient tokens available
218
+ if (bucket.tokens < requestCost) {
219
+ // Rate limit exceeded
220
+ const retryAfter = Math.ceil((requestCost - bucket.tokens) / this.refillRate);
221
+ return new Response(this.message, {
222
+ status: this.statusCode,
223
+ headers: {
224
+ 'Retry-After': String(retryAfter),
225
+ 'X-RateLimit-Limit': String(Math.floor(this.refillRate * 60)), // Requests per minute
226
+ 'X-RateLimit-Remaining': String(Math.floor(bucket.tokens)),
227
+ 'X-RateLimit-Reset': String(Math.floor(bucket.lastRefill / 1000 + retryAfter)),
228
+ },
229
+ });
230
+ }
231
+ // Consume tokens
232
+ bucket.tokens -= requestCost;
233
+ // Set rate limit headers
234
+ context.setResponseHeader('X-RateLimit-Limit', String(Math.floor(this.refillRate * 60)));
235
+ context.setResponseHeader('X-RateLimit-Remaining', String(Math.floor(bucket.tokens)));
236
+ context.setResponseHeader('X-RateLimit-Reset', String(Math.floor(bucket.lastRefill / 1000 + 60)));
237
+ // Continue to next middleware/handler
238
+ return await next();
239
+ }
240
+ /**
241
+ * Cleanup resources (stop cleanup timer)
242
+ *
243
+ * Call this when shutting down the application.
244
+ */
245
+ destroy() {
246
+ if (this.cleanupTimer) {
247
+ clearInterval(this.cleanupTimer);
248
+ }
249
+ }
250
+ /**
251
+ * Get current bucket state for a client (for testing/debugging)
252
+ *
253
+ * @param key - Client identifier
254
+ * @returns Bucket state or undefined
255
+ */
256
+ getBucketState(key) {
257
+ return this.buckets.get(key);
258
+ }
259
+ /**
260
+ * Clear all buckets (for testing)
261
+ */
262
+ clearBuckets() {
263
+ this.buckets.clear();
264
+ }
265
+ /**
266
+ * Default key generator - uses X-Forwarded-For or falls back to 'unknown'
267
+ *
268
+ * @param context - Ergenecore context
269
+ * @returns Client identifier
270
+ */
271
+ defaultKeyGenerator(context) {
272
+ return context.req.headers.get('x-forwarded-for') || context.req.headers.get('cf-connecting-ip') || 'unknown';
273
+ }
274
+ /**
275
+ * Setup automatic cleanup of inactive buckets
276
+ *
277
+ * Prevents memory leaks by removing buckets that haven't been accessed recently.
278
+ *
279
+ * @param interval - Cleanup interval in milliseconds
280
+ */
281
+ setupCleanup(interval) {
282
+ this.cleanupTimer = setInterval(() => {
283
+ const now = Date.now();
284
+ const keysToDelete = [];
285
+ // Find inactive buckets
286
+ for (const [key, bucket] of this.buckets.entries()) {
287
+ if (now - bucket.lastRefill > this.bucketTTL) {
288
+ keysToDelete.push(key);
289
+ }
290
+ }
291
+ // Remove inactive buckets
292
+ for (const key of keysToDelete) {
293
+ this.buckets.delete(key);
294
+ }
295
+ }, interval);
296
+ // Unref timer to allow process to exit
297
+ this.cleanupTimer.unref();
298
+ }
299
+ }
300
+ //# sourceMappingURL=RateLimiterMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Ergenecore handler type definitions
3
+ *
4
+ * Type aliases for route handlers and other function signatures
5
+ * specific to the Ergenecore adapter.
6
+ *
7
+ * @module types/Handler
8
+ */
9
+ import type { Context } from '../ErgenecoreContextWrapper';
10
+ /**
11
+ * Ergenecore route handler function
12
+ *
13
+ * Asynchronous function that receives an ErgenecoreContextWrapper
14
+ * and returns a Response or JSON-serializable data.
15
+ *
16
+ * @param ctx - Ergenecore context wrapper (ErgenecoreContextWrapper)
17
+ * @returns Response object or JSON-serializable data
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const handler: ErgenecoreHandler = async (ctx) => {
22
+ * const id = ctx.getParam('id');
23
+ * const user = await db.users.findById(id);
24
+ * return ctx.send(user);
25
+ * };
26
+ * ```
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const handler: ErgenecoreHandler = async (ctx) => {
31
+ * // Return Response directly
32
+ * return new Response('Hello World', {
33
+ * status: 200,
34
+ * headers: { 'Content-Type': 'text/plain' }
35
+ * });
36
+ * };
37
+ * ```
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const handler: ErgenecoreHandler = async (ctx) => {
42
+ * // Return JSON-serializable data
43
+ * return { message: 'Success', data: { id: 123 } };
44
+ * };
45
+ * ```
46
+ */
47
+ export type ErgenecoreHandler = (ctx: Context) => Promise<Response | any>;
48
+ /**
49
+ * Ergenecore middleware next function
50
+ *
51
+ * Asynchronous function called within middleware to proceed
52
+ * to the next middleware in the chain or the route handler.
53
+ *
54
+ * @returns Promise that resolves when the next middleware/handler completes
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const middleware = async (ctx: Context, next: ErgenecoreNext) => {
59
+ * console.log('Before handler');
60
+ * await next();
61
+ * console.log('After handler');
62
+ * };
63
+ * ```
64
+ */
65
+ export type ErgenecoreNext = () => Promise<void>;