@bunary/http 0.0.2 → 0.0.5
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 +40 -0
- package/README.md +170 -3
- package/dist/app.d.ts +7 -25
- package/dist/app.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +344 -42
- package/dist/pathUtils.d.ts +34 -0
- package/dist/pathUtils.d.ts.map +1 -0
- package/dist/response.d.ts +26 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/router.d.ts +49 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/routes/builder.d.ts +17 -0
- package/dist/routes/builder.d.ts.map +1 -0
- package/dist/routes/find.d.ts +27 -0
- package/dist/routes/find.d.ts.map +1 -0
- package/dist/routes/group.d.ts +7 -0
- package/dist/routes/group.d.ts.map +1 -0
- package/dist/routes/index.d.ts +4 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/types/appOptions.d.ts +8 -0
- package/dist/types/appOptions.d.ts.map +1 -0
- package/dist/types/bunaryApp.d.ts +97 -0
- package/dist/types/bunaryApp.d.ts.map +1 -0
- package/dist/types/bunaryServer.d.ts +14 -0
- package/dist/types/bunaryServer.d.ts.map +1 -0
- package/dist/types/groupOptions.d.ts +13 -0
- package/dist/types/groupOptions.d.ts.map +1 -0
- package/dist/types/groupRouter.d.ts +26 -0
- package/dist/types/groupRouter.d.ts.map +1 -0
- package/dist/types/handlerResponse.d.ts +8 -0
- package/dist/types/handlerResponse.d.ts.map +1 -0
- package/dist/types/httpMethod.d.ts +5 -0
- package/dist/types/httpMethod.d.ts.map +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/middleware.d.ts +21 -0
- package/dist/types/middleware.d.ts.map +1 -0
- package/dist/types/pathParams.d.ts +6 -0
- package/dist/types/pathParams.d.ts.map +1 -0
- package/dist/types/queryParams.d.ts +5 -0
- package/dist/types/queryParams.d.ts.map +1 -0
- package/dist/types/requestContext.d.ts +36 -0
- package/dist/types/requestContext.d.ts.map +1 -0
- package/dist/types/route.d.ts +27 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/routeBuilder.d.ts +24 -0
- package/dist/types/routeBuilder.d.ts.map +1 -0
- package/dist/types/routeHandler.d.ts +17 -0
- package/dist/types/routeHandler.d.ts.map +1 -0
- package/dist/types/routeInfo.d.ts +13 -0
- package/dist/types/routeInfo.d.ts.map +1 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,46 @@ 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.0.5] - 2026-01-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `RequestContext.locals` for per-request middleware state (isolated between concurrent requests)
|
|
13
|
+
|
|
14
|
+
## [0.0.4] - 2026-01-26
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Optional route parameters using `:param?` syntax
|
|
19
|
+
- Routes can match with or without optional parameters
|
|
20
|
+
- Optional params are `undefined` in `ctx.params` when not provided
|
|
21
|
+
- Supports multiple optional parameters in a single route
|
|
22
|
+
- Works with route constraints
|
|
23
|
+
- Comprehensive test suite for optional parameters
|
|
24
|
+
|
|
25
|
+
## [0.0.3] - 2026-01-26
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Route Groups with `app.group()` method
|
|
30
|
+
- Prefix routes with shared path prefix
|
|
31
|
+
- Apply middleware to groups of routes
|
|
32
|
+
- Add name prefixes for named routes in groups
|
|
33
|
+
- Support nested groups
|
|
34
|
+
- Named Routes with `.name()` method
|
|
35
|
+
- Assign names to routes for URL generation
|
|
36
|
+
- Generate URLs with `app.route(name, params)`
|
|
37
|
+
- Check route existence with `app.hasRoute(name)`
|
|
38
|
+
- List all routes with `app.getRoutes()`
|
|
39
|
+
- Support query string parameters in URL generation
|
|
40
|
+
- Route Constraints with `.where()` method
|
|
41
|
+
- Validate route parameters with regex patterns
|
|
42
|
+
- Helper methods: `whereNumber()`, `whereAlpha()`, `whereAlphaNumeric()`, `whereUuid()`, `whereUlid()`, `whereIn()`
|
|
43
|
+
- Support string or RegExp patterns
|
|
44
|
+
- Multiple constraints per route
|
|
45
|
+
- Constraints work with optional parameters
|
|
46
|
+
- Comprehensive test suites for groups, named routes, and constraints
|
|
47
|
+
|
|
8
48
|
## [0.0.2] - 2026-01-24
|
|
9
49
|
|
|
10
50
|
### Added
|
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ A lightweight, type-safe HTTP framework built exclusively for [Bun](https://bun.
|
|
|
4
4
|
|
|
5
5
|
Part of the [Bunary](https://github.com/bunary-dev) ecosystem - a Bun-first backend platform inspired by Laravel.
|
|
6
6
|
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
Canonical documentation for this package lives in [`docs/index.md`](./docs/index.md).
|
|
10
|
+
|
|
7
11
|
## Features
|
|
8
12
|
|
|
9
13
|
- 🚀 **Bun-native** - Uses `Bun.serve()` directly, no Node.js compatibility layer
|
|
@@ -11,6 +15,10 @@ Part of the [Bunary](https://github.com/bunary-dev) ecosystem - a Bun-first back
|
|
|
11
15
|
- 🔒 **Type-safe** - Full TypeScript support with strict types
|
|
12
16
|
- ⚡ **Fast** - Minimal overhead, direct routing
|
|
13
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
|
|
14
22
|
|
|
15
23
|
## Installation
|
|
16
24
|
|
|
@@ -215,9 +223,163 @@ app.use(async (ctx, next) => {
|
|
|
215
223
|
});
|
|
216
224
|
```
|
|
217
225
|
|
|
226
|
+
## Route Groups
|
|
227
|
+
|
|
228
|
+
Group routes together with shared prefixes, middleware, and name prefixes.
|
|
229
|
+
|
|
230
|
+
### Basic Groups
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// Simple prefix
|
|
234
|
+
app.group('/api', (router) => {
|
|
235
|
+
router.get('/users', () => ({ users: [] })); // /api/users
|
|
236
|
+
router.get('/posts', () => ({ posts: [] })); // /api/posts
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Groups with Options
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// Auth middleware for protected routes
|
|
244
|
+
const authMiddleware = async (ctx, next) => {
|
|
245
|
+
const token = ctx.request.headers.get('Authorization');
|
|
246
|
+
if (!token) return new Response('Unauthorized', { status: 401 });
|
|
247
|
+
return await next();
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
app.group({
|
|
251
|
+
prefix: '/admin',
|
|
252
|
+
middleware: [authMiddleware],
|
|
253
|
+
name: 'admin.'
|
|
254
|
+
}, (router) => {
|
|
255
|
+
router.get('/dashboard', () => ({})).name('dashboard'); // name: admin.dashboard
|
|
256
|
+
router.get('/users', () => ({})).name('users'); // name: admin.users
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Nested Groups
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
app.group('/api', (api) => {
|
|
264
|
+
api.group('/v1', (v1) => {
|
|
265
|
+
v1.get('/users', () => ({})); // /api/v1/users
|
|
266
|
+
});
|
|
267
|
+
api.group('/v2', (v2) => {
|
|
268
|
+
v2.get('/users', () => ({})); // /api/v2/users
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Named Routes
|
|
274
|
+
|
|
275
|
+
Assign names to routes for URL generation.
|
|
276
|
+
|
|
277
|
+
### Naming Routes
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
app.get('/users/:id', (ctx) => ({})).name('users.show');
|
|
281
|
+
app.get('/posts/:slug', (ctx) => ({})).name('posts.show');
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Generating URLs
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// Basic URL generation
|
|
288
|
+
const url = app.route('users.show', { id: 42 });
|
|
289
|
+
// "/users/42"
|
|
290
|
+
|
|
291
|
+
// With query string
|
|
292
|
+
const searchUrl = app.route('users.show', { id: 42, tab: 'profile' });
|
|
293
|
+
// "/users/42?tab=profile"
|
|
294
|
+
|
|
295
|
+
// Check if route exists
|
|
296
|
+
if (app.hasRoute('users.show')) {
|
|
297
|
+
// ...
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// List all routes
|
|
301
|
+
const routes = app.getRoutes();
|
|
302
|
+
// [{ name: 'users.show', method: 'GET', path: '/users/:id' }, ...]
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Route Constraints
|
|
306
|
+
|
|
307
|
+
Add regex constraints to validate route parameters.
|
|
308
|
+
|
|
309
|
+
### Basic Constraints
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// Only match if :id is numeric
|
|
313
|
+
app.get('/users/:id', (ctx) => ({}))
|
|
314
|
+
.where('id', /^\d+$/);
|
|
315
|
+
|
|
316
|
+
// Using string pattern
|
|
317
|
+
app.get('/posts/:slug', (ctx) => ({}))
|
|
318
|
+
.where('slug', '^[a-z0-9-]+$');
|
|
319
|
+
|
|
320
|
+
// Multiple constraints
|
|
321
|
+
app.get('/users/:id/posts/:postId', (ctx) => ({}))
|
|
322
|
+
.where({ id: /^\d+$/, postId: /^\d+$/ });
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Helper Methods
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// whereNumber - digits only
|
|
329
|
+
app.get('/users/:id', () => ({})).whereNumber('id');
|
|
330
|
+
|
|
331
|
+
// whereAlpha - letters only (a-zA-Z)
|
|
332
|
+
app.get('/categories/:name', () => ({})).whereAlpha('name');
|
|
333
|
+
|
|
334
|
+
// whereAlphaNumeric - letters and digits
|
|
335
|
+
app.get('/codes/:code', () => ({})).whereAlphaNumeric('code');
|
|
336
|
+
|
|
337
|
+
// whereUuid - UUID format
|
|
338
|
+
app.get('/items/:uuid', () => ({})).whereUuid('uuid');
|
|
339
|
+
|
|
340
|
+
// whereUlid - ULID format
|
|
341
|
+
app.get('/records/:ulid', () => ({})).whereUlid('ulid');
|
|
342
|
+
|
|
343
|
+
// whereIn - specific allowed values
|
|
344
|
+
app.get('/status/:status', () => ({})).whereIn('status', ['active', 'pending', 'archived']);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Chaining Constraints
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
app.get('/users/:id/posts/:slug', (ctx) => ({}))
|
|
351
|
+
.whereNumber('id')
|
|
352
|
+
.whereAlpha('slug')
|
|
353
|
+
.name('users.posts');
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Optional Parameters
|
|
357
|
+
|
|
358
|
+
Use `?` to mark route parameters as optional.
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// :id is optional
|
|
362
|
+
app.get('/users/:id?', (ctx) => {
|
|
363
|
+
if (ctx.params.id) {
|
|
364
|
+
return { user: ctx.params.id };
|
|
365
|
+
}
|
|
366
|
+
return { users: [] };
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Multiple optional params
|
|
370
|
+
app.get('/archive/:year?/:month?', (ctx) => {
|
|
371
|
+
const { year, month } = ctx.params;
|
|
372
|
+
// year and month may be undefined
|
|
373
|
+
return { year, month };
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Constraints work with optional params
|
|
377
|
+
app.get('/posts/:id?', (ctx) => ({})).whereNumber('id');
|
|
378
|
+
```
|
|
379
|
+
|
|
218
380
|
## Error Handling
|
|
219
381
|
|
|
220
|
-
Uncaught errors in handlers return a 500 response:
|
|
382
|
+
Uncaught errors in handlers return a 500 response with the error message:
|
|
221
383
|
|
|
222
384
|
```typescript
|
|
223
385
|
app.get('/error', () => {
|
|
@@ -225,7 +387,7 @@ app.get('/error', () => {
|
|
|
225
387
|
});
|
|
226
388
|
|
|
227
389
|
// Returns: 500 Internal Server Error
|
|
228
|
-
// Body: { error: "
|
|
390
|
+
// Body: { error: "Something went wrong" }
|
|
229
391
|
```
|
|
230
392
|
|
|
231
393
|
## Types
|
|
@@ -238,7 +400,12 @@ import type {
|
|
|
238
400
|
BunaryServer,
|
|
239
401
|
RequestContext,
|
|
240
402
|
RouteHandler,
|
|
241
|
-
Middleware
|
|
403
|
+
Middleware,
|
|
404
|
+
RouteBuilder,
|
|
405
|
+
GroupOptions,
|
|
406
|
+
GroupRouter,
|
|
407
|
+
GroupCallback,
|
|
408
|
+
RouteInfo
|
|
242
409
|
} from '@bunary/http';
|
|
243
410
|
```
|
|
244
411
|
|
package/dist/app.d.ts
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Create a new Bunary HTTP application instance.
|
|
3
|
-
*
|
|
4
|
-
* @returns BunaryApp instance with routing and middleware support
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```ts
|
|
8
|
-
* import { createApp } from "@bunary/http";
|
|
9
|
-
*
|
|
10
|
-
* const app = createApp();
|
|
11
|
-
*
|
|
12
|
-
* app.get("/", () => ({ message: "Hello!" }));
|
|
13
|
-
* app.get("/users/:id", (ctx) => ({ id: ctx.params.id }));
|
|
14
|
-
*
|
|
15
|
-
* app.listen(3000);
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
import type { BunaryApp } from "./types.js";
|
|
1
|
+
import type { BunaryApp } from "./types/index.js";
|
|
19
2
|
/**
|
|
20
3
|
* Create a new Bunary HTTP application instance.
|
|
21
4
|
*
|
|
@@ -38,15 +21,14 @@ import type { BunaryApp } from "./types.js";
|
|
|
38
21
|
* return { id: ctx.params.id };
|
|
39
22
|
* });
|
|
40
23
|
*
|
|
41
|
-
* //
|
|
42
|
-
* app.
|
|
43
|
-
*
|
|
24
|
+
* // Route groups
|
|
25
|
+
* app.group("/api", (router) => {
|
|
26
|
+
* router.get("/users", () => ({ users: [] }));
|
|
44
27
|
* });
|
|
45
28
|
*
|
|
46
|
-
* //
|
|
47
|
-
* app.get("/
|
|
48
|
-
*
|
|
49
|
-
* });
|
|
29
|
+
* // Named routes
|
|
30
|
+
* app.get("/users/:id", (ctx) => ({ id: ctx.params.id })).name("users.show");
|
|
31
|
+
* const url = app.route("users.show", { id: 123 });
|
|
50
32
|
*
|
|
51
33
|
* app.listen(3000);
|
|
52
34
|
* ```
|
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":"AAQA,OAAO,KAAK,EACX,SAAS,EAYT,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAoOrC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
*
|
|
19
19
|
* @packageDocumentation
|
|
20
20
|
*/
|
|
21
|
-
export type { AppOptions, BunaryApp, BunaryServer, HandlerResponse, HttpMethod, Middleware, PathParams, QueryParams, RequestContext, Route, RouteHandler, } from "./types.js";
|
|
21
|
+
export type { AppOptions, BunaryApp, BunaryServer, GroupCallback, GroupOptions, GroupRouter, HandlerResponse, HttpMethod, Middleware, PathParams, QueryParams, RequestContext, Route, RouteBuilder, RouteHandler, RouteInfo, } from "./types/index.js";
|
|
22
22
|
export { createApp } from "./app.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,YAAY,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,eAAe,EACf,UAAU,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,KAAK,EACL,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,YAAY,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,eAAe,EACf,UAAU,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,SAAS,GACT,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC"}
|