@carno.js/core 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +188 -188
- package/dist/Carno.js +46 -26
- package/dist/Carno.mjs +46 -26
- package/dist/bun/index.js +4 -4
- package/dist/bun/index.js.map +29 -29
- package/package.json +2 -2
- package/src/Carno.ts +718 -673
- package/src/DefaultRoutes.ts +34 -34
- package/src/cache/CacheDriver.ts +50 -50
- package/src/cache/CacheService.ts +139 -139
- package/src/cache/MemoryDriver.ts +104 -104
- package/src/cache/RedisDriver.ts +116 -116
- package/src/compiler/JITCompiler.ts +167 -167
- package/src/container/Container.ts +168 -168
- package/src/context/Context.ts +130 -130
- package/src/cors/CorsHandler.ts +145 -145
- package/src/decorators/Controller.ts +63 -63
- package/src/decorators/Inject.ts +16 -16
- package/src/decorators/Middleware.ts +22 -22
- package/src/decorators/Service.ts +18 -18
- package/src/decorators/methods.ts +58 -58
- package/src/decorators/params.ts +47 -47
- package/src/events/Lifecycle.ts +97 -97
- package/src/exceptions/HttpException.ts +99 -99
- package/src/index.ts +95 -95
- package/src/metadata.ts +46 -46
- package/src/middleware/CarnoMiddleware.ts +14 -14
- package/src/router/RadixRouter.ts +225 -225
- package/src/testing/TestHarness.ts +185 -185
- package/src/utils/Metadata.ts +43 -43
- package/src/utils/parseQuery.ts +161 -161
- package/src/validation/ValibotAdapter.ts +95 -95
- package/src/validation/ValidatorAdapter.ts +69 -69
- package/src/validation/ZodAdapter.ts +102 -102
- package/dist/Carno.d.js +0 -14
- package/dist/Carno.d.mjs +0 -1
- package/dist/DefaultRoutes.d.js +0 -13
- package/dist/DefaultRoutes.d.mjs +0 -0
- package/dist/cache/CacheDriver.d.js +0 -13
- package/dist/cache/CacheDriver.d.mjs +0 -0
- package/dist/cache/CacheService.d.js +0 -13
- package/dist/cache/CacheService.d.mjs +0 -0
- package/dist/cache/MemoryDriver.d.js +0 -13
- package/dist/cache/MemoryDriver.d.mjs +0 -0
- package/dist/cache/RedisDriver.d.js +0 -13
- package/dist/cache/RedisDriver.d.mjs +0 -0
- package/dist/compiler/JITCompiler.d.js +0 -13
- package/dist/compiler/JITCompiler.d.mjs +0 -0
- package/dist/container/Container.d.js +0 -13
- package/dist/container/Container.d.mjs +0 -0
- package/dist/context/Context.d.js +0 -13
- package/dist/context/Context.d.mjs +0 -0
- package/dist/cors/CorsHandler.d.js +0 -13
- package/dist/cors/CorsHandler.d.mjs +0 -0
- package/dist/decorators/Controller.d.js +0 -13
- package/dist/decorators/Controller.d.mjs +0 -0
- package/dist/decorators/Inject.d.js +0 -13
- package/dist/decorators/Inject.d.mjs +0 -0
- package/dist/decorators/Middleware.d.js +0 -13
- package/dist/decorators/Middleware.d.mjs +0 -0
- package/dist/decorators/Service.d.js +0 -13
- package/dist/decorators/Service.d.mjs +0 -0
- package/dist/decorators/methods.d.js +0 -13
- package/dist/decorators/methods.d.mjs +0 -0
- package/dist/decorators/params.d.js +0 -13
- package/dist/decorators/params.d.mjs +0 -0
- package/dist/events/Lifecycle.d.js +0 -13
- package/dist/events/Lifecycle.d.mjs +0 -0
- package/dist/exceptions/HttpException.d.js +0 -13
- package/dist/exceptions/HttpException.d.mjs +0 -0
- package/dist/index.d.js +0 -130
- package/dist/index.d.mjs +0 -78
- package/dist/metadata.d.js +0 -13
- package/dist/metadata.d.mjs +0 -0
- package/dist/middleware/CarnoMiddleware.d.js +0 -13
- package/dist/middleware/CarnoMiddleware.d.mjs +0 -0
- package/dist/router/RadixRouter.d.js +0 -13
- package/dist/router/RadixRouter.d.mjs +0 -0
- package/dist/testing/TestHarness.d.js +0 -13
- package/dist/testing/TestHarness.d.mjs +0 -0
- package/dist/utils/Metadata.d.js +0 -13
- package/dist/utils/Metadata.d.mjs +0 -0
- package/dist/utils/parseQuery.d.js +0 -13
- package/dist/utils/parseQuery.d.mjs +0 -0
- package/dist/validation/ValibotAdapter.d.js +0 -13
- package/dist/validation/ValibotAdapter.d.mjs +0 -0
- package/dist/validation/ValidatorAdapter.d.js +0 -13
- package/dist/validation/ValidatorAdapter.d.mjs +0 -0
- package/dist/validation/ZodAdapter.d.js +0 -13
- package/dist/validation/ZodAdapter.d.mjs +0 -0
- package/src/Carno.d.ts +0 -135
- package/src/DefaultRoutes.d.ts +0 -19
- package/src/cache/CacheDriver.d.ts +0 -43
- package/src/cache/CacheService.d.ts +0 -89
- package/src/cache/MemoryDriver.d.ts +0 -32
- package/src/cache/RedisDriver.d.ts +0 -34
- package/src/compiler/JITCompiler.d.ts +0 -36
- package/src/container/Container.d.ts +0 -38
- package/src/context/Context.d.ts +0 -36
- package/src/cors/CorsHandler.d.ts +0 -47
- package/src/decorators/Controller.d.ts +0 -13
- package/src/decorators/Inject.d.ts +0 -6
- package/src/decorators/Middleware.d.ts +0 -5
- package/src/decorators/Service.d.ts +0 -9
- package/src/decorators/methods.d.ts +0 -7
- package/src/decorators/params.d.ts +0 -13
- package/src/events/Lifecycle.d.ts +0 -54
- package/src/exceptions/HttpException.d.ts +0 -43
- package/src/index.d.ts +0 -42
- package/src/metadata.d.ts +0 -41
- package/src/middleware/CarnoMiddleware.d.ts +0 -12
- package/src/router/RadixRouter.d.ts +0 -19
- package/src/testing/TestHarness.d.ts +0 -71
- package/src/utils/Metadata.d.ts +0 -20
- package/src/utils/parseQuery.d.ts +0 -23
- package/src/validation/ValibotAdapter.d.ts +0 -30
- package/src/validation/ValidatorAdapter.d.ts +0 -54
- package/src/validation/ZodAdapter.d.ts +0 -35
package/src/Carno.ts
CHANGED
|
@@ -1,673 +1,718 @@
|
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
private
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
private
|
|
81
|
-
private
|
|
82
|
-
private
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this.routes[path] =
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
this.
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
for (const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
private
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
private
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
return
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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;
|
|
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
|
+
// Already a function
|
|
543
|
+
return { kind: 'function', handler: middleware as MiddlewareHandler };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Build an onion-style middleware chain.
|
|
548
|
+
* Wraps from inside-out so each middleware can run code before and after next().
|
|
549
|
+
*/
|
|
550
|
+
private buildMiddlewareChain(
|
|
551
|
+
middlewares: ResolvedMiddleware[],
|
|
552
|
+
coreHandler: (ctx: Context) => any | Promise<any>,
|
|
553
|
+
buildResponseFn: (result: any) => Response
|
|
554
|
+
): (ctx: Context) => Promise<Response> {
|
|
555
|
+
let chain: (ctx: Context) => Promise<Response> = async (ctx: Context) => {
|
|
556
|
+
const result = await coreHandler(ctx);
|
|
557
|
+
return buildResponseFn(result);
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
561
|
+
const mw = middlewares[i];
|
|
562
|
+
const nextLayer = chain;
|
|
563
|
+
|
|
564
|
+
if (mw.kind === 'function') {
|
|
565
|
+
chain = async (ctx: Context) => {
|
|
566
|
+
const result = await mw.handler(ctx);
|
|
567
|
+
if (result instanceof Response) {
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
return nextLayer(ctx);
|
|
571
|
+
};
|
|
572
|
+
} else {
|
|
573
|
+
chain = async (ctx: Context) => {
|
|
574
|
+
let response: Response | undefined;
|
|
575
|
+
await mw.instance.handle(ctx, async () => {
|
|
576
|
+
response = await nextLayer(ctx);
|
|
577
|
+
});
|
|
578
|
+
return response ?? new Response(null, { status: 200 });
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return chain;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Apply CORS headers to a response.
|
|
588
|
+
*/
|
|
589
|
+
private applyCors(response: Response, req: Request): Response {
|
|
590
|
+
const origin = req.headers.get('origin');
|
|
591
|
+
|
|
592
|
+
if (origin && this.corsHandler) {
|
|
593
|
+
return this.corsHandler.apply(response, origin);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return response;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Fallback handler - only called for unmatched routes.
|
|
601
|
+
* All matched routes go through Bun's native router.
|
|
602
|
+
*/
|
|
603
|
+
private handleNotFound(req: Request): Response {
|
|
604
|
+
// CORS preflight for unmatched routes
|
|
605
|
+
if (this.hasCors && req.method === 'OPTIONS') {
|
|
606
|
+
const origin = req.headers.get('origin');
|
|
607
|
+
|
|
608
|
+
if (origin) {
|
|
609
|
+
return this.corsHandler!.preflight(origin);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return NOT_FOUND_RESPONSE;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private buildResponse(result: any): Response {
|
|
617
|
+
if (result instanceof Response) {
|
|
618
|
+
return result;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (typeof result === 'string') {
|
|
622
|
+
return new Response(result, TEXT_OPTS);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Handle undefined/void return values - return empty 204 No Content
|
|
626
|
+
if (result === undefined) {
|
|
627
|
+
return new Response(null, { status: 204 });
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return Response.json(result);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private normalizePath(path: string): string {
|
|
634
|
+
if (!path.startsWith('/')) path = '/' + path;
|
|
635
|
+
if (path !== '/' && path.endsWith('/')) path = path.slice(0, -1);
|
|
636
|
+
|
|
637
|
+
return path.replace(/\/+/g, '/');
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private hasParams(path: string): boolean {
|
|
641
|
+
return path.includes(':') || path.includes('*');
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
stop(): void {
|
|
645
|
+
this.server?.stop?.();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Error handler for Bun.serve.
|
|
650
|
+
* Converts exceptions to proper HTTP responses.
|
|
651
|
+
*/
|
|
652
|
+
private handleError(error: Error): Response {
|
|
653
|
+
let response: Response;
|
|
654
|
+
|
|
655
|
+
// HttpException - return custom response
|
|
656
|
+
if (error instanceof HttpException) {
|
|
657
|
+
response = error.toResponse();
|
|
658
|
+
}
|
|
659
|
+
// ValidationException - return 400 with errors
|
|
660
|
+
else if (error instanceof ValidationException) {
|
|
661
|
+
response = error.toResponse();
|
|
662
|
+
}
|
|
663
|
+
// Unknown error - return 500
|
|
664
|
+
else {
|
|
665
|
+
console.error('Unhandled error:', error);
|
|
666
|
+
response = INTERNAL_ERROR_RESPONSE;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Apply CORS headers if configured
|
|
670
|
+
if (this.hasCors && this.corsHandler) {
|
|
671
|
+
return this.corsHandler.apply(response, '*');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return response;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Execute lifecycle hooks for a specific event type.
|
|
679
|
+
*/
|
|
680
|
+
private executeLifecycleHooks(type: EventType): void {
|
|
681
|
+
const handlers = getEventHandlers(type);
|
|
682
|
+
|
|
683
|
+
for (const handler of handlers) {
|
|
684
|
+
try {
|
|
685
|
+
const instance = this.container.has(handler.target)
|
|
686
|
+
? this.container.get(handler.target)
|
|
687
|
+
: null;
|
|
688
|
+
|
|
689
|
+
if (instance && typeof (instance as any)[handler.methodName] === 'function') {
|
|
690
|
+
const result = (instance as any)[handler.methodName]();
|
|
691
|
+
|
|
692
|
+
// Handle async hooks
|
|
693
|
+
if (result instanceof Promise) {
|
|
694
|
+
result.catch((err: Error) =>
|
|
695
|
+
console.error(`Error in ${type} hook ${handler.methodName}:`, err)
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} catch (err) {
|
|
700
|
+
console.error(`Error in ${type} hook ${handler.methodName}:`, err);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Register SIGTERM/SIGINT handlers for graceful shutdown.
|
|
707
|
+
*/
|
|
708
|
+
private registerShutdownHandlers(): void {
|
|
709
|
+
const shutdown = () => {
|
|
710
|
+
this.executeLifecycleHooks(EventType.SHUTDOWN);
|
|
711
|
+
this.stop();
|
|
712
|
+
process.exit(0);
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
process.on('SIGTERM', shutdown);
|
|
716
|
+
process.on('SIGINT', shutdown);
|
|
717
|
+
}
|
|
718
|
+
}
|