bxo 0.0.5-dev.65 โ 0.0.5-dev.66
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/README.md +83 -675
- package/example/cors-example.ts +49 -0
- package/example/index.html +5 -0
- package/example/index.ts +57 -0
- package/package.json +9 -15
- package/plugins/cors.ts +124 -98
- package/plugins/index.ts +2 -9
- package/plugins/openapi.ts +130 -0
- package/src/index.ts +646 -59
- package/tsconfig.json +3 -5
- package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +0 -111
- package/examples/serve-react/README.md +0 -15
- package/examples/serve-react/app.tsx +0 -8
- package/examples/serve-react/bun.lock +0 -42
- package/examples/serve-react/index.html +0 -9
- package/examples/serve-react/index.ts +0 -27
- package/examples/serve-react/package.json +0 -17
- package/examples/serve-react/tsconfig.json +0 -29
- package/index.ts +0 -5
- package/plugins/README.md +0 -160
- package/plugins/ratelimit.ts +0 -136
- package/src/core/bxo.ts +0 -458
- package/src/handlers/request-handler.ts +0 -230
- package/src/types/index.ts +0 -167
- package/src/utils/context-factory.ts +0 -158
- package/src/utils/helpers.ts +0 -40
- package/src/utils/index.ts +0 -448
- package/src/utils/response-handler.ts +0 -293
- package/src/utils/route-matcher.ts +0 -191
- package/tests/README.md +0 -359
- package/tests/integration/bxo.test.ts +0 -616
- package/tests/run-tests.ts +0 -44
- package/tests/unit/context-factory.test.ts +0 -386
- package/tests/unit/helpers.test.ts +0 -253
- package/tests/unit/response-handler.test.ts +0 -327
- package/tests/unit/route-matcher.test.ts +0 -181
- package/tests/unit/utils.test.ts +0 -475
package/README.md
CHANGED
|
@@ -1,746 +1,154 @@
|
|
|
1
|
-
#
|
|
1
|
+
# bxo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A fast, lightweight web framework for Bun with built-in Zod validation and lifecycle hooks.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- ๐ **Hot Reload** - Automatic server restart on file changes during development
|
|
13
|
-
- ๐ฎ **Server Management** - Programmatic start, stop, and restart capabilities
|
|
14
|
-
- ๐ **Status Monitoring** - Built-in server status and runtime statistics
|
|
15
|
-
- ๐ฆ **Zero Dependencies** - Only depends on Zod for validation
|
|
16
|
-
- โก **Fast** - Minimal overhead with efficient routing
|
|
7
|
+
- **Type-safe routing** with Zod schema validation
|
|
8
|
+
- **Lifecycle hooks** for middleware and plugins
|
|
9
|
+
- **Plugin system** for extending functionality
|
|
10
|
+
- **Built-in CORS support** via plugin
|
|
11
|
+
- **Fast performance** leveraging Bun's native HTTP server
|
|
17
12
|
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
### Installation
|
|
13
|
+
## Installation
|
|
21
14
|
|
|
22
15
|
```bash
|
|
23
|
-
bun
|
|
16
|
+
bun install
|
|
24
17
|
```
|
|
25
18
|
|
|
26
|
-
|
|
19
|
+
## Quick Start
|
|
27
20
|
|
|
28
21
|
```typescript
|
|
29
|
-
import BXO
|
|
30
|
-
|
|
31
|
-
const app = new BXO()
|
|
32
|
-
.get('/', async (ctx) => {
|
|
33
|
-
return { message: 'Hello, BXO!' };
|
|
34
|
-
})
|
|
35
|
-
.start(3000);
|
|
36
|
-
```
|
|
22
|
+
import BXO from "./src/index";
|
|
23
|
+
import { cors } from "./plugins";
|
|
37
24
|
|
|
38
|
-
|
|
25
|
+
const app = new BXO();
|
|
39
26
|
|
|
40
|
-
|
|
27
|
+
// Use CORS plugin
|
|
28
|
+
app.use(cors());
|
|
41
29
|
|
|
42
|
-
|
|
30
|
+
// Define routes
|
|
31
|
+
app.get("/", (ctx) => ctx.json({ message: "Hello World!" }));
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
const app = new BXO()
|
|
46
|
-
// Simple handler
|
|
47
|
-
.get('/simple', async (ctx) => {
|
|
48
|
-
return { message: 'Hello World' };
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// With validation
|
|
52
|
-
.post('/users', async (ctx) => {
|
|
53
|
-
// ctx.body is fully typed
|
|
54
|
-
return { created: ctx.body };
|
|
55
|
-
}, {
|
|
56
|
-
body: z.object({
|
|
57
|
-
name: z.string(),
|
|
58
|
-
email: z.string().email()
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// Path parameters
|
|
63
|
-
.get('/users/:id', async (ctx) => {
|
|
64
|
-
// ctx.params.id is typed as UUID string
|
|
65
|
-
return { user: { id: ctx.params.id } };
|
|
66
|
-
}, {
|
|
67
|
-
params: z.object({
|
|
68
|
-
id: z.string().uuid()
|
|
69
|
-
}),
|
|
70
|
-
query: z.object({
|
|
71
|
-
include: z.string().optional()
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
// All HTTP methods supported
|
|
76
|
-
.put('/users/:id', handler)
|
|
77
|
-
.delete('/users/:id', handler)
|
|
78
|
-
.patch('/users/:id', handler);
|
|
33
|
+
app.start();
|
|
79
34
|
```
|
|
80
35
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
The context object (`ctx`) provides access to request data and response configuration:
|
|
36
|
+
## Lifecycle Hooks
|
|
84
37
|
|
|
85
|
-
|
|
86
|
-
interface Context<TConfig> {
|
|
87
|
-
params: InferredParamsType; // Path parameters
|
|
88
|
-
query: InferredQueryType; // Query string parameters
|
|
89
|
-
body: InferredBodyType; // Request body
|
|
90
|
-
headers: InferredHeadersType; // Request headers
|
|
91
|
-
request: Request; // Original Request object
|
|
92
|
-
set: { // Response configuration
|
|
93
|
-
status?: number;
|
|
94
|
-
headers?: Record<string, string>;
|
|
95
|
-
};
|
|
96
|
-
status: (code: number, data?: any) => any; // Type-safe status method
|
|
97
|
-
user?: any; // Added by auth plugin
|
|
98
|
-
[key: string]: any; // Extended by plugins
|
|
99
|
-
}
|
|
100
|
-
```
|
|
38
|
+
BXO provides powerful lifecycle hooks that allow you to intercept and modify requests and responses at different stages:
|
|
101
39
|
|
|
102
|
-
###
|
|
103
|
-
|
|
104
|
-
BXO provides a type-safe `ctx.status()` method similar to Elysia, which allows you to set HTTP status codes and return data in one call:
|
|
40
|
+
### beforeRequest
|
|
41
|
+
Runs before any route processing. Can modify the request or return a response to short-circuit processing.
|
|
105
42
|
|
|
106
43
|
```typescript
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// With response validation
|
|
113
|
-
app.get('/user/:id', (ctx) => {
|
|
114
|
-
const userId = ctx.params.id;
|
|
115
|
-
|
|
116
|
-
if (userId === 'not-found') {
|
|
117
|
-
// TypeScript suggests 404 as valid status
|
|
118
|
-
return ctx.status(404, { error: 'User not found' });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// TypeScript suggests 200 as valid status
|
|
122
|
-
return ctx.status(200, { user: { id: userId, name: 'John Doe' } });
|
|
123
|
-
}, {
|
|
124
|
-
response: {
|
|
125
|
-
200: z.object({
|
|
126
|
-
user: z.object({
|
|
127
|
-
id: z.string(),
|
|
128
|
-
name: z.string()
|
|
129
|
-
})
|
|
130
|
-
}),
|
|
131
|
-
404: z.object({
|
|
132
|
-
error: z.string()
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// POST with validation and status responses
|
|
138
|
-
app.post('/users', (ctx) => {
|
|
139
|
-
const { name, email } = ctx.body;
|
|
140
|
-
|
|
141
|
-
if (!name || !email) {
|
|
142
|
-
return ctx.status(400, { error: 'Missing required fields' });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return ctx.status(201, {
|
|
146
|
-
success: true,
|
|
147
|
-
user: { id: 1, name, email }
|
|
148
|
-
});
|
|
149
|
-
}, {
|
|
150
|
-
body: z.object({
|
|
151
|
-
name: z.string(),
|
|
152
|
-
email: z.string().email()
|
|
153
|
-
}),
|
|
154
|
-
response: {
|
|
155
|
-
201: z.object({
|
|
156
|
-
success: z.boolean(),
|
|
157
|
-
user: z.object({
|
|
158
|
-
id: z.number(),
|
|
159
|
-
name: z.string(),
|
|
160
|
-
email: z.string()
|
|
161
|
-
})
|
|
162
|
-
}),
|
|
163
|
-
400: z.object({
|
|
164
|
-
error: z.string()
|
|
165
|
-
})
|
|
166
|
-
}
|
|
44
|
+
app.beforeRequest(async (req) => {
|
|
45
|
+
console.log(`${req.method} ${req.url}`);
|
|
46
|
+
return req; // Continue with request
|
|
167
47
|
});
|
|
168
48
|
```
|
|
169
49
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- **Data Validation**: Return data is validated against the corresponding schema
|
|
173
|
-
- **Autocomplete**: TypeScript provides autocomplete for valid status codes
|
|
174
|
-
- **Return Type Inference**: Return types are properly inferred from schemas
|
|
175
|
-
|
|
176
|
-
### Validation Configuration
|
|
177
|
-
|
|
178
|
-
Each route can specify validation schemas for different parts of the request:
|
|
50
|
+
### afterRequest
|
|
51
|
+
Runs after route processing but before the response is sent. Can modify the final response.
|
|
179
52
|
|
|
180
53
|
```typescript
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
limit: z.coerce.number().max(100).default(10)
|
|
186
|
-
}),
|
|
187
|
-
body: z.object({
|
|
188
|
-
name: z.string().min(1),
|
|
189
|
-
email: z.string().email()
|
|
190
|
-
}),
|
|
191
|
-
headers: z.object({
|
|
192
|
-
'content-type': z.literal('application/json')
|
|
193
|
-
})
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
app.post('/api/users/:id', async (ctx) => {
|
|
197
|
-
// All properties are fully typed based on schemas
|
|
198
|
-
const { id } = ctx.params; // string (UUID)
|
|
199
|
-
const { page, limit } = ctx.query; // number, number
|
|
200
|
-
const { name, email } = ctx.body; // string, string
|
|
201
|
-
}, config);
|
|
54
|
+
app.afterRequest(async (req, res) => {
|
|
55
|
+
res.headers.set("X-Response-Time", Date.now().toString());
|
|
56
|
+
return res;
|
|
57
|
+
});
|
|
202
58
|
```
|
|
203
59
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
BXO has a powerful plugin system with lifecycle hooks. Plugins can be either middleware-style plugins or full BXO instances. Middleware plugins are separate modules imported from `./plugins` and are executed in the order they are added.
|
|
207
|
-
|
|
208
|
-
### Available Plugins
|
|
209
|
-
|
|
210
|
-
#### CORS Plugin
|
|
60
|
+
### beforeResponse
|
|
61
|
+
Runs after the route handler but before response headers are merged. Useful for modifying response metadata.
|
|
211
62
|
|
|
212
63
|
```typescript
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
218
|
-
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
219
|
-
credentials: true,
|
|
220
|
-
maxAge: 86400
|
|
221
|
-
}));
|
|
64
|
+
app.beforeResponse(async (res) => {
|
|
65
|
+
res.headers.set("X-Custom-Header", "value");
|
|
66
|
+
return res;
|
|
67
|
+
});
|
|
222
68
|
```
|
|
223
69
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
- **Referer Header Fallback**: Automatically falls back to Referer header when Origin is not present
|
|
227
|
-
- **Origin Precedence**: When both Origin and Referer are present, Origin takes precedence
|
|
228
|
-
- **Invalid URL Handling**: Gracefully handles invalid Referer URLs
|
|
229
|
-
- **Flexible Origin Configuration**: Supports string, array, boolean, or wildcard (`*`) origins
|
|
230
|
-
|
|
231
|
-
#### Logger Plugin
|
|
70
|
+
### onError
|
|
71
|
+
Runs when an error occurs during request processing. Can return a custom error response.
|
|
232
72
|
|
|
233
73
|
```typescript
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
includeBody: false, // Log request/response bodies
|
|
239
|
-
includeHeaders: false // Log headers
|
|
240
|
-
}));
|
|
74
|
+
app.onError(async (error, req) => {
|
|
75
|
+
console.error(`Error: ${error.message}`);
|
|
76
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
77
|
+
});
|
|
241
78
|
```
|
|
242
79
|
|
|
243
|
-
|
|
80
|
+
## Plugins
|
|
244
81
|
|
|
245
|
-
|
|
246
|
-
import { auth, createJWT } from './plugins';
|
|
247
|
-
|
|
248
|
-
app.use(auth({
|
|
249
|
-
type: 'jwt', // 'jwt' | 'bearer' | 'apikey'
|
|
250
|
-
secret: 'your-secret-key',
|
|
251
|
-
exclude: ['/login', '/health'], // Skip auth for these paths
|
|
252
|
-
verify: async (token, ctx) => {
|
|
253
|
-
// Custom token verification
|
|
254
|
-
return { user: 'data' };
|
|
255
|
-
}
|
|
256
|
-
}));
|
|
82
|
+
### CORS Plugin
|
|
257
83
|
|
|
258
|
-
|
|
259
|
-
const token = createJWT(
|
|
260
|
-
{ userId: 123, role: 'admin' },
|
|
261
|
-
'secret',
|
|
262
|
-
3600 // expires in 1 hour
|
|
263
|
-
);
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
#### Rate Limiting Plugin
|
|
84
|
+
The CORS plugin provides comprehensive Cross-Origin Resource Sharing support:
|
|
267
85
|
|
|
268
86
|
```typescript
|
|
269
|
-
import {
|
|
270
|
-
|
|
271
|
-
app.use(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
keyGenerator: (ctx) => { // Custom key generation
|
|
276
|
-
return ctx.request.headers.get('x-api-key') || 'default';
|
|
277
|
-
},
|
|
278
|
-
message: 'Too many requests',
|
|
279
|
-
statusCode: 429
|
|
87
|
+
import { cors } from "./plugins";
|
|
88
|
+
|
|
89
|
+
app.use(cors({
|
|
90
|
+
origin: ["http://localhost:3000", "https://myapp.com"],
|
|
91
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
92
|
+
credentials: true
|
|
280
93
|
}));
|
|
281
94
|
```
|
|
282
95
|
|
|
283
96
|
### Creating Custom Plugins
|
|
284
97
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
const customPlugin = {
|
|
289
|
-
name: 'custom',
|
|
290
|
-
onRequest: async (ctx) => {
|
|
291
|
-
console.log('Before request processing');
|
|
292
|
-
ctx.startTime = Date.now();
|
|
293
|
-
},
|
|
294
|
-
onResponse: async (ctx, response) => {
|
|
295
|
-
console.log(`Request took ${Date.now() - ctx.startTime}ms`);
|
|
296
|
-
return response;
|
|
297
|
-
},
|
|
298
|
-
onError: async (ctx, error) => {
|
|
299
|
-
console.error('Request failed:', error.message);
|
|
300
|
-
return { error: 'Custom error response' };
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
app.use(customPlugin);
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
#### Full BXO Instance Plugins
|
|
308
|
-
|
|
309
|
-
You can also use full BXO instances as plugins, which will merge their routes and hooks:
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
const pluginApp = new BXO();
|
|
313
|
-
pluginApp.get('/plugin-route', async (ctx) => {
|
|
314
|
-
return { message: 'From plugin' };
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
app.use(pluginApp);
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
## ๐ฃ Lifecycle Hooks
|
|
321
|
-
|
|
322
|
-
BXO provides comprehensive lifecycle hooks with a consistent before/after pattern for both server and request lifecycle:
|
|
323
|
-
|
|
324
|
-
### Server Lifecycle Hooks
|
|
98
|
+
Plugins are just BXO instances with lifecycle hooks:
|
|
325
99
|
|
|
326
100
|
```typescript
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
})
|
|
334
|
-
.onBeforeStop(() => {
|
|
335
|
-
console.log('๐ง Preparing to stop server...');
|
|
336
|
-
})
|
|
337
|
-
.onAfterStop(() => {
|
|
338
|
-
console.log('โ
Server fully stopped!');
|
|
339
|
-
})
|
|
340
|
-
.onBeforeRestart(() => {
|
|
341
|
-
console.log('๐ง Preparing to restart server...');
|
|
342
|
-
})
|
|
343
|
-
.onAfterRestart(() => {
|
|
344
|
-
console.log('โ
Server restart completed!');
|
|
345
|
-
});
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Request Lifecycle Hooks
|
|
349
|
-
|
|
350
|
-
```typescript
|
|
351
|
-
app
|
|
352
|
-
.onRequest((ctx) => {
|
|
353
|
-
console.log(`๐จ ${ctx.request.method} ${ctx.request.url}`);
|
|
354
|
-
})
|
|
355
|
-
.onResponse((ctx, response) => {
|
|
356
|
-
console.log(`๐ค Response sent`);
|
|
357
|
-
return response; // Can modify response
|
|
358
|
-
})
|
|
359
|
-
.onError((ctx, error) => {
|
|
360
|
-
console.error(`๐ฅ Error:`, error.message);
|
|
361
|
-
return { error: 'Something went wrong' }; // Can provide custom error response
|
|
101
|
+
function loggingPlugin() {
|
|
102
|
+
const plugin = new BXO();
|
|
103
|
+
|
|
104
|
+
plugin.beforeRequest(async (req) => {
|
|
105
|
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
|
|
106
|
+
return req;
|
|
362
107
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
## ๐ Hot Reload & Server Management
|
|
366
|
-
|
|
367
|
-
BXO includes built-in hot reload and comprehensive server management capabilities:
|
|
368
|
-
|
|
369
|
-
### Hot Reload
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
const app = new BXO();
|
|
373
|
-
|
|
374
|
-
// Enable hot reload - server will restart when files change
|
|
375
|
-
app.enableHotReload(['./']); // Watch current directory
|
|
376
|
-
|
|
377
|
-
// Hot reload will automatically restart the server when:
|
|
378
|
-
// - Any .ts or .js file changes in watched directories
|
|
379
|
-
// - Server lifecycle hooks are properly executed during restart
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
### Server Management
|
|
383
|
-
|
|
384
|
-
```typescript
|
|
385
|
-
const app = new BXO();
|
|
386
|
-
|
|
387
|
-
// Start server
|
|
388
|
-
await app.start(3000, 'localhost');
|
|
389
|
-
|
|
390
|
-
// Check if server is running
|
|
391
|
-
if (app.isServerRunning()) {
|
|
392
|
-
console.log('Server is running!');
|
|
108
|
+
|
|
109
|
+
return plugin;
|
|
393
110
|
}
|
|
394
111
|
|
|
395
|
-
|
|
396
|
-
const info = app.getServerInfo();
|
|
397
|
-
console.log(info); // { running: true, hotReload: true, watchedFiles: ['./'] }
|
|
398
|
-
|
|
399
|
-
// Restart server programmatically
|
|
400
|
-
await app.restart(3000, 'localhost');
|
|
401
|
-
|
|
402
|
-
// Stop server gracefully
|
|
403
|
-
await app.stop();
|
|
404
|
-
|
|
405
|
-
// Backward compatibility - listen() still works
|
|
406
|
-
await app.listen(3000); // Same as app.start(3000)
|
|
112
|
+
app.use(loggingPlugin());
|
|
407
113
|
```
|
|
408
114
|
|
|
409
|
-
|
|
115
|
+
## Route Validation
|
|
410
116
|
|
|
411
|
-
|
|
117
|
+
Define Zod schemas for request validation:
|
|
412
118
|
|
|
413
119
|
```typescript
|
|
414
|
-
|
|
415
|
-
const app = new BXO({
|
|
416
|
-
serve: {
|
|
417
|
-
port: 3000,
|
|
418
|
-
hostname: "0.0.0.0", // Listen on all interfaces
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// With TLS/HTTPS options
|
|
423
|
-
const app = new BXO({
|
|
424
|
-
serve: {
|
|
425
|
-
port: 443,
|
|
426
|
-
hostname: "localhost",
|
|
427
|
-
// tls: {
|
|
428
|
-
// cert: Bun.file("cert.pem"),
|
|
429
|
-
// key: Bun.file("key.pem"),
|
|
430
|
-
// }
|
|
431
|
-
}
|
|
432
|
-
});
|
|
120
|
+
import { z } from "bxo";
|
|
433
121
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
port: 5000,
|
|
438
|
-
development: true, // Enable development mode
|
|
439
|
-
}
|
|
122
|
+
const UserSchema = z.object({
|
|
123
|
+
name: z.string().min(1),
|
|
124
|
+
email: z.string().email()
|
|
440
125
|
});
|
|
441
126
|
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
console.error("Custom error handler:", error);
|
|
448
|
-
return new Response("Something went wrong", { status: 500 });
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// With custom fetch handler (extends framework functionality)
|
|
454
|
-
const app = new BXO({
|
|
455
|
-
serve: {
|
|
456
|
-
port: 4000,
|
|
457
|
-
// You can override the fetch handler if needed
|
|
458
|
-
// fetch: (request, server) => {
|
|
459
|
-
// // Your custom logic here
|
|
460
|
-
// // You can still call the framework's handler
|
|
461
|
-
// return app.requestHandler.handleRequest(request, server);
|
|
462
|
-
// }
|
|
463
|
-
}
|
|
127
|
+
app.post("/users", async (ctx) => {
|
|
128
|
+
const user = ctx.body; // Already validated by UserSchema
|
|
129
|
+
return ctx.json({ id: 1, ...user });
|
|
130
|
+
}, {
|
|
131
|
+
body: UserSchema
|
|
464
132
|
});
|
|
465
133
|
```
|
|
466
134
|
|
|
467
|
-
|
|
468
|
-
- `port` - Server port (default: 3000)
|
|
469
|
-
- `hostname` - Server hostname (default: 'localhost')
|
|
470
|
-
- `development` - Enable development mode
|
|
471
|
-
- `tls` - TLS/HTTPS configuration
|
|
472
|
-
- `error` - Custom error handler
|
|
473
|
-
- `fetch` - Custom fetch handler (overrides framework's handler)
|
|
474
|
-
- `websocket` - WebSocket configuration
|
|
475
|
-
- And all other [Bun.serve options](https://bun.sh/docs/api/http#bun-serve)
|
|
476
|
-
|
|
477
|
-
The framework will merge your serve options with its own defaults, ensuring that BXO's routing, middleware, and WebSocket functionality continue to work while allowing you to customize the server behavior.
|
|
478
|
-
|
|
479
|
-
### Development vs Production
|
|
480
|
-
|
|
481
|
-
```typescript
|
|
482
|
-
const app = new BXO();
|
|
483
|
-
|
|
484
|
-
if (process.env.NODE_ENV === 'development') {
|
|
485
|
-
// Enable hot reload in development
|
|
486
|
-
app.enableHotReload(['./src', './routes']);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Add server management endpoints for development
|
|
490
|
-
if (process.env.NODE_ENV === 'development') {
|
|
491
|
-
app.post('/dev/restart', async (ctx) => {
|
|
492
|
-
setTimeout(() => app.restart(3000), 100);
|
|
493
|
-
return { message: 'Server restart initiated' };
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
app.get('/dev/status', async (ctx) => {
|
|
497
|
-
return {
|
|
498
|
-
...app.getServerInfo(),
|
|
499
|
-
uptime: process.uptime(),
|
|
500
|
-
memory: process.memoryUsage()
|
|
501
|
-
};
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
## ๐ Complete Example
|
|
507
|
-
|
|
508
|
-
```typescript
|
|
509
|
-
import BXO, { z } from './index';
|
|
510
|
-
import { cors, logger, auth, rateLimit, createJWT } from './plugins';
|
|
511
|
-
|
|
512
|
-
const app = new BXO();
|
|
513
|
-
|
|
514
|
-
// Enable hot reload for development
|
|
515
|
-
app.enableHotReload(['./']);
|
|
516
|
-
|
|
517
|
-
// Add plugins
|
|
518
|
-
app
|
|
519
|
-
.use(logger({ format: 'simple' }))
|
|
520
|
-
.use(cors({
|
|
521
|
-
origin: ['http://localhost:3000'],
|
|
522
|
-
credentials: true
|
|
523
|
-
}))
|
|
524
|
-
.use(rateLimit({
|
|
525
|
-
max: 100,
|
|
526
|
-
window: 60,
|
|
527
|
-
exclude: ['/health']
|
|
528
|
-
}))
|
|
529
|
-
.use(auth({
|
|
530
|
-
type: 'jwt',
|
|
531
|
-
secret: 'your-secret-key',
|
|
532
|
-
exclude: ['/', '/login', '/health']
|
|
533
|
-
}));
|
|
534
|
-
|
|
535
|
-
// Comprehensive lifecycle hooks
|
|
536
|
-
app
|
|
537
|
-
.onBeforeStart(() => console.log('๐ง Preparing server startup...'))
|
|
538
|
-
.onAfterStart(() => console.log('โ
Server ready!'))
|
|
539
|
-
.onBeforeRestart(() => console.log('๐ Restarting server...'))
|
|
540
|
-
.onAfterRestart(() => console.log('โ
Server restarted!'))
|
|
541
|
-
.onError((ctx, error) => ({
|
|
542
|
-
error: 'Internal server error',
|
|
543
|
-
timestamp: new Date().toISOString()
|
|
544
|
-
}));
|
|
545
|
-
|
|
546
|
-
// Routes
|
|
547
|
-
app
|
|
548
|
-
.get('/health', async () => ({
|
|
549
|
-
status: 'ok',
|
|
550
|
-
timestamp: new Date().toISOString(),
|
|
551
|
-
server: app.getServerInfo()
|
|
552
|
-
}))
|
|
553
|
-
|
|
554
|
-
.post('/login', async (ctx) => {
|
|
555
|
-
const { username, password } = ctx.body;
|
|
556
|
-
|
|
557
|
-
if (username === 'admin' && password === 'password') {
|
|
558
|
-
const token = createJWT(
|
|
559
|
-
{ username, role: 'admin' },
|
|
560
|
-
'your-secret-key'
|
|
561
|
-
);
|
|
562
|
-
return { token, user: { username, role: 'admin' } };
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
ctx.set.status = 401;
|
|
566
|
-
return { error: 'Invalid credentials' };
|
|
567
|
-
}, {
|
|
568
|
-
body: z.object({
|
|
569
|
-
username: z.string(),
|
|
570
|
-
password: z.string()
|
|
571
|
-
})
|
|
572
|
-
})
|
|
573
|
-
|
|
574
|
-
.get('/users/:id', async (ctx) => {
|
|
575
|
-
return {
|
|
576
|
-
user: {
|
|
577
|
-
id: ctx.params.id,
|
|
578
|
-
include: ctx.query.include
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
|
-
}, {
|
|
582
|
-
params: z.object({ id: z.string().uuid() }),
|
|
583
|
-
query: z.object({ include: z.string().optional() })
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
.post('/users', async (ctx) => {
|
|
587
|
-
return { created: ctx.body };
|
|
588
|
-
}, {
|
|
589
|
-
body: z.object({
|
|
590
|
-
name: z.string(),
|
|
591
|
-
email: z.string().email()
|
|
592
|
-
})
|
|
593
|
-
})
|
|
594
|
-
|
|
595
|
-
.get('/protected', async (ctx) => {
|
|
596
|
-
// ctx.user available from auth plugin
|
|
597
|
-
return {
|
|
598
|
-
message: 'Protected resource',
|
|
599
|
-
user: ctx.user
|
|
600
|
-
};
|
|
601
|
-
})
|
|
602
|
-
|
|
603
|
-
// Server management endpoints
|
|
604
|
-
.post('/restart', async (ctx) => {
|
|
605
|
-
setTimeout(() => app.restart(3000), 100);
|
|
606
|
-
return { message: 'Server restart initiated' };
|
|
607
|
-
})
|
|
608
|
-
|
|
609
|
-
.get('/status', async (ctx) => {
|
|
610
|
-
return {
|
|
611
|
-
...app.getServerInfo(),
|
|
612
|
-
uptime: process.uptime(),
|
|
613
|
-
memory: process.memoryUsage()
|
|
614
|
-
};
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
// Start server with hot reload
|
|
618
|
-
app.start(3000);
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
## ๐งช Testing Endpoints
|
|
622
|
-
|
|
623
|
-
With the example server running, test these endpoints:
|
|
135
|
+
## Running
|
|
624
136
|
|
|
625
137
|
```bash
|
|
626
|
-
|
|
627
|
-
curl http://localhost:3000/health
|
|
628
|
-
|
|
629
|
-
# Login to get token
|
|
630
|
-
curl -X POST http://localhost:3000/login \
|
|
631
|
-
-H "Content-Type: application/json" \
|
|
632
|
-
-d '{"username": "admin", "password": "password"}'
|
|
633
|
-
|
|
634
|
-
# Create user
|
|
635
|
-
curl -X POST http://localhost:3000/users \
|
|
636
|
-
-H "Content-Type: application/json" \
|
|
637
|
-
-d '{"name": "John Doe", "email": "john@example.com"}'
|
|
638
|
-
|
|
639
|
-
# Get user with validation
|
|
640
|
-
curl "http://localhost:3000/users/123e4567-e89b-12d3-a456-426614174000?include=profile"
|
|
641
|
-
|
|
642
|
-
# Access protected route (use token from login)
|
|
643
|
-
curl http://localhost:3000/protected \
|
|
644
|
-
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
645
|
-
|
|
646
|
-
# Check server status
|
|
647
|
-
curl http://localhost:3000/status
|
|
648
|
-
|
|
649
|
-
# Restart server programmatically
|
|
650
|
-
curl -X POST http://localhost:3000/restart
|
|
138
|
+
bun run ./src/index.ts
|
|
651
139
|
```
|
|
652
140
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
### BXO Class Methods
|
|
656
|
-
|
|
657
|
-
#### HTTP Methods
|
|
658
|
-
- `get(path, handler, config?)` - Handle GET requests
|
|
659
|
-
- `post(path, handler, config?)` - Handle POST requests
|
|
660
|
-
- `put(path, handler, config?)` - Handle PUT requests
|
|
661
|
-
- `delete(path, handler, config?)` - Handle DELETE requests
|
|
662
|
-
- `patch(path, handler, config?)` - Handle PATCH requests
|
|
663
|
-
|
|
664
|
-
#### Plugins & Hooks
|
|
665
|
-
- `use(plugin)` - Add a plugin
|
|
666
|
-
- `onBeforeStart(handler)` - Before server start hook
|
|
667
|
-
- `onAfterStart(handler)` - After server start hook
|
|
668
|
-
- `onBeforeStop(handler)` - Before server stop hook
|
|
669
|
-
- `onAfterStop(handler)` - After server stop hook
|
|
670
|
-
- `onBeforeRestart(handler)` - Before server restart hook
|
|
671
|
-
- `onAfterRestart(handler)` - After server restart hook
|
|
672
|
-
- `onRequest(handler)` - Global request hook
|
|
673
|
-
- `onResponse(handler)` - Global response hook
|
|
674
|
-
- `onError(handler)` - Global error hook
|
|
675
|
-
|
|
676
|
-
#### Server Management
|
|
677
|
-
- `start(port?, hostname?)` - Start the server
|
|
678
|
-
- `stop()` - Stop the server gracefully
|
|
679
|
-
- `restart(port?, hostname?)` - Restart the server
|
|
680
|
-
- `listen(port?, hostname?)` - Start the server (backward compatibility)
|
|
681
|
-
- `isServerRunning()` - Check if server is running
|
|
682
|
-
- `getServerInfo()` - Get server status information
|
|
683
|
-
|
|
684
|
-
#### Hot Reload
|
|
685
|
-
- `enableHotReload(watchPaths?)` - Enable hot reload with file watching
|
|
686
|
-
|
|
687
|
-
### Route Configuration
|
|
688
|
-
|
|
689
|
-
```typescript
|
|
690
|
-
interface RouteConfig {
|
|
691
|
-
params?: z.ZodSchema<any>; // Path parameter validation
|
|
692
|
-
query?: z.ZodSchema<any>; // Query string validation
|
|
693
|
-
body?: z.ZodSchema<any>; // Request body validation
|
|
694
|
-
headers?: z.ZodSchema<any>; // Header validation
|
|
695
|
-
}
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
### Plugin Interface
|
|
699
|
-
|
|
700
|
-
```typescript
|
|
701
|
-
interface Plugin {
|
|
702
|
-
name?: string;
|
|
703
|
-
onRequest?: (ctx: Context) => Promise<void> | void;
|
|
704
|
-
onResponse?: (ctx: Context, response: any) => Promise<any> | any;
|
|
705
|
-
onError?: (ctx: Context, error: Error) => Promise<any> | any;
|
|
706
|
-
}
|
|
707
|
-
```
|
|
708
|
-
|
|
709
|
-
## ๐ ๏ธ Development
|
|
710
|
-
|
|
711
|
-
### Running the Example
|
|
141
|
+
Or run the CORS example:
|
|
712
142
|
|
|
713
143
|
```bash
|
|
714
|
-
|
|
715
|
-
bun run example.ts
|
|
716
|
-
|
|
717
|
-
# The server will automatically restart when you edit any .ts/.js files!
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
### Project Structure
|
|
721
|
-
|
|
722
|
-
```
|
|
723
|
-
bxo/
|
|
724
|
-
โโโ index.ts # Main BXO framework
|
|
725
|
-
โโโ plugins/
|
|
726
|
-
โ โโโ index.ts # Plugin exports
|
|
727
|
-
โ โโโ cors.ts # CORS plugin
|
|
728
|
-
โ โโโ logger.ts # Logger plugin
|
|
729
|
-
โ โโโ auth.ts # Authentication plugin
|
|
730
|
-
โ โโโ ratelimit.ts # Rate limiting plugin
|
|
731
|
-
โโโ example.ts # Usage example
|
|
732
|
-
โโโ package.json
|
|
733
|
-
โโโ README.md
|
|
144
|
+
bun run ./example/cors-example.ts
|
|
734
145
|
```
|
|
735
146
|
|
|
736
|
-
##
|
|
737
|
-
|
|
738
|
-
BXO is designed to be simple and extensible. Contributions are welcome!
|
|
739
|
-
|
|
740
|
-
## ๐ License
|
|
147
|
+
## Examples
|
|
741
148
|
|
|
742
|
-
|
|
149
|
+
Check out the `example/` directory for more usage examples:
|
|
743
150
|
|
|
744
|
-
|
|
151
|
+
- `cors-example.ts` - Demonstrates CORS plugin and lifecycle hooks
|
|
152
|
+
- `index.ts` - Basic routing example
|
|
745
153
|
|
|
746
|
-
|
|
154
|
+
This project was created using `bun init` in bun v1.2.3. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|