@bunary/http 0.0.11 → 0.1.3
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 +44 -0
- package/README.md +2 -499
- 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 +41 -42
- package/dist/pathUtils.d.ts.map +1 -1
- package/dist/router.d.ts +2 -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/index.d.ts +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ 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.1.3] - 2026-02-15
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Path parameters are now decoded with `decodeURIComponent` (#52)
|
|
13
|
+
- `hello%20world` → `"hello world"`, `caf%C3%A9` → `"café"`, emoji and CJK characters decoded correctly
|
|
14
|
+
- Malformed percent-sequences (e.g., `%ZZ`) gracefully return raw value
|
|
15
|
+
- Route constraints now check against decoded values
|
|
16
|
+
- `joinPaths("/", "/users")` no longer returns `"//users"` (#49)
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- 83 new unit tests for utility functions and URL encoding behaviour (#49, #52)
|
|
21
|
+
- Direct unit tests for `compilePath()`, `extractParams()`, `checkConstraints()`, `normalizePrefix()`, `joinPaths()`, `toResponse()`
|
|
22
|
+
- Full coverage of URL-encoded paths, unicode, double-encoding, and encoded slashes
|
|
23
|
+
|
|
24
|
+
## [0.1.2] - 2026-02-15
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Single-pass route resolution replaces multiple scans of the route table (#42, #48)
|
|
29
|
+
- HEAD requests now resolve in one pass instead of three separate `findRoute()` calls
|
|
30
|
+
- 405 responses reuse allowed-methods collected during matching instead of a second full scan
|
|
31
|
+
- New internal `resolveRoute()` function combines matching, HEAD→GET fallback, and method collection
|
|
32
|
+
- No public API changes — purely internal performance improvement
|
|
33
|
+
|
|
34
|
+
## [0.1.1] - 2026-02-15
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Default error handler no longer leaks `error.message` in production (#44)
|
|
39
|
+
- Returns generic `"Internal Server Error"` when `NODE_ENV=production`
|
|
40
|
+
- Full error message still shown in development and test modes
|
|
41
|
+
|
|
42
|
+
### Removed
|
|
43
|
+
|
|
44
|
+
- Removed internal `Route` type from public exports — use `RouteInfo` for route metadata (#45)
|
|
45
|
+
|
|
46
|
+
## [0.1.0] - 2026-01-31
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- First minor release — API stable for development use until 1.0.0
|
|
51
|
+
|
|
8
52
|
## [0.0.11] - 2026-01-29
|
|
9
53
|
|
|
10
54
|
### Removed
|
package/README.md
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
# @bunary/http
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Part of the [Bunary](https://github.com/bunary-dev) ecosystem - a Bun-first backend platform inspired by Laravel.
|
|
6
|
-
|
|
7
|
-
## Documentation
|
|
8
|
-
|
|
9
|
-
Canonical documentation for this package lives in [`docs/index.md`](./docs/index.md).
|
|
10
|
-
|
|
11
|
-
## Features
|
|
12
|
-
|
|
13
|
-
- 🚀 **Bun-native** - Uses `Bun.serve()` directly, no Node.js compatibility layer
|
|
14
|
-
- 📦 **Zero dependencies** - No runtime dependencies
|
|
15
|
-
- 🔒 **Type-safe** - Full TypeScript support with strict types
|
|
16
|
-
- ⚡ **Fast** - Minimal overhead, direct routing
|
|
17
|
-
- 🧩 **Simple API** - Chainable route registration with automatic JSON serialization
|
|
18
|
-
- 📂 **Route Groups** - Organize routes with shared prefixes, middleware, and name prefixes
|
|
19
|
-
- 🏷️ **Named Routes** - URL generation with route names
|
|
20
|
-
- ✅ **Route Constraints** - Validate parameters with regex patterns
|
|
21
|
-
- ❓ **Optional Parameters** - Flexible routes with optional path segments
|
|
3
|
+
Lightweight, type-safe HTTP framework for [Bun](https://bun.sh). Routes, middleware, groups, named routes, constraints. Full reference: [docs/index.md](./docs/index.md).
|
|
22
4
|
|
|
23
5
|
## Installation
|
|
24
6
|
|
|
@@ -32,490 +14,11 @@ bun add @bunary/http
|
|
|
32
14
|
import { createApp } from '@bunary/http';
|
|
33
15
|
|
|
34
16
|
const app = createApp();
|
|
35
|
-
|
|
36
17
|
app.get('/hello', () => ({ message: 'Hello, Bun!' }));
|
|
37
|
-
|
|
38
18
|
app.listen({ port: 3000 });
|
|
39
19
|
```
|
|
40
20
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
### `createApp(options?)`
|
|
44
|
-
|
|
45
|
-
Creates a new Bunary application instance.
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
import { createApp } from '@bunary/http';
|
|
49
|
-
|
|
50
|
-
// Without basePath
|
|
51
|
-
const app = createApp();
|
|
52
|
-
|
|
53
|
-
// With basePath (prefixes all routes)
|
|
54
|
-
const apiApp = createApp({ basePath: '/api' });
|
|
55
|
-
apiApp.get('/users', () => ({})); // Matches /api/users
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
**Options:**
|
|
59
|
-
- `basePath` - Optional base path prefix for all routes (useful when mounting behind a reverse proxy)
|
|
60
|
-
- Automatically normalized (leading slash added, trailing slash removed)
|
|
61
|
-
- Composes with route groups: `basePath + group prefix + route path`
|
|
62
|
-
- Included in `app.route()` URL generation
|
|
63
|
-
- `onNotFound` - Custom handler for 404 Not Found responses
|
|
64
|
-
- Called when no route matches the request path
|
|
65
|
-
- Receives `RequestContext` (params empty, query available)
|
|
66
|
-
- Can return `Response` or `HandlerResponse`
|
|
67
|
-
- `onMethodNotAllowed` - Custom handler for 405 Method Not Allowed responses
|
|
68
|
-
- Called when a route matches the path but not the HTTP method
|
|
69
|
-
- Receives `RequestContext` and array of allowed methods
|
|
70
|
-
- Can return `Response` or `HandlerResponse`
|
|
71
|
-
- `Allow` header is automatically added if not present
|
|
72
|
-
- `onError` - Custom handler for 500 Internal Server Error responses
|
|
73
|
-
- Called when a route handler or middleware throws an error
|
|
74
|
-
- Receives `RequestContext` and the error object
|
|
75
|
-
- Can return `Response` or `HandlerResponse`
|
|
76
|
-
|
|
77
|
-
**Example with custom error handlers:**
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
const app = createApp({
|
|
81
|
-
basePath: '/api',
|
|
82
|
-
onNotFound: (ctx) => {
|
|
83
|
-
return new Response('Not Found', { status: 404 });
|
|
84
|
-
},
|
|
85
|
-
onMethodNotAllowed: (ctx, allowed) => {
|
|
86
|
-
return new Response(
|
|
87
|
-
JSON.stringify({ error: 'Method not allowed', allowed }),
|
|
88
|
-
{ status: 405, headers: { 'Content-Type': 'application/json' } }
|
|
89
|
-
);
|
|
90
|
-
},
|
|
91
|
-
onError: (ctx, error) => {
|
|
92
|
-
console.error('Request error:', error);
|
|
93
|
-
return new Response('Internal Server Error', { status: 500 });
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Route Registration
|
|
99
|
-
|
|
100
|
-
Register routes using chainable HTTP method helpers:
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
app
|
|
104
|
-
.get('/users', () => ({ users: [] }))
|
|
105
|
-
.post('/users', async (ctx) => {
|
|
106
|
-
const body = await ctx.request.json();
|
|
107
|
-
return { id: 1, ...body };
|
|
108
|
-
})
|
|
109
|
-
.put('/users/:id', (ctx) => {
|
|
110
|
-
return { id: ctx.params.id, updated: true };
|
|
111
|
-
})
|
|
112
|
-
.delete('/users/:id', (ctx) => {
|
|
113
|
-
return { deleted: ctx.params.id };
|
|
114
|
-
})
|
|
115
|
-
.patch('/users/:id', (ctx) => {
|
|
116
|
-
return { patched: ctx.params.id };
|
|
117
|
-
});
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Path Parameters
|
|
121
|
-
|
|
122
|
-
Path parameters are extracted automatically:
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
app.get('/users/:id', (ctx) => {
|
|
126
|
-
return { userId: ctx.params.id };
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
app.get('/posts/:postId/comments/:commentId', (ctx) => {
|
|
130
|
-
const { postId, commentId } = ctx.params;
|
|
131
|
-
return { postId, commentId };
|
|
132
|
-
});
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Query Parameters
|
|
136
|
-
|
|
137
|
-
Query parameters are accessed via `URLSearchParams` API:
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
app.get('/search', (ctx) => {
|
|
141
|
-
const q = ctx.query.get('q');
|
|
142
|
-
const page = ctx.query.get('page');
|
|
143
|
-
const limit = ctx.query.get('limit');
|
|
144
|
-
return { query: q, page, limit };
|
|
145
|
-
});
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
For multi-value query parameters (e.g., `?tag=a&tag=b`), use `getAll()`:
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
app.get('/filter', (ctx) => {
|
|
152
|
-
const tags = ctx.query.getAll('tag');
|
|
153
|
-
return { tags };
|
|
154
|
-
});
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Request Context
|
|
158
|
-
|
|
159
|
-
Route handlers receive a `RequestContext` object:
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
interface RequestContext {
|
|
163
|
-
request: Request; // Original Bun Request object
|
|
164
|
-
params: Record<string, string>; // Path parameters
|
|
165
|
-
query: URLSearchParams; // Query parameters (use .get() and .getAll())
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### HTTP Method Handling
|
|
170
|
-
|
|
171
|
-
#### HEAD Requests
|
|
172
|
-
|
|
173
|
-
HEAD requests are automatically handled for GET routes. They return the same status code and headers as the corresponding GET request, but with an empty body:
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
app.get('/users', () => ({ users: [] }));
|
|
177
|
-
|
|
178
|
-
// HEAD /users returns 200 with empty body
|
|
179
|
-
// Preserves all headers from GET handler
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
#### OPTIONS Requests
|
|
183
|
-
|
|
184
|
-
OPTIONS requests return `204 No Content` with an `Allow` header listing all permitted methods for the path:
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
app.get('/users', () => ({}));
|
|
188
|
-
app.post('/users', () => ({}));
|
|
189
|
-
app.delete('/users', () => ({}));
|
|
190
|
-
|
|
191
|
-
// OPTIONS /users returns:
|
|
192
|
-
// Status: 204
|
|
193
|
-
// Allow: DELETE, GET, POST
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
If no route matches the path, OPTIONS returns `404`.
|
|
197
|
-
|
|
198
|
-
#### Method Not Allowed (405)
|
|
199
|
-
|
|
200
|
-
When a path exists but the requested method is not allowed, the response includes an `Allow` header:
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
app.get('/users', () => ({}));
|
|
204
|
-
app.post('/users', () => ({}));
|
|
205
|
-
|
|
206
|
-
// PUT /users returns:
|
|
207
|
-
// Status: 405 Method Not Allowed
|
|
208
|
-
// Allow: GET, POST
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Response Handling
|
|
212
|
-
|
|
213
|
-
Handlers can return various types - they're automatically serialized:
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
// Objects/Arrays → JSON with Content-Type: application/json
|
|
217
|
-
app.get('/json', () => ({ data: 'value' }));
|
|
218
|
-
|
|
219
|
-
// Strings → text/plain
|
|
220
|
-
app.get('/text', () => 'Hello, world!');
|
|
221
|
-
|
|
222
|
-
// Response objects passed through unchanged
|
|
223
|
-
app.get('/custom', () => new Response('Custom', { status: 201 }));
|
|
224
|
-
|
|
225
|
-
// null/undefined → 204 No Content
|
|
226
|
-
app.get('/empty', () => null);
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Starting the Server
|
|
230
|
-
|
|
231
|
-
Both object and positional forms are supported:
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
// Object form (recommended)
|
|
235
|
-
const server = app.listen({ port: 3000, hostname: 'localhost' });
|
|
236
|
-
|
|
237
|
-
// Positional form
|
|
238
|
-
const server = app.listen(3000, 'localhost');
|
|
239
|
-
|
|
240
|
-
console.log(`Server running on ${server.hostname}:${server.port}`);
|
|
241
|
-
|
|
242
|
-
// Stop the server when done
|
|
243
|
-
server.stop();
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Testing Without Server
|
|
247
|
-
|
|
248
|
-
Use `app.fetch()` to test handlers directly:
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
const app = createApp();
|
|
252
|
-
app.get('/hello', () => ({ message: 'hi' }));
|
|
253
|
-
|
|
254
|
-
const response = await app.fetch(new Request('http://localhost/hello'));
|
|
255
|
-
const data = await response.json();
|
|
256
|
-
// { message: 'hi' }
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
## Middleware
|
|
260
|
-
|
|
261
|
-
Add middleware to handle cross-cutting concerns like logging, authentication, and error handling.
|
|
262
|
-
|
|
263
|
-
### Basic Middleware
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
// Logging middleware
|
|
267
|
-
app.use(async (ctx, next) => {
|
|
268
|
-
const start = Date.now();
|
|
269
|
-
const result = await next();
|
|
270
|
-
console.log(`${ctx.request.method} ${new URL(ctx.request.url).pathname} - ${Date.now() - start}ms`);
|
|
271
|
-
return result;
|
|
272
|
-
});
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Middleware Chain
|
|
276
|
-
|
|
277
|
-
Middleware executes in registration order. Each middleware can:
|
|
278
|
-
- Run code before calling `next()`
|
|
279
|
-
- Call `next()` to continue the chain
|
|
280
|
-
- Run code after `next()` returns
|
|
281
|
-
- Return early without calling `next()`
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
app
|
|
285
|
-
.use(async (ctx, next) => {
|
|
286
|
-
console.log('First - before');
|
|
287
|
-
const result = await next();
|
|
288
|
-
console.log('First - after');
|
|
289
|
-
return result;
|
|
290
|
-
})
|
|
291
|
-
.use(async (ctx, next) => {
|
|
292
|
-
console.log('Second - before');
|
|
293
|
-
const result = await next();
|
|
294
|
-
console.log('Second - after');
|
|
295
|
-
return result;
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// Output order: First-before, Second-before, handler, Second-after, First-after
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### Error Handling Middleware
|
|
302
|
-
|
|
303
|
-
```typescript
|
|
304
|
-
app.use(async (ctx, next) => {
|
|
305
|
-
try {
|
|
306
|
-
return await next();
|
|
307
|
-
} catch (error) {
|
|
308
|
-
console.error('Error:', error);
|
|
309
|
-
return new Response(JSON.stringify({ error: error.message }), {
|
|
310
|
-
status: 500,
|
|
311
|
-
headers: { 'Content-Type': 'application/json' }
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Auth Middleware (Example)
|
|
318
|
-
|
|
319
|
-
```typescript
|
|
320
|
-
app.use(async (ctx, next) => {
|
|
321
|
-
const token = ctx.request.headers.get('Authorization');
|
|
322
|
-
if (!token) {
|
|
323
|
-
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
|
|
324
|
-
}
|
|
325
|
-
// Validate token...
|
|
326
|
-
return await next();
|
|
327
|
-
});
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
## Route Groups
|
|
331
|
-
|
|
332
|
-
Group routes together with shared prefixes, middleware, and name prefixes.
|
|
333
|
-
|
|
334
|
-
### Basic Groups
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
// Simple prefix
|
|
338
|
-
app.group('/api', (router) => {
|
|
339
|
-
router.get('/users', () => ({ users: [] })); // /api/users
|
|
340
|
-
router.get('/posts', () => ({ posts: [] })); // /api/posts
|
|
341
|
-
});
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### Groups with Options
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
// Auth middleware for protected routes
|
|
348
|
-
const authMiddleware = async (ctx, next) => {
|
|
349
|
-
const token = ctx.request.headers.get('Authorization');
|
|
350
|
-
if (!token) return new Response('Unauthorized', { status: 401 });
|
|
351
|
-
return await next();
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
app.group({
|
|
355
|
-
prefix: '/admin',
|
|
356
|
-
middleware: [authMiddleware],
|
|
357
|
-
name: 'admin.'
|
|
358
|
-
}, (router) => {
|
|
359
|
-
router.get('/dashboard', () => ({})).name('dashboard'); // name: admin.dashboard
|
|
360
|
-
router.get('/users', () => ({})).name('users'); // name: admin.users
|
|
361
|
-
});
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Nested Groups
|
|
365
|
-
|
|
366
|
-
```typescript
|
|
367
|
-
app.group('/api', (api) => {
|
|
368
|
-
api.group('/v1', (v1) => {
|
|
369
|
-
v1.get('/users', () => ({})); // /api/v1/users
|
|
370
|
-
});
|
|
371
|
-
api.group('/v2', (v2) => {
|
|
372
|
-
v2.get('/users', () => ({})); // /api/v2/users
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
## Named Routes
|
|
378
|
-
|
|
379
|
-
Assign names to routes for URL generation.
|
|
380
|
-
|
|
381
|
-
### Naming Routes
|
|
382
|
-
|
|
383
|
-
```typescript
|
|
384
|
-
app.get('/users/:id', (ctx) => ({})).name('users.show');
|
|
385
|
-
app.get('/posts/:slug', (ctx) => ({})).name('posts.show');
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### Generating URLs
|
|
389
|
-
|
|
390
|
-
```typescript
|
|
391
|
-
// Basic URL generation
|
|
392
|
-
const url = app.route('users.show', { id: 42 });
|
|
393
|
-
// "/users/42"
|
|
394
|
-
|
|
395
|
-
// With query string
|
|
396
|
-
const searchUrl = app.route('users.show', { id: 42, tab: 'profile' });
|
|
397
|
-
// "/users/42?tab=profile"
|
|
398
|
-
|
|
399
|
-
// Check if route exists
|
|
400
|
-
if (app.hasRoute('users.show')) {
|
|
401
|
-
// ...
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// List all routes
|
|
405
|
-
const routes = app.getRoutes();
|
|
406
|
-
// [{ name: 'users.show', method: 'GET', path: '/users/:id' }, ...]
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
## Route Constraints
|
|
410
|
-
|
|
411
|
-
Add regex constraints to validate route parameters.
|
|
412
|
-
|
|
413
|
-
### Basic Constraints
|
|
414
|
-
|
|
415
|
-
```typescript
|
|
416
|
-
// Only match if :id is numeric
|
|
417
|
-
app.get('/users/:id', (ctx) => ({}))
|
|
418
|
-
.where('id', /^\d+$/);
|
|
419
|
-
|
|
420
|
-
// Using string pattern
|
|
421
|
-
app.get('/posts/:slug', (ctx) => ({}))
|
|
422
|
-
.where('slug', '^[a-z0-9-]+$');
|
|
423
|
-
|
|
424
|
-
// Multiple constraints
|
|
425
|
-
app.get('/users/:id/posts/:postId', (ctx) => ({}))
|
|
426
|
-
.where({ id: /^\d+$/, postId: /^\d+$/ });
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### Helper Methods
|
|
430
|
-
|
|
431
|
-
```typescript
|
|
432
|
-
// whereNumber - digits only
|
|
433
|
-
app.get('/users/:id', () => ({})).whereNumber('id');
|
|
434
|
-
|
|
435
|
-
// whereAlpha - letters only (a-zA-Z)
|
|
436
|
-
app.get('/categories/:name', () => ({})).whereAlpha('name');
|
|
437
|
-
|
|
438
|
-
// whereAlphaNumeric - letters and digits
|
|
439
|
-
app.get('/codes/:code', () => ({})).whereAlphaNumeric('code');
|
|
440
|
-
|
|
441
|
-
// whereUuid - UUID format
|
|
442
|
-
app.get('/items/:uuid', () => ({})).whereUuid('uuid');
|
|
443
|
-
|
|
444
|
-
// whereUlid - ULID format
|
|
445
|
-
app.get('/records/:ulid', () => ({})).whereUlid('ulid');
|
|
446
|
-
|
|
447
|
-
// whereIn - specific allowed values
|
|
448
|
-
app.get('/status/:status', () => ({})).whereIn('status', ['active', 'pending', 'archived']);
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
### Chaining Constraints
|
|
452
|
-
|
|
453
|
-
```typescript
|
|
454
|
-
app.get('/users/:id/posts/:slug', (ctx) => ({}))
|
|
455
|
-
.whereNumber('id')
|
|
456
|
-
.whereAlpha('slug')
|
|
457
|
-
.name('users.posts');
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
## Optional Parameters
|
|
461
|
-
|
|
462
|
-
Use `?` to mark route parameters as optional.
|
|
463
|
-
|
|
464
|
-
```typescript
|
|
465
|
-
// :id is optional
|
|
466
|
-
app.get('/users/:id?', (ctx) => {
|
|
467
|
-
if (ctx.params.id) {
|
|
468
|
-
return { user: ctx.params.id };
|
|
469
|
-
}
|
|
470
|
-
return { users: [] };
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
// Multiple optional params
|
|
474
|
-
app.get('/archive/:year?/:month?', (ctx) => {
|
|
475
|
-
const { year, month } = ctx.params;
|
|
476
|
-
// year and month may be undefined
|
|
477
|
-
return { year, month };
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// Constraints work with optional params
|
|
481
|
-
app.get('/posts/:id?', (ctx) => ({})).whereNumber('id');
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
## Error Handling
|
|
485
|
-
|
|
486
|
-
Uncaught errors in handlers return a 500 response with the error message:
|
|
487
|
-
|
|
488
|
-
```typescript
|
|
489
|
-
app.get('/error', () => {
|
|
490
|
-
throw new Error('Something went wrong');
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
// Returns: 500 Internal Server Error
|
|
494
|
-
// Body: { error: "Something went wrong" }
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
## Types
|
|
498
|
-
|
|
499
|
-
All types are exported for TypeScript users:
|
|
500
|
-
|
|
501
|
-
```typescript
|
|
502
|
-
import type {
|
|
503
|
-
BunaryApp,
|
|
504
|
-
BunaryServer,
|
|
505
|
-
RequestContext,
|
|
506
|
-
RouteHandler,
|
|
507
|
-
Middleware,
|
|
508
|
-
RouteBuilder,
|
|
509
|
-
GroupOptions,
|
|
510
|
-
GroupRouter,
|
|
511
|
-
GroupCallback,
|
|
512
|
-
RouteInfo
|
|
513
|
-
} from '@bunary/http';
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
## Requirements
|
|
517
|
-
|
|
518
|
-
- Bun ≥ 1.0.0
|
|
21
|
+
For createApp options, route groups, middleware, named routes, and types, see [docs/index.md](./docs/index.md).
|
|
519
22
|
|
|
520
23
|
## License
|
|
521
24
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CA+OzD"}
|
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" }
|
|
@@ -181,6 +182,13 @@ function compilePath(path) {
|
|
|
181
182
|
optionalParams
|
|
182
183
|
};
|
|
183
184
|
}
|
|
185
|
+
function safeDecodeURIComponent(value) {
|
|
186
|
+
try {
|
|
187
|
+
return decodeURIComponent(value);
|
|
188
|
+
} catch {
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
184
192
|
function extractParams(path, route) {
|
|
185
193
|
const match = path.match(route.pattern);
|
|
186
194
|
if (!match)
|
|
@@ -189,7 +197,7 @@ function extractParams(path, route) {
|
|
|
189
197
|
for (let i = 0;i < route.paramNames.length; i++) {
|
|
190
198
|
const value = match[i + 1];
|
|
191
199
|
if (value !== undefined && value !== "") {
|
|
192
|
-
params[route.paramNames[i]] = value;
|
|
200
|
+
params[route.paramNames[i]] = safeDecodeURIComponent(value);
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
return params;
|
|
@@ -208,27 +216,30 @@ function checkConstraints(params, constraints) {
|
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
// src/routes/find.ts
|
|
211
|
-
function
|
|
219
|
+
function resolveRoute(routes, method, path) {
|
|
220
|
+
let getFallback = null;
|
|
221
|
+
const allowedMethods = new Set;
|
|
212
222
|
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
223
|
if (!route.pattern.test(path))
|
|
228
|
-
|
|
224
|
+
continue;
|
|
229
225
|
const params = extractParams(path, route);
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
if (!checkConstraints(params, route.constraints))
|
|
227
|
+
continue;
|
|
228
|
+
allowedMethods.add(route.method);
|
|
229
|
+
if (route.method === method) {
|
|
230
|
+
return { match: { route, params }, allowedMethods: [] };
|
|
231
|
+
}
|
|
232
|
+
if (method === "HEAD" && route.method === "GET" && !getFallback) {
|
|
233
|
+
getFallback = { route, params };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (getFallback) {
|
|
237
|
+
return { match: getFallback, allowedMethods: [] };
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
match: null,
|
|
241
|
+
allowedMethods: Array.from(allowedMethods).sort()
|
|
242
|
+
};
|
|
232
243
|
}
|
|
233
244
|
function getAllowedMethods(routes, path) {
|
|
234
245
|
const methods = new Set;
|
|
@@ -262,6 +273,9 @@ function joinPaths(prefix, path) {
|
|
|
262
273
|
if (normalizedPath === "/") {
|
|
263
274
|
return normalizedPrefix;
|
|
264
275
|
}
|
|
276
|
+
if (normalizedPrefix === "/") {
|
|
277
|
+
return normalizedPath;
|
|
278
|
+
}
|
|
265
279
|
return normalizedPrefix + normalizedPath;
|
|
266
280
|
}
|
|
267
281
|
|
|
@@ -302,20 +316,6 @@ function createGroupRouter(prefix, groupMiddleware, namePrefix, addRoute) {
|
|
|
302
316
|
return router;
|
|
303
317
|
}
|
|
304
318
|
// 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
319
|
function toHeadResponse(response) {
|
|
320
320
|
return new Response(null, {
|
|
321
321
|
status: response.status,
|
|
@@ -324,9 +324,9 @@ function toHeadResponse(response) {
|
|
|
324
324
|
});
|
|
325
325
|
}
|
|
326
326
|
// src/handlers/methodNotAllowed.ts
|
|
327
|
-
async function handleMethodNotAllowed(request, path, routes, options) {
|
|
327
|
+
async function handleMethodNotAllowed(request, path, routes, options, precomputed) {
|
|
328
328
|
const url = new URL(request.url);
|
|
329
|
-
const allowedMethods = getAllowedMethods(routes, path);
|
|
329
|
+
const allowedMethods = precomputed ?? getAllowedMethods(routes, path);
|
|
330
330
|
const methodNotAllowedCtx = {
|
|
331
331
|
request,
|
|
332
332
|
params: {},
|
|
@@ -376,8 +376,8 @@ async function handleNotFound(request, _path, options) {
|
|
|
376
376
|
}
|
|
377
377
|
// src/handlers/options.ts
|
|
378
378
|
async function handleOptions(request, path, routes, options) {
|
|
379
|
-
|
|
380
|
-
|
|
379
|
+
const allowedMethods = getAllowedMethods(routes, path);
|
|
380
|
+
if (allowedMethods.length > 0) {
|
|
381
381
|
return new Response(null, {
|
|
382
382
|
status: 204,
|
|
383
383
|
headers: { Allow: allowedMethods.join(", ") }
|
|
@@ -439,11 +439,10 @@ function createApp(options) {
|
|
|
439
439
|
if (method === "OPTIONS") {
|
|
440
440
|
return await handleOptions(request, path, routes, options);
|
|
441
441
|
}
|
|
442
|
-
const
|
|
443
|
-
const match = findRoute(routes, actualMethod, path);
|
|
442
|
+
const { match, allowedMethods } = resolveRoute(routes, method, path);
|
|
444
443
|
if (!match) {
|
|
445
|
-
if (
|
|
446
|
-
return await handleMethodNotAllowed(request, path, routes, options);
|
|
444
|
+
if (allowedMethods.length > 0) {
|
|
445
|
+
return await handleMethodNotAllowed(request, path, routes, options, allowedMethods);
|
|
447
446
|
}
|
|
448
447
|
return await handleNotFound(request, path, options);
|
|
449
448
|
}
|
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
|
@@ -32,10 +32,11 @@ export declare function compilePath(path: string): CompiledPath;
|
|
|
32
32
|
/**
|
|
33
33
|
* Extract path parameters from a matched route.
|
|
34
34
|
* Handles optional parameters by only including them if they have values.
|
|
35
|
+
* Applies `decodeURIComponent` to each captured value (standard behaviour).
|
|
35
36
|
*
|
|
36
37
|
* @param path - The request path
|
|
37
38
|
* @param route - The matched route
|
|
38
|
-
* @returns Record of parameter names to values (undefined for missing optional params)
|
|
39
|
+
* @returns Record of parameter names to decoded values (undefined for missing optional params)
|
|
39
40
|
*/
|
|
40
41
|
export declare function extractParams(path: string, route: Route): Record<string, string | undefined>;
|
|
41
42
|
/**
|
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;CACzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAmCtD;
|
|
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"}
|
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"}
|
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"}
|