@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +170 -3
  3. package/dist/app.d.ts +7 -25
  4. package/dist/app.d.ts.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +344 -42
  8. package/dist/pathUtils.d.ts +34 -0
  9. package/dist/pathUtils.d.ts.map +1 -0
  10. package/dist/response.d.ts +26 -0
  11. package/dist/response.d.ts.map +1 -0
  12. package/dist/router.d.ts +49 -0
  13. package/dist/router.d.ts.map +1 -0
  14. package/dist/routes/builder.d.ts +17 -0
  15. package/dist/routes/builder.d.ts.map +1 -0
  16. package/dist/routes/find.d.ts +27 -0
  17. package/dist/routes/find.d.ts.map +1 -0
  18. package/dist/routes/group.d.ts +7 -0
  19. package/dist/routes/group.d.ts.map +1 -0
  20. package/dist/routes/index.d.ts +4 -0
  21. package/dist/routes/index.d.ts.map +1 -0
  22. package/dist/types/appOptions.d.ts +8 -0
  23. package/dist/types/appOptions.d.ts.map +1 -0
  24. package/dist/types/bunaryApp.d.ts +97 -0
  25. package/dist/types/bunaryApp.d.ts.map +1 -0
  26. package/dist/types/bunaryServer.d.ts +14 -0
  27. package/dist/types/bunaryServer.d.ts.map +1 -0
  28. package/dist/types/groupOptions.d.ts +13 -0
  29. package/dist/types/groupOptions.d.ts.map +1 -0
  30. package/dist/types/groupRouter.d.ts +26 -0
  31. package/dist/types/groupRouter.d.ts.map +1 -0
  32. package/dist/types/handlerResponse.d.ts +8 -0
  33. package/dist/types/handlerResponse.d.ts.map +1 -0
  34. package/dist/types/httpMethod.d.ts +5 -0
  35. package/dist/types/httpMethod.d.ts.map +1 -0
  36. package/dist/types/index.d.ts +19 -0
  37. package/dist/types/index.d.ts.map +1 -0
  38. package/dist/types/middleware.d.ts +21 -0
  39. package/dist/types/middleware.d.ts.map +1 -0
  40. package/dist/types/pathParams.d.ts +6 -0
  41. package/dist/types/pathParams.d.ts.map +1 -0
  42. package/dist/types/queryParams.d.ts +5 -0
  43. package/dist/types/queryParams.d.ts.map +1 -0
  44. package/dist/types/requestContext.d.ts +36 -0
  45. package/dist/types/requestContext.d.ts.map +1 -0
  46. package/dist/types/route.d.ts +27 -0
  47. package/dist/types/route.d.ts.map +1 -0
  48. package/dist/types/routeBuilder.d.ts +24 -0
  49. package/dist/types/routeBuilder.d.ts.map +1 -0
  50. package/dist/types/routeHandler.d.ts +17 -0
  51. package/dist/types/routeHandler.d.ts.map +1 -0
  52. package/dist/types/routeInfo.d.ts +13 -0
  53. package/dist/types/routeInfo.d.ts.map +1 -0
  54. 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: "Internal Server 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
- * // Query parameters
42
- * app.get("/search", (ctx) => {
43
- * return { query: ctx.query.get("q") };
24
+ * // Route groups
25
+ * app.group("/api", (router) => {
26
+ * router.get("/users", () => ({ users: [] }));
44
27
  * });
45
28
  *
46
- * // Custom Response
47
- * app.get("/custom", () => {
48
- * return new Response("Custom", { status: 201 });
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":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EACX,SAAS,EAQT,MAAM,YAAY,CAAC;AA0FpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAuOrC"}
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
@@ -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,GACZ,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC"}
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"}