@gravito/core 1.0.0 → 1.2.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/README.md +2 -0
- package/dist/compat.d.cts +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/engine/index.cjs +1176 -0
- package/dist/engine/index.d.cts +607 -0
- package/dist/engine/index.d.ts +607 -0
- package/dist/engine/index.js +1144 -0
- package/dist/index.cjs +1025 -31
- package/dist/index.d.cts +649 -58
- package/dist/index.d.ts +649 -58
- package/dist/index.js +1028 -31
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -54,6 +54,7 @@ __export(index_exports, {
|
|
|
54
54
|
PhotonRequestWrapper: () => PhotonRequestWrapper,
|
|
55
55
|
PlanetCore: () => PlanetCore,
|
|
56
56
|
Route: () => Route,
|
|
57
|
+
RouteGroup: () => RouteGroup,
|
|
57
58
|
Router: () => Router,
|
|
58
59
|
ServiceProvider: () => ServiceProvider,
|
|
59
60
|
Str: () => Str,
|
|
@@ -82,6 +83,7 @@ __export(index_exports, {
|
|
|
82
83
|
dd: () => dd,
|
|
83
84
|
defineConfig: () => defineConfig,
|
|
84
85
|
dump: () => dump,
|
|
86
|
+
engine: () => engine_exports,
|
|
85
87
|
env: () => env,
|
|
86
88
|
errors: () => errors,
|
|
87
89
|
fail: () => fail,
|
|
@@ -112,7 +114,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
112
114
|
// package.json
|
|
113
115
|
var package_default = {
|
|
114
116
|
name: "@gravito/core",
|
|
115
|
-
version: "1.
|
|
117
|
+
version: "1.2.0",
|
|
116
118
|
description: "",
|
|
117
119
|
module: "./dist/index.js",
|
|
118
120
|
main: "./dist/index.cjs",
|
|
@@ -128,6 +130,11 @@ var package_default = {
|
|
|
128
130
|
types: "./dist/compat.d.ts",
|
|
129
131
|
import: "./dist/compat.js",
|
|
130
132
|
require: "./dist/compat.cjs"
|
|
133
|
+
},
|
|
134
|
+
"./engine": {
|
|
135
|
+
types: "./dist/engine/index.d.ts",
|
|
136
|
+
import: "./dist/engine/index.js",
|
|
137
|
+
require: "./dist/engine/index.cjs"
|
|
131
138
|
}
|
|
132
139
|
},
|
|
133
140
|
files: [
|
|
@@ -179,11 +186,45 @@ var package_default = {
|
|
|
179
186
|
};
|
|
180
187
|
|
|
181
188
|
// src/adapters/PhotonAdapter.ts
|
|
182
|
-
var PhotonRequestWrapper = class {
|
|
189
|
+
var PhotonRequestWrapper = class _PhotonRequestWrapper {
|
|
183
190
|
constructor(photonCtx) {
|
|
184
191
|
this.photonCtx = photonCtx;
|
|
185
192
|
}
|
|
186
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Create a proxied instance to delegate to Photon's request
|
|
195
|
+
*/
|
|
196
|
+
static create(photonCtx) {
|
|
197
|
+
const instance = new _PhotonRequestWrapper(photonCtx);
|
|
198
|
+
return new Proxy(instance, {
|
|
199
|
+
get(target, prop, receiver) {
|
|
200
|
+
if (prop in target) {
|
|
201
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
202
|
+
if (typeof value2 === "function") {
|
|
203
|
+
return value2.bind(target);
|
|
204
|
+
}
|
|
205
|
+
return value2;
|
|
206
|
+
}
|
|
207
|
+
const nativeReq = target.photonCtx.req;
|
|
208
|
+
if (prop in nativeReq) {
|
|
209
|
+
const value2 = nativeReq[prop];
|
|
210
|
+
if (typeof value2 === "function") {
|
|
211
|
+
return value2.bind(nativeReq);
|
|
212
|
+
}
|
|
213
|
+
return value2;
|
|
214
|
+
}
|
|
215
|
+
return void 0;
|
|
216
|
+
},
|
|
217
|
+
// Allow setting properties (for addValidated etc.)
|
|
218
|
+
set(target, prop, value2) {
|
|
219
|
+
if (prop in target) {
|
|
220
|
+
return Reflect.set(target, prop, value2);
|
|
221
|
+
}
|
|
222
|
+
;
|
|
223
|
+
target.photonCtx.req[prop] = value2;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
187
228
|
get url() {
|
|
188
229
|
return this.photonCtx.req.url;
|
|
189
230
|
}
|
|
@@ -242,7 +283,7 @@ var PhotonRequestWrapper = class {
|
|
|
242
283
|
var PhotonContextWrapper = class _PhotonContextWrapper {
|
|
243
284
|
constructor(photonCtx) {
|
|
244
285
|
this.photonCtx = photonCtx;
|
|
245
|
-
this._req =
|
|
286
|
+
this._req = PhotonRequestWrapper.create(photonCtx);
|
|
246
287
|
}
|
|
247
288
|
_req;
|
|
248
289
|
/**
|
|
@@ -354,7 +395,7 @@ function toPhotonMiddleware(middleware) {
|
|
|
354
395
|
return async (c, next) => {
|
|
355
396
|
const ctx = PhotonContextWrapper.create(c);
|
|
356
397
|
const gravitoNext = async () => {
|
|
357
|
-
await next();
|
|
398
|
+
return await next();
|
|
358
399
|
};
|
|
359
400
|
return middleware(ctx, gravitoNext);
|
|
360
401
|
};
|
|
@@ -2818,20 +2859,20 @@ var RouteGroup = class _RouteGroup {
|
|
|
2818
2859
|
group(callback) {
|
|
2819
2860
|
callback(this);
|
|
2820
2861
|
}
|
|
2821
|
-
get(path2,
|
|
2822
|
-
return this.router.req("get", path2,
|
|
2862
|
+
get(path2, requestOrHandlerOrMiddleware, handler) {
|
|
2863
|
+
return this.router.req("get", path2, requestOrHandlerOrMiddleware, handler, this.options);
|
|
2823
2864
|
}
|
|
2824
|
-
post(path2,
|
|
2825
|
-
return this.router.req("post", path2,
|
|
2865
|
+
post(path2, requestOrHandlerOrMiddleware, handler) {
|
|
2866
|
+
return this.router.req("post", path2, requestOrHandlerOrMiddleware, handler, this.options);
|
|
2826
2867
|
}
|
|
2827
|
-
put(path2,
|
|
2828
|
-
return this.router.req("put", path2,
|
|
2868
|
+
put(path2, requestOrHandlerOrMiddleware, handler) {
|
|
2869
|
+
return this.router.req("put", path2, requestOrHandlerOrMiddleware, handler, this.options);
|
|
2829
2870
|
}
|
|
2830
|
-
delete(path2,
|
|
2831
|
-
return this.router.req("delete", path2,
|
|
2871
|
+
delete(path2, requestOrHandlerOrMiddleware, handler) {
|
|
2872
|
+
return this.router.req("delete", path2, requestOrHandlerOrMiddleware, handler, this.options);
|
|
2832
2873
|
}
|
|
2833
|
-
patch(path2,
|
|
2834
|
-
return this.router.req("patch", path2,
|
|
2874
|
+
patch(path2, requestOrHandlerOrMiddleware, handler) {
|
|
2875
|
+
return this.router.req("patch", path2, requestOrHandlerOrMiddleware, handler, this.options);
|
|
2835
2876
|
}
|
|
2836
2877
|
resource(name, controller, options = {}) {
|
|
2837
2878
|
const actions = [
|
|
@@ -3007,20 +3048,20 @@ var Router = class {
|
|
|
3007
3048
|
middleware(...handlers) {
|
|
3008
3049
|
return new RouteGroup(this, { middleware: handlers.flat() });
|
|
3009
3050
|
}
|
|
3010
|
-
get(path2,
|
|
3011
|
-
return this.req("get", path2,
|
|
3051
|
+
get(path2, requestOrHandlerOrMiddleware, handler) {
|
|
3052
|
+
return this.req("get", path2, requestOrHandlerOrMiddleware, handler);
|
|
3012
3053
|
}
|
|
3013
|
-
post(path2,
|
|
3014
|
-
return this.req("post", path2,
|
|
3054
|
+
post(path2, requestOrHandlerOrMiddleware, handler) {
|
|
3055
|
+
return this.req("post", path2, requestOrHandlerOrMiddleware, handler);
|
|
3015
3056
|
}
|
|
3016
|
-
put(path2,
|
|
3017
|
-
return this.req("put", path2,
|
|
3057
|
+
put(path2, requestOrHandlerOrMiddleware, handler) {
|
|
3058
|
+
return this.req("put", path2, requestOrHandlerOrMiddleware, handler);
|
|
3018
3059
|
}
|
|
3019
|
-
delete(path2,
|
|
3020
|
-
return this.req("delete", path2,
|
|
3060
|
+
delete(path2, requestOrHandlerOrMiddleware, handler) {
|
|
3061
|
+
return this.req("delete", path2, requestOrHandlerOrMiddleware, handler);
|
|
3021
3062
|
}
|
|
3022
|
-
patch(path2,
|
|
3023
|
-
return this.req("patch", path2,
|
|
3063
|
+
patch(path2, requestOrHandlerOrMiddleware, handler) {
|
|
3064
|
+
return this.req("patch", path2, requestOrHandlerOrMiddleware, handler);
|
|
3024
3065
|
}
|
|
3025
3066
|
/**
|
|
3026
3067
|
* Register a resource route (Laravel-style).
|
|
@@ -3066,17 +3107,25 @@ var Router = class {
|
|
|
3066
3107
|
/**
|
|
3067
3108
|
* Internal Request Registration
|
|
3068
3109
|
*/
|
|
3069
|
-
req(method, path2,
|
|
3110
|
+
req(method, path2, requestOrHandlerOrMiddleware, handler, options = {}) {
|
|
3070
3111
|
const fullPath = (options.prefix || "") + path2;
|
|
3071
3112
|
let formRequestMiddleware = null;
|
|
3113
|
+
let routeMiddleware = [];
|
|
3072
3114
|
let finalRouteHandler;
|
|
3073
3115
|
if (handler !== void 0) {
|
|
3074
|
-
if (isFormRequestClass(
|
|
3075
|
-
formRequestMiddleware = formRequestToMiddleware(
|
|
3116
|
+
if (isFormRequestClass(requestOrHandlerOrMiddleware)) {
|
|
3117
|
+
formRequestMiddleware = formRequestToMiddleware(requestOrHandlerOrMiddleware);
|
|
3118
|
+
} else {
|
|
3119
|
+
const middleware = requestOrHandlerOrMiddleware;
|
|
3120
|
+
if (Array.isArray(middleware)) {
|
|
3121
|
+
routeMiddleware = middleware;
|
|
3122
|
+
} else {
|
|
3123
|
+
routeMiddleware = [middleware];
|
|
3124
|
+
}
|
|
3076
3125
|
}
|
|
3077
3126
|
finalRouteHandler = handler;
|
|
3078
3127
|
} else {
|
|
3079
|
-
finalRouteHandler =
|
|
3128
|
+
finalRouteHandler = requestOrHandlerOrMiddleware;
|
|
3080
3129
|
}
|
|
3081
3130
|
let resolvedHandler;
|
|
3082
3131
|
if (Array.isArray(finalRouteHandler)) {
|
|
@@ -3092,6 +3141,9 @@ var Router = class {
|
|
|
3092
3141
|
if (formRequestMiddleware) {
|
|
3093
3142
|
handlers.push(formRequestMiddleware);
|
|
3094
3143
|
}
|
|
3144
|
+
if (routeMiddleware.length > 0) {
|
|
3145
|
+
handlers.push(...routeMiddleware);
|
|
3146
|
+
}
|
|
3095
3147
|
handlers.push(resolvedHandler);
|
|
3096
3148
|
if (options.domain) {
|
|
3097
3149
|
const domainCheck = async (c, next) => {
|
|
@@ -3366,8 +3418,7 @@ var PlanetCore = class _PlanetCore {
|
|
|
3366
3418
|
const cookieJar = new CookieJar(this.encrypter);
|
|
3367
3419
|
c.set("cookieJar", cookieJar);
|
|
3368
3420
|
c.route = (name, params, query) => this.router.url(name, params, query);
|
|
3369
|
-
await next();
|
|
3370
|
-
return void 0;
|
|
3421
|
+
return await next();
|
|
3371
3422
|
});
|
|
3372
3423
|
this.router = new Router(this);
|
|
3373
3424
|
this.adapter.onError(async (err, c) => {
|
|
@@ -4709,6 +4760,947 @@ function createHttpTester(core) {
|
|
|
4709
4760
|
return new HttpTester(core);
|
|
4710
4761
|
}
|
|
4711
4762
|
|
|
4763
|
+
// src/engine/index.ts
|
|
4764
|
+
var engine_exports = {};
|
|
4765
|
+
__export(engine_exports, {
|
|
4766
|
+
AOTRouter: () => AOTRouter,
|
|
4767
|
+
FastContextImpl: () => FastContext,
|
|
4768
|
+
Gravito: () => Gravito,
|
|
4769
|
+
MinimalContext: () => MinimalContext,
|
|
4770
|
+
ObjectPool: () => ObjectPool,
|
|
4771
|
+
extractPath: () => extractPath
|
|
4772
|
+
});
|
|
4773
|
+
|
|
4774
|
+
// src/engine/AOTRouter.ts
|
|
4775
|
+
var AOTRouter = class {
|
|
4776
|
+
// Static route cache: "METHOD:PATH" -> RouteMetadata
|
|
4777
|
+
staticRoutes = /* @__PURE__ */ new Map();
|
|
4778
|
+
// Dynamic route handler (Radix Tree)
|
|
4779
|
+
dynamicRouter = new RadixRouter();
|
|
4780
|
+
// Global middleware (applies to all routes)
|
|
4781
|
+
globalMiddleware = [];
|
|
4782
|
+
// Path-based middleware: pattern -> middleware[]
|
|
4783
|
+
pathMiddleware = /* @__PURE__ */ new Map();
|
|
4784
|
+
/**
|
|
4785
|
+
* Register a route
|
|
4786
|
+
*
|
|
4787
|
+
* Automatically determines if route is static or dynamic.
|
|
4788
|
+
* Static routes are stored in a Map for O(1) lookup.
|
|
4789
|
+
* Dynamic routes use the Radix Tree.
|
|
4790
|
+
*
|
|
4791
|
+
* @param method - HTTP method
|
|
4792
|
+
* @param path - Route path
|
|
4793
|
+
* @param handler - Route handler
|
|
4794
|
+
* @param middleware - Route-specific middleware
|
|
4795
|
+
*/
|
|
4796
|
+
add(method, path2, handler, middleware = []) {
|
|
4797
|
+
const normalizedMethod = method.toLowerCase();
|
|
4798
|
+
if (this.isStaticPath(path2)) {
|
|
4799
|
+
const key = `${normalizedMethod}:${path2}`;
|
|
4800
|
+
this.staticRoutes.set(key, { handler, middleware });
|
|
4801
|
+
} else {
|
|
4802
|
+
const wrappedHandler = handler;
|
|
4803
|
+
this.dynamicRouter.add(normalizedMethod, path2, [wrappedHandler]);
|
|
4804
|
+
if (middleware.length > 0) {
|
|
4805
|
+
this.pathMiddleware.set(`${normalizedMethod}:${path2}`, middleware);
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
/**
|
|
4810
|
+
* Add global middleware
|
|
4811
|
+
*
|
|
4812
|
+
* These run for every request, before route-specific middleware.
|
|
4813
|
+
*
|
|
4814
|
+
* @param middleware - Middleware functions
|
|
4815
|
+
*/
|
|
4816
|
+
use(...middleware) {
|
|
4817
|
+
this.globalMiddleware.push(...middleware);
|
|
4818
|
+
}
|
|
4819
|
+
/**
|
|
4820
|
+
* Add path-based middleware
|
|
4821
|
+
*
|
|
4822
|
+
* Supports wildcard patterns like '/api/*'
|
|
4823
|
+
*
|
|
4824
|
+
* @param pattern - Path pattern
|
|
4825
|
+
* @param middleware - Middleware functions
|
|
4826
|
+
*/
|
|
4827
|
+
usePattern(pattern, ...middleware) {
|
|
4828
|
+
const existing = this.pathMiddleware.get(pattern) ?? [];
|
|
4829
|
+
this.pathMiddleware.set(pattern, [...existing, ...middleware]);
|
|
4830
|
+
}
|
|
4831
|
+
/**
|
|
4832
|
+
* Match a request to a route
|
|
4833
|
+
*
|
|
4834
|
+
* Returns the handler, params, and all applicable middleware.
|
|
4835
|
+
*
|
|
4836
|
+
* @param method - HTTP method
|
|
4837
|
+
* @param path - Request path
|
|
4838
|
+
* @returns Route match or null if not found
|
|
4839
|
+
*/
|
|
4840
|
+
match(method, path2) {
|
|
4841
|
+
const normalizedMethod = method.toLowerCase();
|
|
4842
|
+
const staticKey = `${normalizedMethod}:${path2}`;
|
|
4843
|
+
const staticRoute = this.staticRoutes.get(staticKey);
|
|
4844
|
+
if (staticRoute) {
|
|
4845
|
+
return {
|
|
4846
|
+
handler: staticRoute.handler,
|
|
4847
|
+
params: {},
|
|
4848
|
+
middleware: this.collectMiddleware(path2, staticRoute.middleware)
|
|
4849
|
+
};
|
|
4850
|
+
}
|
|
4851
|
+
const match = this.dynamicRouter.match(normalizedMethod, path2);
|
|
4852
|
+
if (match && match.handlers.length > 0) {
|
|
4853
|
+
const handler = match.handlers[0];
|
|
4854
|
+
const routeKey = this.findDynamicRouteKey(normalizedMethod, path2);
|
|
4855
|
+
const routeMiddleware = routeKey ? this.pathMiddleware.get(routeKey) ?? [] : [];
|
|
4856
|
+
return {
|
|
4857
|
+
handler,
|
|
4858
|
+
params: match.params,
|
|
4859
|
+
middleware: this.collectMiddleware(path2, routeMiddleware)
|
|
4860
|
+
};
|
|
4861
|
+
}
|
|
4862
|
+
return {
|
|
4863
|
+
handler: null,
|
|
4864
|
+
params: {},
|
|
4865
|
+
middleware: []
|
|
4866
|
+
};
|
|
4867
|
+
}
|
|
4868
|
+
/**
|
|
4869
|
+
* Public wrapper for collectMiddleware (used by Gravito for optimization)
|
|
4870
|
+
*/
|
|
4871
|
+
collectMiddlewarePublic(path2, routeMiddleware) {
|
|
4872
|
+
return this.collectMiddleware(path2, routeMiddleware);
|
|
4873
|
+
}
|
|
4874
|
+
/**
|
|
4875
|
+
* Collect all applicable middleware for a path
|
|
4876
|
+
*
|
|
4877
|
+
* Order: global -> pattern-based -> route-specific
|
|
4878
|
+
*
|
|
4879
|
+
* @param path - Request path
|
|
4880
|
+
* @param routeMiddleware - Route-specific middleware
|
|
4881
|
+
* @returns Combined middleware array
|
|
4882
|
+
*/
|
|
4883
|
+
collectMiddleware(path2, routeMiddleware) {
|
|
4884
|
+
if (this.globalMiddleware.length === 0 && this.pathMiddleware.size === 0 && routeMiddleware.length === 0) {
|
|
4885
|
+
return [];
|
|
4886
|
+
}
|
|
4887
|
+
const middleware = [];
|
|
4888
|
+
if (this.globalMiddleware.length > 0) {
|
|
4889
|
+
middleware.push(...this.globalMiddleware);
|
|
4890
|
+
}
|
|
4891
|
+
if (this.pathMiddleware.size > 0) {
|
|
4892
|
+
for (const [pattern, mw] of this.pathMiddleware) {
|
|
4893
|
+
if (pattern.includes(":")) continue;
|
|
4894
|
+
if (this.matchPattern(pattern, path2)) {
|
|
4895
|
+
middleware.push(...mw);
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
if (routeMiddleware.length > 0) {
|
|
4900
|
+
middleware.push(...routeMiddleware);
|
|
4901
|
+
}
|
|
4902
|
+
return middleware;
|
|
4903
|
+
}
|
|
4904
|
+
/**
|
|
4905
|
+
* Check if a path is static (no parameters or wildcards)
|
|
4906
|
+
*/
|
|
4907
|
+
isStaticPath(path2) {
|
|
4908
|
+
return !path2.includes(":") && !path2.includes("*");
|
|
4909
|
+
}
|
|
4910
|
+
/**
|
|
4911
|
+
* Match a pattern against a path
|
|
4912
|
+
*
|
|
4913
|
+
* Supports:
|
|
4914
|
+
* - Exact match: '/api/users'
|
|
4915
|
+
* - Wildcard suffix: '/api/*'
|
|
4916
|
+
*
|
|
4917
|
+
* @param pattern - Pattern to match
|
|
4918
|
+
* @param path - Path to test
|
|
4919
|
+
* @returns True if pattern matches
|
|
4920
|
+
*/
|
|
4921
|
+
matchPattern(pattern, path2) {
|
|
4922
|
+
if (pattern === "*") return true;
|
|
4923
|
+
if (pattern === path2) return true;
|
|
4924
|
+
if (pattern.endsWith("/*")) {
|
|
4925
|
+
const prefix = pattern.slice(0, -2);
|
|
4926
|
+
return path2.startsWith(prefix);
|
|
4927
|
+
}
|
|
4928
|
+
return false;
|
|
4929
|
+
}
|
|
4930
|
+
/**
|
|
4931
|
+
* Find the original route key for a matched dynamic route
|
|
4932
|
+
*
|
|
4933
|
+
* This is needed to look up route-specific middleware.
|
|
4934
|
+
* It's a bit of a hack, but avoids storing duplicate data.
|
|
4935
|
+
*
|
|
4936
|
+
* @param method - HTTP method
|
|
4937
|
+
* @param path - Matched path
|
|
4938
|
+
* @returns Route key or null
|
|
4939
|
+
*/
|
|
4940
|
+
findDynamicRouteKey(method, _path) {
|
|
4941
|
+
for (const key of this.pathMiddleware.keys()) {
|
|
4942
|
+
if (key.startsWith(`${method}:`)) {
|
|
4943
|
+
return key;
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
return null;
|
|
4947
|
+
}
|
|
4948
|
+
/**
|
|
4949
|
+
* Get all registered routes (for debugging)
|
|
4950
|
+
*/
|
|
4951
|
+
getRoutes() {
|
|
4952
|
+
const routes = [];
|
|
4953
|
+
for (const key of this.staticRoutes.keys()) {
|
|
4954
|
+
const [method, path2] = key.split(":");
|
|
4955
|
+
routes.push({ method, path: path2, type: "static" });
|
|
4956
|
+
}
|
|
4957
|
+
return routes;
|
|
4958
|
+
}
|
|
4959
|
+
};
|
|
4960
|
+
|
|
4961
|
+
// src/engine/analyzer.ts
|
|
4962
|
+
function analyzeHandler(handler) {
|
|
4963
|
+
const source = handler.toString();
|
|
4964
|
+
return {
|
|
4965
|
+
usesHeaders: source.includes(".header(") || source.includes(".header)") || source.includes(".headers(") || source.includes(".headers)"),
|
|
4966
|
+
usesQuery: source.includes(".query(") || source.includes(".query)") || source.includes(".queries(") || source.includes(".queries)"),
|
|
4967
|
+
usesBody: source.includes(".json()") || source.includes(".text()") || source.includes(".formData()") || source.includes(".body"),
|
|
4968
|
+
usesParams: source.includes(".param(") || source.includes(".param)") || source.includes(".params(") || source.includes(".params)"),
|
|
4969
|
+
isAsync: source.includes("async") || source.includes("await")
|
|
4970
|
+
};
|
|
4971
|
+
}
|
|
4972
|
+
function getOptimalContextType(analysis) {
|
|
4973
|
+
if (analysis.usesHeaders) {
|
|
4974
|
+
return "fast";
|
|
4975
|
+
}
|
|
4976
|
+
if (!analysis.usesQuery && !analysis.usesBody && !analysis.usesParams) {
|
|
4977
|
+
return "minimal";
|
|
4978
|
+
}
|
|
4979
|
+
if (!analysis.usesQuery && !analysis.usesBody && analysis.usesParams) {
|
|
4980
|
+
return "minimal";
|
|
4981
|
+
}
|
|
4982
|
+
if (analysis.usesBody) {
|
|
4983
|
+
return "full";
|
|
4984
|
+
}
|
|
4985
|
+
return "fast";
|
|
4986
|
+
}
|
|
4987
|
+
|
|
4988
|
+
// src/engine/FastContext.ts
|
|
4989
|
+
var FastRequestImpl = class {
|
|
4990
|
+
_request;
|
|
4991
|
+
_params;
|
|
4992
|
+
_url = new URL("http://localhost");
|
|
4993
|
+
// Reuse this object
|
|
4994
|
+
_query = null;
|
|
4995
|
+
_headers = null;
|
|
4996
|
+
/**
|
|
4997
|
+
* Reset for pooling
|
|
4998
|
+
*/
|
|
4999
|
+
reset(request, params = {}) {
|
|
5000
|
+
this._request = request;
|
|
5001
|
+
this._params = params;
|
|
5002
|
+
this._url.href = request.url;
|
|
5003
|
+
this._query = null;
|
|
5004
|
+
this._headers = null;
|
|
5005
|
+
}
|
|
5006
|
+
get url() {
|
|
5007
|
+
return this._request.url;
|
|
5008
|
+
}
|
|
5009
|
+
get method() {
|
|
5010
|
+
return this._request.method;
|
|
5011
|
+
}
|
|
5012
|
+
get path() {
|
|
5013
|
+
return this._url.pathname;
|
|
5014
|
+
}
|
|
5015
|
+
param(name) {
|
|
5016
|
+
return this._params[name];
|
|
5017
|
+
}
|
|
5018
|
+
params() {
|
|
5019
|
+
return { ...this._params };
|
|
5020
|
+
}
|
|
5021
|
+
query(name) {
|
|
5022
|
+
if (!this._query) {
|
|
5023
|
+
this._query = this._url.searchParams;
|
|
5024
|
+
}
|
|
5025
|
+
return this._query.get(name) ?? void 0;
|
|
5026
|
+
}
|
|
5027
|
+
queries() {
|
|
5028
|
+
if (!this._query) {
|
|
5029
|
+
this._query = this._url.searchParams;
|
|
5030
|
+
}
|
|
5031
|
+
const result = {};
|
|
5032
|
+
for (const [key, value2] of this._query.entries()) {
|
|
5033
|
+
const existing = result[key];
|
|
5034
|
+
if (existing === void 0) {
|
|
5035
|
+
result[key] = value2;
|
|
5036
|
+
} else if (Array.isArray(existing)) {
|
|
5037
|
+
existing.push(value2);
|
|
5038
|
+
} else {
|
|
5039
|
+
result[key] = [existing, value2];
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
return result;
|
|
5043
|
+
}
|
|
5044
|
+
header(name) {
|
|
5045
|
+
return this._request.headers.get(name) ?? void 0;
|
|
5046
|
+
}
|
|
5047
|
+
headers() {
|
|
5048
|
+
if (!this._headers) {
|
|
5049
|
+
this._headers = {};
|
|
5050
|
+
for (const [key, value2] of this._request.headers.entries()) {
|
|
5051
|
+
this._headers[key] = value2;
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
return { ...this._headers };
|
|
5055
|
+
}
|
|
5056
|
+
async json() {
|
|
5057
|
+
return this._request.json();
|
|
5058
|
+
}
|
|
5059
|
+
async text() {
|
|
5060
|
+
return this._request.text();
|
|
5061
|
+
}
|
|
5062
|
+
async formData() {
|
|
5063
|
+
return this._request.formData();
|
|
5064
|
+
}
|
|
5065
|
+
get raw() {
|
|
5066
|
+
return this._request;
|
|
5067
|
+
}
|
|
5068
|
+
};
|
|
5069
|
+
var FastContext = class {
|
|
5070
|
+
_req = new FastRequestImpl();
|
|
5071
|
+
// private _statusCode = 200
|
|
5072
|
+
_headers = new Headers();
|
|
5073
|
+
// Reuse this object
|
|
5074
|
+
/**
|
|
5075
|
+
* Reset context for pooling
|
|
5076
|
+
*
|
|
5077
|
+
* This is called when acquiring from the pool.
|
|
5078
|
+
* Must clear all state from previous request.
|
|
5079
|
+
*/
|
|
5080
|
+
reset(request, params = {}) {
|
|
5081
|
+
this._req.reset(request, params);
|
|
5082
|
+
this._headers = new Headers();
|
|
5083
|
+
return this;
|
|
5084
|
+
}
|
|
5085
|
+
get req() {
|
|
5086
|
+
return this._req;
|
|
5087
|
+
}
|
|
5088
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5089
|
+
// Response Helpers
|
|
5090
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5091
|
+
json(data, status = 200) {
|
|
5092
|
+
this._headers.set("Content-Type", "application/json; charset=utf-8");
|
|
5093
|
+
return new Response(JSON.stringify(data), {
|
|
5094
|
+
status,
|
|
5095
|
+
headers: this._headers
|
|
5096
|
+
});
|
|
5097
|
+
}
|
|
5098
|
+
text(text, status = 200) {
|
|
5099
|
+
this._headers.set("Content-Type", "text/plain; charset=utf-8");
|
|
5100
|
+
return new Response(text, {
|
|
5101
|
+
status,
|
|
5102
|
+
headers: this._headers
|
|
5103
|
+
});
|
|
5104
|
+
}
|
|
5105
|
+
html(html, status = 200) {
|
|
5106
|
+
this._headers.set("Content-Type", "text/html; charset=utf-8");
|
|
5107
|
+
return new Response(html, {
|
|
5108
|
+
status,
|
|
5109
|
+
headers: this._headers
|
|
5110
|
+
});
|
|
5111
|
+
}
|
|
5112
|
+
redirect(url, status = 302) {
|
|
5113
|
+
this._headers.set("Location", url);
|
|
5114
|
+
return new Response(null, {
|
|
5115
|
+
status,
|
|
5116
|
+
headers: this._headers
|
|
5117
|
+
});
|
|
5118
|
+
}
|
|
5119
|
+
body(data, status = 200) {
|
|
5120
|
+
return new Response(data, {
|
|
5121
|
+
status,
|
|
5122
|
+
headers: this._headers
|
|
5123
|
+
});
|
|
5124
|
+
}
|
|
5125
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5126
|
+
// Header Management
|
|
5127
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5128
|
+
header(name, value2) {
|
|
5129
|
+
this._headers.set(name, value2);
|
|
5130
|
+
}
|
|
5131
|
+
status(_code) {
|
|
5132
|
+
}
|
|
5133
|
+
};
|
|
5134
|
+
|
|
5135
|
+
// src/engine/MinimalContext.ts
|
|
5136
|
+
var MinimalRequest = class {
|
|
5137
|
+
constructor(_request, _params, _path) {
|
|
5138
|
+
this._request = _request;
|
|
5139
|
+
this._params = _params;
|
|
5140
|
+
this._path = _path;
|
|
5141
|
+
}
|
|
5142
|
+
get url() {
|
|
5143
|
+
return this._request.url;
|
|
5144
|
+
}
|
|
5145
|
+
get method() {
|
|
5146
|
+
return this._request.method;
|
|
5147
|
+
}
|
|
5148
|
+
get path() {
|
|
5149
|
+
return this._path;
|
|
5150
|
+
}
|
|
5151
|
+
param(name) {
|
|
5152
|
+
return this._params[name];
|
|
5153
|
+
}
|
|
5154
|
+
params() {
|
|
5155
|
+
return { ...this._params };
|
|
5156
|
+
}
|
|
5157
|
+
query(name) {
|
|
5158
|
+
const url = new URL(this._request.url);
|
|
5159
|
+
return url.searchParams.get(name) ?? void 0;
|
|
5160
|
+
}
|
|
5161
|
+
queries() {
|
|
5162
|
+
const url = new URL(this._request.url);
|
|
5163
|
+
const result = {};
|
|
5164
|
+
for (const [key, value2] of url.searchParams.entries()) {
|
|
5165
|
+
const existing = result[key];
|
|
5166
|
+
if (existing === void 0) {
|
|
5167
|
+
result[key] = value2;
|
|
5168
|
+
} else if (Array.isArray(existing)) {
|
|
5169
|
+
existing.push(value2);
|
|
5170
|
+
} else {
|
|
5171
|
+
result[key] = [existing, value2];
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
return result;
|
|
5175
|
+
}
|
|
5176
|
+
header(name) {
|
|
5177
|
+
return this._request.headers.get(name) ?? void 0;
|
|
5178
|
+
}
|
|
5179
|
+
headers() {
|
|
5180
|
+
const result = {};
|
|
5181
|
+
for (const [key, value2] of this._request.headers.entries()) {
|
|
5182
|
+
result[key] = value2;
|
|
5183
|
+
}
|
|
5184
|
+
return result;
|
|
5185
|
+
}
|
|
5186
|
+
async json() {
|
|
5187
|
+
return this._request.json();
|
|
5188
|
+
}
|
|
5189
|
+
async text() {
|
|
5190
|
+
return this._request.text();
|
|
5191
|
+
}
|
|
5192
|
+
async formData() {
|
|
5193
|
+
return this._request.formData();
|
|
5194
|
+
}
|
|
5195
|
+
get raw() {
|
|
5196
|
+
return this._request;
|
|
5197
|
+
}
|
|
5198
|
+
};
|
|
5199
|
+
var MinimalContext = class {
|
|
5200
|
+
_req;
|
|
5201
|
+
constructor(request, params, path2) {
|
|
5202
|
+
this._req = new MinimalRequest(request, params, path2);
|
|
5203
|
+
}
|
|
5204
|
+
get req() {
|
|
5205
|
+
return this._req;
|
|
5206
|
+
}
|
|
5207
|
+
// Response helpers - create headers inline (no reuse overhead)
|
|
5208
|
+
json(data, status = 200) {
|
|
5209
|
+
return new Response(JSON.stringify(data), {
|
|
5210
|
+
status,
|
|
5211
|
+
headers: { "Content-Type": "application/json; charset=utf-8" }
|
|
5212
|
+
});
|
|
5213
|
+
}
|
|
5214
|
+
text(text, status = 200) {
|
|
5215
|
+
return new Response(text, {
|
|
5216
|
+
status,
|
|
5217
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5220
|
+
html(html, status = 200) {
|
|
5221
|
+
return new Response(html, {
|
|
5222
|
+
status,
|
|
5223
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
5224
|
+
});
|
|
5225
|
+
}
|
|
5226
|
+
redirect(url, status = 302) {
|
|
5227
|
+
return new Response(null, {
|
|
5228
|
+
status,
|
|
5229
|
+
headers: { Location: url }
|
|
5230
|
+
});
|
|
5231
|
+
}
|
|
5232
|
+
body(data, status = 200) {
|
|
5233
|
+
return new Response(data, { status });
|
|
5234
|
+
}
|
|
5235
|
+
header(_name, _value) {
|
|
5236
|
+
console.warn("MinimalContext.header() is a no-op. Use FastContext for custom headers.");
|
|
5237
|
+
}
|
|
5238
|
+
status(_code) {
|
|
5239
|
+
}
|
|
5240
|
+
// Required for interface compatibility
|
|
5241
|
+
reset(_request, _params) {
|
|
5242
|
+
throw new Error("MinimalContext does not support reset. Create a new instance instead.");
|
|
5243
|
+
}
|
|
5244
|
+
};
|
|
5245
|
+
|
|
5246
|
+
// src/engine/path.ts
|
|
5247
|
+
function extractPath(url) {
|
|
5248
|
+
const protocolEnd = url.indexOf("://");
|
|
5249
|
+
const searchStart = protocolEnd === -1 ? 0 : protocolEnd + 3;
|
|
5250
|
+
const pathStart = url.indexOf("/", searchStart);
|
|
5251
|
+
if (pathStart === -1) {
|
|
5252
|
+
return "/";
|
|
5253
|
+
}
|
|
5254
|
+
const queryStart = url.indexOf("?", pathStart);
|
|
5255
|
+
if (queryStart === -1) {
|
|
5256
|
+
return url.slice(pathStart);
|
|
5257
|
+
}
|
|
5258
|
+
return url.slice(pathStart, queryStart);
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5261
|
+
// src/engine/pool.ts
|
|
5262
|
+
var ObjectPool = class {
|
|
5263
|
+
pool = [];
|
|
5264
|
+
factory;
|
|
5265
|
+
reset;
|
|
5266
|
+
maxSize;
|
|
5267
|
+
/**
|
|
5268
|
+
* Create a new object pool
|
|
5269
|
+
*
|
|
5270
|
+
* @param factory - Function to create new objects
|
|
5271
|
+
* @param reset - Function to reset objects before reuse
|
|
5272
|
+
* @param maxSize - Maximum pool size (default: 256)
|
|
5273
|
+
*/
|
|
5274
|
+
constructor(factory, reset, maxSize = 256) {
|
|
5275
|
+
this.factory = factory;
|
|
5276
|
+
this.reset = reset;
|
|
5277
|
+
this.maxSize = maxSize;
|
|
5278
|
+
}
|
|
5279
|
+
/**
|
|
5280
|
+
* Acquire an object from the pool
|
|
5281
|
+
*
|
|
5282
|
+
* If the pool is empty, creates a new object (overflow strategy).
|
|
5283
|
+
* This ensures the pool never blocks under high load.
|
|
5284
|
+
*
|
|
5285
|
+
* @returns Object from pool or newly created
|
|
5286
|
+
*/
|
|
5287
|
+
acquire() {
|
|
5288
|
+
const obj = this.pool.pop();
|
|
5289
|
+
if (obj !== void 0) {
|
|
5290
|
+
return obj;
|
|
5291
|
+
}
|
|
5292
|
+
return this.factory();
|
|
5293
|
+
}
|
|
5294
|
+
/**
|
|
5295
|
+
* Release an object back to the pool
|
|
5296
|
+
*
|
|
5297
|
+
* If the pool is full, the object is discarded (will be GC'd).
|
|
5298
|
+
* This prevents unbounded memory growth.
|
|
5299
|
+
*
|
|
5300
|
+
* @param obj - Object to release
|
|
5301
|
+
*/
|
|
5302
|
+
release(obj) {
|
|
5303
|
+
if (this.pool.length < this.maxSize) {
|
|
5304
|
+
this.reset(obj);
|
|
5305
|
+
this.pool.push(obj);
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5308
|
+
/**
|
|
5309
|
+
* Clear all objects from the pool
|
|
5310
|
+
*
|
|
5311
|
+
* Useful for testing or when you need to force a clean slate.
|
|
5312
|
+
*/
|
|
5313
|
+
clear() {
|
|
5314
|
+
this.pool = [];
|
|
5315
|
+
}
|
|
5316
|
+
/**
|
|
5317
|
+
* Get current pool size
|
|
5318
|
+
*/
|
|
5319
|
+
get size() {
|
|
5320
|
+
return this.pool.length;
|
|
5321
|
+
}
|
|
5322
|
+
/**
|
|
5323
|
+
* Get maximum pool size
|
|
5324
|
+
*/
|
|
5325
|
+
get capacity() {
|
|
5326
|
+
return this.maxSize;
|
|
5327
|
+
}
|
|
5328
|
+
/**
|
|
5329
|
+
* Pre-warm the pool by creating objects in advance
|
|
5330
|
+
*
|
|
5331
|
+
* This can reduce latency for the first N requests.
|
|
5332
|
+
*
|
|
5333
|
+
* @param count - Number of objects to pre-create
|
|
5334
|
+
*/
|
|
5335
|
+
prewarm(count) {
|
|
5336
|
+
const targetSize = Math.min(count, this.maxSize);
|
|
5337
|
+
while (this.pool.length < targetSize) {
|
|
5338
|
+
const obj = this.factory();
|
|
5339
|
+
this.reset(obj);
|
|
5340
|
+
this.pool.push(obj);
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
};
|
|
5344
|
+
|
|
5345
|
+
// src/engine/Gravito.ts
|
|
5346
|
+
var Gravito = class {
|
|
5347
|
+
router = new AOTRouter();
|
|
5348
|
+
contextPool;
|
|
5349
|
+
errorHandler;
|
|
5350
|
+
notFoundHandler;
|
|
5351
|
+
// Direct reference to static routes Map (O(1) access)
|
|
5352
|
+
// Optimization: Bypass getter/setter overhead
|
|
5353
|
+
staticRoutes;
|
|
5354
|
+
// Flag: pure static app (no middleware at all) allows ultra-fast path
|
|
5355
|
+
isPureStaticApp = true;
|
|
5356
|
+
/**
|
|
5357
|
+
* Create a new Gravito instance
|
|
5358
|
+
*
|
|
5359
|
+
* @param options - Engine configuration options
|
|
5360
|
+
*/
|
|
5361
|
+
constructor(options = {}) {
|
|
5362
|
+
const poolSize = options.poolSize ?? 256;
|
|
5363
|
+
this.contextPool = new ObjectPool(
|
|
5364
|
+
() => new FastContext(),
|
|
5365
|
+
(ctx) => ctx.reset(new Request("http://localhost")),
|
|
5366
|
+
poolSize
|
|
5367
|
+
);
|
|
5368
|
+
this.contextPool.prewarm(Math.min(32, poolSize));
|
|
5369
|
+
if (options.onError) {
|
|
5370
|
+
this.errorHandler = options.onError;
|
|
5371
|
+
}
|
|
5372
|
+
if (options.onNotFound) {
|
|
5373
|
+
this.notFoundHandler = options.onNotFound;
|
|
5374
|
+
}
|
|
5375
|
+
this.compileRoutes();
|
|
5376
|
+
}
|
|
5377
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5378
|
+
// HTTP Method Registration
|
|
5379
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5380
|
+
/**
|
|
5381
|
+
* Register a GET route
|
|
5382
|
+
*
|
|
5383
|
+
* @param path - Route path (e.g., '/users/:id')
|
|
5384
|
+
* @param handlers - Handler and optional middleware
|
|
5385
|
+
* @returns This instance for chaining
|
|
5386
|
+
*/
|
|
5387
|
+
get(path2, ...handlers) {
|
|
5388
|
+
return this.addRoute("get", path2, handlers);
|
|
5389
|
+
}
|
|
5390
|
+
/**
|
|
5391
|
+
* Register a POST route
|
|
5392
|
+
*/
|
|
5393
|
+
post(path2, ...handlers) {
|
|
5394
|
+
return this.addRoute("post", path2, handlers);
|
|
5395
|
+
}
|
|
5396
|
+
/**
|
|
5397
|
+
* Register a PUT route
|
|
5398
|
+
*/
|
|
5399
|
+
put(path2, ...handlers) {
|
|
5400
|
+
return this.addRoute("put", path2, handlers);
|
|
5401
|
+
}
|
|
5402
|
+
/**
|
|
5403
|
+
* Register a DELETE route
|
|
5404
|
+
*/
|
|
5405
|
+
delete(path2, ...handlers) {
|
|
5406
|
+
return this.addRoute("delete", path2, handlers);
|
|
5407
|
+
}
|
|
5408
|
+
/**
|
|
5409
|
+
* Register a PATCH route
|
|
5410
|
+
*/
|
|
5411
|
+
patch(path2, ...handlers) {
|
|
5412
|
+
return this.addRoute("patch", path2, handlers);
|
|
5413
|
+
}
|
|
5414
|
+
/**
|
|
5415
|
+
* Register an OPTIONS route
|
|
5416
|
+
*/
|
|
5417
|
+
options(path2, ...handlers) {
|
|
5418
|
+
return this.addRoute("options", path2, handlers);
|
|
5419
|
+
}
|
|
5420
|
+
/**
|
|
5421
|
+
* Register a HEAD route
|
|
5422
|
+
*/
|
|
5423
|
+
head(path2, ...handlers) {
|
|
5424
|
+
return this.addRoute("head", path2, handlers);
|
|
5425
|
+
}
|
|
5426
|
+
/**
|
|
5427
|
+
* Register a route for all HTTP methods
|
|
5428
|
+
*/
|
|
5429
|
+
all(path2, ...handlers) {
|
|
5430
|
+
const methods = ["get", "post", "put", "delete", "patch", "options", "head"];
|
|
5431
|
+
for (const method of methods) {
|
|
5432
|
+
this.addRoute(method, path2, handlers);
|
|
5433
|
+
}
|
|
5434
|
+
return this;
|
|
5435
|
+
}
|
|
5436
|
+
use(pathOrMiddleware, ...middleware) {
|
|
5437
|
+
this.isPureStaticApp = false;
|
|
5438
|
+
if (typeof pathOrMiddleware === "string") {
|
|
5439
|
+
this.router.usePattern(pathOrMiddleware, ...middleware);
|
|
5440
|
+
} else {
|
|
5441
|
+
this.router.use(pathOrMiddleware, ...middleware);
|
|
5442
|
+
}
|
|
5443
|
+
return this;
|
|
5444
|
+
}
|
|
5445
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5446
|
+
// Route Grouping
|
|
5447
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5448
|
+
/**
|
|
5449
|
+
* Mount a sub-application at a path prefix
|
|
5450
|
+
*
|
|
5451
|
+
* @example
|
|
5452
|
+
* ```typescript
|
|
5453
|
+
* const api = new Gravito()
|
|
5454
|
+
* api.get('/users', (c) => c.json({ users: [] }))
|
|
5455
|
+
*
|
|
5456
|
+
* const app = new Gravito()
|
|
5457
|
+
* app.route('/api', api)
|
|
5458
|
+
* // Now accessible at /api/users
|
|
5459
|
+
* ```
|
|
5460
|
+
*/
|
|
5461
|
+
route(_path, _app) {
|
|
5462
|
+
console.warn("route() method is not yet fully implemented");
|
|
5463
|
+
return this;
|
|
5464
|
+
}
|
|
5465
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5466
|
+
// Error Handling
|
|
5467
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5468
|
+
/**
|
|
5469
|
+
* Set custom error handler
|
|
5470
|
+
*
|
|
5471
|
+
* @example
|
|
5472
|
+
* ```typescript
|
|
5473
|
+
* app.onError((err, c) => {
|
|
5474
|
+
* console.error(err)
|
|
5475
|
+
* return c.json({ error: err.message }, 500)
|
|
5476
|
+
* })
|
|
5477
|
+
* ```
|
|
5478
|
+
*/
|
|
5479
|
+
onError(handler) {
|
|
5480
|
+
this.errorHandler = handler;
|
|
5481
|
+
return this;
|
|
5482
|
+
}
|
|
5483
|
+
/**
|
|
5484
|
+
* Set custom 404 handler
|
|
5485
|
+
*
|
|
5486
|
+
* @example
|
|
5487
|
+
* ```typescript
|
|
5488
|
+
* app.notFound((c) => {
|
|
5489
|
+
* return c.json({ error: 'Not Found' }, 404)
|
|
5490
|
+
* })
|
|
5491
|
+
* ```
|
|
5492
|
+
*/
|
|
5493
|
+
notFound(handler) {
|
|
5494
|
+
this.notFoundHandler = handler;
|
|
5495
|
+
return this;
|
|
5496
|
+
}
|
|
5497
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5498
|
+
// Request Handling (Bun.serve integration)
|
|
5499
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5500
|
+
/**
|
|
5501
|
+
* Handle an incoming request
|
|
5502
|
+
*
|
|
5503
|
+
* Optimized for minimal allocations and maximum throughput.
|
|
5504
|
+
* Uses sync/async dual-path strategy inspired by Hono.
|
|
5505
|
+
*
|
|
5506
|
+
* @param request - Incoming Request object
|
|
5507
|
+
* @returns Response object (sync or async)
|
|
5508
|
+
*/
|
|
5509
|
+
fetch = (request) => {
|
|
5510
|
+
const path2 = extractPath(request.url);
|
|
5511
|
+
const method = request.method.toLowerCase();
|
|
5512
|
+
const staticKey = `${method}:${path2}`;
|
|
5513
|
+
const staticRoute = this.staticRoutes.get(staticKey);
|
|
5514
|
+
if (staticRoute) {
|
|
5515
|
+
if (staticRoute.useMinimal) {
|
|
5516
|
+
const ctx = new MinimalContext(request, {}, path2);
|
|
5517
|
+
try {
|
|
5518
|
+
const result = staticRoute.handler(ctx);
|
|
5519
|
+
if (result instanceof Response) {
|
|
5520
|
+
return result;
|
|
5521
|
+
}
|
|
5522
|
+
return result;
|
|
5523
|
+
} catch (error) {
|
|
5524
|
+
return this.handleErrorSync(error, request, path2);
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
return this.handleWithMiddleware(request, path2, staticRoute);
|
|
5528
|
+
}
|
|
5529
|
+
return this.handleDynamicRoute(request, method, path2);
|
|
5530
|
+
};
|
|
5531
|
+
/**
|
|
5532
|
+
* Handle routes with middleware (async path)
|
|
5533
|
+
*/
|
|
5534
|
+
async handleWithMiddleware(request, path2, route) {
|
|
5535
|
+
const ctx = this.contextPool.acquire();
|
|
5536
|
+
try {
|
|
5537
|
+
ctx.reset(request, {});
|
|
5538
|
+
const middleware = this.collectMiddlewareForPath(path2, route.middleware);
|
|
5539
|
+
if (middleware.length === 0) {
|
|
5540
|
+
return await route.handler(ctx);
|
|
5541
|
+
}
|
|
5542
|
+
return await this.executeMiddleware(ctx, middleware, route.handler);
|
|
5543
|
+
} catch (error) {
|
|
5544
|
+
return await this.handleError(error, ctx);
|
|
5545
|
+
} finally {
|
|
5546
|
+
this.contextPool.release(ctx);
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
/**
|
|
5550
|
+
* Handle dynamic routes (Radix Tree lookup)
|
|
5551
|
+
*/
|
|
5552
|
+
handleDynamicRoute(request, method, path2) {
|
|
5553
|
+
const match = this.router.match(method.toUpperCase(), path2);
|
|
5554
|
+
if (!match.handler) {
|
|
5555
|
+
return this.handleNotFoundSync(request, path2);
|
|
5556
|
+
}
|
|
5557
|
+
const ctx = this.contextPool.acquire();
|
|
5558
|
+
const execute = async () => {
|
|
5559
|
+
try {
|
|
5560
|
+
ctx.reset(request, match.params);
|
|
5561
|
+
if (match.middleware.length === 0) {
|
|
5562
|
+
return await match.handler(ctx);
|
|
5563
|
+
}
|
|
5564
|
+
return await this.executeMiddleware(ctx, match.middleware, match.handler);
|
|
5565
|
+
} catch (error) {
|
|
5566
|
+
return await this.handleError(error, ctx);
|
|
5567
|
+
} finally {
|
|
5568
|
+
this.contextPool.release(ctx);
|
|
5569
|
+
}
|
|
5570
|
+
};
|
|
5571
|
+
return execute();
|
|
5572
|
+
}
|
|
5573
|
+
/**
|
|
5574
|
+
* Sync error handler (for ultra-fast path)
|
|
5575
|
+
*/
|
|
5576
|
+
handleErrorSync(error, request, path2) {
|
|
5577
|
+
if (this.errorHandler) {
|
|
5578
|
+
const ctx = new MinimalContext(request, {}, path2);
|
|
5579
|
+
const result = this.errorHandler(error, ctx);
|
|
5580
|
+
if (result instanceof Response) {
|
|
5581
|
+
return result;
|
|
5582
|
+
}
|
|
5583
|
+
return result;
|
|
5584
|
+
}
|
|
5585
|
+
console.error("Unhandled error:", error);
|
|
5586
|
+
return new Response(
|
|
5587
|
+
JSON.stringify({
|
|
5588
|
+
error: "Internal Server Error",
|
|
5589
|
+
message: error.message
|
|
5590
|
+
}),
|
|
5591
|
+
{
|
|
5592
|
+
status: 500,
|
|
5593
|
+
headers: { "Content-Type": "application/json" }
|
|
5594
|
+
}
|
|
5595
|
+
);
|
|
5596
|
+
}
|
|
5597
|
+
/**
|
|
5598
|
+
* Sync 404 handler (for ultra-fast path)
|
|
5599
|
+
*/
|
|
5600
|
+
handleNotFoundSync(request, path2) {
|
|
5601
|
+
if (this.notFoundHandler) {
|
|
5602
|
+
const ctx = new MinimalContext(request, {}, path2);
|
|
5603
|
+
const result = this.notFoundHandler(ctx);
|
|
5604
|
+
if (result instanceof Response) {
|
|
5605
|
+
return result;
|
|
5606
|
+
}
|
|
5607
|
+
return result;
|
|
5608
|
+
}
|
|
5609
|
+
return new Response(JSON.stringify({ error: "Not Found" }), {
|
|
5610
|
+
status: 404,
|
|
5611
|
+
headers: { "Content-Type": "application/json" }
|
|
5612
|
+
});
|
|
5613
|
+
}
|
|
5614
|
+
/**
|
|
5615
|
+
* Collect middleware for a specific path
|
|
5616
|
+
* (Simplified version - assumes we've already checked for pure static)
|
|
5617
|
+
*/
|
|
5618
|
+
collectMiddlewareForPath(path2, routeMiddleware) {
|
|
5619
|
+
if (this.router.globalMiddleware.length === 0 && this.router.pathMiddleware.size === 0) {
|
|
5620
|
+
return routeMiddleware;
|
|
5621
|
+
}
|
|
5622
|
+
return this.router.collectMiddlewarePublic(path2, routeMiddleware);
|
|
5623
|
+
}
|
|
5624
|
+
/**
|
|
5625
|
+
* Compile routes for optimization
|
|
5626
|
+
* Called once during initialization and when routes change
|
|
5627
|
+
*/
|
|
5628
|
+
compileRoutes() {
|
|
5629
|
+
this.staticRoutes = this.router.staticRoutes;
|
|
5630
|
+
const hasGlobalMiddleware = this.router.globalMiddleware.length > 0;
|
|
5631
|
+
const hasPathMiddleware = this.router.pathMiddleware.size > 0;
|
|
5632
|
+
this.isPureStaticApp = !hasGlobalMiddleware && !hasPathMiddleware;
|
|
5633
|
+
for (const [_key, route] of this.staticRoutes) {
|
|
5634
|
+
const analysis = analyzeHandler(route.handler);
|
|
5635
|
+
const optimalType = getOptimalContextType(analysis);
|
|
5636
|
+
route.useMinimal = this.isPureStaticApp && route.middleware.length === 0 && optimalType === "minimal";
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5640
|
+
// Internal Methods
|
|
5641
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5642
|
+
/**
|
|
5643
|
+
* Add a route to the router
|
|
5644
|
+
*/
|
|
5645
|
+
addRoute(method, path2, handlers) {
|
|
5646
|
+
if (handlers.length === 0) {
|
|
5647
|
+
throw new Error(`No handler provided for ${method.toUpperCase()} ${path2}`);
|
|
5648
|
+
}
|
|
5649
|
+
const handler = handlers[handlers.length - 1];
|
|
5650
|
+
const middleware = handlers.slice(0, -1);
|
|
5651
|
+
this.router.add(method, path2, handler, middleware);
|
|
5652
|
+
this.compileRoutes();
|
|
5653
|
+
return this;
|
|
5654
|
+
}
|
|
5655
|
+
/**
|
|
5656
|
+
* Execute middleware chain followed by handler
|
|
5657
|
+
*
|
|
5658
|
+
* Implements the standard middleware pattern:
|
|
5659
|
+
* Each middleware can call next() to continue the chain.
|
|
5660
|
+
*/
|
|
5661
|
+
async executeMiddleware(ctx, middleware, handler) {
|
|
5662
|
+
let index = 0;
|
|
5663
|
+
const next = async () => {
|
|
5664
|
+
if (index < middleware.length) {
|
|
5665
|
+
const mw = middleware[index++];
|
|
5666
|
+
return await mw(ctx, next);
|
|
5667
|
+
}
|
|
5668
|
+
return void 0;
|
|
5669
|
+
};
|
|
5670
|
+
const result = await next();
|
|
5671
|
+
if (result instanceof Response) {
|
|
5672
|
+
return result;
|
|
5673
|
+
}
|
|
5674
|
+
return await handler(ctx);
|
|
5675
|
+
}
|
|
5676
|
+
/*
|
|
5677
|
+
* Handle 404 Not Found (Async version for dynamic/middleware paths)
|
|
5678
|
+
* Note: Currently unused as we handle 404s via handleNotFoundSync or inline
|
|
5679
|
+
*/
|
|
5680
|
+
// private async handleNotFound(ctx: FastContext): Promise<Response> {
|
|
5681
|
+
// if (this.notFoundHandler) {
|
|
5682
|
+
// return await this.notFoundHandler(ctx)
|
|
5683
|
+
// }
|
|
5684
|
+
// return ctx.json({ error: 'Not Found' }, 404)
|
|
5685
|
+
// }
|
|
5686
|
+
/**
|
|
5687
|
+
* Handle errors (Async version for dynamic/middleware paths)
|
|
5688
|
+
*/
|
|
5689
|
+
async handleError(error, ctx) {
|
|
5690
|
+
if (this.errorHandler) {
|
|
5691
|
+
return await this.errorHandler(error, ctx);
|
|
5692
|
+
}
|
|
5693
|
+
console.error("Unhandled error:", error);
|
|
5694
|
+
return ctx.json(
|
|
5695
|
+
{
|
|
5696
|
+
error: "Internal Server Error",
|
|
5697
|
+
message: error.message
|
|
5698
|
+
},
|
|
5699
|
+
500
|
|
5700
|
+
);
|
|
5701
|
+
}
|
|
5702
|
+
};
|
|
5703
|
+
|
|
4712
5704
|
// src/index.ts
|
|
4713
5705
|
var VERSION = package_default.version;
|
|
4714
5706
|
function defineConfig(config2) {
|
|
@@ -4740,6 +5732,7 @@ function defineConfig(config2) {
|
|
|
4740
5732
|
PhotonRequestWrapper,
|
|
4741
5733
|
PlanetCore,
|
|
4742
5734
|
Route,
|
|
5735
|
+
RouteGroup,
|
|
4743
5736
|
Router,
|
|
4744
5737
|
ServiceProvider,
|
|
4745
5738
|
Str,
|
|
@@ -4768,6 +5761,7 @@ function defineConfig(config2) {
|
|
|
4768
5761
|
dd,
|
|
4769
5762
|
defineConfig,
|
|
4770
5763
|
dump,
|
|
5764
|
+
engine,
|
|
4771
5765
|
env,
|
|
4772
5766
|
errors,
|
|
4773
5767
|
fail,
|