@stacksjs/bun-router 0.0.7 → 0.0.9

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.
@@ -39,7 +39,7 @@ export interface ResponseCookieOptions {
39
39
  sameSite?: 'strict' | 'lax' | 'none';
40
40
  }
41
41
  export interface ResponseFactory {
42
- json: <T>(data: T, options?: JsonResponseOptions) => Response;
42
+ json: <T>(data: T, options?: JsonResponseOptions | ResponseStatus) => Response;
43
43
  noContent: (headers?: Record<string, string>) => Response;
44
44
  download: (filePath: string, filename?: string, headers?: Record<string, string>) => Promise<Response>;
45
45
  file: (filePath: string, headers?: Record<string, string>) => Promise<Response>;
@@ -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.7",
4
+ "version": "0.0.9",
5
5
  "description": "A fast, type-safe router for Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -423,6 +423,10 @@ export type MiddlewareWithConfig<T> = (config: T) => MiddlewareFunction
423
423
  const responseType = options.responseType || 'Response'
424
424
  const paramsType = options.paramsType || 'Record<string, string>'
425
425
 
426
+ // pickier mis-parses the embedded `req:` token in this template literal
427
+ // as a real unused function parameter — disable on the next line so a
428
+ // pre-existing rule false positive doesn't block CI.
429
+ // eslint-disable-next-line pickier/no-unused-vars
426
430
  return `(req: ${requestType} & { params: ${paramsType} }) => Promise<${responseType}>`
427
431
  }
428
432
 
@@ -53,7 +53,7 @@ export interface ResponseCookieOptions {
53
53
  // ============================================================================
54
54
 
55
55
  export interface ResponseFactory {
56
- json: <T>(data: T, options?: JsonResponseOptions) => Response
56
+ json: <T>(data: T, options?: JsonResponseOptions | ResponseStatus) => Response
57
57
  noContent: (headers?: Record<string, string>) => Response
58
58
  download: (filePath: string, filename?: string, headers?: Record<string, string>) => Promise<Response>
59
59
  file: (filePath: string, headers?: Record<string, string>) => Promise<Response>
@@ -89,10 +89,18 @@ export interface ResponseFactory {
89
89
  */
90
90
  export const response: ResponseFactory = {
91
91
  /**
92
- * Create a JSON response with proper typing
93
- */
94
- json: <T>(data: T, options: JsonResponseOptions = {}): Response => {
95
- const { status = 200, headers = {}, pretty = false } = options
92
+ * Create a JSON response with proper typing.
93
+ *
94
+ * The second argument may be either a status code (Hono/Express style) or
95
+ * a `JsonResponseOptions` bag. The number form exists because passing a
96
+ * status as the second arg is the most common pattern in other web
97
+ * frameworks, and the silent `response.json(data, 401)` → HTTP 200 trap
98
+ * (the number was being interpreted as `options` and discarded) caused
99
+ * real bugs.
100
+ */
101
+ json: <T>(data: T, options: JsonResponseOptions | ResponseStatus = {}): Response => {
102
+ const opts: JsonResponseOptions = typeof options === 'number' ? { status: options } : options
103
+ const { status = 200, headers = {}, pretty = false } = opts
96
104
  const body = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data)
97
105
 
98
106
  return new Response(body, {
@@ -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' {
@@ -1147,6 +1147,15 @@ export class Router {
1147
1147
  return req.headers.get(name) || req.headers.get(name.toLowerCase()) || null
1148
1148
  }
1149
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
+
1150
1159
  ;(enhancedReq as any).getParam = <T = string>(name: string, defaultValue?: T): T | undefined => {
1151
1160
  const value = params?.[name] as T | undefined
1152
1161
  return value !== undefined ? value : defaultValue
@@ -1522,3 +1531,51 @@ export class RouteGroupBuilder {
1522
1531
  return this
1523
1532
  }
1524
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
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Server } from 'bun'
2
2
  import type { EnhancedRequest, HTTPMethod, ServerOptions } from '../types'
3
3
  import type { Router } from './router'
4
+ import { RequestWithMacros } from '../request/macros'
4
5
  import { runWithRequest, setCurrentRequest } from '../request/context'
5
6
 
6
7
  /**
@@ -333,7 +334,13 @@ export function registerServerHandling(RouterClass: typeof Router): void {
333
334
  _cookiesToDelete: [],
334
335
  }) as unknown as EnhancedRequest
335
336
 
336
- // Add cookie methods to the request
337
+ // Attach registered request macros (input, has, bearerToken, etc.) so
338
+ // they're available in middleware and handlers without manual setup.
339
+ // Applied *before* the cookie utility so the cookies macro doesn't
340
+ // overwrite the get/set/delete object form consumers depend on.
341
+ RequestWithMacros.applyMacros(enhancedReq)
342
+
343
+ // Add cookie methods to the request (overrides any macro named `cookies`).
337
344
  Object.assign(enhancedReq, { cookies: { ...getCookies(), ...cookies } })
338
345
 
339
346
  return enhancedReq
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
  */