@stacksjs/bun-router 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -64,11 +64,48 @@ import {
64
64
  signRequest,
65
65
  singleFileUpload,
66
66
  verifySignature
67
- } from "./chunk-y3xq6wq6.js";
67
+ } from "./chunk-cgptvjdf.js";
68
68
  import {
69
- ValidationException
70
- } from "./chunk-a8p83068.js";
71
- import"./chunk-2mx7fq49.js";
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";
108
+
72
109
  // src/auth.ts
73
110
  import { timingSafeEqual } from "crypto";
74
111
  function base64UrlEncode(str) {
@@ -215,123 +252,1800 @@ class ApiKeyManager {
215
252
  ...options
216
253
  };
217
254
  }
218
- generateKey(owner, scopes = [], expiresIn) {
219
- const key = `${this.options.keyPrefix}${this.randomString(32)}`;
220
- this.keys.set(key, {
221
- owner,
222
- scopes,
223
- expiresAt: expiresIn ? new Date(Date.now() + expiresIn * 1000) : undefined
255
+ generateKey(owner, scopes = [], expiresIn) {
256
+ const key = `${this.options.keyPrefix}${this.randomString(32)}`;
257
+ this.keys.set(key, {
258
+ owner,
259
+ scopes,
260
+ expiresAt: expiresIn ? new Date(Date.now() + expiresIn * 1000) : undefined
261
+ });
262
+ return key;
263
+ }
264
+ validateKey(key, requiredScopes = []) {
265
+ const keyInfo = this.keys.get(key);
266
+ if (!keyInfo) {
267
+ return false;
268
+ }
269
+ if (keyInfo.expiresAt && keyInfo.expiresAt < new Date) {
270
+ return false;
271
+ }
272
+ if (requiredScopes.length > 0) {
273
+ return requiredScopes.every((scope) => keyInfo.scopes.includes(scope));
274
+ }
275
+ return true;
276
+ }
277
+ getKeyInfo(key) {
278
+ return this.keys.get(key) || null;
279
+ }
280
+ revokeKey(key) {
281
+ return this.keys.delete(key);
282
+ }
283
+ extractFromRequest(req) {
284
+ const { source, keyName } = this.options;
285
+ switch (source) {
286
+ case "header":
287
+ return req.headers.get(keyName || "X-API-Key") || null;
288
+ case "query":
289
+ return new URL(req.url).searchParams.get(keyName || "api_key") || null;
290
+ case "cookie":
291
+ if (req.cookies) {
292
+ const key = keyName || "api_key";
293
+ if (typeof req.cookies.get === "function") {
294
+ return req.cookies.get(key) || null;
295
+ }
296
+ return req.cookies[key] || null;
297
+ }
298
+ return null;
299
+ default:
300
+ return null;
301
+ }
302
+ }
303
+ randomString(length) {
304
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
305
+ let result = "";
306
+ for (let i = 0;i < length; i++) {
307
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
308
+ }
309
+ return result;
310
+ }
311
+ }
312
+
313
+ class OAuth2Helper {
314
+ config;
315
+ constructor(config2) {
316
+ this.config = config2;
317
+ }
318
+ getAuthorizationUrl(additionalParams = {}) {
319
+ const params = new URLSearchParams({
320
+ client_id: this.config.clientId,
321
+ redirect_uri: this.config.redirectUri,
322
+ response_type: "code",
323
+ ...additionalParams
324
+ });
325
+ if (this.config.scope) {
326
+ params.set("scope", this.config.scope);
327
+ }
328
+ if (this.config.state) {
329
+ params.set("state", this.config.state);
330
+ }
331
+ return `${this.config.authorizeUrl}?${params.toString()}`;
332
+ }
333
+ async exchangeCodeForToken(code) {
334
+ const params = new URLSearchParams({
335
+ grant_type: "authorization_code",
336
+ code,
337
+ redirect_uri: this.config.redirectUri,
338
+ client_id: this.config.clientId,
339
+ client_secret: this.config.clientSecret
340
+ });
341
+ const response = await fetch(this.config.tokenUrl, {
342
+ method: "POST",
343
+ headers: {
344
+ "Content-Type": "application/x-www-form-urlencoded"
345
+ },
346
+ body: params.toString()
347
+ });
348
+ if (!response.ok) {
349
+ throw new Error(`OAuth token exchange failed: ${response.status} ${response.statusText}`);
350
+ }
351
+ return await response.json();
352
+ }
353
+ async refreshToken(refreshToken) {
354
+ const params = new URLSearchParams({
355
+ grant_type: "refresh_token",
356
+ refresh_token: refreshToken,
357
+ client_id: this.config.clientId,
358
+ client_secret: this.config.clientSecret
359
+ });
360
+ const response = await fetch(this.config.tokenUrl, {
361
+ method: "POST",
362
+ headers: {
363
+ "Content-Type": "application/x-www-form-urlencoded"
364
+ },
365
+ body: params.toString()
366
+ });
367
+ if (!response.ok) {
368
+ throw new Error(`OAuth token refresh failed: ${response.status} ${response.statusText}`);
369
+ }
370
+ return await response.json();
371
+ }
372
+ }
373
+ var Auth = {
374
+ JWT,
375
+ ApiKeyManager,
376
+ OAuth2Helper
377
+ };
378
+ var auth_default = Auth;
379
+ // src/errors/index.ts
380
+ var exports_errors = {};
381
+ __export(exports_errors, {
382
+ withCircuitBreaker: () => withCircuitBreaker,
383
+ globalCircuitBreakerRegistry: () => globalCircuitBreakerRegistry,
384
+ findSimilarRoutes: () => findSimilarRoutes,
385
+ detectRouteConflicts: () => detectRouteConflicts,
386
+ createGracefulDegradationMiddleware: () => createGracefulDegradationMiddleware,
387
+ createErrorReporting: () => createErrorReporting,
388
+ createCircuitBreakerMiddleware: () => createCircuitBreakerMiddleware,
389
+ createCircuitBreaker: () => createCircuitBreaker,
390
+ createAdvancedErrorHandler: () => createAdvancedErrorHandler,
391
+ circuitBreaker: () => circuitBreaker,
392
+ ValidationException: () => ValidationException,
393
+ TimeoutException: () => TimeoutException,
394
+ ServiceUnavailableException: () => ServiceUnavailableException,
395
+ ServiceHealthMonitor: () => ServiceHealthMonitor,
396
+ SentryReporter: () => SentryReporter,
397
+ RouterException: () => RouterException,
398
+ RouterErrorFactory: () => RouterErrorFactory,
399
+ RouteNotFoundException: () => RouteNotFoundException,
400
+ RouteConflictException: () => RouteConflictException,
401
+ RateLimitException: () => RateLimitException,
402
+ NotFoundException: () => NotFoundException,
403
+ ModelBindingException: () => ModelBindingException,
404
+ MiddlewareConfigurationException: () => MiddlewareConfigurationException,
405
+ MethodNotAllowedException: () => MethodNotAllowedException,
406
+ InternalServerException: () => InternalServerException,
407
+ HandlerResolutionException: () => HandlerResolutionException,
408
+ GracefulDegradationManager: () => GracefulDegradationManager,
409
+ FileSystemException: () => FileSystemException,
410
+ ExternalServiceException: () => ExternalServiceException,
411
+ ErrorReportingPresets: () => ErrorReportingPresets,
412
+ ErrorReportingManager: () => ErrorReportingManager,
413
+ ErrorFactory: () => ErrorFactory,
414
+ DegradationStrategies: () => DegradationStrategies,
415
+ DatabaseException: () => DatabaseException,
416
+ CustomReporter: () => CustomReporter,
417
+ ConfigurationException: () => ConfigurationException,
418
+ CircularDependencyException: () => CircularDependencyException,
419
+ CircuitBreakerRegistry: () => CircuitBreakerRegistry,
420
+ CircuitBreakerPresets: () => CircuitBreakerPresets,
421
+ CircuitBreakerOpenException: () => CircuitBreakerOpenException,
422
+ CircuitBreakerHttpClient: () => CircuitBreakerHttpClient,
423
+ CircuitBreaker: () => CircuitBreaker,
424
+ BusinessLogicException: () => BusinessLogicException,
425
+ BugsnagReporter: () => BugsnagReporter,
426
+ AuthorizationException: () => AuthorizationException,
427
+ AuthenticationException: () => AuthenticationException
428
+ });
429
+
430
+ // src/errors/circuit-breaker.ts
431
+ class CircuitBreaker {
432
+ config;
433
+ state = "CLOSED";
434
+ metrics;
435
+ nextAttemptTime;
436
+ halfOpenCallCount = 0;
437
+ recentRequests = [];
438
+ constructor(config2) {
439
+ const defaults = {
440
+ failureThreshold: 5,
441
+ recoveryTimeout: 60000,
442
+ timeout: 30000,
443
+ monitoringPeriod: 60000,
444
+ minimumRequests: 10,
445
+ errorThresholdPercentage: 50,
446
+ halfOpenMaxCalls: 3,
447
+ resetTimeout: 60000
448
+ };
449
+ this.config = {
450
+ ...defaults,
451
+ ...config2
452
+ };
453
+ this.metrics = {
454
+ totalRequests: 0,
455
+ successfulRequests: 0,
456
+ failedRequests: 0,
457
+ timeouts: 0,
458
+ averageResponseTime: 0,
459
+ state: this.state,
460
+ consecutiveFailures: 0,
461
+ consecutiveSuccesses: 0
462
+ };
463
+ setInterval(() => this.cleanupOldRequests(), this.config.monitoringPeriod / 4).unref?.();
464
+ }
465
+ async execute(call) {
466
+ if (this.state === "OPEN") {
467
+ if (this.nextAttemptTime && new Date < this.nextAttemptTime) {
468
+ const error2 = new CircuitBreakerOpenException(this.config.name, this.nextAttemptTime, call.context);
469
+ if (call.fallback) {
470
+ try {
471
+ return await call.fallback();
472
+ } catch (fallbackError) {
473
+ console.error(fallbackError);
474
+ }
475
+ }
476
+ throw error2;
477
+ } else {
478
+ this.transitionToHalfOpen();
479
+ }
480
+ }
481
+ if (this.state === "HALF_OPEN" && this.halfOpenCallCount >= this.config.halfOpenMaxCalls) {
482
+ const error2 = new CircuitBreakerOpenException(this.config.name, this.nextAttemptTime || new Date(Date.now() + this.config.recoveryTimeout), call.context);
483
+ if (call.fallback) {
484
+ try {
485
+ return await call.fallback();
486
+ } catch (fallbackError) {
487
+ console.error(fallbackError);
488
+ throw error2;
489
+ }
490
+ }
491
+ throw error2;
492
+ }
493
+ const startTime = Date.now();
494
+ let result;
495
+ let error = null;
496
+ try {
497
+ if (this.state === "HALF_OPEN") {
498
+ this.halfOpenCallCount++;
499
+ }
500
+ const timeout = call.timeout || this.config.timeout;
501
+ const timeoutPromise = new Promise((_, reject) => {
502
+ setTimeout(() => {
503
+ reject(new TimeoutException(`Circuit breaker timeout for ${this.config.name}`, timeout, call.context));
504
+ }, timeout);
505
+ });
506
+ result = await Promise.race([call.execute(), timeoutPromise]);
507
+ this.onSuccess(Date.now() - startTime);
508
+ return result;
509
+ } catch (err) {
510
+ error = err;
511
+ this.onFailure(error, Date.now() - startTime);
512
+ if (call.fallback) {
513
+ try {
514
+ return await call.fallback();
515
+ } catch (fallbackError) {
516
+ console.error(fallbackError);
517
+ throw error;
518
+ }
519
+ } else {
520
+ throw error;
521
+ }
522
+ }
523
+ }
524
+ getState() {
525
+ return this.state;
526
+ }
527
+ getMetrics() {
528
+ return {
529
+ ...this.metrics,
530
+ state: this.state,
531
+ nextAttemptTime: this.nextAttemptTime
532
+ };
533
+ }
534
+ forceOpen() {
535
+ this.transitionToOpen();
536
+ }
537
+ forceClosed() {
538
+ this.transitionToClosed();
539
+ }
540
+ reset() {
541
+ this.metrics = {
542
+ totalRequests: 0,
543
+ successfulRequests: 0,
544
+ failedRequests: 0,
545
+ timeouts: 0,
546
+ averageResponseTime: 0,
547
+ state: this.state,
548
+ consecutiveFailures: 0,
549
+ consecutiveSuccesses: 0
550
+ };
551
+ this.recentRequests = [];
552
+ }
553
+ onSuccess(responseTime) {
554
+ this.metrics.totalRequests++;
555
+ this.metrics.successfulRequests++;
556
+ this.metrics.consecutiveSuccesses++;
557
+ this.metrics.consecutiveFailures = 0;
558
+ this.metrics.lastSuccessTime = new Date;
559
+ this.updateAverageResponseTime(responseTime);
560
+ this.recentRequests.push({
561
+ timestamp: new Date,
562
+ success: true,
563
+ responseTime
564
+ });
565
+ if (this.state === "HALF_OPEN") {
566
+ if (this.metrics.consecutiveSuccesses >= this.config.halfOpenMaxCalls) {
567
+ this.transitionToClosed();
568
+ }
569
+ }
570
+ if (this.config.onSuccess) {
571
+ this.config.onSuccess(this.config.name);
572
+ }
573
+ }
574
+ onFailure(error, responseTime) {
575
+ this.metrics.totalRequests++;
576
+ this.metrics.failedRequests++;
577
+ this.metrics.consecutiveFailures++;
578
+ this.metrics.consecutiveSuccesses = 0;
579
+ this.metrics.lastFailureTime = new Date;
580
+ if (error instanceof TimeoutException) {
581
+ this.metrics.timeouts++;
582
+ }
583
+ this.updateAverageResponseTime(responseTime);
584
+ this.recentRequests.push({
585
+ timestamp: new Date,
586
+ success: false,
587
+ responseTime
588
+ });
589
+ if (this.shouldTrip(error)) {
590
+ if (this.state === "CLOSED") {
591
+ this.transitionToOpen();
592
+ } else if (this.state === "HALF_OPEN") {
593
+ this.transitionToOpen();
594
+ }
595
+ }
596
+ if (this.config.onFailure) {
597
+ this.config.onFailure(error, this.config.name);
598
+ }
599
+ }
600
+ shouldTrip(error) {
601
+ if (this.config.shouldTripOnError && !this.config.shouldTripOnError(error)) {
602
+ return false;
603
+ }
604
+ if (this.metrics.consecutiveFailures >= this.config.failureThreshold) {
605
+ return true;
606
+ }
607
+ const recentRequests = this.getRecentRequests();
608
+ if (recentRequests.length >= this.config.minimumRequests) {
609
+ const failedRequests = recentRequests.filter((r) => !r.success).length;
610
+ const errorRate = failedRequests / recentRequests.length * 100;
611
+ if (errorRate >= this.config.errorThresholdPercentage) {
612
+ return true;
613
+ }
614
+ }
615
+ return false;
616
+ }
617
+ transitionToClosed() {
618
+ this.state = "CLOSED";
619
+ this.nextAttemptTime = undefined;
620
+ this.halfOpenCallCount = 0;
621
+ this.metrics.state = this.state;
622
+ if (this.config.onStateChange) {
623
+ this.config.onStateChange(this.state, this.config.name);
624
+ }
625
+ }
626
+ transitionToOpen() {
627
+ this.state = "OPEN";
628
+ this.nextAttemptTime = new Date(Date.now() + this.config.recoveryTimeout);
629
+ this.halfOpenCallCount = 0;
630
+ this.metrics.state = this.state;
631
+ this.metrics.nextAttemptTime = this.nextAttemptTime;
632
+ if (this.config.onStateChange) {
633
+ this.config.onStateChange(this.state, this.config.name);
634
+ }
635
+ }
636
+ transitionToHalfOpen() {
637
+ this.state = "HALF_OPEN";
638
+ this.halfOpenCallCount = 0;
639
+ this.metrics.state = this.state;
640
+ if (this.config.onStateChange) {
641
+ this.config.onStateChange(this.state, this.config.name);
642
+ }
643
+ }
644
+ updateAverageResponseTime(responseTime) {
645
+ if (this.metrics.totalRequests === 1) {
646
+ this.metrics.averageResponseTime = responseTime;
647
+ } else {
648
+ const alpha = 0.1;
649
+ this.metrics.averageResponseTime = alpha * responseTime + (1 - alpha) * this.metrics.averageResponseTime;
650
+ }
651
+ }
652
+ getRecentRequests() {
653
+ const cutoff = new Date(Date.now() - this.config.monitoringPeriod);
654
+ return this.recentRequests.filter((r) => r.timestamp > cutoff);
655
+ }
656
+ cleanupOldRequests() {
657
+ const cutoff = new Date(Date.now() - this.config.monitoringPeriod);
658
+ this.recentRequests = this.recentRequests.filter((r) => r.timestamp > cutoff);
659
+ }
660
+ }
661
+
662
+ class CircuitBreakerRegistry {
663
+ breakers = new Map;
664
+ configs = new Map;
665
+ register(config2) {
666
+ const breaker = new CircuitBreaker(config2);
667
+ this.breakers.set(config2.name, breaker);
668
+ this.configs.set(config2.name, config2);
669
+ return breaker;
670
+ }
671
+ get(name) {
672
+ return this.breakers.get(name);
673
+ }
674
+ async execute(name, call) {
675
+ const breaker = this.breakers.get(name);
676
+ if (!breaker) {
677
+ throw new Error(`Circuit breaker '${name}' not found`);
678
+ }
679
+ return breaker.execute(call);
680
+ }
681
+ getAllMetrics() {
682
+ const metrics = {};
683
+ for (const [name, breaker] of this.breakers) {
684
+ metrics[name] = breaker.getMetrics();
685
+ }
686
+ return metrics;
687
+ }
688
+ getBreakersByState(state) {
689
+ const names = [];
690
+ for (const [name, breaker] of this.breakers) {
691
+ if (breaker.getState() === state) {
692
+ names.push(name);
693
+ }
694
+ }
695
+ return names;
696
+ }
697
+ forceAllClosed() {
698
+ for (const breaker of this.breakers.values()) {
699
+ breaker.forceClosed();
700
+ }
701
+ }
702
+ resetAll() {
703
+ for (const breaker of this.breakers.values()) {
704
+ breaker.reset();
705
+ }
706
+ }
707
+ remove(name) {
708
+ this.configs.delete(name);
709
+ return this.breakers.delete(name);
710
+ }
711
+ getNames() {
712
+ return Array.from(this.breakers.keys());
713
+ }
714
+ }
715
+ function circuitBreaker(config2) {
716
+ const breaker = new CircuitBreaker(config2);
717
+ return function(target, propertyName, descriptor) {
718
+ const method = descriptor.value;
719
+ descriptor.value = async function(...args) {
720
+ return breaker.execute({
721
+ execute: () => method.apply(this, args),
722
+ context: {
723
+ method: propertyName,
724
+ timestamp: new Date
725
+ }
726
+ });
727
+ };
728
+ return descriptor;
729
+ };
730
+ }
731
+
732
+ class CircuitBreakerHttpClient {
733
+ registry;
734
+ constructor(registry) {
735
+ this.registry = registry || new CircuitBreakerRegistry;
736
+ }
737
+ async fetch(url, options = {}) {
738
+ const { circuitBreaker: circuitBreaker2, fallback, context, ...fetchOptions } = options;
739
+ let breakerName;
740
+ if (typeof circuitBreaker2 === "string") {
741
+ breakerName = circuitBreaker2;
742
+ } else if (circuitBreaker2) {
743
+ breakerName = circuitBreaker2.name;
744
+ if (!this.registry.get(breakerName)) {
745
+ this.registry.register(circuitBreaker2);
746
+ }
747
+ } else {
748
+ const hostname = new URL(url).hostname;
749
+ breakerName = `http-${hostname}`;
750
+ if (!this.registry.get(breakerName)) {
751
+ this.registry.register({
752
+ name: breakerName,
753
+ failureThreshold: 5,
754
+ recoveryTimeout: 60000,
755
+ timeout: 30000,
756
+ monitoringPeriod: 60000,
757
+ minimumRequests: 10,
758
+ errorThresholdPercentage: 50,
759
+ halfOpenMaxCalls: 3,
760
+ resetTimeout: 60000
761
+ });
762
+ }
763
+ }
764
+ return this.registry.execute(breakerName, {
765
+ execute: async () => {
766
+ const response = await fetch(url, fetchOptions);
767
+ if (response.status >= 500) {
768
+ throw new ExternalServiceException(`HTTP ${response.status}: ${response.statusText}`, breakerName, url, response.status, undefined, context);
769
+ }
770
+ return response;
771
+ },
772
+ fallback,
773
+ context
774
+ });
775
+ }
776
+ getRegistry() {
777
+ return this.registry;
778
+ }
779
+ }
780
+ function createCircuitBreakerMiddleware(registry) {
781
+ return async (req, next) => {
782
+ const serviceName = req.context?.serviceName;
783
+ if (!serviceName) {
784
+ return next();
785
+ }
786
+ const breaker = registry.get(serviceName);
787
+ if (!breaker) {
788
+ return next();
789
+ }
790
+ return breaker.execute({
791
+ execute: next,
792
+ context: {
793
+ requestId: req.requestId,
794
+ route: req.route,
795
+ method: req.method,
796
+ url: req.url
797
+ }
798
+ });
799
+ };
800
+ }
801
+ var CircuitBreakerPresets = {
802
+ critical: (name) => ({
803
+ name,
804
+ failureThreshold: 3,
805
+ recoveryTimeout: 30000,
806
+ timeout: 5000,
807
+ monitoringPeriod: 30000,
808
+ minimumRequests: 5,
809
+ errorThresholdPercentage: 30,
810
+ halfOpenMaxCalls: 2,
811
+ resetTimeout: 30000
812
+ }),
813
+ standard: (name) => ({
814
+ name,
815
+ failureThreshold: 5,
816
+ recoveryTimeout: 60000,
817
+ timeout: 30000,
818
+ monitoringPeriod: 60000,
819
+ minimumRequests: 10,
820
+ errorThresholdPercentage: 50,
821
+ halfOpenMaxCalls: 3,
822
+ resetTimeout: 60000
823
+ }),
824
+ tolerant: (name) => ({
825
+ name,
826
+ failureThreshold: 10,
827
+ recoveryTimeout: 120000,
828
+ timeout: 60000,
829
+ monitoringPeriod: 120000,
830
+ minimumRequests: 20,
831
+ errorThresholdPercentage: 70,
832
+ halfOpenMaxCalls: 5,
833
+ resetTimeout: 120000
834
+ }),
835
+ database: (name) => ({
836
+ name,
837
+ failureThreshold: 3,
838
+ recoveryTimeout: 60000,
839
+ timeout: 1e4,
840
+ monitoringPeriod: 60000,
841
+ minimumRequests: 5,
842
+ errorThresholdPercentage: 40,
843
+ halfOpenMaxCalls: 2,
844
+ resetTimeout: 60000,
845
+ shouldTripOnError: (error) => {
846
+ return !(error.message.includes("validation") || error.message.includes("constraint"));
847
+ }
848
+ }),
849
+ externalApi: (name) => ({
850
+ name,
851
+ failureThreshold: 5,
852
+ recoveryTimeout: 90000,
853
+ timeout: 15000,
854
+ monitoringPeriod: 90000,
855
+ minimumRequests: 8,
856
+ errorThresholdPercentage: 60,
857
+ halfOpenMaxCalls: 3,
858
+ resetTimeout: 90000,
859
+ shouldTripOnError: (error) => {
860
+ if (error instanceof ExternalServiceException) {
861
+ return error.statusCode >= 500;
862
+ }
863
+ return true;
864
+ }
865
+ })
866
+ };
867
+ var globalCircuitBreakerRegistry = new CircuitBreakerRegistry;
868
+ function createCircuitBreaker(config2) {
869
+ return globalCircuitBreakerRegistry.register(config2);
870
+ }
871
+ async function withCircuitBreaker(name, call) {
872
+ return globalCircuitBreakerRegistry.execute(name, call);
873
+ }
874
+ // src/errors/error-reporting.ts
875
+ class SentryReporter {
876
+ config;
877
+ sentry;
878
+ constructor(config2) {
879
+ this.config = config2;
880
+ this.initializeSentry();
881
+ }
882
+ async initializeSentry() {
883
+ try {
884
+ const Sentry = await import("@sentry/node");
885
+ Sentry.init({
886
+ dsn: this.config.dsn,
887
+ environment: this.config.environment || "production",
888
+ release: this.config.release,
889
+ sampleRate: this.config.sampleRate || 1,
890
+ beforeSend: (event, hint) => {
891
+ const error = hint.originalException;
892
+ if (error && this.config.beforeSend) {
893
+ const filteredError = this.config.beforeSend(error, error.context);
894
+ return filteredError ? event : null;
895
+ }
896
+ return event;
897
+ },
898
+ ignoreErrors: this.config.filters?.ignoreErrors,
899
+ denyUrls: this.config.filters?.ignoreUrls,
900
+ allowUrls: this.config.filters?.allowUrls,
901
+ initialScope: {
902
+ tags: this.config.tags,
903
+ extra: this.config.extra,
904
+ user: this.config.user
905
+ },
906
+ maxBreadcrumbs: this.config.breadcrumbs?.maxBreadcrumbs || 100,
907
+ tracesSampleRate: this.config.performance?.tracesSampleRate || 0.1
908
+ });
909
+ this.sentry = Sentry;
910
+ } catch (error) {
911
+ console.warn("Failed to initialize Sentry:", error);
912
+ }
913
+ }
914
+ async report(error, context) {
915
+ if (!this.sentry || !this.config.enabled) {
916
+ return null;
917
+ }
918
+ try {
919
+ if (this.shouldIgnoreError(error)) {
920
+ return null;
921
+ }
922
+ const mergedContext = { ...error.context, ...context };
923
+ return this.sentry.withScope((scope) => {
924
+ if (mergedContext.requestId) {
925
+ scope.setTag("requestId", mergedContext.requestId);
926
+ }
927
+ if (mergedContext.userId) {
928
+ scope.setUser({ id: mergedContext.userId });
929
+ }
930
+ if (mergedContext.traceId) {
931
+ scope.setTag("traceId", mergedContext.traceId);
932
+ }
933
+ if (mergedContext.route) {
934
+ scope.setTag("route", mergedContext.route);
935
+ }
936
+ if (mergedContext.method) {
937
+ scope.setTag("method", mergedContext.method);
938
+ }
939
+ scope.setTag("errorCode", error.code);
940
+ scope.setTag("severity", error.severity);
941
+ scope.setTag("category", error.category);
942
+ scope.setLevel(this.mapSeverityToLevel(error.severity));
943
+ scope.setExtra("statusCode", error.statusCode);
944
+ scope.setExtra("retryable", error.retryable);
945
+ scope.setExtra("timestamp", error.timestamp);
946
+ scope.setExtra("context", mergedContext);
947
+ const fingerprint = this.generateFingerprint(error);
948
+ if (fingerprint.length > 0) {
949
+ scope.setFingerprint(fingerprint);
950
+ }
951
+ return this.sentry.captureException(error);
952
+ });
953
+ } catch (reportingError) {
954
+ console.error("Failed to report error to Sentry:", reportingError);
955
+ return null;
956
+ }
957
+ }
958
+ addBreadcrumb(message, category = "default", level = "info", data) {
959
+ if (!this.sentry || !this.config.breadcrumbs?.enabled) {
960
+ return;
961
+ }
962
+ this.sentry.addBreadcrumb({
963
+ message,
964
+ category,
965
+ level,
966
+ data,
967
+ timestamp: Date.now() / 1000
968
+ });
969
+ }
970
+ setUser(user) {
971
+ if (!this.sentry)
972
+ return;
973
+ this.sentry.setUser(user);
974
+ }
975
+ setTag(key, value) {
976
+ if (!this.sentry)
977
+ return;
978
+ this.sentry.setTag(key, value);
979
+ }
980
+ setExtra(key, value) {
981
+ if (!this.sentry)
982
+ return;
983
+ this.sentry.setExtra(key, value);
984
+ }
985
+ setContext(key, context) {
986
+ if (!this.sentry)
987
+ return;
988
+ this.sentry.setContext(key, context);
989
+ }
990
+ async close(timeout = 2000) {
991
+ if (!this.sentry)
992
+ return true;
993
+ return this.sentry.close(timeout);
994
+ }
995
+ shouldIgnoreError(error) {
996
+ const filters = this.config.filters;
997
+ if (!filters)
998
+ return false;
999
+ if (filters.ignoreCodes?.includes(error.code)) {
1000
+ return true;
1001
+ }
1002
+ if (filters.ignoreErrors?.some((pattern) => {
1003
+ if (typeof pattern === "string") {
1004
+ return error.message.includes(pattern);
1005
+ }
1006
+ return pattern.test(error.message);
1007
+ })) {
1008
+ return true;
1009
+ }
1010
+ return false;
1011
+ }
1012
+ mapSeverityToLevel(severity) {
1013
+ switch (severity) {
1014
+ case "low":
1015
+ return "info";
1016
+ case "medium":
1017
+ return "warning";
1018
+ case "high":
1019
+ return "error";
1020
+ case "critical":
1021
+ return "fatal";
1022
+ default:
1023
+ return "error";
1024
+ }
1025
+ }
1026
+ generateFingerprint(error) {
1027
+ const fingerprint = [];
1028
+ fingerprint.push(error.code);
1029
+ fingerprint.push(error.category);
1030
+ if (error.context.route) {
1031
+ fingerprint.push(error.context.route);
1032
+ }
1033
+ return fingerprint;
1034
+ }
1035
+ }
1036
+
1037
+ class BugsnagReporter {
1038
+ config;
1039
+ bugsnag;
1040
+ constructor(config2) {
1041
+ this.config = config2;
1042
+ this.initializeBugsnag();
1043
+ }
1044
+ async initializeBugsnag() {
1045
+ try {
1046
+ const Bugsnag = await import("@bugsnag/js");
1047
+ Bugsnag.start({
1048
+ apiKey: this.config.apiKey,
1049
+ appVersion: this.config.release,
1050
+ releaseStage: this.config.environment || "production",
1051
+ enabledReleaseStages: ["production", "staging"],
1052
+ maxBreadcrumbs: this.config.breadcrumbs?.maxBreadcrumbs || 25,
1053
+ onError: (event) => {
1054
+ const error = event.originalError;
1055
+ if (error && this.config.beforeSend) {
1056
+ const filteredError = this.config.beforeSend(error, error.context);
1057
+ return filteredError !== null;
1058
+ }
1059
+ return !this.shouldIgnoreError(error);
1060
+ }
1061
+ });
1062
+ this.bugsnag = Bugsnag;
1063
+ } catch (error) {
1064
+ console.warn("Failed to initialize Bugsnag:", error);
1065
+ }
1066
+ }
1067
+ async report(error, context) {
1068
+ if (!this.bugsnag || !this.config.enabled) {
1069
+ return null;
1070
+ }
1071
+ try {
1072
+ if (this.shouldIgnoreError(error)) {
1073
+ return null;
1074
+ }
1075
+ const mergedContext = { ...error.context, ...context };
1076
+ this.bugsnag.notify(error, (event) => {
1077
+ event.severity = this.mapSeverityToBugsnag(error.severity);
1078
+ event.context = mergedContext.route || mergedContext.url;
1079
+ if (mergedContext.userId) {
1080
+ event.setUser(mergedContext.userId, undefined, undefined);
1081
+ }
1082
+ event.addMetadata("error", {
1083
+ code: error.code,
1084
+ category: error.category,
1085
+ statusCode: error.statusCode,
1086
+ retryable: error.retryable,
1087
+ timestamp: error.timestamp
1088
+ });
1089
+ event.addMetadata("request", {
1090
+ requestId: mergedContext.requestId,
1091
+ method: mergedContext.method,
1092
+ url: mergedContext.url,
1093
+ userAgent: mergedContext.userAgent,
1094
+ ip: mergedContext.ip
1095
+ });
1096
+ event.groupingHash = this.generateGroupingHash(error);
1097
+ });
1098
+ return "reported";
1099
+ } catch (reportingError) {
1100
+ console.error("Failed to report error to Bugsnag:", reportingError);
1101
+ return null;
1102
+ }
1103
+ }
1104
+ addBreadcrumb(message, category = "manual", _level = "info", data) {
1105
+ if (!this.bugsnag || !this.config.breadcrumbs?.enabled) {
1106
+ return;
1107
+ }
1108
+ this.bugsnag.leaveBreadcrumb(message, data, category);
1109
+ }
1110
+ setUser(user) {
1111
+ if (!this.bugsnag)
1112
+ return;
1113
+ this.bugsnag.setUser(user.id, user.email, user.username);
1114
+ }
1115
+ setTag(key, value) {
1116
+ if (!this.bugsnag)
1117
+ return;
1118
+ this.bugsnag.addMetadata("tags", { [key]: value });
1119
+ }
1120
+ setExtra(key, value) {
1121
+ if (!this.bugsnag)
1122
+ return;
1123
+ this.bugsnag.addMetadata("extra", { [key]: value });
1124
+ }
1125
+ setContext(key, context) {
1126
+ if (!this.bugsnag)
1127
+ return;
1128
+ this.bugsnag.addMetadata(key, context);
1129
+ }
1130
+ async close() {
1131
+ return true;
1132
+ }
1133
+ shouldIgnoreError(error) {
1134
+ const filters = this.config.filters;
1135
+ if (!filters)
1136
+ return false;
1137
+ if (filters.ignoreCodes?.includes(error.code)) {
1138
+ return true;
1139
+ }
1140
+ if (filters.ignoreErrors?.some((pattern) => {
1141
+ if (typeof pattern === "string") {
1142
+ return error.message.includes(pattern);
1143
+ }
1144
+ return pattern.test(error.message);
1145
+ })) {
1146
+ return true;
1147
+ }
1148
+ return false;
1149
+ }
1150
+ mapSeverityToBugsnag(severity) {
1151
+ switch (severity) {
1152
+ case "low":
1153
+ return "info";
1154
+ case "medium":
1155
+ return "warning";
1156
+ case "high":
1157
+ return "error";
1158
+ case "critical":
1159
+ return "error";
1160
+ default:
1161
+ return "warning";
1162
+ }
1163
+ }
1164
+ generateGroupingHash(error) {
1165
+ return `${error.code}-${error.category}-${error.context.route || "unknown"}`;
1166
+ }
1167
+ }
1168
+
1169
+ class CustomReporter {
1170
+ config;
1171
+ reportHandler;
1172
+ constructor(config2, reportHandler) {
1173
+ this.config = config2;
1174
+ this.reportHandler = reportHandler;
1175
+ }
1176
+ async report(error, context) {
1177
+ if (!this.config.enabled) {
1178
+ return null;
1179
+ }
1180
+ try {
1181
+ const mergedContext = { ...error.context, ...context };
1182
+ if (this.config.beforeSend) {
1183
+ const filteredError = this.config.beforeSend(error, mergedContext);
1184
+ if (!filteredError) {
1185
+ return null;
1186
+ }
1187
+ }
1188
+ const report = {
1189
+ error,
1190
+ context: mergedContext,
1191
+ level: this.mapSeverityToLevel(error.severity),
1192
+ tags: this.config.tags,
1193
+ extra: this.config.extra,
1194
+ user: this.config.user
1195
+ };
1196
+ return await this.reportHandler(report);
1197
+ } catch (reportingError) {
1198
+ console.error("Failed to report error with custom handler:", reportingError);
1199
+ return null;
1200
+ }
1201
+ }
1202
+ addBreadcrumb() {}
1203
+ setUser() {}
1204
+ setTag() {}
1205
+ setExtra() {}
1206
+ setContext() {}
1207
+ async close() {
1208
+ return true;
1209
+ }
1210
+ mapSeverityToLevel(severity) {
1211
+ switch (severity) {
1212
+ case "low":
1213
+ return "info";
1214
+ case "medium":
1215
+ return "warning";
1216
+ case "high":
1217
+ return "error";
1218
+ case "critical":
1219
+ return "fatal";
1220
+ default:
1221
+ return "error";
1222
+ }
1223
+ }
1224
+ }
1225
+
1226
+ class ErrorReportingManager {
1227
+ reporters = [];
1228
+ breadcrumbs = [];
1229
+ maxBreadcrumbs = 50;
1230
+ constructor(configs) {
1231
+ this.initializeReporters(configs);
1232
+ }
1233
+ initializeReporters(configs) {
1234
+ for (const config2 of configs) {
1235
+ if (!config2.enabled)
1236
+ continue;
1237
+ switch (config2.service) {
1238
+ case "sentry":
1239
+ this.reporters.push(new SentryReporter(config2));
1240
+ break;
1241
+ case "bugsnag":
1242
+ this.reporters.push(new BugsnagReporter(config2));
1243
+ break;
1244
+ case "custom":
1245
+ break;
1246
+ }
1247
+ }
1248
+ }
1249
+ addReporter(reporter) {
1250
+ this.reporters.push(reporter);
1251
+ }
1252
+ async report(error, context) {
1253
+ const reportIds = [];
1254
+ this.addBreadcrumb(`Error occurred: ${error.message}`, "error", {
1255
+ code: error.code,
1256
+ severity: error.severity
1257
+ });
1258
+ const promises = this.reporters.map(async (reporter) => {
1259
+ try {
1260
+ const reportId = await reporter.report(error, context);
1261
+ if (reportId) {
1262
+ reportIds.push(reportId);
1263
+ }
1264
+ } catch (reportingError) {
1265
+ console.error("Reporter failed:", reportingError);
1266
+ }
1267
+ });
1268
+ await Promise.allSettled(promises);
1269
+ return reportIds;
1270
+ }
1271
+ addBreadcrumb(message, category = "default", data) {
1272
+ this.breadcrumbs.push({
1273
+ message,
1274
+ timestamp: new Date,
1275
+ data: { category, ...data }
1276
+ });
1277
+ if (this.breadcrumbs.length > this.maxBreadcrumbs) {
1278
+ this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
1279
+ }
1280
+ this.reporters.forEach((reporter) => {
1281
+ reporter.addBreadcrumb(message, category, "info", data);
1282
+ });
1283
+ }
1284
+ setUser(user) {
1285
+ this.reporters.forEach((reporter) => reporter.setUser(user));
1286
+ }
1287
+ setTag(key, value) {
1288
+ this.reporters.forEach((reporter) => reporter.setTag(key, value));
1289
+ }
1290
+ setExtra(key, value) {
1291
+ this.reporters.forEach((reporter) => reporter.setExtra(key, value));
1292
+ }
1293
+ setContext(key, context) {
1294
+ this.reporters.forEach((reporter) => reporter.setContext(key, context));
1295
+ }
1296
+ async close(timeout = 5000) {
1297
+ const promises = this.reporters.map((reporter) => reporter.close(timeout));
1298
+ const results = await Promise.allSettled(promises);
1299
+ return results.every((result) => result.status === "fulfilled" && result.value);
1300
+ }
1301
+ getBreadcrumbs() {
1302
+ return [...this.breadcrumbs];
1303
+ }
1304
+ }
1305
+ function createErrorReporting(configs) {
1306
+ return new ErrorReportingManager(configs);
1307
+ }
1308
+ var ErrorReportingPresets = {
1309
+ sentry: (dsn, environment = "production") => ({
1310
+ enabled: true,
1311
+ service: "sentry",
1312
+ dsn,
1313
+ environment,
1314
+ sampleRate: 1,
1315
+ breadcrumbs: {
1316
+ enabled: true,
1317
+ maxBreadcrumbs: 100
1318
+ },
1319
+ performance: {
1320
+ enabled: true,
1321
+ tracesSampleRate: 0.1
1322
+ },
1323
+ filters: {
1324
+ ignoreErrors: [
1325
+ "Non-Error promise rejection captured",
1326
+ "ResizeObserver loop limit exceeded"
1327
+ ],
1328
+ ignoreCodes: ["VALIDATION_ERROR"]
1329
+ }
1330
+ }),
1331
+ bugsnag: (apiKey, environment = "production") => ({
1332
+ enabled: true,
1333
+ service: "bugsnag",
1334
+ apiKey,
1335
+ environment,
1336
+ breadcrumbs: {
1337
+ enabled: true,
1338
+ maxBreadcrumbs: 25
1339
+ },
1340
+ filters: {
1341
+ ignoreCodes: ["VALIDATION_ERROR"]
1342
+ }
1343
+ }),
1344
+ development: () => ({
1345
+ enabled: false,
1346
+ service: "custom",
1347
+ environment: "development"
1348
+ })
1349
+ };
1350
+
1351
+ // src/errors/graceful-degradation.ts
1352
+ class ServiceHealthMonitor {
1353
+ healthStatus = new Map;
1354
+ healthCheckIntervals = new Map;
1355
+ config;
1356
+ constructor(config2) {
1357
+ this.config = config2;
1358
+ this.initializeHealthChecks();
1359
+ }
1360
+ initializeHealthChecks() {
1361
+ for (const [serviceName, healthConfig] of Object.entries(this.config.healthChecks)) {
1362
+ if (!healthConfig.enabled)
1363
+ continue;
1364
+ this.healthStatus.set(serviceName, {
1365
+ name: serviceName,
1366
+ status: "healthy",
1367
+ lastCheck: new Date,
1368
+ responseTime: 0,
1369
+ errorRate: 0,
1370
+ availability: 100,
1371
+ consecutiveFailures: 0
1372
+ });
1373
+ const interval = setInterval(() => this.performHealthCheck(serviceName, healthConfig), healthConfig.interval);
1374
+ interval.unref?.();
1375
+ this.healthCheckIntervals.set(serviceName, interval);
1376
+ }
1377
+ }
1378
+ async performHealthCheck(serviceName, config2) {
1379
+ const startTime = Date.now();
1380
+ let isHealthy = false;
1381
+ try {
1382
+ const controller = new AbortController;
1383
+ const timeoutId = setTimeout(() => controller.abort(), config2.timeout);
1384
+ const response = await fetch(config2.endpoint, {
1385
+ signal: controller.signal,
1386
+ headers: config2.headers,
1387
+ method: "GET"
1388
+ });
1389
+ clearTimeout(timeoutId);
1390
+ isHealthy = config2.expectedStatus.includes(response.status);
1391
+ if (isHealthy && config2.expectedBody) {
1392
+ const body = await response.text();
1393
+ if (typeof config2.expectedBody === "string") {
1394
+ isHealthy = body.includes(config2.expectedBody);
1395
+ } else {
1396
+ isHealthy = config2.expectedBody.test(body);
1397
+ }
1398
+ }
1399
+ } catch (error) {
1400
+ console.error(error);
1401
+ isHealthy = false;
1402
+ }
1403
+ const responseTime = Date.now() - startTime;
1404
+ this.updateHealthStatus(serviceName, isHealthy, responseTime);
1405
+ if (isHealthy && config2.onHealthy) {
1406
+ config2.onHealthy();
1407
+ } else if (!isHealthy && config2.onUnhealthy) {
1408
+ config2.onUnhealthy(new Error(`Health check failed for ${serviceName}`));
1409
+ }
1410
+ }
1411
+ updateHealthStatus(serviceName, isHealthy, responseTime) {
1412
+ const current = this.healthStatus.get(serviceName);
1413
+ if (!current)
1414
+ return;
1415
+ const updated = {
1416
+ ...current,
1417
+ lastCheck: new Date,
1418
+ responseTime,
1419
+ consecutiveFailures: isHealthy ? 0 : current.consecutiveFailures + 1
1420
+ };
1421
+ if (updated.consecutiveFailures === 0) {
1422
+ updated.status = "healthy";
1423
+ } else if (updated.consecutiveFailures < 3) {
1424
+ updated.status = "degraded";
1425
+ } else {
1426
+ updated.status = "unhealthy";
1427
+ }
1428
+ const healthyCount = isHealthy ? 1 : 0;
1429
+ updated.availability = current.availability * 0.9 + healthyCount * 10;
1430
+ this.healthStatus.set(serviceName, updated);
1431
+ }
1432
+ getServiceHealth(serviceName) {
1433
+ return this.healthStatus.get(serviceName);
1434
+ }
1435
+ getAllServiceHealth() {
1436
+ return Array.from(this.healthStatus.values());
1437
+ }
1438
+ isServiceHealthy(serviceName) {
1439
+ const health = this.healthStatus.get(serviceName);
1440
+ return health?.status === "healthy";
1441
+ }
1442
+ isServiceDegraded(serviceName) {
1443
+ const health = this.healthStatus.get(serviceName);
1444
+ return health?.status === "degraded";
1445
+ }
1446
+ stop() {
1447
+ for (const interval of this.healthCheckIntervals.values()) {
1448
+ clearInterval(interval);
1449
+ }
1450
+ this.healthCheckIntervals.clear();
1451
+ }
1452
+ }
1453
+
1454
+ class GracefulDegradationManager {
1455
+ config;
1456
+ healthMonitor;
1457
+ cache = new Map;
1458
+ metrics = new Map;
1459
+ constructor(config2) {
1460
+ this.config = config2;
1461
+ this.healthMonitor = new ServiceHealthMonitor(config2);
1462
+ }
1463
+ async handleFailure(serviceName, error, request, originalHandler) {
1464
+ if (!this.config.enabled) {
1465
+ throw error;
1466
+ }
1467
+ const strategy = this.config.fallbackStrategies[serviceName];
1468
+ if (!strategy) {
1469
+ throw error;
1470
+ }
1471
+ const context = {
1472
+ request,
1473
+ serviceName,
1474
+ error,
1475
+ attemptCount: 1,
1476
+ startTime: new Date
1477
+ };
1478
+ return this.executeFallbackStrategy(strategy, context, originalHandler);
1479
+ }
1480
+ async executeFallbackStrategy(strategy, context, originalHandler) {
1481
+ if (context.attemptCount <= strategy.retries) {
1482
+ try {
1483
+ if (context.attemptCount > 1) {
1484
+ const delay = this.calculateBackoffDelay(strategy.backoff, context.attemptCount);
1485
+ await this.sleep(delay);
1486
+ }
1487
+ const timeoutPromise = new Promise((_, reject) => {
1488
+ setTimeout(() => reject(new Error("Timeout")), strategy.timeout);
1489
+ });
1490
+ const result = await Promise.race([originalHandler(), timeoutPromise]);
1491
+ this.updateMetrics(context.serviceName, true, Date.now() - context.startTime.getTime());
1492
+ return result;
1493
+ } catch (retryError) {
1494
+ console.error(retryError);
1495
+ context.attemptCount++;
1496
+ if (context.attemptCount <= strategy.retries) {
1497
+ return this.executeFallbackStrategy(strategy, context, originalHandler);
1498
+ }
1499
+ }
1500
+ }
1501
+ this.updateMetrics(context.serviceName, false, Date.now() - context.startTime.getTime());
1502
+ switch (strategy.type) {
1503
+ case "cache":
1504
+ return this.handleCacheFallback(strategy, context);
1505
+ case "static":
1506
+ return this.handleStaticFallback(strategy, context);
1507
+ case "simplified":
1508
+ return this.handleSimplifiedFallback(strategy, context);
1509
+ case "redirect":
1510
+ return this.handleRedirectFallback(strategy, context);
1511
+ case "custom":
1512
+ return this.handleCustomFallback(strategy, context);
1513
+ default:
1514
+ throw context.error;
1515
+ }
1516
+ }
1517
+ async handleCacheFallback(strategy, context) {
1518
+ if (!strategy.cacheConfig) {
1519
+ throw context.error;
1520
+ }
1521
+ const cacheKey = this.generateCacheKey(strategy.cacheConfig.key, context);
1522
+ const cached = this.cache.get(cacheKey);
1523
+ if (cached && (cached.expires > new Date || strategy.cacheConfig.staleWhileRevalidate)) {
1524
+ const headers = new Headers({
1525
+ "Content-Type": "application/json",
1526
+ "X-Cache": "HIT",
1527
+ "X-Degraded": "true",
1528
+ "X-Service-Status": this.healthMonitor.getServiceHealth(context.serviceName)?.status || "unknown"
1529
+ });
1530
+ if (strategy.cacheConfig.staleWhileRevalidate && cached.expires <= new Date) {
1531
+ headers.set("X-Cache", "STALE");
1532
+ }
1533
+ return new Response(JSON.stringify(cached.data), {
1534
+ status: 200,
1535
+ headers
1536
+ });
1537
+ }
1538
+ throw context.error;
1539
+ }
1540
+ async handleStaticFallback(strategy, context) {
1541
+ if (!strategy.staticResponse) {
1542
+ throw context.error;
1543
+ }
1544
+ const headers = new Headers(strategy.staticResponse.headers || {});
1545
+ headers.set("X-Degraded", "true");
1546
+ headers.set("X-Fallback-Type", "static");
1547
+ headers.set("X-Service-Status", this.healthMonitor.getServiceHealth(context.serviceName)?.status || "unknown");
1548
+ return new Response(typeof strategy.staticResponse.body === "string" ? strategy.staticResponse.body : JSON.stringify(strategy.staticResponse.body), {
1549
+ status: strategy.staticResponse.status,
1550
+ headers
1551
+ });
1552
+ }
1553
+ async handleSimplifiedFallback(strategy, context) {
1554
+ const simplifiedData = {
1555
+ message: "Service temporarily unavailable. Showing simplified view.",
1556
+ status: "degraded",
1557
+ timestamp: new Date().toISOString(),
1558
+ requestId: context.request.requestId
1559
+ };
1560
+ return new Response(JSON.stringify(simplifiedData), {
1561
+ status: 200,
1562
+ headers: {
1563
+ "Content-Type": "application/json",
1564
+ "X-Degraded": "true",
1565
+ "X-Fallback-Type": "simplified",
1566
+ "X-Service-Status": this.healthMonitor.getServiceHealth(context.serviceName)?.status || "unknown"
1567
+ }
1568
+ });
1569
+ }
1570
+ async handleRedirectFallback(strategy, context) {
1571
+ if (!strategy.redirectConfig) {
1572
+ throw context.error;
1573
+ }
1574
+ return new Response(null, {
1575
+ status: strategy.redirectConfig.permanent ? 301 : 302,
1576
+ headers: {
1577
+ Location: strategy.redirectConfig.url,
1578
+ "X-Degraded": "true",
1579
+ "X-Fallback-Type": "redirect"
1580
+ }
1581
+ });
1582
+ }
1583
+ async handleCustomFallback(strategy, context) {
1584
+ if (!strategy.fallbackHandler) {
1585
+ throw context.error;
1586
+ }
1587
+ try {
1588
+ const response = await strategy.fallbackHandler(context.error, context.error.context);
1589
+ response.headers.set("X-Degraded", "true");
1590
+ response.headers.set("X-Fallback-Type", "custom");
1591
+ response.headers.set("X-Service-Status", this.healthMonitor.getServiceHealth(context.serviceName)?.status || "unknown");
1592
+ return response;
1593
+ } catch (fallbackError) {
1594
+ console.error(fallbackError);
1595
+ throw context.error;
1596
+ }
1597
+ }
1598
+ cacheResponse(serviceName, key, data, ttl) {
1599
+ const strategy = this.config.fallbackStrategies[serviceName];
1600
+ if (strategy?.type === "cache" && strategy.cacheConfig) {
1601
+ const cacheKey = this.generateCacheKey(key, { serviceName });
1602
+ this.cache.set(cacheKey, {
1603
+ data,
1604
+ expires: new Date(Date.now() + ttl * 1000)
1605
+ });
1606
+ }
1607
+ }
1608
+ getServiceMetrics(serviceName) {
1609
+ const metrics = this.metrics.get(serviceName);
1610
+ if (!metrics || metrics.requests === 0) {
1611
+ return { errorRate: 0, avgResponseTime: 0, totalRequests: 0 };
1612
+ }
1613
+ return {
1614
+ errorRate: metrics.errors / metrics.requests * 100,
1615
+ avgResponseTime: metrics.totalTime / metrics.requests,
1616
+ totalRequests: metrics.requests
1617
+ };
1618
+ }
1619
+ getSystemHealth() {
1620
+ const services = this.healthMonitor.getAllServiceHealth();
1621
+ const unhealthyCount = services.filter((s) => s.status === "unhealthy").length;
1622
+ const degradedCount = services.filter((s) => s.status === "degraded").length;
1623
+ let status = "healthy";
1624
+ if (unhealthyCount > 0) {
1625
+ status = unhealthyCount > services.length / 2 ? "unhealthy" : "degraded";
1626
+ } else if (degradedCount > 0) {
1627
+ status = "degraded";
1628
+ }
1629
+ return { status, services };
1630
+ }
1631
+ calculateBackoffDelay(backoff, attempt) {
1632
+ if (backoff.type === "fixed") {
1633
+ return backoff.delay;
1634
+ }
1635
+ const delay = backoff.delay * 2 ** (attempt - 1);
1636
+ return Math.min(delay, backoff.maxDelay || 30000);
1637
+ }
1638
+ generateCacheKey(template, context) {
1639
+ return template.replace("{service}", context.serviceName).replace("{url}", context.request.url).replace("{method}", context.request.method).replace("{userId}", String(context.request.user?.id || "anonymous"));
1640
+ }
1641
+ updateMetrics(serviceName, success, responseTime) {
1642
+ const current = this.metrics.get(serviceName) || { requests: 0, errors: 0, totalTime: 0 };
1643
+ current.requests++;
1644
+ current.totalTime += responseTime;
1645
+ if (!success) {
1646
+ current.errors++;
1647
+ }
1648
+ this.metrics.set(serviceName, current);
1649
+ }
1650
+ sleep(ms) {
1651
+ return new Promise((resolve) => setTimeout(resolve, ms));
1652
+ }
1653
+ stop() {
1654
+ this.healthMonitor.stop();
1655
+ }
1656
+ }
1657
+ function createGracefulDegradationMiddleware(config2) {
1658
+ const manager = new GracefulDegradationManager(config2);
1659
+ return async (req, next) => {
1660
+ try {
1661
+ const response = await next();
1662
+ const serviceName = req.context?.serviceName;
1663
+ if (serviceName && response.ok) {
1664
+ const strategy = config2.fallbackStrategies[serviceName];
1665
+ if (strategy?.type === "cache" && strategy.cacheConfig) {
1666
+ try {
1667
+ const data = await response.clone().json();
1668
+ manager.cacheResponse(serviceName, strategy.cacheConfig.key, data, strategy.cacheConfig.ttl);
1669
+ } catch {}
1670
+ }
1671
+ }
1672
+ return response;
1673
+ } catch (error) {
1674
+ const serviceName = req.context?.serviceName;
1675
+ if (serviceName && error instanceof Error) {
1676
+ const routerError = error;
1677
+ try {
1678
+ return await manager.handleFailure(serviceName, routerError, req, next);
1679
+ } catch (fallbackError) {
1680
+ console.error(fallbackError);
1681
+ throw error;
1682
+ }
1683
+ }
1684
+ throw error;
1685
+ }
1686
+ };
1687
+ }
1688
+ var DegradationStrategies = {
1689
+ cacheFirst: (ttl = 3600, staleWhileRevalidate = true) => ({
1690
+ type: "cache",
1691
+ priority: 1,
1692
+ timeout: 5000,
1693
+ retries: 2,
1694
+ backoff: { type: "exponential", delay: 1000, maxDelay: 5000 },
1695
+ cacheConfig: {
1696
+ key: "{service}:{url}:{method}",
1697
+ ttl,
1698
+ staleWhileRevalidate
1699
+ }
1700
+ }),
1701
+ staticFallback: (body, status = 200) => ({
1702
+ type: "static",
1703
+ priority: 2,
1704
+ timeout: 1000,
1705
+ retries: 0,
1706
+ backoff: { type: "fixed", delay: 0 },
1707
+ staticResponse: {
1708
+ status,
1709
+ body,
1710
+ headers: { "Content-Type": "application/json" }
1711
+ }
1712
+ }),
1713
+ simplifiedView: () => ({
1714
+ type: "simplified",
1715
+ priority: 3,
1716
+ timeout: 1000,
1717
+ retries: 0,
1718
+ backoff: { type: "fixed", delay: 0 }
1719
+ }),
1720
+ redirectToMaintenance: (url) => ({
1721
+ type: "redirect",
1722
+ priority: 4,
1723
+ timeout: 1000,
1724
+ retries: 0,
1725
+ backoff: { type: "fixed", delay: 0 },
1726
+ redirectConfig: {
1727
+ url,
1728
+ permanent: false
1729
+ }
1730
+ })
1731
+ };
1732
+
1733
+ // src/errors/error-handler.ts
1734
+ class AdvancedErrorHandler {
1735
+ errorReporting;
1736
+ gracefulDegradation;
1737
+ circuitBreakerRegistry;
1738
+ config;
1739
+ constructor(config2 = {}) {
1740
+ this.config = {
1741
+ development: false,
1742
+ includeStackTrace: false,
1743
+ sanitizeErrors: true,
1744
+ logErrors: true,
1745
+ ...config2
1746
+ };
1747
+ this.initializeComponents();
1748
+ }
1749
+ initializeComponents() {
1750
+ if (this.config.errorReporting?.length) {
1751
+ this.errorReporting = new ErrorReportingManager(this.config.errorReporting);
1752
+ }
1753
+ if (this.config.gracefulDegradation?.enabled) {
1754
+ this.gracefulDegradation = new GracefulDegradationManager(this.config.gracefulDegradation);
1755
+ }
1756
+ if (this.config.circuitBreakers?.length) {
1757
+ this.circuitBreakerRegistry = new CircuitBreakerRegistry;
1758
+ for (const config2 of this.config.circuitBreakers) {
1759
+ this.circuitBreakerRegistry.register(config2);
1760
+ }
1761
+ }
1762
+ }
1763
+ middleware() {
1764
+ return async (req, next) => {
1765
+ try {
1766
+ const errorContext = {
1767
+ requestId: req.requestId,
1768
+ userId: req.user?.id,
1769
+ traceId: req.traceId,
1770
+ spanId: req.spanId,
1771
+ route: req.params?.route || req.url,
1772
+ method: req.method,
1773
+ url: req.url,
1774
+ userAgent: req.userAgent?.() || undefined,
1775
+ ip: req.ip?.() || undefined,
1776
+ timestamp: new Date
1777
+ };
1778
+ req.context = { ...req.context, errorContext };
1779
+ if (this.circuitBreakerRegistry && req.context?.serviceName) {
1780
+ const breaker = this.circuitBreakerRegistry.get(req.context.serviceName);
1781
+ if (breaker) {
1782
+ return await breaker.execute({
1783
+ execute: next,
1784
+ context: errorContext
1785
+ });
1786
+ }
1787
+ }
1788
+ return await next();
1789
+ } catch (error) {
1790
+ return this.handleError(error, req);
1791
+ }
1792
+ };
1793
+ }
1794
+ async handleError(error, req) {
1795
+ const routerError = this.normalizeError(error, req);
1796
+ if (this.errorReporting) {
1797
+ this.errorReporting.addBreadcrumb(`Error in ${req.method} ${req.url}`, "error", {
1798
+ code: routerError.code,
1799
+ severity: routerError.severity,
1800
+ statusCode: routerError.statusCode
1801
+ });
1802
+ }
1803
+ if (this.config.logErrors) {
1804
+ this.logError(routerError, req);
1805
+ }
1806
+ if (this.gracefulDegradation && req.context?.serviceName) {
1807
+ try {
1808
+ return await this.gracefulDegradation.handleFailure(req.context.serviceName, routerError, req, async () => {
1809
+ throw routerError;
1810
+ });
1811
+ } catch {}
1812
+ }
1813
+ if (this.errorReporting) {
1814
+ try {
1815
+ await this.errorReporting.report(routerError, routerError.context);
1816
+ } catch (reportingError) {
1817
+ console.error("Failed to report error:", reportingError);
1818
+ }
1819
+ }
1820
+ if (this.config.globalErrorHandler) {
1821
+ try {
1822
+ return await this.config.globalErrorHandler(routerError, req);
1823
+ } catch (handlerError) {
1824
+ console.error("Global error handler failed:", handlerError);
1825
+ }
1826
+ }
1827
+ const customPage = this.config.customErrorPages?.[routerError.statusCode];
1828
+ if (customPage) {
1829
+ try {
1830
+ if (typeof customPage === "string") {
1831
+ return new Response(customPage, {
1832
+ status: routerError.statusCode,
1833
+ headers: { "Content-Type": "text/html" }
1834
+ });
1835
+ } else {
1836
+ return await customPage(routerError, req);
1837
+ }
1838
+ } catch (pageError) {
1839
+ console.error("Custom error page failed:", pageError);
1840
+ }
1841
+ }
1842
+ return this.createErrorResponse(routerError, req);
1843
+ }
1844
+ normalizeError(error, req) {
1845
+ const errorContext = {
1846
+ requestId: req.requestId,
1847
+ userId: req.user?.id,
1848
+ traceId: req.traceId,
1849
+ spanId: req.spanId,
1850
+ route: req.params?.route || req.url,
1851
+ method: req.method,
1852
+ url: req.url,
1853
+ userAgent: req.userAgent?.() || undefined,
1854
+ ip: req.ip?.() || undefined,
1855
+ timestamp: new Date,
1856
+ metadata: req.context
1857
+ };
1858
+ if (error instanceof RouterException) {
1859
+ return error;
1860
+ }
1861
+ if (error instanceof Error) {
1862
+ if (error.name === "ValidationError") {
1863
+ return ErrorFactory.validation(error.message, {}, errorContext);
1864
+ }
1865
+ if (error.name === "UnauthorizedError" || error.message.includes("unauthorized")) {
1866
+ return ErrorFactory.authentication(error.message, errorContext);
1867
+ }
1868
+ if (error.name === "ForbiddenError" || error.message.includes("forbidden")) {
1869
+ return ErrorFactory.authorization(error.message, [], errorContext);
1870
+ }
1871
+ if (error.name === "NotFoundError" || error.message.includes("not found")) {
1872
+ return ErrorFactory.notFound(undefined, undefined, errorContext);
1873
+ }
1874
+ if (error.name === "TimeoutError" || error.message.includes("timeout")) {
1875
+ return ErrorFactory.timeout(30000, error.message, errorContext);
1876
+ }
1877
+ return ErrorFactory.internalServer(error.message, error, errorContext);
1878
+ }
1879
+ if (typeof error === "string") {
1880
+ return ErrorFactory.internalServer(error, undefined, errorContext);
1881
+ }
1882
+ return ErrorFactory.internalServer("An unknown error occurred", undefined, errorContext);
1883
+ }
1884
+ createErrorResponse(error, _req) {
1885
+ const isDevelopment = this.config.development;
1886
+ const includeStackTrace = this.config.includeStackTrace || isDevelopment;
1887
+ const sanitizeErrors = this.config.sanitizeErrors && !isDevelopment;
1888
+ let message = error.message;
1889
+ if (sanitizeErrors && error.statusCode >= 500) {
1890
+ message = "An internal server error occurred";
1891
+ }
1892
+ const errorResponse = {
1893
+ error: {
1894
+ code: error.code,
1895
+ message,
1896
+ statusCode: error.statusCode,
1897
+ timestamp: error.timestamp.toISOString(),
1898
+ requestId: error.context.requestId,
1899
+ traceId: error.context.traceId
1900
+ }
1901
+ };
1902
+ if (isDevelopment) {
1903
+ errorResponse.error.category = error.category;
1904
+ errorResponse.error.severity = error.severity;
1905
+ errorResponse.error.retryable = error.retryable;
1906
+ errorResponse.error.context = error.context;
1907
+ }
1908
+ if (includeStackTrace) {
1909
+ errorResponse.error.stack = error.stack;
1910
+ if (error.cause) {
1911
+ errorResponse.error.cause = {
1912
+ name: error.cause.name,
1913
+ message: error.cause.message,
1914
+ stack: error.cause.stack
1915
+ };
1916
+ }
1917
+ }
1918
+ if ("fields" in error) {
1919
+ errorResponse.error.fields = error.fields;
1920
+ }
1921
+ if ("allowedMethods" in error) {
1922
+ errorResponse.error.allowedMethods = error.allowedMethods;
1923
+ }
1924
+ if ("retryAfter" in error) {
1925
+ errorResponse.error.retryAfter = error.retryAfter;
1926
+ }
1927
+ const headers = new Headers({
1928
+ "Content-Type": "application/json",
1929
+ "X-Error-Code": error.code,
1930
+ "X-Request-ID": error.context.requestId || "",
1931
+ "X-Trace-ID": error.context.traceId || ""
224
1932
  });
225
- return key;
226
- }
227
- validateKey(key, requiredScopes = []) {
228
- const keyInfo = this.keys.get(key);
229
- if (!keyInfo) {
230
- return false;
1933
+ if ("allowedMethods" in error) {
1934
+ headers.set("Allow", error.allowedMethods.join(", "));
231
1935
  }
232
- if (keyInfo.expiresAt && keyInfo.expiresAt < new Date) {
233
- return false;
1936
+ if ("retryAfter" in error) {
1937
+ headers.set("Retry-After", error.retryAfter.toString());
234
1938
  }
235
- if (requiredScopes.length > 0) {
236
- return requiredScopes.every((scope) => keyInfo.scopes.includes(scope));
1939
+ if ("limit" in error) {
1940
+ headers.set("X-RateLimit-Limit", error.limit.toString());
1941
+ headers.set("X-RateLimit-Remaining", error.remaining.toString());
237
1942
  }
238
- return true;
1943
+ return new Response(JSON.stringify(errorResponse), {
1944
+ status: error.statusCode,
1945
+ headers
1946
+ });
239
1947
  }
240
- getKeyInfo(key) {
241
- return this.keys.get(key) || null;
1948
+ logError(error, req) {
1949
+ const logData = {
1950
+ error: error.toJSON(),
1951
+ request: {
1952
+ method: req.method,
1953
+ url: req.url,
1954
+ headers: Object.fromEntries(req.headers.entries()),
1955
+ userAgent: req.userAgent?.() || undefined,
1956
+ ip: req.ip?.() || undefined,
1957
+ requestId: req.requestId,
1958
+ traceId: req.traceId
1959
+ }
1960
+ };
1961
+ switch (error.severity) {
1962
+ case "critical":
1963
+ console.error("[CRITICAL ERROR]", logData);
1964
+ break;
1965
+ case "high":
1966
+ console.error("[ERROR]", logData);
1967
+ break;
1968
+ case "medium":
1969
+ console.warn("[WARNING]", logData);
1970
+ break;
1971
+ case "low":
1972
+ console.warn("[INFO]", logData);
1973
+ break;
1974
+ default:
1975
+ console.error("[ERROR]", logData);
1976
+ }
242
1977
  }
243
- revokeKey(key) {
244
- return this.keys.delete(key);
1978
+ getStats() {
1979
+ return {
1980
+ errorReporting: this.errorReporting ? {
1981
+ breadcrumbs: this.errorReporting.getBreadcrumbs().length
1982
+ } : undefined,
1983
+ gracefulDegradation: this.gracefulDegradation ? this.gracefulDegradation.getSystemHealth() : undefined,
1984
+ circuitBreakers: this.circuitBreakerRegistry ? this.circuitBreakerRegistry.getAllMetrics() : undefined
1985
+ };
245
1986
  }
246
- extractFromRequest(req) {
247
- const { source, keyName } = this.options;
248
- switch (source) {
249
- case "header":
250
- return req.headers.get(keyName || "X-API-Key") || null;
251
- case "query":
252
- return new URL(req.url).searchParams.get(keyName || "api_key") || null;
253
- case "cookie":
254
- if (req.cookies) {
255
- const key = keyName || "api_key";
256
- if (typeof req.cookies.get === "function") {
257
- return req.cookies.get(key) || null;
258
- }
259
- return req.cookies[key] || null;
260
- }
261
- return null;
262
- default:
263
- return null;
1987
+ async cleanup() {
1988
+ if (this.errorReporting) {
1989
+ await this.errorReporting.close();
1990
+ }
1991
+ if (this.gracefulDegradation) {
1992
+ this.gracefulDegradation.stop();
264
1993
  }
265
1994
  }
266
- randomString(length) {
267
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
268
- let result = "";
269
- for (let i = 0;i < length; i++) {
270
- result += chars.charAt(Math.floor(Math.random() * chars.length));
1995
+ }
1996
+ function createAdvancedErrorHandler(config2 = {}) {
1997
+ const handler = new AdvancedErrorHandler(config2);
1998
+ return {
1999
+ middleware: handler.middleware(),
2000
+ getStats: () => handler.getStats(),
2001
+ cleanup: () => handler.cleanup()
2002
+ };
2003
+ }
2004
+ // src/request/context.ts
2005
+ import { AsyncLocalStorage } from "async_hooks";
2006
+ var storage = new AsyncLocalStorage;
2007
+ var contextEnabled = false;
2008
+ var syncCurrent = null;
2009
+ function enableRequestContext() {
2010
+ contextEnabled = true;
2011
+ }
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
+ });
271
2020
  }
2021
+ syncCurrent = null;
272
2022
  return result;
273
2023
  }
2024
+ return storage.run({ current: initial }, fn);
274
2025
  }
275
-
276
- class OAuth2Helper {
277
- config;
278
- constructor(config2) {
279
- this.config = config2;
2026
+ function setCurrentRequest(req) {
2027
+ const store = storage.getStore();
2028
+ if (store) {
2029
+ store.current = req;
2030
+ return;
280
2031
  }
281
- getAuthorizationUrl(additionalParams = {}) {
282
- const params = new URLSearchParams({
283
- client_id: this.config.clientId,
284
- redirect_uri: this.config.redirectUri,
285
- response_type: "code",
286
- ...additionalParams
287
- });
288
- if (this.config.scope) {
289
- params.set("scope", this.config.scope);
290
- }
291
- if (this.config.state) {
292
- params.set("state", this.config.state);
293
- }
294
- return `${this.config.authorizeUrl}?${params.toString()}`;
2032
+ if (syncCurrent !== null) {
2033
+ syncCurrent = req;
295
2034
  }
296
- async exchangeCodeForToken(code) {
297
- const params = new URLSearchParams({
298
- grant_type: "authorization_code",
299
- code,
300
- redirect_uri: this.config.redirectUri,
301
- client_id: this.config.clientId,
302
- client_secret: this.config.clientSecret
303
- });
304
- const response = await fetch(this.config.tokenUrl, {
305
- method: "POST",
306
- headers: {
307
- "Content-Type": "application/x-www-form-urlencoded"
308
- },
309
- body: params.toString()
310
- });
311
- if (!response.ok) {
312
- throw new Error(`OAuth token exchange failed: ${response.status} ${response.statusText}`);
313
- }
314
- return await response.json();
2035
+ }
2036
+ function getCurrentRequest() {
2037
+ if (!contextEnabled) {
2038
+ contextEnabled = true;
2039
+ return syncCurrent ?? undefined;
315
2040
  }
316
- async refreshToken(refreshToken) {
317
- const params = new URLSearchParams({
318
- grant_type: "refresh_token",
319
- refresh_token: refreshToken,
320
- client_id: this.config.clientId,
321
- client_secret: this.config.clientSecret
322
- });
323
- const response = await fetch(this.config.tokenUrl, {
324
- method: "POST",
325
- headers: {
326
- "Content-Type": "application/x-www-form-urlencoded"
327
- },
328
- body: params.toString()
329
- });
330
- if (!response.ok) {
331
- throw new Error(`OAuth token refresh failed: ${response.status} ${response.statusText}`);
332
- }
333
- return await response.json();
2041
+ return storage.getStore()?.current ?? syncCurrent ?? undefined;
2042
+ }
2043
+ function request() {
2044
+ const req = getCurrentRequest();
2045
+ if (!req) {
2046
+ throw new Error("request() called outside a request scope. It can only be invoked from within a route handler or middleware.");
334
2047
  }
2048
+ return req;
335
2049
  }
336
2050
  // src/response/response-factory.ts
337
2051
  var response = {
@@ -429,8 +2143,8 @@ var response = {
429
2143
  redirectTemporary: (url) => {
430
2144
  return response.redirect(url, 302);
431
2145
  },
432
- back: (request, fallback = "/") => {
433
- const referer = request.headers.get("referer") || fallback;
2146
+ back: (request2, fallback = "/") => {
2147
+ const referer = request2.headers.get("referer") || fallback;
434
2148
  return response.redirect(referer);
435
2149
  },
436
2150
  view: (html, status = 200, headers = {}) => {
@@ -675,7 +2389,7 @@ class MiddlewarePipeline {
675
2389
  }
676
2390
  this.compiledPipelines.set(routeKey, compiled);
677
2391
  }
678
- async execute(routeKey, request, finalHandler) {
2392
+ async execute(routeKey, request2, finalHandler) {
679
2393
  const startTime = performance.now();
680
2394
  this.stats.totalExecutions++;
681
2395
  const pipeline = this.compiledPipelines.get(routeKey);
@@ -685,7 +2399,7 @@ class MiddlewarePipeline {
685
2399
  }
686
2400
  this.stats.cacheHits++;
687
2401
  const context = {
688
- request,
2402
+ request: request2,
689
2403
  dependencies: new Map,
690
2404
  metadata: {}
691
2405
  };
@@ -700,7 +2414,7 @@ class MiddlewarePipeline {
700
2414
  this.stats.skippedMiddleware++;
701
2415
  return await next();
702
2416
  }
703
- const result2 = await middleware2.handler(request, next);
2417
+ const result2 = await middleware2.handler(request2, next);
704
2418
  if (result2 instanceof Response && middleware2.canShortCircuit) {
705
2419
  this.stats.shortCircuits++;
706
2420
  return result2;
@@ -1778,12 +3492,12 @@ class StreamHandler {
1778
3492
  }
1779
3493
  }
1780
3494
  class FileStreamHandler {
1781
- static async streamFile(filePath, request, options = {}) {
3495
+ static async streamFile(filePath, request2, options = {}) {
1782
3496
  const { contentType, enableRanges = true, chunkSize: _chunkSize = 64 * 1024 } = options;
1783
3497
  try {
1784
3498
  const file = Bun.file(filePath);
1785
3499
  const fileSize = file.size;
1786
- const range = enableRanges ? request.headers.get("range") : null;
3500
+ const range = enableRanges ? request2.headers.get("range") : null;
1787
3501
  if (range && enableRanges) {
1788
3502
  return FileStreamHandler.handleRangeRequest(file, range, contentType);
1789
3503
  }
@@ -1862,8 +3576,8 @@ function closeReadableStreamAfterError(controller, error) {
1862
3576
  function registerFileStreaming(RouterClass) {
1863
3577
  Object.defineProperties(RouterClass.prototype, {
1864
3578
  streamFile: {
1865
- value(filePath, request, options) {
1866
- return FileStreamHandler.streamFile(filePath, request, options);
3579
+ value(filePath, request2, options) {
3580
+ return FileStreamHandler.streamFile(filePath, request2, options);
1867
3581
  },
1868
3582
  writable: true,
1869
3583
  configurable: true
@@ -3218,6 +4932,7 @@ var SubdomainPatterns = {
3218
4932
  };
3219
4933
 
3220
4934
  // src/url.ts
4935
+ import process3 from "process";
3221
4936
  var namedRouteRegistry = new Map;
3222
4937
  function registerNamedRoute(name, path) {
3223
4938
  namedRouteRegistry.set(name, path);
@@ -3225,6 +4940,54 @@ function registerNamedRoute(name, path) {
3225
4940
  function getNamedRoutePath(name) {
3226
4941
  return namedRouteRegistry.get(name);
3227
4942
  }
4943
+ function getNamedRoutes() {
4944
+ return new Map(namedRouteRegistry);
4945
+ }
4946
+ function clearNamedRoutes() {
4947
+ namedRouteRegistry.clear();
4948
+ }
4949
+ function url(name, params = {}, options = {}) {
4950
+ const path = namedRouteRegistry.get(name);
4951
+ if (!path) {
4952
+ const known = [...namedRouteRegistry.keys()].sort().join(", ") || "(none registered)";
4953
+ throw new Error(`Named route '${name}' is not defined. Known routes: ${known}`);
4954
+ }
4955
+ const consumed = new Set;
4956
+ let resolved = path;
4957
+ for (const [key, value] of Object.entries(params)) {
4958
+ const required = `{${key}}`;
4959
+ const optional = `{${key}?}`;
4960
+ const encoded = encodeURIComponent(String(value));
4961
+ if (resolved.includes(required)) {
4962
+ resolved = resolved.replaceAll(required, encoded);
4963
+ consumed.add(key);
4964
+ } else if (resolved.includes(optional)) {
4965
+ resolved = resolved.replaceAll(optional, encoded);
4966
+ consumed.add(key);
4967
+ }
4968
+ }
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
+ }
4974
+ const missing = resolved.match(/\{([^}?]+)\}/g);
4975
+ if (missing && missing.length > 0)
4976
+ throw new Error(`Missing required params for route '${name}': ${missing.join(", ")}`);
4977
+ const leftover = Object.entries(params).filter(([k]) => !consumed.has(k));
4978
+ if (leftover.length > 0) {
4979
+ const qs = new URLSearchParams;
4980
+ for (const [k, v] of leftover)
4981
+ qs.set(k, String(v));
4982
+ resolved += `?${qs.toString()}`;
4983
+ }
4984
+ if (options.absolute) {
4985
+ const base = (options.base ?? process3.env.APP_URL ?? "http://localhost").replace(/\/$/, "");
4986
+ const prefixed = base.startsWith("http") ? base : `https://${base}`;
4987
+ return `${prefixed}${resolved.startsWith("/") ? resolved : `/${resolved}`}`;
4988
+ }
4989
+ return resolved;
4990
+ }
3228
4991
 
3229
4992
  // src/utils.ts
3230
4993
  import { join as join3 } from "path";
@@ -3778,24 +5541,24 @@ class FluentRouter {
3778
5541
  getRoutes() {
3779
5542
  return [...this.routes, ...this.subdomainRouter.getAllRoutes()];
3780
5543
  }
3781
- async handle(request) {
3782
- const url = new URL(request.url);
3783
- const domain = url.hostname;
5544
+ async handle(request2) {
5545
+ const url2 = new URL(request2.url);
5546
+ const domain = url2.hostname;
3784
5547
  const match = this.subdomainRouter.findDomainGroup(domain);
3785
5548
  if (match) {
3786
- const enhancedReq = request;
5549
+ const enhancedReq = request2;
3787
5550
  enhancedReq.domainParams = match.parameters;
3788
5551
  for (const route of match.group.getRoutes()) {
3789
- const params = this.matchRouteParams(route, request.method, url.pathname);
5552
+ const params = this.matchRouteParams(route, request2.method, url2.pathname);
3790
5553
  if (params) {
3791
5554
  return await this.executeRoute(route, enhancedReq, params);
3792
5555
  }
3793
5556
  }
3794
5557
  }
3795
5558
  for (const route of this.routes) {
3796
- const params = this.matchRouteParams(route, request.method, url.pathname);
5559
+ const params = this.matchRouteParams(route, request2.method, url2.pathname);
3797
5560
  if (params) {
3798
- return await this.executeRoute(route, request, params);
5561
+ return await this.executeRoute(route, request2, params);
3799
5562
  }
3800
5563
  }
3801
5564
  return null;
@@ -3807,11 +5570,11 @@ class FluentRouter {
3807
5570
  const params = {};
3808
5571
  return matchPath(route.path, pathname, params) ? params : null;
3809
5572
  }
3810
- async executeRoute(route, request, params) {
3811
- request.params = params;
5573
+ async executeRoute(route, request2, params) {
5574
+ request2.params = params;
3812
5575
  const allMiddleware = [...this.globalMiddleware];
3813
5576
  for (const conditional of this.conditionalMiddleware) {
3814
- if (await conditional.condition(request)) {
5577
+ if (await conditional.condition(request2)) {
3815
5578
  allMiddleware.push(...conditional.middleware);
3816
5579
  }
3817
5580
  }
@@ -3820,10 +5583,10 @@ class FluentRouter {
3820
5583
  const next = async () => {
3821
5584
  if (index < allMiddleware.length) {
3822
5585
  const middleware2 = allMiddleware[index++];
3823
- const result = await middleware2(request, next);
5586
+ const result = await middleware2(request2, next);
3824
5587
  return result ?? new Response(null);
3825
5588
  }
3826
- return await route.handler(request);
5589
+ return await route.handler(request2);
3827
5590
  };
3828
5591
  return await next();
3829
5592
  }
@@ -3861,27 +5624,27 @@ var RouteFactory = {
3861
5624
  };
3862
5625
  var RouterUtils = {
3863
5626
  route: (name, params = {}, query = {}) => {
3864
- let url = getNamedRoutePath(name) ?? `/${name}`;
5627
+ let url2 = getNamedRoutePath(name) ?? `/${name}`;
3865
5628
  for (const [key, value] of Object.entries(params)) {
3866
5629
  const encoded = encodeURIComponent(value);
3867
- url = url.replace(`{${key}}`, encoded);
3868
- url = url.replace(`{${key}?}`, encoded);
5630
+ url2 = url2.replace(`{${key}}`, encoded);
5631
+ url2 = url2.replace(`{${key}?}`, encoded);
3869
5632
  }
3870
- if (url.includes("?}")) {
3871
- url = url.replace(/\{[^}]+\?\}/g, "").replace(/\/{2,}/g, "/");
3872
- if (url.length > 1 && url.endsWith("/"))
3873
- url = url.slice(0, -1);
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);
3874
5637
  }
3875
5638
  const queryString = new URLSearchParams(query).toString();
3876
5639
  if (queryString) {
3877
- url += `?${queryString}`;
5640
+ url2 += `?${queryString}`;
3878
5641
  }
3879
- return url;
5642
+ return url2;
3880
5643
  },
3881
- redirect: (url, status = 302) => {
5644
+ redirect: (url2, status = 302) => {
3882
5645
  return new Response(null, {
3883
5646
  status,
3884
- headers: { Location: url }
5647
+ headers: { Location: url2 }
3885
5648
  });
3886
5649
  },
3887
5650
  json: (data, status = 200) => {
@@ -4079,10 +5842,10 @@ function registerHttpMethods(RouterClass) {
4079
5842
  route.constraints = constraints;
4080
5843
  }
4081
5844
  route.pattern = {
4082
- exec: (url) => {
5845
+ exec: (url2) => {
4083
5846
  const params = {};
4084
5847
  const constraintsRecord = route.constraints && !Array.isArray(route.constraints) ? route.constraints : undefined;
4085
- const isMatch = matchPath(routePath, url.pathname, params, constraintsRecord);
5848
+ const isMatch = matchPath(routePath, url2.pathname, params, constraintsRecord);
4086
5849
  if (!isMatch) {
4087
5850
  return null;
4088
5851
  }
@@ -4209,18 +5972,18 @@ function registerHttpMethods(RouterClass) {
4209
5972
  if (!route) {
4210
5973
  throw new Error(`Route with name "${name}" not found`);
4211
5974
  }
4212
- let url = route.path;
5975
+ let url2 = route.path;
4213
5976
  for (const [param, value] of Object.entries(params)) {
4214
- url = url.replace(`{${param}}`, encodeURIComponent(value));
4215
- url = url.replace(`{${param}?}`, encodeURIComponent(value));
5977
+ url2 = url2.replace(`{${param}}`, encodeURIComponent(value));
5978
+ url2 = url2.replace(`{${param}?}`, encodeURIComponent(value));
4216
5979
  }
4217
- if (url.includes("{")) {
4218
- url = url.replace(/\{[^}]+\?\}/g, "").replace(/\/{2,}/g, "/");
4219
- if (url.length > 1 && url.endsWith("/")) {
4220
- url = url.slice(0, -1);
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);
4221
5984
  }
4222
5985
  }
4223
- return url;
5986
+ return url2;
4224
5987
  },
4225
5988
  writable: true,
4226
5989
  configurable: true
@@ -4238,9 +6001,9 @@ function registerHttpMethods(RouterClass) {
4238
6001
  function registerRedirectMethods(RouterClass) {
4239
6002
  Object.defineProperties(RouterClass.prototype, {
4240
6003
  redirect: {
4241
- value(url, status = 302) {
6004
+ value(url2, status = 302) {
4242
6005
  const headers = new Headers;
4243
- headers.set("Location", url);
6006
+ headers.set("Location", url2);
4244
6007
  return new Response(null, {
4245
6008
  status,
4246
6009
  headers
@@ -4250,8 +6013,8 @@ function registerRedirectMethods(RouterClass) {
4250
6013
  configurable: true
4251
6014
  },
4252
6015
  permanentRedirect: {
4253
- value(url) {
4254
- return this.redirect(url, 301);
6016
+ value(url2) {
6017
+ return this.redirect(url2, 301);
4255
6018
  },
4256
6019
  writable: true,
4257
6020
  configurable: true
@@ -5249,8 +7012,8 @@ class RouteCompiler {
5249
7012
  }
5250
7013
  const pattern = this.convertToURLPattern(route.path, route.constraints && !Array.isArray(route.constraints) ? route.constraints : undefined);
5251
7014
  const urlPatternAdapter = {
5252
- exec: (url) => {
5253
- const pathname = url.pathname;
7015
+ exec: (url2) => {
7016
+ const pathname = url2.pathname;
5254
7017
  const result = pattern.exec(pathname);
5255
7018
  if (!result)
5256
7019
  return null;
@@ -5621,8 +7384,8 @@ function registerOptimizedRouteMatching(RouterClass) {
5621
7384
  },
5622
7385
  fallbackMatchRoute: {
5623
7386
  value(path, method, domain) {
5624
- const url = new URL(path, "http://localhost");
5625
- const cacheKey = `${domain || ""}:${method}:${url.pathname}`;
7387
+ const url2 = new URL(path, "http://localhost");
7388
+ const cacheKey = `${domain || ""}:${method}:${url2.pathname}`;
5626
7389
  if (this.routeCache.has(cacheKey)) {
5627
7390
  return this.routeCache.get(cacheKey);
5628
7391
  }
@@ -5630,7 +7393,7 @@ function registerOptimizedRouteMatching(RouterClass) {
5630
7393
  this.routeCache.clear();
5631
7394
  }
5632
7395
  if (this.staticRoutes.has(method)) {
5633
- const staticRoute = this.staticRoutes.get(method).get(url.pathname);
7396
+ const staticRoute = this.staticRoutes.get(method).get(url2.pathname);
5634
7397
  if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
5635
7398
  const result = {
5636
7399
  route: staticRoute,
@@ -5643,7 +7406,7 @@ function registerOptimizedRouteMatching(RouterClass) {
5643
7406
  const potentialRoutes = domain && this.domains[domain] ? this.domains[domain] : this.routes;
5644
7407
  const methodRoutes = potentialRoutes.filter((route) => route.method === method);
5645
7408
  for (const route of methodRoutes) {
5646
- if (route.path === url.pathname) {
7409
+ if (route.path === url2.pathname) {
5647
7410
  const result = {
5648
7411
  route,
5649
7412
  params: {}
@@ -5654,7 +7417,7 @@ function registerOptimizedRouteMatching(RouterClass) {
5654
7417
  }
5655
7418
  for (const route of methodRoutes) {
5656
7419
  if (route.pattern) {
5657
- const match = route.pattern.exec(url);
7420
+ const match = route.pattern.exec(url2);
5658
7421
  if (match) {
5659
7422
  const result = {
5660
7423
  route,
@@ -5672,10 +7435,10 @@ function registerOptimizedRouteMatching(RouterClass) {
5672
7435
  continue;
5673
7436
  }
5674
7437
  const basePath = route.path.slice(0, -1);
5675
- if (url.pathname.startsWith(basePath)) {
7438
+ if (url2.pathname.startsWith(basePath)) {
5676
7439
  const result = {
5677
7440
  route,
5678
- params: { wildcard: url.pathname.slice(basePath.length) }
7441
+ params: { wildcard: url2.pathname.slice(basePath.length) }
5679
7442
  };
5680
7443
  this.routeCache.set(cacheKey, result);
5681
7444
  return result;
@@ -5797,10 +7560,10 @@ function registerRouteBuilding(RouterClass) {
5797
7560
  } catch {}
5798
7561
  }
5799
7562
  bunRoutes[path] = async (req) => {
5800
- const url = new URL(req.url);
7563
+ const url2 = new URL(req.url);
5801
7564
  const params = {};
5802
7565
  const constraintsRecord = route.constraints && !Array.isArray(route.constraints) ? route.constraints : undefined;
5803
- matchPath(path, url.pathname, params, constraintsRecord);
7566
+ matchPath(path, url2.pathname, params, constraintsRecord);
5804
7567
  const enhancedReq = this.enhanceRequest(req, params);
5805
7568
  const routeHandlerMiddleware = async (req2, _next) => await this.resolveHandler(route.handler, req2);
5806
7569
  const middlewareStack = [...this.globalMiddleware, ...route.middleware, routeHandlerMiddleware];
@@ -5812,10 +7575,10 @@ function registerRouteBuilding(RouterClass) {
5812
7575
  for (const route of routes) {
5813
7576
  const method = route.method;
5814
7577
  methodHandlers[method] = async (req) => {
5815
- const url = new URL(req.url);
7578
+ const url2 = new URL(req.url);
5816
7579
  const params = {};
5817
7580
  const constraintsRecord = route.constraints && !Array.isArray(route.constraints) ? route.constraints : undefined;
5818
- matchPath(path, url.pathname, params, constraintsRecord);
7581
+ matchPath(path, url2.pathname, params, constraintsRecord);
5819
7582
  const enhancedReq = this.enhanceRequest(req, params);
5820
7583
  const routeHandlerMiddleware = async (req2, _next) => await this.resolveHandler(route.handler, req2);
5821
7584
  const middlewareStack = [...this.globalMiddleware, ...route.middleware, routeHandlerMiddleware];
@@ -5909,13 +7672,13 @@ function registerRouteMatching(RouterClass) {
5909
7672
  Object.defineProperties(RouterClass.prototype, {
5910
7673
  matchRoute: {
5911
7674
  value(path, method, domain) {
5912
- const url = new URL(path, "http://localhost");
5913
- const cacheKey = `${domain || ""}:${method}:${url.pathname}`;
7675
+ const url2 = new URL(path, "http://localhost");
7676
+ const cacheKey = `${domain || ""}:${method}:${url2.pathname}`;
5914
7677
  if (this.routeCache.has(cacheKey)) {
5915
7678
  return this.routeCache.get(cacheKey);
5916
7679
  }
5917
7680
  if (this.staticRoutes.has(method)) {
5918
- const staticRoute = this.staticRoutes.get(method).get(url.pathname);
7681
+ const staticRoute = this.staticRoutes.get(method).get(url2.pathname);
5919
7682
  if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
5920
7683
  const result = {
5921
7684
  route: staticRoute,
@@ -5928,7 +7691,7 @@ function registerRouteMatching(RouterClass) {
5928
7691
  const potentialRoutes = domain && this.domains[domain] ? this.domains[domain] : this.routes;
5929
7692
  const methodRoutes = potentialRoutes.filter((route) => route.method === method);
5930
7693
  for (const route of methodRoutes) {
5931
- if (route.path === url.pathname) {
7694
+ if (route.path === url2.pathname) {
5932
7695
  const result = {
5933
7696
  route,
5934
7697
  params: {}
@@ -5939,7 +7702,7 @@ function registerRouteMatching(RouterClass) {
5939
7702
  }
5940
7703
  for (const route of methodRoutes) {
5941
7704
  if (route.pattern) {
5942
- const match = route.pattern.exec(url);
7705
+ const match = route.pattern.exec(url2);
5943
7706
  if (match) {
5944
7707
  const result = {
5945
7708
  route,
@@ -5954,11 +7717,11 @@ function registerRouteMatching(RouterClass) {
5954
7717
  const wildcardRoutes = this.domains[domain].filter((route) => route.method === method && route.path.endsWith("*"));
5955
7718
  for (const route of wildcardRoutes) {
5956
7719
  const basePath = route.path.slice(0, -1);
5957
- if (url.pathname.startsWith(basePath)) {
7720
+ if (url2.pathname.startsWith(basePath)) {
5958
7721
  const result = {
5959
7722
  route,
5960
7723
  params: {
5961
- wildcard: url.pathname.slice(basePath.length)
7724
+ wildcard: url2.pathname.slice(basePath.length)
5962
7725
  }
5963
7726
  };
5964
7727
  this.routeCache.set(cacheKey, result);
@@ -5969,11 +7732,11 @@ function registerRouteMatching(RouterClass) {
5969
7732
  const globalWildcardRoutes = this.routes.filter((route) => route.method === method && route.path.endsWith("*") && (!domain || !route.domain));
5970
7733
  for (const route of globalWildcardRoutes) {
5971
7734
  const basePath = route.path.slice(0, -1);
5972
- if (url.pathname.startsWith(basePath)) {
7735
+ if (url2.pathname.startsWith(basePath)) {
5973
7736
  const result = {
5974
7737
  route,
5975
7738
  params: {
5976
- wildcard: url.pathname.slice(basePath.length)
7739
+ wildcard: url2.pathname.slice(basePath.length)
5977
7740
  }
5978
7741
  };
5979
7742
  this.routeCache.set(cacheKey, result);
@@ -6074,9 +7837,9 @@ function registerRouteMatching(RouterClass) {
6074
7837
  const routePath = lastRoute.path;
6075
7838
  const constraintsRecord = lastRoute.constraints && !Array.isArray(lastRoute.constraints) ? lastRoute.constraints : undefined;
6076
7839
  lastRoute.pattern = {
6077
- exec: (url) => {
7840
+ exec: (url2) => {
6078
7841
  const params2 = {};
6079
- const isMatch = matchPath(routePath, url.pathname, params2, constraintsRecord);
7842
+ const isMatch = matchPath(routePath, url2.pathname, params2, constraintsRecord);
6080
7843
  if (!isMatch) {
6081
7844
  return null;
6082
7845
  }
@@ -6327,10 +8090,10 @@ class Router {
6327
8090
  route.constraints = constraints;
6328
8091
  }
6329
8092
  route.pattern = {
6330
- exec: (url) => {
8093
+ exec: (url2) => {
6331
8094
  const params = {};
6332
8095
  const constraintsRecord = route.constraints && !Array.isArray(route.constraints) ? route.constraints : undefined;
6333
- const isMatch = matchPath(routePath, url.pathname, params, constraintsRecord);
8096
+ const isMatch = matchPath(routePath, url2.pathname, params, constraintsRecord);
6334
8097
  if (!isMatch) {
6335
8098
  return null;
6336
8099
  }
@@ -6411,27 +8174,27 @@ class Router {
6411
8174
  if (!route) {
6412
8175
  throw new Error(`Route with name "${name}" not found`);
6413
8176
  }
6414
- let url = route.path;
8177
+ let url2 = route.path;
6415
8178
  for (const [param, value] of Object.entries(params)) {
6416
- url = url.replace(`{${param}}`, encodeURIComponent(value));
6417
- url = url.replace(`{${param}?}`, encodeURIComponent(value));
8179
+ url2 = url2.replace(`{${param}}`, encodeURIComponent(value));
8180
+ url2 = url2.replace(`{${param}?}`, encodeURIComponent(value));
6418
8181
  }
6419
- return url;
8182
+ return url2;
6420
8183
  }
6421
8184
  onError(handler) {
6422
8185
  this.errorHandler = handler;
6423
8186
  return this;
6424
8187
  }
6425
- redirect(url, status = 302) {
8188
+ redirect(url2, status = 302) {
6426
8189
  const headers = new Headers;
6427
- headers.set("Location", url);
8190
+ headers.set("Location", url2);
6428
8191
  return new Response(null, {
6429
8192
  status,
6430
8193
  headers
6431
8194
  });
6432
8195
  }
6433
- permanentRedirect(url) {
6434
- return this.redirect(url, 301);
8196
+ permanentRedirect(url2) {
8197
+ return this.redirect(url2, 301);
6435
8198
  }
6436
8199
  redirectRoute(from, to, status = 302) {
6437
8200
  this.get(from, (_req) => {
@@ -6458,13 +8221,13 @@ class Router {
6458
8221
  }
6459
8222
  async handleRequest(req) {
6460
8223
  try {
6461
- const url = new URL(req.url);
8224
+ const url2 = new URL(req.url);
6462
8225
  if (this.config.verbose) {
6463
- console.log(`${req.method} ${url.pathname}`);
8226
+ console.log(`${req.method} ${url2.pathname}`);
6464
8227
  }
6465
8228
  if (req.method === "OPTIONS") {
6466
- const hostname2 = url.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
6467
- const optionsMatch = this.matchRoute(url.pathname, "OPTIONS", hostname2);
8229
+ const hostname2 = url2.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
8230
+ const optionsMatch = this.matchRoute(url2.pathname, "OPTIONS", hostname2);
6468
8231
  if (!optionsMatch) {
6469
8232
  return new Response(null, {
6470
8233
  status: 204,
@@ -6477,8 +8240,8 @@ class Router {
6477
8240
  });
6478
8241
  }
6479
8242
  }
6480
- const hostname = url.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
6481
- const match = this.matchRoute(url.pathname, req.method, hostname);
8243
+ const hostname = url2.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
8244
+ const match = this.matchRoute(url2.pathname, req.method, hostname);
6482
8245
  const enhancedReq = this.enhanceRequest(req, match?.params || {});
6483
8246
  if (match) {
6484
8247
  enhancedReq.route = match.route;
@@ -6496,12 +8259,12 @@ class Router {
6496
8259
  }
6497
8260
  return new Response("No response from middleware chain", { status: 500 });
6498
8261
  }
6499
- const allowedMethods = this.getAllowedMethods(url.pathname, hostname);
8262
+ const allowedMethods = this.getAllowedMethods(url2.pathname, hostname);
6500
8263
  if (allowedMethods.length > 0) {
6501
8264
  const methodNotAllowedHandler = async (_req, _next) => {
6502
8265
  return new Response(JSON.stringify({
6503
8266
  error: "Method Not Allowed",
6504
- path: url.pathname,
8267
+ path: url2.pathname,
6505
8268
  method: req.method,
6506
8269
  allowed: allowedMethods
6507
8270
  }), {
@@ -6528,7 +8291,7 @@ class Router {
6528
8291
  }
6529
8292
  return new Response(JSON.stringify({
6530
8293
  error: "Not Found",
6531
- path: url.pathname,
8294
+ path: url2.pathname,
6532
8295
  method: req.method
6533
8296
  }), {
6534
8297
  status: 404,
@@ -6547,7 +8310,7 @@ class Router {
6547
8310
  }
6548
8311
  return new Response(JSON.stringify({
6549
8312
  error: "Not Found",
6550
- path: url.pathname,
8313
+ path: url2.pathname,
6551
8314
  method: req.method
6552
8315
  }), {
6553
8316
  status: 404,
@@ -6590,15 +8353,15 @@ class Router {
6590
8353
  }
6591
8354
  }
6592
8355
  getAllowedMethods(path, domain) {
6593
- const url = new URL(path, "http://localhost");
6594
- const cacheKey = `${domain || ""}:${url.pathname}`;
8356
+ const url2 = new URL(path, "http://localhost");
8357
+ const cacheKey = `${domain || ""}:${url2.pathname}`;
6595
8358
  const cached = this._allowedMethodsCache.get(cacheKey);
6596
8359
  if (cached) {
6597
8360
  return cached;
6598
8361
  }
6599
8362
  const methods = new Set;
6600
8363
  for (const [method, routesByPath] of this.staticRoutes) {
6601
- const staticRoute = routesByPath.get(url.pathname);
8364
+ const staticRoute = routesByPath.get(url2.pathname);
6602
8365
  if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
6603
8366
  methods.add(method);
6604
8367
  }
@@ -6608,15 +8371,15 @@ class Router {
6608
8371
  if (methods.has(route.method)) {
6609
8372
  continue;
6610
8373
  }
6611
- if (route.path === url.pathname) {
8374
+ if (route.path === url2.pathname) {
6612
8375
  methods.add(route.method);
6613
8376
  continue;
6614
8377
  }
6615
- if (route.pattern && route.pattern.exec(url)) {
8378
+ if (route.pattern && route.pattern.exec(url2)) {
6616
8379
  methods.add(route.method);
6617
8380
  continue;
6618
8381
  }
6619
- if (route.path.endsWith("*") && url.pathname.startsWith(route.path.slice(0, -1))) {
8382
+ if (route.path.endsWith("*") && url2.pathname.startsWith(route.path.slice(0, -1))) {
6620
8383
  methods.add(route.method);
6621
8384
  }
6622
8385
  }
@@ -6631,13 +8394,13 @@ class Router {
6631
8394
  return result;
6632
8395
  }
6633
8396
  matchRoute(path, method, domain) {
6634
- const url = new URL(path, "http://localhost");
6635
- const cacheKey = `${domain || ""}:${method}:${url.pathname}`;
8397
+ const url2 = new URL(path, "http://localhost");
8398
+ const cacheKey = `${domain || ""}:${method}:${url2.pathname}`;
6636
8399
  if (this.routeCache.has(cacheKey)) {
6637
8400
  return this.routeCache.get(cacheKey);
6638
8401
  }
6639
8402
  if (this.staticRoutes.has(method)) {
6640
- const staticRoute = this.staticRoutes.get(method).get(url.pathname);
8403
+ const staticRoute = this.staticRoutes.get(method).get(url2.pathname);
6641
8404
  if (staticRoute && (!domain || !staticRoute.domain || staticRoute.domain === domain)) {
6642
8405
  const result = {
6643
8406
  route: staticRoute,
@@ -6650,7 +8413,7 @@ class Router {
6650
8413
  const potentialRoutes = domain && this.domains[domain] ? this.domains[domain] : this.routes;
6651
8414
  const methodRoutes = potentialRoutes.filter((route) => route.method === method);
6652
8415
  for (const route of methodRoutes) {
6653
- if (route.path === url.pathname) {
8416
+ if (route.path === url2.pathname) {
6654
8417
  const result = {
6655
8418
  route,
6656
8419
  params: {}
@@ -6661,7 +8424,7 @@ class Router {
6661
8424
  }
6662
8425
  for (const route of methodRoutes) {
6663
8426
  if (route.pattern) {
6664
- const match = route.pattern.exec(url);
8427
+ const match = route.pattern.exec(url2);
6665
8428
  if (match) {
6666
8429
  const result = {
6667
8430
  route,
@@ -6676,11 +8439,11 @@ class Router {
6676
8439
  const wildcardRoutes = this.domains[domain].filter((route) => route.method === method && route.path.endsWith("*"));
6677
8440
  for (const route of wildcardRoutes) {
6678
8441
  const basePath = route.path.slice(0, -1);
6679
- if (url.pathname.startsWith(basePath)) {
8442
+ if (url2.pathname.startsWith(basePath)) {
6680
8443
  const result = {
6681
8444
  route,
6682
8445
  params: {
6683
- wildcard: url.pathname.slice(basePath.length)
8446
+ wildcard: url2.pathname.slice(basePath.length)
6684
8447
  }
6685
8448
  };
6686
8449
  this.routeCache.set(cacheKey, result);
@@ -6691,11 +8454,11 @@ class Router {
6691
8454
  const globalWildcardRoutes = this.routes.filter((route) => route.method === method && route.path.endsWith("*") && (!domain || !route.domain));
6692
8455
  for (const route of globalWildcardRoutes) {
6693
8456
  const basePath = route.path.slice(0, -1);
6694
- if (url.pathname.startsWith(basePath)) {
8457
+ if (url2.pathname.startsWith(basePath)) {
6695
8458
  const result = {
6696
8459
  route,
6697
8460
  params: {
6698
- wildcard: url.pathname.slice(basePath.length)
8461
+ wildcard: url2.pathname.slice(basePath.length)
6699
8462
  }
6700
8463
  };
6701
8464
  this.routeCache.set(cacheKey, result);
@@ -6724,9 +8487,9 @@ class Router {
6724
8487
  }
6725
8488
  return parsedCookies;
6726
8489
  };
6727
- const url = new URL(req.url);
8490
+ const url2 = new URL(req.url);
6728
8491
  const query = {};
6729
- url.searchParams.forEach((value, key) => {
8492
+ url2.searchParams.forEach((value, key) => {
6730
8493
  query[key] = value;
6731
8494
  });
6732
8495
  const enhancedReq = {
@@ -6930,6 +8693,13 @@ class Router {
6930
8693
  const value = params?.[name];
6931
8694
  return value !== undefined ? value : defaultValue;
6932
8695
  };
8696
+ enhancedReq.getParamAsInt = (name) => {
8697
+ const value = params?.[name];
8698
+ if (value === undefined || value.trim() === "")
8699
+ return null;
8700
+ const parsed = Number(value);
8701
+ return Number.isSafeInteger(parsed) ? parsed : null;
8702
+ };
6933
8703
  enhancedReq.params = params || {};
6934
8704
  return enhancedReq;
6935
8705
  }
@@ -7103,7 +8873,7 @@ class Router {
7103
8873
  }
7104
8874
  return this;
7105
8875
  }
7106
- async executeMiddleware(middleware2, request, handler) {
8876
+ async executeMiddleware(middleware2, request2, handler) {
7107
8877
  if (middleware2.length === 0) {
7108
8878
  return handler();
7109
8879
  }
@@ -7113,7 +8883,7 @@ class Router {
7113
8883
  return handler();
7114
8884
  }
7115
8885
  const mw = middleware2[currentIndex++];
7116
- const result = await mw(request, next);
8886
+ const result = await mw(request2, next);
7117
8887
  return result || new Response("No response from middleware", { status: 500 });
7118
8888
  };
7119
8889
  return next();
@@ -7275,16 +9045,16 @@ class RequestWithMacros {
7275
9045
  static macroAccessor(name, getter) {
7276
9046
  requestMacroRegistry.register(name, getter, true);
7277
9047
  }
7278
- static applyMacros(request) {
9048
+ static applyMacros(request2) {
7279
9049
  const epoch = requestMacroRegistry.getEpoch();
7280
9050
  if (epoch !== macroProtoEpoch) {
7281
9051
  macroProtoCache = new WeakMap;
7282
9052
  macroProtoEpoch = epoch;
7283
9053
  }
7284
- let base = Object.getPrototypeOf(request) ?? Object.prototype;
9054
+ let base = Object.getPrototypeOf(request2) ?? Object.prototype;
7285
9055
  if (base[MACRO_PROTO]) {
7286
9056
  if (macroProtoCache.get(base[MACRO_BASE]) === base) {
7287
- return request;
9057
+ return request2;
7288
9058
  }
7289
9059
  base = base[MACRO_BASE];
7290
9060
  }
@@ -7313,8 +9083,8 @@ class RequestWithMacros {
7313
9083
  Object.defineProperty(proto, MACRO_BASE, { value: base });
7314
9084
  macroProtoCache.set(base, proto);
7315
9085
  }
7316
- Object.setPrototypeOf(request, proto);
7317
- return request;
9086
+ Object.setPrototypeOf(request2, proto);
9087
+ return request2;
7318
9088
  }
7319
9089
  static hasMacro(name) {
7320
9090
  return requestMacroRegistry.has(name);
@@ -7508,8 +9278,8 @@ var BuiltInRequestMacros = {
7508
9278
  return this.url;
7509
9279
  },
7510
9280
  root() {
7511
- const url = getParsedURL(this);
7512
- return `${url.protocol}//${url.host}`;
9281
+ const url2 = getParsedURL(this);
9282
+ return `${url2.protocol}//${url2.host}`;
7513
9283
  },
7514
9284
  is(pattern) {
7515
9285
  const path = this.path();
@@ -7528,11 +9298,11 @@ var BuiltInRequestMacros = {
7528
9298
  return this.routeName || null;
7529
9299
  },
7530
9300
  fingerprint() {
7531
- const url = getParsedURL(this);
9301
+ const url2 = getParsedURL(this);
7532
9302
  const data = {
7533
9303
  method: this.method,
7534
- path: url.pathname,
7535
- query: url.search,
9304
+ path: url2.pathname,
9305
+ query: url2.search,
7536
9306
  ip: BuiltInRequestMacros.ip.call(this)
7537
9307
  };
7538
9308
  return btoa(JSON.stringify(data));
@@ -7583,36 +9353,6 @@ function registerBuiltInRequestMacros() {
7583
9353
  }
7584
9354
  registerBuiltInRequestMacros();
7585
9355
 
7586
- // src/request/context.ts
7587
- import { AsyncLocalStorage } from "async_hooks";
7588
- var storage = new AsyncLocalStorage;
7589
- var contextEnabled = false;
7590
- var syncCurrent = null;
7591
- function runWithRequest(initial, fn) {
7592
- if (!contextEnabled) {
7593
- syncCurrent = initial;
7594
- const result = fn();
7595
- if (result instanceof Promise) {
7596
- return result.finally(() => {
7597
- syncCurrent = null;
7598
- });
7599
- }
7600
- syncCurrent = null;
7601
- return result;
7602
- }
7603
- return storage.run({ current: initial }, fn);
7604
- }
7605
- function setCurrentRequest(req) {
7606
- const store = storage.getStore();
7607
- if (store) {
7608
- store.current = req;
7609
- return;
7610
- }
7611
- if (syncCurrent !== null) {
7612
- syncCurrent = req;
7613
- }
7614
- }
7615
-
7616
9356
  // src/router/server.ts
7617
9357
  if (!RequestWithMacros.hasMacro("getParam")) {
7618
9358
  RequestWithMacros.macro("getParam", function(name, defaultValue) {
@@ -7620,6 +9360,15 @@ if (!RequestWithMacros.hasMacro("getParam")) {
7620
9360
  return value !== undefined ? value : defaultValue;
7621
9361
  });
7622
9362
  }
9363
+ if (!RequestWithMacros.hasMacro("getParamAsInt")) {
9364
+ RequestWithMacros.macro("getParamAsInt", function(name) {
9365
+ const value = this.params?.[name];
9366
+ if (value === undefined || value.trim() === "")
9367
+ return null;
9368
+ const parsed = Number(value);
9369
+ return Number.isSafeInteger(parsed) ? parsed : null;
9370
+ });
9371
+ }
7623
9372
  if (!RequestWithMacros.hasMacro("get")) {
7624
9373
  RequestWithMacros.macro("get", function(key, defaultValue) {
7625
9374
  const value = getParsedURL(this).searchParams.get(key);
@@ -7898,10 +9647,10 @@ function registerServerHandling(RouterClass) {
7898
9647
  handleRequestImpl: {
7899
9648
  async value(req) {
7900
9649
  try {
7901
- const url = new URL(req.url);
7902
- req._parsedURL = url;
7903
- const hostname = url.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
7904
- const match = this.matchRoute(url.pathname, req.method, hostname);
9650
+ const url2 = new URL(req.url);
9651
+ req._parsedURL = url2;
9652
+ const hostname = url2.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
9653
+ const match = this.matchRoute(url2.pathname, req.method, hostname);
7905
9654
  if (req.method === "OPTIONS" && !match) {
7906
9655
  const origin = req.headers.get("origin");
7907
9656
  const preflightHeaders = {
@@ -7921,7 +9670,7 @@ function registerServerHandling(RouterClass) {
7921
9670
  }
7922
9671
  const enhancedReq = this.enhanceRequest(req, {});
7923
9672
  setCurrentRequest(enhancedReq);
7924
- const allowedMethods = this.getAllowedMethods(url.pathname, hostname);
9673
+ const allowedMethods = this.getAllowedMethods(url2.pathname, hostname);
7925
9674
  const corsHeaders = {
7926
9675
  "Content-Type": "application/json",
7927
9676
  "Access-Control-Allow-Origin": "*",
@@ -7933,7 +9682,7 @@ function registerServerHandling(RouterClass) {
7933
9682
  return new Response(JSON.stringify({
7934
9683
  success: false,
7935
9684
  message: "Method Not Allowed",
7936
- path: url.pathname,
9685
+ path: url2.pathname,
7937
9686
  method: req.method,
7938
9687
  allowed: allowedMethods
7939
9688
  }), {
@@ -7957,7 +9706,7 @@ function registerServerHandling(RouterClass) {
7957
9706
  return new Response(JSON.stringify({
7958
9707
  success: false,
7959
9708
  message: "Not Found",
7960
- path: url.pathname,
9709
+ path: url2.pathname,
7961
9710
  method: req.method
7962
9711
  }), { status: 404, headers: corsHeaders });
7963
9712
  };
@@ -8252,34 +10001,34 @@ function registerWebSocketHandling(RouterClass) {
8252
10001
  configurable: true
8253
10002
  },
8254
10003
  upgrade: {
8255
- value(request, options) {
10004
+ value(request2, options) {
8256
10005
  if (!this.serverInstance) {
8257
10006
  throw new Error("Server not started, cannot upgrade to WebSocket");
8258
10007
  }
8259
10008
  if (!this.wsConfig) {
8260
10009
  throw new Error("WebSocket configuration not set, use router.websocket() to configure");
8261
10010
  }
8262
- return this.serverInstance.upgrade(request, options);
10011
+ return this.serverInstance.upgrade(request2, options);
8263
10012
  },
8264
10013
  writable: true,
8265
10014
  configurable: true
8266
10015
  },
8267
10016
  timeout: {
8268
- value(request, seconds) {
10017
+ value(request2, seconds) {
8269
10018
  if (!this.serverInstance) {
8270
10019
  throw new Error("Server not started, cannot set timeout");
8271
10020
  }
8272
- this.serverInstance.timeout(request, seconds);
10021
+ this.serverInstance.timeout(request2, seconds);
8273
10022
  },
8274
10023
  writable: true,
8275
10024
  configurable: true
8276
10025
  },
8277
10026
  requestIP: {
8278
- value(request) {
10027
+ value(request2) {
8279
10028
  if (!this.serverInstance) {
8280
10029
  throw new Error("Server not started, cannot get request IP");
8281
10030
  }
8282
- return this.serverInstance.requestIP(request);
10031
+ return this.serverInstance.requestIP(request2);
8283
10032
  },
8284
10033
  writable: true,
8285
10034
  configurable: true
@@ -8287,7 +10036,7 @@ function registerWebSocketHandling(RouterClass) {
8287
10036
  });
8288
10037
  }
8289
10038
  // src/response/macros.ts
8290
- import process3 from "process";
10039
+ import process4 from "process";
8291
10040
 
8292
10041
  class ResponseMacroRegistry {
8293
10042
  macros = new Map;
@@ -8468,16 +10217,16 @@ var BuiltInResponseMacros = {
8468
10217
  }
8469
10218
  });
8470
10219
  },
8471
- redirect: (url, status = 302) => {
10220
+ redirect: (url2, status = 302) => {
8472
10221
  return new Response(null, {
8473
10222
  status,
8474
10223
  headers: {
8475
- Location: url
10224
+ Location: url2
8476
10225
  }
8477
10226
  });
8478
10227
  },
8479
- permanentRedirect: (url) => {
8480
- return BuiltInResponseMacros.redirect(url, 301);
10228
+ permanentRedirect: (url2) => {
10229
+ return BuiltInResponseMacros.redirect(url2, 301);
8481
10230
  },
8482
10231
  download: (data, filename, contentType = "application/octet-stream") => {
8483
10232
  return new Response(data, {
@@ -8548,7 +10297,7 @@ var BuiltInResponseMacros = {
8548
10297
  status,
8549
10298
  timestamp: new Date().toISOString(),
8550
10299
  checks,
8551
- uptime: process3.uptime?.() || 0
10300
+ uptime: process4.uptime?.() || 0
8552
10301
  };
8553
10302
  return new Response(JSON.stringify(response2), {
8554
10303
  status: statusCode,
@@ -8612,8 +10361,8 @@ var BuiltInRules = {
8612
10361
  name: "url",
8613
10362
  validate: (value) => {
8614
10363
  try {
8615
- const url = new URL(value);
8616
- return Boolean(url);
10364
+ const url2 = new URL(value);
10365
+ return Boolean(url2);
8617
10366
  } catch {
8618
10367
  return false;
8619
10368
  }
@@ -9272,6 +11021,119 @@ registerRouteBuilding(Router);
9272
11021
  registerModelBinding(Router);
9273
11022
  registerFileBasedRouting(Router);
9274
11023
  registerApiRoutes(Router);
11024
+ // src/router/middleware-groups.ts
11025
+ class MiddlewareGroupRegistry {
11026
+ groups = new Map;
11027
+ globalMiddleware = [];
11028
+ middlewarePriority = new Map;
11029
+ define(name, middleware2) {
11030
+ this.groups.set(name, [...middleware2]);
11031
+ return this;
11032
+ }
11033
+ extend(name, middleware2) {
11034
+ const existing = this.groups.get(name) || [];
11035
+ this.groups.set(name, [...existing, ...middleware2]);
11036
+ return this;
11037
+ }
11038
+ prepend(name, middleware2) {
11039
+ const existing = this.groups.get(name) || [];
11040
+ this.groups.set(name, [...middleware2, ...existing]);
11041
+ return this;
11042
+ }
11043
+ get(name) {
11044
+ return this.groups.get(name) || [];
11045
+ }
11046
+ has(name) {
11047
+ return this.groups.has(name);
11048
+ }
11049
+ remove(name) {
11050
+ this.groups.delete(name);
11051
+ return this;
11052
+ }
11053
+ resolve(middleware2) {
11054
+ if (typeof middleware2 === "function") {
11055
+ return [middleware2];
11056
+ }
11057
+ if (this.groups.has(middleware2)) {
11058
+ return this.get(middleware2);
11059
+ }
11060
+ return [middleware2];
11061
+ }
11062
+ resolveAll(middleware2) {
11063
+ const resolved = [];
11064
+ for (const mw of middleware2) {
11065
+ resolved.push(...this.resolve(mw));
11066
+ }
11067
+ return resolved;
11068
+ }
11069
+ pushGlobal(...middleware2) {
11070
+ this.globalMiddleware.push(...middleware2);
11071
+ return this;
11072
+ }
11073
+ prependGlobal(...middleware2) {
11074
+ this.globalMiddleware.unshift(...middleware2);
11075
+ return this;
11076
+ }
11077
+ removeGlobal(middleware2) {
11078
+ this.globalMiddleware = this.globalMiddleware.filter((mw) => typeof mw === "string" ? mw !== middleware2 : true);
11079
+ return this;
11080
+ }
11081
+ getGlobal() {
11082
+ return [...this.globalMiddleware];
11083
+ }
11084
+ setPriority(name, priority) {
11085
+ this.middlewarePriority.set(name, priority);
11086
+ return this;
11087
+ }
11088
+ sortByPriority(middleware2) {
11089
+ return [...middleware2].sort((a, b) => {
11090
+ const nameA = typeof a === "string" ? a.split(":")[0] : "";
11091
+ const nameB = typeof b === "string" ? b.split(":")[0] : "";
11092
+ const priorityA = this.middlewarePriority.get(nameA) ?? 100;
11093
+ const priorityB = this.middlewarePriority.get(nameB) ?? 100;
11094
+ return priorityA - priorityB;
11095
+ });
11096
+ }
11097
+ filter(middleware2, options) {
11098
+ if (options.only && options.only.length > 0) {
11099
+ return middleware2.filter((mw) => {
11100
+ if (typeof mw === "string") {
11101
+ const name = mw.split(":")[0];
11102
+ return options.only.includes(name);
11103
+ }
11104
+ return false;
11105
+ });
11106
+ }
11107
+ if (options.except && options.except.length > 0) {
11108
+ return middleware2.filter((mw) => {
11109
+ if (typeof mw === "string") {
11110
+ const name = mw.split(":")[0];
11111
+ return !options.except.includes(name);
11112
+ }
11113
+ return true;
11114
+ });
11115
+ }
11116
+ return middleware2;
11117
+ }
11118
+ flush() {
11119
+ this.groups.clear();
11120
+ this.globalMiddleware = [];
11121
+ this.middlewarePriority.clear();
11122
+ }
11123
+ }
11124
+ var middlewareGroups = new MiddlewareGroupRegistry;
11125
+ middlewareGroups.define("web", ["session", "csrf", "cors"]);
11126
+ middlewareGroups.define("api", ["throttle:60,1", "cors"]);
11127
+ middlewareGroups.setPriority("session", 10);
11128
+ middlewareGroups.setPriority("cors", 20);
11129
+ middlewareGroups.setPriority("csrf", 30);
11130
+ middlewareGroups.setPriority("auth", 40);
11131
+ middlewareGroups.setPriority("throttle", 50);
11132
+ middlewareGroups.setPriority("role", 60);
11133
+ middlewareGroups.setPriority("permission", 60);
11134
+ middlewareGroups.setPriority("can", 70);
11135
+ middlewareGroups.setPriority("abilities", 70);
11136
+ middlewareGroups.setPriority("verified", 80);
9275
11137
  // src/testing/auth-testing.ts
9276
11138
  import { mock } from "bun:test";
9277
11139
 
@@ -9490,8 +11352,8 @@ function createCookieAccessor2(cookies) {
9490
11352
 
9491
11353
  class AuthTester {
9492
11354
  request;
9493
- constructor(request) {
9494
- this.request = request || createMockRequest();
11355
+ constructor(request2) {
11356
+ this.request = request2 || createMockRequest();
9495
11357
  }
9496
11358
  as(user) {
9497
11359
  this.request.user = user;
@@ -9719,16 +11581,16 @@ var authMocks = {
9719
11581
  })
9720
11582
  };
9721
11583
  var authHelpers = {
9722
- authenticatedRequest: (user, request) => {
9723
- return new AuthTester(request).as(user).getRequest();
11584
+ authenticatedRequest: (user, request2) => {
11585
+ return new AuthTester(request2).as(user).getRequest();
9724
11586
  },
9725
- jwtRequest: (payload = {}, request) => {
11587
+ jwtRequest: (payload = {}, request2) => {
9726
11588
  const jwt = new JWTTester;
9727
11589
  const token = jwt.generateToken(payload);
9728
- return new AuthTester(request).withJWT(token).getRequest();
11590
+ return new AuthTester(request2).withJWT(token).getRequest();
9729
11591
  },
9730
- sessionRequest: (sessionData = {}, request) => {
9731
- return new AuthTester(request).withSession(sessionData).getRequest();
11592
+ sessionRequest: (sessionData = {}, request2) => {
11593
+ return new AuthTester(request2).withSession(sessionData).getRequest();
9732
11594
  },
9733
11595
  verifyJWTStructure: (token) => {
9734
11596
  const parts = token.split(".");
@@ -9743,8 +11605,8 @@ var authHelpers = {
9743
11605
  }
9744
11606
  }
9745
11607
  };
9746
- function createAuthTester(request) {
9747
- return new AuthTester(request);
11608
+ function createAuthTester(request2) {
11609
+ return new AuthTester(request2);
9748
11610
  }
9749
11611
  function createJWTTester(options) {
9750
11612
  return new JWTTester(options);
@@ -9752,14 +11614,13 @@ function createJWTTester(options) {
9752
11614
  function createSessionTester(options) {
9753
11615
  return new SessionTester(options);
9754
11616
  }
9755
-
9756
11617
  // src/testing/file-upload-testing.ts
9757
11618
  import { mock as mock2 } from "bun:test";
9758
11619
  class FileUploadTester {
9759
11620
  request;
9760
11621
  files;
9761
- constructor(request) {
9762
- this.request = request || createMockRequest();
11622
+ constructor(request2) {
11623
+ this.request = request2 || createMockRequest();
9763
11624
  this.files = [];
9764
11625
  }
9765
11626
  addFile(testFile) {
@@ -9991,8 +11852,8 @@ var fileUploadScenarios = {
9991
11852
  }
9992
11853
  };
9993
11854
  var fileUploadAssertions = {
9994
- hasFiles: (request, count) => {
9995
- const files = request.files || [];
11855
+ hasFiles: (request2, count) => {
11856
+ const files = request2.files || [];
9996
11857
  if (count !== undefined && files.length !== count) {
9997
11858
  throw new Error(`Expected ${count} files, but got ${files.length}`);
9998
11859
  }
@@ -10000,8 +11861,8 @@ var fileUploadAssertions = {
10000
11861
  throw new Error("Expected request to have files");
10001
11862
  }
10002
11863
  },
10003
- hasFileWithField: (request, fieldName) => {
10004
- const files = request.files || [];
11864
+ hasFileWithField: (request2, fieldName) => {
11865
+ const files = request2.files || [];
10005
11866
  const hasField = files.some((file) => file.fieldName === fieldName);
10006
11867
  if (!hasField) {
10007
11868
  throw new Error(`Expected file with field name '${fieldName}'`);
@@ -10025,8 +11886,8 @@ var fileUploadAssertions = {
10025
11886
  }
10026
11887
  }
10027
11888
  };
10028
- function createFileUploadTester(request) {
10029
- return new FileUploadTester(request);
11889
+ function createFileUploadTester(request2) {
11890
+ return new FileUploadTester(request2);
10030
11891
  }
10031
11892
  var testFiles = {
10032
11893
  image: (fieldName = "image", filename = "test.jpg") => ({
@@ -10060,7 +11921,6 @@ var testFiles = {
10060
11921
  mimetype: "application/x-executable"
10061
11922
  })
10062
11923
  };
10063
-
10064
11924
  // src/testing/middleware-testing.ts
10065
11925
  import { mock as mock3, spyOn } from "bun:test";
10066
11926
  class MiddlewareTester {
@@ -10071,15 +11931,15 @@ class MiddlewareTester {
10071
11931
  this.context = this.createContext();
10072
11932
  }
10073
11933
  createContext() {
10074
- const request = createMockRequest();
11934
+ const request2 = createMockRequest();
10075
11935
  const next = mock3(() => Promise.resolve(new Response("Next called", { status: 200 })));
10076
11936
  return {
10077
- request,
11937
+ request: request2,
10078
11938
  next
10079
11939
  };
10080
11940
  }
10081
- withRequest(request) {
10082
- this.context.request = request;
11941
+ withRequest(request2) {
11942
+ this.context.request = request2;
10083
11943
  return this;
10084
11944
  }
10085
11945
  withNext(nextFn) {
@@ -10177,8 +12037,8 @@ class MiddlewareChainTester {
10177
12037
  this.request = createMockRequest();
10178
12038
  this.finalHandler = mock3(async () => new Response("Final handler", { status: 200 }));
10179
12039
  }
10180
- withRequest(request) {
10181
- this.request = request;
12040
+ withRequest(request2) {
12041
+ this.request = request2;
10182
12042
  return this;
10183
12043
  }
10184
12044
  withFinalHandler(handler) {
@@ -10218,11 +12078,11 @@ function createMiddlewareSpy() {
10218
12078
  }
10219
12079
  async function benchmarkMiddleware(middleware2, iterations = 1000) {
10220
12080
  const times = [];
10221
- const request = createMockRequest();
12081
+ const request2 = createMockRequest();
10222
12082
  const next = mock3(async () => new Response("OK"));
10223
12083
  for (let i = 0;i < iterations; i++) {
10224
12084
  const start = performance.now();
10225
- await middleware2(request, next);
12085
+ await middleware2(request2, next);
10226
12086
  const end = performance.now();
10227
12087
  times.push(end - start);
10228
12088
  }
@@ -10237,7 +12097,6 @@ async function benchmarkMiddleware(middleware2, iterations = 1000) {
10237
12097
  totalTime
10238
12098
  };
10239
12099
  }
10240
-
10241
12100
  // src/testing/model-binding-testing.ts
10242
12101
  import { mock as mock4 } from "bun:test";
10243
12102
  class GenericMockModel {
@@ -10321,8 +12180,8 @@ class ModelBindingTester {
10321
12180
  this.options = options;
10322
12181
  this.request = createMockRequest();
10323
12182
  }
10324
- withRequest(request) {
10325
- this.request = request;
12183
+ withRequest(request2) {
12184
+ this.request = request2;
10326
12185
  return this;
10327
12186
  }
10328
12187
  withParams(params) {
@@ -10460,18 +12319,18 @@ var modelMocks = {
10460
12319
  };
10461
12320
  var constraintHelpers = {
10462
12321
  belongsTo: (userIdField = "userId") => {
10463
- const fn = async (model, request) => {
10464
- if (!request.user)
12322
+ const fn = async (model, request2) => {
12323
+ if (!request2.user)
10465
12324
  return false;
10466
- return model[userIdField] === request.user.id;
12325
+ return model[userIdField] === request2.user.id;
10467
12326
  };
10468
12327
  return mock4(fn);
10469
12328
  },
10470
12329
  belongsToUser: () => {
10471
- const fn = async (model, request) => {
10472
- if (!request.user)
12330
+ const fn = async (model, request2) => {
12331
+ if (!request2.user)
10473
12332
  return false;
10474
- return model.userId === request.user.id;
12333
+ return model.userId === request2.user.id;
10475
12334
  };
10476
12335
  return mock4(fn);
10477
12336
  },
@@ -10491,9 +12350,9 @@ var constraintHelpers = {
10491
12350
  return mock4(constraintFn);
10492
12351
  },
10493
12352
  combine: (...constraints) => {
10494
- const fn = async (model, request) => {
12353
+ const fn = async (model, request2) => {
10495
12354
  for (const constraint of constraints) {
10496
- if (!await constraint(model, request)) {
12355
+ if (!await constraint(model, request2)) {
10497
12356
  return false;
10498
12357
  }
10499
12358
  }
@@ -10550,24 +12409,24 @@ class ModelResolverTester {
10550
12409
  }
10551
12410
  var routeModelHelpers = {
10552
12411
  testRoute: async (path, params, modelBindings) => {
10553
- const request = createMockRequest("GET", path);
10554
- request.params = params;
12412
+ const request2 = createMockRequest("GET", path);
12413
+ request2.params = params;
10555
12414
  for (const [key, model] of Object.entries(modelBindings)) {
10556
- request.context = request.context || {};
10557
- request.context[key] = model;
12415
+ request2.context = request2.context || {};
12416
+ request2.context[key] = model;
10558
12417
  }
10559
- return request;
12418
+ return request2;
10560
12419
  },
10561
12420
  createHandler: (expectedModels) => {
10562
- const fn = async (request) => {
10563
- const context = request.context || {};
12421
+ const fn = async (request2) => {
12422
+ const context = request2.context || {};
10564
12423
  for (const modelKey of expectedModels) {
10565
12424
  if (!context[modelKey]) {
10566
12425
  return new Response(`Model ${modelKey} not found`, { status: 404 });
10567
12426
  }
10568
12427
  }
10569
12428
  return new Response(JSON.stringify({
10570
- params: request.params,
12429
+ params: request2.params,
10571
12430
  models: Object.fromEntries(expectedModels.map((key) => [key, context[key]]))
10572
12431
  }), {
10573
12432
  headers: { "Content-Type": "application/json" }
@@ -10576,16 +12435,16 @@ var routeModelHelpers = {
10576
12435
  return mock4(fn);
10577
12436
  },
10578
12437
  testBindingMiddleware: (bindings) => {
10579
- const fn = async (request, next) => {
10580
- request.context = request.context || {};
12438
+ const fn = async (request2, next) => {
12439
+ request2.context = request2.context || {};
10581
12440
  for (const [key, resolver] of Object.entries(bindings)) {
10582
- const paramValue = request.params[key];
12441
+ const paramValue = request2.params[key];
10583
12442
  if (paramValue) {
10584
12443
  try {
10585
12444
  if (typeof resolver === "function") {
10586
- request.context[key] = await resolver(paramValue);
12445
+ request2.context[key] = await resolver(paramValue);
10587
12446
  } else {
10588
- request.context[key] = resolver;
12447
+ request2.context[key] = resolver;
10589
12448
  }
10590
12449
  } catch (error) {
10591
12450
  console.error(`Error resolving model ${key}:`, error);
@@ -10620,10 +12479,9 @@ var scopedBindingHelpers = {
10620
12479
  return requiredPermissions.every((permission) => user.permissions.includes(permission));
10621
12480
  }
10622
12481
  };
10623
-
10624
12482
  // src/testing/performance-testing.ts
10625
12483
  import { mock as mock5 } from "bun:test";
10626
- import process4 from "process";
12484
+ import process5 from "process";
10627
12485
  class PerformanceTester {
10628
12486
  metrics = [];
10629
12487
  startTime = 0;
@@ -10742,10 +12600,10 @@ class LoadTester {
10742
12600
  var benchmarkTests = {
10743
12601
  routeHandler: async (handler, iterations = 1000) => {
10744
12602
  const times = [];
10745
- const request = createMockRequest();
12603
+ const request2 = createMockRequest();
10746
12604
  for (let i = 0;i < iterations; i++) {
10747
12605
  const start = performance.now();
10748
- await handler(request);
12606
+ await handler(request2);
10749
12607
  const end = performance.now();
10750
12608
  times.push(end - start);
10751
12609
  }
@@ -10764,11 +12622,11 @@ var benchmarkTests = {
10764
12622
  },
10765
12623
  middleware: async (middleware2, iterations = 1000) => {
10766
12624
  const times = [];
10767
- const request = createMockRequest();
12625
+ const request2 = createMockRequest();
10768
12626
  const next = mock5(async () => new Response("OK"));
10769
12627
  for (let i = 0;i < iterations; i++) {
10770
12628
  const start = performance.now();
10771
- await middleware2(request, next);
12629
+ await middleware2(request2, next);
10772
12630
  const end = performance.now();
10773
12631
  times.push(end - start);
10774
12632
  }
@@ -10841,11 +12699,11 @@ var memoryTests = {
10841
12699
  if (globalThis.gc) {
10842
12700
  globalThis.gc();
10843
12701
  }
10844
- const initialHeap = process4.memoryUsage().heapUsed;
12702
+ const initialHeap = process5.memoryUsage().heapUsed;
10845
12703
  for (let i = 0;i < iterations; i++) {
10846
12704
  const result = await fn();
10847
12705
  results.push(result);
10848
- const memUsage = process4.memoryUsage();
12706
+ const memUsage = process5.memoryUsage();
10849
12707
  heapUsages.push(memUsage.heapUsed);
10850
12708
  if (i % 10 === 0 && globalThis.gc) {
10851
12709
  globalThis.gc();
@@ -10854,7 +12712,7 @@ var memoryTests = {
10854
12712
  if (globalThis.gc) {
10855
12713
  globalThis.gc();
10856
12714
  }
10857
- const finalHeap = process4.memoryUsage().heapUsed;
12715
+ const finalHeap = process5.memoryUsage().heapUsed;
10858
12716
  const averageHeapUsed = heapUsages.reduce((sum, usage) => sum + usage, 0) / heapUsages.length;
10859
12717
  const maxHeapUsed = Math.max(...heapUsages);
10860
12718
  const memoryLeakDetected = finalHeap - initialHeap > 1024 * 1024;
@@ -10868,17 +12726,17 @@ var memoryTests = {
10868
12726
  testRouteHandlerMemoryLeaks: async (handler, iterations = 1000) => {
10869
12727
  if (globalThis.gc)
10870
12728
  globalThis.gc();
10871
- const initialMemory = process4.memoryUsage().heapUsed;
12729
+ const initialMemory = process5.memoryUsage().heapUsed;
10872
12730
  for (let i = 0;i < iterations; i++) {
10873
- const request = createMockRequest();
10874
- await handler(request);
12731
+ const request2 = createMockRequest();
12732
+ await handler(request2);
10875
12733
  if (i % 100 === 0 && globalThis.gc) {
10876
12734
  globalThis.gc();
10877
12735
  }
10878
12736
  }
10879
12737
  if (globalThis.gc)
10880
12738
  globalThis.gc();
10881
- const finalMemory = process4.memoryUsage().heapUsed;
12739
+ const finalMemory = process5.memoryUsage().heapUsed;
10882
12740
  const memoryGrowth = finalMemory - initialMemory;
10883
12741
  const memoryLeakDetected = memoryGrowth > 5 * 1024 * 1024;
10884
12742
  return {
@@ -10970,7 +12828,7 @@ function createLoadTester(client, config2) {
10970
12828
  }
10971
12829
  var performanceUtils = {
10972
12830
  getCurrentMemoryUsage: () => {
10973
- const usage = process4.memoryUsage();
12831
+ const usage = process5.memoryUsage();
10974
12832
  return {
10975
12833
  heapUsed: usage.heapUsed,
10976
12834
  heapTotal: usage.heapTotal,
@@ -10979,7 +12837,7 @@ var performanceUtils = {
10979
12837
  };
10980
12838
  },
10981
12839
  getCPUUsage: () => {
10982
- const usage = process4.cpuUsage();
12840
+ const usage = process5.cpuUsage();
10983
12841
  return {
10984
12842
  user: usage.user,
10985
12843
  system: usage.system
@@ -10994,7 +12852,6 @@ var performanceUtils = {
10994
12852
  return JSON.stringify(results, null, 2);
10995
12853
  }
10996
12854
  };
10997
-
10998
12855
  // src/testing/test-response.ts
10999
12856
  class TestResponseWrapper {
11000
12857
  status;
@@ -11115,14 +12972,14 @@ class TestResponseWrapper {
11115
12972
  }
11116
12973
  return this;
11117
12974
  }
11118
- expectRedirect(url) {
12975
+ expectRedirect(url2) {
11119
12976
  if (this.status < 300 || this.status >= 400) {
11120
12977
  throw new Error(`Expected redirect status (3xx), got ${this.status}`);
11121
12978
  }
11122
- if (url) {
12979
+ if (url2) {
11123
12980
  const location = this.headers.location;
11124
- if (location !== url) {
11125
- throw new Error(`Expected redirect to '${url}', got '${location}'`);
12981
+ if (location !== url2) {
12982
+ throw new Error(`Expected redirect to '${url2}', got '${location}'`);
11126
12983
  }
11127
12984
  }
11128
12985
  return this;
@@ -11265,24 +13122,24 @@ class TestClient {
11265
13122
  async request(method, path, options = {}) {
11266
13123
  const headers = { ...this.defaultHeaders, ...options.headers };
11267
13124
  const requestOptions = { ...options, headers };
11268
- const request = new TestRequestBuilder(method, path, requestOptions).build();
13125
+ const request2 = new TestRequestBuilder(method, path, requestOptions).build();
11269
13126
  try {
11270
- const response2 = await this.handleRequest(request);
13127
+ const response2 = await this.handleRequest(request2);
11271
13128
  return await createTestResponse(response2);
11272
13129
  } catch (error) {
11273
13130
  throw new Error(`Test request failed: ${error instanceof Error ? error.message : "Unknown error"}`);
11274
13131
  }
11275
13132
  }
11276
- async handleRequest(request) {
11277
- const url = new URL(request.url);
11278
- const matchResult = this.findMatchingRoute(request.method, url.pathname);
13133
+ async handleRequest(request2) {
13134
+ const url2 = new URL(request2.url);
13135
+ const matchResult = this.findMatchingRoute(request2.method, url2.pathname);
11279
13136
  if (!matchResult) {
11280
13137
  return new Response("Not Found", { status: 404 });
11281
13138
  }
11282
13139
  const { route, params } = matchResult;
11283
- request.params = { ...request.params, ...params };
13140
+ request2.params = { ...request2.params, ...params };
11284
13141
  try {
11285
- const response2 = await this.executeMiddlewareChain(request, route);
13142
+ const response2 = await this.executeMiddlewareChain(request2, route);
11286
13143
  return response2 || new Response("No response", { status: 500 });
11287
13144
  } catch (error) {
11288
13145
  return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Internal Server Error" }), {
@@ -11322,28 +13179,28 @@ class TestClient {
11322
13179
  }
11323
13180
  return params;
11324
13181
  }
11325
- async executeMiddlewareChain(request, route) {
13182
+ async executeMiddlewareChain(request2, route) {
11326
13183
  const middlewares = [...this.router.globalMiddleware, ...route.middleware];
11327
13184
  let index = 0;
11328
13185
  const next = async () => {
11329
13186
  if (index < middlewares.length) {
11330
13187
  const middleware2 = middlewares[index++];
11331
- return await middleware2(request, next);
13188
+ return await middleware2(request2, next);
11332
13189
  } else {
11333
- return await this.executeHandler(route.handler, request);
13190
+ return await this.executeHandler(route.handler, request2);
11334
13191
  }
11335
13192
  };
11336
13193
  const result = await next();
11337
13194
  return result || new Response("No response", { status: 500 });
11338
13195
  }
11339
- async executeHandler(handler, request) {
13196
+ async executeHandler(handler, request2) {
11340
13197
  if (typeof handler === "function") {
11341
- return await handler(request);
13198
+ return await handler(request2);
11342
13199
  } else if (typeof handler === "string") {
11343
13200
  throw new TypeError(`String handlers not supported in test environment: ${handler}`);
11344
13201
  } else if (typeof handler === "object" && handler.handle) {
11345
13202
  const instance = new handler;
11346
- return await instance.handle(request);
13203
+ return await instance.handle(request2);
11347
13204
  }
11348
13205
  throw new Error("Invalid handler type");
11349
13206
  }
@@ -11384,7 +13241,6 @@ var testClient = {
11384
13241
  withAuth: (token, type = "Bearer") => (router2) => createTestClient(router2).auth(token, type),
11385
13242
  withHeaders: (headers) => (router2) => createTestClient(router2).withHeaders(headers)
11386
13243
  };
11387
-
11388
13244
  // src/testing/websocket-testing.ts
11389
13245
  import { mock as mock6 } from "bun:test";
11390
13246
 
@@ -11739,13 +13595,13 @@ export {
11739
13595
  spyOnMiddleware,
11740
13596
  singleFileUpload,
11741
13597
  signRequest,
11742
- setCurrentRequest2 as setCurrentRequest,
13598
+ setCurrentRequest,
11743
13599
  setContainer,
11744
13600
  session,
11745
13601
  securitySuite,
11746
13602
  security,
11747
13603
  scopedBindingHelpers,
11748
- runWithRequest2 as runWithRequest,
13604
+ runWithRequest,
11749
13605
  rule,
11750
13606
  router,
11751
13607
  routeModelHelpers,
@@ -11758,7 +13614,7 @@ export {
11758
13614
  requestSigning,
11759
13615
  requestId,
11760
13616
  request,
11761
- registerNamedRoute2 as registerNamedRoute,
13617
+ registerNamedRoute,
11762
13618
  rateLimitRegistry,
11763
13619
  rateLimit,
11764
13620
  processHtmlTemplate,
@@ -11788,7 +13644,7 @@ export {
11788
13644
  globalValidator,
11789
13645
  globalMiddlewarePipeline,
11790
13646
  getNamedRoutes,
11791
- getNamedRoutePath2 as getNamedRoutePath,
13647
+ getNamedRoutePath,
11792
13648
  getCurrentRequest,
11793
13649
  getContainer,
11794
13650
  getConfig,
@@ -11919,7 +13775,7 @@ export {
11919
13775
  FileUpload,
11920
13776
  FileSessionStore,
11921
13777
  FileSecurity,
11922
- Errors,
13778
+ exports_errors as Errors,
11923
13779
  EnvironmentManager,
11924
13780
  EnhancementPresets,
11925
13781
  DomainMatcher,
@@ -11939,6 +13795,6 @@ export {
11939
13795
  BuiltInRules,
11940
13796
  BindingBuilder,
11941
13797
  AuthTester,
11942
- default2 as Auth,
13798
+ auth_default as Auth,
11943
13799
  ApiKeyManager
11944
13800
  };