@stacksjs/bun-router 0.0.13 → 0.0.15

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.
@@ -64,18 +64,26 @@ export function registerRouteBuilding(RouterClass: typeof Router): void {
64
64
  matchPath(path, url.pathname, params, constraintsRecord)
65
65
  const enhancedReq = this.enhanceRequest(req, params)
66
66
 
67
- // Run middleware
68
- const middlewareResult = await this.runMiddleware(
67
+ // Treat the route handler as the terminal middleware so the
68
+ // chain reaches it. Previously this code called
69
+ // `runMiddleware(stack)` without the handler and then only
70
+ // called `resolveHandler()` if middlewareResult was falsy.
71
+ // The chain in `buildMiddlewareChain` bottoms out in
72
+ // `return result || new Response(null, { status: 200 })` —
73
+ // so any user middleware that does `await next()` (cors,
74
+ // request-id, auth gates, etc.) gets a truthy empty Response
75
+ // back from the bottom of the chain, returns it, and the
76
+ // outer `if (middlewareResult)` short-circuits before the
77
+ // handler runs. Visible symptom: every route returns
78
+ // `200 OK` with `Content-Length: 0`.
79
+ const routeHandlerMiddleware = async (req2: any, _next: any) =>
80
+ await this.resolveHandler(route.handler, req2)
81
+ const middlewareStack = [...this.globalMiddleware, ...route.middleware, routeHandlerMiddleware]
82
+ const response = await this.runMiddleware(enhancedReq, middlewareStack)
83
+ return this.applyModifiedCookies(
84
+ response ?? new Response('No response from middleware chain', { status: 500 }),
69
85
  enhancedReq,
70
- [...this.globalMiddleware, ...route.middleware],
71
86
  )
72
- if (middlewareResult) {
73
- return this.applyModifiedCookies(middlewareResult, enhancedReq)
74
- }
75
-
76
- // Run route handler
77
- const response = await this.resolveHandler(route.handler, enhancedReq)
78
- return this.applyModifiedCookies(response, enhancedReq)
79
87
  }
80
88
  }
81
89
  else {
@@ -95,18 +103,19 @@ export function registerRouteBuilding(RouterClass: typeof Router): void {
95
103
  matchPath(path, url.pathname, params, constraintsRecord)
96
104
  const enhancedReq = this.enhanceRequest(req, params)
97
105
 
98
- // Run middleware
99
- const middlewareResult = await this.runMiddleware(
106
+ // See the long comment above the single-method branch:
107
+ // include the route handler as the terminal middleware so
108
+ // the chain reaches it. Without this, any user middleware
109
+ // (cors, request-id) short-circuits to a 200-empty default
110
+ // and the handler never runs.
111
+ const routeHandlerMiddleware = async (req2: any, _next: any) =>
112
+ await this.resolveHandler(route.handler, req2)
113
+ const middlewareStack = [...this.globalMiddleware, ...route.middleware, routeHandlerMiddleware]
114
+ const response = await this.runMiddleware(enhancedReq, middlewareStack)
115
+ return this.applyModifiedCookies(
116
+ response ?? new Response('No response from middleware chain', { status: 500 }),
100
117
  enhancedReq,
101
- [...this.globalMiddleware, ...route.middleware],
102
118
  )
103
- if (middlewareResult) {
104
- return this.applyModifiedCookies(middlewareResult, enhancedReq)
105
- }
106
-
107
- // Run route handler
108
- const response = await this.resolveHandler(route.handler, enhancedReq)
109
- return this.applyModifiedCookies(response, enhancedReq)
110
119
  }
111
120
  }
112
121
 
@@ -389,51 +389,73 @@ export class Router {
389
389
  }
390
390
 
391
391
  /**
392
- * HTTP GET method
392
+ * HTTP GET method.
393
+ *
394
+ * `TPath` is inferred from the path literal at the call site, so an
395
+ * inline handler's `request.params` narrows to the extracted keyset:
396
+ *
397
+ * ```ts
398
+ * router.get('/api/users/{id}', (req) => {
399
+ * req.params.id // typed `string`, no `as any`
400
+ * // @ts-expect-error wrong key
401
+ * req.params.bogus
402
+ * })
403
+ * ```
404
+ *
405
+ * The string-action / class-handler forms (`'Actions/Foo/BarAction'`)
406
+ * keep working — `ActionHandler<TPath>` is a union that accepts them.
407
+ * See stacksjs/stacks#1851 for the broader typed-request work.
393
408
  */
394
- get(path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
409
+ get<TPath extends string>(path: TPath, handler: ActionHandler<TPath>, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
395
410
  return this.registerRoute('GET', path, handler, type, name, middleware)
396
411
  }
397
412
 
398
413
  /**
399
- * HTTP POST method
414
+ * HTTP POST method. See {@link get} for `TPath`-driven param
415
+ * narrowing on inline handlers.
400
416
  */
401
- post(path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
417
+ post<TPath extends string>(path: TPath, handler: ActionHandler<TPath>, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
402
418
  return this.registerRoute('POST', path, handler, type, name, middleware)
403
419
  }
404
420
 
405
421
  /**
406
- * HTTP PUT method
422
+ * HTTP PUT method. See {@link get} for `TPath`-driven param
423
+ * narrowing on inline handlers.
407
424
  */
408
- put(path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
425
+ put<TPath extends string>(path: TPath, handler: ActionHandler<TPath>, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
409
426
  return this.registerRoute('PUT', path, handler, type, name, middleware)
410
427
  }
411
428
 
412
429
  /**
413
- * HTTP PATCH method
430
+ * HTTP PATCH method. See {@link get} for `TPath`-driven param
431
+ * narrowing on inline handlers.
414
432
  */
415
- patch(path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
433
+ patch<TPath extends string>(path: TPath, handler: ActionHandler<TPath>, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
416
434
  return this.registerRoute('PATCH', path, handler, type, name, middleware)
417
435
  }
418
436
 
419
437
  /**
420
- * HTTP DELETE method
438
+ * HTTP DELETE method. See {@link get} for `TPath`-driven param
439
+ * narrowing on inline handlers.
421
440
  */
422
- delete(path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
441
+ delete<TPath extends string>(path: TPath, handler: ActionHandler<TPath>, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
423
442
  return this.registerRoute('DELETE', path, handler, type, name, middleware)
424
443
  }
425
444
 
426
445
  /**
427
- * HTTP OPTIONS method
446
+ * HTTP OPTIONS method. See {@link get} for `TPath`-driven param
447
+ * narrowing on inline handlers.
428
448
  */
429
- options(path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
449
+ options<TPath extends string>(path: TPath, handler: ActionHandler<TPath>, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
430
450
  return this.registerRoute('OPTIONS', path, handler, type, name, middleware)
431
451
  }
432
452
 
433
453
  /**
434
- * Register route for multiple HTTP methods
454
+ * Register the same handler against multiple HTTP methods. The
455
+ * handler is type-narrowed via {@link ActionHandler}'s `TPath`
456
+ * generic, same as the single-method overloads.
435
457
  */
436
- match(methods: string[], path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
458
+ match<TPath extends string>(methods: string[], path: TPath, handler: ActionHandler<TPath>, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
437
459
  for (const method of methods) {
438
460
  this.registerRoute(method, path, handler, type, name, middleware)
439
461
  }
@@ -441,9 +463,10 @@ export class Router {
441
463
  }
442
464
 
443
465
  /**
444
- * Register route for any HTTP method
466
+ * Register the handler against every HTTP method. See {@link get}
467
+ * for `TPath`-driven param narrowing on inline handlers.
445
468
  */
446
- any(path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
469
+ any<TPath extends string>(path: TPath, handler: ActionHandler<TPath>, type?: 'api' | 'web', name?: string, middleware?: (string | MiddlewareHandler)[]): Router {
447
470
  const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']
448
471
  return this.match(methods, path, handler, type, name, middleware)
449
472
  }
package/src/types.ts CHANGED
@@ -1959,6 +1959,18 @@ export interface RequestMacroMethods {
1959
1959
  cookies: () => Record<string, string>
1960
1960
 
1961
1961
  // Files
1962
+ /**
1963
+ * Lookup a single uploaded file by form field name. Returns the
1964
+ * matching `FileInfo` (structurally an `UploadedFile`) or `null` when
1965
+ * no field with that name was uploaded. Populated by the multipart
1966
+ * file-upload middleware alongside `files: UploadedFile[]`. Mirrors
1967
+ * Laravel's `$request->file('avatar')`:
1968
+ *
1969
+ * ```ts
1970
+ * const avatar = req.file('avatar')
1971
+ * if (!avatar) return new Response('Missing avatar', { status: 400 })
1972
+ * ```
1973
+ */
1962
1974
  file: (name: string) => FileInfo | null
1963
1975
  hasFile: (name: string) => boolean
1964
1976