@stacksjs/bun-router 0.0.15 → 0.0.17

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 (83) hide show
  1. package/dist/auth.d.ts +22 -8
  2. package/dist/chunk-1ahs68ys.js +18 -0
  3. package/dist/{chunk-j0e7z7hd.js → chunk-cgptvjdf.js} +153 -75
  4. package/dist/chunk-g3ybefhg.js +1923 -0
  5. package/dist/cli.js +13 -11
  6. package/dist/container/container.d.ts +1 -0
  7. package/dist/container/contextual-binding.d.ts +0 -1
  8. package/dist/container/decorators.d.ts +2 -5
  9. package/dist/container/index.d.ts +16 -0
  10. package/dist/container/index.js +322 -0
  11. package/dist/file-serving/static-files.d.ts +16 -0
  12. package/dist/index.d.ts +17 -1
  13. package/dist/index.js +1086 -1270
  14. package/dist/middleware/cors.d.ts +9 -0
  15. package/dist/middleware/csrf.d.ts +7 -0
  16. package/dist/middleware/ddos_protection.d.ts +16 -1
  17. package/dist/middleware/rate_limit.d.ts +7 -1
  18. package/dist/middleware/request_tracer.d.ts +15 -9
  19. package/dist/middleware/response_cache.d.ts +2 -1
  20. package/dist/request/context.d.ts +12 -1
  21. package/dist/request/macros.d.ts +40 -2
  22. package/dist/response/macros.d.ts +3 -3
  23. package/dist/router/fluent-routing.d.ts +50 -13
  24. package/dist/router/handler-resolver.d.ts +8 -0
  25. package/dist/router/index.d.ts +2 -0
  26. package/dist/router/route-compiler.d.ts +8 -10
  27. package/dist/router/route-trie.d.ts +7 -1
  28. package/dist/router/router.d.ts +13 -2
  29. package/dist/router/validation-integration.d.ts +10 -4
  30. package/dist/types/middleware-types.d.ts +1 -1
  31. package/dist/types/route-inference.d.ts +9 -2
  32. package/dist/types.d.ts +62 -9
  33. package/dist/utils.d.ts +1 -1
  34. package/package.json +11 -1
  35. package/src/auth.ts +32 -12
  36. package/src/container/container.ts +58 -12
  37. package/src/container/contextual-binding.ts +4 -1
  38. package/src/container/decorators.ts +16 -7
  39. package/src/container/index.ts +49 -0
  40. package/src/errors/circuit-breaker.ts +3 -2
  41. package/src/errors/graceful-degradation.ts +2 -0
  42. package/src/file-serving/static-files.ts +144 -7
  43. package/src/index.ts +53 -1
  44. package/src/middleware/cors.ts +76 -8
  45. package/src/middleware/csrf.ts +80 -22
  46. package/src/middleware/ddos_protection.ts +27 -3
  47. package/src/middleware/input_validation.ts +10 -6
  48. package/src/middleware/performance_alerting.ts +2 -0
  49. package/src/middleware/performance_monitor.ts +2 -0
  50. package/src/middleware/rate_limit.ts +7 -1
  51. package/src/middleware/request_signing.ts +2 -0
  52. package/src/middleware/request_tracer.ts +34 -17
  53. package/src/middleware/response_cache.ts +48 -13
  54. package/src/middleware/session.ts +3 -3
  55. package/src/observability/metrics.ts +1 -1
  56. package/src/request/context.ts +50 -3
  57. package/src/request/enhanced-request.ts +20 -1
  58. package/src/request/macros.ts +183 -49
  59. package/src/response/macros.ts +3 -3
  60. package/src/router/file-based-routing.ts +17 -10
  61. package/src/router/fluent-routing.ts +117 -48
  62. package/src/router/group-organization.ts +17 -1
  63. package/src/router/handler-resolver.ts +36 -0
  64. package/src/router/http-methods.ts +33 -3
  65. package/src/router/index.ts +6 -0
  66. package/src/router/middleware.ts +3 -0
  67. package/src/router/optimized-route-matching.ts +58 -9
  68. package/src/router/route-compiler.ts +62 -73
  69. package/src/router/route-matching.ts +19 -0
  70. package/src/router/route-trie.ts +34 -48
  71. package/src/router/router.ts +72 -60
  72. package/src/router/server.ts +361 -139
  73. package/src/router/validation-integration.ts +11 -5
  74. package/src/testing/auth-testing.ts +6 -4
  75. package/src/types/middleware-types.ts +7 -4
  76. package/src/types/route-inference.ts +29 -14
  77. package/src/types.ts +63 -7
  78. package/src/url.ts +7 -2
  79. package/src/utils.ts +107 -39
  80. package/src/validation/validator.ts +7 -4
  81. package/src/websocket/clustering.ts +8 -1
  82. package/dist/router/fluent-router.d.ts +0 -315
  83. package/src/router/fluent-router.ts +0 -927
package/dist/index.js CHANGED
@@ -26,8 +26,6 @@ import {
26
26
  Session,
27
27
  SessionManager,
28
28
  SignatureError,
29
- __export,
30
- __require,
31
29
  apiKeyAuth,
32
30
  auth,
33
31
  basicAuth,
@@ -66,9 +64,50 @@ import {
66
64
  signRequest,
67
65
  singleFileUpload,
68
66
  verifySignature
69
- } from "./chunk-j0e7z7hd.js";
67
+ } from "./chunk-cgptvjdf.js";
68
+ import {
69
+ AuthenticationException,
70
+ AuthorizationException,
71
+ BindingBuilder,
72
+ BusinessLogicException,
73
+ CircuitBreakerOpenException,
74
+ CircularDependencyException,
75
+ ConfigurationException,
76
+ Container,
77
+ ContextualContainer,
78
+ DatabaseException,
79
+ DefaultServiceProviderManager,
80
+ EnvironmentManager,
81
+ ErrorFactory,
82
+ ExternalServiceException,
83
+ FileSystemException,
84
+ HandlerResolutionException,
85
+ InternalServerException,
86
+ MethodNotAllowedException,
87
+ MiddlewareConfigurationException,
88
+ ModelBindingException,
89
+ NotFoundException,
90
+ RateLimitException,
91
+ RouteConflictException,
92
+ RouteNotFoundException,
93
+ RouterErrorFactory,
94
+ RouterException,
95
+ ServiceUnavailableException,
96
+ TimeoutException,
97
+ ValidationException,
98
+ createContainer,
99
+ detectRouteConflicts,
100
+ findSimilarRoutes,
101
+ getContainer,
102
+ setContainer
103
+ } from "./chunk-g3ybefhg.js";
104
+ import {
105
+ __export,
106
+ __require
107
+ } from "./chunk-1ahs68ys.js";
70
108
 
71
109
  // src/auth.ts
110
+ import { timingSafeEqual } from "crypto";
72
111
  function base64UrlEncode(str) {
73
112
  return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
74
113
  }
@@ -132,7 +171,9 @@ class JWT {
132
171
  }
133
172
  const data = `${encodedHeader}.${encodedPayload}`;
134
173
  const expectedSignature = this.createSignature(data, this.secret, header.alg);
135
- if (signature !== expectedSignature) {
174
+ const sigBuf = Buffer.from(signature);
175
+ const expectedBuf = Buffer.from(expectedSignature);
176
+ if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
136
177
  throw new Error("Invalid signature");
137
178
  }
138
179
  const now = Math.floor(Date.now() / 1000);
@@ -386,381 +427,6 @@ __export(exports_errors, {
386
427
  AuthenticationException: () => AuthenticationException
387
428
  });
388
429
 
389
- // src/errors/exceptions.ts
390
- class RouterException extends Error {
391
- code;
392
- statusCode;
393
- context;
394
- retryable;
395
- severity;
396
- category;
397
- timestamp;
398
- cause;
399
- constructor(details) {
400
- super(details.message);
401
- this.name = this.constructor.name;
402
- this.code = details.code;
403
- this.statusCode = details.statusCode || 500;
404
- this.context = details.context || {};
405
- this.retryable = details.retryable || false;
406
- this.severity = details.severity || "medium";
407
- this.category = details.category || "system";
408
- this.timestamp = new Date;
409
- this.cause = details.cause;
410
- if (Error.captureStackTrace) {
411
- Error.captureStackTrace(this, this.constructor);
412
- }
413
- }
414
- toJSON() {
415
- return {
416
- name: this.name,
417
- code: this.code,
418
- message: this.message,
419
- statusCode: this.statusCode,
420
- context: this.context,
421
- retryable: this.retryable,
422
- severity: this.severity,
423
- category: this.category,
424
- timestamp: this.timestamp.toISOString(),
425
- stack: this.stack,
426
- cause: this.cause ? {
427
- name: this.cause.name,
428
- message: this.cause.message,
429
- stack: this.cause.stack
430
- } : undefined
431
- };
432
- }
433
- toResponse() {
434
- const body = {
435
- error: {
436
- code: this.code,
437
- message: this.message,
438
- statusCode: this.statusCode,
439
- timestamp: this.timestamp.toISOString(),
440
- requestId: this.context.requestId
441
- }
442
- };
443
- return new Response(JSON.stringify(body), {
444
- status: this.statusCode,
445
- headers: {
446
- "Content-Type": "application/json",
447
- "X-Error-Code": this.code,
448
- "X-Request-ID": this.context.requestId || ""
449
- }
450
- });
451
- }
452
- }
453
-
454
- class ValidationException extends RouterException {
455
- fields;
456
- constructor(message, fields = {}, context) {
457
- super({
458
- code: "VALIDATION_ERROR",
459
- message,
460
- statusCode: 400,
461
- context,
462
- retryable: false,
463
- severity: "low",
464
- category: "validation"
465
- });
466
- this.fields = fields;
467
- }
468
- toResponse() {
469
- const body = {
470
- error: {
471
- code: this.code,
472
- message: this.message,
473
- statusCode: this.statusCode,
474
- timestamp: this.timestamp.toISOString(),
475
- requestId: this.context.requestId,
476
- fields: this.fields
477
- }
478
- };
479
- return new Response(JSON.stringify(body), {
480
- status: this.statusCode,
481
- headers: {
482
- "Content-Type": "application/json",
483
- "X-Error-Code": this.code,
484
- "X-Request-ID": this.context.requestId || ""
485
- }
486
- });
487
- }
488
- }
489
-
490
- class AuthenticationException extends RouterException {
491
- constructor(message = "Authentication required", context) {
492
- super({
493
- code: "AUTHENTICATION_ERROR",
494
- message,
495
- statusCode: 401,
496
- context,
497
- retryable: false,
498
- severity: "medium",
499
- category: "authentication"
500
- });
501
- }
502
- }
503
-
504
- class AuthorizationException extends RouterException {
505
- requiredPermissions;
506
- constructor(message = "Access denied", requiredPermissions, context) {
507
- super({
508
- code: "AUTHORIZATION_ERROR",
509
- message,
510
- statusCode: 403,
511
- context,
512
- retryable: false,
513
- severity: "medium",
514
- category: "authorization"
515
- });
516
- this.requiredPermissions = requiredPermissions;
517
- }
518
- }
519
-
520
- class NotFoundException extends RouterException {
521
- resource;
522
- resourceId;
523
- constructor(message = "Resource not found", resource, resourceId, context) {
524
- super({
525
- code: "NOT_FOUND",
526
- message,
527
- statusCode: 404,
528
- context,
529
- retryable: false,
530
- severity: "low",
531
- category: "business"
532
- });
533
- this.resource = resource;
534
- this.resourceId = resourceId;
535
- }
536
- }
537
-
538
- class MethodNotAllowedException extends RouterException {
539
- allowedMethods;
540
- constructor(method, allowedMethods, context) {
541
- super({
542
- code: "METHOD_NOT_ALLOWED",
543
- message: `Method ${method} not allowed. Allowed methods: ${allowedMethods.join(", ")}`,
544
- statusCode: 405,
545
- context,
546
- retryable: false,
547
- severity: "low",
548
- category: "business"
549
- });
550
- this.allowedMethods = allowedMethods;
551
- }
552
- toResponse() {
553
- const response = super.toResponse();
554
- response.headers.set("Allow", this.allowedMethods.join(", "));
555
- return response;
556
- }
557
- }
558
-
559
- class TimeoutException extends RouterException {
560
- timeoutMs;
561
- constructor(message = "Request timeout", timeoutMs, context) {
562
- super({
563
- code: "REQUEST_TIMEOUT",
564
- message,
565
- statusCode: 408,
566
- context,
567
- retryable: true,
568
- severity: "medium",
569
- category: "network"
570
- });
571
- this.timeoutMs = timeoutMs;
572
- }
573
- }
574
-
575
- class RateLimitException extends RouterException {
576
- retryAfter;
577
- limit;
578
- remaining;
579
- constructor(message = "Rate limit exceeded", retryAfter, limit, remaining = 0, context) {
580
- super({
581
- code: "RATE_LIMIT_EXCEEDED",
582
- message,
583
- statusCode: 429,
584
- context,
585
- retryable: true,
586
- severity: "medium",
587
- category: "network"
588
- });
589
- this.retryAfter = retryAfter;
590
- this.limit = limit;
591
- this.remaining = remaining;
592
- }
593
- toResponse() {
594
- const response = super.toResponse();
595
- response.headers.set("Retry-After", this.retryAfter.toString());
596
- response.headers.set("X-RateLimit-Limit", this.limit.toString());
597
- response.headers.set("X-RateLimit-Remaining", this.remaining.toString());
598
- return response;
599
- }
600
- }
601
-
602
- class InternalServerException extends RouterException {
603
- constructor(message = "Internal server error", cause, context) {
604
- super({
605
- code: "INTERNAL_SERVER_ERROR",
606
- message,
607
- statusCode: 500,
608
- context,
609
- cause,
610
- retryable: true,
611
- severity: "high",
612
- category: "system"
613
- });
614
- }
615
- }
616
-
617
- class ServiceUnavailableException extends RouterException {
618
- retryAfter;
619
- constructor(message = "Service unavailable", retryAfter, context) {
620
- super({
621
- code: "SERVICE_UNAVAILABLE",
622
- message,
623
- statusCode: 503,
624
- context,
625
- retryable: true,
626
- severity: "high",
627
- category: "system"
628
- });
629
- this.retryAfter = retryAfter;
630
- }
631
- toResponse() {
632
- const response = super.toResponse();
633
- if (this.retryAfter) {
634
- response.headers.set("Retry-After", this.retryAfter.toString());
635
- }
636
- return response;
637
- }
638
- }
639
-
640
- class DatabaseException extends RouterException {
641
- query;
642
- operation;
643
- constructor(message, operation, query, cause, context) {
644
- super({
645
- code: "DATABASE_ERROR",
646
- message,
647
- statusCode: 500,
648
- context,
649
- cause,
650
- retryable: true,
651
- severity: "high",
652
- category: "database"
653
- });
654
- this.operation = operation;
655
- this.query = query;
656
- }
657
- }
658
-
659
- class ExternalServiceException extends RouterException {
660
- service;
661
- endpoint;
662
- constructor(message, service, endpoint, statusCode = 502, cause, context) {
663
- super({
664
- code: "EXTERNAL_SERVICE_ERROR",
665
- message,
666
- statusCode,
667
- context,
668
- cause,
669
- retryable: true,
670
- severity: "medium",
671
- category: "external"
672
- });
673
- this.service = service;
674
- this.endpoint = endpoint;
675
- }
676
- }
677
-
678
- class CircuitBreakerOpenException extends RouterException {
679
- service;
680
- nextAttemptAt;
681
- constructor(service, nextAttemptAt, context) {
682
- super({
683
- code: "CIRCUIT_BREAKER_OPEN",
684
- message: `Circuit breaker is open for service: ${service}. Next attempt at: ${nextAttemptAt.toISOString()}`,
685
- statusCode: 503,
686
- context,
687
- retryable: true,
688
- severity: "medium",
689
- category: "external"
690
- });
691
- this.service = service;
692
- this.nextAttemptAt = nextAttemptAt;
693
- }
694
- }
695
-
696
- class BusinessLogicException extends RouterException {
697
- businessRule;
698
- constructor(message, businessRule, context) {
699
- super({
700
- code: "BUSINESS_LOGIC_ERROR",
701
- message,
702
- statusCode: 422,
703
- context,
704
- retryable: false,
705
- severity: "low",
706
- category: "business"
707
- });
708
- this.businessRule = businessRule;
709
- }
710
- }
711
-
712
- class ConfigurationException extends RouterException {
713
- configKey;
714
- constructor(message, configKey, context) {
715
- super({
716
- code: "CONFIGURATION_ERROR",
717
- message,
718
- statusCode: 500,
719
- context,
720
- retryable: false,
721
- severity: "critical",
722
- category: "system"
723
- });
724
- this.configKey = configKey;
725
- }
726
- }
727
-
728
- class FileSystemException extends RouterException {
729
- path;
730
- operation;
731
- constructor(message, path, operation, cause, context) {
732
- super({
733
- code: "FILESYSTEM_ERROR",
734
- message,
735
- statusCode: 500,
736
- context,
737
- cause,
738
- retryable: false,
739
- severity: "medium",
740
- category: "system"
741
- });
742
- this.path = path;
743
- this.operation = operation;
744
- }
745
- }
746
- var ErrorFactory = {
747
- validation: (message, fields, context) => new ValidationException(message, fields, context),
748
- authentication: (message, context) => new AuthenticationException(message, context),
749
- authorization: (message, requiredPermissions, context) => new AuthorizationException(message, requiredPermissions, context),
750
- notFound: (resource, resourceId, context) => new NotFoundException(undefined, resource, resourceId, context),
751
- methodNotAllowed: (method, allowedMethods, context) => new MethodNotAllowedException(method, allowedMethods, context),
752
- timeout: (timeoutMs, message, context) => new TimeoutException(message, timeoutMs, context),
753
- rateLimit: (retryAfter, limit, remaining, context) => new RateLimitException(undefined, retryAfter, limit, remaining, context),
754
- internalServer: (message, cause, context) => new InternalServerException(message, cause, context),
755
- serviceUnavailable: (message, retryAfter, context) => new ServiceUnavailableException(message, retryAfter, context),
756
- database: (message, operation, query, cause, context) => new DatabaseException(message, operation, query, cause, context),
757
- externalService: (message, service, endpoint, statusCode, cause, context) => new ExternalServiceException(message, service, endpoint, statusCode, cause, context),
758
- circuitBreakerOpen: (service, nextAttemptAt, context) => new CircuitBreakerOpenException(service, nextAttemptAt, context),
759
- businessLogic: (message, businessRule, context) => new BusinessLogicException(message, businessRule, context),
760
- configuration: (message, configKey, context) => new ConfigurationException(message, configKey, context),
761
- fileSystem: (message, path, operation, cause, context) => new FileSystemException(message, path, operation, cause, context)
762
- };
763
-
764
430
  // src/errors/circuit-breaker.ts
765
431
  class CircuitBreaker {
766
432
  config;
@@ -794,7 +460,7 @@ class CircuitBreaker {
794
460
  consecutiveFailures: 0,
795
461
  consecutiveSuccesses: 0
796
462
  };
797
- setInterval(() => this.cleanupOldRequests(), this.config.monitoringPeriod / 4);
463
+ setInterval(() => this.cleanupOldRequests(), this.config.monitoringPeriod / 4).unref?.();
798
464
  }
799
465
  async execute(call) {
800
466
  if (this.state === "OPEN") {
@@ -1705,6 +1371,7 @@ class ServiceHealthMonitor {
1705
1371
  consecutiveFailures: 0
1706
1372
  });
1707
1373
  const interval = setInterval(() => this.performHealthCheck(serviceName, healthConfig), healthConfig.interval);
1374
+ interval.unref?.();
1708
1375
  this.healthCheckIntervals.set(serviceName, interval);
1709
1376
  }
1710
1377
  }
@@ -2334,448 +2001,44 @@ function createAdvancedErrorHandler(config2 = {}) {
2334
2001
  cleanup: () => handler.cleanup()
2335
2002
  };
2336
2003
  }
2337
- // src/errors/router-errors.ts
2338
- class RouteConflictException extends RouterException {
2339
- conflictingRoutes;
2340
- suggestion;
2341
- constructor(conflictingRoutes, context) {
2342
- const message = RouteConflictException.buildMessage(conflictingRoutes);
2343
- const suggestion = RouteConflictException.buildSuggestion(conflictingRoutes);
2344
- super({
2345
- code: "ROUTE_CONFLICT",
2346
- message,
2347
- statusCode: 500,
2348
- context,
2349
- retryable: false,
2350
- severity: "critical",
2351
- category: "system"
2352
- });
2353
- this.conflictingRoutes = conflictingRoutes;
2354
- this.suggestion = suggestion;
2355
- }
2356
- static buildMessage(routes) {
2357
- const lines = [
2358
- "Route conflict detected! Multiple routes match the same path pattern.",
2359
- "",
2360
- "Conflicting routes:"
2361
- ];
2362
- for (const route of routes) {
2363
- lines.push(` \u2022 ${route.method} ${route.path}`);
2364
- if (route.name) {
2365
- lines.push(` Name: "${route.name}"`);
2366
- }
2367
- if (route.definedAt) {
2368
- lines.push(` Defined at: ${route.definedAt}`);
2369
- }
2370
- }
2371
- return lines.join(`
2372
- `);
2373
- }
2374
- static buildSuggestion(routes) {
2375
- const suggestions = [];
2376
- const hasStatic = routes.some((r) => !r.path.includes("{"));
2377
- const hasDynamic = routes.some((r) => r.path.includes("{"));
2378
- if (hasStatic && hasDynamic) {
2379
- suggestions.push("\u2022 Static routes should be defined before dynamic routes", "\u2022 Consider using route constraints (whereNumber, whereUuid) to differentiate");
2380
- }
2381
- const dynamicRoutes = routes.filter((r) => r.path.includes("{"));
2382
- if (dynamicRoutes.length > 1) {
2383
- suggestions.push("\u2022 Use different parameter names or add constraints:", ' router.get("/users/{id}").whereNumber("id")', ' router.get("/users/{slug}").whereAlpha("slug")');
2384
- }
2385
- const hasWildcard = routes.some((r) => r.path.includes("*"));
2386
- if (hasWildcard) {
2387
- suggestions.push("\u2022 Wildcard routes should be defined last", "\u2022 Consider using more specific path prefixes");
2388
- }
2389
- if (suggestions.length === 0) {
2390
- suggestions.push("\u2022 Rename one of the conflicting routes", "\u2022 Use route groups with different prefixes", "\u2022 Add parameter constraints to differentiate routes");
2391
- }
2392
- return `How to fix:
2393
- ${suggestions.join(`
2394
- `)}`;
2395
- }
2396
- toJSON() {
2397
- return {
2398
- ...super.toJSON(),
2399
- conflictingRoutes: this.conflictingRoutes,
2400
- suggestion: this.suggestion
2401
- };
2402
- }
2403
- }
2404
-
2405
- class CircularDependencyException extends RouterException {
2406
- dependencyChain;
2407
- suggestion;
2408
- constructor(dependencyChain, context) {
2409
- const message = CircularDependencyException.buildMessage(dependencyChain);
2410
- const suggestion = CircularDependencyException.buildSuggestion(dependencyChain);
2411
- super({
2412
- code: "CIRCULAR_DEPENDENCY",
2413
- message,
2414
- statusCode: 500,
2415
- context,
2416
- retryable: false,
2417
- severity: "critical",
2418
- category: "system"
2419
- });
2420
- this.dependencyChain = dependencyChain;
2421
- this.suggestion = suggestion;
2422
- }
2423
- static buildMessage(chain) {
2424
- const lines = [
2425
- "Circular dependency detected in the dependency injection container!",
2426
- "",
2427
- "Dependency chain:",
2428
- ""
2429
- ];
2430
- for (let i = 0;i < chain.length; i++) {
2431
- const isLast = i === chain.length - 1;
2432
- const prefix = i === 0 ? "\u250C\u2500\u25BA" : isLast ? "\u2514\u2500\u25BA" : "\u251C\u2500\u25BA";
2433
- lines.push(` ${prefix} ${chain[i]}`);
2434
- if (!isLast) {
2435
- lines.push(" \u2502");
2436
- }
2437
- }
2438
- lines.push(" \u2502");
2439
- lines.push(` \u2514\u2500\u2500 cycles back to: ${chain[0]}`);
2440
- return lines.join(`
2441
- `);
2442
- }
2443
- static buildSuggestion(chain) {
2444
- return `How to fix:
2445
-
2446
- 1. Break the cycle by introducing an interface:
2447
- \u2022 Create an interface for one of the dependencies
2448
- \u2022 Use lazy injection: container.bind('${chain[0]}').lazy()
2449
-
2450
- 2. Restructure your dependencies:
2451
- \u2022 Extract shared logic into a separate service
2452
- \u2022 Use events/callbacks instead of direct dependencies
2453
-
2454
- 3. Use setter injection instead of constructor injection:
2455
- \u2022 Remove the dependency from the constructor
2456
- \u2022 Set it after instantiation using a setter method
2457
-
2458
- 4. Check if the dependency is actually needed:
2459
- \u2022 Sometimes circular dependencies indicate a design issue
2460
- \u2022 Consider if the classes have too many responsibilities
2461
-
2462
- Dependency chain involved:
2463
- ${chain.map((dep, i) => ` ${i + 1}. ${dep}`).join(`
2464
- `)}`;
2465
- }
2466
- toJSON() {
2467
- return {
2468
- ...super.toJSON(),
2469
- dependencyChain: this.dependencyChain,
2470
- suggestion: this.suggestion
2471
- };
2472
- }
2473
- }
2474
-
2475
- class MiddlewareConfigurationException extends RouterException {
2476
- middlewareName;
2477
- configErrors;
2478
- suggestion;
2479
- constructor(middlewareName, configErrors, context) {
2480
- const message = `Middleware "${middlewareName}" configuration error:
2481
- ${configErrors.map((e) => ` \u2022 ${e}`).join(`
2482
- `)}`;
2483
- super({
2484
- code: "MIDDLEWARE_CONFIGURATION_ERROR",
2485
- message,
2486
- statusCode: 500,
2487
- context,
2488
- retryable: false,
2489
- severity: "high",
2490
- category: "system"
2491
- });
2492
- this.middlewareName = middlewareName;
2493
- this.configErrors = configErrors;
2494
- this.suggestion = this.buildSuggestion();
2495
- }
2496
- buildSuggestion() {
2497
- return `How to fix:
2498
- \u2022 Check the middleware documentation for required options
2499
- \u2022 Ensure all required configuration values are provided
2500
- \u2022 Verify configuration value types match expected types
2501
-
2502
- Example configuration:
2503
- router.use(new ${this.middlewareName}({
2504
- // Add required options here
2505
- }))`;
2506
- }
2507
- }
2508
-
2509
- class RouteNotFoundException extends RouterException {
2510
- requestedPath;
2511
- requestedMethod;
2512
- similarRoutes;
2513
- suggestion;
2514
- constructor(requestedMethod, requestedPath, similarRoutes = [], context) {
2515
- const message = RouteNotFoundException.buildMessage(requestedMethod, requestedPath, similarRoutes);
2516
- super({
2517
- code: "ROUTE_NOT_FOUND",
2518
- message,
2519
- statusCode: 404,
2520
- context,
2521
- retryable: false,
2522
- severity: "low",
2523
- category: "business"
2524
- });
2525
- this.requestedPath = requestedPath;
2526
- this.requestedMethod = requestedMethod;
2527
- this.similarRoutes = similarRoutes;
2528
- this.suggestion = this.buildSuggestion();
2529
- }
2530
- static buildMessage(method, path, similarRoutes) {
2531
- const lines = [
2532
- `Route not found: ${method} ${path}`
2533
- ];
2534
- if (similarRoutes.length > 0) {
2535
- lines.push("");
2536
- lines.push("Did you mean one of these routes?");
2537
- for (const route of similarRoutes.slice(0, 5)) {
2538
- lines.push(` \u2022 ${route.method} ${route.path}`);
2539
- if (route.similarity) {
2540
- lines.push(` (${Math.round(route.similarity * 100)}% match)`);
2541
- }
2542
- }
2543
- }
2544
- return lines.join(`
2545
- `);
2546
- }
2547
- buildSuggestion() {
2548
- const suggestions = [
2549
- `\u2022 Verify the HTTP method is correct (you used ${this.requestedMethod})`,
2550
- "\u2022 Check for typos in the URL path",
2551
- "\u2022 Ensure the route is registered before the server starts",
2552
- "\u2022 Check if the route requires specific middleware (e.g., auth)"
2553
- ];
2554
- if (this.requestedPath.includes("//")) {
2555
- suggestions.push("\u2022 Remove duplicate slashes from the URL");
2556
- }
2557
- if (!this.requestedPath.startsWith("/")) {
2558
- suggestions.push("\u2022 Ensure the path starts with a forward slash (/)");
2559
- }
2560
- return `How to fix:
2561
- ${suggestions.join(`
2562
- `)}`;
2563
- }
2564
- }
2565
-
2566
- class HandlerResolutionException extends RouterException {
2567
- handlerRef;
2568
- suggestion;
2569
- constructor(handlerRef, reason, context) {
2570
- const message = `Failed to resolve handler "${handlerRef}": ${reason}`;
2571
- super({
2572
- code: "HANDLER_RESOLUTION_ERROR",
2573
- message,
2574
- statusCode: 500,
2575
- context,
2576
- retryable: false,
2577
- severity: "high",
2578
- category: "system"
2579
- });
2580
- this.handlerRef = handlerRef;
2581
- this.suggestion = this.buildSuggestion(handlerRef);
2582
- }
2583
- buildSuggestion(handlerRef) {
2584
- const isControllerMethod = handlerRef.includes("@");
2585
- const isActionPath = handlerRef.includes("/");
2586
- if (isControllerMethod) {
2587
- const [controller, method] = handlerRef.split("@");
2588
- return `How to fix:
2589
- \u2022 Ensure the controller "${controller}" is registered in the container
2590
- \u2022 Verify the method "${method}" exists and is public
2591
- \u2022 Check the controller file is imported
2592
-
2593
- Example:
2594
- // Register the controller
2595
- container.singleton('${controller}', ${controller})
2596
-
2597
- // Define the route
2598
- router.get('/path', '${handlerRef}')`;
2599
- }
2600
- if (isActionPath) {
2601
- return `How to fix:
2602
- \u2022 Ensure the action class exists at the specified path
2603
- \u2022 Verify the action has a "handle" method
2604
- \u2022 Check the action file is properly exported
2605
-
2606
- Example:
2607
- // Action class at ${handlerRef}.ts
2608
- export default class ${handlerRef.split("/").pop()} {
2609
- handle(req: Request): Response {
2610
- return new Response('OK')
2611
- }
2612
- }`;
2613
- }
2614
- return `How to fix:
2615
- \u2022 Verify the handler reference is correct
2616
- \u2022 Ensure the handler function/class is exported
2617
- \u2022 Check for circular dependencies in the handler module`;
2618
- }
2619
- }
2620
-
2621
- class ModelBindingException extends RouterException {
2622
- modelName;
2623
- paramName;
2624
- paramValue;
2625
- suggestion;
2626
- constructor(modelName, paramName, paramValue, reason, context) {
2627
- const message = `Failed to bind model "${modelName}" for parameter "${paramName}" with value "${paramValue}": ${reason}`;
2628
- super({
2629
- code: "MODEL_BINDING_ERROR",
2630
- message,
2631
- statusCode: 404,
2632
- context,
2633
- retryable: false,
2634
- severity: "low",
2635
- category: "business"
2636
- });
2637
- this.modelName = modelName;
2638
- this.paramName = paramName;
2639
- this.paramValue = paramValue;
2640
- this.suggestion = this.buildSuggestion();
2641
- }
2642
- buildSuggestion() {
2643
- return `How to fix:
2644
- \u2022 Verify the ${this.paramName} "${this.paramValue}" exists in your data source
2645
- \u2022 Check if the model resolver is correctly configured
2646
- \u2022 Ensure the route parameter matches the model's identifier field
2647
-
2648
- Example model binding:
2649
- router.model('${this.paramName}', async (value) => {
2650
- const ${this.modelName.toLowerCase()} = await db.${this.modelName.toLowerCase()}s.findUnique({
2651
- where: { id: value }
2652
- })
2653
- if (!${this.modelName.toLowerCase()}) {
2654
- throw new Error('${this.modelName} not found')
2655
- }
2656
- return ${this.modelName.toLowerCase()}
2657
- })`;
2658
- }
2659
- }
2660
- function findSimilarRoutes(requestedPath, availableRoutes, maxResults = 5) {
2661
- const results = [];
2662
- for (const route of availableRoutes) {
2663
- const similarity = calculateSimilarity(requestedPath, route.path);
2664
- if (similarity > 0.3) {
2665
- results.push({
2666
- method: route.method,
2667
- path: route.path,
2668
- similarity
2669
- });
2670
- }
2671
- }
2672
- return results.sort((a, b) => (b.similarity || 0) - (a.similarity || 0)).slice(0, maxResults);
2673
- }
2674
- function calculateSimilarity(str1, str2) {
2675
- const len1 = str1.length;
2676
- const len2 = str2.length;
2677
- if (len1 === 0)
2678
- return len2 === 0 ? 1 : 0;
2679
- if (len2 === 0)
2680
- return 0;
2681
- const matrix = [];
2682
- for (let i = 0;i <= len1; i++) {
2683
- matrix[i] = [i];
2684
- }
2685
- for (let j = 0;j <= len2; j++) {
2686
- matrix[0][j] = j;
2687
- }
2688
- for (let i = 1;i <= len1; i++) {
2689
- for (let j = 1;j <= len2; j++) {
2690
- const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
2691
- matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
2692
- }
2693
- }
2694
- const distance = matrix[len1][len2];
2695
- const maxLen = Math.max(len1, len2);
2696
- return 1 - distance / maxLen;
2697
- }
2698
- function detectRouteConflicts(routes) {
2699
- const conflicts = [];
2700
- const checked = new Set;
2701
- for (let i = 0;i < routes.length; i++) {
2702
- const route1 = routes[i];
2703
- const key1 = `${route1.method}:${route1.path}`;
2704
- if (checked.has(key1))
2705
- continue;
2706
- const conflicting = [];
2707
- for (let j = i + 1;j < routes.length; j++) {
2708
- const route2 = routes[j];
2709
- if (route1.method === route2.method && routesConflict(route1.path, route2.path)) {
2710
- if (conflicting.length === 0) {
2711
- conflicting.push({
2712
- method: route1.method,
2713
- path: route1.path,
2714
- name: route1.name
2715
- });
2716
- }
2717
- conflicting.push({
2718
- method: route2.method,
2719
- path: route2.path,
2720
- name: route2.name
2721
- });
2722
- checked.add(`${route2.method}:${route2.path}`);
2723
- }
2724
- }
2725
- if (conflicting.length > 0) {
2726
- conflicts.push(conflicting);
2727
- }
2728
- checked.add(key1);
2729
- }
2730
- return conflicts;
2731
- }
2732
- function routesConflict(path1, path2) {
2733
- if (path1 === path2)
2734
- return true;
2735
- const segments1 = path1.split("/").filter(Boolean);
2736
- const segments2 = path2.split("/").filter(Boolean);
2737
- if (segments1.length !== segments2.length) {
2738
- const hasWildcard1 = path1.includes("*");
2739
- const hasWildcard2 = path2.includes("*");
2740
- if (!hasWildcard1 && !hasWildcard2)
2741
- return false;
2742
- }
2743
- const maxLen = Math.max(segments1.length, segments2.length);
2744
- for (let i = 0;i < maxLen; i++) {
2745
- const seg1 = segments1[i] || "";
2746
- const seg2 = segments2[i] || "";
2747
- if (seg1 === "*" || seg2 === "*")
2748
- return true;
2749
- const isDynamic1 = seg1.startsWith("{") && seg1.endsWith("}");
2750
- const isDynamic2 = seg2.startsWith("{") && seg2.endsWith("}");
2751
- if (!isDynamic1 && !isDynamic2) {
2752
- if (seg1 !== seg2)
2753
- return false;
2754
- }
2755
- }
2756
- return true;
2757
- }
2758
- var RouterErrorFactory = {
2759
- routeConflict: (routes, context) => new RouteConflictException(routes, context),
2760
- circularDependency: (chain, context) => new CircularDependencyException(chain, context),
2761
- middlewareConfig: (name, errors, context) => new MiddlewareConfigurationException(name, errors, context),
2762
- routeNotFound: (method, path, similarRoutes, context) => new RouteNotFoundException(method, path, similarRoutes, context),
2763
- handlerResolution: (ref, reason, context) => new HandlerResolutionException(ref, reason, context),
2764
- modelBinding: (model, param, value, reason, context) => new ModelBindingException(model, param, value, reason, context)
2765
- };
2766
2004
  // src/request/context.ts
2767
2005
  import { AsyncLocalStorage } from "async_hooks";
2768
2006
  var storage = new AsyncLocalStorage;
2007
+ var contextEnabled = false;
2008
+ var syncCurrent = null;
2009
+ function enableRequestContext() {
2010
+ contextEnabled = true;
2011
+ }
2769
2012
  function runWithRequest(initial, fn) {
2013
+ if (!contextEnabled) {
2014
+ syncCurrent = initial;
2015
+ const result = fn();
2016
+ if (result instanceof Promise) {
2017
+ return result.finally(() => {
2018
+ syncCurrent = null;
2019
+ });
2020
+ }
2021
+ syncCurrent = null;
2022
+ return result;
2023
+ }
2770
2024
  return storage.run({ current: initial }, fn);
2771
2025
  }
2772
2026
  function setCurrentRequest(req) {
2773
2027
  const store = storage.getStore();
2774
- if (store)
2028
+ if (store) {
2775
2029
  store.current = req;
2030
+ return;
2031
+ }
2032
+ if (syncCurrent !== null) {
2033
+ syncCurrent = req;
2034
+ }
2776
2035
  }
2777
2036
  function getCurrentRequest() {
2778
- return storage.getStore()?.current ?? undefined;
2037
+ if (!contextEnabled) {
2038
+ contextEnabled = true;
2039
+ return syncCurrent ?? undefined;
2040
+ }
2041
+ return storage.getStore()?.current ?? syncCurrent ?? undefined;
2779
2042
  }
2780
2043
  function request() {
2781
2044
  const req = getCurrentRequest();
@@ -3942,13 +3205,20 @@ function registerFileBasedRouting(RouterClass) {
3942
3205
  }
3943
3206
  }
3944
3207
  this._viewsDir = viewsDir;
3208
+ const existingGetPaths = new Set;
3209
+ for (const r of this.routes) {
3210
+ if (r.method === "GET") {
3211
+ existingGetPaths.add(r.path);
3212
+ }
3213
+ }
3945
3214
  for (const route of routes) {
3946
- const handler = createViewHandler(route.filePath, viewsDir, config2, this.config.queryPreservation);
3947
- const existingRoute = this.routes.find((r) => r.path === route.routePath && r.method === "GET");
3948
- if (!existingRoute) {
3949
- this.get(route.routePath, handler, "web");
3950
- this._fileBasedRoutes.push(route);
3215
+ if (existingGetPaths.has(route.routePath)) {
3216
+ continue;
3951
3217
  }
3218
+ const handler = createViewHandler(route.filePath, viewsDir, config2, this.config.queryPreservation);
3219
+ this.get(route.routePath, handler, "web");
3220
+ this._fileBasedRoutes.push(route);
3221
+ existingGetPaths.add(route.routePath);
3952
3222
  }
3953
3223
  this._fileRoutesInitialized = true;
3954
3224
  },
@@ -5696,7 +4966,11 @@ function url(name, params = {}, options = {}) {
5696
4966
  consumed.add(key);
5697
4967
  }
5698
4968
  }
5699
- resolved = resolved.replace(/\/\{[^}]+\?\}/g, "");
4969
+ if (resolved.includes("?}")) {
4970
+ resolved = resolved.replace(/\{[^}]+\?\}/g, "").replace(/\/{2,}/g, "/");
4971
+ if (resolved.length > 1 && resolved.endsWith("/"))
4972
+ resolved = resolved.slice(0, -1);
4973
+ }
5700
4974
  const missing = resolved.match(/\{([^}?]+)\}/g);
5701
4975
  if (missing && missing.length > 0)
5702
4976
  throw new Error(`Missing required params for route '${name}': ${missing.join(", ")}`);
@@ -5715,24 +4989,229 @@ function url(name, params = {}, options = {}) {
5715
4989
  return resolved;
5716
4990
  }
5717
4991
 
5718
- // src/router/fluent-routing.ts
5719
- class FluentRouteBuilder {
5720
- method;
5721
- path;
5722
- handler;
5723
- middleware = [];
5724
- cacheConfig;
5725
- throttleConfig;
5726
- routeName;
5727
- modelBindings = {};
5728
- constructor(method, path, handler) {
5729
- this.method = method;
5730
- this.path = path;
5731
- this.handler = handler;
4992
+ // src/utils.ts
4993
+ import { join as join3 } from "path";
4994
+ function normalizePath(path) {
4995
+ let normalizedPath = path.replace(/\/+/g, "/");
4996
+ if (!normalizedPath.startsWith("/")) {
4997
+ normalizedPath = `/${normalizedPath}`;
5732
4998
  }
5733
- addMiddleware(middleware2) {
5734
- if (Array.isArray(middleware2)) {
5735
- this.middleware.push(...middleware2);
4999
+ if (normalizedPath.length > 1 && normalizedPath.endsWith("/")) {
5000
+ normalizedPath = normalizedPath.slice(0, -1);
5001
+ }
5002
+ return normalizedPath;
5003
+ }
5004
+ function toActionPath(path) {
5005
+ return path.replace(/\//g, "_").toLowerCase();
5006
+ }
5007
+ function isActionClass(handler) {
5008
+ if (typeof handler !== "function" && typeof handler !== "object") {
5009
+ return false;
5010
+ }
5011
+ if (typeof handler === "function" && handler.prototype) {
5012
+ const prototype = handler.prototype;
5013
+ if (typeof prototype.handle === "function") {
5014
+ return true;
5015
+ }
5016
+ }
5017
+ if (typeof handler === "object" && handler !== null && "handle" in handler && typeof handler.handle === "function") {
5018
+ return true;
5019
+ }
5020
+ return false;
5021
+ }
5022
+ function isRouteHandler(handler) {
5023
+ return typeof handler === "function" && !isActionClass(handler);
5024
+ }
5025
+ function extractParamNames(pattern) {
5026
+ const matches = pattern.match(/\{([^}]+)\}/g);
5027
+ return matches ? matches.map((m) => m.slice(1, -1).replace("?", "")) : [];
5028
+ }
5029
+ function createPathRegex(pattern) {
5030
+ const regexPattern = pattern.replace(/\{([^}]+)\}/g, "([^/]+)");
5031
+ return new RegExp(`^${regexPattern}$`);
5032
+ }
5033
+ var PARAM_SEGMENT_RE = /\{([^}:]+)(?::[^}]+)?(\?)?\}/;
5034
+ var OPTIONAL_SEGMENT_RE = /\{[^}]+\?\}/;
5035
+ var compiledPatternCache = new Map;
5036
+ var constraintRegexCache = new Map;
5037
+ function compilePattern(pattern) {
5038
+ const cached = compiledPatternCache.get(pattern);
5039
+ if (cached)
5040
+ return cached;
5041
+ const isWildcard = pattern.endsWith("/*");
5042
+ const normalizedPattern = normalizePath(pattern);
5043
+ let optionalCount = 0;
5044
+ const segments = normalizedPattern.split("/").filter(Boolean).map((seg) => {
5045
+ const isOptional = OPTIONAL_SEGMENT_RE.test(seg);
5046
+ if (isOptional)
5047
+ optionalCount++;
5048
+ const paramMatch = seg.match(PARAM_SEGMENT_RE);
5049
+ return {
5050
+ isParam: !!paramMatch,
5051
+ paramName: paramMatch ? paramMatch[1].replace("?", "") : "",
5052
+ isOptional,
5053
+ literal: seg
5054
+ };
5055
+ });
5056
+ const compiled = {
5057
+ isWildcard,
5058
+ wildcardBase: isWildcard ? pattern.slice(0, -2) : "",
5059
+ segments,
5060
+ optionalCount
5061
+ };
5062
+ compiledPatternCache.set(pattern, compiled);
5063
+ return compiled;
5064
+ }
5065
+ function getConstraintRegex(constraint) {
5066
+ const cached = constraintRegexCache.get(constraint);
5067
+ if (cached !== undefined)
5068
+ return cached;
5069
+ let regex;
5070
+ try {
5071
+ regex = new RegExp(`^${constraint}$`);
5072
+ } catch {
5073
+ regex = null;
5074
+ }
5075
+ constraintRegexCache.set(constraint, regex);
5076
+ return regex;
5077
+ }
5078
+ function matchPath(pattern, path, params, constraints) {
5079
+ const compiled = compilePattern(pattern);
5080
+ if (compiled.isWildcard && path.startsWith(compiled.wildcardBase)) {
5081
+ return true;
5082
+ }
5083
+ const patternSegments = compiled.segments;
5084
+ const pathSegments = normalizePath(path).split("/").filter(Boolean);
5085
+ if (pathSegments.length < patternSegments.length - compiled.optionalCount || pathSegments.length > patternSegments.length) {
5086
+ return false;
5087
+ }
5088
+ for (let i = 0;i < patternSegments.length; i++) {
5089
+ const seg = patternSegments[i];
5090
+ const pathSegment = pathSegments[i];
5091
+ if (pathSegment === undefined) {
5092
+ if (seg.isOptional) {
5093
+ continue;
5094
+ }
5095
+ return false;
5096
+ }
5097
+ if (seg.isParam) {
5098
+ params[seg.paramName] = pathSegment;
5099
+ if (constraints && constraints[seg.paramName]) {
5100
+ const regex = getConstraintRegex(constraints[seg.paramName]);
5101
+ if (regex) {
5102
+ if (!regex.test(pathSegment)) {
5103
+ return false;
5104
+ }
5105
+ } else {
5106
+ console.warn(`Invalid constraint regex for parameter ${seg.paramName}: ${constraints[seg.paramName]}`);
5107
+ }
5108
+ }
5109
+ } else if (seg.literal !== pathSegment) {
5110
+ return false;
5111
+ }
5112
+ }
5113
+ return true;
5114
+ }
5115
+ function joinPaths(...segments) {
5116
+ return normalizePath(segments.join("/"));
5117
+ }
5118
+ function validatePath(path) {
5119
+ if (!path.startsWith("/")) {
5120
+ return false;
5121
+ }
5122
+ const stack = [];
5123
+ for (const char of path) {
5124
+ if (char === "{") {
5125
+ stack.push(char);
5126
+ } else if (char === "}") {
5127
+ if (stack.length === 0 || stack.pop() !== "{") {
5128
+ return false;
5129
+ }
5130
+ }
5131
+ }
5132
+ return stack.length === 0;
5133
+ }
5134
+ async function fileExists(path) {
5135
+ try {
5136
+ const file = Bun.file(path);
5137
+ return await file.exists();
5138
+ } catch {
5139
+ return false;
5140
+ }
5141
+ }
5142
+ async function resolveViewPath(viewName, viewsPath, extensions) {
5143
+ if (extensions.some((ext) => viewName.endsWith(ext))) {
5144
+ const fullPath = normalizePath(join3(viewsPath, viewName));
5145
+ if (await fileExists(fullPath)) {
5146
+ return fullPath;
5147
+ }
5148
+ return null;
5149
+ }
5150
+ for (const ext of extensions) {
5151
+ const fullPath = normalizePath(join3(viewsPath, `${viewName}${ext}`));
5152
+ if (await fileExists(fullPath)) {
5153
+ return fullPath;
5154
+ }
5155
+ }
5156
+ return null;
5157
+ }
5158
+ function processHtmlTemplate(template, data) {
5159
+ let result = template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
5160
+ const trimmedKey = key.trim();
5161
+ const value = getNestedValue(data, trimmedKey);
5162
+ return value !== undefined ? String(value) : match;
5163
+ });
5164
+ result = processConditionals(result, data);
5165
+ result = processLoops(result, data);
5166
+ return result;
5167
+ }
5168
+ function processConditionals(template, data) {
5169
+ return template.replace(/\{\{#if ([^}]+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g, (_match, condition, content, alternative = "") => {
5170
+ const conditionTrimmed = condition.trim();
5171
+ const value = getNestedValue(data, conditionTrimmed);
5172
+ return value ? content : alternative;
5173
+ });
5174
+ }
5175
+ function processLoops(template, data) {
5176
+ return template.replace(/\{\{#each ([^}]+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_match, arrayKey, content) => {
5177
+ const array = getNestedValue(data, arrayKey.trim());
5178
+ if (!Array.isArray(array))
5179
+ return "";
5180
+ return array.map((item) => {
5181
+ return content.replace(/\{\{@index\}\}/g, String(array.indexOf(item))).replace(/\{\{this\}\}/g, String(item)).replace(/\{\{this\.([^}]+)\}\}/g, (_matchStr, prop) => {
5182
+ const value = item[prop.trim()];
5183
+ return value !== undefined ? String(value) : "";
5184
+ });
5185
+ }).join("");
5186
+ });
5187
+ }
5188
+ function getNestedValue(obj, path) {
5189
+ return path.split(".").reduce((prev, curr) => {
5190
+ if (prev && typeof prev === "object" && curr in prev) {
5191
+ return prev[curr];
5192
+ }
5193
+ return;
5194
+ }, obj);
5195
+ }
5196
+
5197
+ // src/router/fluent-routing.ts
5198
+ class FluentRouteBuilder {
5199
+ method;
5200
+ path;
5201
+ handler;
5202
+ middleware = [];
5203
+ cacheConfig;
5204
+ throttleConfig;
5205
+ routeName;
5206
+ modelBindings = {};
5207
+ constructor(method, path, handler) {
5208
+ this.method = method;
5209
+ this.path = path;
5210
+ this.handler = handler;
5211
+ }
5212
+ addMiddleware(middleware2) {
5213
+ if (Array.isArray(middleware2)) {
5214
+ this.middleware.push(...middleware2);
5736
5215
  } else {
5737
5216
  this.middleware.push(middleware2);
5738
5217
  }
@@ -5801,14 +5280,13 @@ class FluentConditionalBuilder {
5801
5280
  }
5802
5281
  middleware(middlewareName) {
5803
5282
  const [name, params] = middlewareName.split(":");
5804
- const middlewareFactory = this.router.namedMiddleware.get(name);
5283
+ const middlewareFactory = this.router.resolveNamedMiddleware(name);
5805
5284
  if (!middlewareFactory) {
5806
5285
  throw new Error(`Unknown middleware: ${name}`);
5807
5286
  }
5808
- const middlewareHandler = middlewareFactory(params);
5809
- this.router.conditionalMiddleware.push({
5287
+ this.router.addConditionalMiddleware({
5810
5288
  condition: this.condition,
5811
- middleware: [middlewareHandler]
5289
+ middleware: [middlewareFactory(params)]
5812
5290
  });
5813
5291
  return this;
5814
5292
  }
@@ -5937,6 +5415,12 @@ class FluentRouter {
5937
5415
  when(condition) {
5938
5416
  return new FluentConditionalBuilder(this, condition);
5939
5417
  }
5418
+ resolveNamedMiddleware(name) {
5419
+ return this.namedMiddleware.get(name);
5420
+ }
5421
+ addConditionalMiddleware(conditional) {
5422
+ this.conditionalMiddleware.push(conditional);
5423
+ }
5940
5424
  middleware(middlewareName) {
5941
5425
  const [name, params] = middlewareName.split(":");
5942
5426
  const middlewareFactory = this.namedMiddleware.get(name);
@@ -6058,7 +5542,6 @@ class FluentRouter {
6058
5542
  return [...this.routes, ...this.subdomainRouter.getAllRoutes()];
6059
5543
  }
6060
5544
  async handle(request2) {
6061
- const _url = new URL(request2.url);
6062
5545
  const url2 = new URL(request2.url);
6063
5546
  const domain = url2.hostname;
6064
5547
  const match = this.subdomainRouter.findDomainGroup(domain);
@@ -6066,34 +5549,42 @@ class FluentRouter {
6066
5549
  const enhancedReq = request2;
6067
5550
  enhancedReq.domainParams = match.parameters;
6068
5551
  for (const route of match.group.getRoutes()) {
6069
- if (this.matchesRoute(route, request2)) {
6070
- return await this.executeRoute(route, enhancedReq);
5552
+ const params = this.matchRouteParams(route, request2.method, url2.pathname);
5553
+ if (params) {
5554
+ return await this.executeRoute(route, enhancedReq, params);
6071
5555
  }
6072
5556
  }
6073
5557
  }
6074
5558
  for (const route of this.routes) {
6075
- if (this.matchesRoute(route, request2)) {
6076
- return await this.executeRoute(route, request2);
5559
+ const params = this.matchRouteParams(route, request2.method, url2.pathname);
5560
+ if (params) {
5561
+ return await this.executeRoute(route, request2, params);
6077
5562
  }
6078
5563
  }
6079
5564
  return null;
6080
5565
  }
6081
- matchesRoute(route, request2) {
6082
- if (route.method !== request2.method) {
6083
- return false;
5566
+ matchRouteParams(route, method, pathname) {
5567
+ if (route.method !== method) {
5568
+ return null;
6084
5569
  }
6085
- const url2 = new URL(request2.url);
6086
- const routePattern = route.path.replace(/\{([^:}]+):?([^}]*)\}/g, "([^/]+)");
6087
- const regex = new RegExp(`^${routePattern}$`);
6088
- return regex.test(url2.pathname);
5570
+ const params = {};
5571
+ return matchPath(route.path, pathname, params) ? params : null;
6089
5572
  }
6090
- async executeRoute(route, request2) {
6091
- const allMiddleware = [...this.globalMiddleware, ...route.middleware];
5573
+ async executeRoute(route, request2, params) {
5574
+ request2.params = params;
5575
+ const allMiddleware = [...this.globalMiddleware];
5576
+ for (const conditional of this.conditionalMiddleware) {
5577
+ if (await conditional.condition(request2)) {
5578
+ allMiddleware.push(...conditional.middleware);
5579
+ }
5580
+ }
5581
+ allMiddleware.push(...route.middleware);
6092
5582
  let index = 0;
6093
5583
  const next = async () => {
6094
5584
  if (index < allMiddleware.length) {
6095
5585
  const middleware2 = allMiddleware[index++];
6096
- return await middleware2(request2, next);
5586
+ const result = await middleware2(request2, next);
5587
+ return result ?? new Response(null);
6097
5588
  }
6098
5589
  return await route.handler(request2);
6099
5590
  };
@@ -6133,9 +5624,16 @@ var RouteFactory = {
6133
5624
  };
6134
5625
  var RouterUtils = {
6135
5626
  route: (name, params = {}, query = {}) => {
6136
- let url2 = `/${name}`;
5627
+ let url2 = getNamedRoutePath(name) ?? `/${name}`;
6137
5628
  for (const [key, value] of Object.entries(params)) {
6138
- url2 = url2.replace(`{${key}}`, value);
5629
+ const encoded = encodeURIComponent(value);
5630
+ url2 = url2.replace(`{${key}}`, encoded);
5631
+ url2 = url2.replace(`{${key}?}`, encoded);
5632
+ }
5633
+ if (url2.includes("?}")) {
5634
+ url2 = url2.replace(/\{[^}]+\?\}/g, "").replace(/\/{2,}/g, "/");
5635
+ if (url2.length > 1 && url2.endsWith("/"))
5636
+ url2 = url2.slice(0, -1);
6139
5637
  }
6140
5638
  const queryString = new URLSearchParams(query).toString();
6141
5639
  if (queryString) {
@@ -6158,171 +5656,6 @@ var RouterUtils = {
6158
5656
  };
6159
5657
  var router = new FluentRouter;
6160
5658
 
6161
- // src/utils.ts
6162
- import { join as join3 } from "path";
6163
- function normalizePath(path) {
6164
- let normalizedPath = path.replace(/\/+/g, "/");
6165
- if (!normalizedPath.startsWith("/")) {
6166
- normalizedPath = `/${normalizedPath}`;
6167
- }
6168
- if (normalizedPath.length > 1 && normalizedPath.endsWith("/")) {
6169
- normalizedPath = normalizedPath.slice(0, -1);
6170
- }
6171
- return normalizedPath;
6172
- }
6173
- function toActionPath(path) {
6174
- return path.replace(/\//g, "_").toLowerCase();
6175
- }
6176
- function isActionClass(handler) {
6177
- if (typeof handler !== "function" && typeof handler !== "object") {
6178
- return false;
6179
- }
6180
- if (typeof handler === "function" && handler.prototype) {
6181
- const prototype = handler.prototype;
6182
- if (typeof prototype.handle === "function") {
6183
- return true;
6184
- }
6185
- }
6186
- if (typeof handler === "object" && handler !== null && "handle" in handler && typeof handler.handle === "function") {
6187
- return true;
6188
- }
6189
- return false;
6190
- }
6191
- function isRouteHandler(handler) {
6192
- return typeof handler === "function" && !isActionClass(handler);
6193
- }
6194
- function extractParamNames(pattern) {
6195
- const matches = pattern.match(/\{([^}]+)\}/g);
6196
- return matches ? matches.map((m) => m.slice(1, -1).replace("?", "")) : [];
6197
- }
6198
- function createPathRegex(pattern) {
6199
- const regexPattern = pattern.replace(/\{([^}]+)\}/g, "([^/]+)");
6200
- return new RegExp(`^${regexPattern}$`);
6201
- }
6202
- function matchPath(pattern, path, params, constraints) {
6203
- const normalizedPattern = normalizePath(pattern);
6204
- const normalizedPath = normalizePath(path);
6205
- if (pattern.endsWith("/*")) {
6206
- const basePattern = pattern.slice(0, -2);
6207
- if (path.startsWith(basePattern)) {
6208
- return true;
6209
- }
6210
- }
6211
- const patternSegments = normalizedPattern.split("/").filter(Boolean);
6212
- const pathSegments = normalizedPath.split("/").filter(Boolean);
6213
- const optionalParamCount = (normalizedPattern.match(/\{[^}]+\?\}/g) || []).length;
6214
- if (pathSegments.length < patternSegments.length - optionalParamCount || pathSegments.length > patternSegments.length) {
6215
- return false;
6216
- }
6217
- for (let i = 0;i < patternSegments.length; i++) {
6218
- const patternSegment = patternSegments[i];
6219
- const pathSegment = pathSegments[i];
6220
- if (pathSegment === undefined) {
6221
- if (patternSegment.match(/\{[^}]+\?\}/)) {
6222
- continue;
6223
- } else {
6224
- return false;
6225
- }
6226
- }
6227
- const paramMatch = patternSegment.match(/\{([^}:]+)(?::[^}]+)?(\?)?\}/);
6228
- if (paramMatch) {
6229
- const paramName = paramMatch[1].replace("?", "");
6230
- params[paramName] = pathSegment;
6231
- if (constraints && constraints[paramName]) {
6232
- try {
6233
- const regex = new RegExp(`^${constraints[paramName]}$`);
6234
- if (!regex.test(pathSegment)) {
6235
- return false;
6236
- }
6237
- } catch {
6238
- console.warn(`Invalid constraint regex for parameter ${paramName}: ${constraints[paramName]}`);
6239
- }
6240
- }
6241
- } else if (patternSegment !== pathSegment) {
6242
- return false;
6243
- }
6244
- }
6245
- return true;
6246
- }
6247
- function joinPaths(...segments) {
6248
- return normalizePath(segments.join("/"));
6249
- }
6250
- function validatePath(path) {
6251
- if (!path.startsWith("/")) {
6252
- return false;
6253
- }
6254
- const stack = [];
6255
- for (const char of path) {
6256
- if (char === "{") {
6257
- stack.push(char);
6258
- } else if (char === "}") {
6259
- if (stack.length === 0 || stack.pop() !== "{") {
6260
- return false;
6261
- }
6262
- }
6263
- }
6264
- return stack.length === 0;
6265
- }
6266
- async function fileExists(path) {
6267
- try {
6268
- const file = Bun.file(path);
6269
- return await file.exists();
6270
- } catch {
6271
- return false;
6272
- }
6273
- }
6274
- async function resolveViewPath(viewName, viewsPath, extensions) {
6275
- if (extensions.some((ext) => viewName.endsWith(ext))) {
6276
- const fullPath = normalizePath(join3(viewsPath, viewName));
6277
- if (await fileExists(fullPath)) {
6278
- return fullPath;
6279
- }
6280
- return null;
6281
- }
6282
- for (const ext of extensions) {
6283
- const fullPath = normalizePath(join3(viewsPath, `${viewName}${ext}`));
6284
- if (await fileExists(fullPath)) {
6285
- return fullPath;
6286
- }
6287
- }
6288
- return null;
6289
- }
6290
- function processHtmlTemplate(template, data) {
6291
- let result = template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
6292
- const trimmedKey = key.trim();
6293
- const value = getNestedValue(data, trimmedKey);
6294
- return value !== undefined ? String(value) : match;
6295
- });
6296
- result = processConditionals(result, data);
6297
- result = processLoops(result, data);
6298
- return result;
6299
- }
6300
- function processConditionals(template, data) {
6301
- return template.replace(/\{\{#if ([^}]+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g, (_match, condition, content, alternative = "") => {
6302
- const conditionTrimmed = condition.trim();
6303
- const value = getNestedValue(data, conditionTrimmed);
6304
- return value ? content : alternative;
6305
- });
6306
- }
6307
- function processLoops(template, data) {
6308
- return template.replace(/\{\{#each ([^}]+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_match, arrayKey, content) => {
6309
- const array = getNestedValue(data, arrayKey.trim());
6310
- if (!Array.isArray(array))
6311
- return "";
6312
- return array.map((item) => {
6313
- return content.replace(/\{\{@index\}\}/g, String(array.indexOf(item))).replace(/\{\{this\}\}/g, String(item)).replace(/\{\{this\.([^}]+)\}\}/g, (_matchStr, prop) => {
6314
- const value = item[prop.trim()];
6315
- return value !== undefined ? String(value) : "";
6316
- });
6317
- }).join("");
6318
- });
6319
- }
6320
- function getNestedValue(obj, path) {
6321
- return path.split(".").reduce((prev, curr) => {
6322
- return prev && prev[curr] !== undefined ? prev[curr] : undefined;
6323
- }, obj);
6324
- }
6325
-
6326
5659
  // src/router/group-organization.ts
6327
5660
  function registerGroupOrganization(RouterClass) {
6328
5661
  Object.defineProperties(RouterClass.prototype, {
@@ -6342,7 +5675,16 @@ function registerGroupOrganization(RouterClass) {
6342
5675
  ];
6343
5676
  }
6344
5677
  this.currentGroup = newGroup;
6345
- callback();
5678
+ const result = callback();
5679
+ if (result instanceof Promise) {
5680
+ return result.then(() => {
5681
+ this.currentGroup = previousGroup;
5682
+ return this;
5683
+ }, (error) => {
5684
+ this.currentGroup = previousGroup;
5685
+ throw error;
5686
+ });
5687
+ }
6346
5688
  this.currentGroup = previousGroup;
6347
5689
  return this;
6348
5690
  },
@@ -6539,10 +5881,11 @@ function registerHttpMethods(RouterClass) {
6539
5881
  this.namedRoutes.set(name, route);
6540
5882
  registerNamedRoute(name, route.path);
6541
5883
  }
6542
- if (this.addRouteToCompiler) {
5884
+ if (this.addRouteToCompiler && !domain) {
6543
5885
  this.addRouteToCompiler(route);
6544
5886
  }
6545
5887
  this.routeCache.clear();
5888
+ this._allowedMethodsCache?.clear();
6546
5889
  return this;
6547
5890
  },
6548
5891
  writable: true,
@@ -6590,6 +5933,13 @@ function registerHttpMethods(RouterClass) {
6590
5933
  writable: true,
6591
5934
  configurable: true
6592
5935
  },
5936
+ head: {
5937
+ value(path, handler, type, name, middleware2) {
5938
+ return this.addRoute("HEAD", path, handler, type, name, middleware2);
5939
+ },
5940
+ writable: true,
5941
+ configurable: true
5942
+ },
6593
5943
  match: {
6594
5944
  value(methods, path, handler, type, name, middleware2) {
6595
5945
  for (const method of methods) {
@@ -6627,6 +5977,12 @@ function registerHttpMethods(RouterClass) {
6627
5977
  url2 = url2.replace(`{${param}}`, encodeURIComponent(value));
6628
5978
  url2 = url2.replace(`{${param}?}`, encodeURIComponent(value));
6629
5979
  }
5980
+ if (url2.includes("{")) {
5981
+ url2 = url2.replace(/\{[^}]+\?\}/g, "").replace(/\/{2,}/g, "/");
5982
+ if (url2.length > 1 && url2.endsWith("/")) {
5983
+ url2 = url2.slice(0, -1);
5984
+ }
5985
+ }
6630
5986
  return url2;
6631
5987
  },
6632
5988
  writable: true,
@@ -6837,6 +6193,19 @@ Last error: ${lastError?.message || "Unknown error"}`);
6837
6193
  function createHandlerResolver(config2) {
6838
6194
  return (handler, req) => resolveHandler(handler, req, config2);
6839
6195
  }
6196
+ function createHandlerInvoker(handler, config2) {
6197
+ if (handler instanceof Response) {
6198
+ return async () => handler.clone();
6199
+ }
6200
+ if (isCallableHandler(handler)) {
6201
+ return async (req) => wrapResponse(await handler(req));
6202
+ }
6203
+ if (typeof handler === "function" && handler.prototype && typeof handler.prototype.handle === "function") {
6204
+ const HandlerClass = handler;
6205
+ return async (req) => wrapResponse(await new HandlerClass().handle(req));
6206
+ }
6207
+ return (req) => resolveHandler(handler, req, config2);
6208
+ }
6840
6209
 
6841
6210
  // src/router/middleware.ts
6842
6211
  function registerMiddlewareHandling(RouterClass) {
@@ -6849,6 +6218,7 @@ function registerMiddlewareHandling(RouterClass) {
6849
6218
  this.globalMiddleware.push(resolvedMiddleware);
6850
6219
  }
6851
6220
  }
6221
+ this._mwEpoch = (this._mwEpoch || 0) + 1;
6852
6222
  return this;
6853
6223
  },
6854
6224
  writable: true,
@@ -7495,40 +6865,36 @@ class RouteTrie {
7495
6865
  return null;
7496
6866
  }
7497
6867
  const segment = segments[segmentIndex];
7498
- const candidates = [];
7499
- if (node.children.has(segment)) {
7500
- candidates.push({
7501
- node: node.children.get(segment),
7502
- newParams: { ...params }
7503
- });
7504
- }
7505
- if (node.paramChild) {
7506
- const paramNode = node.paramChild;
7507
- let matches = true;
7508
- if (paramNode.pattern) {
7509
- matches = paramNode.pattern.test(segment);
7510
- }
7511
- if (matches && paramNode.paramName) {
7512
- candidates.push({
7513
- node: paramNode,
7514
- newParams: { ...params, [paramNode.paramName]: segment }
7515
- });
7516
- }
6868
+ const staticChild = node.children.get(segment);
6869
+ if (staticChild) {
6870
+ const match = this.matchSegments(staticChild, segments, segmentIndex + 1, params, method);
6871
+ if (match)
6872
+ return match;
7517
6873
  }
7518
- if (node.wildcardChild) {
7519
- const remainingPath = segments.slice(segmentIndex).join("/");
7520
- const match = this.matchSegments(node.wildcardChild, [], 0, { ...params, wildcard: remainingPath }, method);
6874
+ const paramNode = node.paramChild;
6875
+ if (paramNode && paramNode.paramName && (!paramNode.pattern || paramNode.pattern.test(segment))) {
6876
+ const previous = params[paramNode.paramName];
6877
+ params[paramNode.paramName] = segment;
6878
+ const match = this.matchSegments(paramNode, segments, segmentIndex + 1, params, method);
7521
6879
  if (match)
7522
6880
  return match;
6881
+ if (previous === undefined)
6882
+ delete params[paramNode.paramName];
6883
+ else
6884
+ params[paramNode.paramName] = previous;
7523
6885
  }
7524
- let bestMatch = null;
7525
- for (const candidate of candidates) {
7526
- const match = this.matchSegments(candidate.node, segments, segmentIndex + 1, candidate.newParams, method);
7527
- if (match && (!bestMatch || match.score > bestMatch.score)) {
7528
- bestMatch = match;
6886
+ if (node.wildcardChild) {
6887
+ const compiled = node.wildcardChild.getRoute(method);
6888
+ if (compiled) {
6889
+ params.wildcard = segments.slice(segmentIndex).join("/");
6890
+ return {
6891
+ route: compiled.route,
6892
+ params,
6893
+ score: compiled.priority
6894
+ };
7529
6895
  }
7530
6896
  }
7531
- return bestMatch;
6897
+ return null;
7532
6898
  }
7533
6899
  getAllRoutes() {
7534
6900
  return Array.from(this.compiledRoutes.values());
@@ -7582,7 +6948,7 @@ class RouteTrie {
7582
6948
  class RouteCompiler {
7583
6949
  trie;
7584
6950
  matchCache = new Map;
7585
- cacheKeys = [];
6951
+ routeKeys = new Set;
7586
6952
  stats = {
7587
6953
  totalMatches: 0,
7588
6954
  cacheHits: 0,
@@ -7599,6 +6965,7 @@ class RouteCompiler {
7599
6965
  enablePriorityOptimization: true,
7600
6966
  cacheSize: 1000,
7601
6967
  precompilePatterns: true,
6968
+ enableProfiling: false,
7602
6969
  ...options
7603
6970
  };
7604
6971
  this.trie = new RouteTrie;
@@ -7607,10 +6974,11 @@ class RouteCompiler {
7607
6974
  if (this.options.precompilePatterns) {
7608
6975
  this.precompileRoutePattern(route);
7609
6976
  }
7610
- const isDuplicate = this.checkForDuplicateRoute(route);
7611
- if (isDuplicate) {
6977
+ const routeKey = `${route.method}:${route.path}`;
6978
+ if (this.routeKeys.has(routeKey)) {
7612
6979
  return false;
7613
6980
  }
6981
+ this.routeKeys.add(routeKey);
7614
6982
  this.trie.addRoute(route);
7615
6983
  if (this.options.enableCaching) {
7616
6984
  this.matchCache.clear();
@@ -7619,15 +6987,6 @@ class RouteCompiler {
7619
6987
  this.stats.methodDistribution[method] = (this.stats.methodDistribution[method] || 0) + 1;
7620
6988
  return true;
7621
6989
  }
7622
- checkForDuplicateRoute(route) {
7623
- const routes = this.trie.getAllRoutes();
7624
- for (const existingRoute of routes) {
7625
- if (existingRoute.route.path === route.path && existingRoute.route.method === route.method) {
7626
- return true;
7627
- }
7628
- }
7629
- return false;
7630
- }
7631
6990
  patternCache = new Map;
7632
6991
  extractParamNames(path) {
7633
6992
  const paramNames = [];
@@ -7670,17 +7029,25 @@ class RouteCompiler {
7670
7029
  }
7671
7030
  }
7672
7031
  convertToURLPattern(path, constraints) {
7673
- let regexPattern = path.replace(/\{([^}:]+)(?::([^}]+))?\}/g, (_match, name, ..._args) => {
7674
- const pattern = _args[0];
7675
- if (constraints && constraints[name]) {
7676
- return `(?<${name}>${constraints[name]})`;
7677
- } else if (pattern) {
7678
- return `(?<${name}>${pattern})`;
7679
- }
7680
- return `(?<${name}>[^/]+)`;
7032
+ const paramTokens = [];
7033
+ const withTokens = path.replace(/\{([^}]+)\}/g, (_m, inner) => {
7034
+ paramTokens.push(inner);
7035
+ return `\x00${paramTokens.length - 1}\x00`;
7036
+ });
7037
+ let regexPattern = withTokens.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
7038
+ regexPattern = regexPattern.replace(/\/?\u0000(\d+)\u0000/g, (token, idx) => {
7039
+ const inner = paramTokens[Number(idx)];
7040
+ const isOptional = inner.endsWith("?");
7041
+ const core = isOptional ? inner.slice(0, -1) : inner;
7042
+ const colonIndex = core.indexOf(":");
7043
+ const name = colonIndex === -1 ? core : core.slice(0, colonIndex);
7044
+ const inlinePattern = colonIndex === -1 ? undefined : core.slice(colonIndex + 1);
7045
+ const pattern = constraints?.[name] ?? inlinePattern ?? "[^/]+";
7046
+ const group = `(?<${name}>${pattern})`;
7047
+ const leadingSlash = token.startsWith("/") ? "/" : "";
7048
+ return isOptional ? `(?:${leadingSlash}${group})?` : `${leadingSlash}${group}`;
7681
7049
  });
7682
- regexPattern = `^${regexPattern.replace(/\//g, "\\/")}$`;
7683
- const regex = new RegExp(regexPattern);
7050
+ const regex = new RegExp(`^${regexPattern}$`);
7684
7051
  return {
7685
7052
  exec: (pathname) => {
7686
7053
  const match = pathname.match(regex);
@@ -7693,16 +7060,16 @@ class RouteCompiler {
7693
7060
  };
7694
7061
  }
7695
7062
  match(path, method) {
7696
- const startTime = performance.now();
7063
+ const startTime = this.options.enableProfiling ? performance.now() : 0;
7697
7064
  this.stats.totalMatches++;
7698
7065
  if (this.options.enableCaching) {
7699
7066
  const cacheKey = `${method}:${path}`;
7700
- if (this.matchCache.has(cacheKey)) {
7067
+ const cached = this.matchCache.get(cacheKey);
7068
+ if (cached !== undefined || this.matchCache.has(cacheKey)) {
7701
7069
  this.stats.cacheHits++;
7702
- this.updateCacheLRU(cacheKey);
7703
- const cached = this.matchCache.get(cacheKey);
7704
- this.updateMatchTime(startTime);
7705
- return cached || null;
7070
+ if (startTime)
7071
+ this.updateMatchTime(startTime);
7072
+ return cached ? { route: cached.route, params: { ...cached.params } } : null;
7706
7073
  }
7707
7074
  this.stats.cacheMisses++;
7708
7075
  }
@@ -7718,25 +7085,18 @@ class RouteCompiler {
7718
7085
  const cacheKey = `${method}:${path}`;
7719
7086
  this.addToCache(cacheKey, result);
7720
7087
  }
7721
- this.updateMatchTime(startTime);
7722
- return result;
7088
+ if (startTime)
7089
+ this.updateMatchTime(startTime);
7090
+ return result ? { route: result.route, params: { ...result.params } } : null;
7723
7091
  }
7724
7092
  addToCache(key, value) {
7725
7093
  if (this.matchCache.size >= this.options.cacheSize && !this.matchCache.has(key)) {
7726
- if (this.cacheKeys.length > 0) {
7727
- const lruKey = this.cacheKeys.shift();
7094
+ const lruKey = this.matchCache.keys().next().value;
7095
+ if (lruKey !== undefined) {
7728
7096
  this.matchCache.delete(lruKey);
7729
7097
  }
7730
7098
  }
7731
7099
  this.matchCache.set(key, value);
7732
- this.updateCacheLRU(key);
7733
- }
7734
- updateCacheLRU(key) {
7735
- const index = this.cacheKeys.indexOf(key);
7736
- if (index !== -1) {
7737
- this.cacheKeys.splice(index, 1);
7738
- }
7739
- this.cacheKeys.push(key);
7740
7100
  }
7741
7101
  updateMatchTime(startTime) {
7742
7102
  const matchTime = performance.now() - startTime;
@@ -7805,7 +7165,7 @@ class RouteCompiler {
7805
7165
  clear() {
7806
7166
  this.trie.clear();
7807
7167
  this.matchCache.clear();
7808
- this.cacheKeys = [];
7168
+ this.routeKeys.clear();
7809
7169
  this.stats = {
7810
7170
  totalMatches: 0,
7811
7171
  cacheHits: 0,
@@ -7999,14 +7359,25 @@ function registerOptimizedRouteMatching(RouterClass) {
7999
7359
  matchRoute: {
8000
7360
  value(path, method, domain) {
8001
7361
  this.initializeRouteCompiler();
8002
- const match = this.routeCompiler.match(path, method);
8003
- if (match) {
8004
- if (domain && match.route.domain && match.route.domain !== domain) {
8005
- return;
7362
+ if (domain && this.domains[domain]) {
7363
+ const domainMatch = this.fallbackMatchRoute(path, method, domain);
7364
+ if (domainMatch) {
7365
+ return domainMatch;
7366
+ }
7367
+ } else {
7368
+ const match = this.routeCompiler.match(path, method);
7369
+ if (match && (!domain || !match.route.domain || match.route.domain === domain)) {
7370
+ return match;
7371
+ }
7372
+ const fallback = this.fallbackMatchRoute(path, method, domain);
7373
+ if (fallback) {
7374
+ return fallback;
8006
7375
  }
8007
- return match;
8008
7376
  }
8009
- return this.fallbackMatchRoute(path, method, domain);
7377
+ if (method === "HEAD") {
7378
+ return this.matchRoute(path, "GET", domain);
7379
+ }
7380
+ return;
8010
7381
  },
8011
7382
  writable: true,
8012
7383
  configurable: true
@@ -8018,6 +7389,9 @@ function registerOptimizedRouteMatching(RouterClass) {
8018
7389
  if (this.routeCache.has(cacheKey)) {
8019
7390
  return this.routeCache.get(cacheKey);
8020
7391
  }
7392
+ if (this.routeCache.size >= 1e4) {
7393
+ this.routeCache.clear();
7394
+ }
8021
7395
  if (this.staticRoutes.has(method)) {
8022
7396
  const staticRoute = this.staticRoutes.get(method).get(url2.pathname);
8023
7397
  if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
@@ -8054,6 +7428,24 @@ function registerOptimizedRouteMatching(RouterClass) {
8054
7428
  }
8055
7429
  }
8056
7430
  }
7431
+ const wildcardPools = domain && this.domains[domain] ? [this.domains[domain], this.routes] : [this.routes];
7432
+ for (const pool of wildcardPools) {
7433
+ for (const route of pool) {
7434
+ if (route.method !== method || !route.path.endsWith("*")) {
7435
+ continue;
7436
+ }
7437
+ const basePath = route.path.slice(0, -1);
7438
+ if (url2.pathname.startsWith(basePath)) {
7439
+ const result = {
7440
+ route,
7441
+ params: { wildcard: url2.pathname.slice(basePath.length) }
7442
+ };
7443
+ this.routeCache.set(cacheKey, result);
7444
+ return result;
7445
+ }
7446
+ }
7447
+ }
7448
+ this.routeCache.set(cacheKey, undefined);
8057
7449
  return;
8058
7450
  },
8059
7451
  writable: true,
@@ -8472,6 +7864,17 @@ function registerRouteMatching(RouterClass) {
8472
7864
  writable: true,
8473
7865
  configurable: true
8474
7866
  },
7867
+ withoutNativeDispatch: {
7868
+ value() {
7869
+ const lastRoute = this.routes[this.routes.length - 1];
7870
+ if (lastRoute) {
7871
+ lastRoute.nativeDispatch = false;
7872
+ }
7873
+ return this;
7874
+ },
7875
+ writable: true,
7876
+ configurable: true
7877
+ },
8475
7878
  whereNumber: {
8476
7879
  value(param) {
8477
7880
  return this.where({ [param]: "\\d+" });
@@ -8534,6 +7937,8 @@ class Router {
8534
7937
  namedMiddleware = new Map;
8535
7938
  conditionalMiddleware = [];
8536
7939
  _middlewarePipeline;
7940
+ _mwEpoch = 0;
7941
+ _allowedMethodsCache = new Map;
8537
7942
  config = {
8538
7943
  verbose: false,
8539
7944
  routesPath: "routes",
@@ -8634,6 +8039,7 @@ class Router {
8634
8039
  }
8635
8040
  invalidateCache() {
8636
8041
  this.routeCache.clear();
8042
+ this._allowedMethodsCache.clear();
8637
8043
  }
8638
8044
  registerRoute(method, path, handler, type, name, middleware2) {
8639
8045
  let routePath = path;
@@ -8948,50 +8354,44 @@ class Router {
8948
8354
  }
8949
8355
  getAllowedMethods(path, domain) {
8950
8356
  const url2 = new URL(path, "http://localhost");
8357
+ const cacheKey = `${domain || ""}:${url2.pathname}`;
8358
+ const cached = this._allowedMethodsCache.get(cacheKey);
8359
+ if (cached) {
8360
+ return cached;
8361
+ }
8951
8362
  const methods = new Set;
8952
- const allMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
8953
- for (const method of allMethods) {
8954
- if (this.staticRoutes.has(method)) {
8955
- const staticRoute = this.staticRoutes.get(method).get(url2.pathname);
8956
- if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
8957
- methods.add(method);
8958
- continue;
8959
- }
8363
+ for (const [method, routesByPath] of this.staticRoutes) {
8364
+ const staticRoute = routesByPath.get(url2.pathname);
8365
+ if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
8366
+ methods.add(method);
8367
+ }
8368
+ }
8369
+ const potentialRoutes = domain && this.domains[domain] ? this.domains[domain] : this.routes;
8370
+ for (const route of potentialRoutes) {
8371
+ if (methods.has(route.method)) {
8372
+ continue;
8960
8373
  }
8961
- const potentialRoutes = domain && this.domains[domain] ? this.domains[domain] : this.routes;
8962
- const methodRoutes = potentialRoutes.filter((route) => route.method === method);
8963
- for (const route of methodRoutes) {
8964
- if (route.path === url2.pathname) {
8965
- methods.add(method);
8966
- break;
8967
- }
8374
+ if (route.path === url2.pathname) {
8375
+ methods.add(route.method);
8376
+ continue;
8968
8377
  }
8969
- if (!methods.has(method)) {
8970
- for (const route of methodRoutes) {
8971
- if (route.pattern) {
8972
- const match = route.pattern.exec(url2);
8973
- if (match) {
8974
- methods.add(method);
8975
- break;
8976
- }
8977
- }
8978
- }
8378
+ if (route.pattern && route.pattern.exec(url2)) {
8379
+ methods.add(route.method);
8380
+ continue;
8979
8381
  }
8980
- if (!methods.has(method)) {
8981
- const wildcardRoutes = potentialRoutes.filter((route) => route.method === method && route.path.endsWith("*"));
8982
- for (const route of wildcardRoutes) {
8983
- const basePath = route.path.slice(0, -1);
8984
- if (url2.pathname.startsWith(basePath)) {
8985
- methods.add(method);
8986
- break;
8987
- }
8988
- }
8382
+ if (route.path.endsWith("*") && url2.pathname.startsWith(route.path.slice(0, -1))) {
8383
+ methods.add(route.method);
8989
8384
  }
8990
8385
  }
8991
8386
  if (methods.has("GET")) {
8992
8387
  methods.add("HEAD");
8993
8388
  }
8994
- return Array.from(methods);
8389
+ const result = Array.from(methods);
8390
+ if (this._allowedMethodsCache.size >= 1e4) {
8391
+ this._allowedMethodsCache.clear();
8392
+ }
8393
+ this._allowedMethodsCache.set(cacheKey, result);
8394
+ return result;
8995
8395
  }
8996
8396
  matchRoute(path, method, domain) {
8997
8397
  const url2 = new URL(path, "http://localhost");
@@ -9419,12 +8819,15 @@ class Router {
9419
8819
  }
9420
8820
  const result = callback();
9421
8821
  if (result instanceof Promise) {
9422
- result.then(() => {
8822
+ return result.then(() => {
8823
+ this.currentGroup = previousGroup;
8824
+ return this;
8825
+ }, (error) => {
9423
8826
  this.currentGroup = previousGroup;
8827
+ throw error;
9424
8828
  });
9425
- } else {
9426
- this.currentGroup = previousGroup;
9427
8829
  }
8830
+ this.currentGroup = previousGroup;
9428
8831
  return this;
9429
8832
  }
9430
8833
  addRoute(route) {
@@ -9556,10 +8959,48 @@ function applyRequestEnhancements(req, params = {}) {
9556
8959
  }
9557
8960
 
9558
8961
  // src/request/macros.ts
8962
+ var MOBILE_REGEX = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
8963
+ var BOT_REGEX = /bot|crawler|spider|crawling/i;
8964
+ var GLOB_PATTERN_CACHE = new Map;
8965
+ function getParsedURL(req) {
8966
+ const cache = req;
8967
+ if (!cache._parsedURL) {
8968
+ cache._parsedURL = new URL(req.url);
8969
+ }
8970
+ return cache._parsedURL;
8971
+ }
8972
+ function getParsedCookies(req) {
8973
+ const cache = req;
8974
+ if (!cache._parsedCookies) {
8975
+ const cookies = {};
8976
+ const cookieHeader = req.headers.get("cookie");
8977
+ if (cookieHeader) {
8978
+ for (const cookie of cookieHeader.split(";")) {
8979
+ const eqIndex = cookie.indexOf("=");
8980
+ if (eqIndex === -1)
8981
+ continue;
8982
+ const key = cookie.slice(0, eqIndex).trim();
8983
+ if (!key)
8984
+ continue;
8985
+ const value = cookie.slice(eqIndex + 1).trim();
8986
+ try {
8987
+ cookies[key] = decodeURIComponent(value);
8988
+ } catch {
8989
+ cookies[key] = value;
8990
+ }
8991
+ }
8992
+ }
8993
+ cache._parsedCookies = cookies;
8994
+ }
8995
+ return cache._parsedCookies;
8996
+ }
8997
+
9559
8998
  class RequestMacroRegistry {
9560
8999
  macros = new Map;
9561
- register(name, handler) {
9562
- this.macros.set(name, { name, handler });
9000
+ epoch = 0;
9001
+ register(name, handler, accessor = false) {
9002
+ this.macros.set(name, { name, handler, accessor });
9003
+ this.epoch++;
9563
9004
  }
9564
9005
  get(name) {
9565
9006
  return this.macros.get(name);
@@ -9571,23 +9012,71 @@ class RequestMacroRegistry {
9571
9012
  return Array.from(this.macros.values());
9572
9013
  }
9573
9014
  remove(name) {
9574
- return this.macros.delete(name);
9015
+ const removed = this.macros.delete(name);
9016
+ if (removed)
9017
+ this.epoch++;
9018
+ return removed;
9575
9019
  }
9576
9020
  clear() {
9577
9021
  this.macros.clear();
9022
+ this.epoch++;
9023
+ }
9024
+ getEpoch() {
9025
+ return this.epoch;
9578
9026
  }
9579
9027
  }
9580
9028
  var requestMacroRegistry = new RequestMacroRegistry;
9029
+ var MACRO_PROTO = Symbol("bunRouterMacroProto");
9030
+ var MACRO_BASE = Symbol("bunRouterMacroBase");
9031
+ var macroProtoCache = new WeakMap;
9032
+ var macroProtoEpoch = -1;
9581
9033
 
9582
9034
  class RequestWithMacros {
9583
9035
  static macro(name, handler) {
9584
9036
  requestMacroRegistry.register(name, handler);
9585
9037
  }
9038
+ static macroAccessor(name, getter) {
9039
+ requestMacroRegistry.register(name, getter, true);
9040
+ }
9586
9041
  static applyMacros(request2) {
9587
- const macros = requestMacroRegistry.all();
9588
- macros.forEach((macro) => {
9589
- request2[macro.name] = macro.handler.bind(request2);
9590
- });
9042
+ const epoch = requestMacroRegistry.getEpoch();
9043
+ if (epoch !== macroProtoEpoch) {
9044
+ macroProtoCache = new WeakMap;
9045
+ macroProtoEpoch = epoch;
9046
+ }
9047
+ let base = Object.getPrototypeOf(request2) ?? Object.prototype;
9048
+ if (base[MACRO_PROTO]) {
9049
+ if (macroProtoCache.get(base[MACRO_BASE]) === base) {
9050
+ return request2;
9051
+ }
9052
+ base = base[MACRO_BASE];
9053
+ }
9054
+ let proto = macroProtoCache.get(base);
9055
+ if (!proto) {
9056
+ proto = Object.create(base);
9057
+ for (const macro of requestMacroRegistry.all()) {
9058
+ if (macro.accessor) {
9059
+ Object.defineProperty(proto, macro.name, {
9060
+ get: macro.handler,
9061
+ set(value) {
9062
+ Object.defineProperty(this, macro.name, {
9063
+ value,
9064
+ writable: true,
9065
+ configurable: true,
9066
+ enumerable: true
9067
+ });
9068
+ },
9069
+ configurable: true
9070
+ });
9071
+ } else {
9072
+ proto[macro.name] = macro.handler;
9073
+ }
9074
+ }
9075
+ Object.defineProperty(proto, MACRO_PROTO, { value: true });
9076
+ Object.defineProperty(proto, MACRO_BASE, { value: base });
9077
+ macroProtoCache.set(base, proto);
9078
+ }
9079
+ Object.setPrototypeOf(request2, proto);
9591
9080
  return request2;
9592
9081
  }
9593
9082
  static hasMacro(name) {
@@ -9617,14 +9106,10 @@ var BuiltInRequestMacros = {
9617
9106
  return this.headers.get("x-pjax") === "true";
9618
9107
  },
9619
9108
  isMobile() {
9620
- const userAgent = this.headers.get("user-agent") || "";
9621
- const mobileRegex = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
9622
- return mobileRegex.test(userAgent);
9109
+ return MOBILE_REGEX.test(this.headers.get("user-agent") || "");
9623
9110
  },
9624
9111
  isBot() {
9625
- const userAgent = this.headers.get("user-agent") || "";
9626
- const botRegex = /bot|crawler|spider|crawling/i;
9627
- return botRegex.test(userAgent);
9112
+ return BOT_REGEX.test(this.headers.get("user-agent") || "");
9628
9113
  },
9629
9114
  isSecure() {
9630
9115
  return this.url.startsWith("https://") || this.headers.get("x-forwarded-proto") === "https" || this.headers.get("x-forwarded-ssl") === "on";
@@ -9652,6 +9137,17 @@ var BuiltInRequestMacros = {
9652
9137
  }
9653
9138
  return null;
9654
9139
  },
9140
+ async rawBody() {
9141
+ if (typeof this._rawBody === "string")
9142
+ return this._rawBody;
9143
+ try {
9144
+ const text = await this.clone().text();
9145
+ this._rawBody = text;
9146
+ return text;
9147
+ } catch {
9148
+ return "";
9149
+ }
9150
+ },
9655
9151
  basicAuth() {
9656
9152
  const auth2 = this.headers.get("authorization");
9657
9153
  if (auth2 && auth2.startsWith("Basic ")) {
@@ -9756,30 +9252,11 @@ var BuiltInRequestMacros = {
9756
9252
  return this.params?.[key] || defaultValue;
9757
9253
  },
9758
9254
  cookie(name, defaultValue) {
9759
- const cookies = this.headers.get("cookie");
9760
- if (!cookies)
9761
- return defaultValue || null;
9762
- const cookieArray = cookies.split(";");
9763
- for (const cookie of cookieArray) {
9764
- const [key, value] = cookie.trim().split("=");
9765
- if (key === name) {
9766
- return decodeURIComponent(value);
9767
- }
9768
- }
9769
- return defaultValue || null;
9255
+ const value = getParsedCookies(this)[name];
9256
+ return value !== undefined ? value : defaultValue || null;
9770
9257
  },
9771
9258
  cookies() {
9772
- const cookies = {};
9773
- const cookieHeader = this.headers.get("cookie");
9774
- if (cookieHeader) {
9775
- cookieHeader.split(";").forEach((cookie) => {
9776
- const [key, value] = cookie.trim().split("=");
9777
- if (key && value) {
9778
- cookies[key] = decodeURIComponent(value);
9779
- }
9780
- });
9781
- }
9782
- return cookies;
9259
+ return { ...getParsedCookies(this) };
9783
9260
  },
9784
9261
  file(name) {
9785
9262
  return this.files?.find((f) => f.fieldName === name);
@@ -9788,26 +9265,33 @@ var BuiltInRequestMacros = {
9788
9265
  return !!this.files?.find((f) => f.fieldName === name);
9789
9266
  },
9790
9267
  path() {
9791
- return new URL(this.url).pathname;
9268
+ return getParsedURL(this).pathname;
9792
9269
  },
9793
9270
  fullUrl() {
9794
9271
  return this.url;
9795
9272
  },
9796
9273
  root() {
9797
- const url2 = new URL(this.url);
9274
+ const url2 = getParsedURL(this);
9798
9275
  return `${url2.protocol}//${url2.host}`;
9799
9276
  },
9800
9277
  is(pattern) {
9801
9278
  const path = this.path();
9802
- const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
9803
- const regex = new RegExp(`^${regexPattern}$`);
9279
+ let regex = GLOB_PATTERN_CACHE.get(pattern);
9280
+ if (!regex) {
9281
+ const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
9282
+ regex = new RegExp(`^${regexPattern}$`);
9283
+ if (GLOB_PATTERN_CACHE.size >= 1000) {
9284
+ GLOB_PATTERN_CACHE.clear();
9285
+ }
9286
+ GLOB_PATTERN_CACHE.set(pattern, regex);
9287
+ }
9804
9288
  return regex.test(path);
9805
9289
  },
9806
9290
  route() {
9807
9291
  return this.routeName || null;
9808
9292
  },
9809
9293
  fingerprint() {
9810
- const url2 = new URL(this.url);
9294
+ const url2 = getParsedURL(this);
9811
9295
  const data = {
9812
9296
  method: this.method,
9813
9297
  path: url2.pathname,
@@ -9863,6 +9347,51 @@ function registerBuiltInRequestMacros() {
9863
9347
  registerBuiltInRequestMacros();
9864
9348
 
9865
9349
  // src/router/server.ts
9350
+ if (!RequestWithMacros.hasMacro("getParam")) {
9351
+ RequestWithMacros.macro("getParam", function(name, defaultValue) {
9352
+ const value = this.params?.[name];
9353
+ return value !== undefined ? value : defaultValue;
9354
+ });
9355
+ }
9356
+ if (!RequestWithMacros.hasMacro("get")) {
9357
+ RequestWithMacros.macro("get", function(key, defaultValue) {
9358
+ const value = getParsedURL(this).searchParams.get(key);
9359
+ return value !== null && value !== undefined ? value : defaultValue;
9360
+ });
9361
+ }
9362
+ RequestWithMacros.macroAccessor("cookies", function() {
9363
+ const req = this;
9364
+ const cookies = () => ({ ...getParsedCookies(req) });
9365
+ for (const [key, value] of Object.entries(getParsedCookies(req))) {
9366
+ Object.defineProperty(cookies, key, {
9367
+ value,
9368
+ writable: true,
9369
+ enumerable: true,
9370
+ configurable: true
9371
+ });
9372
+ }
9373
+ cookies.get = (name) => getParsedCookies(req)[name];
9374
+ cookies.set = (name, value, options = {}) => {
9375
+ if (!req._cookiesToSet) {
9376
+ req._cookiesToSet = [];
9377
+ }
9378
+ req._cookiesToSet.push({ name, value, options });
9379
+ };
9380
+ cookies.delete = (name, options = {}) => {
9381
+ if (!req._cookiesToDelete) {
9382
+ req._cookiesToDelete = [];
9383
+ }
9384
+ req._cookiesToDelete.push({ name, options });
9385
+ };
9386
+ cookies.getAll = () => ({ ...getParsedCookies(req) });
9387
+ Object.defineProperty(req, "cookies", {
9388
+ value: cookies,
9389
+ writable: true,
9390
+ configurable: true,
9391
+ enumerable: true
9392
+ });
9393
+ return cookies;
9394
+ });
9866
9395
  function registerServerHandling(RouterClass) {
9867
9396
  Object.defineProperties(RouterClass.prototype, {
9868
9397
  serve: {
@@ -9894,6 +9423,13 @@ function registerServerHandling(RouterClass) {
9894
9423
  if (options.development !== undefined) {
9895
9424
  serverOptions.development = options.development;
9896
9425
  }
9426
+ this._nativeRoutesEnabled = options.nativeRoutes === true;
9427
+ if (this._nativeRoutesEnabled) {
9428
+ const nativeRoutes = this._buildNativeRoutes();
9429
+ if (nativeRoutes) {
9430
+ serverOptions.routes = nativeRoutes;
9431
+ }
9432
+ }
9897
9433
  if (this.wsConfig) {
9898
9434
  serverOptions.websocket = this.wsConfig;
9899
9435
  }
@@ -9919,12 +9455,19 @@ function registerServerHandling(RouterClass) {
9919
9455
  const port = this.serverInstance.port;
9920
9456
  const hostname = this.serverInstance.hostname;
9921
9457
  this.serverInstance.stop();
9922
- this.serverInstance = Bun.serve({
9458
+ const reloadOptions = {
9923
9459
  port,
9924
9460
  hostname,
9925
9461
  fetch: this.handleRequest.bind(this),
9926
9462
  websocket: this.wsConfig || undefined
9927
- });
9463
+ };
9464
+ if (this._nativeRoutesEnabled) {
9465
+ const nativeRoutes = this._buildNativeRoutes();
9466
+ if (nativeRoutes) {
9467
+ reloadOptions.routes = nativeRoutes;
9468
+ }
9469
+ }
9470
+ this.serverInstance = Bun.serve(reloadOptions);
9928
9471
  if (this.config.verbose) {
9929
9472
  console.log(`\uD83D\uDD04 Server reloaded at http://${hostname}:${port}`);
9930
9473
  }
@@ -9940,46 +9483,177 @@ function registerServerHandling(RouterClass) {
9940
9483
  writable: true,
9941
9484
  configurable: true
9942
9485
  },
9486
+ _dispatchMatchedRoute: {
9487
+ async value(matchedRoute, req, params) {
9488
+ const enhancedReq = this.enhanceRequest(req, params);
9489
+ setCurrentRequest(enhancedReq);
9490
+ enhancedReq.route = matchedRoute;
9491
+ const route = matchedRoute;
9492
+ const epoch = this._mwEpoch || 0;
9493
+ const routeMwLen = route.middleware ? route.middleware.length : 0;
9494
+ let chain = route._compiledChain;
9495
+ if (!chain || route._chainEpoch !== epoch || route._chainMwLen !== routeMwLen) {
9496
+ const invoke = createHandlerInvoker(route.handler, this.config);
9497
+ if (routeMwLen === 0 && this.globalMiddleware.length === 0) {
9498
+ chain = invoke;
9499
+ } else {
9500
+ const middlewareStack = routeMwLen > 0 ? [...this.globalMiddleware, ...route.middleware] : [...this.globalMiddleware];
9501
+ middlewareStack.push((handlerReq, _next) => invoke(handlerReq));
9502
+ chain = this.buildMiddlewareChain(middlewareStack);
9503
+ }
9504
+ route._compiledChain = chain;
9505
+ route._chainEpoch = epoch;
9506
+ route._chainMwLen = routeMwLen;
9507
+ }
9508
+ let response2;
9509
+ try {
9510
+ response2 = await chain(enhancedReq);
9511
+ } catch (error) {
9512
+ if (this.errorHandler) {
9513
+ response2 = await this.errorHandler(error);
9514
+ } else {
9515
+ throw error;
9516
+ }
9517
+ }
9518
+ if (response2) {
9519
+ return this.applyModifiedCookies(response2, enhancedReq);
9520
+ }
9521
+ return new Response("No response from middleware chain", { status: 500 });
9522
+ },
9523
+ writable: true,
9524
+ configurable: true
9525
+ },
9526
+ _buildNativeRoutes: {
9527
+ value() {
9528
+ const NATIVE_METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]);
9529
+ const convertPath = (path) => {
9530
+ if (path === "*") {
9531
+ return "/*";
9532
+ }
9533
+ const segments = path.split("/");
9534
+ const converted = [];
9535
+ for (let i = 0;i < segments.length; i++) {
9536
+ const segment = segments[i];
9537
+ if (segment === "") {
9538
+ converted.push(segment);
9539
+ continue;
9540
+ }
9541
+ if (segment === "*") {
9542
+ return i === segments.length - 1 ? [...converted, "*"].join("/") : null;
9543
+ }
9544
+ const paramMatch = segment.match(/^\{([A-Z_$][\w$]*)\}$/i);
9545
+ if (paramMatch) {
9546
+ converted.push(`:${paramMatch[1]}`);
9547
+ continue;
9548
+ }
9549
+ if (segment.includes("{") || segment.includes(":") || segment.includes("*")) {
9550
+ return null;
9551
+ }
9552
+ converted.push(segment);
9553
+ }
9554
+ return converted.join("/");
9555
+ };
9556
+ const self = this;
9557
+ const wrapRoute = (route, isWildcard) => {
9558
+ return (req) => {
9559
+ return runWithRequest(req, async () => {
9560
+ try {
9561
+ let params = req.params ?? {};
9562
+ if (isWildcard) {
9563
+ const pathname = new URL(req.url).pathname;
9564
+ const basePath = route.path === "*" ? "/" : route.path.slice(0, -1);
9565
+ params = { ...params, wildcard: pathname.slice(basePath.length) };
9566
+ }
9567
+ return await self._dispatchMatchedRoute(route, req, params);
9568
+ } catch (error) {
9569
+ console.error("Error handling request:", error);
9570
+ if (self.errorHandler) {
9571
+ return self.errorHandler(error);
9572
+ }
9573
+ return new Response(JSON.stringify({
9574
+ success: false,
9575
+ message: "Internal Server Error",
9576
+ error: error instanceof Error ? error.message : String(error)
9577
+ }), {
9578
+ status: 500,
9579
+ headers: {
9580
+ "Content-Type": "application/json",
9581
+ "Access-Control-Allow-Origin": "*",
9582
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
9583
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, Accept, Origin"
9584
+ }
9585
+ });
9586
+ }
9587
+ });
9588
+ };
9589
+ };
9590
+ const natives = {};
9591
+ const claimedShapes = new Map;
9592
+ for (const route of this.routes) {
9593
+ if (!NATIVE_METHODS.has(route.method)) {
9594
+ continue;
9595
+ }
9596
+ if (route.nativeDispatch === false) {
9597
+ continue;
9598
+ }
9599
+ if (route.constraints && Object.keys(route.constraints).length > 0) {
9600
+ continue;
9601
+ }
9602
+ if (route.handler instanceof Response) {
9603
+ continue;
9604
+ }
9605
+ const bunPath = convertPath(route.path);
9606
+ if (!bunPath) {
9607
+ continue;
9608
+ }
9609
+ const shape = bunPath.replace(/:[^/]+/g, ":p");
9610
+ const claimedBy = claimedShapes.get(shape);
9611
+ if (claimedBy && claimedBy !== bunPath) {
9612
+ continue;
9613
+ }
9614
+ claimedShapes.set(shape, bunPath);
9615
+ const entry = natives[bunPath] ??= {};
9616
+ if (entry[route.method]) {
9617
+ continue;
9618
+ }
9619
+ entry[route.method] = wrapRoute(route, route.path.endsWith("*"));
9620
+ }
9621
+ for (const entry of Object.values(natives)) {
9622
+ if (entry.GET && !entry.HEAD) {
9623
+ entry.HEAD = entry.GET;
9624
+ }
9625
+ }
9626
+ return Object.keys(natives).length > 0 ? natives : null;
9627
+ },
9628
+ writable: true,
9629
+ configurable: true
9630
+ },
9943
9631
  handleRequestImpl: {
9944
9632
  async value(req) {
9945
9633
  try {
9946
9634
  const url2 = new URL(req.url);
9947
- if (req.method === "OPTIONS") {
9948
- const hostname2 = url2.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
9949
- const optionsMatch = this.matchRoute(url2.pathname, "OPTIONS", hostname2);
9950
- if (!optionsMatch) {
9951
- return new Response(null, {
9952
- status: 204,
9953
- headers: {
9954
- "Access-Control-Allow-Origin": "*",
9955
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
9956
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, Accept, Origin",
9957
- "Access-Control-Max-Age": "86400",
9958
- "Access-Control-Allow-Credentials": "true"
9959
- }
9960
- });
9961
- }
9962
- }
9635
+ req._parsedURL = url2;
9963
9636
  const hostname = url2.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
9964
9637
  const match = this.matchRoute(url2.pathname, req.method, hostname);
9965
- const enhancedReq = this.enhanceRequest(req, match?.params || {});
9966
- setCurrentRequest(enhancedReq);
9967
- if (match) {
9968
- enhancedReq.route = match.route;
9969
- const middlewareStack = [...this.globalMiddleware];
9970
- if (match.route.middleware && match.route.middleware.length > 0) {
9971
- middlewareStack.push(...match.route.middleware);
9972
- }
9973
- const routeHandlerMiddleware = async (req2, _next) => {
9974
- return await this.resolveHandler(match.route.handler, req2);
9638
+ if (req.method === "OPTIONS" && !match) {
9639
+ const origin = req.headers.get("origin");
9640
+ const preflightHeaders = {
9641
+ "Access-Control-Allow-Origin": origin || "*",
9642
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
9643
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, Accept, Origin",
9644
+ "Access-Control-Max-Age": "86400"
9975
9645
  };
9976
- middlewareStack.push(routeHandlerMiddleware);
9977
- const response2 = await this.runMiddleware(enhancedReq, middlewareStack);
9978
- if (response2) {
9979
- return this.applyModifiedCookies(response2, enhancedReq);
9646
+ if (origin) {
9647
+ preflightHeaders["Access-Control-Allow-Credentials"] = "true";
9648
+ preflightHeaders.Vary = "Origin";
9980
9649
  }
9981
- return new Response("No response from middleware chain", { status: 500 });
9650
+ return new Response(null, { status: 204, headers: preflightHeaders });
9651
+ }
9652
+ if (match) {
9653
+ return await this._dispatchMatchedRoute(match.route, req, match.params);
9982
9654
  }
9655
+ const enhancedReq = this.enhanceRequest(req, {});
9656
+ setCurrentRequest(enhancedReq);
9983
9657
  const allowedMethods = this.getAllowedMethods(url2.pathname, hostname);
9984
9658
  const corsHeaders = {
9985
9659
  "Content-Type": "application/json",
@@ -10052,63 +9726,18 @@ function registerServerHandling(RouterClass) {
10052
9726
  },
10053
9727
  enhanceRequest: {
10054
9728
  value(req, params = {}) {
10055
- let parsedCookies = null;
10056
- const getCookies = () => {
10057
- if (parsedCookies === null) {
10058
- parsedCookies = {};
10059
- const cookieHeader = req.headers.get("cookie") || "";
10060
- cookieHeader.split(";").forEach((cookie) => {
10061
- const parts = cookie.trim().split("=");
10062
- if (parts.length >= 2) {
10063
- const name = parts[0].trim();
10064
- const value = parts.slice(1).join("=").trim();
10065
- parsedCookies[name] = decodeURIComponent(value);
10066
- }
10067
- });
10068
- }
10069
- return parsedCookies;
10070
- };
10071
- const cookies = {
10072
- get: (name) => getCookies()[name],
10073
- set: (name, value, options = {}) => {
10074
- const enhancedRequest = req;
10075
- if (!enhancedRequest._cookiesToSet) {
10076
- enhancedRequest._cookiesToSet = [];
10077
- }
10078
- enhancedRequest._cookiesToSet.push({ name, value, options });
10079
- },
10080
- delete: (name, options = {}) => {
10081
- const enhancedRequest = req;
10082
- if (!enhancedRequest._cookiesToDelete) {
10083
- enhancedRequest._cookiesToDelete = [];
10084
- }
10085
- enhancedRequest._cookiesToDelete.push({ name, options });
10086
- },
10087
- getAll: () => ({ ...getCookies() })
10088
- };
10089
- const enhancedReq = Object.assign(req, {
10090
- params,
10091
- cookies: getCookies(),
10092
- _cookiesToSet: [],
10093
- _cookiesToDelete: []
10094
- });
10095
- RequestWithMacros.applyMacros(enhancedReq);
10096
- enhancedReq.getParam = (name, defaultValue) => {
10097
- const value = params?.[name];
10098
- return value !== undefined ? value : defaultValue;
10099
- };
10100
- enhancedReq.cookie = (name, defaultValue) => {
10101
- const value = getCookies()[name];
10102
- return value !== undefined ? value : defaultValue ?? null;
10103
- };
10104
- if (typeof enhancedReq.get !== "function") {
10105
- enhancedReq.get = (key, defaultValue) => {
10106
- const url2 = new URL(req.url);
10107
- const value = url2.searchParams.get(key);
10108
- return value !== null && value !== undefined ? value : defaultValue;
10109
- };
9729
+ const enhancedReq = req;
9730
+ if ("params" in req) {
9731
+ Object.defineProperty(enhancedReq, "params", {
9732
+ value: params,
9733
+ writable: true,
9734
+ configurable: true,
9735
+ enumerable: true
9736
+ });
9737
+ } else {
9738
+ enhancedReq.params = params;
10110
9739
  }
10111
- Object.assign(enhancedReq, { cookies: { ...getCookies(), ...cookies } });
9740
+ RequestWithMacros.applyMacros(enhancedReq);
10112
9741
  return enhancedReq;
10113
9742
  },
10114
9743
  writable: true,
@@ -10116,6 +9745,11 @@ function registerServerHandling(RouterClass) {
10116
9745
  },
10117
9746
  applyModifiedCookies: {
10118
9747
  value(response2, req) {
9748
+ const hasSet = req._cookiesToSet && req._cookiesToSet.length > 0;
9749
+ const hasDelete = req._cookiesToDelete && req._cookiesToDelete.length > 0;
9750
+ if (!hasSet && !hasDelete) {
9751
+ return response2;
9752
+ }
10119
9753
  const newResponse = new Response(response2.body, {
10120
9754
  status: response2.status,
10121
9755
  statusText: response2.statusText,
@@ -10665,6 +10299,7 @@ function registerBuiltInResponseMacros() {
10665
10299
  registerBuiltInResponseMacros();
10666
10300
 
10667
10301
  // src/validation/validator.ts
10302
+ var EMAIL_REGEX = /^[A-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?(?:\.[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?)*\.[A-Z]{2,}$/i;
10668
10303
  var BuiltInRules = {
10669
10304
  required: {
10670
10305
  name: "required",
@@ -10703,10 +10338,7 @@ var BuiltInRules = {
10703
10338
  },
10704
10339
  email: {
10705
10340
  name: "email",
10706
- validate: (value) => {
10707
- const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;
10708
- return typeof value === "string" && emailRegex.test(value);
10709
- },
10341
+ validate: (value) => typeof value === "string" && EMAIL_REGEX.test(value),
10710
10342
  message: "The :field field must be a valid email address."
10711
10343
  },
10712
10344
  url: {
@@ -10876,6 +10508,25 @@ var BuiltInRules = {
10876
10508
  message: "The :field field must be an object."
10877
10509
  }
10878
10510
  };
10511
+ var DatabaseRules = {
10512
+ unique: {
10513
+ name: "unique",
10514
+ validate: async (_value, _parameters) => {
10515
+ console.warn('Database validation rule "unique" requires external implementation');
10516
+ return true;
10517
+ },
10518
+ message: "The :field field must be unique."
10519
+ },
10520
+ exists: {
10521
+ name: "exists",
10522
+ validate: async (_value, _parameters) => {
10523
+ console.warn('Database validation rule "exists" requires external implementation');
10524
+ return true;
10525
+ },
10526
+ message: "The selected :field is invalid."
10527
+ }
10528
+ };
10529
+
10879
10530
  class Validator {
10880
10531
  rules = {};
10881
10532
  config;
@@ -10979,6 +10630,153 @@ function createValidationMiddleware(rules, config2) {
10979
10630
  };
10980
10631
  }
10981
10632
 
10633
+ class ValidationRuleBuilder {
10634
+ rules = [];
10635
+ required() {
10636
+ this.rules.push("required");
10637
+ return this;
10638
+ }
10639
+ string() {
10640
+ this.rules.push("string");
10641
+ return this;
10642
+ }
10643
+ number() {
10644
+ this.rules.push("number");
10645
+ return this;
10646
+ }
10647
+ integer() {
10648
+ this.rules.push("integer");
10649
+ return this;
10650
+ }
10651
+ boolean() {
10652
+ this.rules.push("boolean");
10653
+ return this;
10654
+ }
10655
+ email() {
10656
+ this.rules.push("email");
10657
+ return this;
10658
+ }
10659
+ url() {
10660
+ this.rules.push("url");
10661
+ return this;
10662
+ }
10663
+ min(value) {
10664
+ this.rules.push(`min:${value}`);
10665
+ return this;
10666
+ }
10667
+ max(value) {
10668
+ this.rules.push(`max:${value}`);
10669
+ return this;
10670
+ }
10671
+ between(min, max) {
10672
+ this.rules.push(`between:${min},${max}`);
10673
+ return this;
10674
+ }
10675
+ in(...values) {
10676
+ this.rules.push(`in:${values.join(",")}`);
10677
+ return this;
10678
+ }
10679
+ notIn(...values) {
10680
+ this.rules.push(`not_in:${values.join(",")}`);
10681
+ return this;
10682
+ }
10683
+ regex(pattern) {
10684
+ this.rules.push(`regex:${pattern}`);
10685
+ return this;
10686
+ }
10687
+ alpha() {
10688
+ this.rules.push("alpha");
10689
+ return this;
10690
+ }
10691
+ alphaNum() {
10692
+ this.rules.push("alpha_num");
10693
+ return this;
10694
+ }
10695
+ alphaDash() {
10696
+ this.rules.push("alpha_dash");
10697
+ return this;
10698
+ }
10699
+ date() {
10700
+ this.rules.push("date");
10701
+ return this;
10702
+ }
10703
+ after(date) {
10704
+ this.rules.push(`after:${date}`);
10705
+ return this;
10706
+ }
10707
+ before(date) {
10708
+ this.rules.push(`before:${date}`);
10709
+ return this;
10710
+ }
10711
+ confirmed(field) {
10712
+ this.rules.push(field ? `confirmed:${field}` : "confirmed");
10713
+ return this;
10714
+ }
10715
+ same(field) {
10716
+ this.rules.push(`same:${field}`);
10717
+ return this;
10718
+ }
10719
+ different(field) {
10720
+ this.rules.push(`different:${field}`);
10721
+ return this;
10722
+ }
10723
+ json() {
10724
+ this.rules.push("json");
10725
+ return this;
10726
+ }
10727
+ uuid() {
10728
+ this.rules.push("uuid");
10729
+ return this;
10730
+ }
10731
+ array() {
10732
+ this.rules.push("array");
10733
+ return this;
10734
+ }
10735
+ object() {
10736
+ this.rules.push("object");
10737
+ return this;
10738
+ }
10739
+ unique(table, column) {
10740
+ this.rules.push(column ? `unique:${table},${column}` : `unique:${table}`);
10741
+ return this;
10742
+ }
10743
+ exists(table, column) {
10744
+ this.rules.push(column ? `exists:${table},${column}` : `exists:${table}`);
10745
+ return this;
10746
+ }
10747
+ custom(ruleName, ...parameters) {
10748
+ this.rules.push(parameters.length > 0 ? `${ruleName}:${parameters.join(",")}` : ruleName);
10749
+ return this;
10750
+ }
10751
+ build() {
10752
+ return this.rules.join("|");
10753
+ }
10754
+ }
10755
+ function rule() {
10756
+ return new ValidationRuleBuilder;
10757
+ }
10758
+ var ValidationHelpers = {
10759
+ rules(rules) {
10760
+ const result = {};
10761
+ for (const [field, fieldRules] of Object.entries(rules)) {
10762
+ result[field] = fieldRules instanceof ValidationRuleBuilder ? fieldRules.build() : fieldRules;
10763
+ }
10764
+ return result;
10765
+ },
10766
+ async validateValue(value, rules, field = "value") {
10767
+ const validator = new Validator;
10768
+ const errors = await validator.validate({ [field]: value }, { [field]: rules });
10769
+ return errors[field] || [];
10770
+ },
10771
+ async passes(value, rules) {
10772
+ const errors = await this.validateValue(value, rules);
10773
+ return errors.length === 0;
10774
+ },
10775
+ async fails(value, rules) {
10776
+ return !await this.passes(value, rules);
10777
+ }
10778
+ };
10779
+
10982
10780
  // src/router/validation-integration.ts
10983
10781
  class RouteBuilder {
10984
10782
  method;
@@ -11015,7 +10813,7 @@ class RouteBuilder {
11015
10813
  }
11016
10814
  }
11017
10815
 
11018
- class FluentRouter2 {
10816
+ class ValidationFluentRouter {
11019
10817
  routes = [];
11020
10818
  get(path, handler) {
11021
10819
  return new RouteBuilder("GET", path, handler);
@@ -11128,7 +10926,7 @@ var RouteHelpers = {
11128
10926
  }
11129
10927
  };
11130
10928
  function createFluentRouter() {
11131
- return new FluentRouter2;
10929
+ return new ValidationFluentRouter;
11132
10930
  }
11133
10931
  var MiddlewareHelpers = {
11134
10932
  compose: (...middleware2) => {
@@ -13782,11 +13580,13 @@ export {
13782
13580
  singleFileUpload,
13783
13581
  signRequest,
13784
13582
  setCurrentRequest,
13583
+ setContainer,
13785
13584
  session,
13786
13585
  securitySuite,
13787
13586
  security,
13788
13587
  scopedBindingHelpers,
13789
13588
  runWithRequest,
13589
+ rule,
13790
13590
  router,
13791
13591
  routeModelHelpers,
13792
13592
  routeCacheManager,
@@ -13825,10 +13625,12 @@ export {
13825
13625
  inputValidation,
13826
13626
  injectQueryPreservationScript,
13827
13627
  imageUpload,
13628
+ globalValidator,
13828
13629
  globalMiddlewarePipeline,
13829
13630
  getNamedRoutes,
13830
13631
  getNamedRoutePath,
13831
13632
  getCurrentRequest,
13633
+ getContainer,
13832
13634
  getConfig,
13833
13635
  generateSessionId,
13834
13636
  generateQueryPreservationScript,
@@ -13841,12 +13643,14 @@ export {
13841
13643
  extractBearerToken,
13842
13644
  extractBasicAuth,
13843
13645
  extractApiKey,
13646
+ enableRequestContext,
13844
13647
  documentUpload,
13845
13648
  defaultQueryPreservationConfig,
13846
13649
  defaultConfig,
13847
13650
  ddosProtection,
13848
13651
  csrf,
13849
13652
  createWebSocketTester,
13653
+ createValidationMiddleware,
13850
13654
  createTestResponse,
13851
13655
  createTestClient,
13852
13656
  createSubdomainMiddleware,
@@ -13868,6 +13672,7 @@ export {
13868
13672
  createFluentRouter,
13869
13673
  createFileUploadTester,
13870
13674
  createEnhancementMiddleware,
13675
+ createContainer,
13871
13676
  createAuthTester,
13872
13677
  cors,
13873
13678
  contentSecurityPolicy,
@@ -13885,7 +13690,11 @@ export {
13885
13690
  applyRequestEnhancements,
13886
13691
  apiKeyAuth,
13887
13692
  WebSocketTester,
13693
+ Validator,
13694
+ ValidationRuleBuilder,
13888
13695
  ValidationMiddlewareBuilder,
13696
+ ValidationHelpers,
13697
+ ValidationFluentRouter,
13889
13698
  ThrottleFactory,
13890
13699
  TestResponseWrapper,
13891
13700
  TestRequestBuilder,
@@ -13951,17 +13760,24 @@ export {
13951
13760
  FileSessionStore,
13952
13761
  FileSecurity,
13953
13762
  exports_errors as Errors,
13763
+ EnvironmentManager,
13954
13764
  EnhancementPresets,
13955
13765
  DomainMatcher,
13956
13766
  DomainGroupBuilder,
13957
13767
  DomainGroup,
13958
13768
  Dependencies,
13769
+ DefaultServiceProviderManager,
13959
13770
  DatabaseSessionStore,
13771
+ DatabaseRules,
13960
13772
  DDoSProtection,
13961
13773
  Csrf,
13962
13774
  Cors,
13775
+ ContextualContainer,
13963
13776
  ContentSecurityPolicy,
13777
+ Container,
13964
13778
  CacheInvalidation,
13779
+ BuiltInRules,
13780
+ BindingBuilder,
13965
13781
  AuthTester,
13966
13782
  auth_default as Auth,
13967
13783
  ApiKeyManager