@stacksjs/bun-router 0.0.16 → 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-0145g1me.js → chunk-cgptvjdf.js} +147 -75
  4. package/dist/chunk-g3ybefhg.js +1923 -0
  5. package/dist/cli.js +3 -2
  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 +1159 -1380
  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 +70 -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 +9 -6
  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-0145g1me.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);
@@ -6051,314 +5535,126 @@ class FluentRouter {
6051
5535
  }
6052
5536
  this.register(builder);
6053
5537
  }
6054
- }
6055
- return this;
6056
- }
6057
- getRoutes() {
6058
- return [...this.routes, ...this.subdomainRouter.getAllRoutes()];
6059
- }
6060
- async handle(request2) {
6061
- const _url = new URL(request2.url);
6062
- const url2 = new URL(request2.url);
6063
- const domain = url2.hostname;
6064
- const match = this.subdomainRouter.findDomainGroup(domain);
6065
- if (match) {
6066
- const enhancedReq = request2;
6067
- enhancedReq.domainParams = match.parameters;
6068
- for (const route of match.group.getRoutes()) {
6069
- if (this.matchesRoute(route, request2)) {
6070
- return await this.executeRoute(route, enhancedReq);
6071
- }
6072
- }
6073
- }
6074
- for (const route of this.routes) {
6075
- if (this.matchesRoute(route, request2)) {
6076
- return await this.executeRoute(route, request2);
6077
- }
6078
- }
6079
- return null;
6080
- }
6081
- matchesRoute(route, request2) {
6082
- if (route.method !== request2.method) {
6083
- return false;
6084
- }
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);
6089
- }
6090
- async executeRoute(route, request2) {
6091
- const allMiddleware = [...this.globalMiddleware, ...route.middleware];
6092
- let index = 0;
6093
- const next = async () => {
6094
- if (index < allMiddleware.length) {
6095
- const middleware2 = allMiddleware[index++];
6096
- return await middleware2(request2, next);
6097
- }
6098
- return await route.handler(request2);
6099
- };
6100
- return await next();
6101
- }
6102
- }
6103
- var RouteFactory = {
6104
- api: (path, handler, options = {}) => {
6105
- const method = options.method || "GET";
6106
- const builder = new FluentRouteBuilder(method, path, handler);
6107
- if (options.cache) {
6108
- builder.cached(options.cache);
6109
- }
6110
- if (options.throttle) {
6111
- builder.throttle(options.throttle);
6112
- }
6113
- return builder;
6114
- },
6115
- protected: (path, handler, options = {}) => {
6116
- const method = options.method || "GET";
6117
- const builder = new FluentRouteBuilder(method, path, handler);
6118
- builder.addMiddleware(async (req, next) => {
6119
- if (!req.headers.get("Authorization")) {
6120
- return new Response("Unauthorized", { status: 401 });
6121
- }
6122
- return await next();
6123
- });
6124
- return builder;
6125
- },
6126
- upload: (path, handler, _options = {}) => {
6127
- const builder = new FluentRouteBuilder("POST", path, handler);
6128
- builder.addMiddleware(async (_req, next) => {
6129
- return await next();
6130
- });
6131
- return builder;
6132
- }
6133
- };
6134
- var RouterUtils = {
6135
- route: (name, params = {}, query = {}) => {
6136
- let url2 = `/${name}`;
6137
- for (const [key, value] of Object.entries(params)) {
6138
- url2 = url2.replace(`{${key}}`, value);
6139
- }
6140
- const queryString = new URLSearchParams(query).toString();
6141
- if (queryString) {
6142
- url2 += `?${queryString}`;
6143
- }
6144
- return url2;
6145
- },
6146
- redirect: (url2, status = 302) => {
6147
- return new Response(null, {
6148
- status,
6149
- headers: { Location: url2 }
6150
- });
6151
- },
6152
- json: (data, status = 200) => {
6153
- return new Response(JSON.stringify(data), {
6154
- status,
6155
- headers: { "Content-Type": "application/json" }
6156
- });
6157
- }
6158
- };
6159
- var router = new FluentRouter;
6160
-
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
- var PARAM_SEGMENT_RE = /\{([^}:]+)(?::[^}]+)?(\?)?\}/;
6203
- var OPTIONAL_SEGMENT_RE = /\{[^}]+\?\}/;
6204
- var compiledPatternCache = new Map;
6205
- var constraintRegexCache = new Map;
6206
- function compilePattern(pattern) {
6207
- const cached = compiledPatternCache.get(pattern);
6208
- if (cached)
6209
- return cached;
6210
- const isWildcard = pattern.endsWith("/*");
6211
- const normalizedPattern = normalizePath(pattern);
6212
- let optionalCount = 0;
6213
- const segments = normalizedPattern.split("/").filter(Boolean).map((seg) => {
6214
- const isOptional = OPTIONAL_SEGMENT_RE.test(seg);
6215
- if (isOptional)
6216
- optionalCount++;
6217
- const paramMatch = seg.match(PARAM_SEGMENT_RE);
6218
- return {
6219
- isParam: !!paramMatch,
6220
- paramName: paramMatch ? paramMatch[1].replace("?", "") : "",
6221
- isOptional,
6222
- literal: seg
6223
- };
6224
- });
6225
- const compiled = {
6226
- isWildcard,
6227
- wildcardBase: isWildcard ? pattern.slice(0, -2) : "",
6228
- segments,
6229
- optionalCount
6230
- };
6231
- compiledPatternCache.set(pattern, compiled);
6232
- return compiled;
6233
- }
6234
- function getConstraintRegex(constraint) {
6235
- const cached = constraintRegexCache.get(constraint);
6236
- if (cached !== undefined)
6237
- return cached;
6238
- let regex;
6239
- try {
6240
- regex = new RegExp(`^${constraint}$`);
6241
- } catch {
6242
- regex = null;
6243
- }
6244
- constraintRegexCache.set(constraint, regex);
6245
- return regex;
6246
- }
6247
- function matchPath(pattern, path, params, constraints) {
6248
- const compiled = compilePattern(pattern);
6249
- if (compiled.isWildcard && path.startsWith(compiled.wildcardBase)) {
6250
- return true;
5538
+ }
5539
+ return this;
6251
5540
  }
6252
- const patternSegments = compiled.segments;
6253
- const pathSegments = normalizePath(path).split("/").filter(Boolean);
6254
- if (pathSegments.length < patternSegments.length - compiled.optionalCount || pathSegments.length > patternSegments.length) {
6255
- return false;
5541
+ getRoutes() {
5542
+ return [...this.routes, ...this.subdomainRouter.getAllRoutes()];
6256
5543
  }
6257
- for (let i = 0;i < patternSegments.length; i++) {
6258
- const seg = patternSegments[i];
6259
- const pathSegment = pathSegments[i];
6260
- if (pathSegment === undefined) {
6261
- if (seg.isOptional) {
6262
- continue;
5544
+ async handle(request2) {
5545
+ const url2 = new URL(request2.url);
5546
+ const domain = url2.hostname;
5547
+ const match = this.subdomainRouter.findDomainGroup(domain);
5548
+ if (match) {
5549
+ const enhancedReq = request2;
5550
+ enhancedReq.domainParams = match.parameters;
5551
+ for (const route of match.group.getRoutes()) {
5552
+ const params = this.matchRouteParams(route, request2.method, url2.pathname);
5553
+ if (params) {
5554
+ return await this.executeRoute(route, enhancedReq, params);
5555
+ }
6263
5556
  }
6264
- return false;
6265
5557
  }
6266
- if (seg.isParam) {
6267
- params[seg.paramName] = pathSegment;
6268
- if (constraints && constraints[seg.paramName]) {
6269
- const regex = getConstraintRegex(constraints[seg.paramName]);
6270
- if (regex) {
6271
- if (!regex.test(pathSegment)) {
6272
- return false;
6273
- }
6274
- } else {
6275
- console.warn(`Invalid constraint regex for parameter ${seg.paramName}: ${constraints[seg.paramName]}`);
6276
- }
5558
+ for (const route of this.routes) {
5559
+ const params = this.matchRouteParams(route, request2.method, url2.pathname);
5560
+ if (params) {
5561
+ return await this.executeRoute(route, request2, params);
6277
5562
  }
6278
- } else if (seg.literal !== pathSegment) {
6279
- return false;
6280
5563
  }
5564
+ return null;
6281
5565
  }
6282
- return true;
6283
- }
6284
- function joinPaths(...segments) {
6285
- return normalizePath(segments.join("/"));
6286
- }
6287
- function validatePath(path) {
6288
- if (!path.startsWith("/")) {
6289
- return false;
5566
+ matchRouteParams(route, method, pathname) {
5567
+ if (route.method !== method) {
5568
+ return null;
5569
+ }
5570
+ const params = {};
5571
+ return matchPath(route.path, pathname, params) ? params : null;
6290
5572
  }
6291
- const stack = [];
6292
- for (const char of path) {
6293
- if (char === "{") {
6294
- stack.push(char);
6295
- } else if (char === "}") {
6296
- if (stack.length === 0 || stack.pop() !== "{") {
6297
- return false;
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);
6298
5579
  }
6299
5580
  }
6300
- }
6301
- return stack.length === 0;
6302
- }
6303
- async function fileExists(path) {
6304
- try {
6305
- const file = Bun.file(path);
6306
- return await file.exists();
6307
- } catch {
6308
- return false;
5581
+ allMiddleware.push(...route.middleware);
5582
+ let index = 0;
5583
+ const next = async () => {
5584
+ if (index < allMiddleware.length) {
5585
+ const middleware2 = allMiddleware[index++];
5586
+ const result = await middleware2(request2, next);
5587
+ return result ?? new Response(null);
5588
+ }
5589
+ return await route.handler(request2);
5590
+ };
5591
+ return await next();
6309
5592
  }
6310
5593
  }
6311
- async function resolveViewPath(viewName, viewsPath, extensions) {
6312
- if (extensions.some((ext) => viewName.endsWith(ext))) {
6313
- const fullPath = normalizePath(join3(viewsPath, viewName));
6314
- if (await fileExists(fullPath)) {
6315
- return fullPath;
5594
+ var RouteFactory = {
5595
+ api: (path, handler, options = {}) => {
5596
+ const method = options.method || "GET";
5597
+ const builder = new FluentRouteBuilder(method, path, handler);
5598
+ if (options.cache) {
5599
+ builder.cached(options.cache);
6316
5600
  }
6317
- return null;
5601
+ if (options.throttle) {
5602
+ builder.throttle(options.throttle);
5603
+ }
5604
+ return builder;
5605
+ },
5606
+ protected: (path, handler, options = {}) => {
5607
+ const method = options.method || "GET";
5608
+ const builder = new FluentRouteBuilder(method, path, handler);
5609
+ builder.addMiddleware(async (req, next) => {
5610
+ if (!req.headers.get("Authorization")) {
5611
+ return new Response("Unauthorized", { status: 401 });
5612
+ }
5613
+ return await next();
5614
+ });
5615
+ return builder;
5616
+ },
5617
+ upload: (path, handler, _options = {}) => {
5618
+ const builder = new FluentRouteBuilder("POST", path, handler);
5619
+ builder.addMiddleware(async (_req, next) => {
5620
+ return await next();
5621
+ });
5622
+ return builder;
6318
5623
  }
6319
- for (const ext of extensions) {
6320
- const fullPath = normalizePath(join3(viewsPath, `${viewName}${ext}`));
6321
- if (await fileExists(fullPath)) {
6322
- return fullPath;
5624
+ };
5625
+ var RouterUtils = {
5626
+ route: (name, params = {}, query = {}) => {
5627
+ let url2 = getNamedRoutePath(name) ?? `/${name}`;
5628
+ for (const [key, value] of Object.entries(params)) {
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);
5637
+ }
5638
+ const queryString = new URLSearchParams(query).toString();
5639
+ if (queryString) {
5640
+ url2 += `?${queryString}`;
6323
5641
  }
5642
+ return url2;
5643
+ },
5644
+ redirect: (url2, status = 302) => {
5645
+ return new Response(null, {
5646
+ status,
5647
+ headers: { Location: url2 }
5648
+ });
5649
+ },
5650
+ json: (data, status = 200) => {
5651
+ return new Response(JSON.stringify(data), {
5652
+ status,
5653
+ headers: { "Content-Type": "application/json" }
5654
+ });
6324
5655
  }
6325
- return null;
6326
- }
6327
- function processHtmlTemplate(template, data) {
6328
- let result = template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
6329
- const trimmedKey = key.trim();
6330
- const value = getNestedValue(data, trimmedKey);
6331
- return value !== undefined ? String(value) : match;
6332
- });
6333
- result = processConditionals(result, data);
6334
- result = processLoops(result, data);
6335
- return result;
6336
- }
6337
- function processConditionals(template, data) {
6338
- return template.replace(/\{\{#if ([^}]+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g, (_match, condition, content, alternative = "") => {
6339
- const conditionTrimmed = condition.trim();
6340
- const value = getNestedValue(data, conditionTrimmed);
6341
- return value ? content : alternative;
6342
- });
6343
- }
6344
- function processLoops(template, data) {
6345
- return template.replace(/\{\{#each ([^}]+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_match, arrayKey, content) => {
6346
- const array = getNestedValue(data, arrayKey.trim());
6347
- if (!Array.isArray(array))
6348
- return "";
6349
- return array.map((item) => {
6350
- return content.replace(/\{\{@index\}\}/g, String(array.indexOf(item))).replace(/\{\{this\}\}/g, String(item)).replace(/\{\{this\.([^}]+)\}\}/g, (_matchStr, prop) => {
6351
- const value = item[prop.trim()];
6352
- return value !== undefined ? String(value) : "";
6353
- });
6354
- }).join("");
6355
- });
6356
- }
6357
- function getNestedValue(obj, path) {
6358
- return path.split(".").reduce((prev, curr) => {
6359
- return prev && prev[curr] !== undefined ? prev[curr] : undefined;
6360
- }, obj);
6361
- }
5656
+ };
5657
+ var router = new FluentRouter;
6362
5658
 
6363
5659
  // src/router/group-organization.ts
6364
5660
  function registerGroupOrganization(RouterClass) {
@@ -6379,7 +5675,16 @@ function registerGroupOrganization(RouterClass) {
6379
5675
  ];
6380
5676
  }
6381
5677
  this.currentGroup = newGroup;
6382
- 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
+ }
6383
5688
  this.currentGroup = previousGroup;
6384
5689
  return this;
6385
5690
  },
@@ -6576,10 +5881,11 @@ function registerHttpMethods(RouterClass) {
6576
5881
  this.namedRoutes.set(name, route);
6577
5882
  registerNamedRoute(name, route.path);
6578
5883
  }
6579
- if (this.addRouteToCompiler) {
5884
+ if (this.addRouteToCompiler && !domain) {
6580
5885
  this.addRouteToCompiler(route);
6581
5886
  }
6582
5887
  this.routeCache.clear();
5888
+ this._allowedMethodsCache?.clear();
6583
5889
  return this;
6584
5890
  },
6585
5891
  writable: true,
@@ -6627,6 +5933,13 @@ function registerHttpMethods(RouterClass) {
6627
5933
  writable: true,
6628
5934
  configurable: true
6629
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
+ },
6630
5943
  match: {
6631
5944
  value(methods, path, handler, type, name, middleware2) {
6632
5945
  for (const method of methods) {
@@ -6664,6 +5977,12 @@ function registerHttpMethods(RouterClass) {
6664
5977
  url2 = url2.replace(`{${param}}`, encodeURIComponent(value));
6665
5978
  url2 = url2.replace(`{${param}?}`, encodeURIComponent(value));
6666
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
+ }
6667
5986
  return url2;
6668
5987
  },
6669
5988
  writable: true,
@@ -6874,6 +6193,19 @@ Last error: ${lastError?.message || "Unknown error"}`);
6874
6193
  function createHandlerResolver(config2) {
6875
6194
  return (handler, req) => resolveHandler(handler, req, config2);
6876
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
+ }
6877
6209
 
6878
6210
  // src/router/middleware.ts
6879
6211
  function registerMiddlewareHandling(RouterClass) {
@@ -6886,6 +6218,7 @@ function registerMiddlewareHandling(RouterClass) {
6886
6218
  this.globalMiddleware.push(resolvedMiddleware);
6887
6219
  }
6888
6220
  }
6221
+ this._mwEpoch = (this._mwEpoch || 0) + 1;
6889
6222
  return this;
6890
6223
  },
6891
6224
  writable: true,
@@ -7532,40 +6865,36 @@ class RouteTrie {
7532
6865
  return null;
7533
6866
  }
7534
6867
  const segment = segments[segmentIndex];
7535
- const candidates = [];
7536
- if (node.children.has(segment)) {
7537
- candidates.push({
7538
- node: node.children.get(segment),
7539
- newParams: { ...params }
7540
- });
7541
- }
7542
- if (node.paramChild) {
7543
- const paramNode = node.paramChild;
7544
- let matches = true;
7545
- if (paramNode.pattern) {
7546
- matches = paramNode.pattern.test(segment);
7547
- }
7548
- if (matches && paramNode.paramName) {
7549
- candidates.push({
7550
- node: paramNode,
7551
- newParams: { ...params, [paramNode.paramName]: segment }
7552
- });
7553
- }
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;
7554
6873
  }
7555
- if (node.wildcardChild) {
7556
- const remainingPath = segments.slice(segmentIndex).join("/");
7557
- 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);
7558
6879
  if (match)
7559
6880
  return match;
6881
+ if (previous === undefined)
6882
+ delete params[paramNode.paramName];
6883
+ else
6884
+ params[paramNode.paramName] = previous;
7560
6885
  }
7561
- let bestMatch = null;
7562
- for (const candidate of candidates) {
7563
- const match = this.matchSegments(candidate.node, segments, segmentIndex + 1, candidate.newParams, method);
7564
- if (match && (!bestMatch || match.score > bestMatch.score)) {
7565
- 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
+ };
7566
6895
  }
7567
6896
  }
7568
- return bestMatch;
6897
+ return null;
7569
6898
  }
7570
6899
  getAllRoutes() {
7571
6900
  return Array.from(this.compiledRoutes.values());
@@ -7619,7 +6948,7 @@ class RouteTrie {
7619
6948
  class RouteCompiler {
7620
6949
  trie;
7621
6950
  matchCache = new Map;
7622
- cacheKeys = [];
6951
+ routeKeys = new Set;
7623
6952
  stats = {
7624
6953
  totalMatches: 0,
7625
6954
  cacheHits: 0,
@@ -7636,6 +6965,7 @@ class RouteCompiler {
7636
6965
  enablePriorityOptimization: true,
7637
6966
  cacheSize: 1000,
7638
6967
  precompilePatterns: true,
6968
+ enableProfiling: false,
7639
6969
  ...options
7640
6970
  };
7641
6971
  this.trie = new RouteTrie;
@@ -7644,10 +6974,11 @@ class RouteCompiler {
7644
6974
  if (this.options.precompilePatterns) {
7645
6975
  this.precompileRoutePattern(route);
7646
6976
  }
7647
- const isDuplicate = this.checkForDuplicateRoute(route);
7648
- if (isDuplicate) {
6977
+ const routeKey = `${route.method}:${route.path}`;
6978
+ if (this.routeKeys.has(routeKey)) {
7649
6979
  return false;
7650
6980
  }
6981
+ this.routeKeys.add(routeKey);
7651
6982
  this.trie.addRoute(route);
7652
6983
  if (this.options.enableCaching) {
7653
6984
  this.matchCache.clear();
@@ -7656,15 +6987,6 @@ class RouteCompiler {
7656
6987
  this.stats.methodDistribution[method] = (this.stats.methodDistribution[method] || 0) + 1;
7657
6988
  return true;
7658
6989
  }
7659
- checkForDuplicateRoute(route) {
7660
- const routes = this.trie.getAllRoutes();
7661
- for (const existingRoute of routes) {
7662
- if (existingRoute.route.path === route.path && existingRoute.route.method === route.method) {
7663
- return true;
7664
- }
7665
- }
7666
- return false;
7667
- }
7668
6990
  patternCache = new Map;
7669
6991
  extractParamNames(path) {
7670
6992
  const paramNames = [];
@@ -7707,17 +7029,25 @@ class RouteCompiler {
7707
7029
  }
7708
7030
  }
7709
7031
  convertToURLPattern(path, constraints) {
7710
- let regexPattern = path.replace(/\{([^}:]+)(?::([^}]+))?\}/g, (_match, name, ..._args) => {
7711
- const pattern = _args[0];
7712
- if (constraints && constraints[name]) {
7713
- return `(?<${name}>${constraints[name]})`;
7714
- } else if (pattern) {
7715
- return `(?<${name}>${pattern})`;
7716
- }
7717
- 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}`;
7718
7049
  });
7719
- regexPattern = `^${regexPattern.replace(/\//g, "\\/")}$`;
7720
- const regex = new RegExp(regexPattern);
7050
+ const regex = new RegExp(`^${regexPattern}$`);
7721
7051
  return {
7722
7052
  exec: (pathname) => {
7723
7053
  const match = pathname.match(regex);
@@ -7730,16 +7060,16 @@ class RouteCompiler {
7730
7060
  };
7731
7061
  }
7732
7062
  match(path, method) {
7733
- const startTime = performance.now();
7063
+ const startTime = this.options.enableProfiling ? performance.now() : 0;
7734
7064
  this.stats.totalMatches++;
7735
7065
  if (this.options.enableCaching) {
7736
7066
  const cacheKey = `${method}:${path}`;
7737
- if (this.matchCache.has(cacheKey)) {
7067
+ const cached = this.matchCache.get(cacheKey);
7068
+ if (cached !== undefined || this.matchCache.has(cacheKey)) {
7738
7069
  this.stats.cacheHits++;
7739
- this.updateCacheLRU(cacheKey);
7740
- const cached = this.matchCache.get(cacheKey);
7741
- this.updateMatchTime(startTime);
7742
- return cached || null;
7070
+ if (startTime)
7071
+ this.updateMatchTime(startTime);
7072
+ return cached ? { route: cached.route, params: { ...cached.params } } : null;
7743
7073
  }
7744
7074
  this.stats.cacheMisses++;
7745
7075
  }
@@ -7755,25 +7085,18 @@ class RouteCompiler {
7755
7085
  const cacheKey = `${method}:${path}`;
7756
7086
  this.addToCache(cacheKey, result);
7757
7087
  }
7758
- this.updateMatchTime(startTime);
7759
- return result;
7088
+ if (startTime)
7089
+ this.updateMatchTime(startTime);
7090
+ return result ? { route: result.route, params: { ...result.params } } : null;
7760
7091
  }
7761
7092
  addToCache(key, value) {
7762
7093
  if (this.matchCache.size >= this.options.cacheSize && !this.matchCache.has(key)) {
7763
- if (this.cacheKeys.length > 0) {
7764
- const lruKey = this.cacheKeys.shift();
7094
+ const lruKey = this.matchCache.keys().next().value;
7095
+ if (lruKey !== undefined) {
7765
7096
  this.matchCache.delete(lruKey);
7766
7097
  }
7767
7098
  }
7768
7099
  this.matchCache.set(key, value);
7769
- this.updateCacheLRU(key);
7770
- }
7771
- updateCacheLRU(key) {
7772
- const index = this.cacheKeys.indexOf(key);
7773
- if (index !== -1) {
7774
- this.cacheKeys.splice(index, 1);
7775
- }
7776
- this.cacheKeys.push(key);
7777
7100
  }
7778
7101
  updateMatchTime(startTime) {
7779
7102
  const matchTime = performance.now() - startTime;
@@ -7842,7 +7165,7 @@ class RouteCompiler {
7842
7165
  clear() {
7843
7166
  this.trie.clear();
7844
7167
  this.matchCache.clear();
7845
- this.cacheKeys = [];
7168
+ this.routeKeys.clear();
7846
7169
  this.stats = {
7847
7170
  totalMatches: 0,
7848
7171
  cacheHits: 0,
@@ -8036,14 +7359,25 @@ function registerOptimizedRouteMatching(RouterClass) {
8036
7359
  matchRoute: {
8037
7360
  value(path, method, domain) {
8038
7361
  this.initializeRouteCompiler();
8039
- const match = this.routeCompiler.match(path, method);
8040
- if (match) {
8041
- if (domain && match.route.domain && match.route.domain !== domain) {
8042
- return;
7362
+ if (domain && this.domains[domain]) {
7363
+ const domainMatch = this.fallbackMatchRoute(path, method, domain);
7364
+ if (domainMatch) {
7365
+ return domainMatch;
8043
7366
  }
8044
- return match;
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;
7375
+ }
7376
+ }
7377
+ if (method === "HEAD") {
7378
+ return this.matchRoute(path, "GET", domain);
8045
7379
  }
8046
- return this.fallbackMatchRoute(path, method, domain);
7380
+ return;
8047
7381
  },
8048
7382
  writable: true,
8049
7383
  configurable: true
@@ -8055,6 +7389,9 @@ function registerOptimizedRouteMatching(RouterClass) {
8055
7389
  if (this.routeCache.has(cacheKey)) {
8056
7390
  return this.routeCache.get(cacheKey);
8057
7391
  }
7392
+ if (this.routeCache.size >= 1e4) {
7393
+ this.routeCache.clear();
7394
+ }
8058
7395
  if (this.staticRoutes.has(method)) {
8059
7396
  const staticRoute = this.staticRoutes.get(method).get(url2.pathname);
8060
7397
  if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
@@ -8091,6 +7428,24 @@ function registerOptimizedRouteMatching(RouterClass) {
8091
7428
  }
8092
7429
  }
8093
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);
8094
7449
  return;
8095
7450
  },
8096
7451
  writable: true,
@@ -8509,6 +7864,17 @@ function registerRouteMatching(RouterClass) {
8509
7864
  writable: true,
8510
7865
  configurable: true
8511
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
+ },
8512
7878
  whereNumber: {
8513
7879
  value(param) {
8514
7880
  return this.where({ [param]: "\\d+" });
@@ -8571,6 +7937,8 @@ class Router {
8571
7937
  namedMiddleware = new Map;
8572
7938
  conditionalMiddleware = [];
8573
7939
  _middlewarePipeline;
7940
+ _mwEpoch = 0;
7941
+ _allowedMethodsCache = new Map;
8574
7942
  config = {
8575
7943
  verbose: false,
8576
7944
  routesPath: "routes",
@@ -8671,6 +8039,7 @@ class Router {
8671
8039
  }
8672
8040
  invalidateCache() {
8673
8041
  this.routeCache.clear();
8042
+ this._allowedMethodsCache.clear();
8674
8043
  }
8675
8044
  registerRoute(method, path, handler, type, name, middleware2) {
8676
8045
  let routePath = path;
@@ -8985,50 +8354,44 @@ class Router {
8985
8354
  }
8986
8355
  getAllowedMethods(path, domain) {
8987
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
+ }
8988
8362
  const methods = new Set;
8989
- const allMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
8990
- for (const method of allMethods) {
8991
- if (this.staticRoutes.has(method)) {
8992
- const staticRoute = this.staticRoutes.get(method).get(url2.pathname);
8993
- if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
8994
- methods.add(method);
8995
- continue;
8996
- }
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;
8997
8373
  }
8998
- const potentialRoutes = domain && this.domains[domain] ? this.domains[domain] : this.routes;
8999
- const methodRoutes = potentialRoutes.filter((route) => route.method === method);
9000
- for (const route of methodRoutes) {
9001
- if (route.path === url2.pathname) {
9002
- methods.add(method);
9003
- break;
9004
- }
8374
+ if (route.path === url2.pathname) {
8375
+ methods.add(route.method);
8376
+ continue;
9005
8377
  }
9006
- if (!methods.has(method)) {
9007
- for (const route of methodRoutes) {
9008
- if (route.pattern) {
9009
- const match = route.pattern.exec(url2);
9010
- if (match) {
9011
- methods.add(method);
9012
- break;
9013
- }
9014
- }
9015
- }
8378
+ if (route.pattern && route.pattern.exec(url2)) {
8379
+ methods.add(route.method);
8380
+ continue;
9016
8381
  }
9017
- if (!methods.has(method)) {
9018
- const wildcardRoutes = potentialRoutes.filter((route) => route.method === method && route.path.endsWith("*"));
9019
- for (const route of wildcardRoutes) {
9020
- const basePath = route.path.slice(0, -1);
9021
- if (url2.pathname.startsWith(basePath)) {
9022
- methods.add(method);
9023
- break;
9024
- }
9025
- }
8382
+ if (route.path.endsWith("*") && url2.pathname.startsWith(route.path.slice(0, -1))) {
8383
+ methods.add(route.method);
9026
8384
  }
9027
8385
  }
9028
8386
  if (methods.has("GET")) {
9029
8387
  methods.add("HEAD");
9030
8388
  }
9031
- 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;
9032
8395
  }
9033
8396
  matchRoute(path, method, domain) {
9034
8397
  const url2 = new URL(path, "http://localhost");
@@ -9456,12 +8819,15 @@ class Router {
9456
8819
  }
9457
8820
  const result = callback();
9458
8821
  if (result instanceof Promise) {
9459
- result.then(() => {
8822
+ return result.then(() => {
8823
+ this.currentGroup = previousGroup;
8824
+ return this;
8825
+ }, (error) => {
9460
8826
  this.currentGroup = previousGroup;
8827
+ throw error;
9461
8828
  });
9462
- } else {
9463
- this.currentGroup = previousGroup;
9464
8829
  }
8830
+ this.currentGroup = previousGroup;
9465
8831
  return this;
9466
8832
  }
9467
8833
  addRoute(route) {
@@ -9593,10 +8959,48 @@ function applyRequestEnhancements(req, params = {}) {
9593
8959
  }
9594
8960
 
9595
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
+
9596
8998
  class RequestMacroRegistry {
9597
8999
  macros = new Map;
9598
- register(name, handler) {
9599
- 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++;
9600
9004
  }
9601
9005
  get(name) {
9602
9006
  return this.macros.get(name);
@@ -9608,23 +9012,71 @@ class RequestMacroRegistry {
9608
9012
  return Array.from(this.macros.values());
9609
9013
  }
9610
9014
  remove(name) {
9611
- return this.macros.delete(name);
9015
+ const removed = this.macros.delete(name);
9016
+ if (removed)
9017
+ this.epoch++;
9018
+ return removed;
9612
9019
  }
9613
9020
  clear() {
9614
9021
  this.macros.clear();
9022
+ this.epoch++;
9023
+ }
9024
+ getEpoch() {
9025
+ return this.epoch;
9615
9026
  }
9616
9027
  }
9617
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;
9618
9033
 
9619
9034
  class RequestWithMacros {
9620
9035
  static macro(name, handler) {
9621
9036
  requestMacroRegistry.register(name, handler);
9622
9037
  }
9038
+ static macroAccessor(name, getter) {
9039
+ requestMacroRegistry.register(name, getter, true);
9040
+ }
9623
9041
  static applyMacros(request2) {
9624
- const macros = requestMacroRegistry.all();
9625
- macros.forEach((macro) => {
9626
- request2[macro.name] = macro.handler.bind(request2);
9627
- });
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);
9628
9080
  return request2;
9629
9081
  }
9630
9082
  static hasMacro(name) {
@@ -9654,14 +9106,10 @@ var BuiltInRequestMacros = {
9654
9106
  return this.headers.get("x-pjax") === "true";
9655
9107
  },
9656
9108
  isMobile() {
9657
- const userAgent = this.headers.get("user-agent") || "";
9658
- const mobileRegex = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
9659
- return mobileRegex.test(userAgent);
9109
+ return MOBILE_REGEX.test(this.headers.get("user-agent") || "");
9660
9110
  },
9661
9111
  isBot() {
9662
- const userAgent = this.headers.get("user-agent") || "";
9663
- const botRegex = /bot|crawler|spider|crawling/i;
9664
- return botRegex.test(userAgent);
9112
+ return BOT_REGEX.test(this.headers.get("user-agent") || "");
9665
9113
  },
9666
9114
  isSecure() {
9667
9115
  return this.url.startsWith("https://") || this.headers.get("x-forwarded-proto") === "https" || this.headers.get("x-forwarded-ssl") === "on";
@@ -9689,6 +9137,17 @@ var BuiltInRequestMacros = {
9689
9137
  }
9690
9138
  return null;
9691
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
+ },
9692
9151
  basicAuth() {
9693
9152
  const auth2 = this.headers.get("authorization");
9694
9153
  if (auth2 && auth2.startsWith("Basic ")) {
@@ -9793,30 +9252,11 @@ var BuiltInRequestMacros = {
9793
9252
  return this.params?.[key] || defaultValue;
9794
9253
  },
9795
9254
  cookie(name, defaultValue) {
9796
- const cookies = this.headers.get("cookie");
9797
- if (!cookies)
9798
- return defaultValue || null;
9799
- const cookieArray = cookies.split(";");
9800
- for (const cookie of cookieArray) {
9801
- const [key, value] = cookie.trim().split("=");
9802
- if (key === name) {
9803
- return decodeURIComponent(value);
9804
- }
9805
- }
9806
- return defaultValue || null;
9255
+ const value = getParsedCookies(this)[name];
9256
+ return value !== undefined ? value : defaultValue || null;
9807
9257
  },
9808
9258
  cookies() {
9809
- const cookies = {};
9810
- const cookieHeader = this.headers.get("cookie");
9811
- if (cookieHeader) {
9812
- cookieHeader.split(";").forEach((cookie) => {
9813
- const [key, value] = cookie.trim().split("=");
9814
- if (key && value) {
9815
- cookies[key] = decodeURIComponent(value);
9816
- }
9817
- });
9818
- }
9819
- return cookies;
9259
+ return { ...getParsedCookies(this) };
9820
9260
  },
9821
9261
  file(name) {
9822
9262
  return this.files?.find((f) => f.fieldName === name);
@@ -9825,26 +9265,33 @@ var BuiltInRequestMacros = {
9825
9265
  return !!this.files?.find((f) => f.fieldName === name);
9826
9266
  },
9827
9267
  path() {
9828
- return new URL(this.url).pathname;
9268
+ return getParsedURL(this).pathname;
9829
9269
  },
9830
9270
  fullUrl() {
9831
9271
  return this.url;
9832
9272
  },
9833
9273
  root() {
9834
- const url2 = new URL(this.url);
9274
+ const url2 = getParsedURL(this);
9835
9275
  return `${url2.protocol}//${url2.host}`;
9836
9276
  },
9837
9277
  is(pattern) {
9838
9278
  const path = this.path();
9839
- const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
9840
- 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
+ }
9841
9288
  return regex.test(path);
9842
9289
  },
9843
9290
  route() {
9844
9291
  return this.routeName || null;
9845
9292
  },
9846
9293
  fingerprint() {
9847
- const url2 = new URL(this.url);
9294
+ const url2 = getParsedURL(this);
9848
9295
  const data = {
9849
9296
  method: this.method,
9850
9297
  path: url2.pathname,
@@ -9900,6 +9347,51 @@ function registerBuiltInRequestMacros() {
9900
9347
  registerBuiltInRequestMacros();
9901
9348
 
9902
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
+ });
9903
9395
  function registerServerHandling(RouterClass) {
9904
9396
  Object.defineProperties(RouterClass.prototype, {
9905
9397
  serve: {
@@ -9931,6 +9423,13 @@ function registerServerHandling(RouterClass) {
9931
9423
  if (options.development !== undefined) {
9932
9424
  serverOptions.development = options.development;
9933
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
+ }
9934
9433
  if (this.wsConfig) {
9935
9434
  serverOptions.websocket = this.wsConfig;
9936
9435
  }
@@ -9956,12 +9455,19 @@ function registerServerHandling(RouterClass) {
9956
9455
  const port = this.serverInstance.port;
9957
9456
  const hostname = this.serverInstance.hostname;
9958
9457
  this.serverInstance.stop();
9959
- this.serverInstance = Bun.serve({
9458
+ const reloadOptions = {
9960
9459
  port,
9961
9460
  hostname,
9962
9461
  fetch: this.handleRequest.bind(this),
9963
9462
  websocket: this.wsConfig || undefined
9964
- });
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);
9965
9471
  if (this.config.verbose) {
9966
9472
  console.log(`\uD83D\uDD04 Server reloaded at http://${hostname}:${port}`);
9967
9473
  }
@@ -9977,46 +9483,177 @@ function registerServerHandling(RouterClass) {
9977
9483
  writable: true,
9978
9484
  configurable: true
9979
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
+ },
9980
9631
  handleRequestImpl: {
9981
9632
  async value(req) {
9982
9633
  try {
9983
9634
  const url2 = new URL(req.url);
9984
- if (req.method === "OPTIONS") {
9985
- const hostname2 = url2.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
9986
- const optionsMatch = this.matchRoute(url2.pathname, "OPTIONS", hostname2);
9987
- if (!optionsMatch) {
9988
- return new Response(null, {
9989
- status: 204,
9990
- headers: {
9991
- "Access-Control-Allow-Origin": "*",
9992
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
9993
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, Accept, Origin",
9994
- "Access-Control-Max-Age": "86400",
9995
- "Access-Control-Allow-Credentials": "true"
9996
- }
9997
- });
9998
- }
9999
- }
9635
+ req._parsedURL = url2;
10000
9636
  const hostname = url2.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
10001
9637
  const match = this.matchRoute(url2.pathname, req.method, hostname);
10002
- const enhancedReq = this.enhanceRequest(req, match?.params || {});
10003
- setCurrentRequest(enhancedReq);
10004
- if (match) {
10005
- enhancedReq.route = match.route;
10006
- const middlewareStack = [...this.globalMiddleware];
10007
- if (match.route.middleware && match.route.middleware.length > 0) {
10008
- middlewareStack.push(...match.route.middleware);
10009
- }
10010
- const routeHandlerMiddleware = async (req2, _next) => {
10011
- 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"
10012
9645
  };
10013
- middlewareStack.push(routeHandlerMiddleware);
10014
- const response2 = await this.runMiddleware(enhancedReq, middlewareStack);
10015
- if (response2) {
10016
- return this.applyModifiedCookies(response2, enhancedReq);
9646
+ if (origin) {
9647
+ preflightHeaders["Access-Control-Allow-Credentials"] = "true";
9648
+ preflightHeaders.Vary = "Origin";
10017
9649
  }
10018
- 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);
10019
9654
  }
9655
+ const enhancedReq = this.enhanceRequest(req, {});
9656
+ setCurrentRequest(enhancedReq);
10020
9657
  const allowedMethods = this.getAllowedMethods(url2.pathname, hostname);
10021
9658
  const corsHeaders = {
10022
9659
  "Content-Type": "application/json",
@@ -10089,63 +9726,18 @@ function registerServerHandling(RouterClass) {
10089
9726
  },
10090
9727
  enhanceRequest: {
10091
9728
  value(req, params = {}) {
10092
- let parsedCookies = null;
10093
- const getCookies = () => {
10094
- if (parsedCookies === null) {
10095
- parsedCookies = {};
10096
- const cookieHeader = req.headers.get("cookie") || "";
10097
- cookieHeader.split(";").forEach((cookie) => {
10098
- const parts = cookie.trim().split("=");
10099
- if (parts.length >= 2) {
10100
- const name = parts[0].trim();
10101
- const value = parts.slice(1).join("=").trim();
10102
- parsedCookies[name] = decodeURIComponent(value);
10103
- }
10104
- });
10105
- }
10106
- return parsedCookies;
10107
- };
10108
- const cookies = {
10109
- get: (name) => getCookies()[name],
10110
- set: (name, value, options = {}) => {
10111
- const enhancedRequest = req;
10112
- if (!enhancedRequest._cookiesToSet) {
10113
- enhancedRequest._cookiesToSet = [];
10114
- }
10115
- enhancedRequest._cookiesToSet.push({ name, value, options });
10116
- },
10117
- delete: (name, options = {}) => {
10118
- const enhancedRequest = req;
10119
- if (!enhancedRequest._cookiesToDelete) {
10120
- enhancedRequest._cookiesToDelete = [];
10121
- }
10122
- enhancedRequest._cookiesToDelete.push({ name, options });
10123
- },
10124
- getAll: () => ({ ...getCookies() })
10125
- };
10126
- const enhancedReq = Object.assign(req, {
10127
- params,
10128
- cookies: getCookies(),
10129
- _cookiesToSet: [],
10130
- _cookiesToDelete: []
10131
- });
10132
- RequestWithMacros.applyMacros(enhancedReq);
10133
- enhancedReq.getParam = (name, defaultValue) => {
10134
- const value = params?.[name];
10135
- return value !== undefined ? value : defaultValue;
10136
- };
10137
- enhancedReq.cookie = (name, defaultValue) => {
10138
- const value = getCookies()[name];
10139
- return value !== undefined ? value : defaultValue ?? null;
10140
- };
10141
- if (typeof enhancedReq.get !== "function") {
10142
- enhancedReq.get = (key, defaultValue) => {
10143
- const url2 = new URL(req.url);
10144
- const value = url2.searchParams.get(key);
10145
- return value !== null && value !== undefined ? value : defaultValue;
10146
- };
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;
10147
9739
  }
10148
- Object.assign(enhancedReq, { cookies: { ...getCookies(), ...cookies } });
9740
+ RequestWithMacros.applyMacros(enhancedReq);
10149
9741
  return enhancedReq;
10150
9742
  },
10151
9743
  writable: true,
@@ -10153,6 +9745,11 @@ function registerServerHandling(RouterClass) {
10153
9745
  },
10154
9746
  applyModifiedCookies: {
10155
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
+ }
10156
9753
  const newResponse = new Response(response2.body, {
10157
9754
  status: response2.status,
10158
9755
  statusText: response2.statusText,
@@ -10702,6 +10299,7 @@ function registerBuiltInResponseMacros() {
10702
10299
  registerBuiltInResponseMacros();
10703
10300
 
10704
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;
10705
10303
  var BuiltInRules = {
10706
10304
  required: {
10707
10305
  name: "required",
@@ -10740,10 +10338,7 @@ var BuiltInRules = {
10740
10338
  },
10741
10339
  email: {
10742
10340
  name: "email",
10743
- validate: (value) => {
10744
- const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;
10745
- return typeof value === "string" && emailRegex.test(value);
10746
- },
10341
+ validate: (value) => typeof value === "string" && EMAIL_REGEX.test(value),
10747
10342
  message: "The :field field must be a valid email address."
10748
10343
  },
10749
10344
  url: {
@@ -10913,6 +10508,25 @@ var BuiltInRules = {
10913
10508
  message: "The :field field must be an object."
10914
10509
  }
10915
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
+
10916
10530
  class Validator {
10917
10531
  rules = {};
10918
10532
  config;
@@ -11016,6 +10630,153 @@ function createValidationMiddleware(rules, config2) {
11016
10630
  };
11017
10631
  }
11018
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
+
11019
10780
  // src/router/validation-integration.ts
11020
10781
  class RouteBuilder {
11021
10782
  method;
@@ -11052,7 +10813,7 @@ class RouteBuilder {
11052
10813
  }
11053
10814
  }
11054
10815
 
11055
- class FluentRouter2 {
10816
+ class ValidationFluentRouter {
11056
10817
  routes = [];
11057
10818
  get(path, handler) {
11058
10819
  return new RouteBuilder("GET", path, handler);
@@ -11165,7 +10926,7 @@ var RouteHelpers = {
11165
10926
  }
11166
10927
  };
11167
10928
  function createFluentRouter() {
11168
- return new FluentRouter2;
10929
+ return new ValidationFluentRouter;
11169
10930
  }
11170
10931
  var MiddlewareHelpers = {
11171
10932
  compose: (...middleware2) => {
@@ -13819,11 +13580,13 @@ export {
13819
13580
  singleFileUpload,
13820
13581
  signRequest,
13821
13582
  setCurrentRequest,
13583
+ setContainer,
13822
13584
  session,
13823
13585
  securitySuite,
13824
13586
  security,
13825
13587
  scopedBindingHelpers,
13826
13588
  runWithRequest,
13589
+ rule,
13827
13590
  router,
13828
13591
  routeModelHelpers,
13829
13592
  routeCacheManager,
@@ -13862,10 +13625,12 @@ export {
13862
13625
  inputValidation,
13863
13626
  injectQueryPreservationScript,
13864
13627
  imageUpload,
13628
+ globalValidator,
13865
13629
  globalMiddlewarePipeline,
13866
13630
  getNamedRoutes,
13867
13631
  getNamedRoutePath,
13868
13632
  getCurrentRequest,
13633
+ getContainer,
13869
13634
  getConfig,
13870
13635
  generateSessionId,
13871
13636
  generateQueryPreservationScript,
@@ -13878,12 +13643,14 @@ export {
13878
13643
  extractBearerToken,
13879
13644
  extractBasicAuth,
13880
13645
  extractApiKey,
13646
+ enableRequestContext,
13881
13647
  documentUpload,
13882
13648
  defaultQueryPreservationConfig,
13883
13649
  defaultConfig,
13884
13650
  ddosProtection,
13885
13651
  csrf,
13886
13652
  createWebSocketTester,
13653
+ createValidationMiddleware,
13887
13654
  createTestResponse,
13888
13655
  createTestClient,
13889
13656
  createSubdomainMiddleware,
@@ -13905,6 +13672,7 @@ export {
13905
13672
  createFluentRouter,
13906
13673
  createFileUploadTester,
13907
13674
  createEnhancementMiddleware,
13675
+ createContainer,
13908
13676
  createAuthTester,
13909
13677
  cors,
13910
13678
  contentSecurityPolicy,
@@ -13922,7 +13690,11 @@ export {
13922
13690
  applyRequestEnhancements,
13923
13691
  apiKeyAuth,
13924
13692
  WebSocketTester,
13693
+ Validator,
13694
+ ValidationRuleBuilder,
13925
13695
  ValidationMiddlewareBuilder,
13696
+ ValidationHelpers,
13697
+ ValidationFluentRouter,
13926
13698
  ThrottleFactory,
13927
13699
  TestResponseWrapper,
13928
13700
  TestRequestBuilder,
@@ -13988,17 +13760,24 @@ export {
13988
13760
  FileSessionStore,
13989
13761
  FileSecurity,
13990
13762
  exports_errors as Errors,
13763
+ EnvironmentManager,
13991
13764
  EnhancementPresets,
13992
13765
  DomainMatcher,
13993
13766
  DomainGroupBuilder,
13994
13767
  DomainGroup,
13995
13768
  Dependencies,
13769
+ DefaultServiceProviderManager,
13996
13770
  DatabaseSessionStore,
13771
+ DatabaseRules,
13997
13772
  DDoSProtection,
13998
13773
  Csrf,
13999
13774
  Cors,
13775
+ ContextualContainer,
14000
13776
  ContentSecurityPolicy,
13777
+ Container,
14001
13778
  CacheInvalidation,
13779
+ BuiltInRules,
13780
+ BindingBuilder,
14002
13781
  AuthTester,
14003
13782
  auth_default as Auth,
14004
13783
  ApiKeyManager