@asenajs/hono-adapter 1.1.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 +211 -5
- package/dist/lib/HonoAdapter.js +622 -56
- package/dist/lib/HonoAdapter.js.map +1 -1
- package/dist/lib/HonoContextWrapper.d.ts +1 -2
- package/dist/lib/HonoWebsocketAdapter.d.ts +37 -4
- package/dist/lib/HonoWebsocketAdapter.js +162 -10
- package/dist/lib/HonoWebsocketAdapter.js.map +1 -1
- package/dist/lib/defaults/index.d.ts +2 -0
- package/dist/lib/defaults/index.js +2 -0
- 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 +8 -0
- package/dist/lib/types/StaticServeExtras.d.ts +2 -0
- package/dist/lib/utils/middlewareParser.d.ts +6 -0
- package/dist/lib/utils/middlewareParser.js +28 -2
- package/dist/lib/utils/middlewareParser.js.map +1 -1
- package/package.json +13 -10
|
@@ -0,0 +1,301 @@
|
|
|
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 { MiddlewareService } from '../defaults';
|
|
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.getValue('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 - Hono 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 using Hono's context
|
|
234
|
+
const honoContext = context.context;
|
|
235
|
+
honoContext.header('X-RateLimit-Limit', String(Math.floor(this.refillRate * 60)));
|
|
236
|
+
honoContext.header('X-RateLimit-Remaining', String(Math.floor(bucket.tokens)));
|
|
237
|
+
honoContext.header('X-RateLimit-Reset', String(Math.floor(bucket.lastRefill / 1000 + 60)));
|
|
238
|
+
// Continue to next middleware/handler
|
|
239
|
+
return await next();
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Cleanup resources (stop cleanup timer)
|
|
243
|
+
*
|
|
244
|
+
* Call this when shutting down the application.
|
|
245
|
+
*/
|
|
246
|
+
destroy() {
|
|
247
|
+
if (this.cleanupTimer) {
|
|
248
|
+
clearInterval(this.cleanupTimer);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get current bucket state for a client (for testing/debugging)
|
|
253
|
+
*
|
|
254
|
+
* @param key - Client identifier
|
|
255
|
+
* @returns Bucket state or undefined
|
|
256
|
+
*/
|
|
257
|
+
getBucketState(key) {
|
|
258
|
+
return this.buckets.get(key);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Clear all buckets (for testing)
|
|
262
|
+
*/
|
|
263
|
+
clearBuckets() {
|
|
264
|
+
this.buckets.clear();
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Default key generator - uses X-Forwarded-For or CF-Connecting-IP or falls back to 'unknown'
|
|
268
|
+
*
|
|
269
|
+
* @param context - Hono context
|
|
270
|
+
* @returns Client identifier
|
|
271
|
+
*/
|
|
272
|
+
defaultKeyGenerator(context) {
|
|
273
|
+
return context.req.header('x-forwarded-for') || context.req.header('cf-connecting-ip') || 'unknown';
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Setup automatic cleanup of inactive buckets
|
|
277
|
+
*
|
|
278
|
+
* Prevents memory leaks by removing buckets that haven't been accessed recently.
|
|
279
|
+
*
|
|
280
|
+
* @param interval - Cleanup interval in milliseconds
|
|
281
|
+
*/
|
|
282
|
+
setupCleanup(interval) {
|
|
283
|
+
this.cleanupTimer = setInterval(() => {
|
|
284
|
+
const now = Date.now();
|
|
285
|
+
const keysToDelete = [];
|
|
286
|
+
// Find inactive buckets
|
|
287
|
+
for (const [key, bucket] of this.buckets.entries()) {
|
|
288
|
+
if (now - bucket.lastRefill > this.bucketTTL) {
|
|
289
|
+
keysToDelete.push(key);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Remove inactive buckets
|
|
293
|
+
for (const key of keysToDelete) {
|
|
294
|
+
this.buckets.delete(key);
|
|
295
|
+
}
|
|
296
|
+
}, interval);
|
|
297
|
+
// Unref timer to allow process to exit
|
|
298
|
+
this.cleanupTimer.unref();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
//# 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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"}
|
|
@@ -2,3 +2,11 @@ import type { AsenaHandler, ErrorHandler } from '@asenajs/asena/adapter';
|
|
|
2
2
|
import type { Context } from '../defaults';
|
|
3
3
|
export type HonoHandler = AsenaHandler<Context>;
|
|
4
4
|
export type HonoErrorHandler = ErrorHandler<Context>;
|
|
5
|
+
/**
|
|
6
|
+
* Typed route handler for better type inference
|
|
7
|
+
*/
|
|
8
|
+
export type TypedHonoHandler<TBody = unknown, TResponse = any> = (context: Context & {
|
|
9
|
+
req: {
|
|
10
|
+
json: () => Promise<TBody>;
|
|
11
|
+
};
|
|
12
|
+
}) => Promise<TResponse> | TResponse;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { MiddlewareHandler } from 'hono';
|
|
2
2
|
import type { Context as HonoAdapterContext } from '../defaults';
|
|
3
3
|
import type { BaseMiddleware } from '@asenajs/asena/adapter';
|
|
4
|
+
/**
|
|
5
|
+
* Parses Asena middlewares to Hono middleware format with optimizations
|
|
6
|
+
* - Reuses context wrappers when possible
|
|
7
|
+
* - Handles middleware return values (false stops pipeline)
|
|
8
|
+
* - Adds error handling
|
|
9
|
+
*/
|
|
4
10
|
export declare const middlewareParser: (middlewares: BaseMiddleware<HonoAdapterContext>[]) => MiddlewareHandler[];
|
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
import { HonoContextWrapper } from '../HonoContextWrapper';
|
|
2
2
|
import { createFactory } from 'hono/factory';
|
|
3
|
+
/**
|
|
4
|
+
* Parses Asena middlewares to Hono middleware format with optimizations
|
|
5
|
+
* - Reuses context wrappers when possible
|
|
6
|
+
* - Handles middleware return values (false stops pipeline)
|
|
7
|
+
* - Adds error handling
|
|
8
|
+
*/
|
|
3
9
|
export const middlewareParser = (middlewares) => {
|
|
4
10
|
const factory = createFactory();
|
|
5
11
|
return middlewares.map((middleware) => {
|
|
6
12
|
if (middleware.override) {
|
|
7
|
-
// @ts-ignore
|
|
13
|
+
// @ts-ignore - Allow override middleware to use raw Hono context
|
|
8
14
|
return (c, next) => middleware.handle(c, next);
|
|
9
15
|
}
|
|
10
16
|
return factory.createMiddleware(async (context, next) => {
|
|
11
|
-
|
|
17
|
+
// Reuse wrapper if already exists in context (performance optimization)
|
|
18
|
+
let wrapper = context.get('_asenaContextWrapper');
|
|
19
|
+
if (!wrapper) {
|
|
20
|
+
wrapper = new HonoContextWrapper(context);
|
|
21
|
+
context.set('_asenaContextWrapper', wrapper);
|
|
22
|
+
}
|
|
23
|
+
// eslint-disable-next-line no-useless-catch
|
|
24
|
+
try {
|
|
25
|
+
// Call middleware handler
|
|
26
|
+
const result = await middleware.handle(wrapper, next);
|
|
27
|
+
// If middleware returns false, stop the pipeline
|
|
28
|
+
if (result === false) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// If middleware didn't call next(), we call it here
|
|
32
|
+
// Note: Hono middleware should handle next() calls properly
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// Let Hono's error handler catch it
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
12
38
|
});
|
|
13
39
|
});
|
|
14
40
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middlewareParser.js","sourceRoot":"","sources":["../../../lib/utils/middlewareParser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAI7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,WAAiD,EAAuB,EAAE;IACzG,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;IAEhC,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;QACpC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;YACxB,
|
|
1
|
+
{"version":3,"file":"middlewareParser.js","sourceRoot":"","sources":["../../../lib/utils/middlewareParser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAI7C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,WAAiD,EAAuB,EAAE;IACzG,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;IAEhC,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;QACpC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;YACxB,iEAAiE;YACjE,OAAO,CAAC,CAAU,EAAE,IAAc,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACpE,CAAC;QAED,OAAO,OAAO,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAgB,EAAE,IAAU,EAAE,EAAE;YACrE,wEAAwE;YACxE,IAAI,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAuB,CAAC;YAExE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC;gBAC1C,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;YAC/C,CAAC;YAED,4CAA4C;YAC5C,IAAI,CAAC;gBACH,0BAA0B;gBAC1B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBAEtD,iDAAiD;gBACjD,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;oBACrB,OAAO;gBACT,CAAC;gBAED,oDAAoD;gBACpD,4DAA4D;YAC9D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,oCAAoC;gBACpC,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"README.md",
|
|
10
10
|
"LICENSE"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.
|
|
12
|
+
"version": "1.2.0",
|
|
13
13
|
"author": "LibirSoft",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -22,29 +22,32 @@
|
|
|
22
22
|
"test:coverage": "bun test --coverage",
|
|
23
23
|
"build": "bun run clean && tsc",
|
|
24
24
|
"clean": "rm -rf dist",
|
|
25
|
-
"prepublishOnly": "bun run build"
|
|
25
|
+
"prepublishOnly": "bun run build",
|
|
26
|
+
"benchmark": "bun run benchmark/run-benchmark.ts",
|
|
27
|
+
"benchmark:native": "bun run benchmark/native-hono.ts",
|
|
28
|
+
"benchmark:adapter": "bun run benchmark/hono-adapter.ts"
|
|
26
29
|
},
|
|
27
30
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@changesets/cli": "^2.29.3",
|
|
31
|
+
"@changesets/cli": "^2.29.7",
|
|
30
32
|
"@types/bun": "latest",
|
|
31
33
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
32
34
|
"eslint": "^8.57.1",
|
|
33
35
|
"eslint-config-alloy": "^5.1.2",
|
|
34
|
-
"eslint-config-prettier": "^9.1.
|
|
36
|
+
"eslint-config-prettier": "^9.1.2",
|
|
35
37
|
"eslint-plugin-alloy": "^1.2.1",
|
|
36
|
-
"eslint-plugin-import": "^2.
|
|
38
|
+
"eslint-plugin-import": "^2.32.0",
|
|
37
39
|
"eslint-plugin-n": "^16.6.2",
|
|
38
|
-
"eslint-plugin-prettier": "^5.4
|
|
40
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
39
41
|
"eslint-plugin-promise": "^6.6.0",
|
|
40
|
-
"prettier": "^3.
|
|
42
|
+
"prettier": "^3.6.2"
|
|
41
43
|
},
|
|
42
44
|
"peerDependencies": {
|
|
43
45
|
"typescript": "^5.8.2"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
48
|
+
"@asenajs/asena": "^0.4.0",
|
|
46
49
|
"@hono/zod-validator": "^0.4.3",
|
|
47
|
-
"hono": "^4.
|
|
48
|
-
"zod": "^3.
|
|
50
|
+
"hono": "^4.9.12",
|
|
51
|
+
"zod": "^3.25.76"
|
|
49
52
|
}
|
|
50
53
|
}
|