@bunary/http 0.1.0 → 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 +55 -0
- package/dist/app.d.ts +7 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/handlers/error.d.ts +4 -0
- package/dist/handlers/error.d.ts.map +1 -1
- package/dist/handlers/index.d.ts +1 -1
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/methodNotAllowed.d.ts +4 -1
- package/dist/handlers/methodNotAllowed.d.ts.map +1 -1
- package/dist/handlers/options.d.ts +3 -0
- package/dist/handlers/options.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +83 -53
- package/dist/pathUtils.d.ts.map +1 -1
- package/dist/router.d.ts +4 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/routes/find.d.ts +23 -0
- package/dist/routes/find.d.ts.map +1 -1
- package/dist/routes/group.d.ts.map +1 -1
- package/dist/routes/index.d.ts +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/types/appOptions.d.ts +6 -4
- package/dist/types/appOptions.d.ts.map +1 -1
- package/dist/types/bunaryApp.d.ts +19 -10
- package/dist/types/bunaryApp.d.ts.map +1 -1
- package/dist/types/groupOptions.d.ts +4 -2
- package/dist/types/groupOptions.d.ts.map +1 -1
- package/dist/types/groupRouter.d.ts +13 -8
- package/dist/types/groupRouter.d.ts.map +1 -1
- package/dist/types/middleware.d.ts +9 -4
- package/dist/types/middleware.d.ts.map +1 -1
- package/dist/types/requestContext.d.ts +18 -7
- package/dist/types/requestContext.d.ts.map +1 -1
- package/dist/types/route.d.ts +2 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/routeBuilder.d.ts +11 -9
- package/dist/types/routeBuilder.d.ts.map +1 -1
- package/dist/types/routeHandler.d.ts +7 -3
- package/dist/types/routeHandler.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,61 @@ 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
|
+
|
|
25
|
+
## [0.1.3] - 2026-02-15
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Path parameters are now decoded with `decodeURIComponent` (#52)
|
|
30
|
+
- `hello%20world` → `"hello world"`, `caf%C3%A9` → `"café"`, emoji and CJK characters decoded correctly
|
|
31
|
+
- Malformed percent-sequences (e.g., `%ZZ`) gracefully return raw value
|
|
32
|
+
- Route constraints now check against decoded values
|
|
33
|
+
- `joinPaths("/", "/users")` no longer returns `"//users"` (#49)
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- 83 new unit tests for utility functions and URL encoding behaviour (#49, #52)
|
|
38
|
+
- Direct unit tests for `compilePath()`, `extractParams()`, `checkConstraints()`, `normalizePrefix()`, `joinPaths()`, `toResponse()`
|
|
39
|
+
- Full coverage of URL-encoded paths, unicode, double-encoding, and encoded slashes
|
|
40
|
+
|
|
41
|
+
## [0.1.2] - 2026-02-15
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- Single-pass route resolution replaces multiple scans of the route table (#42, #48)
|
|
46
|
+
- HEAD requests now resolve in one pass instead of three separate `findRoute()` calls
|
|
47
|
+
- 405 responses reuse allowed-methods collected during matching instead of a second full scan
|
|
48
|
+
- New internal `resolveRoute()` function combines matching, HEAD→GET fallback, and method collection
|
|
49
|
+
- No public API changes — purely internal performance improvement
|
|
50
|
+
|
|
51
|
+
## [0.1.1] - 2026-02-15
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- Default error handler no longer leaks `error.message` in production (#44)
|
|
56
|
+
- Returns generic `"Internal Server Error"` when `NODE_ENV=production`
|
|
57
|
+
- Full error message still shown in development and test modes
|
|
58
|
+
|
|
59
|
+
### Removed
|
|
60
|
+
|
|
61
|
+
- Removed internal `Route` type from public exports — use `RouteInfo` for route metadata (#45)
|
|
62
|
+
|
|
8
63
|
## [0.1.0] - 2026-01-31
|
|
9
64
|
|
|
10
65
|
### Added
|
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":"
|
|
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/handlers/error.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { AppOptions, RequestContext } from "../types/index.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Handle 500 Internal Server Error responses.
|
|
4
4
|
* Uses custom onError handler if provided, otherwise returns default JSON response.
|
|
5
|
+
*
|
|
6
|
+
* In production (`NODE_ENV=production`), the default handler returns a generic
|
|
7
|
+
* "Internal Server Error" message to avoid leaking sensitive information.
|
|
8
|
+
* In development and test, the full error message is included.
|
|
5
9
|
*/
|
|
6
10
|
export declare function handleError(ctx: RequestContext, error: unknown, options?: AppOptions): Promise<Response>;
|
|
7
11
|
//# sourceMappingURL=error.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/handlers/error.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEpE
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/handlers/error.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEpE;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAChC,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,QAAQ,CAAC,CAenB"}
|
package/dist/handlers/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { handleError } from "./error.js";
|
|
2
|
-
export {
|
|
2
|
+
export { toHeadResponse } from "./head.js";
|
|
3
3
|
export { handleMethodNotAllowed } from "./methodNotAllowed.js";
|
|
4
4
|
export { handleNotFound } from "./notFound.js";
|
|
5
5
|
export { handleOptions } from "./options.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -3,6 +3,9 @@ import type { AppOptions, Route } from "../types/index.js";
|
|
|
3
3
|
* Handle 405 Method Not Allowed responses.
|
|
4
4
|
* Uses custom onMethodNotAllowed handler if provided, otherwise returns default JSON response.
|
|
5
5
|
* Ensures Allow header is always present.
|
|
6
|
+
*
|
|
7
|
+
* @param precomputed - Pre-computed allowed methods from resolveRoute() to avoid re-scanning.
|
|
8
|
+
* Falls back to scanning routes if not provided.
|
|
6
9
|
*/
|
|
7
|
-
export declare function handleMethodNotAllowed(request: Request, path: string, routes: Route[], options?: AppOptions): Promise<Response>;
|
|
10
|
+
export declare function handleMethodNotAllowed(request: Request, path: string, routes: Route[], options?: AppOptions, precomputed?: string[]): Promise<Response>;
|
|
8
11
|
//# sourceMappingURL=methodNotAllowed.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"methodNotAllowed.d.ts","sourceRoot":"","sources":["../../src/handlers/methodNotAllowed.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAkB,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE3E
|
|
1
|
+
{"version":3,"file":"methodNotAllowed.d.ts","sourceRoot":"","sources":["../../src/handlers/methodNotAllowed.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAkB,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE3E;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC3C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,UAAU,EACpB,WAAW,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,QAAQ,CAAC,CAgCnB"}
|
|
@@ -2,6 +2,9 @@ import type { AppOptions, Route } from "../types/index.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Handle OPTIONS requests.
|
|
4
4
|
* Returns 204 with Allow header if path exists, otherwise delegates to 404 handler.
|
|
5
|
+
*
|
|
6
|
+
* Uses a single getAllowedMethods() scan — if the result is non-empty the path
|
|
7
|
+
* exists, avoiding a separate hasMatchingPath() pass.
|
|
5
8
|
*/
|
|
6
9
|
export declare function handleOptions(request: Request, path: string, routes: Route[], options?: AppOptions): Promise<Response>;
|
|
7
10
|
//# sourceMappingURL=options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/handlers/options.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG3D
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/handlers/options.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG3D;;;;;;GAMG;AACH,wBAAsB,aAAa,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
* @packageDocumentation
|
|
20
20
|
*/
|
|
21
21
|
export { createApp } from "./app.js";
|
|
22
|
-
export type { AppOptions, BunaryApp, BunaryServer, GroupCallback, GroupOptions, GroupRouter, HandlerResponse, HttpMethod, ListenOptions, Middleware, PathParams, RequestContext,
|
|
22
|
+
export type { AppOptions, BunaryApp, BunaryServer, GroupCallback, GroupOptions, GroupRouter, HandlerResponse, HttpMethod, ListenOptions, Middleware, PathParams, RequestContext, RouteBuilder, RouteHandler, RouteInfo, } from "./types/index.js";
|
|
23
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,YAAY,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,eAAe,EACf,UAAU,EACV,aAAa,EACb,UAAU,EACV,UAAU,EACV,cAAc,EACd,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,YAAY,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,eAAe,EACf,UAAU,EACV,aAAa,EACb,UAAU,EACV,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,SAAS,GACT,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,8 @@ async function handleError(ctx, error, options) {
|
|
|
31
31
|
const result = await options.onError(ctx, error);
|
|
32
32
|
return toResponse(result);
|
|
33
33
|
}
|
|
34
|
-
const
|
|
34
|
+
const isProduction = Bun.env.NODE_ENV === "production";
|
|
35
|
+
const message = isProduction ? "Internal Server Error" : error instanceof Error ? error.message : "Internal server error";
|
|
35
36
|
return new Response(JSON.stringify({ error: message }), {
|
|
36
37
|
status: 500,
|
|
37
38
|
headers: { "Content-Type": "application/json" }
|
|
@@ -162,7 +163,19 @@ function wrapBuilderWithNamePrefix(builder, namePrefix) {
|
|
|
162
163
|
function compilePath(path) {
|
|
163
164
|
const paramNames = [];
|
|
164
165
|
const optionalParams = [];
|
|
165
|
-
let
|
|
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, "\\$&");
|
|
166
179
|
regexString = regexString.replace(/\/:([a-zA-Z_][a-zA-Z0-9_]*)(\\\?)?/g, (_match, paramName, isOptional) => {
|
|
167
180
|
if (paramNames.includes(paramName)) {
|
|
168
181
|
throw new Error(`Duplicate parameter name ":${paramName}" in route pattern "${path}". Each parameter name must be unique within a route.`);
|
|
@@ -174,13 +187,26 @@ function compilePath(path) {
|
|
|
174
187
|
}
|
|
175
188
|
return "/([^/]+)";
|
|
176
189
|
});
|
|
177
|
-
|
|
190
|
+
if (isWildcard) {
|
|
191
|
+
regexString += "(?:/(.*))?";
|
|
192
|
+
paramNames.push("*");
|
|
193
|
+
} else {
|
|
194
|
+
regexString += "/?";
|
|
195
|
+
}
|
|
178
196
|
return {
|
|
179
197
|
pattern: new RegExp(`^${regexString}$`),
|
|
180
198
|
paramNames,
|
|
181
|
-
optionalParams
|
|
199
|
+
optionalParams,
|
|
200
|
+
isWildcard
|
|
182
201
|
};
|
|
183
202
|
}
|
|
203
|
+
function safeDecodeURIComponent(value) {
|
|
204
|
+
try {
|
|
205
|
+
return decodeURIComponent(value);
|
|
206
|
+
} catch {
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
184
210
|
function extractParams(path, route) {
|
|
185
211
|
const match = path.match(route.pattern);
|
|
186
212
|
if (!match)
|
|
@@ -189,7 +215,7 @@ function extractParams(path, route) {
|
|
|
189
215
|
for (let i = 0;i < route.paramNames.length; i++) {
|
|
190
216
|
const value = match[i + 1];
|
|
191
217
|
if (value !== undefined && value !== "") {
|
|
192
|
-
params[route.paramNames[i]] = value;
|
|
218
|
+
params[route.paramNames[i]] = safeDecodeURIComponent(value);
|
|
193
219
|
}
|
|
194
220
|
}
|
|
195
221
|
return params;
|
|
@@ -208,27 +234,30 @@ function checkConstraints(params, constraints) {
|
|
|
208
234
|
}
|
|
209
235
|
|
|
210
236
|
// src/routes/find.ts
|
|
211
|
-
function
|
|
237
|
+
function resolveRoute(routes, method, path) {
|
|
238
|
+
let getFallback = null;
|
|
239
|
+
const allowedMethods = new Set;
|
|
212
240
|
for (const route of routes) {
|
|
213
|
-
if (route.pattern.test(path)) {
|
|
214
|
-
if (route.method === method) {
|
|
215
|
-
const params = extractParams(path, route);
|
|
216
|
-
if (!checkConstraints(params, route.constraints)) {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
return { route, params };
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
function hasMatchingPath(routes, path) {
|
|
226
|
-
return routes.some((route) => {
|
|
227
241
|
if (!route.pattern.test(path))
|
|
228
|
-
|
|
242
|
+
continue;
|
|
229
243
|
const params = extractParams(path, route);
|
|
230
|
-
|
|
231
|
-
|
|
244
|
+
if (!checkConstraints(params, route.constraints))
|
|
245
|
+
continue;
|
|
246
|
+
allowedMethods.add(route.method);
|
|
247
|
+
if (route.method === method) {
|
|
248
|
+
return { match: { route, params }, allowedMethods: [] };
|
|
249
|
+
}
|
|
250
|
+
if (method === "HEAD" && route.method === "GET" && !getFallback) {
|
|
251
|
+
getFallback = { route, params };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (getFallback) {
|
|
255
|
+
return { match: getFallback, allowedMethods: [] };
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
match: null,
|
|
259
|
+
allowedMethods: Array.from(allowedMethods).sort()
|
|
260
|
+
};
|
|
232
261
|
}
|
|
233
262
|
function getAllowedMethods(routes, path) {
|
|
234
263
|
const methods = new Set;
|
|
@@ -262,6 +291,9 @@ function joinPaths(prefix, path) {
|
|
|
262
291
|
if (normalizedPath === "/") {
|
|
263
292
|
return normalizedPrefix;
|
|
264
293
|
}
|
|
294
|
+
if (normalizedPrefix === "/") {
|
|
295
|
+
return normalizedPath;
|
|
296
|
+
}
|
|
265
297
|
return normalizedPrefix + normalizedPath;
|
|
266
298
|
}
|
|
267
299
|
|
|
@@ -302,20 +334,6 @@ function createGroupRouter(prefix, groupMiddleware, namePrefix, addRoute) {
|
|
|
302
334
|
return router;
|
|
303
335
|
}
|
|
304
336
|
// src/handlers/head.ts
|
|
305
|
-
function normalizeHeadMethod(method, path, routes) {
|
|
306
|
-
if (method !== "HEAD") {
|
|
307
|
-
return method;
|
|
308
|
-
}
|
|
309
|
-
const headMatch = findRoute(routes, "HEAD", path);
|
|
310
|
-
if (headMatch) {
|
|
311
|
-
return "HEAD";
|
|
312
|
-
}
|
|
313
|
-
const getMatch = findRoute(routes, "GET", path);
|
|
314
|
-
if (getMatch) {
|
|
315
|
-
return "GET";
|
|
316
|
-
}
|
|
317
|
-
return "HEAD";
|
|
318
|
-
}
|
|
319
337
|
function toHeadResponse(response) {
|
|
320
338
|
return new Response(null, {
|
|
321
339
|
status: response.status,
|
|
@@ -324,9 +342,9 @@ function toHeadResponse(response) {
|
|
|
324
342
|
});
|
|
325
343
|
}
|
|
326
344
|
// src/handlers/methodNotAllowed.ts
|
|
327
|
-
async function handleMethodNotAllowed(request, path, routes, options) {
|
|
345
|
+
async function handleMethodNotAllowed(request, path, routes, options, precomputed) {
|
|
328
346
|
const url = new URL(request.url);
|
|
329
|
-
const allowedMethods = getAllowedMethods(routes, path);
|
|
347
|
+
const allowedMethods = precomputed ?? getAllowedMethods(routes, path);
|
|
330
348
|
const methodNotAllowedCtx = {
|
|
331
349
|
request,
|
|
332
350
|
params: {},
|
|
@@ -376,8 +394,8 @@ async function handleNotFound(request, _path, options) {
|
|
|
376
394
|
}
|
|
377
395
|
// src/handlers/options.ts
|
|
378
396
|
async function handleOptions(request, path, routes, options) {
|
|
379
|
-
|
|
380
|
-
|
|
397
|
+
const allowedMethods = getAllowedMethods(routes, path);
|
|
398
|
+
if (allowedMethods.length > 0) {
|
|
381
399
|
return new Response(null, {
|
|
382
400
|
status: 204,
|
|
383
401
|
headers: { Allow: allowedMethods.join(", ") }
|
|
@@ -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 =
|
|
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,15 +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,
|
|
460
|
+
return await handleOptions(request, path, routes, internalOpts);
|
|
441
461
|
}
|
|
442
|
-
const
|
|
443
|
-
const match = findRoute(routes, actualMethod, path);
|
|
462
|
+
const { match, allowedMethods } = resolveRoute(routes, method, path);
|
|
444
463
|
if (!match) {
|
|
445
|
-
if (
|
|
446
|
-
return await handleMethodNotAllowed(request, path, routes,
|
|
464
|
+
if (allowedMethods.length > 0) {
|
|
465
|
+
return await handleMethodNotAllowed(request, path, routes, internalOpts, allowedMethods);
|
|
447
466
|
}
|
|
448
|
-
return await handleNotFound(request, path,
|
|
467
|
+
return await handleNotFound(request, path, internalOpts);
|
|
449
468
|
}
|
|
450
469
|
const ctx = {
|
|
451
470
|
request,
|
|
@@ -460,7 +479,7 @@ function createApp(options) {
|
|
|
460
479
|
}
|
|
461
480
|
return response;
|
|
462
481
|
} catch (error) {
|
|
463
|
-
return await handleError(ctx, error,
|
|
482
|
+
return await handleError(ctx, error, internalOpts);
|
|
464
483
|
}
|
|
465
484
|
}
|
|
466
485
|
const app = {
|
|
@@ -475,8 +494,8 @@ function createApp(options) {
|
|
|
475
494
|
return app;
|
|
476
495
|
},
|
|
477
496
|
group: (prefixOrOptions, callback) => {
|
|
478
|
-
const
|
|
479
|
-
const groupRouter = createGroupRouter(
|
|
497
|
+
const groupOpts = typeof prefixOrOptions === "string" ? { prefix: prefixOrOptions } : prefixOrOptions;
|
|
498
|
+
const groupRouter = createGroupRouter(groupOpts.prefix, groupOpts.middleware ?? [], groupOpts.name ?? "", addRoute);
|
|
480
499
|
callback(groupRouter);
|
|
481
500
|
return app;
|
|
482
501
|
},
|
|
@@ -498,6 +517,17 @@ function createApp(options) {
|
|
|
498
517
|
const queryParams = {};
|
|
499
518
|
const usedParams = new Set;
|
|
500
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
|
+
}
|
|
501
531
|
const isOptional = route.optionalParams?.includes(paramName);
|
|
502
532
|
const value = params?.[paramName];
|
|
503
533
|
if (value !== undefined) {
|
package/dist/pathUtils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathUtils.d.ts","sourceRoot":"","sources":["../src/pathUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"pathUtils.d.ts","sourceRoot":"","sources":["../src/pathUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAmB9D"}
|
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.
|
|
@@ -32,10 +34,11 @@ export declare function compilePath(path: string): CompiledPath;
|
|
|
32
34
|
/**
|
|
33
35
|
* Extract path parameters from a matched route.
|
|
34
36
|
* Handles optional parameters by only including them if they have values.
|
|
37
|
+
* Applies `decodeURIComponent` to each captured value (standard behaviour).
|
|
35
38
|
*
|
|
36
39
|
* @param path - The request path
|
|
37
40
|
* @param route - The matched route
|
|
38
|
-
* @returns Record of parameter names to values (undefined for missing optional params)
|
|
41
|
+
* @returns Record of parameter names to decoded values (undefined for missing optional params)
|
|
39
42
|
*/
|
|
40
43
|
export declare function extractParams(path: string, route: Route): Record<string, string | undefined>;
|
|
41
44
|
/**
|
package/dist/router.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/routes/find.d.ts
CHANGED
|
@@ -3,6 +3,29 @@ export interface RouteMatch {
|
|
|
3
3
|
route: Route;
|
|
4
4
|
params: Record<string, string | undefined>;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Result of a single-pass route resolution.
|
|
8
|
+
*
|
|
9
|
+
* Combines route matching, HEAD→GET fallback, and allowed-method
|
|
10
|
+
* collection into one scan of the route table.
|
|
11
|
+
*/
|
|
12
|
+
export interface RouteResolution {
|
|
13
|
+
/** The matched route and extracted params, or null if no match */
|
|
14
|
+
match: RouteMatch | null;
|
|
15
|
+
/** Methods that match this path (populated when match is null and path exists for other methods) */
|
|
16
|
+
allowedMethods: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a route in a single pass through the route table.
|
|
20
|
+
*
|
|
21
|
+
* For HEAD requests this also checks for a GET fallback.
|
|
22
|
+
* When no exact match is found it collects all methods whose path
|
|
23
|
+
* pattern matches (with constraints), enabling 405 responses and
|
|
24
|
+
* OPTIONS Allow headers without a second scan.
|
|
25
|
+
*
|
|
26
|
+
* **Complexity**: O(n) — one pass regardless of outcome.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveRoute(routes: Route[], method: string, path: string): RouteResolution;
|
|
6
29
|
/**
|
|
7
30
|
* Find a matching route for the given method and path.
|
|
8
31
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../src/routes/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAc1F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMtE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAYzE"}
|
|
1
|
+
{"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../src/routes/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3C;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B,kEAAkE;IAClE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,oGAAoG;IACpG,cAAc,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,CAiC3F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAc1F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMtE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAYzE"}
|
|
@@ -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,
|
|
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"}
|
package/dist/routes/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { compilePattern, createRouteBuilder, wrapBuilderWithNamePrefix } from "./builder.js";
|
|
2
|
-
export { findRoute, getAllowedMethods, hasMatchingPath, type RouteMatch } from "./find.js";
|
|
2
|
+
export { findRoute, getAllowedMethods, hasMatchingPath, type RouteMatch, type RouteResolution, resolveRoute, } from "./find.js";
|
|
3
3
|
export { type AddRouteFn, createGroupRouter } from "./group.js";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EACN,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,YAAY,GACZ,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,UAAU,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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) => ({
|
|
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
|
|
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
|
|
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,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;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,SAAS,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1E;;;;OAIG;IACH,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;IAE3B;;;;OAIG;IACH,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;IAE3B;;;;OAIG;IACH,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;IAE3B;;;;OAIG;IACH,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;IAE3B;;;;OAIG;IACH,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;IAE3B;;;;OAIG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,OAAO,CAAC,CAAC;IAE7D;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,OAAO,CAAC,CAAC,GAChF,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAE5F;;;;;;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,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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
17
|
+
* const logger: Middleware<{ requestId: string }> = async (ctx, next) => {
|
|
18
|
+
* ctx.locals.requestId = crypto.randomUUID();
|
|
14
19
|
* const response = await next();
|
|
15
|
-
* console.log(
|
|
20
|
+
* console.log(`[${ctx.locals.requestId}] done`);
|
|
16
21
|
* return response;
|
|
17
22
|
* };
|
|
18
23
|
* ```
|
|
19
24
|
*/
|
|
20
|
-
export type Middleware = (ctx: RequestContext
|
|
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
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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:
|
|
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:
|
|
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
|
|
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"}
|
package/dist/types/route.d.ts
CHANGED
|
@@ -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;
|
|
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
|
|
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 {
|
|
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
|
|
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"}
|