@stacksjs/bun-router 0.0.6 → 0.0.8

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/cli.js CHANGED
@@ -1879,7 +1879,7 @@ var prefix = getPrefix();
1879
1879
  var color18 = __toESM(require_picocolors(), 1);
1880
1880
  var import_picocolors19 = __toESM(require_picocolors(), 1);
1881
1881
  // package.json
1882
- var version = "0.0.6";
1882
+ var version = "0.0.8";
1883
1883
 
1884
1884
  // src/cli/middleware.ts
1885
1885
  import fs from "fs/promises";
package/dist/index.js CHANGED
@@ -8847,7 +8847,12 @@ class Router {
8847
8847
  const allowedMethods = this.getAllowedMethods(url2.pathname, hostname);
8848
8848
  if (allowedMethods.length > 0) {
8849
8849
  const methodNotAllowedHandler = async (_req, _next) => {
8850
- return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
8850
+ return new Response(JSON.stringify({
8851
+ error: "Method Not Allowed",
8852
+ path: url2.pathname,
8853
+ method: req.method,
8854
+ allowed: allowedMethods
8855
+ }), {
8851
8856
  status: 405,
8852
8857
  headers: {
8853
8858
  "Content-Type": "application/json",
@@ -8869,7 +8874,11 @@ class Router {
8869
8874
  if (this.fallbackHandler) {
8870
8875
  return await this.resolveHandler(this.fallbackHandler, enhancedReq);
8871
8876
  }
8872
- return new Response(JSON.stringify({ error: "Not Found" }), {
8877
+ return new Response(JSON.stringify({
8878
+ error: "Not Found",
8879
+ path: url2.pathname,
8880
+ method: req.method
8881
+ }), {
8873
8882
  status: 404,
8874
8883
  headers: { "Content-Type": "application/json" }
8875
8884
  });
@@ -8884,7 +8893,11 @@ class Router {
8884
8893
  const response2 = await this.resolveHandler(this.fallbackHandler, enhancedReq);
8885
8894
  return this.applyModifiedCookies(response2, enhancedReq);
8886
8895
  }
8887
- return new Response(JSON.stringify({ error: "Not Found" }), {
8896
+ return new Response(JSON.stringify({
8897
+ error: "Not Found",
8898
+ path: url2.pathname,
8899
+ method: req.method
8900
+ }), {
8888
8901
  status: 404,
8889
8902
  headers: { "Content-Type": "application/json" }
8890
8903
  });
@@ -9263,6 +9276,10 @@ class Router {
9263
9276
  enhancedReq.header = (name) => {
9264
9277
  return req.headers.get(name) || req.headers.get(name.toLowerCase()) || null;
9265
9278
  };
9279
+ enhancedReq.cookie = (name, defaultValue) => {
9280
+ const value = getCookies()[name];
9281
+ return value !== undefined ? value : defaultValue ?? null;
9282
+ };
9266
9283
  enhancedReq.getParam = (name, defaultValue) => {
9267
9284
  const value = params?.[name];
9268
9285
  return value !== undefined ? value : defaultValue;
@@ -9517,6 +9534,17 @@ class RouteGroupBuilder2 {
9517
9534
  return this;
9518
9535
  }
9519
9536
  }
9537
+ var _enhancementHost = null;
9538
+ function getEnhancementHost() {
9539
+ if (_enhancementHost === null)
9540
+ _enhancementHost = new Router;
9541
+ return _enhancementHost;
9542
+ }
9543
+ function applyRequestEnhancements(req, params = {}) {
9544
+ if (typeof req.bearerToken === "function")
9545
+ return req;
9546
+ return getEnhancementHost().enhanceRequest(req, params);
9547
+ }
9520
9548
 
9521
9549
  // src/router/server.ts
9522
9550
  function registerServerHandling(RouterClass) {
@@ -9637,31 +9665,52 @@ function registerServerHandling(RouterClass) {
9637
9665
  return new Response("No response from middleware chain", { status: 500 });
9638
9666
  }
9639
9667
  const allowedMethods = this.getAllowedMethods(url2.pathname, hostname);
9668
+ const corsHeaders = {
9669
+ "Content-Type": "application/json",
9670
+ "Access-Control-Allow-Origin": "*",
9671
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
9672
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, Accept, Origin"
9673
+ };
9640
9674
  if (allowedMethods.length > 0) {
9641
- return new Response(JSON.stringify({ success: false, message: "Method Not Allowed" }), {
9642
- status: 405,
9643
- headers: {
9644
- "Content-Type": "application/json",
9645
- Allow: allowedMethods.join(", "),
9646
- "Access-Control-Allow-Origin": "*",
9647
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
9648
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, Accept, Origin"
9649
- }
9650
- });
9675
+ const methodNotAllowedHandler = async (_req, _next) => {
9676
+ return new Response(JSON.stringify({
9677
+ success: false,
9678
+ message: "Method Not Allowed",
9679
+ path: url2.pathname,
9680
+ method: req.method,
9681
+ allowed: allowedMethods
9682
+ }), {
9683
+ status: 405,
9684
+ headers: { ...corsHeaders, Allow: allowedMethods.join(", ") }
9685
+ });
9686
+ };
9687
+ if (this.globalMiddleware.length > 0) {
9688
+ const stack = [...this.globalMiddleware, methodNotAllowedHandler];
9689
+ const response2 = await this.runMiddleware(enhancedReq, stack);
9690
+ if (response2)
9691
+ return this.applyModifiedCookies(response2, enhancedReq);
9692
+ }
9693
+ return await methodNotAllowedHandler(enhancedReq, async () => null);
9651
9694
  }
9652
9695
  if (this.fallbackHandler) {
9653
9696
  const response2 = await this.resolveHandler(this.fallbackHandler, enhancedReq);
9654
9697
  return this.applyModifiedCookies(response2, enhancedReq);
9655
9698
  }
9656
- return new Response(JSON.stringify({ success: false, message: "Not Found" }), {
9657
- status: 404,
9658
- headers: {
9659
- "Content-Type": "application/json",
9660
- "Access-Control-Allow-Origin": "*",
9661
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
9662
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, Accept, Origin"
9663
- }
9664
- });
9699
+ const notFoundHandler = async (_req, _next) => {
9700
+ return new Response(JSON.stringify({
9701
+ success: false,
9702
+ message: "Not Found",
9703
+ path: url2.pathname,
9704
+ method: req.method
9705
+ }), { status: 404, headers: corsHeaders });
9706
+ };
9707
+ if (this.globalMiddleware.length > 0) {
9708
+ const stack = [...this.globalMiddleware, notFoundHandler];
9709
+ const response2 = await this.runMiddleware(enhancedReq, stack);
9710
+ if (response2)
9711
+ return this.applyModifiedCookies(response2, enhancedReq);
9712
+ }
9713
+ return await notFoundHandler(enhancedReq, async () => null);
9665
9714
  } catch (error) {
9666
9715
  console.error("Error handling request:", error);
9667
9716
  if (this.errorHandler) {
@@ -13781,6 +13830,7 @@ export {
13781
13830
  authMocks,
13782
13831
  authHelpers,
13783
13832
  auth,
13833
+ applyRequestEnhancements,
13784
13834
  apiKeyAuth,
13785
13835
  WebSocketTester,
13786
13836
  ValidationMiddlewareBuilder,
@@ -4,6 +4,7 @@ import { FluentRouteBuilder, FluentRouter, RouteFactory, router, RouterUtils } f
4
4
  import { Router } from './router';
5
5
  import '../types';
6
6
  export { Router };
7
+ export { applyRequestEnhancements } from './router';
7
8
  declare module './router' {
8
9
  interface Router {
9
10
  stream: (callback: () => Generator<string | Uint8Array, void, unknown> | AsyncGenerator<string | Uint8Array, void, unknown>, status?: number, headers?: Record<string, string>) => Response;
@@ -274,3 +274,21 @@ export declare class RouteGroupBuilder {
274
274
  put(_path: string, _handler: RouteHandler): this;
275
275
  delete(_path: string, _handler: RouteHandler): this;
276
276
  }
277
+ /**
278
+ * Attach bun-router's request macros (`bearerToken`, `getParam`, `cookie`,
279
+ * `cookies`, `header`, `params`, plus the Laravel-style input helpers
280
+ * `get`, `input`, `string`, `integer`, `float`, `boolean`, `array`, `has`,
281
+ * `filled`, etc.) to a request.
282
+ *
283
+ * Idempotent: calling on an already-enhanced request returns it unchanged.
284
+ * We sniff `req.bearerToken` as the marker — cheap and reliable since no
285
+ * native Request has it.
286
+ *
287
+ * @example
288
+ * import { applyRequestEnhancements } from '@stacksjs/bun-router'
289
+ *
290
+ * const enhanced = applyRequestEnhancements(req, { id: '42' })
291
+ * enhanced.bearerToken() // → string | null
292
+ * enhanced.getParam('id') // → '42'
293
+ */
294
+ export declare function applyRequestEnhancements(req: Request | EnhancedRequest, params?: Record<string, string>): EnhancedRequest;
package/dist/types.d.ts CHANGED
@@ -688,6 +688,12 @@ export interface EnhancedRequest extends Request, Omit<RequestMacroMethods, 'ip'
688
688
  * Route parameters extracted from the URL
689
689
  */
690
690
  params: Record<string, string>;
691
+ /**
692
+ * Lookup a single route param. Equivalent to `request.params[name]` but
693
+ * with optional default-value handling, matching the Laravel-style
694
+ * `$request->route('name')` ergonomics.
695
+ */
696
+ getParam: <T = string>(name: string, defaultValue?: T) => T | undefined;
691
697
  /**
692
698
  * Query parameters from the URL
693
699
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/bun-router",
3
3
  "type": "module",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "description": "A fast, type-safe router for Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -33,8 +33,10 @@ registerModelBinding(Router)
33
33
  registerFileBasedRouting(Router)
34
34
  registerApiRoutes(Router)
35
35
 
36
- // Export the Router class and fluent routing features
36
+ // Export the Router class, the standalone request-enhancement helper, and
37
+ // fluent routing features.
37
38
  export { Router }
39
+ export { applyRequestEnhancements } from './router'
38
40
 
39
41
  // Type augmentation for Laravel-style methods
40
42
  declare module './router' {
@@ -611,13 +611,21 @@ export class Router {
611
611
  return new Response('No response from middleware chain', { status: 500 })
612
612
  }
613
613
 
614
- // No route found - check if the path exists with a different method (405 vs 404)
614
+ // No route found - check if the path exists with a different method (405 vs 404).
615
+ // 404/405 bodies now include path + method so client-side debugging (typo'd
616
+ // endpoint, stale SPA cache, missing route registration) is one grep away.
617
+ // Both responses still flow through globalMiddleware so user middleware
618
+ // (X-Request-ID, audit, custom CORS) sees them.
615
619
  const allowedMethods = this.getAllowedMethods(url.pathname, hostname)
616
620
 
617
- // If there are allowed methods, return 405 Method Not Allowed
618
621
  if (allowedMethods.length > 0) {
619
622
  const methodNotAllowedHandler = async (_req: EnhancedRequest, _next: NextFunction) => {
620
- return new Response(JSON.stringify({ error: 'Method Not Allowed' }), {
623
+ return new Response(JSON.stringify({
624
+ error: 'Method Not Allowed',
625
+ path: url.pathname,
626
+ method: req.method,
627
+ allowed: allowedMethods,
628
+ }), {
621
629
  status: 405,
622
630
  headers: {
623
631
  'Content-Type': 'application/json',
@@ -643,7 +651,11 @@ export class Router {
643
651
  if (this.fallbackHandler) {
644
652
  return await this.resolveHandler(this.fallbackHandler, enhancedReq)
645
653
  }
646
- return new Response(JSON.stringify({ error: 'Not Found' }), {
654
+ return new Response(JSON.stringify({
655
+ error: 'Not Found',
656
+ path: url.pathname,
657
+ method: req.method,
658
+ }), {
647
659
  status: 404,
648
660
  headers: { 'Content-Type': 'application/json' },
649
661
  })
@@ -661,8 +673,12 @@ export class Router {
661
673
  return this.applyModifiedCookies(response, enhancedReq)
662
674
  }
663
675
 
664
- // No fallback handler, return a 404
665
- return new Response(JSON.stringify({ error: 'Not Found' }), {
676
+ // No fallback handler, return a 404 with path context
677
+ return new Response(JSON.stringify({
678
+ error: 'Not Found',
679
+ path: url.pathname,
680
+ method: req.method,
681
+ }), {
666
682
  status: 404,
667
683
  headers: { 'Content-Type': 'application/json' },
668
684
  })
@@ -1131,6 +1147,15 @@ export class Router {
1131
1147
  return req.headers.get(name) || req.headers.get(name.toLowerCase()) || null
1132
1148
  }
1133
1149
 
1150
+ // Convenience cookie reader. The full `enhancedReq.cookies.get(name)` API
1151
+ // is also available below — `cookie(name)` is the shorter form callers
1152
+ // (and the Laravel-style macros) reach for first, so it deserves a
1153
+ // direct method on the enhanced request, not just on the macros class.
1154
+ ;(enhancedReq as any).cookie = (name: string, defaultValue?: string): string | null => {
1155
+ const value = getCookies()[name]
1156
+ return value !== undefined ? value : (defaultValue ?? null)
1157
+ }
1158
+
1134
1159
  ;(enhancedReq as any).getParam = <T = string>(name: string, defaultValue?: T): T | undefined => {
1135
1160
  const value = params?.[name] as T | undefined
1136
1161
  return value !== undefined ? value : defaultValue
@@ -1506,3 +1531,51 @@ export class RouteGroupBuilder {
1506
1531
  return this
1507
1532
  }
1508
1533
  }
1534
+
1535
+ // ---------------------------------------------------------------------------
1536
+ // Standalone enhancement helper
1537
+ // ---------------------------------------------------------------------------
1538
+
1539
+ // Singleton scratch Router used to expose `enhanceRequest` as a standalone
1540
+ // function. Downstream consumers (frameworks layered on bun-router) often
1541
+ // need to attach the request macros to a request that was created outside
1542
+ // of `route.serve()` — for instance, when a higher-level router wraps
1543
+ // each handler with its own middleware chain. Without an exported function,
1544
+ // those consumers either spin up their own `new Router()` per call or
1545
+ // duplicate the attachment logic in user code.
1546
+ //
1547
+ // We share one instance because `enhanceRequest` does not depend on any
1548
+ // per-router state (route table, middleware groups, etc.) — it only reads
1549
+ // from the request and the supplied params.
1550
+ let _enhancementHost: Router | null = null
1551
+
1552
+ function getEnhancementHost(): Router {
1553
+ if (_enhancementHost === null) _enhancementHost = new Router()
1554
+ return _enhancementHost
1555
+ }
1556
+
1557
+ /**
1558
+ * Attach bun-router's request macros (`bearerToken`, `getParam`, `cookie`,
1559
+ * `cookies`, `header`, `params`, plus the Laravel-style input helpers
1560
+ * `get`, `input`, `string`, `integer`, `float`, `boolean`, `array`, `has`,
1561
+ * `filled`, etc.) to a request.
1562
+ *
1563
+ * Idempotent: calling on an already-enhanced request returns it unchanged.
1564
+ * We sniff `req.bearerToken` as the marker — cheap and reliable since no
1565
+ * native Request has it.
1566
+ *
1567
+ * @example
1568
+ * import { applyRequestEnhancements } from '@stacksjs/bun-router'
1569
+ *
1570
+ * const enhanced = applyRequestEnhancements(req, { id: '42' })
1571
+ * enhanced.bearerToken() // → string | null
1572
+ * enhanced.getParam('id') // → '42'
1573
+ */
1574
+ export function applyRequestEnhancements(
1575
+ req: Request | EnhancedRequest,
1576
+ params: Record<string, string> = {},
1577
+ ): EnhancedRequest {
1578
+ if (typeof (req as any).bearerToken === 'function')
1579
+ return req as EnhancedRequest
1580
+ return getEnhancementHost().enhanceRequest(req as Request, params)
1581
+ }
@@ -195,21 +195,38 @@ export function registerServerHandling(RouterClass: typeof Router): void {
195
195
  return new Response('No response from middleware chain', { status: 500 })
196
196
  }
197
197
 
198
- // No route found - check if the path exists with a different method (405 vs 404)
198
+ // No route found - check if the path exists with a different method (405 vs 404).
199
+ // The 404/405 responses below now (a) include path + method in the body so client
200
+ // debugging is one grep away, and (b) flow through globalMiddleware so cross-cutting
201
+ // concerns (X-Request-ID, Server-Timing, audit logging, custom CORS) can observe
202
+ // them. Previously these paths short-circuited entirely.
199
203
  const allowedMethods = this.getAllowedMethods(url.pathname, hostname)
204
+ const corsHeaders = {
205
+ 'Content-Type': 'application/json',
206
+ 'Access-Control-Allow-Origin': '*',
207
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
208
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
209
+ }
200
210
 
201
- // If there are allowed methods, return 405 Method Not Allowed
202
211
  if (allowedMethods.length > 0) {
203
- return new Response(JSON.stringify({ success: false, message: 'Method Not Allowed' }), {
204
- status: 405,
205
- headers: {
206
- 'Content-Type': 'application/json',
207
- 'Allow': allowedMethods.join(', '),
208
- 'Access-Control-Allow-Origin': '*',
209
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
210
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
211
- },
212
- })
212
+ const methodNotAllowedHandler = async (_req: EnhancedRequest, _next: any) => {
213
+ return new Response(JSON.stringify({
214
+ success: false,
215
+ message: 'Method Not Allowed',
216
+ path: url.pathname,
217
+ method: req.method,
218
+ allowed: allowedMethods,
219
+ }), {
220
+ status: 405,
221
+ headers: { ...corsHeaders, Allow: allowedMethods.join(', ') },
222
+ })
223
+ }
224
+ if (this.globalMiddleware.length > 0) {
225
+ const stack = [...this.globalMiddleware, methodNotAllowedHandler]
226
+ const response = await this.runMiddleware(enhancedReq, stack)
227
+ if (response) return this.applyModifiedCookies(response, enhancedReq)
228
+ }
229
+ return await methodNotAllowedHandler(enhancedReq, async () => null as any)
213
230
  }
214
231
 
215
232
  // No route found, try the fallback handler
@@ -218,16 +235,22 @@ export function registerServerHandling(RouterClass: typeof Router): void {
218
235
  return this.applyModifiedCookies(response, enhancedReq)
219
236
  }
220
237
 
221
- // No fallback handler, return a 404 with CORS headers
222
- return new Response(JSON.stringify({ success: false, message: 'Not Found' }), {
223
- status: 404,
224
- headers: {
225
- 'Content-Type': 'application/json',
226
- 'Access-Control-Allow-Origin': '*',
227
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
228
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
229
- },
230
- })
238
+ // No fallback — emit a 404 enriched with path + method, also through
239
+ // globalMiddleware so user middleware sees it.
240
+ const notFoundHandler = async (_req: EnhancedRequest, _next: any) => {
241
+ return new Response(JSON.stringify({
242
+ success: false,
243
+ message: 'Not Found',
244
+ path: url.pathname,
245
+ method: req.method,
246
+ }), { status: 404, headers: corsHeaders })
247
+ }
248
+ if (this.globalMiddleware.length > 0) {
249
+ const stack = [...this.globalMiddleware, notFoundHandler]
250
+ const response = await this.runMiddleware(enhancedReq, stack)
251
+ if (response) return this.applyModifiedCookies(response, enhancedReq)
252
+ }
253
+ return await notFoundHandler(enhancedReq, async () => null as any)
231
254
  }
232
255
  catch (error) {
233
256
  console.error('Error handling request:', error)
package/src/types.ts CHANGED
@@ -793,6 +793,12 @@ export interface EnhancedRequest extends Request, Omit<RequestMacroMethods, 'ip'
793
793
  * Route parameters extracted from the URL
794
794
  */
795
795
  params: Record<string, string>
796
+ /**
797
+ * Lookup a single route param. Equivalent to `request.params[name]` but
798
+ * with optional default-value handling, matching the Laravel-style
799
+ * `$request->route('name')` ergonomics.
800
+ */
801
+ getParam: <T = string>(name: string, defaultValue?: T) => T | undefined
796
802
  /**
797
803
  * Query parameters from the URL
798
804
  */