@gravito/core 1.2.0 → 1.6.0

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.
@@ -239,13 +239,25 @@ var RadixRouter = class _RadixRouter {
239
239
  // src/engine/AOTRouter.ts
240
240
  var AOTRouter = class {
241
241
  // Static route cache: "METHOD:PATH" -> RouteMetadata
242
+ /** @internal */
242
243
  staticRoutes = /* @__PURE__ */ new Map();
243
244
  // Dynamic route handler (Radix Tree)
244
245
  dynamicRouter = new RadixRouter();
246
+ // Store all route definitions to support mounting/merging
247
+ /** @internal */
248
+ routeDefinitions = [];
245
249
  // Global middleware (applies to all routes)
250
+ /** @internal */
246
251
  globalMiddleware = [];
247
252
  // Path-based middleware: pattern -> middleware[]
253
+ /** @internal */
248
254
  pathMiddleware = /* @__PURE__ */ new Map();
255
+ // Dynamic route patterns: handler function -> route pattern
256
+ // 用於追蹤動態路由的模式,防止高基數問題
257
+ dynamicRoutePatterns = /* @__PURE__ */ new Map();
258
+ middlewareCache = /* @__PURE__ */ new Map();
259
+ cacheMaxSize = 1e3;
260
+ version = 0;
249
261
  /**
250
262
  * Register a route
251
263
  *
@@ -259,6 +271,7 @@ var AOTRouter = class {
259
271
  * @param middleware - Route-specific middleware
260
272
  */
261
273
  add(method, path, handler, middleware = []) {
274
+ this.routeDefinitions.push({ method, path, handler, middleware });
262
275
  const normalizedMethod = method.toLowerCase();
263
276
  if (this.isStaticPath(path)) {
264
277
  const key = `${normalizedMethod}:${path}`;
@@ -266,11 +279,47 @@ var AOTRouter = class {
266
279
  } else {
267
280
  const wrappedHandler = handler;
268
281
  this.dynamicRouter.add(normalizedMethod, path, [wrappedHandler]);
282
+ this.dynamicRoutePatterns.set(wrappedHandler, path);
269
283
  if (middleware.length > 0) {
270
284
  this.pathMiddleware.set(`${normalizedMethod}:${path}`, middleware);
271
285
  }
272
286
  }
273
287
  }
288
+ /**
289
+ * Mount another router at a prefix
290
+ */
291
+ mount(prefix, other) {
292
+ if (other.globalMiddleware.length > 0) {
293
+ this.usePattern(prefix, ...other.globalMiddleware);
294
+ const wildcard = prefix === "/" ? "/*" : `${prefix}/*`;
295
+ this.usePattern(wildcard, ...other.globalMiddleware);
296
+ }
297
+ for (const [pattern, mws] of other.pathMiddleware) {
298
+ if (pattern.includes(":")) {
299
+ continue;
300
+ }
301
+ let newPattern;
302
+ if (pattern === "*") {
303
+ newPattern = prefix === "/" ? "/*" : `${prefix}/*`;
304
+ } else if (pattern.startsWith("/")) {
305
+ newPattern = prefix === "/" ? pattern : `${prefix}${pattern}`;
306
+ } else {
307
+ newPattern = prefix === "/" ? `/${pattern}` : `${prefix}/${pattern}`;
308
+ }
309
+ this.usePattern(newPattern, ...mws);
310
+ }
311
+ for (const def of other.routeDefinitions) {
312
+ let newPath;
313
+ if (prefix === "/") {
314
+ newPath = def.path;
315
+ } else if (def.path === "/") {
316
+ newPath = prefix;
317
+ } else {
318
+ newPath = `${prefix}${def.path}`;
319
+ }
320
+ this.add(def.method, newPath, def.handler, def.middleware);
321
+ }
322
+ }
274
323
  /**
275
324
  * Add global middleware
276
325
  *
@@ -280,6 +329,7 @@ var AOTRouter = class {
280
329
  */
281
330
  use(...middleware) {
282
331
  this.globalMiddleware.push(...middleware);
332
+ this.version++;
283
333
  }
284
334
  /**
285
335
  * Add path-based middleware
@@ -290,8 +340,13 @@ var AOTRouter = class {
290
340
  * @param middleware - Middleware functions
291
341
  */
292
342
  usePattern(pattern, ...middleware) {
293
- const existing = this.pathMiddleware.get(pattern) ?? [];
294
- this.pathMiddleware.set(pattern, [...existing, ...middleware]);
343
+ if (pattern === "*") {
344
+ this.globalMiddleware.push(...middleware);
345
+ } else {
346
+ const existing = this.pathMiddleware.get(pattern) ?? [];
347
+ this.pathMiddleware.set(pattern, [...existing, ...middleware]);
348
+ }
349
+ this.version++;
295
350
  }
296
351
  /**
297
352
  * Match a request to a route
@@ -310,18 +365,22 @@ var AOTRouter = class {
310
365
  return {
311
366
  handler: staticRoute.handler,
312
367
  params: {},
313
- middleware: this.collectMiddleware(path, staticRoute.middleware)
368
+ middleware: this.collectMiddleware(path, staticRoute.middleware),
369
+ routePattern: path
314
370
  };
315
371
  }
316
372
  const match = this.dynamicRouter.match(normalizedMethod, path);
317
373
  if (match && match.handlers.length > 0) {
318
374
  const handler = match.handlers[0];
319
- const routeKey = this.findDynamicRouteKey(normalizedMethod, path);
375
+ const wrappedHandler = match.handlers[0];
376
+ const routePattern = this.dynamicRoutePatterns.get(wrappedHandler);
377
+ const routeKey = routePattern ? `${normalizedMethod}:${routePattern}` : null;
320
378
  const routeMiddleware = routeKey ? this.pathMiddleware.get(routeKey) ?? [] : [];
321
379
  return {
322
380
  handler,
323
381
  params: match.params,
324
- middleware: this.collectMiddleware(path, routeMiddleware)
382
+ middleware: this.collectMiddleware(path, routeMiddleware),
383
+ routePattern
325
384
  };
326
385
  }
327
386
  return {
@@ -349,13 +408,20 @@ var AOTRouter = class {
349
408
  if (this.globalMiddleware.length === 0 && this.pathMiddleware.size === 0 && routeMiddleware.length === 0) {
350
409
  return [];
351
410
  }
411
+ const cacheKey = `${path}:${routeMiddleware.length}`;
412
+ const cached = this.middlewareCache.get(cacheKey);
413
+ if (cached !== void 0 && cached.version === this.version) {
414
+ return cached.data;
415
+ }
352
416
  const middleware = [];
353
417
  if (this.globalMiddleware.length > 0) {
354
418
  middleware.push(...this.globalMiddleware);
355
419
  }
356
420
  if (this.pathMiddleware.size > 0) {
357
421
  for (const [pattern, mw] of this.pathMiddleware) {
358
- if (pattern.includes(":")) continue;
422
+ if (pattern.includes(":")) {
423
+ continue;
424
+ }
359
425
  if (this.matchPattern(pattern, path)) {
360
426
  middleware.push(...mw);
361
427
  }
@@ -364,6 +430,9 @@ var AOTRouter = class {
364
430
  if (routeMiddleware.length > 0) {
365
431
  middleware.push(...routeMiddleware);
366
432
  }
433
+ if (this.middlewareCache.size < this.cacheMaxSize) {
434
+ this.middlewareCache.set(cacheKey, { data: middleware, version: this.version });
435
+ }
367
436
  return middleware;
368
437
  }
369
438
  /**
@@ -384,32 +453,18 @@ var AOTRouter = class {
384
453
  * @returns True if pattern matches
385
454
  */
386
455
  matchPattern(pattern, path) {
387
- if (pattern === "*") return true;
388
- if (pattern === path) return true;
456
+ if (pattern === "*") {
457
+ return true;
458
+ }
459
+ if (pattern === path) {
460
+ return true;
461
+ }
389
462
  if (pattern.endsWith("/*")) {
390
463
  const prefix = pattern.slice(0, -2);
391
464
  return path.startsWith(prefix);
392
465
  }
393
466
  return false;
394
467
  }
395
- /**
396
- * Find the original route key for a matched dynamic route
397
- *
398
- * This is needed to look up route-specific middleware.
399
- * It's a bit of a hack, but avoids storing duplicate data.
400
- *
401
- * @param method - HTTP method
402
- * @param path - Matched path
403
- * @returns Route key or null
404
- */
405
- findDynamicRouteKey(method, _path) {
406
- for (const key of this.pathMiddleware.keys()) {
407
- if (key.startsWith(`${method}:`)) {
408
- return key;
409
- }
410
- }
411
- return null;
412
- }
413
468
  /**
414
469
  * Get all registered routes (for debugging)
415
470
  */
@@ -450,48 +505,111 @@ function getOptimalContextType(analysis) {
450
505
  return "fast";
451
506
  }
452
507
 
508
+ // src/engine/constants.ts
509
+ var encoder = new TextEncoder();
510
+ var CACHED_RESPONSES = {
511
+ NOT_FOUND: encoder.encode('{"error":"Not Found"}'),
512
+ INTERNAL_ERROR: encoder.encode('{"error":"Internal Server Error"}'),
513
+ OK: encoder.encode('{"ok":true}'),
514
+ EMPTY: new Uint8Array(0)
515
+ };
516
+ var HEADERS = {
517
+ JSON: { "Content-Type": "application/json; charset=utf-8" },
518
+ TEXT: { "Content-Type": "text/plain; charset=utf-8" },
519
+ HTML: { "Content-Type": "text/html; charset=utf-8" }
520
+ };
521
+
453
522
  // src/engine/FastContext.ts
454
523
  var FastRequestImpl = class {
455
524
  _request;
456
525
  _params;
457
- _url = new URL("http://localhost");
458
- // Reuse this object
526
+ _path;
527
+ _routePattern;
528
+ _url = null;
459
529
  _query = null;
460
530
  _headers = null;
531
+ _cachedJson = void 0;
532
+ _jsonParsed = false;
533
+ // Back-reference for release check optimization
534
+ _ctx;
535
+ constructor(ctx) {
536
+ this._ctx = ctx;
537
+ }
461
538
  /**
462
- * Reset for pooling
539
+ * Initialize for new request
463
540
  */
464
- reset(request, params = {}) {
541
+ init(request, params = {}, path = "", routePattern) {
465
542
  this._request = request;
466
543
  this._params = params;
467
- this._url.href = request.url;
544
+ this._path = path;
545
+ this._routePattern = routePattern;
546
+ this._url = null;
468
547
  this._query = null;
469
548
  this._headers = null;
549
+ this._cachedJson = void 0;
550
+ this._jsonParsed = false;
551
+ return this;
552
+ }
553
+ /**
554
+ * Reset for pooling
555
+ */
556
+ reset() {
557
+ this._request = void 0;
558
+ this._params = void 0;
559
+ this._url = null;
560
+ this._query = null;
561
+ this._headers = null;
562
+ this._cachedJson = void 0;
563
+ this._jsonParsed = false;
564
+ }
565
+ checkReleased() {
566
+ if (this._ctx._isReleased) {
567
+ throw new Error(
568
+ "FastContext usage after release detected! (Object Pool Strict Lifecycle Guard)"
569
+ );
570
+ }
470
571
  }
471
572
  get url() {
573
+ this.checkReleased();
472
574
  return this._request.url;
473
575
  }
474
576
  get method() {
577
+ this.checkReleased();
475
578
  return this._request.method;
476
579
  }
477
580
  get path() {
478
- return this._url.pathname;
581
+ this.checkReleased();
582
+ return this._path;
583
+ }
584
+ get routePattern() {
585
+ this.checkReleased();
586
+ return this._routePattern;
479
587
  }
480
588
  param(name) {
589
+ this.checkReleased();
481
590
  return this._params[name];
482
591
  }
483
592
  params() {
593
+ this.checkReleased();
484
594
  return { ...this._params };
485
595
  }
596
+ getUrl() {
597
+ if (!this._url) {
598
+ this._url = new URL(this._request.url);
599
+ }
600
+ return this._url;
601
+ }
486
602
  query(name) {
603
+ this.checkReleased();
487
604
  if (!this._query) {
488
- this._query = this._url.searchParams;
605
+ this._query = this.getUrl().searchParams;
489
606
  }
490
607
  return this._query.get(name) ?? void 0;
491
608
  }
492
609
  queries() {
610
+ this.checkReleased();
493
611
  if (!this._query) {
494
- this._query = this._url.searchParams;
612
+ this._query = this.getUrl().searchParams;
495
613
  }
496
614
  const result = {};
497
615
  for (const [key, value] of this._query.entries()) {
@@ -507,9 +625,11 @@ var FastRequestImpl = class {
507
625
  return result;
508
626
  }
509
627
  header(name) {
628
+ this.checkReleased();
510
629
  return this._request.headers.get(name) ?? void 0;
511
630
  }
512
631
  headers() {
632
+ this.checkReleased();
513
633
  if (!this._headers) {
514
634
  this._headers = {};
515
635
  for (const [key, value] of this._request.headers.entries()) {
@@ -519,41 +639,70 @@ var FastRequestImpl = class {
519
639
  return { ...this._headers };
520
640
  }
521
641
  async json() {
522
- return this._request.json();
642
+ this.checkReleased();
643
+ if (!this._jsonParsed) {
644
+ this._cachedJson = await this._request.json();
645
+ this._jsonParsed = true;
646
+ }
647
+ return this._cachedJson;
523
648
  }
524
649
  async text() {
650
+ this.checkReleased();
525
651
  return this._request.text();
526
652
  }
527
653
  async formData() {
654
+ this.checkReleased();
528
655
  return this._request.formData();
529
656
  }
530
657
  get raw() {
658
+ this.checkReleased();
531
659
  return this._request;
532
660
  }
533
661
  };
534
662
  var FastContext = class {
535
- _req = new FastRequestImpl();
663
+ req = new FastRequestImpl(this);
536
664
  // private _statusCode = 200
537
665
  _headers = new Headers();
538
666
  // Reuse this object
667
+ _isReleased = false;
668
+ // Made public for internal check access
539
669
  /**
540
- * Reset context for pooling
670
+ * Initialize context for a new request
541
671
  *
542
672
  * This is called when acquiring from the pool.
543
- * Must clear all state from previous request.
544
673
  */
545
- reset(request, params = {}) {
546
- this._req.reset(request, params);
674
+ init(request, params = {}, path = "", routePattern) {
675
+ this._isReleased = false;
676
+ this.req.init(request, params, path, routePattern);
547
677
  this._headers = new Headers();
548
678
  return this;
549
679
  }
550
- get req() {
551
- return this._req;
680
+ /**
681
+ * Reset context for pooling (Cleanup)
682
+ *
683
+ * This is called when releasing back to the pool.
684
+ * Implements "Deep-Reset Protocol" and "Release Guard".
685
+ */
686
+ reset() {
687
+ this._isReleased = true;
688
+ this.req.reset();
689
+ this._store.clear();
690
+ }
691
+ /**
692
+ * Check if context is released
693
+ */
694
+ checkReleased() {
695
+ if (this._isReleased) {
696
+ throw new Error(
697
+ "FastContext usage after release detected! (Object Pool Strict Lifecycle Guard)"
698
+ );
699
+ }
552
700
  }
553
701
  // ─────────────────────────────────────────────────────────────────────────
554
702
  // Response Helpers
555
703
  // ─────────────────────────────────────────────────────────────────────────
556
704
  json(data, status = 200) {
705
+ this.checkReleased();
557
706
  this._headers.set("Content-Type", "application/json; charset=utf-8");
558
707
  return new Response(JSON.stringify(data), {
559
708
  status,
@@ -561,6 +710,7 @@ var FastContext = class {
561
710
  });
562
711
  }
563
712
  text(text, status = 200) {
713
+ this.checkReleased();
564
714
  this._headers.set("Content-Type", "text/plain; charset=utf-8");
565
715
  return new Response(text, {
566
716
  status,
@@ -568,6 +718,7 @@ var FastContext = class {
568
718
  });
569
719
  }
570
720
  html(html, status = 200) {
721
+ this.checkReleased();
571
722
  this._headers.set("Content-Type", "text/html; charset=utf-8");
572
723
  return new Response(html, {
573
724
  status,
@@ -575,6 +726,7 @@ var FastContext = class {
575
726
  });
576
727
  }
577
728
  redirect(url, status = 302) {
729
+ this.checkReleased();
578
730
  this._headers.set("Location", url);
579
731
  return new Response(null, {
580
732
  status,
@@ -582,28 +734,89 @@ var FastContext = class {
582
734
  });
583
735
  }
584
736
  body(data, status = 200) {
737
+ this.checkReleased();
585
738
  return new Response(data, {
586
739
  status,
587
740
  headers: this._headers
588
741
  });
589
742
  }
590
- // ─────────────────────────────────────────────────────────────────────────
591
- // Header Management
592
- // ─────────────────────────────────────────────────────────────────────────
743
+ stream(stream, status = 200) {
744
+ this.checkReleased();
745
+ this._headers.set("Content-Type", "application/octet-stream");
746
+ return new Response(stream, {
747
+ status,
748
+ headers: this._headers
749
+ });
750
+ }
751
+ notFound(message = "Not Found") {
752
+ return this.text(message, 404);
753
+ }
754
+ forbidden(message = "Forbidden") {
755
+ return this.text(message, 403);
756
+ }
757
+ unauthorized(message = "Unauthorized") {
758
+ return this.text(message, 401);
759
+ }
760
+ badRequest(message = "Bad Request") {
761
+ return this.text(message, 400);
762
+ }
763
+ async forward(target, _options = {}) {
764
+ this.checkReleased();
765
+ const url = new URL(this.req.url);
766
+ const targetUrl = new URL(
767
+ target.startsWith("http") ? target : `${url.protocol}//${target}${this.req.path}`
768
+ );
769
+ const searchParams = new URLSearchParams(url.search);
770
+ searchParams.forEach((v, k) => {
771
+ targetUrl.searchParams.set(k, v);
772
+ });
773
+ return fetch(targetUrl.toString(), {
774
+ method: this.req.method,
775
+ headers: this.req.raw.headers,
776
+ body: this.req.method !== "GET" && this.req.method !== "HEAD" ? this.req.raw.body : null,
777
+ // @ts-expect-error - Bun/Fetch specific
778
+ duplex: "half"
779
+ });
780
+ }
593
781
  header(name, value) {
594
- this._headers.set(name, value);
782
+ this.checkReleased();
783
+ if (value !== void 0) {
784
+ this._headers.set(name, value);
785
+ return;
786
+ }
787
+ return this.req.header(name);
595
788
  }
596
789
  status(_code) {
790
+ this.checkReleased();
791
+ }
792
+ // ─────────────────────────────────────────────────────────────────────────
793
+ // Context Variables
794
+ // ─────────────────────────────────────────────────────────────────────────
795
+ _store = /* @__PURE__ */ new Map();
796
+ get(key) {
797
+ return this._store.get(key);
798
+ }
799
+ set(key, value) {
800
+ this._store.set(key, value);
801
+ }
802
+ // ─────────────────────────────────────────────────────────────────────────
803
+ // Lifecycle helpers
804
+ // ─────────────────────────────────────────────────────────────────────────
805
+ route = () => "";
806
+ get native() {
807
+ return this;
597
808
  }
598
809
  };
599
810
 
600
811
  // src/engine/MinimalContext.ts
601
812
  var MinimalRequest = class {
602
- constructor(_request, _params, _path) {
813
+ constructor(_request, _params, _path, _routePattern) {
603
814
  this._request = _request;
604
815
  this._params = _params;
605
816
  this._path = _path;
817
+ this._routePattern = _routePattern;
606
818
  }
819
+ _searchParams = null;
607
820
  get url() {
608
821
  return this._request.url;
609
822
  }
@@ -613,20 +826,39 @@ var MinimalRequest = class {
613
826
  get path() {
614
827
  return this._path;
615
828
  }
829
+ get routePattern() {
830
+ return this._routePattern;
831
+ }
616
832
  param(name) {
617
833
  return this._params[name];
618
834
  }
619
835
  params() {
620
836
  return { ...this._params };
621
837
  }
838
+ /**
839
+ * Lazy-initialize searchParams, only parse once
840
+ */
841
+ getSearchParams() {
842
+ if (this._searchParams === null) {
843
+ const url = this._request.url;
844
+ const queryStart = url.indexOf("?");
845
+ if (queryStart === -1) {
846
+ this._searchParams = new URLSearchParams();
847
+ } else {
848
+ const hashStart = url.indexOf("#", queryStart);
849
+ const queryString = hashStart === -1 ? url.slice(queryStart + 1) : url.slice(queryStart + 1, hashStart);
850
+ this._searchParams = new URLSearchParams(queryString);
851
+ }
852
+ }
853
+ return this._searchParams;
854
+ }
622
855
  query(name) {
623
- const url = new URL(this._request.url);
624
- return url.searchParams.get(name) ?? void 0;
856
+ return this.getSearchParams().get(name) ?? void 0;
625
857
  }
626
858
  queries() {
627
- const url = new URL(this._request.url);
859
+ const params = this.getSearchParams();
628
860
  const result = {};
629
- for (const [key, value] of url.searchParams.entries()) {
861
+ for (const [key, value] of params.entries()) {
630
862
  const existing = result[key];
631
863
  if (existing === void 0) {
632
864
  result[key] = value;
@@ -662,49 +894,103 @@ var MinimalRequest = class {
662
894
  }
663
895
  };
664
896
  var MinimalContext = class {
665
- _req;
666
- constructor(request, params, path) {
667
- this._req = new MinimalRequest(request, params, path);
897
+ req;
898
+ _resHeaders = {};
899
+ constructor(request, params, path, routePattern) {
900
+ this.req = new MinimalRequest(request, params, path, routePattern);
668
901
  }
669
- get req() {
670
- return this._req;
902
+ // get req(): FastRequest {
903
+ // return this._req
904
+ // }
905
+ // Response helpers - merge custom headers with defaults
906
+ getHeaders(contentType) {
907
+ return {
908
+ ...this._resHeaders,
909
+ "Content-Type": contentType
910
+ };
671
911
  }
672
- // Response helpers - create headers inline (no reuse overhead)
673
912
  json(data, status = 200) {
674
913
  return new Response(JSON.stringify(data), {
675
914
  status,
676
- headers: { "Content-Type": "application/json; charset=utf-8" }
915
+ headers: this.getHeaders("application/json; charset=utf-8")
677
916
  });
678
917
  }
679
918
  text(text, status = 200) {
680
919
  return new Response(text, {
681
920
  status,
682
- headers: { "Content-Type": "text/plain; charset=utf-8" }
921
+ headers: this.getHeaders("text/plain; charset=utf-8")
683
922
  });
684
923
  }
685
924
  html(html, status = 200) {
686
925
  return new Response(html, {
687
926
  status,
688
- headers: { "Content-Type": "text/html; charset=utf-8" }
927
+ headers: this.getHeaders("text/html; charset=utf-8")
689
928
  });
690
929
  }
691
930
  redirect(url, status = 302) {
692
931
  return new Response(null, {
693
932
  status,
694
- headers: { Location: url }
933
+ headers: { ...this._resHeaders, Location: url }
695
934
  });
696
935
  }
697
936
  body(data, status = 200) {
698
- return new Response(data, { status });
937
+ return new Response(data, {
938
+ status,
939
+ headers: this._resHeaders
940
+ });
699
941
  }
700
- header(_name, _value) {
701
- console.warn("MinimalContext.header() is a no-op. Use FastContext for custom headers.");
942
+ header(name, value) {
943
+ if (value !== void 0) {
944
+ this._resHeaders[name] = value;
945
+ return;
946
+ }
947
+ return this.req.header(name);
702
948
  }
703
949
  status(_code) {
704
950
  }
951
+ stream(stream, status = 200) {
952
+ return new Response(stream, {
953
+ status,
954
+ headers: this.getHeaders("application/octet-stream")
955
+ });
956
+ }
957
+ notFound(message = "Not Found") {
958
+ return this.text(message, 404);
959
+ }
960
+ forbidden(message = "Forbidden") {
961
+ return this.text(message, 403);
962
+ }
963
+ unauthorized(message = "Unauthorized") {
964
+ return this.text(message, 401);
965
+ }
966
+ badRequest(message = "Bad Request") {
967
+ return this.text(message, 400);
968
+ }
969
+ async forward(target, _options = {}) {
970
+ const url = new URL(this.req.url);
971
+ const targetUrl = new URL(
972
+ target.startsWith("http") ? target : `${url.protocol}//${target}${this.req.path}`
973
+ );
974
+ return fetch(targetUrl.toString(), {
975
+ method: this.req.method,
976
+ headers: this.req.raw.headers
977
+ });
978
+ }
979
+ get(_key) {
980
+ return void 0;
981
+ }
982
+ set(_key, _value) {
983
+ }
984
+ route = () => "";
985
+ get native() {
986
+ return this;
987
+ }
705
988
  // Required for interface compatibility
706
- reset(_request, _params) {
707
- throw new Error("MinimalContext does not support reset. Create a new instance instead.");
989
+ init(_request, _params, _path) {
990
+ throw new Error("MinimalContext does not support init. Create a new instance instead.");
991
+ }
992
+ // Required for interface compatibility
993
+ reset() {
708
994
  }
709
995
  };
710
996
 
@@ -808,16 +1094,40 @@ var ObjectPool = class {
808
1094
  };
809
1095
 
810
1096
  // src/engine/Gravito.ts
1097
+ function compileMiddlewareChain(middleware, handler) {
1098
+ if (middleware.length === 0) {
1099
+ return handler;
1100
+ }
1101
+ let compiled = handler;
1102
+ for (let i = middleware.length - 1; i >= 0; i--) {
1103
+ const mw = middleware[i];
1104
+ const nextHandler = compiled;
1105
+ compiled = async (ctx) => {
1106
+ let nextResult;
1107
+ const next = async () => {
1108
+ nextResult = await nextHandler(ctx);
1109
+ return nextResult;
1110
+ };
1111
+ const result = await mw(ctx, next);
1112
+ return result ?? nextResult;
1113
+ };
1114
+ }
1115
+ return compiled;
1116
+ }
811
1117
  var Gravito = class {
812
1118
  router = new AOTRouter();
813
1119
  contextPool;
814
1120
  errorHandler;
815
1121
  notFoundHandler;
816
1122
  // Direct reference to static routes Map (O(1) access)
817
- // Optimization: Bypass getter/setter overhead
1123
+ /** @internal */
818
1124
  staticRoutes;
819
1125
  // Flag: pure static app (no middleware at all) allows ultra-fast path
820
1126
  isPureStaticApp = true;
1127
+ // Cache for precompiled dynamic routes
1128
+ compiledDynamicRoutes = /* @__PURE__ */ new Map();
1129
+ // Version tracking for cache invalidation
1130
+ middlewareVersion = 0;
821
1131
  /**
822
1132
  * Create a new Gravito instance
823
1133
  *
@@ -827,7 +1137,7 @@ var Gravito = class {
827
1137
  const poolSize = options.poolSize ?? 256;
828
1138
  this.contextPool = new ObjectPool(
829
1139
  () => new FastContext(),
830
- (ctx) => ctx.reset(new Request("http://localhost")),
1140
+ (ctx) => ctx.reset(),
831
1141
  poolSize
832
1142
  );
833
1143
  this.contextPool.prewarm(Math.min(32, poolSize));
@@ -871,7 +1181,7 @@ var Gravito = class {
871
1181
  return this.addRoute("delete", path, handlers);
872
1182
  }
873
1183
  /**
874
- * Register a PATCH route
1184
+ * Register a PDF route
875
1185
  */
876
1186
  patch(path, ...handlers) {
877
1187
  return this.addRoute("patch", path, handlers);
@@ -905,6 +1215,8 @@ var Gravito = class {
905
1215
  } else {
906
1216
  this.router.use(pathOrMiddleware, ...middleware);
907
1217
  }
1218
+ this.middlewareVersion++;
1219
+ this.compileRoutes();
908
1220
  return this;
909
1221
  }
910
1222
  // ─────────────────────────────────────────────────────────────────────────
@@ -912,19 +1224,10 @@ var Gravito = class {
912
1224
  // ─────────────────────────────────────────────────────────────────────────
913
1225
  /**
914
1226
  * Mount a sub-application at a path prefix
915
- *
916
- * @example
917
- * ```typescript
918
- * const api = new Gravito()
919
- * api.get('/users', (c) => c.json({ users: [] }))
920
- *
921
- * const app = new Gravito()
922
- * app.route('/api', api)
923
- * // Now accessible at /api/users
924
- * ```
925
1227
  */
926
- route(_path, _app) {
927
- console.warn("route() method is not yet fully implemented");
1228
+ route(path, app) {
1229
+ this.router.mount(path, app.router);
1230
+ this.compileRoutes();
928
1231
  return this;
929
1232
  }
930
1233
  // ─────────────────────────────────────────────────────────────────────────
@@ -932,14 +1235,6 @@ var Gravito = class {
932
1235
  // ─────────────────────────────────────────────────────────────────────────
933
1236
  /**
934
1237
  * Set custom error handler
935
- *
936
- * @example
937
- * ```typescript
938
- * app.onError((err, c) => {
939
- * console.error(err)
940
- * return c.json({ error: err.message }, 500)
941
- * })
942
- * ```
943
1238
  */
944
1239
  onError(handler) {
945
1240
  this.errorHandler = handler;
@@ -947,13 +1242,6 @@ var Gravito = class {
947
1242
  }
948
1243
  /**
949
1244
  * Set custom 404 handler
950
- *
951
- * @example
952
- * ```typescript
953
- * app.notFound((c) => {
954
- * return c.json({ error: 'Not Found' }, 404)
955
- * })
956
- * ```
957
1245
  */
958
1246
  notFound(handler) {
959
1247
  this.notFoundHandler = handler;
@@ -963,35 +1251,44 @@ var Gravito = class {
963
1251
  // Request Handling (Bun.serve integration)
964
1252
  // ─────────────────────────────────────────────────────────────────────────
965
1253
  /**
966
- * Handle an incoming request
1254
+ * Predictive Route Warming (JIT Optimization)
967
1255
  *
968
- * Optimized for minimal allocations and maximum throughput.
969
- * Uses sync/async dual-path strategy inspired by Hono.
1256
+ * Simulates requests to specified routes to trigger JIT compilation (FTL)
1257
+ * before real traffic arrives.
970
1258
  *
971
- * @param request - Incoming Request object
972
- * @returns Response object (sync or async)
1259
+ * @param paths List of paths to warm up (e.g. ['/api/users', '/health'])
1260
+ */
1261
+ async warmup(paths) {
1262
+ const dummyReqOpts = { headers: { "User-Agent": "Gravito-Warmup/1.0" } };
1263
+ for (const path of paths) {
1264
+ const req = new Request(`http://localhost${path}`, dummyReqOpts);
1265
+ await this.fetch(req);
1266
+ }
1267
+ }
1268
+ /**
1269
+ * Handle an incoming request
973
1270
  */
974
- fetch = (request) => {
1271
+ fetch = async (request) => {
975
1272
  const path = extractPath(request.url);
976
1273
  const method = request.method.toLowerCase();
977
1274
  const staticKey = `${method}:${path}`;
978
1275
  const staticRoute = this.staticRoutes.get(staticKey);
979
1276
  if (staticRoute) {
980
1277
  if (staticRoute.useMinimal) {
981
- const ctx = new MinimalContext(request, {}, path);
1278
+ const ctx = new MinimalContext(request, {}, path, path);
982
1279
  try {
983
1280
  const result = staticRoute.handler(ctx);
984
1281
  if (result instanceof Response) {
985
1282
  return result;
986
1283
  }
987
- return result;
1284
+ return await result;
988
1285
  } catch (error) {
989
1286
  return this.handleErrorSync(error, request, path);
990
1287
  }
991
1288
  }
992
- return this.handleWithMiddleware(request, path, staticRoute);
1289
+ return await this.handleWithMiddleware(request, path, staticRoute);
993
1290
  }
994
- return this.handleDynamicRoute(request, method, path);
1291
+ return await this.handleDynamicRoute(request, method, path);
995
1292
  };
996
1293
  /**
997
1294
  * Handle routes with middleware (async path)
@@ -999,7 +1296,10 @@ var Gravito = class {
999
1296
  async handleWithMiddleware(request, path, route) {
1000
1297
  const ctx = this.contextPool.acquire();
1001
1298
  try {
1002
- ctx.reset(request, {});
1299
+ ctx.init(request, {}, path, path);
1300
+ if (route.compiled) {
1301
+ return await route.compiled(ctx);
1302
+ }
1003
1303
  const middleware = this.collectMiddlewareForPath(path, route.middleware);
1004
1304
  if (middleware.length === 0) {
1005
1305
  return await route.handler(ctx);
@@ -1019,14 +1319,21 @@ var Gravito = class {
1019
1319
  if (!match.handler) {
1020
1320
  return this.handleNotFoundSync(request, path);
1021
1321
  }
1322
+ const cacheKey = `${method}:${match.routePattern ?? path}`;
1323
+ let entry = this.compiledDynamicRoutes.get(cacheKey);
1324
+ if (!entry || entry.version !== this.middlewareVersion) {
1325
+ const compiled = compileMiddlewareChain(match.middleware, match.handler);
1326
+ if (this.compiledDynamicRoutes.size > 1e3) {
1327
+ this.compiledDynamicRoutes.clear();
1328
+ }
1329
+ entry = { compiled, version: this.middlewareVersion };
1330
+ this.compiledDynamicRoutes.set(cacheKey, entry);
1331
+ }
1022
1332
  const ctx = this.contextPool.acquire();
1023
1333
  const execute = async () => {
1024
1334
  try {
1025
- ctx.reset(request, match.params);
1026
- if (match.middleware.length === 0) {
1027
- return await match.handler(ctx);
1028
- }
1029
- return await this.executeMiddleware(ctx, match.middleware, match.handler);
1335
+ ctx.init(request, match.params, path, match.routePattern);
1336
+ return await entry?.compiled(ctx);
1030
1337
  } catch (error) {
1031
1338
  return await this.handleError(error, ctx);
1032
1339
  } finally {
@@ -1048,16 +1355,10 @@ var Gravito = class {
1048
1355
  return result;
1049
1356
  }
1050
1357
  console.error("Unhandled error:", error);
1051
- return new Response(
1052
- JSON.stringify({
1053
- error: "Internal Server Error",
1054
- message: error.message
1055
- }),
1056
- {
1057
- status: 500,
1058
- headers: { "Content-Type": "application/json" }
1059
- }
1060
- );
1358
+ return new Response(CACHED_RESPONSES.INTERNAL_ERROR, {
1359
+ status: 500,
1360
+ headers: HEADERS.JSON
1361
+ });
1061
1362
  }
1062
1363
  /**
1063
1364
  * Sync 404 handler (for ultra-fast path)
@@ -1071,14 +1372,13 @@ var Gravito = class {
1071
1372
  }
1072
1373
  return result;
1073
1374
  }
1074
- return new Response(JSON.stringify({ error: "Not Found" }), {
1375
+ return new Response(CACHED_RESPONSES.NOT_FOUND, {
1075
1376
  status: 404,
1076
- headers: { "Content-Type": "application/json" }
1377
+ headers: HEADERS.JSON
1077
1378
  });
1078
1379
  }
1079
1380
  /**
1080
1381
  * Collect middleware for a specific path
1081
- * (Simplified version - assumes we've already checked for pure static)
1082
1382
  */
1083
1383
  collectMiddlewareForPath(path, routeMiddleware) {
1084
1384
  if (this.router.globalMiddleware.length === 0 && this.router.pathMiddleware.size === 0) {
@@ -1088,22 +1388,26 @@ var Gravito = class {
1088
1388
  }
1089
1389
  /**
1090
1390
  * Compile routes for optimization
1091
- * Called once during initialization and when routes change
1092
1391
  */
1093
1392
  compileRoutes() {
1094
1393
  this.staticRoutes = this.router.staticRoutes;
1095
1394
  const hasGlobalMiddleware = this.router.globalMiddleware.length > 0;
1096
1395
  const hasPathMiddleware = this.router.pathMiddleware.size > 0;
1097
1396
  this.isPureStaticApp = !hasGlobalMiddleware && !hasPathMiddleware;
1098
- for (const [_key, route] of this.staticRoutes) {
1397
+ for (const [key, route] of this.staticRoutes) {
1398
+ if (route.compiledVersion === this.middlewareVersion) {
1399
+ continue;
1400
+ }
1099
1401
  const analysis = analyzeHandler(route.handler);
1100
1402
  const optimalType = getOptimalContextType(analysis);
1101
1403
  route.useMinimal = this.isPureStaticApp && route.middleware.length === 0 && optimalType === "minimal";
1404
+ if (!route.useMinimal) {
1405
+ const allMiddleware = this.collectMiddlewareForPath(key.split(":")[1], route.middleware);
1406
+ route.compiled = compileMiddlewareChain(allMiddleware, route.handler);
1407
+ }
1408
+ route.compiledVersion = this.middlewareVersion;
1102
1409
  }
1103
1410
  }
1104
- // ─────────────────────────────────────────────────────────────────────────
1105
- // Internal Methods
1106
- // ─────────────────────────────────────────────────────────────────────────
1107
1411
  /**
1108
1412
  * Add a route to the router
1109
1413
  */
@@ -1119,9 +1423,6 @@ var Gravito = class {
1119
1423
  }
1120
1424
  /**
1121
1425
  * Execute middleware chain followed by handler
1122
- *
1123
- * Implements the standard middleware pattern:
1124
- * Each middleware can call next() to continue the chain.
1125
1426
  */
1126
1427
  async executeMiddleware(ctx, middleware, handler) {
1127
1428
  let index = 0;
@@ -1138,16 +1439,6 @@ var Gravito = class {
1138
1439
  }
1139
1440
  return await handler(ctx);
1140
1441
  }
1141
- /*
1142
- * Handle 404 Not Found (Async version for dynamic/middleware paths)
1143
- * Note: Currently unused as we handle 404s via handleNotFoundSync or inline
1144
- */
1145
- // private async handleNotFound(ctx: FastContext): Promise<Response> {
1146
- // if (this.notFoundHandler) {
1147
- // return await this.notFoundHandler(ctx)
1148
- // }
1149
- // return ctx.json({ error: 'Not Found' }, 404)
1150
- // }
1151
1442
  /**
1152
1443
  * Handle errors (Async version for dynamic/middleware paths)
1153
1444
  */