@gravito/core 1.6.0 → 1.6.1
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/Metrics-VOWWRNNR.js +219 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/{compat-C4Src6NN.d.cts → compat-CI8hiulX.d.cts} +19 -0
- package/dist/{compat-C4Src6NN.d.ts → compat-CI8hiulX.d.ts} +19 -0
- package/dist/compat.d.cts +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/engine/index.cjs +326 -29
- package/dist/engine/index.d.cts +222 -2
- package/dist/engine/index.d.ts +222 -2
- package/dist/engine/index.js +326 -29
- package/dist/index.cjs +9039 -4976
- package/dist/index.d.cts +2693 -419
- package/dist/index.d.ts +2693 -419
- package/dist/index.js +8779 -5118
- package/package.json +4 -3
package/dist/engine/index.cjs
CHANGED
|
@@ -257,7 +257,14 @@ var AOTRouter = class {
|
|
|
257
257
|
dynamicRoutePatterns = /* @__PURE__ */ new Map();
|
|
258
258
|
middlewareCache = /* @__PURE__ */ new Map();
|
|
259
259
|
cacheMaxSize = 1e3;
|
|
260
|
-
|
|
260
|
+
_version = 0;
|
|
261
|
+
/**
|
|
262
|
+
* Get the current version for cache invalidation
|
|
263
|
+
* Incremented whenever middleware or routes are modified
|
|
264
|
+
*/
|
|
265
|
+
get version() {
|
|
266
|
+
return this._version;
|
|
267
|
+
}
|
|
261
268
|
/**
|
|
262
269
|
* Register a route
|
|
263
270
|
*
|
|
@@ -329,7 +336,7 @@ var AOTRouter = class {
|
|
|
329
336
|
*/
|
|
330
337
|
use(...middleware) {
|
|
331
338
|
this.globalMiddleware.push(...middleware);
|
|
332
|
-
this.
|
|
339
|
+
this._version++;
|
|
333
340
|
}
|
|
334
341
|
/**
|
|
335
342
|
* Add path-based middleware
|
|
@@ -346,7 +353,7 @@ var AOTRouter = class {
|
|
|
346
353
|
const existing = this.pathMiddleware.get(pattern) ?? [];
|
|
347
354
|
this.pathMiddleware.set(pattern, [...existing, ...middleware]);
|
|
348
355
|
}
|
|
349
|
-
this.
|
|
356
|
+
this._version++;
|
|
350
357
|
}
|
|
351
358
|
/**
|
|
352
359
|
* Match a request to a route
|
|
@@ -408,9 +415,9 @@ var AOTRouter = class {
|
|
|
408
415
|
if (this.globalMiddleware.length === 0 && this.pathMiddleware.size === 0 && routeMiddleware.length === 0) {
|
|
409
416
|
return [];
|
|
410
417
|
}
|
|
411
|
-
const cacheKey =
|
|
418
|
+
const cacheKey = path;
|
|
412
419
|
const cached = this.middlewareCache.get(cacheKey);
|
|
413
|
-
if (cached !== void 0 && cached.version === this.
|
|
420
|
+
if (cached !== void 0 && cached.version === this._version) {
|
|
414
421
|
return cached.data;
|
|
415
422
|
}
|
|
416
423
|
const middleware = [];
|
|
@@ -431,7 +438,9 @@ var AOTRouter = class {
|
|
|
431
438
|
middleware.push(...routeMiddleware);
|
|
432
439
|
}
|
|
433
440
|
if (this.middlewareCache.size < this.cacheMaxSize) {
|
|
434
|
-
this.middlewareCache.set(cacheKey, { data: middleware, version: this.
|
|
441
|
+
this.middlewareCache.set(cacheKey, { data: middleware, version: this._version });
|
|
442
|
+
} else if (this.middlewareCache.has(cacheKey)) {
|
|
443
|
+
this.middlewareCache.set(cacheKey, { data: middleware, version: this._version });
|
|
435
444
|
}
|
|
436
445
|
return middleware;
|
|
437
446
|
}
|
|
@@ -519,6 +528,172 @@ var HEADERS = {
|
|
|
519
528
|
HTML: { "Content-Type": "text/html; charset=utf-8" }
|
|
520
529
|
};
|
|
521
530
|
|
|
531
|
+
// src/Container/RequestScopeMetrics.ts
|
|
532
|
+
var RequestScopeMetrics = class {
|
|
533
|
+
cleanupStartTime = null;
|
|
534
|
+
cleanupDuration = null;
|
|
535
|
+
scopeSize = 0;
|
|
536
|
+
servicesCleaned = 0;
|
|
537
|
+
errorsOccurred = 0;
|
|
538
|
+
/**
|
|
539
|
+
* Record start of cleanup operation
|
|
540
|
+
*/
|
|
541
|
+
recordCleanupStart() {
|
|
542
|
+
this.cleanupStartTime = performance.now();
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Record end of cleanup operation
|
|
546
|
+
*
|
|
547
|
+
* @param scopeSize - Number of services in the scope
|
|
548
|
+
* @param servicesCleaned - Number of services that had cleanup called
|
|
549
|
+
* @param errorsOccurred - Number of cleanup errors
|
|
550
|
+
*/
|
|
551
|
+
recordCleanupEnd(scopeSize, servicesCleaned, errorsOccurred = 0) {
|
|
552
|
+
if (this.cleanupStartTime !== null) {
|
|
553
|
+
this.cleanupDuration = performance.now() - this.cleanupStartTime;
|
|
554
|
+
this.cleanupStartTime = null;
|
|
555
|
+
}
|
|
556
|
+
this.scopeSize = scopeSize;
|
|
557
|
+
this.servicesCleaned = servicesCleaned;
|
|
558
|
+
this.errorsOccurred = errorsOccurred;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Get cleanup duration in milliseconds
|
|
562
|
+
*
|
|
563
|
+
* @returns Duration in ms, or null if cleanup not completed
|
|
564
|
+
*/
|
|
565
|
+
getCleanupDuration() {
|
|
566
|
+
return this.cleanupDuration;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Check if cleanup took longer than threshold (default 2ms)
|
|
570
|
+
* Useful for detecting slow cleanups
|
|
571
|
+
*
|
|
572
|
+
* @param thresholdMs - Threshold in milliseconds
|
|
573
|
+
* @returns True if cleanup exceeded threshold
|
|
574
|
+
*/
|
|
575
|
+
isSlowCleanup(thresholdMs = 2) {
|
|
576
|
+
if (this.cleanupDuration === null) return false;
|
|
577
|
+
return this.cleanupDuration > thresholdMs;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Export metrics as JSON for logging/monitoring
|
|
581
|
+
*/
|
|
582
|
+
toJSON() {
|
|
583
|
+
return {
|
|
584
|
+
cleanupDuration: this.cleanupDuration,
|
|
585
|
+
scopeSize: this.scopeSize,
|
|
586
|
+
servicesCleaned: this.servicesCleaned,
|
|
587
|
+
errorsOccurred: this.errorsOccurred,
|
|
588
|
+
hasErrors: this.errorsOccurred > 0,
|
|
589
|
+
isSlowCleanup: this.isSlowCleanup()
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Export metrics as compact string for logging
|
|
594
|
+
*/
|
|
595
|
+
toString() {
|
|
596
|
+
const duration = this.cleanupDuration ?? "pending";
|
|
597
|
+
return `cleanup: ${duration}ms, scope: ${this.scopeSize}, cleaned: ${this.servicesCleaned}, errors: ${this.errorsOccurred}`;
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// src/Container/RequestScopeManager.ts
|
|
602
|
+
var RequestScopeManager = class {
|
|
603
|
+
scoped = /* @__PURE__ */ new Map();
|
|
604
|
+
metadata = /* @__PURE__ */ new Map();
|
|
605
|
+
metrics = new RequestScopeMetrics();
|
|
606
|
+
observer = null;
|
|
607
|
+
constructor(observer) {
|
|
608
|
+
this.observer = observer || null;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Set observer for monitoring scope lifecycle
|
|
612
|
+
*/
|
|
613
|
+
setObserver(observer) {
|
|
614
|
+
this.observer = observer;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Get metrics for this scope
|
|
618
|
+
*/
|
|
619
|
+
getMetrics() {
|
|
620
|
+
return this.metrics;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Resolve or retrieve a request-scoped service instance.
|
|
624
|
+
*
|
|
625
|
+
* If the service already exists in this scope, returns the cached instance.
|
|
626
|
+
* Otherwise, calls the factory function to create a new instance and caches it.
|
|
627
|
+
*
|
|
628
|
+
* Automatically detects and records services with cleanup methods.
|
|
629
|
+
*
|
|
630
|
+
* @template T - The type of the service.
|
|
631
|
+
* @param key - The service key (for caching).
|
|
632
|
+
* @param factory - Factory function to create the instance if not cached.
|
|
633
|
+
* @returns The cached or newly created instance.
|
|
634
|
+
*/
|
|
635
|
+
resolve(key, factory) {
|
|
636
|
+
const keyStr = String(key);
|
|
637
|
+
const isFromCache = this.scoped.has(keyStr);
|
|
638
|
+
if (!isFromCache) {
|
|
639
|
+
const instance = factory();
|
|
640
|
+
this.scoped.set(keyStr, instance);
|
|
641
|
+
if (instance && typeof instance === "object" && "cleanup" in instance) {
|
|
642
|
+
this.metadata.set(keyStr, { hasCleanup: true });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
this.observer?.onServiceResolved?.(key, isFromCache);
|
|
646
|
+
return this.scoped.get(keyStr);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Clean up all request-scoped instances.
|
|
650
|
+
*
|
|
651
|
+
* Calls the cleanup() method on each service that has one.
|
|
652
|
+
* Silently ignores cleanup errors to prevent cascading failures.
|
|
653
|
+
* Called automatically by the Gravito engine in the request finally block.
|
|
654
|
+
*
|
|
655
|
+
* @returns Promise that resolves when all cleanup is complete.
|
|
656
|
+
*/
|
|
657
|
+
async cleanup() {
|
|
658
|
+
this.metrics.recordCleanupStart();
|
|
659
|
+
this.observer?.onCleanupStart?.();
|
|
660
|
+
const errors = [];
|
|
661
|
+
let servicesCleaned = 0;
|
|
662
|
+
for (const [, instance] of this.scoped) {
|
|
663
|
+
if (instance && typeof instance === "object" && "cleanup" in instance) {
|
|
664
|
+
const fn = instance.cleanup;
|
|
665
|
+
if (typeof fn === "function") {
|
|
666
|
+
try {
|
|
667
|
+
await fn.call(instance);
|
|
668
|
+
servicesCleaned++;
|
|
669
|
+
} catch (error) {
|
|
670
|
+
errors.push(error);
|
|
671
|
+
this.observer?.onCleanupError?.(
|
|
672
|
+
error instanceof Error ? error : new Error(String(error))
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const scopeSize = this.scoped.size;
|
|
679
|
+
this.scoped.clear();
|
|
680
|
+
this.metadata.clear();
|
|
681
|
+
this.metrics.recordCleanupEnd(scopeSize, servicesCleaned, errors.length);
|
|
682
|
+
this.observer?.onCleanupEnd?.(this.metrics);
|
|
683
|
+
if (errors.length > 0) {
|
|
684
|
+
console.error("RequestScope cleanup errors:", errors);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Get the number of services in this scope (for monitoring).
|
|
689
|
+
*
|
|
690
|
+
* @returns The count of cached services.
|
|
691
|
+
*/
|
|
692
|
+
size() {
|
|
693
|
+
return this.scoped.size;
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
522
697
|
// src/engine/FastContext.ts
|
|
523
698
|
var FastRequestImpl = class {
|
|
524
699
|
_request;
|
|
@@ -530,6 +705,11 @@ var FastRequestImpl = class {
|
|
|
530
705
|
_headers = null;
|
|
531
706
|
_cachedJson = void 0;
|
|
532
707
|
_jsonParsed = false;
|
|
708
|
+
_cachedText = void 0;
|
|
709
|
+
_textParsed = false;
|
|
710
|
+
_cachedFormData = void 0;
|
|
711
|
+
_formDataParsed = false;
|
|
712
|
+
_cachedQueries = null;
|
|
533
713
|
// Back-reference for release check optimization
|
|
534
714
|
_ctx;
|
|
535
715
|
constructor(ctx) {
|
|
@@ -548,6 +728,11 @@ var FastRequestImpl = class {
|
|
|
548
728
|
this._headers = null;
|
|
549
729
|
this._cachedJson = void 0;
|
|
550
730
|
this._jsonParsed = false;
|
|
731
|
+
this._cachedText = void 0;
|
|
732
|
+
this._textParsed = false;
|
|
733
|
+
this._cachedFormData = void 0;
|
|
734
|
+
this._formDataParsed = false;
|
|
735
|
+
this._cachedQueries = null;
|
|
551
736
|
return this;
|
|
552
737
|
}
|
|
553
738
|
/**
|
|
@@ -561,6 +746,11 @@ var FastRequestImpl = class {
|
|
|
561
746
|
this._headers = null;
|
|
562
747
|
this._cachedJson = void 0;
|
|
563
748
|
this._jsonParsed = false;
|
|
749
|
+
this._cachedText = void 0;
|
|
750
|
+
this._textParsed = false;
|
|
751
|
+
this._cachedFormData = void 0;
|
|
752
|
+
this._formDataParsed = false;
|
|
753
|
+
this._cachedQueries = null;
|
|
564
754
|
}
|
|
565
755
|
checkReleased() {
|
|
566
756
|
if (this._ctx._isReleased) {
|
|
@@ -608,6 +798,9 @@ var FastRequestImpl = class {
|
|
|
608
798
|
}
|
|
609
799
|
queries() {
|
|
610
800
|
this.checkReleased();
|
|
801
|
+
if (this._cachedQueries !== null) {
|
|
802
|
+
return this._cachedQueries;
|
|
803
|
+
}
|
|
611
804
|
if (!this._query) {
|
|
612
805
|
this._query = this.getUrl().searchParams;
|
|
613
806
|
}
|
|
@@ -622,6 +815,7 @@ var FastRequestImpl = class {
|
|
|
622
815
|
result[key] = [existing, value];
|
|
623
816
|
}
|
|
624
817
|
}
|
|
818
|
+
this._cachedQueries = result;
|
|
625
819
|
return result;
|
|
626
820
|
}
|
|
627
821
|
header(name) {
|
|
@@ -648,11 +842,19 @@ var FastRequestImpl = class {
|
|
|
648
842
|
}
|
|
649
843
|
async text() {
|
|
650
844
|
this.checkReleased();
|
|
651
|
-
|
|
845
|
+
if (!this._textParsed) {
|
|
846
|
+
this._cachedText = await this._request.text();
|
|
847
|
+
this._textParsed = true;
|
|
848
|
+
}
|
|
849
|
+
return this._cachedText;
|
|
652
850
|
}
|
|
653
851
|
async formData() {
|
|
654
852
|
this.checkReleased();
|
|
655
|
-
|
|
853
|
+
if (!this._formDataParsed) {
|
|
854
|
+
this._cachedFormData = await this._request.formData();
|
|
855
|
+
this._formDataParsed = true;
|
|
856
|
+
}
|
|
857
|
+
return this._cachedFormData;
|
|
656
858
|
}
|
|
657
859
|
get raw() {
|
|
658
860
|
this.checkReleased();
|
|
@@ -666,6 +868,8 @@ var FastContext = class {
|
|
|
666
868
|
// Reuse this object
|
|
667
869
|
_isReleased = false;
|
|
668
870
|
// Made public for internal check access
|
|
871
|
+
_requestScope = null;
|
|
872
|
+
// Request-scoped services
|
|
669
873
|
/**
|
|
670
874
|
* Initialize context for a new request
|
|
671
875
|
*
|
|
@@ -675,6 +879,7 @@ var FastContext = class {
|
|
|
675
879
|
this._isReleased = false;
|
|
676
880
|
this.req.init(request, params, path, routePattern);
|
|
677
881
|
this._headers = new Headers();
|
|
882
|
+
this._requestScope = new RequestScopeManager();
|
|
678
883
|
return this;
|
|
679
884
|
}
|
|
680
885
|
/**
|
|
@@ -800,6 +1005,32 @@ var FastContext = class {
|
|
|
800
1005
|
this._store.set(key, value);
|
|
801
1006
|
}
|
|
802
1007
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1008
|
+
// Request Scope Management
|
|
1009
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1010
|
+
/**
|
|
1011
|
+
* Get the request-scoped service manager for this request.
|
|
1012
|
+
*
|
|
1013
|
+
* @returns The RequestScopeManager for this request.
|
|
1014
|
+
* @throws Error if called before init() or after reset().
|
|
1015
|
+
*/
|
|
1016
|
+
requestScope() {
|
|
1017
|
+
if (!this._requestScope) {
|
|
1018
|
+
throw new Error("RequestScope not initialized. Call init() first.");
|
|
1019
|
+
}
|
|
1020
|
+
return this._requestScope;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Resolve a request-scoped service (convenience method).
|
|
1024
|
+
*
|
|
1025
|
+
* @template T - The service type.
|
|
1026
|
+
* @param key - The service key for caching.
|
|
1027
|
+
* @param factory - Factory function to create the service.
|
|
1028
|
+
* @returns The cached or newly created service instance.
|
|
1029
|
+
*/
|
|
1030
|
+
scoped(key, factory) {
|
|
1031
|
+
return this.requestScope().resolve(key, factory);
|
|
1032
|
+
}
|
|
1033
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
803
1034
|
// Lifecycle helpers
|
|
804
1035
|
// ─────────────────────────────────────────────────────────────────────────
|
|
805
1036
|
route = () => "";
|
|
@@ -817,6 +1048,10 @@ var MinimalRequest = class {
|
|
|
817
1048
|
this._routePattern = _routePattern;
|
|
818
1049
|
}
|
|
819
1050
|
_searchParams = null;
|
|
1051
|
+
_cachedQueries = null;
|
|
1052
|
+
_cachedJsonPromise = null;
|
|
1053
|
+
_cachedTextPromise = null;
|
|
1054
|
+
_cachedFormDataPromise = null;
|
|
820
1055
|
get url() {
|
|
821
1056
|
return this._request.url;
|
|
822
1057
|
}
|
|
@@ -856,6 +1091,9 @@ var MinimalRequest = class {
|
|
|
856
1091
|
return this.getSearchParams().get(name) ?? void 0;
|
|
857
1092
|
}
|
|
858
1093
|
queries() {
|
|
1094
|
+
if (this._cachedQueries !== null) {
|
|
1095
|
+
return this._cachedQueries;
|
|
1096
|
+
}
|
|
859
1097
|
const params = this.getSearchParams();
|
|
860
1098
|
const result = {};
|
|
861
1099
|
for (const [key, value] of params.entries()) {
|
|
@@ -868,6 +1106,7 @@ var MinimalRequest = class {
|
|
|
868
1106
|
result[key] = [existing, value];
|
|
869
1107
|
}
|
|
870
1108
|
}
|
|
1109
|
+
this._cachedQueries = result;
|
|
871
1110
|
return result;
|
|
872
1111
|
}
|
|
873
1112
|
header(name) {
|
|
@@ -881,13 +1120,22 @@ var MinimalRequest = class {
|
|
|
881
1120
|
return result;
|
|
882
1121
|
}
|
|
883
1122
|
async json() {
|
|
884
|
-
|
|
1123
|
+
if (this._cachedJsonPromise === null) {
|
|
1124
|
+
this._cachedJsonPromise = this._request.json();
|
|
1125
|
+
}
|
|
1126
|
+
return this._cachedJsonPromise;
|
|
885
1127
|
}
|
|
886
1128
|
async text() {
|
|
887
|
-
|
|
1129
|
+
if (this._cachedTextPromise === null) {
|
|
1130
|
+
this._cachedTextPromise = this._request.text();
|
|
1131
|
+
}
|
|
1132
|
+
return this._cachedTextPromise;
|
|
888
1133
|
}
|
|
889
1134
|
async formData() {
|
|
890
|
-
|
|
1135
|
+
if (this._cachedFormDataPromise === null) {
|
|
1136
|
+
this._cachedFormDataPromise = this._request.formData();
|
|
1137
|
+
}
|
|
1138
|
+
return this._cachedFormDataPromise;
|
|
891
1139
|
}
|
|
892
1140
|
get raw() {
|
|
893
1141
|
return this._request;
|
|
@@ -896,18 +1144,19 @@ var MinimalRequest = class {
|
|
|
896
1144
|
var MinimalContext = class {
|
|
897
1145
|
req;
|
|
898
1146
|
_resHeaders = {};
|
|
1147
|
+
_requestScope;
|
|
899
1148
|
constructor(request, params, path, routePattern) {
|
|
900
1149
|
this.req = new MinimalRequest(request, params, path, routePattern);
|
|
1150
|
+
this._requestScope = new RequestScopeManager();
|
|
901
1151
|
}
|
|
902
1152
|
// get req(): FastRequest {
|
|
903
1153
|
// return this._req
|
|
904
1154
|
// }
|
|
905
1155
|
// Response helpers - merge custom headers with defaults
|
|
1156
|
+
// Optimized: use Object.assign instead of spread to avoid shallow copy overhead
|
|
906
1157
|
getHeaders(contentType) {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
"Content-Type": contentType
|
|
910
|
-
};
|
|
1158
|
+
const headers = Object.assign({ "Content-Type": contentType }, this._resHeaders);
|
|
1159
|
+
return headers;
|
|
911
1160
|
}
|
|
912
1161
|
json(data, status = 200) {
|
|
913
1162
|
return new Response(JSON.stringify(data), {
|
|
@@ -981,6 +1230,25 @@ var MinimalContext = class {
|
|
|
981
1230
|
}
|
|
982
1231
|
set(_key, _value) {
|
|
983
1232
|
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Get the request-scoped service manager for this request.
|
|
1235
|
+
*
|
|
1236
|
+
* @returns The RequestScopeManager for this request.
|
|
1237
|
+
*/
|
|
1238
|
+
requestScope() {
|
|
1239
|
+
return this._requestScope;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Resolve a request-scoped service (convenience method).
|
|
1243
|
+
*
|
|
1244
|
+
* @template T - The service type.
|
|
1245
|
+
* @param key - The service key for caching.
|
|
1246
|
+
* @param factory - Factory function to create the service.
|
|
1247
|
+
* @returns The cached or newly created service instance.
|
|
1248
|
+
*/
|
|
1249
|
+
scoped(key, factory) {
|
|
1250
|
+
return this._requestScope.resolve(key, factory);
|
|
1251
|
+
}
|
|
984
1252
|
route = () => "";
|
|
985
1253
|
get native() {
|
|
986
1254
|
return this;
|
|
@@ -1098,18 +1366,40 @@ function compileMiddlewareChain(middleware, handler) {
|
|
|
1098
1366
|
if (middleware.length === 0) {
|
|
1099
1367
|
return handler;
|
|
1100
1368
|
}
|
|
1369
|
+
if (middleware.length === 1) {
|
|
1370
|
+
const mw = middleware[0];
|
|
1371
|
+
return async (ctx) => {
|
|
1372
|
+
let nextCalled = false;
|
|
1373
|
+
const result = await mw(ctx, async () => {
|
|
1374
|
+
nextCalled = true;
|
|
1375
|
+
return void 0;
|
|
1376
|
+
});
|
|
1377
|
+
if (result instanceof Response) {
|
|
1378
|
+
return result;
|
|
1379
|
+
}
|
|
1380
|
+
if (nextCalled) {
|
|
1381
|
+
return await handler(ctx);
|
|
1382
|
+
}
|
|
1383
|
+
return ctx.json({ error: "Middleware did not call next or return response" }, 500);
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1101
1386
|
let compiled = handler;
|
|
1102
1387
|
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
1103
1388
|
const mw = middleware[i];
|
|
1104
1389
|
const nextHandler = compiled;
|
|
1105
1390
|
compiled = async (ctx) => {
|
|
1106
|
-
let
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
return
|
|
1110
|
-
};
|
|
1111
|
-
|
|
1112
|
-
|
|
1391
|
+
let nextCalled = false;
|
|
1392
|
+
const result = await mw(ctx, async () => {
|
|
1393
|
+
nextCalled = true;
|
|
1394
|
+
return void 0;
|
|
1395
|
+
});
|
|
1396
|
+
if (result instanceof Response) {
|
|
1397
|
+
return result;
|
|
1398
|
+
}
|
|
1399
|
+
if (nextCalled) {
|
|
1400
|
+
return await nextHandler(ctx);
|
|
1401
|
+
}
|
|
1402
|
+
return ctx.json({ error: "Middleware did not call next or return response" }, 500);
|
|
1113
1403
|
};
|
|
1114
1404
|
}
|
|
1115
1405
|
return compiled;
|
|
@@ -1126,8 +1416,6 @@ var Gravito = class {
|
|
|
1126
1416
|
isPureStaticApp = true;
|
|
1127
1417
|
// Cache for precompiled dynamic routes
|
|
1128
1418
|
compiledDynamicRoutes = /* @__PURE__ */ new Map();
|
|
1129
|
-
// Version tracking for cache invalidation
|
|
1130
|
-
middlewareVersion = 0;
|
|
1131
1419
|
/**
|
|
1132
1420
|
* Create a new Gravito instance
|
|
1133
1421
|
*
|
|
@@ -1215,7 +1503,6 @@ var Gravito = class {
|
|
|
1215
1503
|
} else {
|
|
1216
1504
|
this.router.use(pathOrMiddleware, ...middleware);
|
|
1217
1505
|
}
|
|
1218
|
-
this.middlewareVersion++;
|
|
1219
1506
|
this.compileRoutes();
|
|
1220
1507
|
return this;
|
|
1221
1508
|
}
|
|
@@ -1308,6 +1595,11 @@ var Gravito = class {
|
|
|
1308
1595
|
} catch (error) {
|
|
1309
1596
|
return await this.handleError(error, ctx);
|
|
1310
1597
|
} finally {
|
|
1598
|
+
try {
|
|
1599
|
+
await ctx.requestScope().cleanup();
|
|
1600
|
+
} catch (cleanupError) {
|
|
1601
|
+
console.error("RequestScope cleanup failed:", cleanupError);
|
|
1602
|
+
}
|
|
1311
1603
|
this.contextPool.release(ctx);
|
|
1312
1604
|
}
|
|
1313
1605
|
}
|
|
@@ -1321,12 +1613,12 @@ var Gravito = class {
|
|
|
1321
1613
|
}
|
|
1322
1614
|
const cacheKey = `${method}:${match.routePattern ?? path}`;
|
|
1323
1615
|
let entry = this.compiledDynamicRoutes.get(cacheKey);
|
|
1324
|
-
if (!entry || entry.version !== this.
|
|
1616
|
+
if (!entry || entry.version !== this.router.version) {
|
|
1325
1617
|
const compiled = compileMiddlewareChain(match.middleware, match.handler);
|
|
1326
1618
|
if (this.compiledDynamicRoutes.size > 1e3) {
|
|
1327
1619
|
this.compiledDynamicRoutes.clear();
|
|
1328
1620
|
}
|
|
1329
|
-
entry = { compiled, version: this.
|
|
1621
|
+
entry = { compiled, version: this.router.version };
|
|
1330
1622
|
this.compiledDynamicRoutes.set(cacheKey, entry);
|
|
1331
1623
|
}
|
|
1332
1624
|
const ctx = this.contextPool.acquire();
|
|
@@ -1337,6 +1629,11 @@ var Gravito = class {
|
|
|
1337
1629
|
} catch (error) {
|
|
1338
1630
|
return await this.handleError(error, ctx);
|
|
1339
1631
|
} finally {
|
|
1632
|
+
try {
|
|
1633
|
+
await ctx.requestScope().cleanup();
|
|
1634
|
+
} catch (cleanupError) {
|
|
1635
|
+
console.error("RequestScope cleanup failed:", cleanupError);
|
|
1636
|
+
}
|
|
1340
1637
|
this.contextPool.release(ctx);
|
|
1341
1638
|
}
|
|
1342
1639
|
};
|
|
@@ -1395,7 +1692,7 @@ var Gravito = class {
|
|
|
1395
1692
|
const hasPathMiddleware = this.router.pathMiddleware.size > 0;
|
|
1396
1693
|
this.isPureStaticApp = !hasGlobalMiddleware && !hasPathMiddleware;
|
|
1397
1694
|
for (const [key, route] of this.staticRoutes) {
|
|
1398
|
-
if (route.compiledVersion === this.
|
|
1695
|
+
if (route.compiledVersion === this.router.version) {
|
|
1399
1696
|
continue;
|
|
1400
1697
|
}
|
|
1401
1698
|
const analysis = analyzeHandler(route.handler);
|
|
@@ -1405,7 +1702,7 @@ var Gravito = class {
|
|
|
1405
1702
|
const allMiddleware = this.collectMiddlewareForPath(key.split(":")[1], route.middleware);
|
|
1406
1703
|
route.compiled = compileMiddlewareChain(allMiddleware, route.handler);
|
|
1407
1704
|
}
|
|
1408
|
-
route.compiledVersion = this.
|
|
1705
|
+
route.compiledVersion = this.router.version;
|
|
1409
1706
|
}
|
|
1410
1707
|
}
|
|
1411
1708
|
/**
|