@gravito/core 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1176 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/engine/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- AOTRouter: () => AOTRouter,
24
- FastContextImpl: () => FastContext,
25
- Gravito: () => Gravito,
26
- MinimalContext: () => MinimalContext,
27
- ObjectPool: () => ObjectPool,
28
- extractPath: () => extractPath
29
- });
30
- module.exports = __toCommonJS(index_exports);
31
-
32
- // src/adapters/bun/RadixNode.ts
33
- var RadixNode = class _RadixNode {
34
- // Path segment for this node (e.g., "users", ":id")
35
- segment;
36
- // Node type (Static, Param, Wildcard)
37
- type;
38
- // Children nodes (mapped by segment for fast lookup)
39
- children = /* @__PURE__ */ new Map();
40
- // Specialized child for parameter node (only one per level allowed usually to avoid ambiguity, though some routers support multiple)
41
- paramChild = null;
42
- // Specialized child for wildcard node
43
- wildcardChild = null;
44
- // Handlers registered at this node (keyed by HTTP method)
45
- handlers = /* @__PURE__ */ new Map();
46
- // Parameter name if this is a PARAM node (e.g., "id" for ":id")
47
- paramName = null;
48
- // Parameter constraints (regex) - only applicable if this is a PARAM node
49
- // If we support per-route constraints, they might need to be stored differently,
50
- // but for now assume constraints are defined at node level (uncommon) or checked at match time.
51
- // Laravel allows global pattern constraints or per-route.
52
- // Ideally, constraints should be stored with the handler or part of matching logic.
53
- // For a Radix tree, if we have constraints, we might need to backtrack if constraint fails?
54
- // Or simply store constraint with the param node.
55
- regex = null;
56
- constructor(segment = "", type = 0 /* STATIC */) {
57
- this.segment = segment;
58
- this.type = type;
59
- }
60
- toJSON() {
61
- return {
62
- segment: this.segment,
63
- type: this.type,
64
- children: Array.from(this.children.entries()).map(([k, v]) => [k, v.toJSON()]),
65
- paramChild: this.paramChild?.toJSON() || null,
66
- wildcardChild: this.wildcardChild?.toJSON() || null,
67
- paramName: this.paramName,
68
- regex: this.regex ? this.regex.source : null
69
- };
70
- }
71
- static fromJSON(json) {
72
- const node = new _RadixNode(json.segment, json.type);
73
- node.paramName = json.paramName;
74
- if (json.regex) {
75
- node.regex = new RegExp(json.regex);
76
- }
77
- if (json.children) {
78
- for (const [key, childJson] of json.children) {
79
- node.children.set(key, _RadixNode.fromJSON(childJson));
80
- }
81
- }
82
- if (json.paramChild) {
83
- node.paramChild = _RadixNode.fromJSON(json.paramChild);
84
- }
85
- if (json.wildcardChild) {
86
- node.wildcardChild = _RadixNode.fromJSON(json.wildcardChild);
87
- }
88
- return node;
89
- }
90
- };
91
-
92
- // src/adapters/bun/RadixRouter.ts
93
- var RadixRouter = class _RadixRouter {
94
- root = new RadixNode();
95
- // Global parameter constraints (e.g., id => /^\d+$/)
96
- globalConstraints = /* @__PURE__ */ new Map();
97
- /**
98
- * Add a generic parameter constraint
99
- */
100
- where(param, regex) {
101
- this.globalConstraints.set(param, regex);
102
- }
103
- /**
104
- * Register a route
105
- */
106
- add(method, path, handlers) {
107
- let node = this.root;
108
- const segments = this.splitPath(path);
109
- for (let i = 0; i < segments.length; i++) {
110
- const segment = segments[i];
111
- if (segment === "*") {
112
- if (!node.wildcardChild) {
113
- node.wildcardChild = new RadixNode("*", 2 /* WILDCARD */);
114
- }
115
- node = node.wildcardChild;
116
- break;
117
- } else if (segment.startsWith(":")) {
118
- const paramName = segment.slice(1);
119
- if (!node.paramChild) {
120
- const child = new RadixNode(segment, 1 /* PARAM */);
121
- child.paramName = paramName;
122
- const constraint = this.globalConstraints.get(paramName);
123
- if (constraint) {
124
- child.regex = constraint;
125
- }
126
- node.paramChild = child;
127
- }
128
- node = node.paramChild;
129
- } else {
130
- if (!node.children.has(segment)) {
131
- node.children.set(segment, new RadixNode(segment, 0 /* STATIC */));
132
- }
133
- node = node.children.get(segment);
134
- }
135
- }
136
- node.handlers.set(method.toLowerCase(), handlers);
137
- }
138
- /**
139
- * Match a request
140
- */
141
- match(method, path) {
142
- const normalizedMethod = method.toLowerCase();
143
- if (path === "/" || path === "") {
144
- const handlers = this.root.handlers.get(normalizedMethod);
145
- if (handlers) {
146
- return { handlers, params: {} };
147
- }
148
- return null;
149
- }
150
- const searchPath = path.startsWith("/") ? path.slice(1) : path;
151
- const segments = searchPath.split("/");
152
- return this.matchRecursive(this.root, segments, 0, {}, normalizedMethod);
153
- }
154
- matchRecursive(node, segments, depth, params, method) {
155
- if (depth >= segments.length) {
156
- let handlers = node.handlers.get(method);
157
- if (!handlers) {
158
- handlers = node.handlers.get("all");
159
- }
160
- if (handlers) {
161
- return { handlers, params };
162
- }
163
- return null;
164
- }
165
- const segment = segments[depth];
166
- const staticChild = node.children.get(segment);
167
- if (staticChild) {
168
- const match = this.matchRecursive(staticChild, segments, depth + 1, params, method);
169
- if (match) {
170
- return match;
171
- }
172
- }
173
- const paramChild = node.paramChild;
174
- if (paramChild) {
175
- if (paramChild.regex && !paramChild.regex.test(segment)) {
176
- } else {
177
- if (paramChild.paramName) {
178
- params[paramChild.paramName] = decodeURIComponent(segment);
179
- const match = this.matchRecursive(paramChild, segments, depth + 1, params, method);
180
- if (match) {
181
- return match;
182
- }
183
- delete params[paramChild.paramName];
184
- }
185
- }
186
- }
187
- if (node.wildcardChild) {
188
- let handlers = node.wildcardChild.handlers.get(method);
189
- if (!handlers) {
190
- handlers = node.wildcardChild.handlers.get("all");
191
- }
192
- if (handlers) {
193
- return { handlers, params };
194
- }
195
- }
196
- return null;
197
- }
198
- splitPath(path) {
199
- if (path === "/" || path === "") {
200
- return [];
201
- }
202
- let p = path;
203
- if (p.startsWith("/")) {
204
- p = p.slice(1);
205
- }
206
- if (p.endsWith("/")) {
207
- p = p.slice(0, -1);
208
- }
209
- return p.split("/");
210
- }
211
- /**
212
- * Serialize the router to a JSON string
213
- */
214
- serialize() {
215
- return JSON.stringify({
216
- root: this.root.toJSON(),
217
- globalConstraints: Array.from(this.globalConstraints.entries()).map(([k, v]) => [
218
- k,
219
- v.source
220
- ])
221
- });
222
- }
223
- /**
224
- * Restore a router from a serialized JSON string
225
- */
226
- static fromSerialized(json) {
227
- const data = JSON.parse(json);
228
- const router = new _RadixRouter();
229
- router.root = RadixNode.fromJSON(data.root);
230
- if (data.globalConstraints) {
231
- for (const [key, source] of data.globalConstraints) {
232
- router.globalConstraints.set(key, new RegExp(source));
233
- }
234
- }
235
- return router;
236
- }
237
- };
238
-
239
- // src/engine/AOTRouter.ts
240
- var AOTRouter = class {
241
- // Static route cache: "METHOD:PATH" -> RouteMetadata
242
- staticRoutes = /* @__PURE__ */ new Map();
243
- // Dynamic route handler (Radix Tree)
244
- dynamicRouter = new RadixRouter();
245
- // Global middleware (applies to all routes)
246
- globalMiddleware = [];
247
- // Path-based middleware: pattern -> middleware[]
248
- pathMiddleware = /* @__PURE__ */ new Map();
249
- /**
250
- * Register a route
251
- *
252
- * Automatically determines if route is static or dynamic.
253
- * Static routes are stored in a Map for O(1) lookup.
254
- * Dynamic routes use the Radix Tree.
255
- *
256
- * @param method - HTTP method
257
- * @param path - Route path
258
- * @param handler - Route handler
259
- * @param middleware - Route-specific middleware
260
- */
261
- add(method, path, handler, middleware = []) {
262
- const normalizedMethod = method.toLowerCase();
263
- if (this.isStaticPath(path)) {
264
- const key = `${normalizedMethod}:${path}`;
265
- this.staticRoutes.set(key, { handler, middleware });
266
- } else {
267
- const wrappedHandler = handler;
268
- this.dynamicRouter.add(normalizedMethod, path, [wrappedHandler]);
269
- if (middleware.length > 0) {
270
- this.pathMiddleware.set(`${normalizedMethod}:${path}`, middleware);
271
- }
272
- }
273
- }
274
- /**
275
- * Add global middleware
276
- *
277
- * These run for every request, before route-specific middleware.
278
- *
279
- * @param middleware - Middleware functions
280
- */
281
- use(...middleware) {
282
- this.globalMiddleware.push(...middleware);
283
- }
284
- /**
285
- * Add path-based middleware
286
- *
287
- * Supports wildcard patterns like '/api/*'
288
- *
289
- * @param pattern - Path pattern
290
- * @param middleware - Middleware functions
291
- */
292
- usePattern(pattern, ...middleware) {
293
- const existing = this.pathMiddleware.get(pattern) ?? [];
294
- this.pathMiddleware.set(pattern, [...existing, ...middleware]);
295
- }
296
- /**
297
- * Match a request to a route
298
- *
299
- * Returns the handler, params, and all applicable middleware.
300
- *
301
- * @param method - HTTP method
302
- * @param path - Request path
303
- * @returns Route match or null if not found
304
- */
305
- match(method, path) {
306
- const normalizedMethod = method.toLowerCase();
307
- const staticKey = `${normalizedMethod}:${path}`;
308
- const staticRoute = this.staticRoutes.get(staticKey);
309
- if (staticRoute) {
310
- return {
311
- handler: staticRoute.handler,
312
- params: {},
313
- middleware: this.collectMiddleware(path, staticRoute.middleware)
314
- };
315
- }
316
- const match = this.dynamicRouter.match(normalizedMethod, path);
317
- if (match && match.handlers.length > 0) {
318
- const handler = match.handlers[0];
319
- const routeKey = this.findDynamicRouteKey(normalizedMethod, path);
320
- const routeMiddleware = routeKey ? this.pathMiddleware.get(routeKey) ?? [] : [];
321
- return {
322
- handler,
323
- params: match.params,
324
- middleware: this.collectMiddleware(path, routeMiddleware)
325
- };
326
- }
327
- return {
328
- handler: null,
329
- params: {},
330
- middleware: []
331
- };
332
- }
333
- /**
334
- * Public wrapper for collectMiddleware (used by Gravito for optimization)
335
- */
336
- collectMiddlewarePublic(path, routeMiddleware) {
337
- return this.collectMiddleware(path, routeMiddleware);
338
- }
339
- /**
340
- * Collect all applicable middleware for a path
341
- *
342
- * Order: global -> pattern-based -> route-specific
343
- *
344
- * @param path - Request path
345
- * @param routeMiddleware - Route-specific middleware
346
- * @returns Combined middleware array
347
- */
348
- collectMiddleware(path, routeMiddleware) {
349
- if (this.globalMiddleware.length === 0 && this.pathMiddleware.size === 0 && routeMiddleware.length === 0) {
350
- return [];
351
- }
352
- const middleware = [];
353
- if (this.globalMiddleware.length > 0) {
354
- middleware.push(...this.globalMiddleware);
355
- }
356
- if (this.pathMiddleware.size > 0) {
357
- for (const [pattern, mw] of this.pathMiddleware) {
358
- if (pattern.includes(":")) continue;
359
- if (this.matchPattern(pattern, path)) {
360
- middleware.push(...mw);
361
- }
362
- }
363
- }
364
- if (routeMiddleware.length > 0) {
365
- middleware.push(...routeMiddleware);
366
- }
367
- return middleware;
368
- }
369
- /**
370
- * Check if a path is static (no parameters or wildcards)
371
- */
372
- isStaticPath(path) {
373
- return !path.includes(":") && !path.includes("*");
374
- }
375
- /**
376
- * Match a pattern against a path
377
- *
378
- * Supports:
379
- * - Exact match: '/api/users'
380
- * - Wildcard suffix: '/api/*'
381
- *
382
- * @param pattern - Pattern to match
383
- * @param path - Path to test
384
- * @returns True if pattern matches
385
- */
386
- matchPattern(pattern, path) {
387
- if (pattern === "*") return true;
388
- if (pattern === path) return true;
389
- if (pattern.endsWith("/*")) {
390
- const prefix = pattern.slice(0, -2);
391
- return path.startsWith(prefix);
392
- }
393
- return false;
394
- }
395
- /**
396
- * Find the original route key for a matched dynamic route
397
- *
398
- * This is needed to look up route-specific middleware.
399
- * It's a bit of a hack, but avoids storing duplicate data.
400
- *
401
- * @param method - HTTP method
402
- * @param path - Matched path
403
- * @returns Route key or null
404
- */
405
- findDynamicRouteKey(method, _path) {
406
- for (const key of this.pathMiddleware.keys()) {
407
- if (key.startsWith(`${method}:`)) {
408
- return key;
409
- }
410
- }
411
- return null;
412
- }
413
- /**
414
- * Get all registered routes (for debugging)
415
- */
416
- getRoutes() {
417
- const routes = [];
418
- for (const key of this.staticRoutes.keys()) {
419
- const [method, path] = key.split(":");
420
- routes.push({ method, path, type: "static" });
421
- }
422
- return routes;
423
- }
424
- };
425
-
426
- // src/engine/analyzer.ts
427
- function analyzeHandler(handler) {
428
- const source = handler.toString();
429
- return {
430
- usesHeaders: source.includes(".header(") || source.includes(".header)") || source.includes(".headers(") || source.includes(".headers)"),
431
- usesQuery: source.includes(".query(") || source.includes(".query)") || source.includes(".queries(") || source.includes(".queries)"),
432
- usesBody: source.includes(".json()") || source.includes(".text()") || source.includes(".formData()") || source.includes(".body"),
433
- usesParams: source.includes(".param(") || source.includes(".param)") || source.includes(".params(") || source.includes(".params)"),
434
- isAsync: source.includes("async") || source.includes("await")
435
- };
436
- }
437
- function getOptimalContextType(analysis) {
438
- if (analysis.usesHeaders) {
439
- return "fast";
440
- }
441
- if (!analysis.usesQuery && !analysis.usesBody && !analysis.usesParams) {
442
- return "minimal";
443
- }
444
- if (!analysis.usesQuery && !analysis.usesBody && analysis.usesParams) {
445
- return "minimal";
446
- }
447
- if (analysis.usesBody) {
448
- return "full";
449
- }
450
- return "fast";
451
- }
452
-
453
- // src/engine/FastContext.ts
454
- var FastRequestImpl = class {
455
- _request;
456
- _params;
457
- _url = new URL("http://localhost");
458
- // Reuse this object
459
- _query = null;
460
- _headers = null;
461
- /**
462
- * Reset for pooling
463
- */
464
- reset(request, params = {}) {
465
- this._request = request;
466
- this._params = params;
467
- this._url.href = request.url;
468
- this._query = null;
469
- this._headers = null;
470
- }
471
- get url() {
472
- return this._request.url;
473
- }
474
- get method() {
475
- return this._request.method;
476
- }
477
- get path() {
478
- return this._url.pathname;
479
- }
480
- param(name) {
481
- return this._params[name];
482
- }
483
- params() {
484
- return { ...this._params };
485
- }
486
- query(name) {
487
- if (!this._query) {
488
- this._query = this._url.searchParams;
489
- }
490
- return this._query.get(name) ?? void 0;
491
- }
492
- queries() {
493
- if (!this._query) {
494
- this._query = this._url.searchParams;
495
- }
496
- const result = {};
497
- for (const [key, value] of this._query.entries()) {
498
- const existing = result[key];
499
- if (existing === void 0) {
500
- result[key] = value;
501
- } else if (Array.isArray(existing)) {
502
- existing.push(value);
503
- } else {
504
- result[key] = [existing, value];
505
- }
506
- }
507
- return result;
508
- }
509
- header(name) {
510
- return this._request.headers.get(name) ?? void 0;
511
- }
512
- headers() {
513
- if (!this._headers) {
514
- this._headers = {};
515
- for (const [key, value] of this._request.headers.entries()) {
516
- this._headers[key] = value;
517
- }
518
- }
519
- return { ...this._headers };
520
- }
521
- async json() {
522
- return this._request.json();
523
- }
524
- async text() {
525
- return this._request.text();
526
- }
527
- async formData() {
528
- return this._request.formData();
529
- }
530
- get raw() {
531
- return this._request;
532
- }
533
- };
534
- var FastContext = class {
535
- _req = new FastRequestImpl();
536
- // private _statusCode = 200
537
- _headers = new Headers();
538
- // Reuse this object
539
- /**
540
- * Reset context for pooling
541
- *
542
- * This is called when acquiring from the pool.
543
- * Must clear all state from previous request.
544
- */
545
- reset(request, params = {}) {
546
- this._req.reset(request, params);
547
- this._headers = new Headers();
548
- return this;
549
- }
550
- get req() {
551
- return this._req;
552
- }
553
- // ─────────────────────────────────────────────────────────────────────────
554
- // Response Helpers
555
- // ─────────────────────────────────────────────────────────────────────────
556
- json(data, status = 200) {
557
- this._headers.set("Content-Type", "application/json; charset=utf-8");
558
- return new Response(JSON.stringify(data), {
559
- status,
560
- headers: this._headers
561
- });
562
- }
563
- text(text, status = 200) {
564
- this._headers.set("Content-Type", "text/plain; charset=utf-8");
565
- return new Response(text, {
566
- status,
567
- headers: this._headers
568
- });
569
- }
570
- html(html, status = 200) {
571
- this._headers.set("Content-Type", "text/html; charset=utf-8");
572
- return new Response(html, {
573
- status,
574
- headers: this._headers
575
- });
576
- }
577
- redirect(url, status = 302) {
578
- this._headers.set("Location", url);
579
- return new Response(null, {
580
- status,
581
- headers: this._headers
582
- });
583
- }
584
- body(data, status = 200) {
585
- return new Response(data, {
586
- status,
587
- headers: this._headers
588
- });
589
- }
590
- // ─────────────────────────────────────────────────────────────────────────
591
- // Header Management
592
- // ─────────────────────────────────────────────────────────────────────────
593
- header(name, value) {
594
- this._headers.set(name, value);
595
- }
596
- status(_code) {
597
- }
598
- };
599
-
600
- // src/engine/MinimalContext.ts
601
- var MinimalRequest = class {
602
- constructor(_request, _params, _path) {
603
- this._request = _request;
604
- this._params = _params;
605
- this._path = _path;
606
- }
607
- get url() {
608
- return this._request.url;
609
- }
610
- get method() {
611
- return this._request.method;
612
- }
613
- get path() {
614
- return this._path;
615
- }
616
- param(name) {
617
- return this._params[name];
618
- }
619
- params() {
620
- return { ...this._params };
621
- }
622
- query(name) {
623
- const url = new URL(this._request.url);
624
- return url.searchParams.get(name) ?? void 0;
625
- }
626
- queries() {
627
- const url = new URL(this._request.url);
628
- const result = {};
629
- for (const [key, value] of url.searchParams.entries()) {
630
- const existing = result[key];
631
- if (existing === void 0) {
632
- result[key] = value;
633
- } else if (Array.isArray(existing)) {
634
- existing.push(value);
635
- } else {
636
- result[key] = [existing, value];
637
- }
638
- }
639
- return result;
640
- }
641
- header(name) {
642
- return this._request.headers.get(name) ?? void 0;
643
- }
644
- headers() {
645
- const result = {};
646
- for (const [key, value] of this._request.headers.entries()) {
647
- result[key] = value;
648
- }
649
- return result;
650
- }
651
- async json() {
652
- return this._request.json();
653
- }
654
- async text() {
655
- return this._request.text();
656
- }
657
- async formData() {
658
- return this._request.formData();
659
- }
660
- get raw() {
661
- return this._request;
662
- }
663
- };
664
- var MinimalContext = class {
665
- _req;
666
- constructor(request, params, path) {
667
- this._req = new MinimalRequest(request, params, path);
668
- }
669
- get req() {
670
- return this._req;
671
- }
672
- // Response helpers - create headers inline (no reuse overhead)
673
- json(data, status = 200) {
674
- return new Response(JSON.stringify(data), {
675
- status,
676
- headers: { "Content-Type": "application/json; charset=utf-8" }
677
- });
678
- }
679
- text(text, status = 200) {
680
- return new Response(text, {
681
- status,
682
- headers: { "Content-Type": "text/plain; charset=utf-8" }
683
- });
684
- }
685
- html(html, status = 200) {
686
- return new Response(html, {
687
- status,
688
- headers: { "Content-Type": "text/html; charset=utf-8" }
689
- });
690
- }
691
- redirect(url, status = 302) {
692
- return new Response(null, {
693
- status,
694
- headers: { Location: url }
695
- });
696
- }
697
- body(data, status = 200) {
698
- return new Response(data, { status });
699
- }
700
- header(_name, _value) {
701
- console.warn("MinimalContext.header() is a no-op. Use FastContext for custom headers.");
702
- }
703
- status(_code) {
704
- }
705
- // Required for interface compatibility
706
- reset(_request, _params) {
707
- throw new Error("MinimalContext does not support reset. Create a new instance instead.");
708
- }
709
- };
710
-
711
- // src/engine/path.ts
712
- function extractPath(url) {
713
- const protocolEnd = url.indexOf("://");
714
- const searchStart = protocolEnd === -1 ? 0 : protocolEnd + 3;
715
- const pathStart = url.indexOf("/", searchStart);
716
- if (pathStart === -1) {
717
- return "/";
718
- }
719
- const queryStart = url.indexOf("?", pathStart);
720
- if (queryStart === -1) {
721
- return url.slice(pathStart);
722
- }
723
- return url.slice(pathStart, queryStart);
724
- }
725
-
726
- // src/engine/pool.ts
727
- var ObjectPool = class {
728
- pool = [];
729
- factory;
730
- reset;
731
- maxSize;
732
- /**
733
- * Create a new object pool
734
- *
735
- * @param factory - Function to create new objects
736
- * @param reset - Function to reset objects before reuse
737
- * @param maxSize - Maximum pool size (default: 256)
738
- */
739
- constructor(factory, reset, maxSize = 256) {
740
- this.factory = factory;
741
- this.reset = reset;
742
- this.maxSize = maxSize;
743
- }
744
- /**
745
- * Acquire an object from the pool
746
- *
747
- * If the pool is empty, creates a new object (overflow strategy).
748
- * This ensures the pool never blocks under high load.
749
- *
750
- * @returns Object from pool or newly created
751
- */
752
- acquire() {
753
- const obj = this.pool.pop();
754
- if (obj !== void 0) {
755
- return obj;
756
- }
757
- return this.factory();
758
- }
759
- /**
760
- * Release an object back to the pool
761
- *
762
- * If the pool is full, the object is discarded (will be GC'd).
763
- * This prevents unbounded memory growth.
764
- *
765
- * @param obj - Object to release
766
- */
767
- release(obj) {
768
- if (this.pool.length < this.maxSize) {
769
- this.reset(obj);
770
- this.pool.push(obj);
771
- }
772
- }
773
- /**
774
- * Clear all objects from the pool
775
- *
776
- * Useful for testing or when you need to force a clean slate.
777
- */
778
- clear() {
779
- this.pool = [];
780
- }
781
- /**
782
- * Get current pool size
783
- */
784
- get size() {
785
- return this.pool.length;
786
- }
787
- /**
788
- * Get maximum pool size
789
- */
790
- get capacity() {
791
- return this.maxSize;
792
- }
793
- /**
794
- * Pre-warm the pool by creating objects in advance
795
- *
796
- * This can reduce latency for the first N requests.
797
- *
798
- * @param count - Number of objects to pre-create
799
- */
800
- prewarm(count) {
801
- const targetSize = Math.min(count, this.maxSize);
802
- while (this.pool.length < targetSize) {
803
- const obj = this.factory();
804
- this.reset(obj);
805
- this.pool.push(obj);
806
- }
807
- }
808
- };
809
-
810
- // src/engine/Gravito.ts
811
- var Gravito = class {
812
- router = new AOTRouter();
813
- contextPool;
814
- errorHandler;
815
- notFoundHandler;
816
- // Direct reference to static routes Map (O(1) access)
817
- // Optimization: Bypass getter/setter overhead
818
- staticRoutes;
819
- // Flag: pure static app (no middleware at all) allows ultra-fast path
820
- isPureStaticApp = true;
821
- /**
822
- * Create a new Gravito instance
823
- *
824
- * @param options - Engine configuration options
825
- */
826
- constructor(options = {}) {
827
- const poolSize = options.poolSize ?? 256;
828
- this.contextPool = new ObjectPool(
829
- () => new FastContext(),
830
- (ctx) => ctx.reset(new Request("http://localhost")),
831
- poolSize
832
- );
833
- this.contextPool.prewarm(Math.min(32, poolSize));
834
- if (options.onError) {
835
- this.errorHandler = options.onError;
836
- }
837
- if (options.onNotFound) {
838
- this.notFoundHandler = options.onNotFound;
839
- }
840
- this.compileRoutes();
841
- }
842
- // ─────────────────────────────────────────────────────────────────────────
843
- // HTTP Method Registration
844
- // ─────────────────────────────────────────────────────────────────────────
845
- /**
846
- * Register a GET route
847
- *
848
- * @param path - Route path (e.g., '/users/:id')
849
- * @param handlers - Handler and optional middleware
850
- * @returns This instance for chaining
851
- */
852
- get(path, ...handlers) {
853
- return this.addRoute("get", path, handlers);
854
- }
855
- /**
856
- * Register a POST route
857
- */
858
- post(path, ...handlers) {
859
- return this.addRoute("post", path, handlers);
860
- }
861
- /**
862
- * Register a PUT route
863
- */
864
- put(path, ...handlers) {
865
- return this.addRoute("put", path, handlers);
866
- }
867
- /**
868
- * Register a DELETE route
869
- */
870
- delete(path, ...handlers) {
871
- return this.addRoute("delete", path, handlers);
872
- }
873
- /**
874
- * Register a PATCH route
875
- */
876
- patch(path, ...handlers) {
877
- return this.addRoute("patch", path, handlers);
878
- }
879
- /**
880
- * Register an OPTIONS route
881
- */
882
- options(path, ...handlers) {
883
- return this.addRoute("options", path, handlers);
884
- }
885
- /**
886
- * Register a HEAD route
887
- */
888
- head(path, ...handlers) {
889
- return this.addRoute("head", path, handlers);
890
- }
891
- /**
892
- * Register a route for all HTTP methods
893
- */
894
- all(path, ...handlers) {
895
- const methods = ["get", "post", "put", "delete", "patch", "options", "head"];
896
- for (const method of methods) {
897
- this.addRoute(method, path, handlers);
898
- }
899
- return this;
900
- }
901
- use(pathOrMiddleware, ...middleware) {
902
- this.isPureStaticApp = false;
903
- if (typeof pathOrMiddleware === "string") {
904
- this.router.usePattern(pathOrMiddleware, ...middleware);
905
- } else {
906
- this.router.use(pathOrMiddleware, ...middleware);
907
- }
908
- return this;
909
- }
910
- // ─────────────────────────────────────────────────────────────────────────
911
- // Route Grouping
912
- // ─────────────────────────────────────────────────────────────────────────
913
- /**
914
- * Mount a sub-application at a path prefix
915
- *
916
- * @example
917
- * ```typescript
918
- * const api = new Gravito()
919
- * api.get('/users', (c) => c.json({ users: [] }))
920
- *
921
- * const app = new Gravito()
922
- * app.route('/api', api)
923
- * // Now accessible at /api/users
924
- * ```
925
- */
926
- route(_path, _app) {
927
- console.warn("route() method is not yet fully implemented");
928
- return this;
929
- }
930
- // ─────────────────────────────────────────────────────────────────────────
931
- // Error Handling
932
- // ─────────────────────────────────────────────────────────────────────────
933
- /**
934
- * Set custom error handler
935
- *
936
- * @example
937
- * ```typescript
938
- * app.onError((err, c) => {
939
- * console.error(err)
940
- * return c.json({ error: err.message }, 500)
941
- * })
942
- * ```
943
- */
944
- onError(handler) {
945
- this.errorHandler = handler;
946
- return this;
947
- }
948
- /**
949
- * Set custom 404 handler
950
- *
951
- * @example
952
- * ```typescript
953
- * app.notFound((c) => {
954
- * return c.json({ error: 'Not Found' }, 404)
955
- * })
956
- * ```
957
- */
958
- notFound(handler) {
959
- this.notFoundHandler = handler;
960
- return this;
961
- }
962
- // ─────────────────────────────────────────────────────────────────────────
963
- // Request Handling (Bun.serve integration)
964
- // ─────────────────────────────────────────────────────────────────────────
965
- /**
966
- * Handle an incoming request
967
- *
968
- * Optimized for minimal allocations and maximum throughput.
969
- * Uses sync/async dual-path strategy inspired by Hono.
970
- *
971
- * @param request - Incoming Request object
972
- * @returns Response object (sync or async)
973
- */
974
- fetch = (request) => {
975
- const path = extractPath(request.url);
976
- const method = request.method.toLowerCase();
977
- const staticKey = `${method}:${path}`;
978
- const staticRoute = this.staticRoutes.get(staticKey);
979
- if (staticRoute) {
980
- if (staticRoute.useMinimal) {
981
- const ctx = new MinimalContext(request, {}, path);
982
- try {
983
- const result = staticRoute.handler(ctx);
984
- if (result instanceof Response) {
985
- return result;
986
- }
987
- return result;
988
- } catch (error) {
989
- return this.handleErrorSync(error, request, path);
990
- }
991
- }
992
- return this.handleWithMiddleware(request, path, staticRoute);
993
- }
994
- return this.handleDynamicRoute(request, method, path);
995
- };
996
- /**
997
- * Handle routes with middleware (async path)
998
- */
999
- async handleWithMiddleware(request, path, route) {
1000
- const ctx = this.contextPool.acquire();
1001
- try {
1002
- ctx.reset(request, {});
1003
- const middleware = this.collectMiddlewareForPath(path, route.middleware);
1004
- if (middleware.length === 0) {
1005
- return await route.handler(ctx);
1006
- }
1007
- return await this.executeMiddleware(ctx, middleware, route.handler);
1008
- } catch (error) {
1009
- return await this.handleError(error, ctx);
1010
- } finally {
1011
- this.contextPool.release(ctx);
1012
- }
1013
- }
1014
- /**
1015
- * Handle dynamic routes (Radix Tree lookup)
1016
- */
1017
- handleDynamicRoute(request, method, path) {
1018
- const match = this.router.match(method.toUpperCase(), path);
1019
- if (!match.handler) {
1020
- return this.handleNotFoundSync(request, path);
1021
- }
1022
- const ctx = this.contextPool.acquire();
1023
- const execute = async () => {
1024
- try {
1025
- ctx.reset(request, match.params);
1026
- if (match.middleware.length === 0) {
1027
- return await match.handler(ctx);
1028
- }
1029
- return await this.executeMiddleware(ctx, match.middleware, match.handler);
1030
- } catch (error) {
1031
- return await this.handleError(error, ctx);
1032
- } finally {
1033
- this.contextPool.release(ctx);
1034
- }
1035
- };
1036
- return execute();
1037
- }
1038
- /**
1039
- * Sync error handler (for ultra-fast path)
1040
- */
1041
- handleErrorSync(error, request, path) {
1042
- if (this.errorHandler) {
1043
- const ctx = new MinimalContext(request, {}, path);
1044
- const result = this.errorHandler(error, ctx);
1045
- if (result instanceof Response) {
1046
- return result;
1047
- }
1048
- return result;
1049
- }
1050
- console.error("Unhandled error:", error);
1051
- return new Response(
1052
- JSON.stringify({
1053
- error: "Internal Server Error",
1054
- message: error.message
1055
- }),
1056
- {
1057
- status: 500,
1058
- headers: { "Content-Type": "application/json" }
1059
- }
1060
- );
1061
- }
1062
- /**
1063
- * Sync 404 handler (for ultra-fast path)
1064
- */
1065
- handleNotFoundSync(request, path) {
1066
- if (this.notFoundHandler) {
1067
- const ctx = new MinimalContext(request, {}, path);
1068
- const result = this.notFoundHandler(ctx);
1069
- if (result instanceof Response) {
1070
- return result;
1071
- }
1072
- return result;
1073
- }
1074
- return new Response(JSON.stringify({ error: "Not Found" }), {
1075
- status: 404,
1076
- headers: { "Content-Type": "application/json" }
1077
- });
1078
- }
1079
- /**
1080
- * Collect middleware for a specific path
1081
- * (Simplified version - assumes we've already checked for pure static)
1082
- */
1083
- collectMiddlewareForPath(path, routeMiddleware) {
1084
- if (this.router.globalMiddleware.length === 0 && this.router.pathMiddleware.size === 0) {
1085
- return routeMiddleware;
1086
- }
1087
- return this.router.collectMiddlewarePublic(path, routeMiddleware);
1088
- }
1089
- /**
1090
- * Compile routes for optimization
1091
- * Called once during initialization and when routes change
1092
- */
1093
- compileRoutes() {
1094
- this.staticRoutes = this.router.staticRoutes;
1095
- const hasGlobalMiddleware = this.router.globalMiddleware.length > 0;
1096
- const hasPathMiddleware = this.router.pathMiddleware.size > 0;
1097
- this.isPureStaticApp = !hasGlobalMiddleware && !hasPathMiddleware;
1098
- for (const [_key, route] of this.staticRoutes) {
1099
- const analysis = analyzeHandler(route.handler);
1100
- const optimalType = getOptimalContextType(analysis);
1101
- route.useMinimal = this.isPureStaticApp && route.middleware.length === 0 && optimalType === "minimal";
1102
- }
1103
- }
1104
- // ─────────────────────────────────────────────────────────────────────────
1105
- // Internal Methods
1106
- // ─────────────────────────────────────────────────────────────────────────
1107
- /**
1108
- * Add a route to the router
1109
- */
1110
- addRoute(method, path, handlers) {
1111
- if (handlers.length === 0) {
1112
- throw new Error(`No handler provided for ${method.toUpperCase()} ${path}`);
1113
- }
1114
- const handler = handlers[handlers.length - 1];
1115
- const middleware = handlers.slice(0, -1);
1116
- this.router.add(method, path, handler, middleware);
1117
- this.compileRoutes();
1118
- return this;
1119
- }
1120
- /**
1121
- * Execute middleware chain followed by handler
1122
- *
1123
- * Implements the standard middleware pattern:
1124
- * Each middleware can call next() to continue the chain.
1125
- */
1126
- async executeMiddleware(ctx, middleware, handler) {
1127
- let index = 0;
1128
- const next = async () => {
1129
- if (index < middleware.length) {
1130
- const mw = middleware[index++];
1131
- return await mw(ctx, next);
1132
- }
1133
- return void 0;
1134
- };
1135
- const result = await next();
1136
- if (result instanceof Response) {
1137
- return result;
1138
- }
1139
- return await handler(ctx);
1140
- }
1141
- /*
1142
- * Handle 404 Not Found (Async version for dynamic/middleware paths)
1143
- * Note: Currently unused as we handle 404s via handleNotFoundSync or inline
1144
- */
1145
- // private async handleNotFound(ctx: FastContext): Promise<Response> {
1146
- // if (this.notFoundHandler) {
1147
- // return await this.notFoundHandler(ctx)
1148
- // }
1149
- // return ctx.json({ error: 'Not Found' }, 404)
1150
- // }
1151
- /**
1152
- * Handle errors (Async version for dynamic/middleware paths)
1153
- */
1154
- async handleError(error, ctx) {
1155
- if (this.errorHandler) {
1156
- return await this.errorHandler(error, ctx);
1157
- }
1158
- console.error("Unhandled error:", error);
1159
- return ctx.json(
1160
- {
1161
- error: "Internal Server Error",
1162
- message: error.message
1163
- },
1164
- 500
1165
- );
1166
- }
1167
- };
1168
- // Annotate the CommonJS export names for ESM import in node:
1169
- 0 && (module.exports = {
1170
- AOTRouter,
1171
- FastContextImpl,
1172
- Gravito,
1173
- MinimalContext,
1174
- ObjectPool,
1175
- extractPath
1176
- });