@asenajs/hono-adapter 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/HonoAdapter.d.ts +222 -14
- package/dist/lib/HonoAdapter.js +672 -51
- package/dist/lib/HonoAdapter.js.map +1 -1
- package/dist/lib/HonoContextWrapper.d.ts +4 -5
- package/dist/lib/HonoContextWrapper.js +8 -14
- package/dist/lib/HonoContextWrapper.js.map +1 -1
- package/dist/lib/HonoWebsocketAdapter.d.ts +43 -14
- package/dist/lib/HonoWebsocketAdapter.js +175 -53
- package/dist/lib/HonoWebsocketAdapter.js.map +1 -1
- package/dist/lib/defaults/ConfigService.d.ts +2 -2
- package/dist/lib/defaults/Context.d.ts +3 -3
- package/dist/lib/defaults/MiddlewareService.d.ts +3 -3
- package/dist/lib/defaults/MiddlewareService.js +1 -1
- package/dist/lib/defaults/MiddlewareService.js.map +1 -1
- package/dist/lib/defaults/StaticServeService.d.ts +5 -0
- package/dist/lib/defaults/StaticServeService.js +4 -0
- package/dist/lib/defaults/StaticServeService.js.map +1 -0
- package/dist/lib/defaults/ValidationService.d.ts +3 -3
- package/dist/lib/defaults/ValidationService.js.map +1 -1
- package/dist/lib/defaults/index.d.ts +7 -4
- package/dist/lib/defaults/index.js +7 -4
- package/dist/lib/defaults/index.js.map +1 -1
- package/dist/lib/middlewares/CorsMiddleware.d.ts +148 -0
- package/dist/lib/middlewares/CorsMiddleware.js +169 -0
- package/dist/lib/middlewares/CorsMiddleware.js.map +1 -0
- package/dist/lib/middlewares/RateLimiterMiddleware.d.ts +329 -0
- package/dist/lib/middlewares/RateLimiterMiddleware.js +301 -0
- package/dist/lib/middlewares/RateLimiterMiddleware.js.map +1 -0
- package/dist/lib/types/HonoHandler.d.ts +10 -2
- package/dist/lib/types/StaticServeExtras.d.ts +6 -0
- package/dist/lib/types/StaticServeExtras.js +2 -0
- package/dist/lib/types/StaticServeExtras.js.map +1 -0
- package/dist/lib/types/index.d.ts +3 -2
- package/dist/lib/types/index.js +3 -2
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/utils/createHonoAdapter.d.ts +2 -2
- package/dist/lib/utils/createHonoAdapter.js +2 -2
- package/dist/lib/utils/createHonoAdapter.js.map +1 -1
- package/dist/lib/utils/middlewareParser.d.ts +9 -3
- package/dist/lib/utils/middlewareParser.js +30 -4
- package/dist/lib/utils/middlewareParser.js.map +1 -1
- package/package.json +17 -11
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from './Context';
|
|
2
|
+
export * from './MiddlewareService';
|
|
3
|
+
export * from './ValidationService';
|
|
4
|
+
export * from './ConfigService';
|
|
5
|
+
export * from './StaticServeService';
|
|
6
|
+
export * from '../middlewares/CorsMiddleware';
|
|
7
|
+
export * from '../middlewares/RateLimiterMiddleware';
|
|
5
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/defaults/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/defaults/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORS Middleware for Hono Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles Cross-Origin Resource Sharing (CORS) by:
|
|
5
|
+
* - Setting appropriate CORS headers on responses
|
|
6
|
+
* - Handling preflight OPTIONS requests
|
|
7
|
+
* - Supporting origin validation
|
|
8
|
+
* - Uses Hono's native header system
|
|
9
|
+
*
|
|
10
|
+
* @module defaults/CorsMiddleware
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { Middleware } from '@asenajs/asena/server';
|
|
15
|
+
* import { CorsMiddleware } from '@asenajs/hono-adapter';
|
|
16
|
+
*
|
|
17
|
+
* @Middleware()
|
|
18
|
+
* export class MyCorsMiddleware extends CorsMiddleware {
|
|
19
|
+
* constructor() {
|
|
20
|
+
* super({
|
|
21
|
+
* origin: '*', // or ['https://example.com']
|
|
22
|
+
* methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
23
|
+
* credentials: true
|
|
24
|
+
* });
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
import { type Context, MiddlewareService } from '../defaults';
|
|
30
|
+
/**
|
|
31
|
+
* CORS configuration options
|
|
32
|
+
*/
|
|
33
|
+
export interface CorsOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Allowed origins
|
|
36
|
+
* - Use '*' to allow all origins
|
|
37
|
+
* - Use string to allow a single specific origin
|
|
38
|
+
* - Use string[] to allow specific origins
|
|
39
|
+
* - Use function for dynamic origin validation
|
|
40
|
+
*
|
|
41
|
+
* @default '*'
|
|
42
|
+
*/
|
|
43
|
+
origin?: '*' | string | string[] | ((origin: string) => boolean);
|
|
44
|
+
/**
|
|
45
|
+
* Allowed HTTP methods
|
|
46
|
+
*
|
|
47
|
+
* @default ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
|
|
48
|
+
*/
|
|
49
|
+
methods?: string[];
|
|
50
|
+
/**
|
|
51
|
+
* Allowed headers
|
|
52
|
+
*
|
|
53
|
+
* @default ['Content-Type', 'Authorization']
|
|
54
|
+
*/
|
|
55
|
+
allowedHeaders?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* Exposed headers (visible to client JavaScript)
|
|
58
|
+
*
|
|
59
|
+
* @default []
|
|
60
|
+
*/
|
|
61
|
+
exposedHeaders?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Allow credentials (cookies, authorization headers, TLS client certificates)
|
|
64
|
+
*
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
credentials?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Preflight cache duration in seconds
|
|
70
|
+
*
|
|
71
|
+
* @default 86400 (24 hours)
|
|
72
|
+
*/
|
|
73
|
+
maxAge?: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* CORS Middleware
|
|
77
|
+
*
|
|
78
|
+
* Implements CORS (Cross-Origin Resource Sharing) for Hono adapter.
|
|
79
|
+
* Extend this class and configure it via constructor to customize CORS behavior.
|
|
80
|
+
*
|
|
81
|
+
* **Performance Optimizations:**
|
|
82
|
+
* - Lazy header allocation (only when needed)
|
|
83
|
+
* - Pre-joined header strings for common cases
|
|
84
|
+
* - Fast origin validation
|
|
85
|
+
* - Minimal string allocations
|
|
86
|
+
*
|
|
87
|
+
* **CORS Flow:**
|
|
88
|
+
* 1. Check if request has Origin header (if not, skip CORS)
|
|
89
|
+
* 2. Validate origin against allowed origins
|
|
90
|
+
* 3. Handle preflight OPTIONS request → return 204 immediately
|
|
91
|
+
* 4. For other requests → set CORS headers and call next()
|
|
92
|
+
*/
|
|
93
|
+
export declare class CorsMiddleware extends MiddlewareService {
|
|
94
|
+
private readonly origin;
|
|
95
|
+
private readonly methods;
|
|
96
|
+
private readonly allowedHeaders;
|
|
97
|
+
private readonly exposedHeaders;
|
|
98
|
+
private readonly credentials;
|
|
99
|
+
private readonly maxAge;
|
|
100
|
+
/**
|
|
101
|
+
* Creates a new CORS middleware instance
|
|
102
|
+
*
|
|
103
|
+
* @param options - CORS configuration options
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* @Middleware()
|
|
108
|
+
* export class CustomCors extends CorsMiddleware {
|
|
109
|
+
* constructor() {
|
|
110
|
+
* super({
|
|
111
|
+
* origin: ['https://example.com', 'https://app.example.com'],
|
|
112
|
+
* methods: ['GET', 'POST'],
|
|
113
|
+
* credentials: true,
|
|
114
|
+
* maxAge: 3600
|
|
115
|
+
* });
|
|
116
|
+
* }
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
constructor(options?: CorsOptions);
|
|
121
|
+
/**
|
|
122
|
+
* CORS middleware handler
|
|
123
|
+
*
|
|
124
|
+
* Execution flow:
|
|
125
|
+
* 1. Check if Origin header present (if not, skip CORS)
|
|
126
|
+
* 2. Validate origin
|
|
127
|
+
* 3. Handle preflight OPTIONS → return 204 No Content
|
|
128
|
+
* 4. Set CORS headers for actual request → call next()
|
|
129
|
+
*
|
|
130
|
+
* @param context - Hono context wrapper
|
|
131
|
+
* @param next - Function to call next middleware or handler
|
|
132
|
+
* @returns Response for preflight, or result of next() for actual requests
|
|
133
|
+
*/
|
|
134
|
+
handle(context: Context, next: () => Promise<void>): Promise<any>;
|
|
135
|
+
/**
|
|
136
|
+
* Determines the allowed origin value for the response
|
|
137
|
+
*
|
|
138
|
+
* Performance:
|
|
139
|
+
* - Fast path for '*' (most common case)
|
|
140
|
+
* - String comparison for single origin
|
|
141
|
+
* - Array.includes() for string array (O(n) but n is typically small)
|
|
142
|
+
* - Function call for dynamic validation
|
|
143
|
+
*
|
|
144
|
+
* @param requestOrigin - Origin from request header
|
|
145
|
+
* @returns Allowed origin string or null if not allowed
|
|
146
|
+
*/
|
|
147
|
+
private getAllowedOrigin;
|
|
148
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORS Middleware for Hono Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles Cross-Origin Resource Sharing (CORS) by:
|
|
5
|
+
* - Setting appropriate CORS headers on responses
|
|
6
|
+
* - Handling preflight OPTIONS requests
|
|
7
|
+
* - Supporting origin validation
|
|
8
|
+
* - Uses Hono's native header system
|
|
9
|
+
*
|
|
10
|
+
* @module defaults/CorsMiddleware
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { Middleware } from '@asenajs/asena/server';
|
|
15
|
+
* import { CorsMiddleware } from '@asenajs/hono-adapter';
|
|
16
|
+
*
|
|
17
|
+
* @Middleware()
|
|
18
|
+
* export class MyCorsMiddleware extends CorsMiddleware {
|
|
19
|
+
* constructor() {
|
|
20
|
+
* super({
|
|
21
|
+
* origin: '*', // or ['https://example.com']
|
|
22
|
+
* methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
23
|
+
* credentials: true
|
|
24
|
+
* });
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
import { MiddlewareService } from '../defaults';
|
|
30
|
+
/**
|
|
31
|
+
* CORS Middleware
|
|
32
|
+
*
|
|
33
|
+
* Implements CORS (Cross-Origin Resource Sharing) for Hono adapter.
|
|
34
|
+
* Extend this class and configure it via constructor to customize CORS behavior.
|
|
35
|
+
*
|
|
36
|
+
* **Performance Optimizations:**
|
|
37
|
+
* - Lazy header allocation (only when needed)
|
|
38
|
+
* - Pre-joined header strings for common cases
|
|
39
|
+
* - Fast origin validation
|
|
40
|
+
* - Minimal string allocations
|
|
41
|
+
*
|
|
42
|
+
* **CORS Flow:**
|
|
43
|
+
* 1. Check if request has Origin header (if not, skip CORS)
|
|
44
|
+
* 2. Validate origin against allowed origins
|
|
45
|
+
* 3. Handle preflight OPTIONS request → return 204 immediately
|
|
46
|
+
* 4. For other requests → set CORS headers and call next()
|
|
47
|
+
*/
|
|
48
|
+
export class CorsMiddleware extends MiddlewareService {
|
|
49
|
+
origin;
|
|
50
|
+
methods;
|
|
51
|
+
allowedHeaders;
|
|
52
|
+
exposedHeaders;
|
|
53
|
+
credentials;
|
|
54
|
+
maxAge;
|
|
55
|
+
/**
|
|
56
|
+
* Creates a new CORS middleware instance
|
|
57
|
+
*
|
|
58
|
+
* @param options - CORS configuration options
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* @Middleware()
|
|
63
|
+
* export class CustomCors extends CorsMiddleware {
|
|
64
|
+
* constructor() {
|
|
65
|
+
* super({
|
|
66
|
+
* origin: ['https://example.com', 'https://app.example.com'],
|
|
67
|
+
* methods: ['GET', 'POST'],
|
|
68
|
+
* credentials: true,
|
|
69
|
+
* maxAge: 3600
|
|
70
|
+
* });
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
constructor(options = {}) {
|
|
76
|
+
super();
|
|
77
|
+
// Pre-process and store configuration (avoid runtime processing)
|
|
78
|
+
this.origin = options.origin ?? '*';
|
|
79
|
+
this.methods = (options.methods ?? ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']).join(', ');
|
|
80
|
+
this.allowedHeaders = (options.allowedHeaders ?? ['Content-Type', 'Authorization']).join(', ');
|
|
81
|
+
this.exposedHeaders = (options.exposedHeaders ?? []).join(', ');
|
|
82
|
+
this.credentials = options.credentials ?? false;
|
|
83
|
+
this.maxAge = String(options.maxAge ?? 86400); // 24 hours default
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* CORS middleware handler
|
|
87
|
+
*
|
|
88
|
+
* Execution flow:
|
|
89
|
+
* 1. Check if Origin header present (if not, skip CORS)
|
|
90
|
+
* 2. Validate origin
|
|
91
|
+
* 3. Handle preflight OPTIONS → return 204 No Content
|
|
92
|
+
* 4. Set CORS headers for actual request → call next()
|
|
93
|
+
*
|
|
94
|
+
* @param context - Hono context wrapper
|
|
95
|
+
* @param next - Function to call next middleware or handler
|
|
96
|
+
* @returns Response for preflight, or result of next() for actual requests
|
|
97
|
+
*/
|
|
98
|
+
async handle(context, next) {
|
|
99
|
+
const origin = context.req.header('Origin');
|
|
100
|
+
// If no Origin header, this is not a CORS request → skip
|
|
101
|
+
if (!origin) {
|
|
102
|
+
return await next();
|
|
103
|
+
}
|
|
104
|
+
// Validate origin
|
|
105
|
+
const allowedOrigin = this.getAllowedOrigin(origin);
|
|
106
|
+
if (!allowedOrigin) {
|
|
107
|
+
// Origin not allowed → block request
|
|
108
|
+
return new Response('CORS: Origin not allowed', { status: 403 });
|
|
109
|
+
}
|
|
110
|
+
// Set CORS headers using Hono's context (access via context.context for HonoContextWrapper)
|
|
111
|
+
const honoContext = context.context;
|
|
112
|
+
honoContext.header('Access-Control-Allow-Origin', allowedOrigin);
|
|
113
|
+
if (this.credentials) {
|
|
114
|
+
honoContext.header('Access-Control-Allow-Credentials', 'true');
|
|
115
|
+
}
|
|
116
|
+
if (this.exposedHeaders) {
|
|
117
|
+
honoContext.header('Access-Control-Expose-Headers', this.exposedHeaders);
|
|
118
|
+
}
|
|
119
|
+
// Handle preflight OPTIONS request
|
|
120
|
+
if (context.req.method === 'OPTIONS') {
|
|
121
|
+
// Build headers object for preflight response
|
|
122
|
+
const headers = {
|
|
123
|
+
'Access-Control-Allow-Origin': allowedOrigin,
|
|
124
|
+
'Access-Control-Allow-Methods': this.methods,
|
|
125
|
+
'Access-Control-Allow-Headers': this.allowedHeaders,
|
|
126
|
+
'Access-Control-Max-Age': this.maxAge,
|
|
127
|
+
};
|
|
128
|
+
if (this.credentials) {
|
|
129
|
+
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
130
|
+
}
|
|
131
|
+
// Return 204 No Content for preflight
|
|
132
|
+
return new Response(null, { status: 204, headers });
|
|
133
|
+
}
|
|
134
|
+
// For actual requests, continue to handler
|
|
135
|
+
return await next();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Determines the allowed origin value for the response
|
|
139
|
+
*
|
|
140
|
+
* Performance:
|
|
141
|
+
* - Fast path for '*' (most common case)
|
|
142
|
+
* - String comparison for single origin
|
|
143
|
+
* - Array.includes() for string array (O(n) but n is typically small)
|
|
144
|
+
* - Function call for dynamic validation
|
|
145
|
+
*
|
|
146
|
+
* @param requestOrigin - Origin from request header
|
|
147
|
+
* @returns Allowed origin string or null if not allowed
|
|
148
|
+
*/
|
|
149
|
+
getAllowedOrigin(requestOrigin) {
|
|
150
|
+
// Fast path: Allow all origins
|
|
151
|
+
if (this.origin === '*') {
|
|
152
|
+
return '*';
|
|
153
|
+
}
|
|
154
|
+
// Single origin string
|
|
155
|
+
if (typeof this.origin === 'string') {
|
|
156
|
+
return this.origin === requestOrigin ? requestOrigin : null;
|
|
157
|
+
}
|
|
158
|
+
// Array of specific origins
|
|
159
|
+
if (Array.isArray(this.origin)) {
|
|
160
|
+
return this.origin.includes(requestOrigin) ? requestOrigin : null;
|
|
161
|
+
}
|
|
162
|
+
// Function-based validation
|
|
163
|
+
if (typeof this.origin === 'function') {
|
|
164
|
+
return this.origin(requestOrigin) ? requestOrigin : null;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=CorsMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CorsMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/CorsMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter Middleware for Hono Adapter
|
|
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/hono-adapter';
|
|
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, MiddlewareService } from '../defaults';
|
|
50
|
+
/**
|
|
51
|
+
* Token bucket for a single client
|
|
52
|
+
*/
|
|
53
|
+
interface Bucket {
|
|
54
|
+
/**
|
|
55
|
+
* Current number of tokens available
|
|
56
|
+
*/
|
|
57
|
+
tokens: number;
|
|
58
|
+
/**
|
|
59
|
+
* Last refill timestamp (milliseconds)
|
|
60
|
+
*/
|
|
61
|
+
lastRefill: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Rate limiter configuration options
|
|
65
|
+
*/
|
|
66
|
+
export interface RateLimiterOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Maximum token capacity (burst size)
|
|
69
|
+
*
|
|
70
|
+
* This defines how many requests can be made in a burst.
|
|
71
|
+
* Example: capacity = 100 means 100 requests can be made instantly if bucket is full.
|
|
72
|
+
*
|
|
73
|
+
* @default 100
|
|
74
|
+
*/
|
|
75
|
+
capacity?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Token refill rate (tokens per second)
|
|
78
|
+
*
|
|
79
|
+
* This defines the sustained request rate.
|
|
80
|
+
* Example: refillRate = 10 means 10 requests per second sustained rate.
|
|
81
|
+
*
|
|
82
|
+
* Common patterns:
|
|
83
|
+
* - 10 req/s → refillRate: 10
|
|
84
|
+
* - 100 req/min → refillRate: 100/60 ≈ 1.67
|
|
85
|
+
* - 1000 req/hour → refillRate: 1000/3600 ≈ 0.28
|
|
86
|
+
*
|
|
87
|
+
* @default 10
|
|
88
|
+
*/
|
|
89
|
+
refillRate?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Custom key generator function
|
|
92
|
+
*
|
|
93
|
+
* By default, uses IP address from X-Forwarded-For header or CF-Connecting-IP.
|
|
94
|
+
* You can customize this to use user ID, API key, or any other identifier.
|
|
95
|
+
*
|
|
96
|
+
* @default (ctx) => ctx.req.headers.get('x-forwarded-for') || ctx.req.headers.get('cf-connecting-ip') || 'unknown'
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* // Rate limit by user ID
|
|
101
|
+
* keyGenerator: (ctx) => ctx.getValue('user')?.id || 'anonymous'
|
|
102
|
+
*
|
|
103
|
+
* // Rate limit by API key
|
|
104
|
+
* keyGenerator: (ctx) => ctx.req.headers.get('x-api-key') || 'unknown'
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
keyGenerator?: (context: Context) => string;
|
|
108
|
+
/**
|
|
109
|
+
* Custom message for rate limit exceeded response
|
|
110
|
+
*
|
|
111
|
+
* @default 'Rate limit exceeded. Please try again later.'
|
|
112
|
+
*/
|
|
113
|
+
message?: string;
|
|
114
|
+
/**
|
|
115
|
+
* HTTP status code for rate limit exceeded response
|
|
116
|
+
*
|
|
117
|
+
* @default 429
|
|
118
|
+
*/
|
|
119
|
+
statusCode?: number;
|
|
120
|
+
/**
|
|
121
|
+
* Token cost per request
|
|
122
|
+
*
|
|
123
|
+
* Advanced feature: Different endpoints can cost different amounts of tokens.
|
|
124
|
+
*
|
|
125
|
+
* @default 1
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* // Expensive operations cost more tokens
|
|
130
|
+
* cost: (ctx) => ctx.req.url.includes('/search') ? 5 : 1
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
cost?: number | ((context: Context) => number);
|
|
134
|
+
/**
|
|
135
|
+
* Skip rate limiting based on context
|
|
136
|
+
*
|
|
137
|
+
* Useful for whitelisting certain IPs, paths, or authenticated users.
|
|
138
|
+
*
|
|
139
|
+
* @default undefined
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* // Skip rate limiting for health check
|
|
144
|
+
* skip: (ctx) => ctx.req.url === '/health'
|
|
145
|
+
*
|
|
146
|
+
* // Skip for admin users
|
|
147
|
+
* skip: (ctx) => ctx.getValue('user')?.role === 'admin'
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
skip?: (context: Context) => boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Bucket cleanup interval in milliseconds
|
|
153
|
+
*
|
|
154
|
+
* Periodically removes inactive buckets to prevent memory leaks.
|
|
155
|
+
* Set to 0 to disable automatic cleanup.
|
|
156
|
+
*
|
|
157
|
+
* @default 60000 (1 minute)
|
|
158
|
+
*/
|
|
159
|
+
cleanupInterval?: number;
|
|
160
|
+
/**
|
|
161
|
+
* Bucket TTL (time to live) in milliseconds
|
|
162
|
+
*
|
|
163
|
+
* Buckets that haven't been accessed for this duration will be removed during cleanup.
|
|
164
|
+
*
|
|
165
|
+
* @default 600000 (10 minutes)
|
|
166
|
+
*/
|
|
167
|
+
bucketTTL?: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Rate Limiter Middleware
|
|
171
|
+
*
|
|
172
|
+
* Implements Token Bucket algorithm for controlling request rate.
|
|
173
|
+
* Each middleware instance maintains its own bucket storage (instance-based strategy).
|
|
174
|
+
*
|
|
175
|
+
* **Token Bucket Algorithm:**
|
|
176
|
+
* 1. Each client has a bucket with a maximum capacity of tokens
|
|
177
|
+
* 2. Tokens refill at a constant rate (refillRate per second)
|
|
178
|
+
* 3. Each request consumes tokens (default: 1 token)
|
|
179
|
+
* 4. If bucket has insufficient tokens → 429 Too Many Requests
|
|
180
|
+
* 5. Bucket capacity never exceeds maximum (tokens don't accumulate indefinitely)
|
|
181
|
+
*
|
|
182
|
+
* **Performance Optimizations:**
|
|
183
|
+
* - O(1) bucket lookup (Map)
|
|
184
|
+
* - O(1) token calculation (pure math, no arrays)
|
|
185
|
+
* - Lazy token refill (calculated on-demand, not via setInterval)
|
|
186
|
+
* - Memory-efficient bucket structure (2 numbers per client)
|
|
187
|
+
* - Automatic cleanup of inactive buckets
|
|
188
|
+
*
|
|
189
|
+
* **Instance-based Strategy:**
|
|
190
|
+
* Each RateLimiterMiddleware instance has its own bucket Map.
|
|
191
|
+
* This enables route-specific rate limiting:
|
|
192
|
+
* - GlobalRateLimiter → 1000 req/min for all routes
|
|
193
|
+
* - ApiRateLimiter → 100 req/min for /api/* routes
|
|
194
|
+
* - StrictRateLimiter → 5 req/min for /login route
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* // Global rate limiter (1000 req/min)
|
|
199
|
+
* @Middleware()
|
|
200
|
+
* export class GlobalRateLimiter extends RateLimiterMiddleware {
|
|
201
|
+
* constructor() {
|
|
202
|
+
* super({ capacity: 1000, refillRate: 1000/60 });
|
|
203
|
+
* }
|
|
204
|
+
* }
|
|
205
|
+
*
|
|
206
|
+
* // Strict rate limiter for auth endpoints (5 req/min)
|
|
207
|
+
* @Middleware()
|
|
208
|
+
* export class StrictRateLimiter extends RateLimiterMiddleware {
|
|
209
|
+
* constructor() {
|
|
210
|
+
* super({ capacity: 5, refillRate: 5/60 });
|
|
211
|
+
* }
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export declare class RateLimiterMiddleware extends MiddlewareService {
|
|
216
|
+
/**
|
|
217
|
+
* Instance-specific bucket storage
|
|
218
|
+
* Key: Client identifier (IP, user ID, etc.)
|
|
219
|
+
* Value: Token bucket state
|
|
220
|
+
*/
|
|
221
|
+
private readonly buckets;
|
|
222
|
+
/**
|
|
223
|
+
* Maximum token capacity
|
|
224
|
+
*/
|
|
225
|
+
private readonly capacity;
|
|
226
|
+
/**
|
|
227
|
+
* Token refill rate (tokens per second)
|
|
228
|
+
*/
|
|
229
|
+
private readonly refillRate;
|
|
230
|
+
/**
|
|
231
|
+
* Client key generator function
|
|
232
|
+
*/
|
|
233
|
+
private readonly keyGenerator;
|
|
234
|
+
/**
|
|
235
|
+
* Rate limit exceeded message
|
|
236
|
+
*/
|
|
237
|
+
private readonly message;
|
|
238
|
+
/**
|
|
239
|
+
* Rate limit exceeded status code
|
|
240
|
+
*/
|
|
241
|
+
private readonly statusCode;
|
|
242
|
+
/**
|
|
243
|
+
* Token cost per request
|
|
244
|
+
*/
|
|
245
|
+
private readonly cost;
|
|
246
|
+
/**
|
|
247
|
+
* Skip predicate function
|
|
248
|
+
*/
|
|
249
|
+
private readonly skip?;
|
|
250
|
+
/**
|
|
251
|
+
* Cleanup interval timer ID
|
|
252
|
+
*/
|
|
253
|
+
private cleanupTimer?;
|
|
254
|
+
/**
|
|
255
|
+
* Bucket TTL in milliseconds
|
|
256
|
+
*/
|
|
257
|
+
private readonly bucketTTL;
|
|
258
|
+
/**
|
|
259
|
+
* Creates a new rate limiter middleware instance
|
|
260
|
+
*
|
|
261
|
+
* @param options - Rate limiter configuration options
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```typescript
|
|
265
|
+
* @Middleware()
|
|
266
|
+
* export class CustomRateLimiter extends RateLimiterMiddleware {
|
|
267
|
+
* constructor() {
|
|
268
|
+
* super({
|
|
269
|
+
* capacity: 100,
|
|
270
|
+
* refillRate: 10,
|
|
271
|
+
* keyGenerator: (ctx) => ctx.getValue('user')?.id || 'anonymous',
|
|
272
|
+
* message: 'Çok fazla istek gönderdiniz. Lütfen bekleyin.',
|
|
273
|
+
* skip: (ctx) => ctx.req.url === '/health'
|
|
274
|
+
* });
|
|
275
|
+
* }
|
|
276
|
+
* }
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
constructor(options?: RateLimiterOptions);
|
|
280
|
+
/**
|
|
281
|
+
* Rate limiter middleware handler
|
|
282
|
+
*
|
|
283
|
+
* Execution flow:
|
|
284
|
+
* 1. Check if request should be skipped
|
|
285
|
+
* 2. Generate client key (IP, user ID, etc.)
|
|
286
|
+
* 3. Get or create bucket for client
|
|
287
|
+
* 4. Refill tokens based on elapsed time
|
|
288
|
+
* 5. Check if sufficient tokens available
|
|
289
|
+
* 6. Consume tokens and continue, or return 429
|
|
290
|
+
*
|
|
291
|
+
* @param context - Hono context wrapper
|
|
292
|
+
* @param next - Function to call next middleware or handler
|
|
293
|
+
* @returns Response for rate limited requests, or result of next() for allowed requests
|
|
294
|
+
*/
|
|
295
|
+
handle(context: Context, next: () => Promise<void>): Promise<any>;
|
|
296
|
+
/**
|
|
297
|
+
* Cleanup resources (stop cleanup timer)
|
|
298
|
+
*
|
|
299
|
+
* Call this when shutting down the application.
|
|
300
|
+
*/
|
|
301
|
+
destroy(): void;
|
|
302
|
+
/**
|
|
303
|
+
* Get current bucket state for a client (for testing/debugging)
|
|
304
|
+
*
|
|
305
|
+
* @param key - Client identifier
|
|
306
|
+
* @returns Bucket state or undefined
|
|
307
|
+
*/
|
|
308
|
+
getBucketState(key: string): Bucket | undefined;
|
|
309
|
+
/**
|
|
310
|
+
* Clear all buckets (for testing)
|
|
311
|
+
*/
|
|
312
|
+
clearBuckets(): void;
|
|
313
|
+
/**
|
|
314
|
+
* Default key generator - uses X-Forwarded-For or CF-Connecting-IP or falls back to 'unknown'
|
|
315
|
+
*
|
|
316
|
+
* @param context - Hono context
|
|
317
|
+
* @returns Client identifier
|
|
318
|
+
*/
|
|
319
|
+
private defaultKeyGenerator;
|
|
320
|
+
/**
|
|
321
|
+
* Setup automatic cleanup of inactive buckets
|
|
322
|
+
*
|
|
323
|
+
* Prevents memory leaks by removing buckets that haven't been accessed recently.
|
|
324
|
+
*
|
|
325
|
+
* @param interval - Cleanup interval in milliseconds
|
|
326
|
+
*/
|
|
327
|
+
private setupCleanup;
|
|
328
|
+
}
|
|
329
|
+
export {};
|