@stacksjs/bun-router 0.0.19 → 0.0.20

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 = {
@@ -7103,7 +8866,7 @@ class Router {
7103
8866
  }
7104
8867
  return this;
7105
8868
  }
7106
- async executeMiddleware(middleware2, request, handler) {
8869
+ async executeMiddleware(middleware2, request2, handler) {
7107
8870
  if (middleware2.length === 0) {
7108
8871
  return handler();
7109
8872
  }
@@ -7113,7 +8876,7 @@ class Router {
7113
8876
  return handler();
7114
8877
  }
7115
8878
  const mw = middleware2[currentIndex++];
7116
- const result = await mw(request, next);
8879
+ const result = await mw(request2, next);
7117
8880
  return result || new Response("No response from middleware", { status: 500 });
7118
8881
  };
7119
8882
  return next();
@@ -7275,16 +9038,16 @@ class RequestWithMacros {
7275
9038
  static macroAccessor(name, getter) {
7276
9039
  requestMacroRegistry.register(name, getter, true);
7277
9040
  }
7278
- static applyMacros(request) {
9041
+ static applyMacros(request2) {
7279
9042
  const epoch = requestMacroRegistry.getEpoch();
7280
9043
  if (epoch !== macroProtoEpoch) {
7281
9044
  macroProtoCache = new WeakMap;
7282
9045
  macroProtoEpoch = epoch;
7283
9046
  }
7284
- let base = Object.getPrototypeOf(request) ?? Object.prototype;
9047
+ let base = Object.getPrototypeOf(request2) ?? Object.prototype;
7285
9048
  if (base[MACRO_PROTO]) {
7286
9049
  if (macroProtoCache.get(base[MACRO_BASE]) === base) {
7287
- return request;
9050
+ return request2;
7288
9051
  }
7289
9052
  base = base[MACRO_BASE];
7290
9053
  }
@@ -7313,8 +9076,8 @@ class RequestWithMacros {
7313
9076
  Object.defineProperty(proto, MACRO_BASE, { value: base });
7314
9077
  macroProtoCache.set(base, proto);
7315
9078
  }
7316
- Object.setPrototypeOf(request, proto);
7317
- return request;
9079
+ Object.setPrototypeOf(request2, proto);
9080
+ return request2;
7318
9081
  }
7319
9082
  static hasMacro(name) {
7320
9083
  return requestMacroRegistry.has(name);
@@ -7508,8 +9271,8 @@ var BuiltInRequestMacros = {
7508
9271
  return this.url;
7509
9272
  },
7510
9273
  root() {
7511
- const url = getParsedURL(this);
7512
- return `${url.protocol}//${url.host}`;
9274
+ const url2 = getParsedURL(this);
9275
+ return `${url2.protocol}//${url2.host}`;
7513
9276
  },
7514
9277
  is(pattern) {
7515
9278
  const path = this.path();
@@ -7528,11 +9291,11 @@ var BuiltInRequestMacros = {
7528
9291
  return this.routeName || null;
7529
9292
  },
7530
9293
  fingerprint() {
7531
- const url = getParsedURL(this);
9294
+ const url2 = getParsedURL(this);
7532
9295
  const data = {
7533
9296
  method: this.method,
7534
- path: url.pathname,
7535
- query: url.search,
9297
+ path: url2.pathname,
9298
+ query: url2.search,
7536
9299
  ip: BuiltInRequestMacros.ip.call(this)
7537
9300
  };
7538
9301
  return btoa(JSON.stringify(data));
@@ -7583,36 +9346,6 @@ function registerBuiltInRequestMacros() {
7583
9346
  }
7584
9347
  registerBuiltInRequestMacros();
7585
9348
 
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
9349
  // src/router/server.ts
7617
9350
  if (!RequestWithMacros.hasMacro("getParam")) {
7618
9351
  RequestWithMacros.macro("getParam", function(name, defaultValue) {
@@ -7898,10 +9631,10 @@ function registerServerHandling(RouterClass) {
7898
9631
  handleRequestImpl: {
7899
9632
  async value(req) {
7900
9633
  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);
9634
+ const url2 = new URL(req.url);
9635
+ req._parsedURL = url2;
9636
+ const hostname = url2.hostname || req.headers.get("host")?.split(":")[0] || "localhost";
9637
+ const match = this.matchRoute(url2.pathname, req.method, hostname);
7905
9638
  if (req.method === "OPTIONS" && !match) {
7906
9639
  const origin = req.headers.get("origin");
7907
9640
  const preflightHeaders = {
@@ -7921,7 +9654,7 @@ function registerServerHandling(RouterClass) {
7921
9654
  }
7922
9655
  const enhancedReq = this.enhanceRequest(req, {});
7923
9656
  setCurrentRequest(enhancedReq);
7924
- const allowedMethods = this.getAllowedMethods(url.pathname, hostname);
9657
+ const allowedMethods = this.getAllowedMethods(url2.pathname, hostname);
7925
9658
  const corsHeaders = {
7926
9659
  "Content-Type": "application/json",
7927
9660
  "Access-Control-Allow-Origin": "*",
@@ -7933,7 +9666,7 @@ function registerServerHandling(RouterClass) {
7933
9666
  return new Response(JSON.stringify({
7934
9667
  success: false,
7935
9668
  message: "Method Not Allowed",
7936
- path: url.pathname,
9669
+ path: url2.pathname,
7937
9670
  method: req.method,
7938
9671
  allowed: allowedMethods
7939
9672
  }), {
@@ -7957,7 +9690,7 @@ function registerServerHandling(RouterClass) {
7957
9690
  return new Response(JSON.stringify({
7958
9691
  success: false,
7959
9692
  message: "Not Found",
7960
- path: url.pathname,
9693
+ path: url2.pathname,
7961
9694
  method: req.method
7962
9695
  }), { status: 404, headers: corsHeaders });
7963
9696
  };
@@ -8252,34 +9985,34 @@ function registerWebSocketHandling(RouterClass) {
8252
9985
  configurable: true
8253
9986
  },
8254
9987
  upgrade: {
8255
- value(request, options) {
9988
+ value(request2, options) {
8256
9989
  if (!this.serverInstance) {
8257
9990
  throw new Error("Server not started, cannot upgrade to WebSocket");
8258
9991
  }
8259
9992
  if (!this.wsConfig) {
8260
9993
  throw new Error("WebSocket configuration not set, use router.websocket() to configure");
8261
9994
  }
8262
- return this.serverInstance.upgrade(request, options);
9995
+ return this.serverInstance.upgrade(request2, options);
8263
9996
  },
8264
9997
  writable: true,
8265
9998
  configurable: true
8266
9999
  },
8267
10000
  timeout: {
8268
- value(request, seconds) {
10001
+ value(request2, seconds) {
8269
10002
  if (!this.serverInstance) {
8270
10003
  throw new Error("Server not started, cannot set timeout");
8271
10004
  }
8272
- this.serverInstance.timeout(request, seconds);
10005
+ this.serverInstance.timeout(request2, seconds);
8273
10006
  },
8274
10007
  writable: true,
8275
10008
  configurable: true
8276
10009
  },
8277
10010
  requestIP: {
8278
- value(request) {
10011
+ value(request2) {
8279
10012
  if (!this.serverInstance) {
8280
10013
  throw new Error("Server not started, cannot get request IP");
8281
10014
  }
8282
- return this.serverInstance.requestIP(request);
10015
+ return this.serverInstance.requestIP(request2);
8283
10016
  },
8284
10017
  writable: true,
8285
10018
  configurable: true
@@ -8287,7 +10020,7 @@ function registerWebSocketHandling(RouterClass) {
8287
10020
  });
8288
10021
  }
8289
10022
  // src/response/macros.ts
8290
- import process3 from "process";
10023
+ import process4 from "process";
8291
10024
 
8292
10025
  class ResponseMacroRegistry {
8293
10026
  macros = new Map;
@@ -8468,16 +10201,16 @@ var BuiltInResponseMacros = {
8468
10201
  }
8469
10202
  });
8470
10203
  },
8471
- redirect: (url, status = 302) => {
10204
+ redirect: (url2, status = 302) => {
8472
10205
  return new Response(null, {
8473
10206
  status,
8474
10207
  headers: {
8475
- Location: url
10208
+ Location: url2
8476
10209
  }
8477
10210
  });
8478
10211
  },
8479
- permanentRedirect: (url) => {
8480
- return BuiltInResponseMacros.redirect(url, 301);
10212
+ permanentRedirect: (url2) => {
10213
+ return BuiltInResponseMacros.redirect(url2, 301);
8481
10214
  },
8482
10215
  download: (data, filename, contentType = "application/octet-stream") => {
8483
10216
  return new Response(data, {
@@ -8548,7 +10281,7 @@ var BuiltInResponseMacros = {
8548
10281
  status,
8549
10282
  timestamp: new Date().toISOString(),
8550
10283
  checks,
8551
- uptime: process3.uptime?.() || 0
10284
+ uptime: process4.uptime?.() || 0
8552
10285
  };
8553
10286
  return new Response(JSON.stringify(response2), {
8554
10287
  status: statusCode,
@@ -8612,8 +10345,8 @@ var BuiltInRules = {
8612
10345
  name: "url",
8613
10346
  validate: (value) => {
8614
10347
  try {
8615
- const url = new URL(value);
8616
- return Boolean(url);
10348
+ const url2 = new URL(value);
10349
+ return Boolean(url2);
8617
10350
  } catch {
8618
10351
  return false;
8619
10352
  }
@@ -9272,6 +11005,119 @@ registerRouteBuilding(Router);
9272
11005
  registerModelBinding(Router);
9273
11006
  registerFileBasedRouting(Router);
9274
11007
  registerApiRoutes(Router);
11008
+ // src/router/middleware-groups.ts
11009
+ class MiddlewareGroupRegistry {
11010
+ groups = new Map;
11011
+ globalMiddleware = [];
11012
+ middlewarePriority = new Map;
11013
+ define(name, middleware2) {
11014
+ this.groups.set(name, [...middleware2]);
11015
+ return this;
11016
+ }
11017
+ extend(name, middleware2) {
11018
+ const existing = this.groups.get(name) || [];
11019
+ this.groups.set(name, [...existing, ...middleware2]);
11020
+ return this;
11021
+ }
11022
+ prepend(name, middleware2) {
11023
+ const existing = this.groups.get(name) || [];
11024
+ this.groups.set(name, [...middleware2, ...existing]);
11025
+ return this;
11026
+ }
11027
+ get(name) {
11028
+ return this.groups.get(name) || [];
11029
+ }
11030
+ has(name) {
11031
+ return this.groups.has(name);
11032
+ }
11033
+ remove(name) {
11034
+ this.groups.delete(name);
11035
+ return this;
11036
+ }
11037
+ resolve(middleware2) {
11038
+ if (typeof middleware2 === "function") {
11039
+ return [middleware2];
11040
+ }
11041
+ if (this.groups.has(middleware2)) {
11042
+ return this.get(middleware2);
11043
+ }
11044
+ return [middleware2];
11045
+ }
11046
+ resolveAll(middleware2) {
11047
+ const resolved = [];
11048
+ for (const mw of middleware2) {
11049
+ resolved.push(...this.resolve(mw));
11050
+ }
11051
+ return resolved;
11052
+ }
11053
+ pushGlobal(...middleware2) {
11054
+ this.globalMiddleware.push(...middleware2);
11055
+ return this;
11056
+ }
11057
+ prependGlobal(...middleware2) {
11058
+ this.globalMiddleware.unshift(...middleware2);
11059
+ return this;
11060
+ }
11061
+ removeGlobal(middleware2) {
11062
+ this.globalMiddleware = this.globalMiddleware.filter((mw) => typeof mw === "string" ? mw !== middleware2 : true);
11063
+ return this;
11064
+ }
11065
+ getGlobal() {
11066
+ return [...this.globalMiddleware];
11067
+ }
11068
+ setPriority(name, priority) {
11069
+ this.middlewarePriority.set(name, priority);
11070
+ return this;
11071
+ }
11072
+ sortByPriority(middleware2) {
11073
+ return [...middleware2].sort((a, b) => {
11074
+ const nameA = typeof a === "string" ? a.split(":")[0] : "";
11075
+ const nameB = typeof b === "string" ? b.split(":")[0] : "";
11076
+ const priorityA = this.middlewarePriority.get(nameA) ?? 100;
11077
+ const priorityB = this.middlewarePriority.get(nameB) ?? 100;
11078
+ return priorityA - priorityB;
11079
+ });
11080
+ }
11081
+ filter(middleware2, options) {
11082
+ if (options.only && options.only.length > 0) {
11083
+ return middleware2.filter((mw) => {
11084
+ if (typeof mw === "string") {
11085
+ const name = mw.split(":")[0];
11086
+ return options.only.includes(name);
11087
+ }
11088
+ return false;
11089
+ });
11090
+ }
11091
+ if (options.except && options.except.length > 0) {
11092
+ return middleware2.filter((mw) => {
11093
+ if (typeof mw === "string") {
11094
+ const name = mw.split(":")[0];
11095
+ return !options.except.includes(name);
11096
+ }
11097
+ return true;
11098
+ });
11099
+ }
11100
+ return middleware2;
11101
+ }
11102
+ flush() {
11103
+ this.groups.clear();
11104
+ this.globalMiddleware = [];
11105
+ this.middlewarePriority.clear();
11106
+ }
11107
+ }
11108
+ var middlewareGroups = new MiddlewareGroupRegistry;
11109
+ middlewareGroups.define("web", ["session", "csrf", "cors"]);
11110
+ middlewareGroups.define("api", ["throttle:60,1", "cors"]);
11111
+ middlewareGroups.setPriority("session", 10);
11112
+ middlewareGroups.setPriority("cors", 20);
11113
+ middlewareGroups.setPriority("csrf", 30);
11114
+ middlewareGroups.setPriority("auth", 40);
11115
+ middlewareGroups.setPriority("throttle", 50);
11116
+ middlewareGroups.setPriority("role", 60);
11117
+ middlewareGroups.setPriority("permission", 60);
11118
+ middlewareGroups.setPriority("can", 70);
11119
+ middlewareGroups.setPriority("abilities", 70);
11120
+ middlewareGroups.setPriority("verified", 80);
9275
11121
  // src/testing/auth-testing.ts
9276
11122
  import { mock } from "bun:test";
9277
11123
 
@@ -9490,8 +11336,8 @@ function createCookieAccessor2(cookies) {
9490
11336
 
9491
11337
  class AuthTester {
9492
11338
  request;
9493
- constructor(request) {
9494
- this.request = request || createMockRequest();
11339
+ constructor(request2) {
11340
+ this.request = request2 || createMockRequest();
9495
11341
  }
9496
11342
  as(user) {
9497
11343
  this.request.user = user;
@@ -9719,16 +11565,16 @@ var authMocks = {
9719
11565
  })
9720
11566
  };
9721
11567
  var authHelpers = {
9722
- authenticatedRequest: (user, request) => {
9723
- return new AuthTester(request).as(user).getRequest();
11568
+ authenticatedRequest: (user, request2) => {
11569
+ return new AuthTester(request2).as(user).getRequest();
9724
11570
  },
9725
- jwtRequest: (payload = {}, request) => {
11571
+ jwtRequest: (payload = {}, request2) => {
9726
11572
  const jwt = new JWTTester;
9727
11573
  const token = jwt.generateToken(payload);
9728
- return new AuthTester(request).withJWT(token).getRequest();
11574
+ return new AuthTester(request2).withJWT(token).getRequest();
9729
11575
  },
9730
- sessionRequest: (sessionData = {}, request) => {
9731
- return new AuthTester(request).withSession(sessionData).getRequest();
11576
+ sessionRequest: (sessionData = {}, request2) => {
11577
+ return new AuthTester(request2).withSession(sessionData).getRequest();
9732
11578
  },
9733
11579
  verifyJWTStructure: (token) => {
9734
11580
  const parts = token.split(".");
@@ -9743,8 +11589,8 @@ var authHelpers = {
9743
11589
  }
9744
11590
  }
9745
11591
  };
9746
- function createAuthTester(request) {
9747
- return new AuthTester(request);
11592
+ function createAuthTester(request2) {
11593
+ return new AuthTester(request2);
9748
11594
  }
9749
11595
  function createJWTTester(options) {
9750
11596
  return new JWTTester(options);
@@ -9752,14 +11598,13 @@ function createJWTTester(options) {
9752
11598
  function createSessionTester(options) {
9753
11599
  return new SessionTester(options);
9754
11600
  }
9755
-
9756
11601
  // src/testing/file-upload-testing.ts
9757
11602
  import { mock as mock2 } from "bun:test";
9758
11603
  class FileUploadTester {
9759
11604
  request;
9760
11605
  files;
9761
- constructor(request) {
9762
- this.request = request || createMockRequest();
11606
+ constructor(request2) {
11607
+ this.request = request2 || createMockRequest();
9763
11608
  this.files = [];
9764
11609
  }
9765
11610
  addFile(testFile) {
@@ -9991,8 +11836,8 @@ var fileUploadScenarios = {
9991
11836
  }
9992
11837
  };
9993
11838
  var fileUploadAssertions = {
9994
- hasFiles: (request, count) => {
9995
- const files = request.files || [];
11839
+ hasFiles: (request2, count) => {
11840
+ const files = request2.files || [];
9996
11841
  if (count !== undefined && files.length !== count) {
9997
11842
  throw new Error(`Expected ${count} files, but got ${files.length}`);
9998
11843
  }
@@ -10000,8 +11845,8 @@ var fileUploadAssertions = {
10000
11845
  throw new Error("Expected request to have files");
10001
11846
  }
10002
11847
  },
10003
- hasFileWithField: (request, fieldName) => {
10004
- const files = request.files || [];
11848
+ hasFileWithField: (request2, fieldName) => {
11849
+ const files = request2.files || [];
10005
11850
  const hasField = files.some((file) => file.fieldName === fieldName);
10006
11851
  if (!hasField) {
10007
11852
  throw new Error(`Expected file with field name '${fieldName}'`);
@@ -10025,8 +11870,8 @@ var fileUploadAssertions = {
10025
11870
  }
10026
11871
  }
10027
11872
  };
10028
- function createFileUploadTester(request) {
10029
- return new FileUploadTester(request);
11873
+ function createFileUploadTester(request2) {
11874
+ return new FileUploadTester(request2);
10030
11875
  }
10031
11876
  var testFiles = {
10032
11877
  image: (fieldName = "image", filename = "test.jpg") => ({
@@ -10060,7 +11905,6 @@ var testFiles = {
10060
11905
  mimetype: "application/x-executable"
10061
11906
  })
10062
11907
  };
10063
-
10064
11908
  // src/testing/middleware-testing.ts
10065
11909
  import { mock as mock3, spyOn } from "bun:test";
10066
11910
  class MiddlewareTester {
@@ -10071,15 +11915,15 @@ class MiddlewareTester {
10071
11915
  this.context = this.createContext();
10072
11916
  }
10073
11917
  createContext() {
10074
- const request = createMockRequest();
11918
+ const request2 = createMockRequest();
10075
11919
  const next = mock3(() => Promise.resolve(new Response("Next called", { status: 200 })));
10076
11920
  return {
10077
- request,
11921
+ request: request2,
10078
11922
  next
10079
11923
  };
10080
11924
  }
10081
- withRequest(request) {
10082
- this.context.request = request;
11925
+ withRequest(request2) {
11926
+ this.context.request = request2;
10083
11927
  return this;
10084
11928
  }
10085
11929
  withNext(nextFn) {
@@ -10177,8 +12021,8 @@ class MiddlewareChainTester {
10177
12021
  this.request = createMockRequest();
10178
12022
  this.finalHandler = mock3(async () => new Response("Final handler", { status: 200 }));
10179
12023
  }
10180
- withRequest(request) {
10181
- this.request = request;
12024
+ withRequest(request2) {
12025
+ this.request = request2;
10182
12026
  return this;
10183
12027
  }
10184
12028
  withFinalHandler(handler) {
@@ -10218,11 +12062,11 @@ function createMiddlewareSpy() {
10218
12062
  }
10219
12063
  async function benchmarkMiddleware(middleware2, iterations = 1000) {
10220
12064
  const times = [];
10221
- const request = createMockRequest();
12065
+ const request2 = createMockRequest();
10222
12066
  const next = mock3(async () => new Response("OK"));
10223
12067
  for (let i = 0;i < iterations; i++) {
10224
12068
  const start = performance.now();
10225
- await middleware2(request, next);
12069
+ await middleware2(request2, next);
10226
12070
  const end = performance.now();
10227
12071
  times.push(end - start);
10228
12072
  }
@@ -10237,7 +12081,6 @@ async function benchmarkMiddleware(middleware2, iterations = 1000) {
10237
12081
  totalTime
10238
12082
  };
10239
12083
  }
10240
-
10241
12084
  // src/testing/model-binding-testing.ts
10242
12085
  import { mock as mock4 } from "bun:test";
10243
12086
  class GenericMockModel {
@@ -10321,8 +12164,8 @@ class ModelBindingTester {
10321
12164
  this.options = options;
10322
12165
  this.request = createMockRequest();
10323
12166
  }
10324
- withRequest(request) {
10325
- this.request = request;
12167
+ withRequest(request2) {
12168
+ this.request = request2;
10326
12169
  return this;
10327
12170
  }
10328
12171
  withParams(params) {
@@ -10460,18 +12303,18 @@ var modelMocks = {
10460
12303
  };
10461
12304
  var constraintHelpers = {
10462
12305
  belongsTo: (userIdField = "userId") => {
10463
- const fn = async (model, request) => {
10464
- if (!request.user)
12306
+ const fn = async (model, request2) => {
12307
+ if (!request2.user)
10465
12308
  return false;
10466
- return model[userIdField] === request.user.id;
12309
+ return model[userIdField] === request2.user.id;
10467
12310
  };
10468
12311
  return mock4(fn);
10469
12312
  },
10470
12313
  belongsToUser: () => {
10471
- const fn = async (model, request) => {
10472
- if (!request.user)
12314
+ const fn = async (model, request2) => {
12315
+ if (!request2.user)
10473
12316
  return false;
10474
- return model.userId === request.user.id;
12317
+ return model.userId === request2.user.id;
10475
12318
  };
10476
12319
  return mock4(fn);
10477
12320
  },
@@ -10491,9 +12334,9 @@ var constraintHelpers = {
10491
12334
  return mock4(constraintFn);
10492
12335
  },
10493
12336
  combine: (...constraints) => {
10494
- const fn = async (model, request) => {
12337
+ const fn = async (model, request2) => {
10495
12338
  for (const constraint of constraints) {
10496
- if (!await constraint(model, request)) {
12339
+ if (!await constraint(model, request2)) {
10497
12340
  return false;
10498
12341
  }
10499
12342
  }
@@ -10550,24 +12393,24 @@ class ModelResolverTester {
10550
12393
  }
10551
12394
  var routeModelHelpers = {
10552
12395
  testRoute: async (path, params, modelBindings) => {
10553
- const request = createMockRequest("GET", path);
10554
- request.params = params;
12396
+ const request2 = createMockRequest("GET", path);
12397
+ request2.params = params;
10555
12398
  for (const [key, model] of Object.entries(modelBindings)) {
10556
- request.context = request.context || {};
10557
- request.context[key] = model;
12399
+ request2.context = request2.context || {};
12400
+ request2.context[key] = model;
10558
12401
  }
10559
- return request;
12402
+ return request2;
10560
12403
  },
10561
12404
  createHandler: (expectedModels) => {
10562
- const fn = async (request) => {
10563
- const context = request.context || {};
12405
+ const fn = async (request2) => {
12406
+ const context = request2.context || {};
10564
12407
  for (const modelKey of expectedModels) {
10565
12408
  if (!context[modelKey]) {
10566
12409
  return new Response(`Model ${modelKey} not found`, { status: 404 });
10567
12410
  }
10568
12411
  }
10569
12412
  return new Response(JSON.stringify({
10570
- params: request.params,
12413
+ params: request2.params,
10571
12414
  models: Object.fromEntries(expectedModels.map((key) => [key, context[key]]))
10572
12415
  }), {
10573
12416
  headers: { "Content-Type": "application/json" }
@@ -10576,16 +12419,16 @@ var routeModelHelpers = {
10576
12419
  return mock4(fn);
10577
12420
  },
10578
12421
  testBindingMiddleware: (bindings) => {
10579
- const fn = async (request, next) => {
10580
- request.context = request.context || {};
12422
+ const fn = async (request2, next) => {
12423
+ request2.context = request2.context || {};
10581
12424
  for (const [key, resolver] of Object.entries(bindings)) {
10582
- const paramValue = request.params[key];
12425
+ const paramValue = request2.params[key];
10583
12426
  if (paramValue) {
10584
12427
  try {
10585
12428
  if (typeof resolver === "function") {
10586
- request.context[key] = await resolver(paramValue);
12429
+ request2.context[key] = await resolver(paramValue);
10587
12430
  } else {
10588
- request.context[key] = resolver;
12431
+ request2.context[key] = resolver;
10589
12432
  }
10590
12433
  } catch (error) {
10591
12434
  console.error(`Error resolving model ${key}:`, error);
@@ -10620,10 +12463,9 @@ var scopedBindingHelpers = {
10620
12463
  return requiredPermissions.every((permission) => user.permissions.includes(permission));
10621
12464
  }
10622
12465
  };
10623
-
10624
12466
  // src/testing/performance-testing.ts
10625
12467
  import { mock as mock5 } from "bun:test";
10626
- import process4 from "process";
12468
+ import process5 from "process";
10627
12469
  class PerformanceTester {
10628
12470
  metrics = [];
10629
12471
  startTime = 0;
@@ -10742,10 +12584,10 @@ class LoadTester {
10742
12584
  var benchmarkTests = {
10743
12585
  routeHandler: async (handler, iterations = 1000) => {
10744
12586
  const times = [];
10745
- const request = createMockRequest();
12587
+ const request2 = createMockRequest();
10746
12588
  for (let i = 0;i < iterations; i++) {
10747
12589
  const start = performance.now();
10748
- await handler(request);
12590
+ await handler(request2);
10749
12591
  const end = performance.now();
10750
12592
  times.push(end - start);
10751
12593
  }
@@ -10764,11 +12606,11 @@ var benchmarkTests = {
10764
12606
  },
10765
12607
  middleware: async (middleware2, iterations = 1000) => {
10766
12608
  const times = [];
10767
- const request = createMockRequest();
12609
+ const request2 = createMockRequest();
10768
12610
  const next = mock5(async () => new Response("OK"));
10769
12611
  for (let i = 0;i < iterations; i++) {
10770
12612
  const start = performance.now();
10771
- await middleware2(request, next);
12613
+ await middleware2(request2, next);
10772
12614
  const end = performance.now();
10773
12615
  times.push(end - start);
10774
12616
  }
@@ -10841,11 +12683,11 @@ var memoryTests = {
10841
12683
  if (globalThis.gc) {
10842
12684
  globalThis.gc();
10843
12685
  }
10844
- const initialHeap = process4.memoryUsage().heapUsed;
12686
+ const initialHeap = process5.memoryUsage().heapUsed;
10845
12687
  for (let i = 0;i < iterations; i++) {
10846
12688
  const result = await fn();
10847
12689
  results.push(result);
10848
- const memUsage = process4.memoryUsage();
12690
+ const memUsage = process5.memoryUsage();
10849
12691
  heapUsages.push(memUsage.heapUsed);
10850
12692
  if (i % 10 === 0 && globalThis.gc) {
10851
12693
  globalThis.gc();
@@ -10854,7 +12696,7 @@ var memoryTests = {
10854
12696
  if (globalThis.gc) {
10855
12697
  globalThis.gc();
10856
12698
  }
10857
- const finalHeap = process4.memoryUsage().heapUsed;
12699
+ const finalHeap = process5.memoryUsage().heapUsed;
10858
12700
  const averageHeapUsed = heapUsages.reduce((sum, usage) => sum + usage, 0) / heapUsages.length;
10859
12701
  const maxHeapUsed = Math.max(...heapUsages);
10860
12702
  const memoryLeakDetected = finalHeap - initialHeap > 1024 * 1024;
@@ -10868,17 +12710,17 @@ var memoryTests = {
10868
12710
  testRouteHandlerMemoryLeaks: async (handler, iterations = 1000) => {
10869
12711
  if (globalThis.gc)
10870
12712
  globalThis.gc();
10871
- const initialMemory = process4.memoryUsage().heapUsed;
12713
+ const initialMemory = process5.memoryUsage().heapUsed;
10872
12714
  for (let i = 0;i < iterations; i++) {
10873
- const request = createMockRequest();
10874
- await handler(request);
12715
+ const request2 = createMockRequest();
12716
+ await handler(request2);
10875
12717
  if (i % 100 === 0 && globalThis.gc) {
10876
12718
  globalThis.gc();
10877
12719
  }
10878
12720
  }
10879
12721
  if (globalThis.gc)
10880
12722
  globalThis.gc();
10881
- const finalMemory = process4.memoryUsage().heapUsed;
12723
+ const finalMemory = process5.memoryUsage().heapUsed;
10882
12724
  const memoryGrowth = finalMemory - initialMemory;
10883
12725
  const memoryLeakDetected = memoryGrowth > 5 * 1024 * 1024;
10884
12726
  return {
@@ -10970,7 +12812,7 @@ function createLoadTester(client, config2) {
10970
12812
  }
10971
12813
  var performanceUtils = {
10972
12814
  getCurrentMemoryUsage: () => {
10973
- const usage = process4.memoryUsage();
12815
+ const usage = process5.memoryUsage();
10974
12816
  return {
10975
12817
  heapUsed: usage.heapUsed,
10976
12818
  heapTotal: usage.heapTotal,
@@ -10979,7 +12821,7 @@ var performanceUtils = {
10979
12821
  };
10980
12822
  },
10981
12823
  getCPUUsage: () => {
10982
- const usage = process4.cpuUsage();
12824
+ const usage = process5.cpuUsage();
10983
12825
  return {
10984
12826
  user: usage.user,
10985
12827
  system: usage.system
@@ -10994,7 +12836,6 @@ var performanceUtils = {
10994
12836
  return JSON.stringify(results, null, 2);
10995
12837
  }
10996
12838
  };
10997
-
10998
12839
  // src/testing/test-response.ts
10999
12840
  class TestResponseWrapper {
11000
12841
  status;
@@ -11115,14 +12956,14 @@ class TestResponseWrapper {
11115
12956
  }
11116
12957
  return this;
11117
12958
  }
11118
- expectRedirect(url) {
12959
+ expectRedirect(url2) {
11119
12960
  if (this.status < 300 || this.status >= 400) {
11120
12961
  throw new Error(`Expected redirect status (3xx), got ${this.status}`);
11121
12962
  }
11122
- if (url) {
12963
+ if (url2) {
11123
12964
  const location = this.headers.location;
11124
- if (location !== url) {
11125
- throw new Error(`Expected redirect to '${url}', got '${location}'`);
12965
+ if (location !== url2) {
12966
+ throw new Error(`Expected redirect to '${url2}', got '${location}'`);
11126
12967
  }
11127
12968
  }
11128
12969
  return this;
@@ -11265,24 +13106,24 @@ class TestClient {
11265
13106
  async request(method, path, options = {}) {
11266
13107
  const headers = { ...this.defaultHeaders, ...options.headers };
11267
13108
  const requestOptions = { ...options, headers };
11268
- const request = new TestRequestBuilder(method, path, requestOptions).build();
13109
+ const request2 = new TestRequestBuilder(method, path, requestOptions).build();
11269
13110
  try {
11270
- const response2 = await this.handleRequest(request);
13111
+ const response2 = await this.handleRequest(request2);
11271
13112
  return await createTestResponse(response2);
11272
13113
  } catch (error) {
11273
13114
  throw new Error(`Test request failed: ${error instanceof Error ? error.message : "Unknown error"}`);
11274
13115
  }
11275
13116
  }
11276
- async handleRequest(request) {
11277
- const url = new URL(request.url);
11278
- const matchResult = this.findMatchingRoute(request.method, url.pathname);
13117
+ async handleRequest(request2) {
13118
+ const url2 = new URL(request2.url);
13119
+ const matchResult = this.findMatchingRoute(request2.method, url2.pathname);
11279
13120
  if (!matchResult) {
11280
13121
  return new Response("Not Found", { status: 404 });
11281
13122
  }
11282
13123
  const { route, params } = matchResult;
11283
- request.params = { ...request.params, ...params };
13124
+ request2.params = { ...request2.params, ...params };
11284
13125
  try {
11285
- const response2 = await this.executeMiddlewareChain(request, route);
13126
+ const response2 = await this.executeMiddlewareChain(request2, route);
11286
13127
  return response2 || new Response("No response", { status: 500 });
11287
13128
  } catch (error) {
11288
13129
  return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Internal Server Error" }), {
@@ -11322,28 +13163,28 @@ class TestClient {
11322
13163
  }
11323
13164
  return params;
11324
13165
  }
11325
- async executeMiddlewareChain(request, route) {
13166
+ async executeMiddlewareChain(request2, route) {
11326
13167
  const middlewares = [...this.router.globalMiddleware, ...route.middleware];
11327
13168
  let index = 0;
11328
13169
  const next = async () => {
11329
13170
  if (index < middlewares.length) {
11330
13171
  const middleware2 = middlewares[index++];
11331
- return await middleware2(request, next);
13172
+ return await middleware2(request2, next);
11332
13173
  } else {
11333
- return await this.executeHandler(route.handler, request);
13174
+ return await this.executeHandler(route.handler, request2);
11334
13175
  }
11335
13176
  };
11336
13177
  const result = await next();
11337
13178
  return result || new Response("No response", { status: 500 });
11338
13179
  }
11339
- async executeHandler(handler, request) {
13180
+ async executeHandler(handler, request2) {
11340
13181
  if (typeof handler === "function") {
11341
- return await handler(request);
13182
+ return await handler(request2);
11342
13183
  } else if (typeof handler === "string") {
11343
13184
  throw new TypeError(`String handlers not supported in test environment: ${handler}`);
11344
13185
  } else if (typeof handler === "object" && handler.handle) {
11345
13186
  const instance = new handler;
11346
- return await instance.handle(request);
13187
+ return await instance.handle(request2);
11347
13188
  }
11348
13189
  throw new Error("Invalid handler type");
11349
13190
  }
@@ -11384,7 +13225,6 @@ var testClient = {
11384
13225
  withAuth: (token, type = "Bearer") => (router2) => createTestClient(router2).auth(token, type),
11385
13226
  withHeaders: (headers) => (router2) => createTestClient(router2).withHeaders(headers)
11386
13227
  };
11387
-
11388
13228
  // src/testing/websocket-testing.ts
11389
13229
  import { mock as mock6 } from "bun:test";
11390
13230
 
@@ -11739,13 +13579,13 @@ export {
11739
13579
  spyOnMiddleware,
11740
13580
  singleFileUpload,
11741
13581
  signRequest,
11742
- setCurrentRequest2 as setCurrentRequest,
13582
+ setCurrentRequest,
11743
13583
  setContainer,
11744
13584
  session,
11745
13585
  securitySuite,
11746
13586
  security,
11747
13587
  scopedBindingHelpers,
11748
- runWithRequest2 as runWithRequest,
13588
+ runWithRequest,
11749
13589
  rule,
11750
13590
  router,
11751
13591
  routeModelHelpers,
@@ -11758,7 +13598,7 @@ export {
11758
13598
  requestSigning,
11759
13599
  requestId,
11760
13600
  request,
11761
- registerNamedRoute2 as registerNamedRoute,
13601
+ registerNamedRoute,
11762
13602
  rateLimitRegistry,
11763
13603
  rateLimit,
11764
13604
  processHtmlTemplate,
@@ -11788,7 +13628,7 @@ export {
11788
13628
  globalValidator,
11789
13629
  globalMiddlewarePipeline,
11790
13630
  getNamedRoutes,
11791
- getNamedRoutePath2 as getNamedRoutePath,
13631
+ getNamedRoutePath,
11792
13632
  getCurrentRequest,
11793
13633
  getContainer,
11794
13634
  getConfig,
@@ -11919,7 +13759,7 @@ export {
11919
13759
  FileUpload,
11920
13760
  FileSessionStore,
11921
13761
  FileSecurity,
11922
- Errors,
13762
+ exports_errors as Errors,
11923
13763
  EnvironmentManager,
11924
13764
  EnhancementPresets,
11925
13765
  DomainMatcher,
@@ -11939,6 +13779,6 @@ export {
11939
13779
  BuiltInRules,
11940
13780
  BindingBuilder,
11941
13781
  AuthTester,
11942
- default2 as Auth,
13782
+ auth_default as Auth,
11943
13783
  ApiKeyManager
11944
13784
  };