@bunary/http 0.1.3 → 0.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/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to `@bunary/http` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2026-02-15
9
+
10
+ ### Added
11
+
12
+ - Typed `ctx.locals` via `createApp<TLocals>()` generic (#43)
13
+ - `RequestContext<TLocals, TParams>` now accepts two type parameters with backward-compatible defaults
14
+ - `createApp<{ user: User }>()` propagates `TLocals` to all handlers, middleware, groups, and options callbacks
15
+ - `Middleware<TLocals>`, `BunaryApp<TLocals>`, `GroupRouter<TLocals>`, `GroupOptions<TLocals>`, `AppOptions<TLocals>` all generic
16
+ - Fully backward-compatible — omitting the generic keeps the existing `Record<string, unknown>` behaviour
17
+
18
+ - Typed route parameters via per-route `<TParams>` generic (#50)
19
+ - `app.get<{ id: string }>("/users/:id", handler)` narrows `ctx.params` to `{ id: string }` inside the handler
20
+ - Works on all HTTP methods: `get`, `post`, `put`, `patch`, `delete`
21
+ - Works inside route groups: `router.get<{ id: string }>(...)`
22
+ - Values remain strings at runtime — the generic only narrows the TypeScript type
23
+ - Default `PathParams` (`Record<string, string | undefined>`) preserved when no generic is provided
24
+
8
25
  ## [0.1.3] - 2026-02-15
9
26
 
10
27
  ### Fixed
package/dist/app.d.ts CHANGED
@@ -35,6 +35,7 @@ import type { AppOptions, BunaryApp } from "./types/index.js";
35
35
  *
36
36
  * @param options - Optional configuration
37
37
  * @param options.basePath - Base path prefix for all routes (e.g., "/api")
38
+ * @typeParam TLocals — Shape of `ctx.locals`. Defaults to `Record<string, unknown>`.
38
39
  * @returns BunaryApp instance
39
40
  *
40
41
  * @example
@@ -46,7 +47,12 @@ import type { AppOptions, BunaryApp } from "./types/index.js";
46
47
  * // With basePath
47
48
  * const apiApp = createApp({ basePath: "/api" });
48
49
  * apiApp.get("/users", () => ({})); // Matches /api/users
50
+ *
51
+ * // With typed locals
52
+ * interface Locals { user: User; requestId: string }
53
+ * const typedApp = createApp<Locals>();
54
+ * typedApp.get("/me", (ctx) => ({ user: ctx.locals.user })); // typed
49
55
  * ```
50
56
  */
51
- export declare function createApp(options?: AppOptions): BunaryApp;
57
+ export declare function createApp<TLocals extends object = Record<string, unknown>>(options?: AppOptions<TLocals>): BunaryApp<TLocals>;
52
58
  //# sourceMappingURL=app.d.ts.map
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACX,UAAU,EACV,SAAS,EAWT,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CA+OzD"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACX,UAAU,EACV,SAAS,EAWT,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,wBAAgB,SAAS,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzE,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAC3B,SAAS,CAAC,OAAO,CAAC,CAyQpB"}
package/dist/index.js CHANGED
@@ -163,7 +163,19 @@ function wrapBuilderWithNamePrefix(builder, namePrefix) {
163
163
  function compilePath(path) {
164
164
  const paramNames = [];
165
165
  const optionalParams = [];
166
- let regexString = path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
166
+ let isWildcard = false;
167
+ let processedPath = path;
168
+ if (processedPath.endsWith("/**")) {
169
+ processedPath = processedPath.slice(0, -3);
170
+ isWildcard = true;
171
+ } else if (processedPath.endsWith("/*")) {
172
+ processedPath = processedPath.slice(0, -2);
173
+ isWildcard = true;
174
+ }
175
+ if (processedPath.includes("*")) {
176
+ throw new Error(`Wildcard "*" must appear at the end of the route pattern "${path}". Mid-path wildcards are not supported.`);
177
+ }
178
+ let regexString = processedPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
167
179
  regexString = regexString.replace(/\/:([a-zA-Z_][a-zA-Z0-9_]*)(\\\?)?/g, (_match, paramName, isOptional) => {
168
180
  if (paramNames.includes(paramName)) {
169
181
  throw new Error(`Duplicate parameter name ":${paramName}" in route pattern "${path}". Each parameter name must be unique within a route.`);
@@ -175,11 +187,17 @@ function compilePath(path) {
175
187
  }
176
188
  return "/([^/]+)";
177
189
  });
178
- regexString += "/?";
190
+ if (isWildcard) {
191
+ regexString += "(?:/(.*))?";
192
+ paramNames.push("*");
193
+ } else {
194
+ regexString += "/?";
195
+ }
179
196
  return {
180
197
  pattern: new RegExp(`^${regexString}$`),
181
198
  paramNames,
182
- optionalParams
199
+ optionalParams,
200
+ isWildcard
183
201
  };
184
202
  }
185
203
  function safeDecodeURIComponent(value) {
@@ -401,10 +419,11 @@ async function executeRoute(match, ctx, getMiddlewareChain) {
401
419
  }
402
420
  // src/app.ts
403
421
  function createApp(options) {
422
+ const internalOpts = options;
404
423
  const routes = [];
405
424
  const middlewares = [];
406
425
  const namedRoutes = new Map;
407
- const normalizedBasePath = options?.basePath ? normalizePrefix(options.basePath) : "";
426
+ const normalizedBasePath = internalOpts?.basePath ? normalizePrefix(internalOpts.basePath) : "";
408
427
  const basePath = normalizedBasePath === "/" ? "" : normalizedBasePath;
409
428
  let globalMiddlewareVersion = 0;
410
429
  const middlewareCache = new WeakMap;
@@ -419,7 +438,7 @@ function createApp(options) {
419
438
  }
420
439
  function addRoute(method, path, handler, groupMiddleware = []) {
421
440
  const fullPath = basePath ? joinPaths(basePath, path) : path;
422
- const { pattern, paramNames, optionalParams } = compilePath(fullPath);
441
+ const { pattern, paramNames, optionalParams, isWildcard } = compilePath(fullPath);
423
442
  const route = {
424
443
  method,
425
444
  path: fullPath,
@@ -427,7 +446,8 @@ function createApp(options) {
427
446
  paramNames,
428
447
  handler,
429
448
  optionalParams: optionalParams.length > 0 ? optionalParams : undefined,
430
- middleware: groupMiddleware.length > 0 ? [...groupMiddleware] : undefined
449
+ middleware: groupMiddleware.length > 0 ? [...groupMiddleware] : undefined,
450
+ isWildcard: isWildcard || undefined
431
451
  };
432
452
  routes.push(route);
433
453
  return createRouteBuilder(route, namedRoutes, app);
@@ -437,14 +457,14 @@ function createApp(options) {
437
457
  const path = url.pathname;
438
458
  const method = request.method;
439
459
  if (method === "OPTIONS") {
440
- return await handleOptions(request, path, routes, options);
460
+ return await handleOptions(request, path, routes, internalOpts);
441
461
  }
442
462
  const { match, allowedMethods } = resolveRoute(routes, method, path);
443
463
  if (!match) {
444
464
  if (allowedMethods.length > 0) {
445
- return await handleMethodNotAllowed(request, path, routes, options, allowedMethods);
465
+ return await handleMethodNotAllowed(request, path, routes, internalOpts, allowedMethods);
446
466
  }
447
- return await handleNotFound(request, path, options);
467
+ return await handleNotFound(request, path, internalOpts);
448
468
  }
449
469
  const ctx = {
450
470
  request,
@@ -459,7 +479,7 @@ function createApp(options) {
459
479
  }
460
480
  return response;
461
481
  } catch (error) {
462
- return await handleError(ctx, error, options);
482
+ return await handleError(ctx, error, internalOpts);
463
483
  }
464
484
  }
465
485
  const app = {
@@ -474,8 +494,8 @@ function createApp(options) {
474
494
  return app;
475
495
  },
476
496
  group: (prefixOrOptions, callback) => {
477
- const opts = typeof prefixOrOptions === "string" ? { prefix: prefixOrOptions } : prefixOrOptions;
478
- const groupRouter = createGroupRouter(opts.prefix, opts.middleware ?? [], opts.name ?? "", addRoute);
497
+ const groupOpts = typeof prefixOrOptions === "string" ? { prefix: prefixOrOptions } : prefixOrOptions;
498
+ const groupRouter = createGroupRouter(groupOpts.prefix, groupOpts.middleware ?? [], groupOpts.name ?? "", addRoute);
479
499
  callback(groupRouter);
480
500
  return app;
481
501
  },
@@ -497,6 +517,17 @@ function createApp(options) {
497
517
  const queryParams = {};
498
518
  const usedParams = new Set;
499
519
  for (const paramName of route.paramNames) {
520
+ if (paramName === "*") {
521
+ const wildcardValue = params?.["*"];
522
+ if (wildcardValue !== undefined) {
523
+ const encoded = String(wildcardValue).split("/").map(encodeURIComponent).join("/");
524
+ url = url.replace(/\/\*{1,2}$/, `/${encoded}`);
525
+ usedParams.add("*");
526
+ } else {
527
+ url = url.replace(/\/\*{1,2}$/, "");
528
+ }
529
+ continue;
530
+ }
500
531
  const isOptional = route.optionalParams?.includes(paramName);
501
532
  const value = params?.[paramName];
502
533
  if (value !== undefined) {
package/dist/router.d.ts CHANGED
@@ -12,6 +12,8 @@ export interface CompiledPath {
12
12
  paramNames: string[];
13
13
  /** Names of optional parameters */
14
14
  optionalParams: string[];
15
+ /** Whether this path ends with a wildcard catch-all */
16
+ isWildcard: boolean;
15
17
  }
16
18
  /**
17
19
  * Compile a path pattern into a regex and extract parameter names.
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,mCAAmC;IACnC,cAAc,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAmCtD;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAa5F;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAC1C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAUT"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,mCAAmC;IACnC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CA8DtD;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAa5F;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAC1C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAUT"}
@@ -1 +1 @@
1
- {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/routes/group.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGX,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,UAAU,GAAG,CACxB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,EACnD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,EACrB,eAAe,CAAC,EAAE,UAAU,EAAE,KAC1B,YAAY,CAAC;AAElB;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,UAAU,EAAE,EAC7B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,UAAU,GAClB,WAAW,CAoDb"}
1
+ {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/routes/group.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGX,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,UAAU,GAAG,CACxB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,EACnD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,EACrB,eAAe,CAAC,EAAE,UAAU,EAAE,KAC1B,YAAY,CAAC;AAElB;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,UAAU,EAAE,EAC7B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,UAAU,GAClB,WAAW,CAuDb"}
@@ -2,8 +2,10 @@ import type { HandlerResponse } from "./handlerResponse.js";
2
2
  import type { RequestContext } from "./requestContext.js";
3
3
  /**
4
4
  * Configuration options for creating a Bunary app.
5
+ *
6
+ * @typeParam TLocals — Shape of `ctx.locals` (must match `createApp<TLocals>()`)
5
7
  */
6
- export interface AppOptions {
8
+ export interface AppOptions<TLocals extends object = Record<string, unknown>> {
7
9
  /** Base path prefix for all routes (default: "") */
8
10
  basePath?: string;
9
11
  /**
@@ -23,7 +25,7 @@ export interface AppOptions {
23
25
  * });
24
26
  * ```
25
27
  */
26
- onNotFound?: (ctx: RequestContext) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
28
+ onNotFound?: (ctx: RequestContext<TLocals>) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
27
29
  /**
28
30
  * Custom handler for 405 Method Not Allowed responses.
29
31
  * Called when a route matches the path but not the HTTP method.
@@ -45,7 +47,7 @@ export interface AppOptions {
45
47
  * });
46
48
  * ```
47
49
  */
48
- onMethodNotAllowed?: (ctx: RequestContext, allowedMethods: string[]) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
50
+ onMethodNotAllowed?: (ctx: RequestContext<TLocals>, allowedMethods: string[]) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
49
51
  /**
50
52
  * Custom handler for 500 Internal Server Error responses.
51
53
  * Called when a route handler or middleware throws an error.
@@ -67,6 +69,6 @@ export interface AppOptions {
67
69
  * });
68
70
  * ```
69
71
  */
70
- onError?: (ctx: RequestContext, error: unknown) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
72
+ onError?: (ctx: RequestContext<TLocals>, error: unknown) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
71
73
  }
72
74
  //# sourceMappingURL=appOptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"appOptions.d.ts","sourceRoot":"","sources":["../../src/types/appOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,CACZ,GAAG,EAAE,cAAc,KACf,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;IACtE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,kBAAkB,CAAC,EAAE,CACpB,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,MAAM,EAAE,KACpB,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;IACtE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,EAAE,CACT,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,OAAO,KACV,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;CACtE"}
1
+ {"version":3,"file":"appOptions.d.ts","sourceRoot":"","sources":["../../src/types/appOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,UAAU,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3E,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,CACZ,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,KACxB,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;IACtE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,kBAAkB,CAAC,EAAE,CACpB,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,EAC5B,cAAc,EAAE,MAAM,EAAE,KACpB,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;IACtE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,EAAE,CACT,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,EAC5B,KAAK,EAAE,OAAO,KACV,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;CACtE"}
@@ -3,65 +3,74 @@ import type { GroupOptions } from "./groupOptions.js";
3
3
  import type { GroupCallback } from "./groupRouter.js";
4
4
  import type { ListenOptions } from "./listenOptions.js";
5
5
  import type { Middleware } from "./middleware.js";
6
+ import type { PathParams } from "./pathParams.js";
6
7
  import type { RouteBuilder } from "./routeBuilder.js";
7
8
  import type { RouteHandler } from "./routeHandler.js";
8
9
  import type { RouteInfo } from "./routeInfo.js";
9
10
  /**
10
11
  * The Bunary application instance for HTTP routing and middleware.
11
12
  *
13
+ * @typeParam TLocals — Shape of the per-request `locals` store. Set via
14
+ * `createApp<TLocals>()` and propagated to all handlers and middleware.
15
+ *
12
16
  * @example
13
17
  * ```ts
14
- * const app = createApp();
18
+ * interface Locals { user: User }
19
+ *
20
+ * const app = createApp<Locals>();
15
21
  *
16
22
  * app.get("/", () => ({ message: "Hello!" }));
17
- * app.get("/users/:id", (ctx) => ({ id: ctx.params.id }));
23
+ * app.get<{ id: string }>("/users/:id", (ctx) => ({
24
+ * id: ctx.params.id, // string
25
+ * user: ctx.locals.user, // User
26
+ * }));
18
27
  *
19
28
  * app.listen(3000);
20
29
  * ```
21
30
  */
22
- export interface BunaryApp {
31
+ export interface BunaryApp<TLocals extends object = Record<string, unknown>> {
23
32
  /**
24
33
  * Register a GET route.
25
34
  * @param path - URL path pattern (supports :param and :param? syntax)
26
35
  * @param handler - Function to handle requests
27
36
  */
28
- get: (path: string, handler: RouteHandler) => RouteBuilder;
37
+ get: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
29
38
  /**
30
39
  * Register a POST route.
31
40
  * @param path - URL path pattern (supports :param and :param? syntax)
32
41
  * @param handler - Function to handle requests
33
42
  */
34
- post: (path: string, handler: RouteHandler) => RouteBuilder;
43
+ post: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
35
44
  /**
36
45
  * Register a PUT route.
37
46
  * @param path - URL path pattern (supports :param and :param? syntax)
38
47
  * @param handler - Function to handle requests
39
48
  */
40
- put: (path: string, handler: RouteHandler) => RouteBuilder;
49
+ put: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
41
50
  /**
42
51
  * Register a DELETE route.
43
52
  * @param path - URL path pattern (supports :param and :param? syntax)
44
53
  * @param handler - Function to handle requests
45
54
  */
46
- delete: (path: string, handler: RouteHandler) => RouteBuilder;
55
+ delete: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
47
56
  /**
48
57
  * Register a PATCH route.
49
58
  * @param path - URL path pattern (supports :param and :param? syntax)
50
59
  * @param handler - Function to handle requests
51
60
  */
52
- patch: (path: string, handler: RouteHandler) => RouteBuilder;
61
+ patch: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
53
62
  /**
54
63
  * Add middleware to the request pipeline.
55
64
  * Middleware executes in registration order.
56
65
  * @param middleware - Middleware function
57
66
  */
58
- use: (middleware: Middleware) => BunaryApp;
67
+ use: (middleware: Middleware<TLocals>) => BunaryApp<TLocals>;
59
68
  /**
60
69
  * Create a route group with shared prefix, middleware, or name prefix.
61
70
  * @param prefix - URL prefix for all routes in the group
62
71
  * @param callback - Function to define routes within the group
63
72
  */
64
- group: ((prefix: string, callback: GroupCallback) => BunaryApp) & ((options: GroupOptions, callback: GroupCallback) => BunaryApp);
73
+ group: ((prefix: string, callback: GroupCallback<TLocals>) => BunaryApp<TLocals>) & ((options: GroupOptions<TLocals>, callback: GroupCallback<TLocals>) => BunaryApp<TLocals>);
65
74
  /**
66
75
  * Generate a URL for a named route.
67
76
  * @param name - The route name
@@ -1 +1 @@
1
- {"version":3,"file":"bunaryApp.d.ts","sourceRoot":"","sources":["../../src/types/bunaryApp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACzB;;;;OAIG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE3D;;;;OAIG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE5D;;;;OAIG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE3D;;;;OAIG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE9D;;;;OAIG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE7D;;;;OAIG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,SAAS,CAAC;IAE3C;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,SAAS,CAAC,GAC9D,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,KAAK,SAAS,CAAC,CAAC;IAEjE;;;;;;OAMG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;IAE1E;;;;OAIG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAEpC;;;OAGG;IACH,SAAS,EAAE,MAAM,SAAS,EAAE,CAAC;IAE7B;;;;;;;;;;OAUG;IACH,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,YAAY,CAAC,GAC3D,CAAC,CAAC,OAAO,EAAE,aAAa,KAAK,YAAY,CAAC,CAAC;IAE5C;;;;OAIG;IACH,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/C"}
1
+ {"version":3,"file":"bunaryApp.d.ts","sourceRoot":"","sources":["../../src/types/bunaryApp.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,14 @@
1
1
  import type { Middleware } from "./middleware.js";
2
2
  /**
3
3
  * Options for route groups.
4
+ *
5
+ * @typeParam TLocals — Shape of `ctx.locals` (inherited from `createApp<TLocals>()`)
4
6
  */
5
- export interface GroupOptions {
7
+ export interface GroupOptions<TLocals extends object = Record<string, unknown>> {
6
8
  /** URL prefix for all routes in the group */
7
9
  prefix: string;
8
10
  /** Middleware to apply to all routes in the group */
9
- middleware?: Middleware[];
11
+ middleware?: Middleware<TLocals>[];
10
12
  /** Name prefix for all routes in the group */
11
13
  name?: string;
12
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"groupOptions.d.ts","sourceRoot":"","sources":["../../src/types/groupOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IAC1B,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;CACd"}
1
+ {"version":3,"file":"groupOptions.d.ts","sourceRoot":"","sources":["../../src/types/groupOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7E,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,UAAU,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;IACnC,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;CACd"}
@@ -1,26 +1,31 @@
1
1
  import type { GroupOptions } from "./groupOptions.js";
2
+ import type { PathParams } from "./pathParams.js";
2
3
  import type { RouteBuilder } from "./routeBuilder.js";
3
4
  import type { RouteHandler } from "./routeHandler.js";
4
5
  /**
5
6
  * Router interface for route groups.
6
7
  * Provides the same routing methods as BunaryApp but scoped to a group.
8
+ *
9
+ * @typeParam TLocals — Shape of `ctx.locals` (inherited from `createApp<TLocals>()`)
7
10
  */
8
- export interface GroupRouter {
11
+ export interface GroupRouter<TLocals extends object = Record<string, unknown>> {
9
12
  /** Register a GET route */
10
- get: (path: string, handler: RouteHandler) => RouteBuilder;
13
+ get: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
11
14
  /** Register a POST route */
12
- post: (path: string, handler: RouteHandler) => RouteBuilder;
15
+ post: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
13
16
  /** Register a PUT route */
14
- put: (path: string, handler: RouteHandler) => RouteBuilder;
17
+ put: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
15
18
  /** Register a DELETE route */
16
- delete: (path: string, handler: RouteHandler) => RouteBuilder;
19
+ delete: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
17
20
  /** Register a PATCH route */
18
- patch: (path: string, handler: RouteHandler) => RouteBuilder;
21
+ patch: <P extends PathParams = PathParams>(path: string, handler: RouteHandler<TLocals, P>) => RouteBuilder<TLocals>;
19
22
  /** Create a nested route group */
20
- group: ((prefix: string, callback: GroupCallback) => GroupRouter) & ((options: GroupOptions, callback: GroupCallback) => GroupRouter);
23
+ group: ((prefix: string, callback: GroupCallback<TLocals>) => GroupRouter<TLocals>) & ((options: GroupOptions<TLocals>, callback: GroupCallback<TLocals>) => GroupRouter<TLocals>);
21
24
  }
22
25
  /**
23
26
  * Callback function for defining routes within a group.
27
+ *
28
+ * @typeParam TLocals — Shape of `ctx.locals` (inherited from `createApp<TLocals>()`)
24
29
  */
25
- export type GroupCallback = (router: GroupRouter) => void;
30
+ export type GroupCallback<TLocals extends object = Record<string, unknown>> = (router: GroupRouter<TLocals>) => void;
26
31
  //# sourceMappingURL=groupRouter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"groupRouter.d.ts","sourceRoot":"","sources":["../../src/types/groupRouter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC3B,2BAA2B;IAC3B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAC3D,4BAA4B;IAC5B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAC5D,2BAA2B;IAC3B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAC3D,8BAA8B;IAC9B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAC9D,6BAA6B;IAC7B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAC7D,kCAAkC;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,WAAW,CAAC,GAChE,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,KAAK,WAAW,CAAC,CAAC;CACnE;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC"}
1
+ {"version":3,"file":"groupRouter.d.ts","sourceRoot":"","sources":["../../src/types/groupRouter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,WAAW,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5E,2BAA2B;IAC3B,GAAG,EAAE,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,KAC7B,YAAY,CAAC,OAAO,CAAC,CAAC;IAC3B,4BAA4B;IAC5B,IAAI,EAAE,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,KAC7B,YAAY,CAAC,OAAO,CAAC,CAAC;IAC3B,2BAA2B;IAC3B,GAAG,EAAE,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,KAC7B,YAAY,CAAC,OAAO,CAAC,CAAC;IAC3B,8BAA8B;IAC9B,MAAM,EAAE,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EACzC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,KAC7B,YAAY,CAAC,OAAO,CAAC,CAAC;IAC3B,6BAA6B;IAC7B,KAAK,EAAE,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EACxC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,KAC7B,YAAY,CAAC,OAAO,CAAC,CAAC;IAC3B,kCAAkC;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,CAAC,GAClF,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;CAC9F;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAC7E,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KACxB,IAAI,CAAC"}
@@ -3,19 +3,24 @@ import type { RequestContext } from "./requestContext.js";
3
3
  /**
4
4
  * Middleware function for processing requests in a pipeline.
5
5
  *
6
+ * Middleware receives the app-level `TLocals` type but not per-route
7
+ * `TParams`, since middleware runs before route matching resolves params.
8
+ *
9
+ * @typeParam TLocals — Shape of `ctx.locals` (inherited from `createApp<TLocals>()`)
10
+ *
6
11
  * @param ctx - The request context
7
12
  * @param next - Function to call the next middleware or route handler
8
13
  * @returns Response data or void (if next() handles it)
9
14
  *
10
15
  * @example
11
16
  * ```ts
12
- * const logger: Middleware = async (ctx, next) => {
13
- * console.log(`${ctx.request.method} ${ctx.request.url}`);
17
+ * const logger: Middleware<{ requestId: string }> = async (ctx, next) => {
18
+ * ctx.locals.requestId = crypto.randomUUID();
14
19
  * const response = await next();
15
- * console.log("Response sent");
20
+ * console.log(`[${ctx.locals.requestId}] done`);
16
21
  * return response;
17
22
  * };
18
23
  * ```
19
24
  */
20
- export type Middleware = (ctx: RequestContext, next: () => Promise<HandlerResponse>) => HandlerResponse | Promise<HandlerResponse>;
25
+ export type Middleware<TLocals extends object = Record<string, unknown>> = (ctx: RequestContext<TLocals>, next: () => Promise<HandlerResponse>) => HandlerResponse | Promise<HandlerResponse>;
21
26
  //# sourceMappingURL=middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/types/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,UAAU,GAAG,CACxB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,KAChC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/types/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,UAAU,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAC1E,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,EAC5B,IAAI,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,KAChC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC"}
@@ -2,20 +2,31 @@ import type { PathParams } from "./pathParams.js";
2
2
  /**
3
3
  * Context object passed to route handlers containing request data.
4
4
  *
5
+ * @typeParam TLocals — Shape of the per-request `locals` store. Defaults to
6
+ * `Record<string, unknown>` for backward compatibility. Narrow it via
7
+ * `createApp<TLocals>()` to get type-safe middleware→handler data passing.
8
+ * @typeParam TParams — Shape of the route parameters. Defaults to `PathParams`
9
+ * (`Record<string, string | undefined>`). Narrow it per-route via
10
+ * `app.get<TParams>()` to get typed parameter access.
11
+ *
5
12
  * @example
6
13
  * ```ts
7
- * app.get("/users/:id", (ctx) => {
8
- * console.log(ctx.params.id); // "123"
9
- * console.log(ctx.query.get("sort")); // "name"
10
- * return { id: ctx.params.id };
14
+ * interface Locals { user: User; requestId: string }
15
+ *
16
+ * const app = createApp<Locals>();
17
+ *
18
+ * app.get<{ id: string }>("/users/:id", (ctx) => {
19
+ * ctx.params.id; // string
20
+ * ctx.locals.user; // User
21
+ * ctx.locals.requestId; // string
11
22
  * });
12
23
  * ```
13
24
  */
14
- export interface RequestContext {
25
+ export interface RequestContext<TLocals extends object = Record<string, unknown>, TParams extends PathParams = PathParams> {
15
26
  /** The original Bun Request object */
16
27
  request: Request;
17
28
  /** Path parameters extracted from the route pattern */
18
- params: PathParams;
29
+ params: TParams;
19
30
  /** Query parameters from the URL search string */
20
31
  query: URLSearchParams;
21
32
  /**
@@ -31,6 +42,6 @@ export interface RequestContext {
31
42
  * });
32
43
  * ```
33
44
  */
34
- locals: Record<string, unknown>;
45
+ locals: TLocals;
35
46
  }
36
47
  //# sourceMappingURL=requestContext.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"requestContext.d.ts","sourceRoot":"","sources":["../../src/types/requestContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC9B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,MAAM,EAAE,UAAU,CAAC;IACnB,kDAAkD;IAClD,KAAK,EAAE,eAAe,CAAC;IACvB;;;;;;;;;;;;OAYG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC"}
1
+ {"version":3,"file":"requestContext.d.ts","sourceRoot":"","sources":["../../src/types/requestContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,cAAc,CAC9B,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChD,OAAO,SAAS,UAAU,GAAG,UAAU;IAEvC,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,MAAM,EAAE,OAAO,CAAC;IAChB,kDAAkD;IAClD,KAAK,EAAE,eAAe,CAAC;IACvB;;;;;;;;;;;;OAYG;IACH,MAAM,EAAE,OAAO,CAAC;CAChB"}
@@ -23,5 +23,7 @@ export interface Route {
23
23
  middleware?: Middleware[];
24
24
  /** Names of optional parameters */
25
25
  optionalParams?: string[];
26
+ /** Whether this route uses a wildcard catch-all (/* or /**) */
27
+ isWildcard?: boolean;
26
28
  }
27
29
  //# sourceMappingURL=route.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/types/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,KAAK;IACrB,iCAAiC;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,sCAAsC;IACtC,OAAO,EAAE,YAAY,CAAC;IACtB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,gCAAgC;IAChC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IAC1B,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/types/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,KAAK;IACrB,iCAAiC;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,sCAAsC;IACtC,OAAO,EAAE,YAAY,CAAC;IACtB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,gCAAgC;IAChC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IAC1B,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB"}
@@ -2,23 +2,25 @@ import type { BunaryApp } from "./bunaryApp.js";
2
2
  /**
3
3
  * Fluent builder for route configuration.
4
4
  * Allows chaining methods like name(), where(), etc.
5
+ *
6
+ * @typeParam TLocals — Shape of `ctx.locals` (inherited from `createApp<TLocals>()`)
5
7
  */
6
- export interface RouteBuilder extends BunaryApp {
8
+ export interface RouteBuilder<TLocals extends object = Record<string, unknown>> extends BunaryApp<TLocals> {
7
9
  /** Assign a name to the route for URL generation */
8
- name: (name: string) => RouteBuilder;
10
+ name: (name: string) => RouteBuilder<TLocals>;
9
11
  /** Add a regex constraint to a route parameter */
10
- where: ((param: string, pattern: RegExp | string) => RouteBuilder) & ((constraints: Record<string, RegExp | string>) => RouteBuilder);
12
+ where: ((param: string, pattern: RegExp | string) => RouteBuilder<TLocals>) & ((constraints: Record<string, RegExp | string>) => RouteBuilder<TLocals>);
11
13
  /** Constrain parameter to digits only */
12
- whereNumber: (param: string) => RouteBuilder;
14
+ whereNumber: (param: string) => RouteBuilder<TLocals>;
13
15
  /** Constrain parameter to letters only */
14
- whereAlpha: (param: string) => RouteBuilder;
16
+ whereAlpha: (param: string) => RouteBuilder<TLocals>;
15
17
  /** Constrain parameter to letters and digits only */
16
- whereAlphaNumeric: (param: string) => RouteBuilder;
18
+ whereAlphaNumeric: (param: string) => RouteBuilder<TLocals>;
17
19
  /** Constrain parameter to UUID format */
18
- whereUuid: (param: string) => RouteBuilder;
20
+ whereUuid: (param: string) => RouteBuilder<TLocals>;
19
21
  /** Constrain parameter to ULID format */
20
- whereUlid: (param: string) => RouteBuilder;
22
+ whereUlid: (param: string) => RouteBuilder<TLocals>;
21
23
  /** Constrain parameter to specific allowed values */
22
- whereIn: (param: string, values: string[]) => RouteBuilder;
24
+ whereIn: (param: string, values: string[]) => RouteBuilder<TLocals>;
23
25
  }
24
26
  //# sourceMappingURL=routeBuilder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"routeBuilder.d.ts","sourceRoot":"","sources":["../../src/types/routeBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC9C,oDAAoD;IACpD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,CAAC;IACrC,kDAAkD;IAClD,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,YAAY,CAAC,GACjE,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,YAAY,CAAC,CAAC;IAClE,yCAAyC;IACzC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC;IAC7C,0CAA0C;IAC1C,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC;IAC5C,qDAAqD;IACrD,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC;IACnD,yCAAyC;IACzC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC;IAC3C,yCAAyC;IACzC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC;IAC3C,qDAAqD;IACrD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC;CAC3D"}
1
+ {"version":3,"file":"routeBuilder.d.ts","sourceRoot":"","sources":["../../src/types/routeBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC7E,SAAQ,SAAS,CAAC,OAAO,CAAC;IAC1B,oDAAoD;IACpD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC;IAC9C,kDAAkD;IAClD,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC,GAC1E,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3E,yCAAyC;IACzC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC;IACtD,0CAA0C;IAC1C,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC;IACrD,qDAAqD;IACrD,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,yCAAyC;IACzC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC;IACpD,yCAAyC;IACzC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC;IACpD,qDAAqD;IACrD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC;CACpE"}
@@ -1,17 +1,21 @@
1
1
  import type { HandlerResponse } from "./handlerResponse.js";
2
+ import type { PathParams } from "./pathParams.js";
2
3
  import type { RequestContext } from "./requestContext.js";
3
4
  /**
4
5
  * Route handler function that processes incoming requests.
5
6
  *
7
+ * @typeParam TLocals — Shape of `ctx.locals` (inherited from `createApp<TLocals>()`)
8
+ * @typeParam TParams — Shape of `ctx.params` (specified per-route via `app.get<TParams>()`)
9
+ *
6
10
  * @param ctx - The request context containing request, params, and query
7
11
  * @returns Response data (object for JSON, Response for custom, or primitive)
8
12
  *
9
13
  * @example
10
14
  * ```ts
11
- * const handler: RouteHandler = (ctx) => {
12
- * return { message: "Hello, World!" };
15
+ * const handler: RouteHandler<{ user: User }, { id: string }> = (ctx) => {
16
+ * return { id: ctx.params.id, name: ctx.locals.user.name };
13
17
  * };
14
18
  * ```
15
19
  */
16
- export type RouteHandler = (ctx: RequestContext) => HandlerResponse | Promise<HandlerResponse>;
20
+ export type RouteHandler<TLocals extends object = Record<string, unknown>, TParams extends PathParams = PathParams> = (ctx: RequestContext<TLocals, TParams>) => HandlerResponse | Promise<HandlerResponse>;
17
21
  //# sourceMappingURL=routeHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"routeHandler.d.ts","sourceRoot":"","sources":["../../src/types/routeHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"routeHandler.d.ts","sourceRoot":"","sources":["../../src/types/routeHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,YAAY,CACvB,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChD,OAAO,SAAS,UAAU,GAAG,UAAU,IACpC,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunary/http",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "HTTP routing and middleware for Bunary - a Bun-first backend framework inspired by Laravel",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",