@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.js
CHANGED
|
@@ -226,7 +226,14 @@ var AOTRouter = class {
|
|
|
226
226
|
dynamicRoutePatterns = /* @__PURE__ */ new Map();
|
|
227
227
|
middlewareCache = /* @__PURE__ */ new Map();
|
|
228
228
|
cacheMaxSize = 1e3;
|
|
229
|
-
|
|
229
|
+
_version = 0;
|
|
230
|
+
/**
|
|
231
|
+
* Get the current version for cache invalidation
|
|
232
|
+
* Incremented whenever middleware or routes are modified
|
|
233
|
+
*/
|
|
234
|
+
get version() {
|
|
235
|
+
return this._version;
|
|
236
|
+
}
|
|
230
237
|
/**
|
|
231
238
|
* Register a route
|
|
232
239
|
*
|
|
@@ -298,7 +305,7 @@ var AOTRouter = class {
|
|
|
298
305
|
*/
|
|
299
306
|
use(...middleware) {
|
|
300
307
|
this.globalMiddleware.push(...middleware);
|
|
301
|
-
this.
|
|
308
|
+
this._version++;
|
|
302
309
|
}
|
|
303
310
|
/**
|
|
304
311
|
* Add path-based middleware
|
|
@@ -315,7 +322,7 @@ var AOTRouter = class {
|
|
|
315
322
|
const existing = this.pathMiddleware.get(pattern) ?? [];
|
|
316
323
|
this.pathMiddleware.set(pattern, [...existing, ...middleware]);
|
|
317
324
|
}
|
|
318
|
-
this.
|
|
325
|
+
this._version++;
|
|
319
326
|
}
|
|
320
327
|
/**
|
|
321
328
|
* Match a request to a route
|
|
@@ -377,9 +384,9 @@ var AOTRouter = class {
|
|
|
377
384
|
if (this.globalMiddleware.length === 0 && this.pathMiddleware.size === 0 && routeMiddleware.length === 0) {
|
|
378
385
|
return [];
|
|
379
386
|
}
|
|
380
|
-
const cacheKey =
|
|
387
|
+
const cacheKey = path;
|
|
381
388
|
const cached = this.middlewareCache.get(cacheKey);
|
|
382
|
-
if (cached !== void 0 && cached.version === this.
|
|
389
|
+
if (cached !== void 0 && cached.version === this._version) {
|
|
383
390
|
return cached.data;
|
|
384
391
|
}
|
|
385
392
|
const middleware = [];
|
|
@@ -400,7 +407,9 @@ var AOTRouter = class {
|
|
|
400
407
|
middleware.push(...routeMiddleware);
|
|
401
408
|
}
|
|
402
409
|
if (this.middlewareCache.size < this.cacheMaxSize) {
|
|
403
|
-
this.middlewareCache.set(cacheKey, { data: middleware, version: this.
|
|
410
|
+
this.middlewareCache.set(cacheKey, { data: middleware, version: this._version });
|
|
411
|
+
} else if (this.middlewareCache.has(cacheKey)) {
|
|
412
|
+
this.middlewareCache.set(cacheKey, { data: middleware, version: this._version });
|
|
404
413
|
}
|
|
405
414
|
return middleware;
|
|
406
415
|
}
|
|
@@ -488,6 +497,172 @@ var HEADERS = {
|
|
|
488
497
|
HTML: { "Content-Type": "text/html; charset=utf-8" }
|
|
489
498
|
};
|
|
490
499
|
|
|
500
|
+
// src/Container/RequestScopeMetrics.ts
|
|
501
|
+
var RequestScopeMetrics = class {
|
|
502
|
+
cleanupStartTime = null;
|
|
503
|
+
cleanupDuration = null;
|
|
504
|
+
scopeSize = 0;
|
|
505
|
+
servicesCleaned = 0;
|
|
506
|
+
errorsOccurred = 0;
|
|
507
|
+
/**
|
|
508
|
+
* Record start of cleanup operation
|
|
509
|
+
*/
|
|
510
|
+
recordCleanupStart() {
|
|
511
|
+
this.cleanupStartTime = performance.now();
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Record end of cleanup operation
|
|
515
|
+
*
|
|
516
|
+
* @param scopeSize - Number of services in the scope
|
|
517
|
+
* @param servicesCleaned - Number of services that had cleanup called
|
|
518
|
+
* @param errorsOccurred - Number of cleanup errors
|
|
519
|
+
*/
|
|
520
|
+
recordCleanupEnd(scopeSize, servicesCleaned, errorsOccurred = 0) {
|
|
521
|
+
if (this.cleanupStartTime !== null) {
|
|
522
|
+
this.cleanupDuration = performance.now() - this.cleanupStartTime;
|
|
523
|
+
this.cleanupStartTime = null;
|
|
524
|
+
}
|
|
525
|
+
this.scopeSize = scopeSize;
|
|
526
|
+
this.servicesCleaned = servicesCleaned;
|
|
527
|
+
this.errorsOccurred = errorsOccurred;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Get cleanup duration in milliseconds
|
|
531
|
+
*
|
|
532
|
+
* @returns Duration in ms, or null if cleanup not completed
|
|
533
|
+
*/
|
|
534
|
+
getCleanupDuration() {
|
|
535
|
+
return this.cleanupDuration;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Check if cleanup took longer than threshold (default 2ms)
|
|
539
|
+
* Useful for detecting slow cleanups
|
|
540
|
+
*
|
|
541
|
+
* @param thresholdMs - Threshold in milliseconds
|
|
542
|
+
* @returns True if cleanup exceeded threshold
|
|
543
|
+
*/
|
|
544
|
+
isSlowCleanup(thresholdMs = 2) {
|
|
545
|
+
if (this.cleanupDuration === null) return false;
|
|
546
|
+
return this.cleanupDuration > thresholdMs;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Export metrics as JSON for logging/monitoring
|
|
550
|
+
*/
|
|
551
|
+
toJSON() {
|
|
552
|
+
return {
|
|
553
|
+
cleanupDuration: this.cleanupDuration,
|
|
554
|
+
scopeSize: this.scopeSize,
|
|
555
|
+
servicesCleaned: this.servicesCleaned,
|
|
556
|
+
errorsOccurred: this.errorsOccurred,
|
|
557
|
+
hasErrors: this.errorsOccurred > 0,
|
|
558
|
+
isSlowCleanup: this.isSlowCleanup()
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Export metrics as compact string for logging
|
|
563
|
+
*/
|
|
564
|
+
toString() {
|
|
565
|
+
const duration = this.cleanupDuration ?? "pending";
|
|
566
|
+
return `cleanup: ${duration}ms, scope: ${this.scopeSize}, cleaned: ${this.servicesCleaned}, errors: ${this.errorsOccurred}`;
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// src/Container/RequestScopeManager.ts
|
|
571
|
+
var RequestScopeManager = class {
|
|
572
|
+
scoped = /* @__PURE__ */ new Map();
|
|
573
|
+
metadata = /* @__PURE__ */ new Map();
|
|
574
|
+
metrics = new RequestScopeMetrics();
|
|
575
|
+
observer = null;
|
|
576
|
+
constructor(observer) {
|
|
577
|
+
this.observer = observer || null;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Set observer for monitoring scope lifecycle
|
|
581
|
+
*/
|
|
582
|
+
setObserver(observer) {
|
|
583
|
+
this.observer = observer;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Get metrics for this scope
|
|
587
|
+
*/
|
|
588
|
+
getMetrics() {
|
|
589
|
+
return this.metrics;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Resolve or retrieve a request-scoped service instance.
|
|
593
|
+
*
|
|
594
|
+
* If the service already exists in this scope, returns the cached instance.
|
|
595
|
+
* Otherwise, calls the factory function to create a new instance and caches it.
|
|
596
|
+
*
|
|
597
|
+
* Automatically detects and records services with cleanup methods.
|
|
598
|
+
*
|
|
599
|
+
* @template T - The type of the service.
|
|
600
|
+
* @param key - The service key (for caching).
|
|
601
|
+
* @param factory - Factory function to create the instance if not cached.
|
|
602
|
+
* @returns The cached or newly created instance.
|
|
603
|
+
*/
|
|
604
|
+
resolve(key, factory) {
|
|
605
|
+
const keyStr = String(key);
|
|
606
|
+
const isFromCache = this.scoped.has(keyStr);
|
|
607
|
+
if (!isFromCache) {
|
|
608
|
+
const instance = factory();
|
|
609
|
+
this.scoped.set(keyStr, instance);
|
|
610
|
+
if (instance && typeof instance === "object" && "cleanup" in instance) {
|
|
611
|
+
this.metadata.set(keyStr, { hasCleanup: true });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
this.observer?.onServiceResolved?.(key, isFromCache);
|
|
615
|
+
return this.scoped.get(keyStr);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Clean up all request-scoped instances.
|
|
619
|
+
*
|
|
620
|
+
* Calls the cleanup() method on each service that has one.
|
|
621
|
+
* Silently ignores cleanup errors to prevent cascading failures.
|
|
622
|
+
* Called automatically by the Gravito engine in the request finally block.
|
|
623
|
+
*
|
|
624
|
+
* @returns Promise that resolves when all cleanup is complete.
|
|
625
|
+
*/
|
|
626
|
+
async cleanup() {
|
|
627
|
+
this.metrics.recordCleanupStart();
|
|
628
|
+
this.observer?.onCleanupStart?.();
|
|
629
|
+
const errors = [];
|
|
630
|
+
let servicesCleaned = 0;
|
|
631
|
+
for (const [, instance] of this.scoped) {
|
|
632
|
+
if (instance && typeof instance === "object" && "cleanup" in instance) {
|
|
633
|
+
const fn = instance.cleanup;
|
|
634
|
+
if (typeof fn === "function") {
|
|
635
|
+
try {
|
|
636
|
+
await fn.call(instance);
|
|
637
|
+
servicesCleaned++;
|
|
638
|
+
} catch (error) {
|
|
639
|
+
errors.push(error);
|
|
640
|
+
this.observer?.onCleanupError?.(
|
|
641
|
+
error instanceof Error ? error : new Error(String(error))
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const scopeSize = this.scoped.size;
|
|
648
|
+
this.scoped.clear();
|
|
649
|
+
this.metadata.clear();
|
|
650
|
+
this.metrics.recordCleanupEnd(scopeSize, servicesCleaned, errors.length);
|
|
651
|
+
this.observer?.onCleanupEnd?.(this.metrics);
|
|
652
|
+
if (errors.length > 0) {
|
|
653
|
+
console.error("RequestScope cleanup errors:", errors);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get the number of services in this scope (for monitoring).
|
|
658
|
+
*
|
|
659
|
+
* @returns The count of cached services.
|
|
660
|
+
*/
|
|
661
|
+
size() {
|
|
662
|
+
return this.scoped.size;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
491
666
|
// src/engine/FastContext.ts
|
|
492
667
|
var FastRequestImpl = class {
|
|
493
668
|
_request;
|
|
@@ -499,6 +674,11 @@ var FastRequestImpl = class {
|
|
|
499
674
|
_headers = null;
|
|
500
675
|
_cachedJson = void 0;
|
|
501
676
|
_jsonParsed = false;
|
|
677
|
+
_cachedText = void 0;
|
|
678
|
+
_textParsed = false;
|
|
679
|
+
_cachedFormData = void 0;
|
|
680
|
+
_formDataParsed = false;
|
|
681
|
+
_cachedQueries = null;
|
|
502
682
|
// Back-reference for release check optimization
|
|
503
683
|
_ctx;
|
|
504
684
|
constructor(ctx) {
|
|
@@ -517,6 +697,11 @@ var FastRequestImpl = class {
|
|
|
517
697
|
this._headers = null;
|
|
518
698
|
this._cachedJson = void 0;
|
|
519
699
|
this._jsonParsed = false;
|
|
700
|
+
this._cachedText = void 0;
|
|
701
|
+
this._textParsed = false;
|
|
702
|
+
this._cachedFormData = void 0;
|
|
703
|
+
this._formDataParsed = false;
|
|
704
|
+
this._cachedQueries = null;
|
|
520
705
|
return this;
|
|
521
706
|
}
|
|
522
707
|
/**
|
|
@@ -530,6 +715,11 @@ var FastRequestImpl = class {
|
|
|
530
715
|
this._headers = null;
|
|
531
716
|
this._cachedJson = void 0;
|
|
532
717
|
this._jsonParsed = false;
|
|
718
|
+
this._cachedText = void 0;
|
|
719
|
+
this._textParsed = false;
|
|
720
|
+
this._cachedFormData = void 0;
|
|
721
|
+
this._formDataParsed = false;
|
|
722
|
+
this._cachedQueries = null;
|
|
533
723
|
}
|
|
534
724
|
checkReleased() {
|
|
535
725
|
if (this._ctx._isReleased) {
|
|
@@ -577,6 +767,9 @@ var FastRequestImpl = class {
|
|
|
577
767
|
}
|
|
578
768
|
queries() {
|
|
579
769
|
this.checkReleased();
|
|
770
|
+
if (this._cachedQueries !== null) {
|
|
771
|
+
return this._cachedQueries;
|
|
772
|
+
}
|
|
580
773
|
if (!this._query) {
|
|
581
774
|
this._query = this.getUrl().searchParams;
|
|
582
775
|
}
|
|
@@ -591,6 +784,7 @@ var FastRequestImpl = class {
|
|
|
591
784
|
result[key] = [existing, value];
|
|
592
785
|
}
|
|
593
786
|
}
|
|
787
|
+
this._cachedQueries = result;
|
|
594
788
|
return result;
|
|
595
789
|
}
|
|
596
790
|
header(name) {
|
|
@@ -617,11 +811,19 @@ var FastRequestImpl = class {
|
|
|
617
811
|
}
|
|
618
812
|
async text() {
|
|
619
813
|
this.checkReleased();
|
|
620
|
-
|
|
814
|
+
if (!this._textParsed) {
|
|
815
|
+
this._cachedText = await this._request.text();
|
|
816
|
+
this._textParsed = true;
|
|
817
|
+
}
|
|
818
|
+
return this._cachedText;
|
|
621
819
|
}
|
|
622
820
|
async formData() {
|
|
623
821
|
this.checkReleased();
|
|
624
|
-
|
|
822
|
+
if (!this._formDataParsed) {
|
|
823
|
+
this._cachedFormData = await this._request.formData();
|
|
824
|
+
this._formDataParsed = true;
|
|
825
|
+
}
|
|
826
|
+
return this._cachedFormData;
|
|
625
827
|
}
|
|
626
828
|
get raw() {
|
|
627
829
|
this.checkReleased();
|
|
@@ -635,6 +837,8 @@ var FastContext = class {
|
|
|
635
837
|
// Reuse this object
|
|
636
838
|
_isReleased = false;
|
|
637
839
|
// Made public for internal check access
|
|
840
|
+
_requestScope = null;
|
|
841
|
+
// Request-scoped services
|
|
638
842
|
/**
|
|
639
843
|
* Initialize context for a new request
|
|
640
844
|
*
|
|
@@ -644,6 +848,7 @@ var FastContext = class {
|
|
|
644
848
|
this._isReleased = false;
|
|
645
849
|
this.req.init(request, params, path, routePattern);
|
|
646
850
|
this._headers = new Headers();
|
|
851
|
+
this._requestScope = new RequestScopeManager();
|
|
647
852
|
return this;
|
|
648
853
|
}
|
|
649
854
|
/**
|
|
@@ -769,6 +974,32 @@ var FastContext = class {
|
|
|
769
974
|
this._store.set(key, value);
|
|
770
975
|
}
|
|
771
976
|
// ─────────────────────────────────────────────────────────────────────────
|
|
977
|
+
// Request Scope Management
|
|
978
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
979
|
+
/**
|
|
980
|
+
* Get the request-scoped service manager for this request.
|
|
981
|
+
*
|
|
982
|
+
* @returns The RequestScopeManager for this request.
|
|
983
|
+
* @throws Error if called before init() or after reset().
|
|
984
|
+
*/
|
|
985
|
+
requestScope() {
|
|
986
|
+
if (!this._requestScope) {
|
|
987
|
+
throw new Error("RequestScope not initialized. Call init() first.");
|
|
988
|
+
}
|
|
989
|
+
return this._requestScope;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Resolve a request-scoped service (convenience method).
|
|
993
|
+
*
|
|
994
|
+
* @template T - The service type.
|
|
995
|
+
* @param key - The service key for caching.
|
|
996
|
+
* @param factory - Factory function to create the service.
|
|
997
|
+
* @returns The cached or newly created service instance.
|
|
998
|
+
*/
|
|
999
|
+
scoped(key, factory) {
|
|
1000
|
+
return this.requestScope().resolve(key, factory);
|
|
1001
|
+
}
|
|
1002
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
772
1003
|
// Lifecycle helpers
|
|
773
1004
|
// ─────────────────────────────────────────────────────────────────────────
|
|
774
1005
|
route = () => "";
|
|
@@ -786,6 +1017,10 @@ var MinimalRequest = class {
|
|
|
786
1017
|
this._routePattern = _routePattern;
|
|
787
1018
|
}
|
|
788
1019
|
_searchParams = null;
|
|
1020
|
+
_cachedQueries = null;
|
|
1021
|
+
_cachedJsonPromise = null;
|
|
1022
|
+
_cachedTextPromise = null;
|
|
1023
|
+
_cachedFormDataPromise = null;
|
|
789
1024
|
get url() {
|
|
790
1025
|
return this._request.url;
|
|
791
1026
|
}
|
|
@@ -825,6 +1060,9 @@ var MinimalRequest = class {
|
|
|
825
1060
|
return this.getSearchParams().get(name) ?? void 0;
|
|
826
1061
|
}
|
|
827
1062
|
queries() {
|
|
1063
|
+
if (this._cachedQueries !== null) {
|
|
1064
|
+
return this._cachedQueries;
|
|
1065
|
+
}
|
|
828
1066
|
const params = this.getSearchParams();
|
|
829
1067
|
const result = {};
|
|
830
1068
|
for (const [key, value] of params.entries()) {
|
|
@@ -837,6 +1075,7 @@ var MinimalRequest = class {
|
|
|
837
1075
|
result[key] = [existing, value];
|
|
838
1076
|
}
|
|
839
1077
|
}
|
|
1078
|
+
this._cachedQueries = result;
|
|
840
1079
|
return result;
|
|
841
1080
|
}
|
|
842
1081
|
header(name) {
|
|
@@ -850,13 +1089,22 @@ var MinimalRequest = class {
|
|
|
850
1089
|
return result;
|
|
851
1090
|
}
|
|
852
1091
|
async json() {
|
|
853
|
-
|
|
1092
|
+
if (this._cachedJsonPromise === null) {
|
|
1093
|
+
this._cachedJsonPromise = this._request.json();
|
|
1094
|
+
}
|
|
1095
|
+
return this._cachedJsonPromise;
|
|
854
1096
|
}
|
|
855
1097
|
async text() {
|
|
856
|
-
|
|
1098
|
+
if (this._cachedTextPromise === null) {
|
|
1099
|
+
this._cachedTextPromise = this._request.text();
|
|
1100
|
+
}
|
|
1101
|
+
return this._cachedTextPromise;
|
|
857
1102
|
}
|
|
858
1103
|
async formData() {
|
|
859
|
-
|
|
1104
|
+
if (this._cachedFormDataPromise === null) {
|
|
1105
|
+
this._cachedFormDataPromise = this._request.formData();
|
|
1106
|
+
}
|
|
1107
|
+
return this._cachedFormDataPromise;
|
|
860
1108
|
}
|
|
861
1109
|
get raw() {
|
|
862
1110
|
return this._request;
|
|
@@ -865,18 +1113,19 @@ var MinimalRequest = class {
|
|
|
865
1113
|
var MinimalContext = class {
|
|
866
1114
|
req;
|
|
867
1115
|
_resHeaders = {};
|
|
1116
|
+
_requestScope;
|
|
868
1117
|
constructor(request, params, path, routePattern) {
|
|
869
1118
|
this.req = new MinimalRequest(request, params, path, routePattern);
|
|
1119
|
+
this._requestScope = new RequestScopeManager();
|
|
870
1120
|
}
|
|
871
1121
|
// get req(): FastRequest {
|
|
872
1122
|
// return this._req
|
|
873
1123
|
// }
|
|
874
1124
|
// Response helpers - merge custom headers with defaults
|
|
1125
|
+
// Optimized: use Object.assign instead of spread to avoid shallow copy overhead
|
|
875
1126
|
getHeaders(contentType) {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
"Content-Type": contentType
|
|
879
|
-
};
|
|
1127
|
+
const headers = Object.assign({ "Content-Type": contentType }, this._resHeaders);
|
|
1128
|
+
return headers;
|
|
880
1129
|
}
|
|
881
1130
|
json(data, status = 200) {
|
|
882
1131
|
return new Response(JSON.stringify(data), {
|
|
@@ -950,6 +1199,25 @@ var MinimalContext = class {
|
|
|
950
1199
|
}
|
|
951
1200
|
set(_key, _value) {
|
|
952
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Get the request-scoped service manager for this request.
|
|
1204
|
+
*
|
|
1205
|
+
* @returns The RequestScopeManager for this request.
|
|
1206
|
+
*/
|
|
1207
|
+
requestScope() {
|
|
1208
|
+
return this._requestScope;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Resolve a request-scoped service (convenience method).
|
|
1212
|
+
*
|
|
1213
|
+
* @template T - The service type.
|
|
1214
|
+
* @param key - The service key for caching.
|
|
1215
|
+
* @param factory - Factory function to create the service.
|
|
1216
|
+
* @returns The cached or newly created service instance.
|
|
1217
|
+
*/
|
|
1218
|
+
scoped(key, factory) {
|
|
1219
|
+
return this._requestScope.resolve(key, factory);
|
|
1220
|
+
}
|
|
953
1221
|
route = () => "";
|
|
954
1222
|
get native() {
|
|
955
1223
|
return this;
|
|
@@ -1067,18 +1335,40 @@ function compileMiddlewareChain(middleware, handler) {
|
|
|
1067
1335
|
if (middleware.length === 0) {
|
|
1068
1336
|
return handler;
|
|
1069
1337
|
}
|
|
1338
|
+
if (middleware.length === 1) {
|
|
1339
|
+
const mw = middleware[0];
|
|
1340
|
+
return async (ctx) => {
|
|
1341
|
+
let nextCalled = false;
|
|
1342
|
+
const result = await mw(ctx, async () => {
|
|
1343
|
+
nextCalled = true;
|
|
1344
|
+
return void 0;
|
|
1345
|
+
});
|
|
1346
|
+
if (result instanceof Response) {
|
|
1347
|
+
return result;
|
|
1348
|
+
}
|
|
1349
|
+
if (nextCalled) {
|
|
1350
|
+
return await handler(ctx);
|
|
1351
|
+
}
|
|
1352
|
+
return ctx.json({ error: "Middleware did not call next or return response" }, 500);
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1070
1355
|
let compiled = handler;
|
|
1071
1356
|
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
1072
1357
|
const mw = middleware[i];
|
|
1073
1358
|
const nextHandler = compiled;
|
|
1074
1359
|
compiled = async (ctx) => {
|
|
1075
|
-
let
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
return
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
|
-
|
|
1360
|
+
let nextCalled = false;
|
|
1361
|
+
const result = await mw(ctx, async () => {
|
|
1362
|
+
nextCalled = true;
|
|
1363
|
+
return void 0;
|
|
1364
|
+
});
|
|
1365
|
+
if (result instanceof Response) {
|
|
1366
|
+
return result;
|
|
1367
|
+
}
|
|
1368
|
+
if (nextCalled) {
|
|
1369
|
+
return await nextHandler(ctx);
|
|
1370
|
+
}
|
|
1371
|
+
return ctx.json({ error: "Middleware did not call next or return response" }, 500);
|
|
1082
1372
|
};
|
|
1083
1373
|
}
|
|
1084
1374
|
return compiled;
|
|
@@ -1095,8 +1385,6 @@ var Gravito = class {
|
|
|
1095
1385
|
isPureStaticApp = true;
|
|
1096
1386
|
// Cache for precompiled dynamic routes
|
|
1097
1387
|
compiledDynamicRoutes = /* @__PURE__ */ new Map();
|
|
1098
|
-
// Version tracking for cache invalidation
|
|
1099
|
-
middlewareVersion = 0;
|
|
1100
1388
|
/**
|
|
1101
1389
|
* Create a new Gravito instance
|
|
1102
1390
|
*
|
|
@@ -1184,7 +1472,6 @@ var Gravito = class {
|
|
|
1184
1472
|
} else {
|
|
1185
1473
|
this.router.use(pathOrMiddleware, ...middleware);
|
|
1186
1474
|
}
|
|
1187
|
-
this.middlewareVersion++;
|
|
1188
1475
|
this.compileRoutes();
|
|
1189
1476
|
return this;
|
|
1190
1477
|
}
|
|
@@ -1277,6 +1564,11 @@ var Gravito = class {
|
|
|
1277
1564
|
} catch (error) {
|
|
1278
1565
|
return await this.handleError(error, ctx);
|
|
1279
1566
|
} finally {
|
|
1567
|
+
try {
|
|
1568
|
+
await ctx.requestScope().cleanup();
|
|
1569
|
+
} catch (cleanupError) {
|
|
1570
|
+
console.error("RequestScope cleanup failed:", cleanupError);
|
|
1571
|
+
}
|
|
1280
1572
|
this.contextPool.release(ctx);
|
|
1281
1573
|
}
|
|
1282
1574
|
}
|
|
@@ -1290,12 +1582,12 @@ var Gravito = class {
|
|
|
1290
1582
|
}
|
|
1291
1583
|
const cacheKey = `${method}:${match.routePattern ?? path}`;
|
|
1292
1584
|
let entry = this.compiledDynamicRoutes.get(cacheKey);
|
|
1293
|
-
if (!entry || entry.version !== this.
|
|
1585
|
+
if (!entry || entry.version !== this.router.version) {
|
|
1294
1586
|
const compiled = compileMiddlewareChain(match.middleware, match.handler);
|
|
1295
1587
|
if (this.compiledDynamicRoutes.size > 1e3) {
|
|
1296
1588
|
this.compiledDynamicRoutes.clear();
|
|
1297
1589
|
}
|
|
1298
|
-
entry = { compiled, version: this.
|
|
1590
|
+
entry = { compiled, version: this.router.version };
|
|
1299
1591
|
this.compiledDynamicRoutes.set(cacheKey, entry);
|
|
1300
1592
|
}
|
|
1301
1593
|
const ctx = this.contextPool.acquire();
|
|
@@ -1306,6 +1598,11 @@ var Gravito = class {
|
|
|
1306
1598
|
} catch (error) {
|
|
1307
1599
|
return await this.handleError(error, ctx);
|
|
1308
1600
|
} finally {
|
|
1601
|
+
try {
|
|
1602
|
+
await ctx.requestScope().cleanup();
|
|
1603
|
+
} catch (cleanupError) {
|
|
1604
|
+
console.error("RequestScope cleanup failed:", cleanupError);
|
|
1605
|
+
}
|
|
1309
1606
|
this.contextPool.release(ctx);
|
|
1310
1607
|
}
|
|
1311
1608
|
};
|
|
@@ -1364,7 +1661,7 @@ var Gravito = class {
|
|
|
1364
1661
|
const hasPathMiddleware = this.router.pathMiddleware.size > 0;
|
|
1365
1662
|
this.isPureStaticApp = !hasGlobalMiddleware && !hasPathMiddleware;
|
|
1366
1663
|
for (const [key, route] of this.staticRoutes) {
|
|
1367
|
-
if (route.compiledVersion === this.
|
|
1664
|
+
if (route.compiledVersion === this.router.version) {
|
|
1368
1665
|
continue;
|
|
1369
1666
|
}
|
|
1370
1667
|
const analysis = analyzeHandler(route.handler);
|
|
@@ -1374,7 +1671,7 @@ var Gravito = class {
|
|
|
1374
1671
|
const allMiddleware = this.collectMiddlewareForPath(key.split(":")[1], route.middleware);
|
|
1375
1672
|
route.compiled = compileMiddlewareChain(allMiddleware, route.handler);
|
|
1376
1673
|
}
|
|
1377
|
-
route.compiledVersion = this.
|
|
1674
|
+
route.compiledVersion = this.router.version;
|
|
1378
1675
|
}
|
|
1379
1676
|
}
|
|
1380
1677
|
/**
|