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