@gravito/core 1.0.0 → 1.1.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.
package/dist/index.cjs CHANGED
@@ -82,6 +82,7 @@ __export(index_exports, {
82
82
  dd: () => dd,
83
83
  defineConfig: () => defineConfig,
84
84
  dump: () => dump,
85
+ engine: () => engine_exports,
85
86
  env: () => env,
86
87
  errors: () => errors,
87
88
  fail: () => fail,
@@ -112,7 +113,7 @@ module.exports = __toCommonJS(index_exports);
112
113
  // package.json
113
114
  var package_default = {
114
115
  name: "@gravito/core",
115
- version: "1.0.0",
116
+ version: "1.1.0",
116
117
  description: "",
117
118
  module: "./dist/index.js",
118
119
  main: "./dist/index.cjs",
@@ -128,6 +129,11 @@ var package_default = {
128
129
  types: "./dist/compat.d.ts",
129
130
  import: "./dist/compat.js",
130
131
  require: "./dist/compat.cjs"
132
+ },
133
+ "./engine": {
134
+ types: "./dist/engine/index.d.ts",
135
+ import: "./dist/engine/index.js",
136
+ require: "./dist/engine/index.cjs"
131
137
  }
132
138
  },
133
139
  files: [
@@ -183,7 +189,6 @@ var PhotonRequestWrapper = class {
183
189
  constructor(photonCtx) {
184
190
  this.photonCtx = photonCtx;
185
191
  }
186
- _cachedJson = null;
187
192
  get url() {
188
193
  return this.photonCtx.req.url;
189
194
  }
@@ -4709,6 +4714,944 @@ function createHttpTester(core) {
4709
4714
  return new HttpTester(core);
4710
4715
  }
4711
4716
 
4717
+ // src/engine/index.ts
4718
+ var engine_exports = {};
4719
+ __export(engine_exports, {
4720
+ AOTRouter: () => AOTRouter,
4721
+ FastContextImpl: () => FastContext,
4722
+ Gravito: () => Gravito,
4723
+ MinimalContext: () => MinimalContext,
4724
+ ObjectPool: () => ObjectPool,
4725
+ extractPath: () => extractPath
4726
+ });
4727
+
4728
+ // src/engine/AOTRouter.ts
4729
+ var AOTRouter = class {
4730
+ // Static route cache: "METHOD:PATH" -> RouteMetadata
4731
+ staticRoutes = /* @__PURE__ */ new Map();
4732
+ // Dynamic route handler (Radix Tree)
4733
+ dynamicRouter = new RadixRouter();
4734
+ // Global middleware (applies to all routes)
4735
+ globalMiddleware = [];
4736
+ // Path-based middleware: pattern -> middleware[]
4737
+ pathMiddleware = /* @__PURE__ */ new Map();
4738
+ /**
4739
+ * Register a route
4740
+ *
4741
+ * Automatically determines if route is static or dynamic.
4742
+ * Static routes are stored in a Map for O(1) lookup.
4743
+ * Dynamic routes use the Radix Tree.
4744
+ *
4745
+ * @param method - HTTP method
4746
+ * @param path - Route path
4747
+ * @param handler - Route handler
4748
+ * @param middleware - Route-specific middleware
4749
+ */
4750
+ add(method, path2, handler, middleware = []) {
4751
+ const normalizedMethod = method.toLowerCase();
4752
+ if (this.isStaticPath(path2)) {
4753
+ const key = `${normalizedMethod}:${path2}`;
4754
+ this.staticRoutes.set(key, { handler, middleware });
4755
+ } else {
4756
+ const wrappedHandler = handler;
4757
+ this.dynamicRouter.add(normalizedMethod, path2, [wrappedHandler]);
4758
+ if (middleware.length > 0) {
4759
+ this.pathMiddleware.set(`${normalizedMethod}:${path2}`, middleware);
4760
+ }
4761
+ }
4762
+ }
4763
+ /**
4764
+ * Add global middleware
4765
+ *
4766
+ * These run for every request, before route-specific middleware.
4767
+ *
4768
+ * @param middleware - Middleware functions
4769
+ */
4770
+ use(...middleware) {
4771
+ this.globalMiddleware.push(...middleware);
4772
+ }
4773
+ /**
4774
+ * Add path-based middleware
4775
+ *
4776
+ * Supports wildcard patterns like '/api/*'
4777
+ *
4778
+ * @param pattern - Path pattern
4779
+ * @param middleware - Middleware functions
4780
+ */
4781
+ usePattern(pattern, ...middleware) {
4782
+ const existing = this.pathMiddleware.get(pattern) ?? [];
4783
+ this.pathMiddleware.set(pattern, [...existing, ...middleware]);
4784
+ }
4785
+ /**
4786
+ * Match a request to a route
4787
+ *
4788
+ * Returns the handler, params, and all applicable middleware.
4789
+ *
4790
+ * @param method - HTTP method
4791
+ * @param path - Request path
4792
+ * @returns Route match or null if not found
4793
+ */
4794
+ match(method, path2) {
4795
+ const normalizedMethod = method.toLowerCase();
4796
+ const staticKey = `${normalizedMethod}:${path2}`;
4797
+ const staticRoute = this.staticRoutes.get(staticKey);
4798
+ if (staticRoute) {
4799
+ return {
4800
+ handler: staticRoute.handler,
4801
+ params: {},
4802
+ middleware: this.collectMiddleware(path2, staticRoute.middleware)
4803
+ };
4804
+ }
4805
+ const match = this.dynamicRouter.match(normalizedMethod, path2);
4806
+ if (match && match.handlers.length > 0) {
4807
+ const handler = match.handlers[0];
4808
+ const routeKey = this.findDynamicRouteKey(normalizedMethod, path2);
4809
+ const routeMiddleware = routeKey ? this.pathMiddleware.get(routeKey) ?? [] : [];
4810
+ return {
4811
+ handler,
4812
+ params: match.params,
4813
+ middleware: this.collectMiddleware(path2, routeMiddleware)
4814
+ };
4815
+ }
4816
+ return {
4817
+ handler: null,
4818
+ params: {},
4819
+ middleware: []
4820
+ };
4821
+ }
4822
+ /**
4823
+ * Public wrapper for collectMiddleware (used by Gravito for optimization)
4824
+ */
4825
+ collectMiddlewarePublic(path2, routeMiddleware) {
4826
+ return this.collectMiddleware(path2, routeMiddleware);
4827
+ }
4828
+ /**
4829
+ * Collect all applicable middleware for a path
4830
+ *
4831
+ * Order: global -> pattern-based -> route-specific
4832
+ *
4833
+ * @param path - Request path
4834
+ * @param routeMiddleware - Route-specific middleware
4835
+ * @returns Combined middleware array
4836
+ */
4837
+ collectMiddleware(path2, routeMiddleware) {
4838
+ if (this.globalMiddleware.length === 0 && this.pathMiddleware.size === 0 && routeMiddleware.length === 0) {
4839
+ return [];
4840
+ }
4841
+ const middleware = [];
4842
+ if (this.globalMiddleware.length > 0) {
4843
+ middleware.push(...this.globalMiddleware);
4844
+ }
4845
+ if (this.pathMiddleware.size > 0) {
4846
+ for (const [pattern, mw] of this.pathMiddleware) {
4847
+ if (pattern.includes(":")) continue;
4848
+ if (this.matchPattern(pattern, path2)) {
4849
+ middleware.push(...mw);
4850
+ }
4851
+ }
4852
+ }
4853
+ if (routeMiddleware.length > 0) {
4854
+ middleware.push(...routeMiddleware);
4855
+ }
4856
+ return middleware;
4857
+ }
4858
+ /**
4859
+ * Check if a path is static (no parameters or wildcards)
4860
+ */
4861
+ isStaticPath(path2) {
4862
+ return !path2.includes(":") && !path2.includes("*");
4863
+ }
4864
+ /**
4865
+ * Match a pattern against a path
4866
+ *
4867
+ * Supports:
4868
+ * - Exact match: '/api/users'
4869
+ * - Wildcard suffix: '/api/*'
4870
+ *
4871
+ * @param pattern - Pattern to match
4872
+ * @param path - Path to test
4873
+ * @returns True if pattern matches
4874
+ */
4875
+ matchPattern(pattern, path2) {
4876
+ if (pattern === "*") return true;
4877
+ if (pattern === path2) return true;
4878
+ if (pattern.endsWith("/*")) {
4879
+ const prefix = pattern.slice(0, -2);
4880
+ return path2.startsWith(prefix);
4881
+ }
4882
+ return false;
4883
+ }
4884
+ /**
4885
+ * Find the original route key for a matched dynamic route
4886
+ *
4887
+ * This is needed to look up route-specific middleware.
4888
+ * It's a bit of a hack, but avoids storing duplicate data.
4889
+ *
4890
+ * @param method - HTTP method
4891
+ * @param path - Matched path
4892
+ * @returns Route key or null
4893
+ */
4894
+ findDynamicRouteKey(method, path2) {
4895
+ for (const key of this.pathMiddleware.keys()) {
4896
+ if (key.startsWith(`${method}:`)) {
4897
+ return key;
4898
+ }
4899
+ }
4900
+ return null;
4901
+ }
4902
+ /**
4903
+ * Get all registered routes (for debugging)
4904
+ */
4905
+ getRoutes() {
4906
+ const routes = [];
4907
+ for (const key of this.staticRoutes.keys()) {
4908
+ const [method, path2] = key.split(":");
4909
+ routes.push({ method, path: path2, type: "static" });
4910
+ }
4911
+ return routes;
4912
+ }
4913
+ };
4914
+
4915
+ // src/engine/analyzer.ts
4916
+ function analyzeHandler(handler) {
4917
+ const source = handler.toString();
4918
+ return {
4919
+ usesHeaders: source.includes(".header(") || source.includes(".header)") || source.includes(".headers(") || source.includes(".headers)"),
4920
+ usesQuery: source.includes(".query(") || source.includes(".query)") || source.includes(".queries(") || source.includes(".queries)"),
4921
+ usesBody: source.includes(".json()") || source.includes(".text()") || source.includes(".formData()") || source.includes(".body"),
4922
+ usesParams: source.includes(".param(") || source.includes(".param)") || source.includes(".params(") || source.includes(".params)"),
4923
+ isAsync: source.includes("async") || source.includes("await")
4924
+ };
4925
+ }
4926
+ function getOptimalContextType(analysis) {
4927
+ if (analysis.usesHeaders) {
4928
+ return "fast";
4929
+ }
4930
+ if (!analysis.usesQuery && !analysis.usesBody && !analysis.usesParams) {
4931
+ return "minimal";
4932
+ }
4933
+ if (!analysis.usesQuery && !analysis.usesBody && analysis.usesParams) {
4934
+ return "minimal";
4935
+ }
4936
+ if (analysis.usesBody) {
4937
+ return "full";
4938
+ }
4939
+ return "fast";
4940
+ }
4941
+
4942
+ // src/engine/FastContext.ts
4943
+ var FastRequestImpl = class {
4944
+ _request;
4945
+ _params;
4946
+ _url = new URL("http://localhost");
4947
+ // Reuse this object
4948
+ _query = null;
4949
+ _headers = null;
4950
+ /**
4951
+ * Reset for pooling
4952
+ */
4953
+ reset(request, params = {}) {
4954
+ this._request = request;
4955
+ this._params = params;
4956
+ this._url.href = request.url;
4957
+ this._query = null;
4958
+ this._headers = null;
4959
+ }
4960
+ get url() {
4961
+ return this._request.url;
4962
+ }
4963
+ get method() {
4964
+ return this._request.method;
4965
+ }
4966
+ get path() {
4967
+ return this._url.pathname;
4968
+ }
4969
+ param(name) {
4970
+ return this._params[name];
4971
+ }
4972
+ params() {
4973
+ return { ...this._params };
4974
+ }
4975
+ query(name) {
4976
+ if (!this._query) {
4977
+ this._query = this._url.searchParams;
4978
+ }
4979
+ return this._query.get(name) ?? void 0;
4980
+ }
4981
+ queries() {
4982
+ if (!this._query) {
4983
+ this._query = this._url.searchParams;
4984
+ }
4985
+ const result = {};
4986
+ for (const [key, value2] of this._query.entries()) {
4987
+ const existing = result[key];
4988
+ if (existing === void 0) {
4989
+ result[key] = value2;
4990
+ } else if (Array.isArray(existing)) {
4991
+ existing.push(value2);
4992
+ } else {
4993
+ result[key] = [existing, value2];
4994
+ }
4995
+ }
4996
+ return result;
4997
+ }
4998
+ header(name) {
4999
+ return this._request.headers.get(name) ?? void 0;
5000
+ }
5001
+ headers() {
5002
+ if (!this._headers) {
5003
+ this._headers = {};
5004
+ for (const [key, value2] of this._request.headers.entries()) {
5005
+ this._headers[key] = value2;
5006
+ }
5007
+ }
5008
+ return { ...this._headers };
5009
+ }
5010
+ async json() {
5011
+ return this._request.json();
5012
+ }
5013
+ async text() {
5014
+ return this._request.text();
5015
+ }
5016
+ async formData() {
5017
+ return this._request.formData();
5018
+ }
5019
+ get raw() {
5020
+ return this._request;
5021
+ }
5022
+ };
5023
+ var FastContext = class {
5024
+ _req = new FastRequestImpl();
5025
+ _statusCode = 200;
5026
+ _headers = new Headers();
5027
+ // Reuse this object
5028
+ /**
5029
+ * Reset context for pooling
5030
+ *
5031
+ * This is called when acquiring from the pool.
5032
+ * Must clear all state from previous request.
5033
+ */
5034
+ reset(request, params = {}) {
5035
+ this._req.reset(request, params);
5036
+ this._statusCode = 200;
5037
+ this._headers = new Headers();
5038
+ return this;
5039
+ }
5040
+ get req() {
5041
+ return this._req;
5042
+ }
5043
+ // ─────────────────────────────────────────────────────────────────────────
5044
+ // Response Helpers
5045
+ // ─────────────────────────────────────────────────────────────────────────
5046
+ json(data, status = 200) {
5047
+ this._headers.set("Content-Type", "application/json; charset=utf-8");
5048
+ return new Response(JSON.stringify(data), {
5049
+ status,
5050
+ headers: this._headers
5051
+ });
5052
+ }
5053
+ text(text, status = 200) {
5054
+ this._headers.set("Content-Type", "text/plain; charset=utf-8");
5055
+ return new Response(text, {
5056
+ status,
5057
+ headers: this._headers
5058
+ });
5059
+ }
5060
+ html(html, status = 200) {
5061
+ this._headers.set("Content-Type", "text/html; charset=utf-8");
5062
+ return new Response(html, {
5063
+ status,
5064
+ headers: this._headers
5065
+ });
5066
+ }
5067
+ redirect(url, status = 302) {
5068
+ this._headers.set("Location", url);
5069
+ return new Response(null, {
5070
+ status,
5071
+ headers: this._headers
5072
+ });
5073
+ }
5074
+ body(data, status = 200) {
5075
+ return new Response(data, {
5076
+ status,
5077
+ headers: this._headers
5078
+ });
5079
+ }
5080
+ // ─────────────────────────────────────────────────────────────────────────
5081
+ // Header Management
5082
+ // ─────────────────────────────────────────────────────────────────────────
5083
+ header(name, value2) {
5084
+ this._headers.set(name, value2);
5085
+ }
5086
+ status(code) {
5087
+ this._statusCode = code;
5088
+ }
5089
+ };
5090
+
5091
+ // src/engine/MinimalContext.ts
5092
+ var MinimalRequest = class {
5093
+ constructor(_request, _params, _path) {
5094
+ this._request = _request;
5095
+ this._params = _params;
5096
+ this._path = _path;
5097
+ }
5098
+ get url() {
5099
+ return this._request.url;
5100
+ }
5101
+ get method() {
5102
+ return this._request.method;
5103
+ }
5104
+ get path() {
5105
+ return this._path;
5106
+ }
5107
+ param(name) {
5108
+ return this._params[name];
5109
+ }
5110
+ params() {
5111
+ return { ...this._params };
5112
+ }
5113
+ query(name) {
5114
+ const url = new URL(this._request.url);
5115
+ return url.searchParams.get(name) ?? void 0;
5116
+ }
5117
+ queries() {
5118
+ const url = new URL(this._request.url);
5119
+ const result = {};
5120
+ for (const [key, value2] of url.searchParams.entries()) {
5121
+ const existing = result[key];
5122
+ if (existing === void 0) {
5123
+ result[key] = value2;
5124
+ } else if (Array.isArray(existing)) {
5125
+ existing.push(value2);
5126
+ } else {
5127
+ result[key] = [existing, value2];
5128
+ }
5129
+ }
5130
+ return result;
5131
+ }
5132
+ header(name) {
5133
+ return this._request.headers.get(name) ?? void 0;
5134
+ }
5135
+ headers() {
5136
+ const result = {};
5137
+ for (const [key, value2] of this._request.headers.entries()) {
5138
+ result[key] = value2;
5139
+ }
5140
+ return result;
5141
+ }
5142
+ async json() {
5143
+ return this._request.json();
5144
+ }
5145
+ async text() {
5146
+ return this._request.text();
5147
+ }
5148
+ async formData() {
5149
+ return this._request.formData();
5150
+ }
5151
+ get raw() {
5152
+ return this._request;
5153
+ }
5154
+ };
5155
+ var MinimalContext = class {
5156
+ _req;
5157
+ constructor(request, params, path2) {
5158
+ this._req = new MinimalRequest(request, params, path2);
5159
+ }
5160
+ get req() {
5161
+ return this._req;
5162
+ }
5163
+ // Response helpers - create headers inline (no reuse overhead)
5164
+ json(data, status = 200) {
5165
+ return new Response(JSON.stringify(data), {
5166
+ status,
5167
+ headers: { "Content-Type": "application/json; charset=utf-8" }
5168
+ });
5169
+ }
5170
+ text(text, status = 200) {
5171
+ return new Response(text, {
5172
+ status,
5173
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
5174
+ });
5175
+ }
5176
+ html(html, status = 200) {
5177
+ return new Response(html, {
5178
+ status,
5179
+ headers: { "Content-Type": "text/html; charset=utf-8" }
5180
+ });
5181
+ }
5182
+ redirect(url, status = 302) {
5183
+ return new Response(null, {
5184
+ status,
5185
+ headers: { Location: url }
5186
+ });
5187
+ }
5188
+ body(data, status = 200) {
5189
+ return new Response(data, { status });
5190
+ }
5191
+ header(_name, _value) {
5192
+ console.warn("MinimalContext.header() is a no-op. Use FastContext for custom headers.");
5193
+ }
5194
+ status(_code) {
5195
+ }
5196
+ // Required for interface compatibility
5197
+ reset(_request, _params) {
5198
+ throw new Error("MinimalContext does not support reset. Create a new instance instead.");
5199
+ }
5200
+ };
5201
+
5202
+ // src/engine/path.ts
5203
+ function extractPath(url) {
5204
+ const protocolEnd = url.indexOf("://");
5205
+ const searchStart = protocolEnd === -1 ? 0 : protocolEnd + 3;
5206
+ const pathStart = url.indexOf("/", searchStart);
5207
+ if (pathStart === -1) {
5208
+ return "/";
5209
+ }
5210
+ const queryStart = url.indexOf("?", pathStart);
5211
+ if (queryStart === -1) {
5212
+ return url.slice(pathStart);
5213
+ }
5214
+ return url.slice(pathStart, queryStart);
5215
+ }
5216
+
5217
+ // src/engine/pool.ts
5218
+ var ObjectPool = class {
5219
+ pool = [];
5220
+ factory;
5221
+ reset;
5222
+ maxSize;
5223
+ /**
5224
+ * Create a new object pool
5225
+ *
5226
+ * @param factory - Function to create new objects
5227
+ * @param reset - Function to reset objects before reuse
5228
+ * @param maxSize - Maximum pool size (default: 256)
5229
+ */
5230
+ constructor(factory, reset, maxSize = 256) {
5231
+ this.factory = factory;
5232
+ this.reset = reset;
5233
+ this.maxSize = maxSize;
5234
+ }
5235
+ /**
5236
+ * Acquire an object from the pool
5237
+ *
5238
+ * If the pool is empty, creates a new object (overflow strategy).
5239
+ * This ensures the pool never blocks under high load.
5240
+ *
5241
+ * @returns Object from pool or newly created
5242
+ */
5243
+ acquire() {
5244
+ const obj = this.pool.pop();
5245
+ if (obj !== void 0) {
5246
+ return obj;
5247
+ }
5248
+ return this.factory();
5249
+ }
5250
+ /**
5251
+ * Release an object back to the pool
5252
+ *
5253
+ * If the pool is full, the object is discarded (will be GC'd).
5254
+ * This prevents unbounded memory growth.
5255
+ *
5256
+ * @param obj - Object to release
5257
+ */
5258
+ release(obj) {
5259
+ if (this.pool.length < this.maxSize) {
5260
+ this.reset(obj);
5261
+ this.pool.push(obj);
5262
+ }
5263
+ }
5264
+ /**
5265
+ * Clear all objects from the pool
5266
+ *
5267
+ * Useful for testing or when you need to force a clean slate.
5268
+ */
5269
+ clear() {
5270
+ this.pool = [];
5271
+ }
5272
+ /**
5273
+ * Get current pool size
5274
+ */
5275
+ get size() {
5276
+ return this.pool.length;
5277
+ }
5278
+ /**
5279
+ * Get maximum pool size
5280
+ */
5281
+ get capacity() {
5282
+ return this.maxSize;
5283
+ }
5284
+ /**
5285
+ * Pre-warm the pool by creating objects in advance
5286
+ *
5287
+ * This can reduce latency for the first N requests.
5288
+ *
5289
+ * @param count - Number of objects to pre-create
5290
+ */
5291
+ prewarm(count) {
5292
+ const targetSize = Math.min(count, this.maxSize);
5293
+ while (this.pool.length < targetSize) {
5294
+ const obj = this.factory();
5295
+ this.reset(obj);
5296
+ this.pool.push(obj);
5297
+ }
5298
+ }
5299
+ };
5300
+
5301
+ // src/engine/Gravito.ts
5302
+ var Gravito = class {
5303
+ router = new AOTRouter();
5304
+ contextPool;
5305
+ errorHandler;
5306
+ notFoundHandler;
5307
+ // Direct reference to static routes Map (O(1) access)
5308
+ // Optimization: Bypass getter/setter overhead
5309
+ staticRoutes;
5310
+ // Flag: pure static app (no middleware at all) allows ultra-fast path
5311
+ isPureStaticApp = true;
5312
+ /**
5313
+ * Create a new Gravito instance
5314
+ *
5315
+ * @param options - Engine configuration options
5316
+ */
5317
+ constructor(options = {}) {
5318
+ const poolSize = options.poolSize ?? 256;
5319
+ this.contextPool = new ObjectPool(
5320
+ () => new FastContext(),
5321
+ (ctx) => ctx.reset(new Request("http://localhost")),
5322
+ poolSize
5323
+ );
5324
+ this.contextPool.prewarm(Math.min(32, poolSize));
5325
+ if (options.onError) {
5326
+ this.errorHandler = options.onError;
5327
+ }
5328
+ if (options.onNotFound) {
5329
+ this.notFoundHandler = options.onNotFound;
5330
+ }
5331
+ this.compileRoutes();
5332
+ }
5333
+ // ─────────────────────────────────────────────────────────────────────────
5334
+ // HTTP Method Registration
5335
+ // ─────────────────────────────────────────────────────────────────────────
5336
+ /**
5337
+ * Register a GET route
5338
+ *
5339
+ * @param path - Route path (e.g., '/users/:id')
5340
+ * @param handlers - Handler and optional middleware
5341
+ * @returns This instance for chaining
5342
+ */
5343
+ get(path2, ...handlers) {
5344
+ return this.addRoute("get", path2, handlers);
5345
+ }
5346
+ /**
5347
+ * Register a POST route
5348
+ */
5349
+ post(path2, ...handlers) {
5350
+ return this.addRoute("post", path2, handlers);
5351
+ }
5352
+ /**
5353
+ * Register a PUT route
5354
+ */
5355
+ put(path2, ...handlers) {
5356
+ return this.addRoute("put", path2, handlers);
5357
+ }
5358
+ /**
5359
+ * Register a DELETE route
5360
+ */
5361
+ delete(path2, ...handlers) {
5362
+ return this.addRoute("delete", path2, handlers);
5363
+ }
5364
+ /**
5365
+ * Register a PATCH route
5366
+ */
5367
+ patch(path2, ...handlers) {
5368
+ return this.addRoute("patch", path2, handlers);
5369
+ }
5370
+ /**
5371
+ * Register an OPTIONS route
5372
+ */
5373
+ options(path2, ...handlers) {
5374
+ return this.addRoute("options", path2, handlers);
5375
+ }
5376
+ /**
5377
+ * Register a HEAD route
5378
+ */
5379
+ head(path2, ...handlers) {
5380
+ return this.addRoute("head", path2, handlers);
5381
+ }
5382
+ /**
5383
+ * Register a route for all HTTP methods
5384
+ */
5385
+ all(path2, ...handlers) {
5386
+ const methods = ["get", "post", "put", "delete", "patch", "options", "head"];
5387
+ for (const method of methods) {
5388
+ this.addRoute(method, path2, handlers);
5389
+ }
5390
+ return this;
5391
+ }
5392
+ use(pathOrMiddleware, ...middleware) {
5393
+ this.isPureStaticApp = false;
5394
+ if (typeof pathOrMiddleware === "string") {
5395
+ this.router.usePattern(pathOrMiddleware, ...middleware);
5396
+ } else {
5397
+ this.router.use(pathOrMiddleware, ...middleware);
5398
+ }
5399
+ return this;
5400
+ }
5401
+ // ─────────────────────────────────────────────────────────────────────────
5402
+ // Route Grouping
5403
+ // ─────────────────────────────────────────────────────────────────────────
5404
+ /**
5405
+ * Mount a sub-application at a path prefix
5406
+ *
5407
+ * @example
5408
+ * ```typescript
5409
+ * const api = new Gravito()
5410
+ * api.get('/users', (c) => c.json({ users: [] }))
5411
+ *
5412
+ * const app = new Gravito()
5413
+ * app.route('/api', api)
5414
+ * // Now accessible at /api/users
5415
+ * ```
5416
+ */
5417
+ route(path2, app2) {
5418
+ console.warn("route() method is not yet fully implemented");
5419
+ return this;
5420
+ }
5421
+ // ─────────────────────────────────────────────────────────────────────────
5422
+ // Error Handling
5423
+ // ─────────────────────────────────────────────────────────────────────────
5424
+ /**
5425
+ * Set custom error handler
5426
+ *
5427
+ * @example
5428
+ * ```typescript
5429
+ * app.onError((err, c) => {
5430
+ * console.error(err)
5431
+ * return c.json({ error: err.message }, 500)
5432
+ * })
5433
+ * ```
5434
+ */
5435
+ onError(handler) {
5436
+ this.errorHandler = handler;
5437
+ return this;
5438
+ }
5439
+ /**
5440
+ * Set custom 404 handler
5441
+ *
5442
+ * @example
5443
+ * ```typescript
5444
+ * app.notFound((c) => {
5445
+ * return c.json({ error: 'Not Found' }, 404)
5446
+ * })
5447
+ * ```
5448
+ */
5449
+ notFound(handler) {
5450
+ this.notFoundHandler = handler;
5451
+ return this;
5452
+ }
5453
+ // ─────────────────────────────────────────────────────────────────────────
5454
+ // Request Handling (Bun.serve integration)
5455
+ // ─────────────────────────────────────────────────────────────────────────
5456
+ /**
5457
+ * Handle an incoming request
5458
+ *
5459
+ * Optimized for minimal allocations and maximum throughput.
5460
+ * Uses sync/async dual-path strategy inspired by Hono.
5461
+ *
5462
+ * @param request - Incoming Request object
5463
+ * @returns Response object (sync or async)
5464
+ */
5465
+ fetch = (request) => {
5466
+ const path2 = extractPath(request.url);
5467
+ const method = request.method.toLowerCase();
5468
+ const staticKey = `${method}:${path2}`;
5469
+ const staticRoute = this.staticRoutes.get(staticKey);
5470
+ if (staticRoute) {
5471
+ if (staticRoute.useMinimal) {
5472
+ const ctx = new MinimalContext(request, {}, path2);
5473
+ try {
5474
+ const result = staticRoute.handler(ctx);
5475
+ if (result instanceof Response) {
5476
+ return result;
5477
+ }
5478
+ return result;
5479
+ } catch (error) {
5480
+ return this.handleErrorSync(error, request, path2);
5481
+ }
5482
+ }
5483
+ return this.handleWithMiddleware(request, path2, staticRoute);
5484
+ }
5485
+ return this.handleDynamicRoute(request, method, path2);
5486
+ };
5487
+ /**
5488
+ * Handle routes with middleware (async path)
5489
+ */
5490
+ async handleWithMiddleware(request, path2, route) {
5491
+ const ctx = this.contextPool.acquire();
5492
+ try {
5493
+ ctx.reset(request, {});
5494
+ const middleware = this.collectMiddlewareForPath(path2, route.middleware);
5495
+ if (middleware.length === 0) {
5496
+ return await route.handler(ctx);
5497
+ }
5498
+ return await this.executeMiddleware(ctx, middleware, route.handler);
5499
+ } catch (error) {
5500
+ return await this.handleError(error, ctx);
5501
+ } finally {
5502
+ this.contextPool.release(ctx);
5503
+ }
5504
+ }
5505
+ /**
5506
+ * Handle dynamic routes (Radix Tree lookup)
5507
+ */
5508
+ handleDynamicRoute(request, method, path2) {
5509
+ const match = this.router.match(method.toUpperCase(), path2);
5510
+ if (!match.handler) {
5511
+ return this.handleNotFoundSync(request, path2);
5512
+ }
5513
+ const ctx = this.contextPool.acquire();
5514
+ const execute = async () => {
5515
+ try {
5516
+ ctx.reset(request, match.params);
5517
+ if (match.middleware.length === 0) {
5518
+ return await match.handler(ctx);
5519
+ }
5520
+ return await this.executeMiddleware(ctx, match.middleware, match.handler);
5521
+ } catch (error) {
5522
+ return await this.handleError(error, ctx);
5523
+ } finally {
5524
+ this.contextPool.release(ctx);
5525
+ }
5526
+ };
5527
+ return execute();
5528
+ }
5529
+ /**
5530
+ * Sync error handler (for ultra-fast path)
5531
+ */
5532
+ handleErrorSync(error, request, path2) {
5533
+ if (this.errorHandler) {
5534
+ const ctx = new MinimalContext(request, {}, path2);
5535
+ const result = this.errorHandler(error, ctx);
5536
+ if (result instanceof Response) {
5537
+ return result;
5538
+ }
5539
+ return result;
5540
+ }
5541
+ console.error("Unhandled error:", error);
5542
+ return new Response(
5543
+ JSON.stringify({
5544
+ error: "Internal Server Error",
5545
+ message: error.message
5546
+ }),
5547
+ {
5548
+ status: 500,
5549
+ headers: { "Content-Type": "application/json" }
5550
+ }
5551
+ );
5552
+ }
5553
+ /**
5554
+ * Sync 404 handler (for ultra-fast path)
5555
+ */
5556
+ handleNotFoundSync(request, path2) {
5557
+ if (this.notFoundHandler) {
5558
+ const ctx = new MinimalContext(request, {}, path2);
5559
+ const result = this.notFoundHandler(ctx);
5560
+ if (result instanceof Response) {
5561
+ return result;
5562
+ }
5563
+ return result;
5564
+ }
5565
+ return new Response(JSON.stringify({ error: "Not Found" }), {
5566
+ status: 404,
5567
+ headers: { "Content-Type": "application/json" }
5568
+ });
5569
+ }
5570
+ /**
5571
+ * Collect middleware for a specific path
5572
+ * (Simplified version - assumes we've already checked for pure static)
5573
+ */
5574
+ collectMiddlewareForPath(path2, routeMiddleware) {
5575
+ if (this.router.globalMiddleware.length === 0 && this.router.pathMiddleware.size === 0) {
5576
+ return routeMiddleware;
5577
+ }
5578
+ return this.router.collectMiddlewarePublic(path2, routeMiddleware);
5579
+ }
5580
+ /**
5581
+ * Compile routes for optimization
5582
+ * Called once during initialization and when routes change
5583
+ */
5584
+ compileRoutes() {
5585
+ this.staticRoutes = this.router.staticRoutes;
5586
+ const hasGlobalMiddleware = this.router.globalMiddleware.length > 0;
5587
+ const hasPathMiddleware = this.router.pathMiddleware.size > 0;
5588
+ this.isPureStaticApp = !hasGlobalMiddleware && !hasPathMiddleware;
5589
+ for (const [_key, route] of this.staticRoutes) {
5590
+ const analysis = analyzeHandler(route.handler);
5591
+ const optimalType = getOptimalContextType(analysis);
5592
+ route.useMinimal = this.isPureStaticApp && route.middleware.length === 0 && optimalType === "minimal";
5593
+ }
5594
+ }
5595
+ // ─────────────────────────────────────────────────────────────────────────
5596
+ // Internal Methods
5597
+ // ─────────────────────────────────────────────────────────────────────────
5598
+ /**
5599
+ * Add a route to the router
5600
+ */
5601
+ addRoute(method, path2, handlers) {
5602
+ if (handlers.length === 0) {
5603
+ throw new Error(`No handler provided for ${method.toUpperCase()} ${path2}`);
5604
+ }
5605
+ const handler = handlers[handlers.length - 1];
5606
+ const middleware = handlers.slice(0, -1);
5607
+ this.router.add(method, path2, handler, middleware);
5608
+ this.compileRoutes();
5609
+ return this;
5610
+ }
5611
+ /**
5612
+ * Execute middleware chain followed by handler
5613
+ *
5614
+ * Implements the standard middleware pattern:
5615
+ * Each middleware can call next() to continue the chain.
5616
+ */
5617
+ async executeMiddleware(ctx, middleware, handler) {
5618
+ let index = 0;
5619
+ const next = async () => {
5620
+ if (index < middleware.length) {
5621
+ const mw = middleware[index++];
5622
+ await mw(ctx, next);
5623
+ }
5624
+ };
5625
+ await next();
5626
+ return await handler(ctx);
5627
+ }
5628
+ /**
5629
+ * Handle 404 Not Found (Async version for dynamic/middleware paths)
5630
+ */
5631
+ async handleNotFound(ctx) {
5632
+ if (this.notFoundHandler) {
5633
+ return await this.notFoundHandler(ctx);
5634
+ }
5635
+ return ctx.json({ error: "Not Found" }, 404);
5636
+ }
5637
+ /**
5638
+ * Handle errors (Async version for dynamic/middleware paths)
5639
+ */
5640
+ async handleError(error, ctx) {
5641
+ if (this.errorHandler) {
5642
+ return await this.errorHandler(error, ctx);
5643
+ }
5644
+ console.error("Unhandled error:", error);
5645
+ return ctx.json(
5646
+ {
5647
+ error: "Internal Server Error",
5648
+ message: error.message
5649
+ },
5650
+ 500
5651
+ );
5652
+ }
5653
+ };
5654
+
4712
5655
  // src/index.ts
4713
5656
  var VERSION = package_default.version;
4714
5657
  function defineConfig(config2) {
@@ -4768,6 +5711,7 @@ function defineConfig(config2) {
4768
5711
  dd,
4769
5712
  defineConfig,
4770
5713
  dump,
5714
+ engine,
4771
5715
  env,
4772
5716
  errors,
4773
5717
  fail,