@bunary/http 0.0.1 → 0.0.4
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 +21 -1
- package/README.md +237 -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 +342 -41
- 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 +22 -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 +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,27 @@ 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.
|
|
8
|
+
## [0.0.2] - 2026-01-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Comprehensive middleware test suite (19 tests)
|
|
13
|
+
- Middleware documentation in README with examples:
|
|
14
|
+
- Basic logging middleware
|
|
15
|
+
- Error handling middleware
|
|
16
|
+
- Authentication middleware pattern
|
|
17
|
+
- Middleware chain execution order
|
|
18
|
+
|
|
19
|
+
### Verified
|
|
20
|
+
|
|
21
|
+
- Middleware pipeline executes in registration order (FR-016)
|
|
22
|
+
- `app.use(middleware)` adds middleware to pipeline (FR-015)
|
|
23
|
+
- Middleware can call `next()` for chain continuation
|
|
24
|
+
- Middleware can return early without calling `next()`
|
|
25
|
+
- Middleware can catch and handle errors from `next()`
|
|
26
|
+
- Middleware errors return 500 response
|
|
27
|
+
|
|
28
|
+
## [0.0.1] - 2026-01-24
|
|
9
29
|
|
|
10
30
|
### Added
|
|
11
31
|
|
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@ Part of the [Bunary](https://github.com/bunary-dev) ecosystem - a Bun-first back
|
|
|
11
11
|
- 🔒 **Type-safe** - Full TypeScript support with strict types
|
|
12
12
|
- ⚡ **Fast** - Minimal overhead, direct routing
|
|
13
13
|
- 🧩 **Simple API** - Chainable route registration with automatic JSON serialization
|
|
14
|
+
- 📂 **Route Groups** - Organize routes with shared prefixes, middleware, and name prefixes
|
|
15
|
+
- 🏷️ **Named Routes** - URL generation with route names
|
|
16
|
+
- ✅ **Route Constraints** - Validate parameters with regex patterns
|
|
17
|
+
- ❓ **Optional Parameters** - Flexible routes with optional path segments
|
|
14
18
|
|
|
15
19
|
## Installation
|
|
16
20
|
|
|
@@ -144,9 +148,234 @@ const data = await response.json();
|
|
|
144
148
|
// { message: 'hi' }
|
|
145
149
|
```
|
|
146
150
|
|
|
151
|
+
## Middleware
|
|
152
|
+
|
|
153
|
+
Add middleware to handle cross-cutting concerns like logging, authentication, and error handling.
|
|
154
|
+
|
|
155
|
+
### Basic Middleware
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Logging middleware
|
|
159
|
+
app.use(async (ctx, next) => {
|
|
160
|
+
const start = Date.now();
|
|
161
|
+
const result = await next();
|
|
162
|
+
console.log(`${ctx.request.method} ${new URL(ctx.request.url).pathname} - ${Date.now() - start}ms`);
|
|
163
|
+
return result;
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Middleware Chain
|
|
168
|
+
|
|
169
|
+
Middleware executes in registration order. Each middleware can:
|
|
170
|
+
- Run code before calling `next()`
|
|
171
|
+
- Call `next()` to continue the chain
|
|
172
|
+
- Run code after `next()` returns
|
|
173
|
+
- Return early without calling `next()`
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
app
|
|
177
|
+
.use(async (ctx, next) => {
|
|
178
|
+
console.log('First - before');
|
|
179
|
+
const result = await next();
|
|
180
|
+
console.log('First - after');
|
|
181
|
+
return result;
|
|
182
|
+
})
|
|
183
|
+
.use(async (ctx, next) => {
|
|
184
|
+
console.log('Second - before');
|
|
185
|
+
const result = await next();
|
|
186
|
+
console.log('Second - after');
|
|
187
|
+
return result;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Output order: First-before, Second-before, handler, Second-after, First-after
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Error Handling Middleware
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
app.use(async (ctx, next) => {
|
|
197
|
+
try {
|
|
198
|
+
return await next();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error('Error:', error);
|
|
201
|
+
return new Response(JSON.stringify({ error: error.message }), {
|
|
202
|
+
status: 500,
|
|
203
|
+
headers: { 'Content-Type': 'application/json' }
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Auth Middleware (Example)
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
app.use(async (ctx, next) => {
|
|
213
|
+
const token = ctx.request.headers.get('Authorization');
|
|
214
|
+
if (!token) {
|
|
215
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
|
|
216
|
+
}
|
|
217
|
+
// Validate token...
|
|
218
|
+
return await next();
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Route Groups
|
|
223
|
+
|
|
224
|
+
Group routes together with shared prefixes, middleware, and name prefixes.
|
|
225
|
+
|
|
226
|
+
### Basic Groups
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// Simple prefix
|
|
230
|
+
app.group('/api', (router) => {
|
|
231
|
+
router.get('/users', () => ({ users: [] })); // /api/users
|
|
232
|
+
router.get('/posts', () => ({ posts: [] })); // /api/posts
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Groups with Options
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// Auth middleware for protected routes
|
|
240
|
+
const authMiddleware = async (ctx, next) => {
|
|
241
|
+
const token = ctx.request.headers.get('Authorization');
|
|
242
|
+
if (!token) return new Response('Unauthorized', { status: 401 });
|
|
243
|
+
return await next();
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
app.group({
|
|
247
|
+
prefix: '/admin',
|
|
248
|
+
middleware: [authMiddleware],
|
|
249
|
+
name: 'admin.'
|
|
250
|
+
}, (router) => {
|
|
251
|
+
router.get('/dashboard', () => ({})).name('dashboard'); // name: admin.dashboard
|
|
252
|
+
router.get('/users', () => ({})).name('users'); // name: admin.users
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Nested Groups
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
app.group('/api', (api) => {
|
|
260
|
+
api.group('/v1', (v1) => {
|
|
261
|
+
v1.get('/users', () => ({})); // /api/v1/users
|
|
262
|
+
});
|
|
263
|
+
api.group('/v2', (v2) => {
|
|
264
|
+
v2.get('/users', () => ({})); // /api/v2/users
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Named Routes
|
|
270
|
+
|
|
271
|
+
Assign names to routes for URL generation.
|
|
272
|
+
|
|
273
|
+
### Naming Routes
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
app.get('/users/:id', (ctx) => ({})).name('users.show');
|
|
277
|
+
app.get('/posts/:slug', (ctx) => ({})).name('posts.show');
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Generating URLs
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// Basic URL generation
|
|
284
|
+
const url = app.route('users.show', { id: 42 });
|
|
285
|
+
// "/users/42"
|
|
286
|
+
|
|
287
|
+
// With query string
|
|
288
|
+
const searchUrl = app.route('users.show', { id: 42, tab: 'profile' });
|
|
289
|
+
// "/users/42?tab=profile"
|
|
290
|
+
|
|
291
|
+
// Check if route exists
|
|
292
|
+
if (app.hasRoute('users.show')) {
|
|
293
|
+
// ...
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// List all routes
|
|
297
|
+
const routes = app.getRoutes();
|
|
298
|
+
// [{ name: 'users.show', method: 'GET', path: '/users/:id' }, ...]
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Route Constraints
|
|
302
|
+
|
|
303
|
+
Add regex constraints to validate route parameters.
|
|
304
|
+
|
|
305
|
+
### Basic Constraints
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Only match if :id is numeric
|
|
309
|
+
app.get('/users/:id', (ctx) => ({}))
|
|
310
|
+
.where('id', /^\d+$/);
|
|
311
|
+
|
|
312
|
+
// Using string pattern
|
|
313
|
+
app.get('/posts/:slug', (ctx) => ({}))
|
|
314
|
+
.where('slug', '^[a-z0-9-]+$');
|
|
315
|
+
|
|
316
|
+
// Multiple constraints
|
|
317
|
+
app.get('/users/:id/posts/:postId', (ctx) => ({}))
|
|
318
|
+
.where({ id: /^\d+$/, postId: /^\d+$/ });
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Helper Methods
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// whereNumber - digits only
|
|
325
|
+
app.get('/users/:id', () => ({})).whereNumber('id');
|
|
326
|
+
|
|
327
|
+
// whereAlpha - letters only (a-zA-Z)
|
|
328
|
+
app.get('/categories/:name', () => ({})).whereAlpha('name');
|
|
329
|
+
|
|
330
|
+
// whereAlphaNumeric - letters and digits
|
|
331
|
+
app.get('/codes/:code', () => ({})).whereAlphaNumeric('code');
|
|
332
|
+
|
|
333
|
+
// whereUuid - UUID format
|
|
334
|
+
app.get('/items/:uuid', () => ({})).whereUuid('uuid');
|
|
335
|
+
|
|
336
|
+
// whereUlid - ULID format
|
|
337
|
+
app.get('/records/:ulid', () => ({})).whereUlid('ulid');
|
|
338
|
+
|
|
339
|
+
// whereIn - specific allowed values
|
|
340
|
+
app.get('/status/:status', () => ({})).whereIn('status', ['active', 'pending', 'archived']);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Chaining Constraints
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
app.get('/users/:id/posts/:slug', (ctx) => ({}))
|
|
347
|
+
.whereNumber('id')
|
|
348
|
+
.whereAlpha('slug')
|
|
349
|
+
.name('users.posts');
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Optional Parameters
|
|
353
|
+
|
|
354
|
+
Use `?` to mark route parameters as optional.
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// :id is optional
|
|
358
|
+
app.get('/users/:id?', (ctx) => {
|
|
359
|
+
if (ctx.params.id) {
|
|
360
|
+
return { user: ctx.params.id };
|
|
361
|
+
}
|
|
362
|
+
return { users: [] };
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Multiple optional params
|
|
366
|
+
app.get('/archive/:year?/:month?', (ctx) => {
|
|
367
|
+
const { year, month } = ctx.params;
|
|
368
|
+
// year and month may be undefined
|
|
369
|
+
return { year, month };
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Constraints work with optional params
|
|
373
|
+
app.get('/posts/:id?', (ctx) => ({})).whereNumber('id');
|
|
374
|
+
```
|
|
375
|
+
|
|
147
376
|
## Error Handling
|
|
148
377
|
|
|
149
|
-
Uncaught errors in handlers return a 500 response:
|
|
378
|
+
Uncaught errors in handlers return a 500 response with the error message:
|
|
150
379
|
|
|
151
380
|
```typescript
|
|
152
381
|
app.get('/error', () => {
|
|
@@ -154,7 +383,7 @@ app.get('/error', () => {
|
|
|
154
383
|
});
|
|
155
384
|
|
|
156
385
|
// Returns: 500 Internal Server Error
|
|
157
|
-
// Body: { error: "
|
|
386
|
+
// Body: { error: "Something went wrong" }
|
|
158
387
|
```
|
|
159
388
|
|
|
160
389
|
## Types
|
|
@@ -167,7 +396,12 @@ import type {
|
|
|
167
396
|
BunaryServer,
|
|
168
397
|
RequestContext,
|
|
169
398
|
RouteHandler,
|
|
170
|
-
Middleware
|
|
399
|
+
Middleware,
|
|
400
|
+
RouteBuilder,
|
|
401
|
+
GroupOptions,
|
|
402
|
+
GroupRouter,
|
|
403
|
+
GroupCallback,
|
|
404
|
+
RouteInfo
|
|
171
405
|
} from '@bunary/http';
|
|
172
406
|
```
|
|
173
407
|
|
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,CAmOrC"}
|
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"}
|