@carno.js/core 1.1.1 → 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.
Files changed (124) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +188 -188
  3. package/dist/Carno.js +45 -26
  4. package/dist/Carno.mjs +45 -26
  5. package/dist/bun/index.js +4 -4
  6. package/dist/bun/index.js.map +30 -29
  7. package/dist/compression/CompressionMiddleware.js +110 -0
  8. package/dist/compression/CompressionMiddleware.mjs +90 -0
  9. package/dist/index.js +3 -1
  10. package/dist/index.mjs +2 -0
  11. package/package.json +2 -2
  12. package/src/Carno.ts +728 -673
  13. package/src/DefaultRoutes.ts +34 -34
  14. package/src/cache/CacheDriver.ts +50 -50
  15. package/src/cache/CacheService.ts +139 -139
  16. package/src/cache/MemoryDriver.ts +104 -104
  17. package/src/cache/RedisDriver.ts +116 -116
  18. package/src/compiler/JITCompiler.ts +167 -167
  19. package/src/compression/CompressionMiddleware.ts +221 -0
  20. package/src/container/Container.ts +168 -168
  21. package/src/context/Context.ts +130 -130
  22. package/src/cors/CorsHandler.ts +145 -145
  23. package/src/decorators/Controller.ts +63 -63
  24. package/src/decorators/Inject.ts +16 -16
  25. package/src/decorators/Middleware.ts +22 -22
  26. package/src/decorators/Service.ts +18 -18
  27. package/src/decorators/methods.ts +58 -58
  28. package/src/decorators/params.ts +47 -47
  29. package/src/events/Lifecycle.ts +97 -97
  30. package/src/exceptions/HttpException.ts +99 -99
  31. package/src/index.ts +99 -95
  32. package/src/metadata.ts +46 -46
  33. package/src/middleware/CarnoMiddleware.ts +20 -14
  34. package/src/router/RadixRouter.ts +225 -225
  35. package/src/testing/TestHarness.ts +185 -185
  36. package/src/utils/Metadata.ts +43 -43
  37. package/src/utils/parseQuery.ts +161 -161
  38. package/src/validation/ValibotAdapter.ts +95 -95
  39. package/src/validation/ValidatorAdapter.ts +69 -69
  40. package/src/validation/ZodAdapter.ts +102 -102
  41. package/dist/Carno.d.js +0 -14
  42. package/dist/Carno.d.mjs +0 -1
  43. package/dist/DefaultRoutes.d.js +0 -13
  44. package/dist/DefaultRoutes.d.mjs +0 -0
  45. package/dist/cache/CacheDriver.d.js +0 -13
  46. package/dist/cache/CacheDriver.d.mjs +0 -0
  47. package/dist/cache/CacheService.d.js +0 -13
  48. package/dist/cache/CacheService.d.mjs +0 -0
  49. package/dist/cache/MemoryDriver.d.js +0 -13
  50. package/dist/cache/MemoryDriver.d.mjs +0 -0
  51. package/dist/cache/RedisDriver.d.js +0 -13
  52. package/dist/cache/RedisDriver.d.mjs +0 -0
  53. package/dist/compiler/JITCompiler.d.js +0 -13
  54. package/dist/compiler/JITCompiler.d.mjs +0 -0
  55. package/dist/container/Container.d.js +0 -13
  56. package/dist/container/Container.d.mjs +0 -0
  57. package/dist/context/Context.d.js +0 -13
  58. package/dist/context/Context.d.mjs +0 -0
  59. package/dist/cors/CorsHandler.d.js +0 -13
  60. package/dist/cors/CorsHandler.d.mjs +0 -0
  61. package/dist/decorators/Controller.d.js +0 -13
  62. package/dist/decorators/Controller.d.mjs +0 -0
  63. package/dist/decorators/Inject.d.js +0 -13
  64. package/dist/decorators/Inject.d.mjs +0 -0
  65. package/dist/decorators/Middleware.d.js +0 -13
  66. package/dist/decorators/Middleware.d.mjs +0 -0
  67. package/dist/decorators/Service.d.js +0 -13
  68. package/dist/decorators/Service.d.mjs +0 -0
  69. package/dist/decorators/methods.d.js +0 -13
  70. package/dist/decorators/methods.d.mjs +0 -0
  71. package/dist/decorators/params.d.js +0 -13
  72. package/dist/decorators/params.d.mjs +0 -0
  73. package/dist/events/Lifecycle.d.js +0 -13
  74. package/dist/events/Lifecycle.d.mjs +0 -0
  75. package/dist/exceptions/HttpException.d.js +0 -13
  76. package/dist/exceptions/HttpException.d.mjs +0 -0
  77. package/dist/index.d.js +0 -130
  78. package/dist/index.d.mjs +0 -78
  79. package/dist/metadata.d.js +0 -13
  80. package/dist/metadata.d.mjs +0 -0
  81. package/dist/middleware/CarnoMiddleware.d.js +0 -13
  82. package/dist/middleware/CarnoMiddleware.d.mjs +0 -0
  83. package/dist/router/RadixRouter.d.js +0 -13
  84. package/dist/router/RadixRouter.d.mjs +0 -0
  85. package/dist/testing/TestHarness.d.js +0 -13
  86. package/dist/testing/TestHarness.d.mjs +0 -0
  87. package/dist/utils/Metadata.d.js +0 -13
  88. package/dist/utils/Metadata.d.mjs +0 -0
  89. package/dist/utils/parseQuery.d.js +0 -13
  90. package/dist/utils/parseQuery.d.mjs +0 -0
  91. package/dist/validation/ValibotAdapter.d.js +0 -13
  92. package/dist/validation/ValibotAdapter.d.mjs +0 -0
  93. package/dist/validation/ValidatorAdapter.d.js +0 -13
  94. package/dist/validation/ValidatorAdapter.d.mjs +0 -0
  95. package/dist/validation/ZodAdapter.d.js +0 -13
  96. package/dist/validation/ZodAdapter.d.mjs +0 -0
  97. package/src/Carno.d.ts +0 -135
  98. package/src/DefaultRoutes.d.ts +0 -19
  99. package/src/cache/CacheDriver.d.ts +0 -43
  100. package/src/cache/CacheService.d.ts +0 -89
  101. package/src/cache/MemoryDriver.d.ts +0 -32
  102. package/src/cache/RedisDriver.d.ts +0 -34
  103. package/src/compiler/JITCompiler.d.ts +0 -36
  104. package/src/container/Container.d.ts +0 -38
  105. package/src/context/Context.d.ts +0 -36
  106. package/src/cors/CorsHandler.d.ts +0 -47
  107. package/src/decorators/Controller.d.ts +0 -13
  108. package/src/decorators/Inject.d.ts +0 -6
  109. package/src/decorators/Middleware.d.ts +0 -5
  110. package/src/decorators/Service.d.ts +0 -9
  111. package/src/decorators/methods.d.ts +0 -7
  112. package/src/decorators/params.d.ts +0 -13
  113. package/src/events/Lifecycle.d.ts +0 -54
  114. package/src/exceptions/HttpException.d.ts +0 -43
  115. package/src/index.d.ts +0 -42
  116. package/src/metadata.d.ts +0 -41
  117. package/src/middleware/CarnoMiddleware.d.ts +0 -12
  118. package/src/router/RadixRouter.d.ts +0 -19
  119. package/src/testing/TestHarness.d.ts +0 -71
  120. package/src/utils/Metadata.d.ts +0 -20
  121. package/src/utils/parseQuery.d.ts +0 -23
  122. package/src/validation/ValibotAdapter.d.ts +0 -30
  123. package/src/validation/ValidatorAdapter.d.ts +0 -54
  124. package/src/validation/ZodAdapter.d.ts +0 -35
package/src/Carno.ts CHANGED
@@ -1,673 +1,728 @@
1
- import 'reflect-metadata';
2
-
3
- import { CONTROLLER_META, ROUTES_META, PARAMS_META, MIDDLEWARE_META } from './metadata';
4
- import type { RouteInfo, MiddlewareInfo, ControllerMeta } from './metadata';
5
- import type { ParamMetadata } from './decorators/params';
6
- // RadixRouter removed - using Bun's native SIMD-accelerated router
7
- import { compileHandler } from './compiler/JITCompiler';
8
- import { Context } from './context/Context';
9
- import { Container, Scope } from './container/Container';
10
- import type { Token, ProviderConfig } from './container/Container';
11
- import { CorsHandler, type CorsConfig } from './cors/CorsHandler';
12
- import type { ValidatorAdapter } from './validation/ValidatorAdapter';
13
- import { HttpException } from './exceptions/HttpException';
14
- import { ValidationException } from './validation/ZodAdapter';
15
- import { EventType, hasEventHandlers, getEventHandlers } from './events/Lifecycle';
16
- import { CacheService } from './cache/CacheService';
17
- import type { CacheConfig } from './cache/CacheDriver';
18
- import { DEFAULT_STATIC_ROUTES } from './DefaultRoutes';
19
- import { ZodAdapter } from './validation/ZodAdapter';
20
- import type { CarnoMiddleware } from './middleware/CarnoMiddleware';
21
-
22
- export type MiddlewareHandler = (ctx: Context) => Response | void | Promise<Response | void>;
23
-
24
- /**
25
- * Carno plugin configuration.
26
- */
27
- export interface CarnoConfig {
28
- exports?: (Token | ProviderConfig)[];
29
- globalMiddlewares?: MiddlewareHandler[];
30
- disableStartupLog?: boolean;
31
- cors?: CorsConfig;
32
- validation?: ValidatorAdapter | boolean | (new (...args: any[]) => ValidatorAdapter);
33
- cache?: CacheConfig | boolean;
34
- }
35
-
36
- // CompiledRoute removed - handlers are registered directly in Bun's routes
37
-
38
- const NOT_FOUND_RESPONSE = new Response('Not Found', { status: 404 });
39
-
40
- /**
41
- * Pre-computed response - frozen and reused.
42
- */
43
- const TEXT_OPTS = Object.freeze({
44
- status: 200,
45
- headers: { 'Content-Type': 'text/plain' }
46
- });
47
-
48
- const JSON_OPTS = Object.freeze({
49
- status: 200,
50
- headers: { 'Content-Type': 'application/json' }
51
- });
52
-
53
- const INTERNAL_ERROR_RESPONSE = new Response(
54
- '{"statusCode":500,"message":"Internal Server Error"}',
55
- { status: 500, headers: { 'Content-Type': 'application/json' } }
56
- );
57
-
58
- // METHOD_MAP removed - Bun handles method routing natively
59
-
60
- /**
61
- * Carno Application - Ultra-aggressive performance.
62
- *
63
- * ZERO runtime work in hot path:
64
- * - All responses pre-created at startup
65
- * - Direct Bun native routes - no fetch fallback needed
66
- * - No function calls in hot path
67
- */
68
- export class Carno {
69
- private _controllers: (new (...args: any[]) => any)[] = [];
70
- private _services: (Token | ProviderConfig)[] = [];
71
- private _middlewares: MiddlewareHandler[] = [];
72
- private routes: Record<string, Record<string, Response | Function> | Response | Function> = {};
73
- private container = new Container();
74
- private corsHandler: CorsHandler | null = null;
75
- private hasCors = false;
76
- private validator: ValidatorAdapter | null = null;
77
- private server: any;
78
-
79
- // Cached lifecycle event flags - checked once at startup
80
- private hasInitHooks = false;
81
- private hasBootHooks = false;
82
- private hasShutdownHooks = false;
83
-
84
- constructor(public config: CarnoConfig = {}) {
85
- this.config.exports = this.config.exports || [];
86
- this.config.globalMiddlewares = this.config.globalMiddlewares || [];
87
-
88
- // Initialize CORS handler if configured
89
- if (this.config.cors) {
90
- this.corsHandler = new CorsHandler(this.config.cors);
91
- this.hasCors = true;
92
- }
93
-
94
- // Initialize validator
95
- // Default: ZodAdapter if undefined or true
96
- if (this.config.validation === undefined || this.config.validation === true) {
97
- this.validator = new ZodAdapter();
98
- }
99
- // Constructor class passed directly
100
- else if (typeof this.config.validation === 'function') {
101
- const AdapterClass = this.config.validation as (new (...args: any[]) => ValidatorAdapter);
102
- this.validator = new AdapterClass();
103
- }
104
- // Instance passed directly
105
- else if (this.config.validation) {
106
- this.validator = this.config.validation as ValidatorAdapter;
107
- }
108
- }
109
-
110
- /**
111
- * Use a Carno plugin.
112
- * Imports controllers, services, middlewares, and routes from another Carno instance.
113
- */
114
- use(plugin: Carno): this {
115
- // Import controllers from plugin
116
- if (plugin._controllers.length > 0) {
117
- this._controllers.push(...plugin._controllers);
118
- }
119
-
120
- // Import services from plugin exports
121
- for (const exported of plugin.config.exports || []) {
122
- const existingService = this.findServiceInPlugin(plugin, exported);
123
- const serviceToAdd = this.shouldCloneService(existingService)
124
- ? { ...existingService }
125
- : exported;
126
-
127
- this._services.push(serviceToAdd);
128
- }
129
-
130
- // Import services registered via .services() on the plugin
131
- if (plugin._services.length > 0) {
132
- this._services.push(...plugin._services);
133
- }
134
-
135
- // Import global middlewares
136
- if (plugin.config.globalMiddlewares) {
137
- this._middlewares.push(...plugin.config.globalMiddlewares);
138
- }
139
-
140
- // Import middlewares registered via .middlewares() on the plugin
141
- if (plugin._middlewares.length > 0) {
142
- this._middlewares.push(...plugin._middlewares);
143
- }
144
-
145
- // Import routes registered via .route() or .addRoutes() on the plugin
146
- for (const [path, methods] of Object.entries(plugin.routes)) {
147
- if (!this.routes[path]) {
148
- this.routes[path] = {};
149
- }
150
-
151
- // Merge methods for this path
152
- if (typeof methods === 'object' && methods !== null && !(methods instanceof Response)) {
153
- Object.assign(this.routes[path], methods);
154
- } else {
155
- // Single handler (Response or Function) - preserve it
156
- this.routes[path] = methods;
157
- }
158
- }
159
-
160
- return this;
161
- }
162
-
163
- private findServiceInPlugin(plugin: Carno, exported: any): any | undefined {
164
- return plugin._services.find(
165
- s => this.getServiceToken(s) === this.getServiceToken(exported)
166
- );
167
- }
168
-
169
- private getServiceToken(service: any): any {
170
- return service?.token || service;
171
- }
172
-
173
- private shouldCloneService(service: any): boolean {
174
- return !!(service?.useValue !== undefined || service?.useClass);
175
- }
176
-
177
- /**
178
- * Register one or more services/providers.
179
- */
180
- services(serviceClass: Token | ProviderConfig | (Token | ProviderConfig)[]): this {
181
- const items = Array.isArray(serviceClass) ? serviceClass : [serviceClass];
182
- this._services.push(...items);
183
- return this;
184
- }
185
-
186
- /**
187
- * Register one or more global middlewares.
188
- */
189
- middlewares(handler: MiddlewareHandler | MiddlewareHandler[]): this {
190
- const items = Array.isArray(handler) ? handler : [handler];
191
- this._middlewares.push(...items);
192
- return this;
193
- }
194
-
195
- /**
196
- * Register one or more controllers.
197
- */
198
- controllers(controllerClass: (new (...args: any[]) => any) | (new (...args: any[]) => any)[]): this {
199
- const items = Array.isArray(controllerClass) ? controllerClass : [controllerClass];
200
- this._controllers.push(...items);
201
- return this;
202
- }
203
-
204
- /**
205
- * Register a route programmatically.
206
- * Useful for plugins that need to register routes without controllers.
207
- *
208
- * @example
209
- * ```ts
210
- * app.route('GET', '/health', () => ({ status: 'ok' }));
211
- * app.route('POST', '/webhook', async (ctx) => {
212
- * const data = await ctx.parseBody();
213
- * return { received: true };
214
- * });
215
- * ```
216
- */
217
- route(method: string, path: string, handler: Response | Function): this {
218
- const normalizedMethod = method.toUpperCase();
219
-
220
- if (!this.routes[path]) {
221
- this.routes[path] = {};
222
- }
223
-
224
- this.routes[path][normalizedMethod] = handler;
225
- return this;
226
- }
227
-
228
- /**
229
- * Bulk register multiple routes at once.
230
- *
231
- * @example
232
- * ```ts
233
- * app.addRoutes({
234
- * '/api/users': {
235
- * 'GET': () => ({ users: [] }),
236
- * 'POST': (ctx) => ({ created: true })
237
- * },
238
- * '/api/health': {
239
- * 'GET': () => ({ status: 'ok' })
240
- * }
241
- * });
242
- * ```
243
- */
244
- addRoutes(routes: Record<string, Record<string, Response | Function>>): this {
245
- for (const [path, methods] of Object.entries(routes)) {
246
- if (!this.routes[path]) {
247
- this.routes[path] = {};
248
- }
249
-
250
- for (const [method, handler] of Object.entries(methods)) {
251
- this.routes[path][method.toUpperCase()] = handler;
252
- }
253
- }
254
- return this;
255
- }
256
-
257
- /**
258
- * Get a service instance from the container.
259
- */
260
- get<T>(token: Token<T>): T {
261
- return this.container.get(token);
262
- }
263
-
264
- listen(port: number = 3000): void {
265
- this.bootstrap();
266
- this.compileRoutes();
267
-
268
- // All routes go through Bun's native SIMD-accelerated router
269
- const config: any = {
270
- port,
271
- fetch: this.handleNotFound.bind(this),
272
- error: this.handleError.bind(this),
273
- routes: {
274
- ...DEFAULT_STATIC_ROUTES,
275
- ...this.routes
276
- }
277
- };
278
-
279
- this.server = Bun.serve(config);
280
-
281
- // Execute BOOT hooks after server is ready
282
- if (this.hasBootHooks) {
283
- this.executeLifecycleHooks(EventType.BOOT);
284
- }
285
-
286
- // Register shutdown handlers
287
- if (this.hasShutdownHooks) {
288
- this.registerShutdownHandlers();
289
- }
290
-
291
- if (!this.config.disableStartupLog) {
292
- console.log(`Carno running on port ${port}`);
293
- }
294
- }
295
-
296
- private bootstrap(): void {
297
- // Cache lifecycle event flags
298
- this.hasInitHooks = hasEventHandlers(EventType.INIT);
299
- this.hasBootHooks = hasEventHandlers(EventType.BOOT);
300
- this.hasShutdownHooks = hasEventHandlers(EventType.SHUTDOWN);
301
-
302
- // Register Container itself so it can be injected
303
- this.container.register({
304
- token: Container,
305
- useValue: this.container
306
- });
307
-
308
- // Always register CacheService (Memory by default)
309
- const cacheConfig = typeof this.config.cache === 'object' ? this.config.cache : {};
310
- this.container.register({
311
- token: CacheService,
312
- useValue: new CacheService(cacheConfig)
313
- });
314
-
315
- for (const service of this._services) {
316
- this.container.register(service);
317
- }
318
-
319
- for (const ControllerClass of this._controllers) {
320
- this.container.register(ControllerClass);
321
- }
322
-
323
- if (this.hasInitHooks) {
324
- this.executeLifecycleHooks(EventType.INIT);
325
- }
326
-
327
- for (const service of this._services) {
328
- const token = typeof service === 'function' ? service : service.token;
329
- const serviceConfig = typeof service === 'function' ? null : service;
330
-
331
- if (!serviceConfig || serviceConfig.scope !== Scope.REQUEST) {
332
- this.container.get(token);
333
- }
334
- }
335
- }
336
-
337
- private compileRoutes(): void {
338
- for (const ControllerClass of this._controllers) {
339
- this.compileController(ControllerClass);
340
- }
341
- }
342
-
343
- private compileController(
344
- ControllerClass: new (...args: any[]) => any,
345
- parentPath: string = '',
346
- inheritedMiddlewares: MiddlewareHandler[] = []
347
- ): void {
348
- const meta: ControllerMeta = Reflect.getMetadata(CONTROLLER_META, ControllerClass) || { path: '' };
349
- const basePath = parentPath + (meta.path || '');
350
- const routes: RouteInfo[] = Reflect.getMetadata(ROUTES_META, ControllerClass) || [];
351
- const middlewares: MiddlewareInfo[] = Reflect.getMetadata(MIDDLEWARE_META, ControllerClass) || [];
352
- const instance = this.container.get(ControllerClass);
353
-
354
- // Extract controller-level middlewares (applied to all routes of this controller)
355
- const controllerMiddlewares = middlewares
356
- .filter(m => !m.target)
357
- .map(m => m.handler as MiddlewareHandler);
358
-
359
- // Combine inherited middlewares with this controller's middlewares
360
- // This combined list is passed down to children and applied to current routes
361
- const scopedMiddlewares = [...inheritedMiddlewares, ...controllerMiddlewares];
362
-
363
- for (const route of routes) {
364
- const fullPath = this.normalizePath(basePath + route.path);
365
- const params: ParamMetadata[] = Reflect.getMetadata(PARAMS_META, ControllerClass, route.handlerName) || [];
366
-
367
- // Middlewares specific to this route handler
368
- const routeMiddlewares = middlewares
369
- .filter(m => m.target === route.handlerName)
370
- .map(m => m.handler as MiddlewareHandler);
371
-
372
- // Get parameter types for validation
373
- const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', ControllerClass.prototype, route.handlerName) || [];
374
-
375
- // Find Body param with DTO that has @Schema for validation
376
- let bodyDtoType: any = null;
377
- for (const param of params) {
378
- if (param.type === 'body' && !param.key) {
379
- const dtoType = paramTypes[param.index];
380
- if (dtoType && this.validator?.hasValidation(dtoType)) {
381
- bodyDtoType = dtoType;
382
- }
383
- }
384
- }
385
-
386
- const compiled = compileHandler(instance, route.handlerName, params);
387
-
388
- const allMiddlewares = [
389
- ...(this.config.globalMiddlewares || []),
390
- ...this._middlewares,
391
- ...scopedMiddlewares,
392
- ...routeMiddlewares
393
- ];
394
-
395
- // Pre-resolve class-based middlewares at compile time for maximum performance
396
- const resolvedMiddlewares = allMiddlewares.map(m => this.resolveMiddleware(m));
397
-
398
- const hasMiddlewares = resolvedMiddlewares.length > 0;
399
-
400
- const method = route.method.toUpperCase();
401
-
402
- // Static response - no function needed
403
- if (compiled.isStatic && !hasMiddlewares) {
404
- this.registerRoute(fullPath, method, this.createStaticResponse(compiled.staticValue));
405
- } else {
406
- // Dynamic handler - compile to Bun-compatible function
407
- this.registerRoute(fullPath, method, this.createHandler(compiled, params, resolvedMiddlewares, bodyDtoType));
408
- }
409
- }
410
-
411
- // Compile child controllers with parent path and inherited middlewares
412
- if (meta.children) {
413
- for (const ChildController of meta.children) {
414
- if (!this.container.has(ChildController)) {
415
- this.container.register(ChildController);
416
- }
417
-
418
- this.compileController(ChildController, basePath, scopedMiddlewares);
419
- }
420
- }
421
- }
422
-
423
- /**
424
- * Register a route with Bun's native router format.
425
- * Path: "/users/:id", Method: "GET", Handler: Function or Response
426
- */
427
- private registerRoute(path: string, method: string, handler: Response | Function): void {
428
- if (!this.routes[path]) {
429
- this.routes[path] = {};
430
- }
431
-
432
- (this.routes[path] as Record<string, Response | Function>)[method] = handler;
433
- }
434
-
435
- private createStaticResponse(value: any): Response {
436
- const isString = typeof value === 'string';
437
- const body = isString ? value : JSON.stringify(value);
438
- const opts = isString ? TEXT_OPTS : JSON_OPTS;
439
-
440
- return new Response(body, opts);
441
- }
442
-
443
- private createHandler(
444
- compiled: { fn: Function; isAsync: boolean },
445
- params: ParamMetadata[],
446
- middlewares: MiddlewareHandler[],
447
- bodyDtoType?: any
448
- ): Function {
449
- const handler = compiled.fn;
450
- const hasMiddlewares = middlewares.length > 0;
451
- const hasParams = params.length > 0;
452
- const applyCors = this.hasCors ? this.applyCors.bind(this) : null;
453
- const validator = bodyDtoType ? this.validator : null;
454
- const needsValidation = !!validator;
455
-
456
- // Force middleware path when validation is needed
457
- const hasMiddlewaresOrValidation = hasMiddlewares || needsValidation;
458
-
459
- // No middlewares, no params - fastest path
460
- if (!hasMiddlewaresOrValidation && !hasParams) {
461
- if (compiled.isAsync) {
462
- return async (req: Request) => {
463
- const ctx = new Context(req);
464
- const result = await handler(ctx);
465
- const response = this.buildResponse(result);
466
-
467
- return applyCors ? applyCors(response, req) : response;
468
- };
469
- }
470
-
471
- return (req: Request) => {
472
- const ctx = new Context(req);
473
- const result = handler(ctx);
474
- const response = this.buildResponse(result);
475
-
476
- return applyCors ? applyCors(response, req) : response;
477
- };
478
- }
479
-
480
- // With params - use Bun's native req.params
481
- if (!hasMiddlewaresOrValidation && hasParams) {
482
- if (compiled.isAsync) {
483
- return async (req: Request) => {
484
- const ctx = new Context(req, (req as any).params);
485
- const result = await handler(ctx);
486
- const response = this.buildResponse(result);
487
-
488
- return applyCors ? applyCors(response, req) : response;
489
- };
490
- }
491
-
492
- return (req: Request) => {
493
- const ctx = new Context(req, (req as any).params);
494
- const result = handler(ctx);
495
- const response = this.buildResponse(result);
496
-
497
- return applyCors ? applyCors(response, req) : response;
498
- };
499
- }
500
-
501
- // With middlewares - full pipeline
502
- return async (req: Request) => {
503
- const ctx = new Context(req, (req as any).params || {});
504
-
505
- for (const middleware of middlewares) {
506
- const result = await middleware(ctx);
507
-
508
- if (result instanceof Response) {
509
- return applyCors ? applyCors(result, req) : result;
510
- }
511
- }
512
-
513
- // Validate body if validator is configured
514
- if (validator && bodyDtoType) {
515
- await ctx.parseBody();
516
- validator.validateOrThrow(bodyDtoType, ctx.body);
517
- }
518
-
519
- const result = compiled.isAsync
520
- ? await handler(ctx)
521
- : handler(ctx);
522
-
523
- const response = this.buildResponse(result);
524
-
525
- return applyCors ? applyCors(response, req) : response;
526
- };
527
- }
528
-
529
- private resolveMiddleware(middleware: any): MiddlewareHandler {
530
- // Check if it's a class with a handle method
531
- if (typeof middleware === 'function' && middleware.prototype?.handle) {
532
- // Instantiate via Container and bind the handle method
533
- const instance = this.container.get(middleware) as CarnoMiddleware;
534
- return (ctx: Context) => instance.handle(ctx, () => { });
535
- }
536
-
537
- // Already a function
538
- return middleware;
539
- }
540
-
541
- /**
542
- * Apply CORS headers to a response.
543
- */
544
- private applyCors(response: Response, req: Request): Response {
545
- const origin = req.headers.get('origin');
546
-
547
- if (origin && this.corsHandler) {
548
- return this.corsHandler.apply(response, origin);
549
- }
550
-
551
- return response;
552
- }
553
-
554
- /**
555
- * Fallback handler - only called for unmatched routes.
556
- * All matched routes go through Bun's native router.
557
- */
558
- private handleNotFound(req: Request): Response {
559
- // CORS preflight for unmatched routes
560
- if (this.hasCors && req.method === 'OPTIONS') {
561
- const origin = req.headers.get('origin');
562
-
563
- if (origin) {
564
- return this.corsHandler!.preflight(origin);
565
- }
566
- }
567
-
568
- return NOT_FOUND_RESPONSE;
569
- }
570
-
571
- private buildResponse(result: any): Response {
572
- if (result instanceof Response) {
573
- return result;
574
- }
575
-
576
- if (typeof result === 'string') {
577
- return new Response(result, TEXT_OPTS);
578
- }
579
-
580
- // Handle undefined/void return values - return empty 204 No Content
581
- if (result === undefined) {
582
- return new Response(null, { status: 204 });
583
- }
584
-
585
- return Response.json(result);
586
- }
587
-
588
- private normalizePath(path: string): string {
589
- if (!path.startsWith('/')) path = '/' + path;
590
- if (path !== '/' && path.endsWith('/')) path = path.slice(0, -1);
591
-
592
- return path.replace(/\/+/g, '/');
593
- }
594
-
595
- private hasParams(path: string): boolean {
596
- return path.includes(':') || path.includes('*');
597
- }
598
-
599
- stop(): void {
600
- this.server?.stop?.();
601
- }
602
-
603
- /**
604
- * Error handler for Bun.serve.
605
- * Converts exceptions to proper HTTP responses.
606
- */
607
- private handleError(error: Error): Response {
608
- let response: Response;
609
-
610
- // HttpException - return custom response
611
- if (error instanceof HttpException) {
612
- response = error.toResponse();
613
- }
614
- // ValidationException - return 400 with errors
615
- else if (error instanceof ValidationException) {
616
- response = error.toResponse();
617
- }
618
- // Unknown error - return 500
619
- else {
620
- console.error('Unhandled error:', error);
621
- response = INTERNAL_ERROR_RESPONSE;
622
- }
623
-
624
- // Apply CORS headers if configured
625
- if (this.hasCors && this.corsHandler) {
626
- return this.corsHandler.apply(response, '*');
627
- }
628
-
629
- return response;
630
- }
631
-
632
- /**
633
- * Execute lifecycle hooks for a specific event type.
634
- */
635
- private executeLifecycleHooks(type: EventType): void {
636
- const handlers = getEventHandlers(type);
637
-
638
- for (const handler of handlers) {
639
- try {
640
- const instance = this.container.has(handler.target)
641
- ? this.container.get(handler.target)
642
- : null;
643
-
644
- if (instance && typeof (instance as any)[handler.methodName] === 'function') {
645
- const result = (instance as any)[handler.methodName]();
646
-
647
- // Handle async hooks
648
- if (result instanceof Promise) {
649
- result.catch((err: Error) =>
650
- console.error(`Error in ${type} hook ${handler.methodName}:`, err)
651
- );
652
- }
653
- }
654
- } catch (err) {
655
- console.error(`Error in ${type} hook ${handler.methodName}:`, err);
656
- }
657
- }
658
- }
659
-
660
- /**
661
- * Register SIGTERM/SIGINT handlers for graceful shutdown.
662
- */
663
- private registerShutdownHandlers(): void {
664
- const shutdown = () => {
665
- this.executeLifecycleHooks(EventType.SHUTDOWN);
666
- this.stop();
667
- process.exit(0);
668
- };
669
-
670
- process.on('SIGTERM', shutdown);
671
- process.on('SIGINT', shutdown);
672
- }
673
- }
1
+ import 'reflect-metadata';
2
+
3
+ import { CONTROLLER_META, ROUTES_META, PARAMS_META, MIDDLEWARE_META } from './metadata';
4
+ import type { RouteInfo, MiddlewareInfo, ControllerMeta } from './metadata';
5
+ import type { ParamMetadata } from './decorators/params';
6
+ // RadixRouter removed - using Bun's native SIMD-accelerated router
7
+ import { compileHandler } from './compiler/JITCompiler';
8
+ import { Context } from './context/Context';
9
+ import { Container, Scope } from './container/Container';
10
+ import type { Token, ProviderConfig } from './container/Container';
11
+ import { CorsHandler, type CorsConfig } from './cors/CorsHandler';
12
+ import type { ValidatorAdapter } from './validation/ValidatorAdapter';
13
+ import { HttpException } from './exceptions/HttpException';
14
+ import { ValidationException } from './validation/ZodAdapter';
15
+ import { EventType, hasEventHandlers, getEventHandlers } from './events/Lifecycle';
16
+ import { CacheService } from './cache/CacheService';
17
+ import type { CacheConfig } from './cache/CacheDriver';
18
+ import { DEFAULT_STATIC_ROUTES } from './DefaultRoutes';
19
+ import { ZodAdapter } from './validation/ZodAdapter';
20
+ import type { CarnoMiddleware } from './middleware/CarnoMiddleware';
21
+
22
+ export type MiddlewareHandler = (ctx: Context) => Response | void | Promise<Response | void>;
23
+
24
+ export type MiddlewareClass = new (...args: any[]) => CarnoMiddleware;
25
+
26
+ export type MiddlewareEntry = MiddlewareHandler | MiddlewareClass | CarnoMiddleware;
27
+
28
+ type ResolvedMiddleware =
29
+ | { kind: 'function'; handler: MiddlewareHandler }
30
+ | { kind: 'class'; instance: CarnoMiddleware };
31
+
32
+ /**
33
+ * Carno plugin configuration.
34
+ */
35
+ export interface CarnoConfig {
36
+ exports?: (Token | ProviderConfig)[];
37
+ globalMiddlewares?: MiddlewareEntry[];
38
+ disableStartupLog?: boolean;
39
+ cors?: CorsConfig;
40
+ validation?: ValidatorAdapter | boolean | (new (...args: any[]) => ValidatorAdapter);
41
+ cache?: CacheConfig | boolean;
42
+ }
43
+
44
+ // CompiledRoute removed - handlers are registered directly in Bun's routes
45
+
46
+ const NOT_FOUND_RESPONSE = new Response('Not Found', { status: 404 });
47
+
48
+ /**
49
+ * Pre-computed response - frozen and reused.
50
+ */
51
+ const TEXT_OPTS = Object.freeze({
52
+ status: 200,
53
+ headers: { 'Content-Type': 'text/plain' }
54
+ });
55
+
56
+ const JSON_OPTS = Object.freeze({
57
+ status: 200,
58
+ headers: { 'Content-Type': 'application/json' }
59
+ });
60
+
61
+ const INTERNAL_ERROR_RESPONSE = new Response(
62
+ '{"statusCode":500,"message":"Internal Server Error"}',
63
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
64
+ );
65
+
66
+ // METHOD_MAP removed - Bun handles method routing natively
67
+
68
+ /**
69
+ * Carno Application - Ultra-aggressive performance.
70
+ *
71
+ * ZERO runtime work in hot path:
72
+ * - All responses pre-created at startup
73
+ * - Direct Bun native routes - no fetch fallback needed
74
+ * - No function calls in hot path
75
+ */
76
+ export class Carno {
77
+ private _controllers: (new (...args: any[]) => any)[] = [];
78
+ private _services: (Token | ProviderConfig)[] = [];
79
+ private _middlewares: MiddlewareEntry[] = [];
80
+ private routes: Record<string, Record<string, Response | Function> | Response | Function> = {};
81
+ private container = new Container();
82
+ private corsHandler: CorsHandler | null = null;
83
+ private hasCors = false;
84
+ private validator: ValidatorAdapter | null = null;
85
+ private server: any;
86
+
87
+ // Cached lifecycle event flags - checked once at startup
88
+ private hasInitHooks = false;
89
+ private hasBootHooks = false;
90
+ private hasShutdownHooks = false;
91
+
92
+ constructor(public config: CarnoConfig = {}) {
93
+ this.config.exports = this.config.exports || [];
94
+ this.config.globalMiddlewares = this.config.globalMiddlewares || [];
95
+
96
+ // Initialize CORS handler if configured
97
+ if (this.config.cors) {
98
+ this.corsHandler = new CorsHandler(this.config.cors);
99
+ this.hasCors = true;
100
+ }
101
+
102
+ // Initialize validator
103
+ // Default: ZodAdapter if undefined or true
104
+ if (this.config.validation === undefined || this.config.validation === true) {
105
+ this.validator = new ZodAdapter();
106
+ }
107
+ // Constructor class passed directly
108
+ else if (typeof this.config.validation === 'function') {
109
+ const AdapterClass = this.config.validation as (new (...args: any[]) => ValidatorAdapter);
110
+ this.validator = new AdapterClass();
111
+ }
112
+ // Instance passed directly
113
+ else if (this.config.validation) {
114
+ this.validator = this.config.validation as ValidatorAdapter;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Use a Carno plugin.
120
+ * Imports controllers, services, middlewares, and routes from another Carno instance.
121
+ */
122
+ use(plugin: Carno): this {
123
+ // Import controllers from plugin
124
+ if (plugin._controllers.length > 0) {
125
+ this._controllers.push(...plugin._controllers);
126
+ }
127
+
128
+ // Import services from plugin exports
129
+ for (const exported of plugin.config.exports || []) {
130
+ const existingService = this.findServiceInPlugin(plugin, exported);
131
+ const serviceToAdd = this.shouldCloneService(existingService)
132
+ ? { ...existingService }
133
+ : exported;
134
+
135
+ this._services.push(serviceToAdd);
136
+ }
137
+
138
+ // Import services registered via .services() on the plugin
139
+ if (plugin._services.length > 0) {
140
+ this._services.push(...plugin._services);
141
+ }
142
+
143
+ // Import global middlewares
144
+ if (plugin.config.globalMiddlewares) {
145
+ this._middlewares.push(...plugin.config.globalMiddlewares);
146
+ }
147
+
148
+ // Import middlewares registered via .middlewares() on the plugin
149
+ if (plugin._middlewares.length > 0) {
150
+ this._middlewares.push(...plugin._middlewares);
151
+ }
152
+
153
+ // Import routes registered via .route() or .addRoutes() on the plugin
154
+ for (const [path, methods] of Object.entries(plugin.routes)) {
155
+ if (!this.routes[path]) {
156
+ this.routes[path] = {};
157
+ }
158
+
159
+ // Merge methods for this path
160
+ if (typeof methods === 'object' && methods !== null && !(methods instanceof Response)) {
161
+ Object.assign(this.routes[path], methods);
162
+ } else {
163
+ // Single handler (Response or Function) - preserve it
164
+ this.routes[path] = methods;
165
+ }
166
+ }
167
+
168
+ return this;
169
+ }
170
+
171
+ private findServiceInPlugin(plugin: Carno, exported: any): any | undefined {
172
+ return plugin._services.find(
173
+ s => this.getServiceToken(s) === this.getServiceToken(exported)
174
+ );
175
+ }
176
+
177
+ private getServiceToken(service: any): any {
178
+ return service?.token || service;
179
+ }
180
+
181
+ private shouldCloneService(service: any): boolean {
182
+ return !!(service?.useValue !== undefined || service?.useClass);
183
+ }
184
+
185
+ /**
186
+ * Register one or more services/providers.
187
+ */
188
+ services(serviceClass: Token | ProviderConfig | (Token | ProviderConfig)[]): this {
189
+ const items = Array.isArray(serviceClass) ? serviceClass : [serviceClass];
190
+ this._services.push(...items);
191
+ return this;
192
+ }
193
+
194
+ /**
195
+ * Register one or more global middlewares.
196
+ */
197
+ middlewares(handler: MiddlewareEntry | MiddlewareEntry[]): this {
198
+ const items = Array.isArray(handler) ? handler : [handler];
199
+ this._middlewares.push(...items);
200
+ return this;
201
+ }
202
+
203
+ /**
204
+ * Register one or more controllers.
205
+ */
206
+ controllers(controllerClass: (new (...args: any[]) => any) | (new (...args: any[]) => any)[]): this {
207
+ const items = Array.isArray(controllerClass) ? controllerClass : [controllerClass];
208
+ this._controllers.push(...items);
209
+ return this;
210
+ }
211
+
212
+ /**
213
+ * Register a route programmatically.
214
+ * Useful for plugins that need to register routes without controllers.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * app.route('GET', '/health', () => ({ status: 'ok' }));
219
+ * app.route('POST', '/webhook', async (ctx) => {
220
+ * const data = await ctx.parseBody();
221
+ * return { received: true };
222
+ * });
223
+ * ```
224
+ */
225
+ route(method: string, path: string, handler: Response | Function): this {
226
+ const normalizedMethod = method.toUpperCase();
227
+
228
+ if (!this.routes[path]) {
229
+ this.routes[path] = {};
230
+ }
231
+
232
+ this.routes[path][normalizedMethod] = handler;
233
+ return this;
234
+ }
235
+
236
+ /**
237
+ * Bulk register multiple routes at once.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * app.addRoutes({
242
+ * '/api/users': {
243
+ * 'GET': () => ({ users: [] }),
244
+ * 'POST': (ctx) => ({ created: true })
245
+ * },
246
+ * '/api/health': {
247
+ * 'GET': () => ({ status: 'ok' })
248
+ * }
249
+ * });
250
+ * ```
251
+ */
252
+ addRoutes(routes: Record<string, Record<string, Response | Function>>): this {
253
+ for (const [path, methods] of Object.entries(routes)) {
254
+ if (!this.routes[path]) {
255
+ this.routes[path] = {};
256
+ }
257
+
258
+ for (const [method, handler] of Object.entries(methods)) {
259
+ this.routes[path][method.toUpperCase()] = handler;
260
+ }
261
+ }
262
+ return this;
263
+ }
264
+
265
+ /**
266
+ * Get a service instance from the container.
267
+ */
268
+ get<T>(token: Token<T>): T {
269
+ return this.container.get(token);
270
+ }
271
+
272
+ listen(port: number = 3000): void {
273
+ this.bootstrap();
274
+ this.compileRoutes();
275
+
276
+ // All routes go through Bun's native SIMD-accelerated router
277
+ const config: any = {
278
+ port,
279
+ fetch: this.handleNotFound.bind(this),
280
+ error: this.handleError.bind(this),
281
+ routes: {
282
+ ...DEFAULT_STATIC_ROUTES,
283
+ ...this.routes
284
+ }
285
+ };
286
+
287
+ this.server = Bun.serve(config);
288
+
289
+ // Execute BOOT hooks after server is ready
290
+ if (this.hasBootHooks) {
291
+ this.executeLifecycleHooks(EventType.BOOT);
292
+ }
293
+
294
+ // Register shutdown handlers
295
+ if (this.hasShutdownHooks) {
296
+ this.registerShutdownHandlers();
297
+ }
298
+
299
+ if (!this.config.disableStartupLog) {
300
+ console.log(`Carno running on port ${port}`);
301
+ }
302
+ }
303
+
304
+ private bootstrap(): void {
305
+ // Cache lifecycle event flags
306
+ this.hasInitHooks = hasEventHandlers(EventType.INIT);
307
+ this.hasBootHooks = hasEventHandlers(EventType.BOOT);
308
+ this.hasShutdownHooks = hasEventHandlers(EventType.SHUTDOWN);
309
+
310
+ // Register Container itself so it can be injected
311
+ this.container.register({
312
+ token: Container,
313
+ useValue: this.container
314
+ });
315
+
316
+ // Always register CacheService (Memory by default)
317
+ const cacheConfig = typeof this.config.cache === 'object' ? this.config.cache : {};
318
+ this.container.register({
319
+ token: CacheService,
320
+ useValue: new CacheService(cacheConfig)
321
+ });
322
+
323
+ for (const service of this._services) {
324
+ this.container.register(service);
325
+ }
326
+
327
+ for (const ControllerClass of this._controllers) {
328
+ this.container.register(ControllerClass);
329
+ }
330
+
331
+ if (this.hasInitHooks) {
332
+ this.executeLifecycleHooks(EventType.INIT);
333
+ }
334
+
335
+ for (const service of this._services) {
336
+ const token = typeof service === 'function' ? service : service.token;
337
+ const serviceConfig = typeof service === 'function' ? null : service;
338
+
339
+ if (!serviceConfig || serviceConfig.scope !== Scope.REQUEST) {
340
+ this.container.get(token);
341
+ }
342
+ }
343
+ }
344
+
345
+ private compileRoutes(): void {
346
+ for (const ControllerClass of this._controllers) {
347
+ this.compileController(ControllerClass);
348
+ }
349
+ }
350
+
351
+ private compileController(
352
+ ControllerClass: new (...args: any[]) => any,
353
+ parentPath: string = '',
354
+ inheritedMiddlewares: MiddlewareEntry[] = []
355
+ ): void {
356
+ const meta: ControllerMeta = Reflect.getMetadata(CONTROLLER_META, ControllerClass) || { path: '' };
357
+ const basePath = parentPath + (meta.path || '');
358
+ const routes: RouteInfo[] = Reflect.getMetadata(ROUTES_META, ControllerClass) || [];
359
+ const middlewares: MiddlewareInfo[] = Reflect.getMetadata(MIDDLEWARE_META, ControllerClass) || [];
360
+ const instance = this.container.get(ControllerClass);
361
+
362
+ // Extract controller-level middlewares (applied to all routes of this controller)
363
+ const controllerMiddlewares = middlewares
364
+ .filter(m => !m.target)
365
+ .map(m => m.handler as MiddlewareEntry);
366
+
367
+ // Combine inherited middlewares with this controller's middlewares
368
+ // This combined list is passed down to children and applied to current routes
369
+ const scopedMiddlewares = [...inheritedMiddlewares, ...controllerMiddlewares];
370
+
371
+ for (const route of routes) {
372
+ const fullPath = this.normalizePath(basePath + route.path);
373
+ const params: ParamMetadata[] = Reflect.getMetadata(PARAMS_META, ControllerClass, route.handlerName) || [];
374
+
375
+ // Middlewares specific to this route handler
376
+ const routeMiddlewares = middlewares
377
+ .filter(m => m.target === route.handlerName)
378
+ .map(m => m.handler as MiddlewareEntry);
379
+
380
+ // Get parameter types for validation
381
+ const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', ControllerClass.prototype, route.handlerName) || [];
382
+
383
+ // Find Body param with DTO that has @Schema for validation
384
+ let bodyDtoType: any = null;
385
+ for (const param of params) {
386
+ if (param.type === 'body' && !param.key) {
387
+ const dtoType = paramTypes[param.index];
388
+ if (dtoType && this.validator?.hasValidation(dtoType)) {
389
+ bodyDtoType = dtoType;
390
+ }
391
+ }
392
+ }
393
+
394
+ const compiled = compileHandler(instance, route.handlerName, params);
395
+
396
+ const allMiddlewares = [
397
+ ...(this.config.globalMiddlewares || []),
398
+ ...this._middlewares,
399
+ ...scopedMiddlewares,
400
+ ...routeMiddlewares
401
+ ];
402
+
403
+ // Pre-resolve class-based middlewares at compile time for maximum performance
404
+ const resolvedMiddlewares = allMiddlewares.map(m => this.resolveMiddleware(m));
405
+
406
+ const hasMiddlewares = resolvedMiddlewares.length > 0;
407
+
408
+ const method = route.method.toUpperCase();
409
+
410
+ // Static response - no function needed
411
+ if (compiled.isStatic && !hasMiddlewares) {
412
+ this.registerRoute(fullPath, method, this.createStaticResponse(compiled.staticValue));
413
+ } else {
414
+ // Dynamic handler - compile to Bun-compatible function
415
+ this.registerRoute(fullPath, method, this.createHandler(compiled, params, resolvedMiddlewares, bodyDtoType));
416
+ }
417
+ }
418
+
419
+ // Compile child controllers with parent path and inherited middlewares
420
+ if (meta.children) {
421
+ for (const ChildController of meta.children) {
422
+ if (!this.container.has(ChildController)) {
423
+ this.container.register(ChildController);
424
+ }
425
+
426
+ this.compileController(ChildController, basePath, scopedMiddlewares);
427
+ }
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Register a route with Bun's native router format.
433
+ * Path: "/users/:id", Method: "GET", Handler: Function or Response
434
+ */
435
+ private registerRoute(path: string, method: string, handler: Response | Function): void {
436
+ if (!this.routes[path]) {
437
+ this.routes[path] = {};
438
+ }
439
+
440
+ (this.routes[path] as Record<string, Response | Function>)[method] = handler;
441
+ }
442
+
443
+ private createStaticResponse(value: any): Response {
444
+ const isString = typeof value === 'string';
445
+ const body = isString ? value : JSON.stringify(value);
446
+ const opts = isString ? TEXT_OPTS : JSON_OPTS;
447
+
448
+ return new Response(body, opts);
449
+ }
450
+
451
+ private createHandler(
452
+ compiled: { fn: Function; isAsync: boolean },
453
+ params: ParamMetadata[],
454
+ middlewares: ResolvedMiddleware[],
455
+ bodyDtoType?: any
456
+ ): Function {
457
+ const handler = compiled.fn;
458
+ const hasMiddlewares = middlewares.length > 0;
459
+ const hasParams = params.length > 0;
460
+ const applyCors = this.hasCors ? this.applyCors.bind(this) : null;
461
+ const validator = bodyDtoType ? this.validator : null;
462
+ const needsValidation = !!validator;
463
+
464
+ // Force middleware path when validation is needed
465
+ const hasMiddlewaresOrValidation = hasMiddlewares || needsValidation;
466
+
467
+ // No middlewares, no params - fastest path
468
+ if (!hasMiddlewaresOrValidation && !hasParams) {
469
+ if (compiled.isAsync) {
470
+ return async (req: Request) => {
471
+ const ctx = new Context(req);
472
+ const result = await handler(ctx);
473
+ const response = this.buildResponse(result);
474
+
475
+ return applyCors ? applyCors(response, req) : response;
476
+ };
477
+ }
478
+
479
+ return (req: Request) => {
480
+ const ctx = new Context(req);
481
+ const result = handler(ctx);
482
+ const response = this.buildResponse(result);
483
+
484
+ return applyCors ? applyCors(response, req) : response;
485
+ };
486
+ }
487
+
488
+ // With params - use Bun's native req.params
489
+ if (!hasMiddlewaresOrValidation && hasParams) {
490
+ if (compiled.isAsync) {
491
+ return async (req: Request) => {
492
+ const ctx = new Context(req, (req as any).params);
493
+ const result = await handler(ctx);
494
+ const response = this.buildResponse(result);
495
+
496
+ return applyCors ? applyCors(response, req) : response;
497
+ };
498
+ }
499
+
500
+ return (req: Request) => {
501
+ const ctx = new Context(req, (req as any).params);
502
+ const result = handler(ctx);
503
+ const response = this.buildResponse(result);
504
+
505
+ return applyCors ? applyCors(response, req) : response;
506
+ };
507
+ }
508
+
509
+ // With middlewares - onion pipeline
510
+ const coreHandler = async (ctx: Context) => {
511
+ // Validate body if validator is configured
512
+ if (validator && bodyDtoType) {
513
+ await ctx.parseBody();
514
+ validator.validateOrThrow(bodyDtoType, ctx.body);
515
+ }
516
+
517
+ return compiled.isAsync
518
+ ? await handler(ctx)
519
+ : handler(ctx);
520
+ };
521
+
522
+ const chain = this.buildMiddlewareChain(
523
+ middlewares,
524
+ coreHandler,
525
+ this.buildResponse.bind(this)
526
+ );
527
+
528
+ return async (req: Request) => {
529
+ const ctx = new Context(req, (req as any).params || {});
530
+ const response = await chain(ctx);
531
+
532
+ return applyCors ? applyCors(response, req) : response;
533
+ };
534
+ }
535
+
536
+ private resolveMiddleware(middleware: MiddlewareEntry): ResolvedMiddleware {
537
+ if (typeof middleware === 'function' && middleware.prototype?.handle) {
538
+ const instance = this.container.get(middleware as MiddlewareClass) as CarnoMiddleware;
539
+ return { kind: 'class', instance };
540
+ }
541
+
542
+ // Pre-built instance with handle method
543
+ if (typeof middleware === 'object' && middleware !== null && 'handle' in middleware) {
544
+ return { kind: 'class', instance: middleware as CarnoMiddleware };
545
+ }
546
+
547
+ // Already a function
548
+ return { kind: 'function', handler: middleware as MiddlewareHandler };
549
+ }
550
+
551
+ /**
552
+ * Build an onion-style middleware chain.
553
+ * Wraps from inside-out so each middleware can run code before and after next().
554
+ */
555
+ private buildMiddlewareChain(
556
+ middlewares: ResolvedMiddleware[],
557
+ coreHandler: (ctx: Context) => any | Promise<any>,
558
+ buildResponseFn: (result: any) => Response
559
+ ): (ctx: Context) => Promise<Response> {
560
+ let chain: (ctx: Context) => Promise<Response> = async (ctx: Context) => {
561
+ const result = await coreHandler(ctx);
562
+ return buildResponseFn(result);
563
+ };
564
+
565
+ for (let i = middlewares.length - 1; i >= 0; i--) {
566
+ const mw = middlewares[i];
567
+ const nextLayer = chain;
568
+
569
+ if (mw.kind === 'function') {
570
+ chain = async (ctx: Context) => {
571
+ const result = await mw.handler(ctx);
572
+ if (result instanceof Response) {
573
+ return result;
574
+ }
575
+ return nextLayer(ctx);
576
+ };
577
+ } else {
578
+ chain = async (ctx: Context) => {
579
+ let response: Response | undefined;
580
+ const result = await mw.instance.handle(ctx, async () => {
581
+ response = await nextLayer(ctx);
582
+ return response;
583
+ });
584
+ // If middleware returned a Response, use it (enables response transformation)
585
+ if (result instanceof Response) {
586
+ return result;
587
+ }
588
+ return response ?? new Response(null, { status: 200 });
589
+ };
590
+ }
591
+ }
592
+
593
+ return chain;
594
+ }
595
+
596
+ /**
597
+ * Apply CORS headers to a response.
598
+ */
599
+ private applyCors(response: Response, req: Request): Response {
600
+ const origin = req.headers.get('origin');
601
+
602
+ if (origin && this.corsHandler) {
603
+ return this.corsHandler.apply(response, origin);
604
+ }
605
+
606
+ return response;
607
+ }
608
+
609
+ /**
610
+ * Fallback handler - only called for unmatched routes.
611
+ * All matched routes go through Bun's native router.
612
+ */
613
+ private handleNotFound(req: Request): Response {
614
+ // CORS preflight for unmatched routes
615
+ if (this.hasCors && req.method === 'OPTIONS') {
616
+ const origin = req.headers.get('origin');
617
+
618
+ if (origin) {
619
+ return this.corsHandler!.preflight(origin);
620
+ }
621
+ }
622
+
623
+ return NOT_FOUND_RESPONSE;
624
+ }
625
+
626
+ private buildResponse(result: any): Response {
627
+ if (result instanceof Response) {
628
+ return result;
629
+ }
630
+
631
+ if (typeof result === 'string') {
632
+ return new Response(result, TEXT_OPTS);
633
+ }
634
+
635
+ // Handle undefined/void return values - return empty 204 No Content
636
+ if (result === undefined) {
637
+ return new Response(null, { status: 204 });
638
+ }
639
+
640
+ return Response.json(result);
641
+ }
642
+
643
+ private normalizePath(path: string): string {
644
+ if (!path.startsWith('/')) path = '/' + path;
645
+ if (path !== '/' && path.endsWith('/')) path = path.slice(0, -1);
646
+
647
+ return path.replace(/\/+/g, '/');
648
+ }
649
+
650
+ private hasParams(path: string): boolean {
651
+ return path.includes(':') || path.includes('*');
652
+ }
653
+
654
+ stop(): void {
655
+ this.server?.stop?.();
656
+ }
657
+
658
+ /**
659
+ * Error handler for Bun.serve.
660
+ * Converts exceptions to proper HTTP responses.
661
+ */
662
+ private handleError(error: Error): Response {
663
+ let response: Response;
664
+
665
+ // HttpException - return custom response
666
+ if (error instanceof HttpException) {
667
+ response = error.toResponse();
668
+ }
669
+ // ValidationException - return 400 with errors
670
+ else if (error instanceof ValidationException) {
671
+ response = error.toResponse();
672
+ }
673
+ // Unknown error - return 500
674
+ else {
675
+ console.error('Unhandled error:', error);
676
+ response = INTERNAL_ERROR_RESPONSE;
677
+ }
678
+
679
+ // Apply CORS headers if configured
680
+ if (this.hasCors && this.corsHandler) {
681
+ return this.corsHandler.apply(response, '*');
682
+ }
683
+
684
+ return response;
685
+ }
686
+
687
+ /**
688
+ * Execute lifecycle hooks for a specific event type.
689
+ */
690
+ private executeLifecycleHooks(type: EventType): void {
691
+ const handlers = getEventHandlers(type);
692
+
693
+ for (const handler of handlers) {
694
+ try {
695
+ const instance = this.container.has(handler.target)
696
+ ? this.container.get(handler.target)
697
+ : null;
698
+
699
+ if (instance && typeof (instance as any)[handler.methodName] === 'function') {
700
+ const result = (instance as any)[handler.methodName]();
701
+
702
+ // Handle async hooks
703
+ if (result instanceof Promise) {
704
+ result.catch((err: Error) =>
705
+ console.error(`Error in ${type} hook ${handler.methodName}:`, err)
706
+ );
707
+ }
708
+ }
709
+ } catch (err) {
710
+ console.error(`Error in ${type} hook ${handler.methodName}:`, err);
711
+ }
712
+ }
713
+ }
714
+
715
+ /**
716
+ * Register SIGTERM/SIGINT handlers for graceful shutdown.
717
+ */
718
+ private registerShutdownHandlers(): void {
719
+ const shutdown = () => {
720
+ this.executeLifecycleHooks(EventType.SHUTDOWN);
721
+ this.stop();
722
+ process.exit(0);
723
+ };
724
+
725
+ process.on('SIGTERM', shutdown);
726
+ process.on('SIGINT', shutdown);
727
+ }
728
+ }