@bunary/http 0.0.5 → 0.1.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 +68 -0
- package/README.md +2 -395
- package/dist/app.d.ts +17 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/handlers/error.d.ts +7 -0
- package/dist/handlers/error.d.ts.map +1 -0
- package/dist/handlers/head.d.ts +11 -0
- package/dist/handlers/head.d.ts.map +1 -0
- package/dist/handlers/index.d.ts +7 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/methodNotAllowed.d.ts +8 -0
- package/dist/handlers/methodNotAllowed.d.ts.map +1 -0
- package/dist/handlers/notFound.d.ts +7 -0
- package/dist/handlers/notFound.d.ts.map +1 -0
- package/dist/handlers/options.d.ts +7 -0
- package/dist/handlers/options.d.ts.map +1 -0
- package/dist/handlers/route.d.ts +8 -0
- package/dist/handlers/route.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +199 -76
- package/dist/routes/find.d.ts +9 -0
- package/dist/routes/find.d.ts.map +1 -1
- package/dist/routes/index.d.ts +3 -3
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/types/appOptions.d.ts +64 -0
- package/dist/types/appOptions.d.ts.map +1 -1
- package/dist/types/bunaryApp.d.ts +9 -3
- package/dist/types/bunaryApp.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/listenOptions.d.ts +16 -0
- package/dist/types/listenOptions.d.ts.map +1 -0
- package/package.json +3 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,74 @@ 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.0] - 2026-01-31
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- First minor release — API stable for development use until 1.0.0
|
|
13
|
+
|
|
14
|
+
## [0.0.11] - 2026-01-29
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
|
|
18
|
+
- Removed unused `@bunary/core` dependency (chore #21)
|
|
19
|
+
- Package now has zero runtime dependencies
|
|
20
|
+
- Updated README and documentation to reflect this change
|
|
21
|
+
|
|
22
|
+
## [0.0.10] - 2026-01-29
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Configurable error handlers in `createApp()` options (feature #20)
|
|
27
|
+
- `onNotFound` - Custom handler for 404 Not Found responses
|
|
28
|
+
- `onMethodNotAllowed` - Custom handler for 405 Method Not Allowed responses
|
|
29
|
+
- `onError` - Custom handler for 500 Internal Server Error responses
|
|
30
|
+
- All handlers receive `RequestContext` and can return `Response` or `HandlerResponse`
|
|
31
|
+
- Default behavior unchanged when handlers are not provided
|
|
32
|
+
- `Allow` header automatically added to 405 responses if custom handler doesn't set it
|
|
33
|
+
|
|
34
|
+
## [0.0.9] - 2026-01-29
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Proper HEAD and OPTIONS request handling (bug #16)
|
|
39
|
+
- HEAD requests to GET routes now return 200 with empty body (preserves headers and status)
|
|
40
|
+
- OPTIONS requests return 204 with `Allow` header listing permitted methods
|
|
41
|
+
- 405 Method Not Allowed responses now include `Allow` header
|
|
42
|
+
- Route constraints are respected when determining allowed methods
|
|
43
|
+
|
|
44
|
+
## [0.0.8] - 2026-01-29
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- `createApp({ basePath })` option to prefix all routes (feature #18)
|
|
49
|
+
- Base path is automatically normalized (leading slash added, trailing slash removed)
|
|
50
|
+
- Composes correctly with route groups: `basePath + group prefix + route path`
|
|
51
|
+
- Included in `app.route()` URL generation for named routes
|
|
52
|
+
- Useful when mounting the app behind a reverse proxy
|
|
53
|
+
|
|
54
|
+
## [0.0.7] - 2026-01-29
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- `app.listen({ port, hostname })` overload (feature #19)
|
|
59
|
+
- Object form: `app.listen({ port: 3000, hostname: 'localhost' })`
|
|
60
|
+
- Existing positional form still works: `app.listen(3000, 'localhost')`
|
|
61
|
+
- Exported `ListenOptions` type for options object
|
|
62
|
+
|
|
63
|
+
## [0.0.6] - 2026-01-29
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- Aligned query parameter API with actual implementation (bug #17)
|
|
68
|
+
- Removed misleading `QueryParams` type export that didn't match runtime `URLSearchParams`
|
|
69
|
+
- Updated README examples to use `ctx.query.get()` and `ctx.query.getAll()` instead of destructuring
|
|
70
|
+
- Fixed `RequestContext` interface documentation to show `query: URLSearchParams`
|
|
71
|
+
|
|
72
|
+
### Removed
|
|
73
|
+
|
|
74
|
+
- `QueryParams` type export (was inconsistent with actual `URLSearchParams` runtime type)
|
|
75
|
+
|
|
8
76
|
## [0.0.5] - 2026-01-28
|
|
9
77
|
|
|
10
78
|
### Added
|
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** - Only depends on `@bunary/core`
|
|
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,386 +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()`
|
|
44
|
-
|
|
45
|
-
Creates a new Bunary application instance.
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
import { createApp } from '@bunary/http';
|
|
49
|
-
|
|
50
|
-
const app = createApp();
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Route Registration
|
|
54
|
-
|
|
55
|
-
Register routes using chainable HTTP method helpers:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
app
|
|
59
|
-
.get('/users', () => ({ users: [] }))
|
|
60
|
-
.post('/users', async (ctx) => {
|
|
61
|
-
const body = await ctx.request.json();
|
|
62
|
-
return { id: 1, ...body };
|
|
63
|
-
})
|
|
64
|
-
.put('/users/:id', (ctx) => {
|
|
65
|
-
return { id: ctx.params.id, updated: true };
|
|
66
|
-
})
|
|
67
|
-
.delete('/users/:id', (ctx) => {
|
|
68
|
-
return { deleted: ctx.params.id };
|
|
69
|
-
})
|
|
70
|
-
.patch('/users/:id', (ctx) => {
|
|
71
|
-
return { patched: ctx.params.id };
|
|
72
|
-
});
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Path Parameters
|
|
76
|
-
|
|
77
|
-
Path parameters are extracted automatically:
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
app.get('/users/:id', (ctx) => {
|
|
81
|
-
return { userId: ctx.params.id };
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
app.get('/posts/:postId/comments/:commentId', (ctx) => {
|
|
85
|
-
const { postId, commentId } = ctx.params;
|
|
86
|
-
return { postId, commentId };
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Query Parameters
|
|
91
|
-
|
|
92
|
-
Query parameters are parsed from the URL:
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
app.get('/search', (ctx) => {
|
|
96
|
-
const { q, page, limit } = ctx.query;
|
|
97
|
-
return { query: q, page, limit };
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Request Context
|
|
102
|
-
|
|
103
|
-
Route handlers receive a `RequestContext` object:
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
interface RequestContext {
|
|
107
|
-
request: Request; // Original Bun Request object
|
|
108
|
-
params: Record<string, string>; // Path parameters
|
|
109
|
-
query: Record<string, string>; // Query parameters
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Response Handling
|
|
114
|
-
|
|
115
|
-
Handlers can return various types - they're automatically serialized:
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
// Objects/Arrays → JSON with Content-Type: application/json
|
|
119
|
-
app.get('/json', () => ({ data: 'value' }));
|
|
120
|
-
|
|
121
|
-
// Strings → text/plain
|
|
122
|
-
app.get('/text', () => 'Hello, world!');
|
|
123
|
-
|
|
124
|
-
// Response objects passed through unchanged
|
|
125
|
-
app.get('/custom', () => new Response('Custom', { status: 201 }));
|
|
126
|
-
|
|
127
|
-
// null/undefined → 204 No Content
|
|
128
|
-
app.get('/empty', () => null);
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Starting the Server
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
const server = app.listen({ port: 3000, hostname: 'localhost' });
|
|
135
|
-
|
|
136
|
-
console.log(`Server running on ${server.hostname}:${server.port}`);
|
|
137
|
-
|
|
138
|
-
// Stop the server when done
|
|
139
|
-
server.stop();
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Testing Without Server
|
|
143
|
-
|
|
144
|
-
Use `app.fetch()` to test handlers directly:
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
const app = createApp();
|
|
148
|
-
app.get('/hello', () => ({ message: 'hi' }));
|
|
149
|
-
|
|
150
|
-
const response = await app.fetch(new Request('http://localhost/hello'));
|
|
151
|
-
const data = await response.json();
|
|
152
|
-
// { message: 'hi' }
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## Middleware
|
|
156
|
-
|
|
157
|
-
Add middleware to handle cross-cutting concerns like logging, authentication, and error handling.
|
|
158
|
-
|
|
159
|
-
### Basic Middleware
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
// Logging middleware
|
|
163
|
-
app.use(async (ctx, next) => {
|
|
164
|
-
const start = Date.now();
|
|
165
|
-
const result = await next();
|
|
166
|
-
console.log(`${ctx.request.method} ${new URL(ctx.request.url).pathname} - ${Date.now() - start}ms`);
|
|
167
|
-
return result;
|
|
168
|
-
});
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Middleware Chain
|
|
172
|
-
|
|
173
|
-
Middleware executes in registration order. Each middleware can:
|
|
174
|
-
- Run code before calling `next()`
|
|
175
|
-
- Call `next()` to continue the chain
|
|
176
|
-
- Run code after `next()` returns
|
|
177
|
-
- Return early without calling `next()`
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
app
|
|
181
|
-
.use(async (ctx, next) => {
|
|
182
|
-
console.log('First - before');
|
|
183
|
-
const result = await next();
|
|
184
|
-
console.log('First - after');
|
|
185
|
-
return result;
|
|
186
|
-
})
|
|
187
|
-
.use(async (ctx, next) => {
|
|
188
|
-
console.log('Second - before');
|
|
189
|
-
const result = await next();
|
|
190
|
-
console.log('Second - after');
|
|
191
|
-
return result;
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Output order: First-before, Second-before, handler, Second-after, First-after
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Error Handling Middleware
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
app.use(async (ctx, next) => {
|
|
201
|
-
try {
|
|
202
|
-
return await next();
|
|
203
|
-
} catch (error) {
|
|
204
|
-
console.error('Error:', error);
|
|
205
|
-
return new Response(JSON.stringify({ error: error.message }), {
|
|
206
|
-
status: 500,
|
|
207
|
-
headers: { 'Content-Type': 'application/json' }
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Auth Middleware (Example)
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
app.use(async (ctx, next) => {
|
|
217
|
-
const token = ctx.request.headers.get('Authorization');
|
|
218
|
-
if (!token) {
|
|
219
|
-
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
|
|
220
|
-
}
|
|
221
|
-
// Validate token...
|
|
222
|
-
return await next();
|
|
223
|
-
});
|
|
224
|
-
```
|
|
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
|
-
|
|
380
|
-
## Error Handling
|
|
381
|
-
|
|
382
|
-
Uncaught errors in handlers return a 500 response with the error message:
|
|
383
|
-
|
|
384
|
-
```typescript
|
|
385
|
-
app.get('/error', () => {
|
|
386
|
-
throw new Error('Something went wrong');
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// Returns: 500 Internal Server Error
|
|
390
|
-
// Body: { error: "Something went wrong" }
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
## Types
|
|
394
|
-
|
|
395
|
-
All types are exported for TypeScript users:
|
|
396
|
-
|
|
397
|
-
```typescript
|
|
398
|
-
import type {
|
|
399
|
-
BunaryApp,
|
|
400
|
-
BunaryServer,
|
|
401
|
-
RequestContext,
|
|
402
|
-
RouteHandler,
|
|
403
|
-
Middleware,
|
|
404
|
-
RouteBuilder,
|
|
405
|
-
GroupOptions,
|
|
406
|
-
GroupRouter,
|
|
407
|
-
GroupCallback,
|
|
408
|
-
RouteInfo
|
|
409
|
-
} from '@bunary/http';
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
## Requirements
|
|
413
|
-
|
|
414
|
-
- Bun ≥ 1.0.0
|
|
21
|
+
For createApp options, route groups, middleware, named routes, and types, see [docs/index.md](./docs/index.md).
|
|
415
22
|
|
|
416
23
|
## License
|
|
417
24
|
|
package/dist/app.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BunaryApp } from "./types/index.js";
|
|
1
|
+
import type { AppOptions, BunaryApp } from "./types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Create a new Bunary HTTP application instance.
|
|
4
4
|
*
|
|
@@ -32,6 +32,21 @@ import type { BunaryApp } from "./types/index.js";
|
|
|
32
32
|
*
|
|
33
33
|
* app.listen(3000);
|
|
34
34
|
* ```
|
|
35
|
+
*
|
|
36
|
+
* @param options - Optional configuration
|
|
37
|
+
* @param options.basePath - Base path prefix for all routes (e.g., "/api")
|
|
38
|
+
* @returns BunaryApp instance
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* // Without basePath
|
|
43
|
+
* const app = createApp();
|
|
44
|
+
* app.get("/users", () => ({})); // Matches /users
|
|
45
|
+
*
|
|
46
|
+
* // With basePath
|
|
47
|
+
* const apiApp = createApp({ basePath: "/api" });
|
|
48
|
+
* apiApp.get("/users", () => ({})); // Matches /api/users
|
|
49
|
+
* ```
|
|
35
50
|
*/
|
|
36
|
-
export declare function createApp(): BunaryApp;
|
|
51
|
+
export declare function createApp(options?: AppOptions): BunaryApp;
|
|
37
52
|
//# 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":"AAiBA,OAAO,KAAK,EACX,UAAU,EACV,SAAS,EAWT,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CAiPzD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AppOptions, RequestContext } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handle 500 Internal Server Error responses.
|
|
4
|
+
* Uses custom onError handler if provided, otherwise returns default JSON response.
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleError(ctx: RequestContext, error: unknown, options?: AppOptions): Promise<Response>;
|
|
7
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +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;;;GAGG;AACH,wBAAsB,WAAW,CAChC,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HttpMethod, Route } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Normalize HEAD requests to use GET route if no explicit HEAD route exists.
|
|
4
|
+
* Returns the method to use for route matching.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizeHeadMethod(method: HttpMethod, path: string, routes: Route[]): HttpMethod;
|
|
7
|
+
/**
|
|
8
|
+
* Convert a response to HEAD format (empty body, preserve headers and status).
|
|
9
|
+
*/
|
|
10
|
+
export declare function toHeadResponse(response: Response): Response;
|
|
11
|
+
//# sourceMappingURL=head.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../../src/handlers/head.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,UAAU,CAejG;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAM3D"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { handleError } from "./error.js";
|
|
2
|
+
export { normalizeHeadMethod, toHeadResponse } from "./head.js";
|
|
3
|
+
export { handleMethodNotAllowed } from "./methodNotAllowed.js";
|
|
4
|
+
export { handleNotFound } from "./notFound.js";
|
|
5
|
+
export { handleOptions } from "./options.js";
|
|
6
|
+
export { executeRoute } from "./route.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,mBAAmB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChE,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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppOptions, Route } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handle 405 Method Not Allowed responses.
|
|
4
|
+
* Uses custom onMethodNotAllowed handler if provided, otherwise returns default JSON response.
|
|
5
|
+
* Ensures Allow header is always present.
|
|
6
|
+
*/
|
|
7
|
+
export declare function handleMethodNotAllowed(request: Request, path: string, routes: Route[], options?: AppOptions): Promise<Response>;
|
|
8
|
+
//# sourceMappingURL=methodNotAllowed.d.ts.map
|
|
@@ -0,0 +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;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC3C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,QAAQ,CAAC,CAgCnB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AppOptions } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handle 404 Not Found responses.
|
|
4
|
+
* Uses custom onNotFound handler if provided, otherwise returns default JSON response.
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleNotFound(request: Request, _path: string, options?: AppOptions): Promise<Response>;
|
|
7
|
+
//# sourceMappingURL=notFound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notFound.d.ts","sourceRoot":"","sources":["../../src/handlers/notFound.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAC;AAEpE;;;GAGG;AACH,wBAAsB,cAAc,CACnC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,QAAQ,CAAC,CAgBnB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AppOptions, Route } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handle OPTIONS requests.
|
|
4
|
+
* Returns 204 with Allow header if path exists, otherwise delegates to 404 handler.
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleOptions(request: Request, path: string, routes: Route[], options?: AppOptions): Promise<Response>;
|
|
7
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +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;;;GAGG;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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RouteMatch } from "../routes/index.js";
|
|
2
|
+
import type { Middleware, RequestContext, Route } from "../types/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Execute route handler with middleware chain.
|
|
5
|
+
* Returns the response from the handler or middleware.
|
|
6
|
+
*/
|
|
7
|
+
export declare function executeRoute(match: RouteMatch, ctx: RequestContext, getMiddlewareChain: (route: Route) => Middleware[]): Promise<Response>;
|
|
8
|
+
//# sourceMappingURL=route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/handlers/route.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAmB,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE5F;;;GAGG;AACH,wBAAsB,YAAY,CACjC,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,cAAc,EACnB,kBAAkB,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,UAAU,EAAE,GAChD,OAAO,CAAC,QAAQ,CAAC,CAgBnB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
*
|
|
19
19
|
* @packageDocumentation
|
|
20
20
|
*/
|
|
21
|
-
export type { AppOptions, BunaryApp, BunaryServer, GroupCallback, GroupOptions, GroupRouter, HandlerResponse, HttpMethod, Middleware, PathParams, QueryParams, RequestContext, Route, RouteBuilder, RouteHandler, RouteInfo, } from "./types/index.js";
|
|
22
21
|
export { createApp } from "./app.js";
|
|
22
|
+
export type { AppOptions, BunaryApp, BunaryServer, GroupCallback, GroupOptions, GroupRouter, HandlerResponse, HttpMethod, ListenOptions, Middleware, PathParams, RequestContext, Route, 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,YAAY,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,eAAe,EACf,UAAU,EACV,UAAU,EACV,UAAU,EACV,
|
|
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,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,SAAS,GACT,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -25,55 +25,18 @@ function toResponse(result) {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// src/
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
regexString = regexString.replace(/\/:([a-zA-Z_][a-zA-Z0-9_]*)(\\\?)?/g, (_match, paramName, isOptional) => {
|
|
34
|
-
if (paramNames.includes(paramName)) {
|
|
35
|
-
throw new Error(`Duplicate parameter name ":${paramName}" in route pattern "${path}". Each parameter name must be unique within a route.`);
|
|
36
|
-
}
|
|
37
|
-
paramNames.push(paramName);
|
|
38
|
-
if (isOptional) {
|
|
39
|
-
optionalParams.push(paramName);
|
|
40
|
-
return "(?:/([^/]+))?";
|
|
41
|
-
}
|
|
42
|
-
return "/([^/]+)";
|
|
43
|
-
});
|
|
44
|
-
regexString += "/?";
|
|
45
|
-
return {
|
|
46
|
-
pattern: new RegExp(`^${regexString}$`),
|
|
47
|
-
paramNames,
|
|
48
|
-
optionalParams
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function extractParams(path, route) {
|
|
52
|
-
const match = path.match(route.pattern);
|
|
53
|
-
if (!match)
|
|
54
|
-
return {};
|
|
55
|
-
const params = {};
|
|
56
|
-
for (let i = 0;i < route.paramNames.length; i++) {
|
|
57
|
-
const value = match[i + 1];
|
|
58
|
-
if (value !== undefined && value !== "") {
|
|
59
|
-
params[route.paramNames[i]] = value;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return params;
|
|
63
|
-
}
|
|
64
|
-
function checkConstraints(params, constraints) {
|
|
65
|
-
if (!constraints)
|
|
66
|
-
return true;
|
|
67
|
-
for (const [param, pattern] of Object.entries(constraints)) {
|
|
68
|
-
const value = params[param];
|
|
69
|
-
if (value === undefined)
|
|
70
|
-
continue;
|
|
71
|
-
if (!pattern.test(value))
|
|
72
|
-
return false;
|
|
28
|
+
// src/handlers/error.ts
|
|
29
|
+
async function handleError(ctx, error, options) {
|
|
30
|
+
if (options?.onError) {
|
|
31
|
+
const result = await options.onError(ctx, error);
|
|
32
|
+
return toResponse(result);
|
|
73
33
|
}
|
|
74
|
-
|
|
34
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
35
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
36
|
+
status: 500,
|
|
37
|
+
headers: { "Content-Type": "application/json" }
|
|
38
|
+
});
|
|
75
39
|
}
|
|
76
|
-
|
|
77
40
|
// src/routes/builder.ts
|
|
78
41
|
function compilePattern(pattern, param) {
|
|
79
42
|
try {
|
|
@@ -195,6 +158,55 @@ function wrapBuilderWithNamePrefix(builder, namePrefix) {
|
|
|
195
158
|
}
|
|
196
159
|
});
|
|
197
160
|
}
|
|
161
|
+
// src/router.ts
|
|
162
|
+
function compilePath(path) {
|
|
163
|
+
const paramNames = [];
|
|
164
|
+
const optionalParams = [];
|
|
165
|
+
let regexString = path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
166
|
+
regexString = regexString.replace(/\/:([a-zA-Z_][a-zA-Z0-9_]*)(\\\?)?/g, (_match, paramName, isOptional) => {
|
|
167
|
+
if (paramNames.includes(paramName)) {
|
|
168
|
+
throw new Error(`Duplicate parameter name ":${paramName}" in route pattern "${path}". Each parameter name must be unique within a route.`);
|
|
169
|
+
}
|
|
170
|
+
paramNames.push(paramName);
|
|
171
|
+
if (isOptional) {
|
|
172
|
+
optionalParams.push(paramName);
|
|
173
|
+
return "(?:/([^/]+))?";
|
|
174
|
+
}
|
|
175
|
+
return "/([^/]+)";
|
|
176
|
+
});
|
|
177
|
+
regexString += "/?";
|
|
178
|
+
return {
|
|
179
|
+
pattern: new RegExp(`^${regexString}$`),
|
|
180
|
+
paramNames,
|
|
181
|
+
optionalParams
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function extractParams(path, route) {
|
|
185
|
+
const match = path.match(route.pattern);
|
|
186
|
+
if (!match)
|
|
187
|
+
return {};
|
|
188
|
+
const params = {};
|
|
189
|
+
for (let i = 0;i < route.paramNames.length; i++) {
|
|
190
|
+
const value = match[i + 1];
|
|
191
|
+
if (value !== undefined && value !== "") {
|
|
192
|
+
params[route.paramNames[i]] = value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return params;
|
|
196
|
+
}
|
|
197
|
+
function checkConstraints(params, constraints) {
|
|
198
|
+
if (!constraints)
|
|
199
|
+
return true;
|
|
200
|
+
for (const [param, pattern] of Object.entries(constraints)) {
|
|
201
|
+
const value = params[param];
|
|
202
|
+
if (value === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
if (!pattern.test(value))
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
198
210
|
// src/routes/find.ts
|
|
199
211
|
function findRoute(routes, method, path) {
|
|
200
212
|
for (const route of routes) {
|
|
@@ -218,6 +230,18 @@ function hasMatchingPath(routes, path) {
|
|
|
218
230
|
return checkConstraints(params, route.constraints);
|
|
219
231
|
});
|
|
220
232
|
}
|
|
233
|
+
function getAllowedMethods(routes, path) {
|
|
234
|
+
const methods = new Set;
|
|
235
|
+
for (const route of routes) {
|
|
236
|
+
if (route.pattern.test(path)) {
|
|
237
|
+
const params = extractParams(path, route);
|
|
238
|
+
if (checkConstraints(params, route.constraints)) {
|
|
239
|
+
methods.add(route.method);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return Array.from(methods).sort();
|
|
244
|
+
}
|
|
221
245
|
// src/pathUtils.ts
|
|
222
246
|
function normalizePrefix(prefix) {
|
|
223
247
|
let normalized = prefix;
|
|
@@ -277,11 +301,111 @@ function createGroupRouter(prefix, groupMiddleware, namePrefix, addRoute) {
|
|
|
277
301
|
};
|
|
278
302
|
return router;
|
|
279
303
|
}
|
|
304
|
+
// 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
|
+
function toHeadResponse(response) {
|
|
320
|
+
return new Response(null, {
|
|
321
|
+
status: response.status,
|
|
322
|
+
statusText: response.statusText,
|
|
323
|
+
headers: response.headers
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
// src/handlers/methodNotAllowed.ts
|
|
327
|
+
async function handleMethodNotAllowed(request, path, routes, options) {
|
|
328
|
+
const url = new URL(request.url);
|
|
329
|
+
const allowedMethods = getAllowedMethods(routes, path);
|
|
330
|
+
const methodNotAllowedCtx = {
|
|
331
|
+
request,
|
|
332
|
+
params: {},
|
|
333
|
+
query: url.searchParams,
|
|
334
|
+
locals: {}
|
|
335
|
+
};
|
|
336
|
+
if (options?.onMethodNotAllowed) {
|
|
337
|
+
const result = await options.onMethodNotAllowed(methodNotAllowedCtx, allowedMethods);
|
|
338
|
+
const response = toResponse(result);
|
|
339
|
+
const allowHeader = response.headers.get("Allow");
|
|
340
|
+
if (!allowHeader) {
|
|
341
|
+
const headers = new Headers(response.headers);
|
|
342
|
+
headers.set("Allow", allowedMethods.join(", "));
|
|
343
|
+
return new Response(response.body, {
|
|
344
|
+
status: response.status,
|
|
345
|
+
statusText: response.statusText,
|
|
346
|
+
headers
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return response;
|
|
350
|
+
}
|
|
351
|
+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
352
|
+
status: 405,
|
|
353
|
+
headers: {
|
|
354
|
+
"Content-Type": "application/json",
|
|
355
|
+
Allow: allowedMethods.join(", ")
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// src/handlers/notFound.ts
|
|
360
|
+
async function handleNotFound(request, _path, options) {
|
|
361
|
+
const url = new URL(request.url);
|
|
362
|
+
const notFoundCtx = {
|
|
363
|
+
request,
|
|
364
|
+
params: {},
|
|
365
|
+
query: url.searchParams,
|
|
366
|
+
locals: {}
|
|
367
|
+
};
|
|
368
|
+
if (options?.onNotFound) {
|
|
369
|
+
const result = await options.onNotFound(notFoundCtx);
|
|
370
|
+
return toResponse(result);
|
|
371
|
+
}
|
|
372
|
+
return new Response(JSON.stringify({ error: "Not found" }), {
|
|
373
|
+
status: 404,
|
|
374
|
+
headers: { "Content-Type": "application/json" }
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
// src/handlers/options.ts
|
|
378
|
+
async function handleOptions(request, path, routes, options) {
|
|
379
|
+
if (hasMatchingPath(routes, path)) {
|
|
380
|
+
const allowedMethods = getAllowedMethods(routes, path);
|
|
381
|
+
return new Response(null, {
|
|
382
|
+
status: 204,
|
|
383
|
+
headers: { Allow: allowedMethods.join(", ") }
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return await handleNotFound(request, path, options);
|
|
387
|
+
}
|
|
388
|
+
// src/handlers/route.ts
|
|
389
|
+
async function executeRoute(match, ctx, getMiddlewareChain) {
|
|
390
|
+
const allMiddleware = getMiddlewareChain(match.route);
|
|
391
|
+
let index = 0;
|
|
392
|
+
const next = async () => {
|
|
393
|
+
if (index < allMiddleware.length) {
|
|
394
|
+
const middleware = allMiddleware[index++];
|
|
395
|
+
return await middleware(ctx, next);
|
|
396
|
+
}
|
|
397
|
+
return await match.route.handler(ctx);
|
|
398
|
+
};
|
|
399
|
+
const result = await next();
|
|
400
|
+
return toResponse(result);
|
|
401
|
+
}
|
|
280
402
|
// src/app.ts
|
|
281
|
-
function createApp() {
|
|
403
|
+
function createApp(options) {
|
|
282
404
|
const routes = [];
|
|
283
405
|
const middlewares = [];
|
|
284
406
|
const namedRoutes = new Map;
|
|
407
|
+
const normalizedBasePath = options?.basePath ? normalizePrefix(options.basePath) : "";
|
|
408
|
+
const basePath = normalizedBasePath === "/" ? "" : normalizedBasePath;
|
|
285
409
|
let globalMiddlewareVersion = 0;
|
|
286
410
|
const middlewareCache = new WeakMap;
|
|
287
411
|
function getMiddlewareChain(route) {
|
|
@@ -294,10 +418,11 @@ function createApp() {
|
|
|
294
418
|
return chain;
|
|
295
419
|
}
|
|
296
420
|
function addRoute(method, path, handler, groupMiddleware = []) {
|
|
297
|
-
const
|
|
421
|
+
const fullPath = basePath ? joinPaths(basePath, path) : path;
|
|
422
|
+
const { pattern, paramNames, optionalParams } = compilePath(fullPath);
|
|
298
423
|
const route = {
|
|
299
424
|
method,
|
|
300
|
-
path,
|
|
425
|
+
path: fullPath,
|
|
301
426
|
pattern,
|
|
302
427
|
paramNames,
|
|
303
428
|
handler,
|
|
@@ -311,18 +436,16 @@ function createApp() {
|
|
|
311
436
|
const url = new URL(request.url);
|
|
312
437
|
const path = url.pathname;
|
|
313
438
|
const method = request.method;
|
|
314
|
-
|
|
439
|
+
if (method === "OPTIONS") {
|
|
440
|
+
return await handleOptions(request, path, routes, options);
|
|
441
|
+
}
|
|
442
|
+
const actualMethod = normalizeHeadMethod(method, path, routes);
|
|
443
|
+
const match = findRoute(routes, actualMethod, path);
|
|
315
444
|
if (!match) {
|
|
316
445
|
if (hasMatchingPath(routes, path)) {
|
|
317
|
-
return
|
|
318
|
-
status: 405,
|
|
319
|
-
headers: { "Content-Type": "application/json" }
|
|
320
|
-
});
|
|
446
|
+
return await handleMethodNotAllowed(request, path, routes, options);
|
|
321
447
|
}
|
|
322
|
-
return
|
|
323
|
-
status: 404,
|
|
324
|
-
headers: { "Content-Type": "application/json" }
|
|
325
|
-
});
|
|
448
|
+
return await handleNotFound(request, path, options);
|
|
326
449
|
}
|
|
327
450
|
const ctx = {
|
|
328
451
|
request,
|
|
@@ -331,23 +454,13 @@ function createApp() {
|
|
|
331
454
|
locals: {}
|
|
332
455
|
};
|
|
333
456
|
try {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return await middleware(ctx, next);
|
|
340
|
-
}
|
|
341
|
-
return await match.route.handler(ctx);
|
|
342
|
-
};
|
|
343
|
-
const result = await next();
|
|
344
|
-
return toResponse(result);
|
|
457
|
+
const response = await executeRoute(match, ctx, getMiddlewareChain);
|
|
458
|
+
if (method === "HEAD") {
|
|
459
|
+
return toHeadResponse(response);
|
|
460
|
+
}
|
|
461
|
+
return response;
|
|
345
462
|
} catch (error) {
|
|
346
|
-
|
|
347
|
-
return new Response(JSON.stringify({ error: message }), {
|
|
348
|
-
status: 500,
|
|
349
|
-
headers: { "Content-Type": "application/json" }
|
|
350
|
-
});
|
|
463
|
+
return await handleError(ctx, error, options);
|
|
351
464
|
}
|
|
352
465
|
}
|
|
353
466
|
const app = {
|
|
@@ -419,7 +532,17 @@ function createApp() {
|
|
|
419
532
|
path: route.path
|
|
420
533
|
}));
|
|
421
534
|
},
|
|
422
|
-
listen: (
|
|
535
|
+
listen: (portOrOptions, hostnameArg) => {
|
|
536
|
+
let port;
|
|
537
|
+
let hostname;
|
|
538
|
+
const isOptionsObject = portOrOptions !== undefined && portOrOptions !== null && typeof portOrOptions === "object";
|
|
539
|
+
if (isOptionsObject) {
|
|
540
|
+
port = portOrOptions.port ?? 3000;
|
|
541
|
+
hostname = portOrOptions.hostname ?? "localhost";
|
|
542
|
+
} else {
|
|
543
|
+
port = typeof portOrOptions === "number" ? portOrOptions : 3000;
|
|
544
|
+
hostname = hostnameArg ?? "localhost";
|
|
545
|
+
}
|
|
423
546
|
const server = Bun.serve({
|
|
424
547
|
port,
|
|
425
548
|
hostname,
|
package/dist/routes/find.d.ts
CHANGED
|
@@ -24,4 +24,13 @@ export declare function findRoute(routes: Route[], method: string, path: string)
|
|
|
24
24
|
* Check if any route matches the path (regardless of method).
|
|
25
25
|
*/
|
|
26
26
|
export declare function hasMatchingPath(routes: Route[], path: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Get all allowed HTTP methods for a given path.
|
|
29
|
+
* Respects route constraints when determining matches.
|
|
30
|
+
*
|
|
31
|
+
* @param routes - All registered routes
|
|
32
|
+
* @param path - Path to check
|
|
33
|
+
* @returns Array of allowed HTTP methods
|
|
34
|
+
*/
|
|
35
|
+
export declare function getAllowedMethods(routes: Route[], path: string): string[];
|
|
27
36
|
//# sourceMappingURL=find.d.ts.map
|
|
@@ -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"}
|
|
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"}
|
package/dist/routes/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { findRoute, hasMatchingPath, type RouteMatch } from "./find.js";
|
|
3
|
-
export {
|
|
1
|
+
export { compilePattern, createRouteBuilder, wrapBuilderWithNamePrefix } from "./builder.js";
|
|
2
|
+
export { findRoute, getAllowedMethods, hasMatchingPath, type RouteMatch } from "./find.js";
|
|
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,
|
|
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,EAAE,SAAS,EAAE,iBAAiB,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAC3F,OAAO,EAAE,KAAK,UAAU,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -1,8 +1,72 @@
|
|
|
1
|
+
import type { HandlerResponse } from "./handlerResponse.js";
|
|
2
|
+
import type { RequestContext } from "./requestContext.js";
|
|
1
3
|
/**
|
|
2
4
|
* Configuration options for creating a Bunary app.
|
|
3
5
|
*/
|
|
4
6
|
export interface AppOptions {
|
|
5
7
|
/** Base path prefix for all routes (default: "") */
|
|
6
8
|
basePath?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Custom handler for 404 Not Found responses.
|
|
11
|
+
* Called when no route matches the request path.
|
|
12
|
+
*
|
|
13
|
+
* @param ctx - Request context (params will be empty, query available)
|
|
14
|
+
* @returns Response, HandlerResponse, or Promise of either (will be converted to Response)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const app = createApp({
|
|
19
|
+
* onNotFound: async (ctx) => {
|
|
20
|
+
* await logToExternalService(ctx.request.url);
|
|
21
|
+
* return new Response("Custom 404", { status: 404 });
|
|
22
|
+
* }
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
onNotFound?: (ctx: RequestContext) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
|
|
27
|
+
/**
|
|
28
|
+
* Custom handler for 405 Method Not Allowed responses.
|
|
29
|
+
* Called when a route matches the path but not the HTTP method.
|
|
30
|
+
*
|
|
31
|
+
* @param ctx - Request context (params will be empty, query available)
|
|
32
|
+
* @param allowedMethods - Array of allowed HTTP methods for this path
|
|
33
|
+
* @returns Response, HandlerResponse, or Promise of either (will be converted to Response)
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const app = createApp({
|
|
38
|
+
* onMethodNotAllowed: async (ctx, allowed) => {
|
|
39
|
+
* await logMethodNotAllowed(ctx.request.url, allowed);
|
|
40
|
+
* return new Response(
|
|
41
|
+
* JSON.stringify({ error: "Method not allowed", allowed }),
|
|
42
|
+
* { status: 405, headers: { "Content-Type": "application/json" } }
|
|
43
|
+
* );
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
onMethodNotAllowed?: (ctx: RequestContext, allowedMethods: string[]) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
|
|
49
|
+
/**
|
|
50
|
+
* Custom handler for 500 Internal Server Error responses.
|
|
51
|
+
* Called when a route handler or middleware throws an error.
|
|
52
|
+
*
|
|
53
|
+
* @param ctx - Request context
|
|
54
|
+
* @param error - The error that was thrown
|
|
55
|
+
* @returns Response, HandlerResponse, or Promise of either (will be converted to Response)
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const app = createApp({
|
|
60
|
+
* onError: async (ctx, error) => {
|
|
61
|
+
* await logErrorToExternalService(error, ctx.request.url);
|
|
62
|
+
* return new Response(
|
|
63
|
+
* JSON.stringify({ error: "Internal server error" }),
|
|
64
|
+
* { status: 500, headers: { "Content-Type": "application/json" } }
|
|
65
|
+
* );
|
|
66
|
+
* }
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
onError?: (ctx: RequestContext, error: unknown) => Response | HandlerResponse | Promise<Response | HandlerResponse>;
|
|
7
71
|
}
|
|
8
72
|
//# sourceMappingURL=appOptions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appOptions.d.ts","sourceRoot":"","sources":["../../src/types/appOptions.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
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;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,CACZ,GAAG,EAAE,cAAc,KACf,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;IACtE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,kBAAkB,CAAC,EAAE,CACpB,GAAG,EAAE,cAAc,EACnB,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,EACnB,KAAK,EAAE,OAAO,KACV,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;CACtE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BunaryServer } from "./bunaryServer.js";
|
|
2
2
|
import type { GroupOptions } from "./groupOptions.js";
|
|
3
3
|
import type { GroupCallback } from "./groupRouter.js";
|
|
4
|
+
import type { ListenOptions } from "./listenOptions.js";
|
|
4
5
|
import type { Middleware } from "./middleware.js";
|
|
5
6
|
import type { RouteBuilder } from "./routeBuilder.js";
|
|
6
7
|
import type { RouteHandler } from "./routeHandler.js";
|
|
@@ -82,11 +83,16 @@ export interface BunaryApp {
|
|
|
82
83
|
getRoutes: () => RouteInfo[];
|
|
83
84
|
/**
|
|
84
85
|
* Start the HTTP server.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
86
|
+
*
|
|
87
|
+
* Supports two call styles:
|
|
88
|
+
* - `listen(port?, hostname?)` - positional arguments
|
|
89
|
+
* - `listen({ port?, hostname? })` - options object
|
|
90
|
+
*
|
|
91
|
+
* @param portOrOptions - Port number, or options object with port and hostname
|
|
92
|
+
* @param hostname - Hostname to bind to (when using positional form)
|
|
87
93
|
* @returns Server instance with stop() method
|
|
88
94
|
*/
|
|
89
|
-
listen: (port?: number, hostname?: string) => BunaryServer;
|
|
95
|
+
listen: ((port?: number, hostname?: string) => BunaryServer) & ((options: ListenOptions) => BunaryServer);
|
|
90
96
|
/**
|
|
91
97
|
* Handle an incoming request (used internally and for testing).
|
|
92
98
|
* @param request - The incoming Request object
|
|
@@ -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,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;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACzB;;;;OAIG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE3D;;;;OAIG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE5D;;;;OAIG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE3D;;;;OAIG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE9D;;;;OAIG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE7D;;;;OAIG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,SAAS,CAAC;IAE3C;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,SAAS,CAAC,GAC9D,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,KAAK,SAAS,CAAC,CAAC;IAEjE;;;;;;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
|
|
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;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACzB;;;;OAIG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE3D;;;;OAIG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE5D;;;;OAIG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE3D;;;;OAIG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE9D;;;;OAIG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IAE7D;;;;OAIG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,SAAS,CAAC;IAE3C;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,SAAS,CAAC,GAC9D,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,KAAK,SAAS,CAAC,CAAC;IAEjE;;;;;;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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
export type { AppOptions } from "./appOptions.js";
|
|
5
5
|
export type { BunaryApp } from "./bunaryApp.js";
|
|
6
6
|
export type { BunaryServer } from "./bunaryServer.js";
|
|
7
|
-
export type { GroupCallback, GroupRouter } from "./groupRouter.js";
|
|
8
7
|
export type { GroupOptions } from "./groupOptions.js";
|
|
8
|
+
export type { GroupCallback, GroupRouter } from "./groupRouter.js";
|
|
9
9
|
export type { HandlerResponse } from "./handlerResponse.js";
|
|
10
10
|
export type { HttpMethod } from "./httpMethod.js";
|
|
11
|
+
export type { ListenOptions } from "./listenOptions.js";
|
|
11
12
|
export type { Middleware } from "./middleware.js";
|
|
12
13
|
export type { PathParams } from "./pathParams.js";
|
|
13
|
-
export type { QueryParams } from "./queryParams.js";
|
|
14
14
|
export type { RequestContext } from "./requestContext.js";
|
|
15
15
|
export type { Route } from "./route.js";
|
|
16
16
|
export type { RouteBuilder } from "./routeBuilder.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for app.listen() when using the object form.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* app.listen({ port: 3000, hostname: "localhost" });
|
|
7
|
+
* app.listen({ port: 8080 });
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export interface ListenOptions {
|
|
11
|
+
/** Port number to listen on (default: 3000) */
|
|
12
|
+
port?: number;
|
|
13
|
+
/** Hostname to bind to (default: "localhost") */
|
|
14
|
+
hostname?: string;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=listenOptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listenOptions.d.ts","sourceRoot":"","sources":["../../src/types/listenOptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC7B,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bunary/http",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "HTTP routing and middleware for Bunary - a Bun-first backend framework inspired by Laravel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -44,11 +44,9 @@
|
|
|
44
44
|
"engines": {
|
|
45
45
|
"bun": ">=1.0.0"
|
|
46
46
|
},
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"@bunary/core": "^0.0.2"
|
|
49
|
-
},
|
|
47
|
+
"dependencies": {},
|
|
50
48
|
"devDependencies": {
|
|
51
|
-
"@biomejs/biome": "^
|
|
49
|
+
"@biomejs/biome": "^2.3.13",
|
|
52
50
|
"bun-types": "^1.0.0",
|
|
53
51
|
"typescript": "^5.0.0"
|
|
54
52
|
}
|