bxo 0.0.5-dev.64 โ 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 +85 -607
- 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/index.ts +0 -5
- package/plugins/README.md +0 -160
- package/plugins/ratelimit.ts +0 -136
- package/src/core/bxo.ts +0 -438
- package/src/handlers/request-handler.ts +0 -229
- package/src/types/index.ts +0 -166
- 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,676 +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
|
-
|
|
36
|
+
## Lifecycle Hooks
|
|
82
37
|
|
|
83
|
-
|
|
38
|
+
BXO provides powerful lifecycle hooks that allow you to intercept and modify requests and responses at different stages:
|
|
84
39
|
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
```
|
|
101
|
-
|
|
102
|
-
### Type-Safe Status Method
|
|
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
|
|
44
|
+
app.beforeRequest(async (req) => {
|
|
45
|
+
console.log(`${req.method} ${req.url}`);
|
|
46
|
+
return req; // Continue with request
|
|
110
47
|
});
|
|
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
|
-
}
|
|
167
|
-
});
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
**Key Features:**
|
|
171
|
-
- **Type Safety**: Status codes are suggested based on your response configuration
|
|
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:
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
const config = {
|
|
182
|
-
params: z.object({ id: z.string().uuid() }),
|
|
183
|
-
query: z.object({
|
|
184
|
-
page: z.coerce.number().default(1),
|
|
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);
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
## ๐ Plugin System
|
|
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
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
import { cors } from './plugins';
|
|
214
|
-
|
|
215
|
-
app.use(cors({
|
|
216
|
-
origin: ['http://localhost:3000', 'https://example.com'],
|
|
217
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
218
|
-
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
219
|
-
credentials: true,
|
|
220
|
-
maxAge: 86400
|
|
221
|
-
}));
|
|
222
48
|
```
|
|
223
49
|
|
|
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
|
|
50
|
+
### afterRequest
|
|
51
|
+
Runs after route processing but before the response is sent. Can modify the final response.
|
|
232
52
|
|
|
233
53
|
```typescript
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
format: 'simple', // 'simple' | 'detailed' | 'json'
|
|
238
|
-
includeBody: false, // Log request/response bodies
|
|
239
|
-
includeHeaders: false // Log headers
|
|
240
|
-
}));
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
#### Authentication Plugin
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
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
|
-
}));
|
|
257
|
-
|
|
258
|
-
// Create JWT tokens
|
|
259
|
-
const token = createJWT(
|
|
260
|
-
{ userId: 123, role: 'admin' },
|
|
261
|
-
'secret',
|
|
262
|
-
3600 // expires in 1 hour
|
|
263
|
-
);
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
#### Rate Limiting Plugin
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
import { rateLimit } from './plugins';
|
|
270
|
-
|
|
271
|
-
app.use(rateLimit({
|
|
272
|
-
max: 100, // Max requests
|
|
273
|
-
window: 60, // Time window in seconds
|
|
274
|
-
exclude: ['/health'], // Skip rate limiting for these paths
|
|
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
|
|
280
|
-
}));
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### Creating Custom Plugins
|
|
284
|
-
|
|
285
|
-
#### Middleware-Style Plugins (Recommended)
|
|
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' };
|
|
54
|
+
app.afterRequest(async (req, res) => {
|
|
55
|
+
res.headers.set("X-Response-Time", Date.now().toString());
|
|
56
|
+
return res;
|
|
315
57
|
});
|
|
316
|
-
|
|
317
|
-
app.use(pluginApp);
|
|
318
58
|
```
|
|
319
59
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
BXO provides comprehensive lifecycle hooks with a consistent before/after pattern for both server and request lifecycle:
|
|
323
|
-
|
|
324
|
-
### Server Lifecycle Hooks
|
|
60
|
+
### beforeResponse
|
|
61
|
+
Runs after the route handler but before response headers are merged. Useful for modifying response metadata.
|
|
325
62
|
|
|
326
63
|
```typescript
|
|
327
|
-
app
|
|
328
|
-
.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
.onAfterStart(() => {
|
|
332
|
-
console.log('โ
Server fully started and ready!');
|
|
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
|
-
});
|
|
64
|
+
app.beforeResponse(async (res) => {
|
|
65
|
+
res.headers.set("X-Custom-Header", "value");
|
|
66
|
+
return res;
|
|
67
|
+
});
|
|
346
68
|
```
|
|
347
69
|
|
|
348
|
-
###
|
|
70
|
+
### onError
|
|
71
|
+
Runs when an error occurs during request processing. Can return a custom error response.
|
|
349
72
|
|
|
350
73
|
```typescript
|
|
351
|
-
app
|
|
352
|
-
.
|
|
353
|
-
|
|
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
|
|
362
|
-
});
|
|
74
|
+
app.onError(async (error, req) => {
|
|
75
|
+
console.error(`Error: ${error.message}`);
|
|
76
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
77
|
+
});
|
|
363
78
|
```
|
|
364
79
|
|
|
365
|
-
##
|
|
80
|
+
## Plugins
|
|
366
81
|
|
|
367
|
-
|
|
82
|
+
### CORS Plugin
|
|
368
83
|
|
|
369
|
-
|
|
84
|
+
The CORS plugin provides comprehensive Cross-Origin Resource Sharing support:
|
|
370
85
|
|
|
371
86
|
```typescript
|
|
372
|
-
|
|
87
|
+
import { cors } from "./plugins";
|
|
373
88
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// - Server lifecycle hooks are properly executed during restart
|
|
89
|
+
app.use(cors({
|
|
90
|
+
origin: ["http://localhost:3000", "https://myapp.com"],
|
|
91
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
92
|
+
credentials: true
|
|
93
|
+
}));
|
|
380
94
|
```
|
|
381
95
|
|
|
382
|
-
###
|
|
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!');
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Get server information
|
|
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)
|
|
407
|
-
```
|
|
96
|
+
### Creating Custom Plugins
|
|
408
97
|
|
|
409
|
-
|
|
98
|
+
Plugins are just BXO instances with lifecycle hooks:
|
|
410
99
|
|
|
411
100
|
```typescript
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (process.env.NODE_ENV === 'development') {
|
|
415
|
-
// Enable hot reload in development
|
|
416
|
-
app.enableHotReload(['./src', './routes']);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Add server management endpoints for development
|
|
420
|
-
if (process.env.NODE_ENV === 'development') {
|
|
421
|
-
app.post('/dev/restart', async (ctx) => {
|
|
422
|
-
setTimeout(() => app.restart(3000), 100);
|
|
423
|
-
return { message: 'Server restart initiated' };
|
|
424
|
-
});
|
|
101
|
+
function loggingPlugin() {
|
|
102
|
+
const plugin = new BXO();
|
|
425
103
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
uptime: process.uptime(),
|
|
430
|
-
memory: process.memoryUsage()
|
|
431
|
-
};
|
|
104
|
+
plugin.beforeRequest(async (req) => {
|
|
105
|
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
|
|
106
|
+
return req;
|
|
432
107
|
});
|
|
433
|
-
}
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
## ๐ Complete Example
|
|
437
|
-
|
|
438
|
-
```typescript
|
|
439
|
-
import BXO, { z } from './index';
|
|
440
|
-
import { cors, logger, auth, rateLimit, createJWT } from './plugins';
|
|
441
|
-
|
|
442
|
-
const app = new BXO();
|
|
443
|
-
|
|
444
|
-
// Enable hot reload for development
|
|
445
|
-
app.enableHotReload(['./']);
|
|
446
|
-
|
|
447
|
-
// Add plugins
|
|
448
|
-
app
|
|
449
|
-
.use(logger({ format: 'simple' }))
|
|
450
|
-
.use(cors({
|
|
451
|
-
origin: ['http://localhost:3000'],
|
|
452
|
-
credentials: true
|
|
453
|
-
}))
|
|
454
|
-
.use(rateLimit({
|
|
455
|
-
max: 100,
|
|
456
|
-
window: 60,
|
|
457
|
-
exclude: ['/health']
|
|
458
|
-
}))
|
|
459
|
-
.use(auth({
|
|
460
|
-
type: 'jwt',
|
|
461
|
-
secret: 'your-secret-key',
|
|
462
|
-
exclude: ['/', '/login', '/health']
|
|
463
|
-
}));
|
|
464
|
-
|
|
465
|
-
// Comprehensive lifecycle hooks
|
|
466
|
-
app
|
|
467
|
-
.onBeforeStart(() => console.log('๐ง Preparing server startup...'))
|
|
468
|
-
.onAfterStart(() => console.log('โ
Server ready!'))
|
|
469
|
-
.onBeforeRestart(() => console.log('๐ Restarting server...'))
|
|
470
|
-
.onAfterRestart(() => console.log('โ
Server restarted!'))
|
|
471
|
-
.onError((ctx, error) => ({
|
|
472
|
-
error: 'Internal server error',
|
|
473
|
-
timestamp: new Date().toISOString()
|
|
474
|
-
}));
|
|
475
|
-
|
|
476
|
-
// Routes
|
|
477
|
-
app
|
|
478
|
-
.get('/health', async () => ({
|
|
479
|
-
status: 'ok',
|
|
480
|
-
timestamp: new Date().toISOString(),
|
|
481
|
-
server: app.getServerInfo()
|
|
482
|
-
}))
|
|
483
|
-
|
|
484
|
-
.post('/login', async (ctx) => {
|
|
485
|
-
const { username, password } = ctx.body;
|
|
486
|
-
|
|
487
|
-
if (username === 'admin' && password === 'password') {
|
|
488
|
-
const token = createJWT(
|
|
489
|
-
{ username, role: 'admin' },
|
|
490
|
-
'your-secret-key'
|
|
491
|
-
);
|
|
492
|
-
return { token, user: { username, role: 'admin' } };
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
ctx.set.status = 401;
|
|
496
|
-
return { error: 'Invalid credentials' };
|
|
497
|
-
}, {
|
|
498
|
-
body: z.object({
|
|
499
|
-
username: z.string(),
|
|
500
|
-
password: z.string()
|
|
501
|
-
})
|
|
502
|
-
})
|
|
503
|
-
|
|
504
|
-
.get('/users/:id', async (ctx) => {
|
|
505
|
-
return {
|
|
506
|
-
user: {
|
|
507
|
-
id: ctx.params.id,
|
|
508
|
-
include: ctx.query.include
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
}, {
|
|
512
|
-
params: z.object({ id: z.string().uuid() }),
|
|
513
|
-
query: z.object({ include: z.string().optional() })
|
|
514
|
-
})
|
|
515
108
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}, {
|
|
519
|
-
body: z.object({
|
|
520
|
-
name: z.string(),
|
|
521
|
-
email: z.string().email()
|
|
522
|
-
})
|
|
523
|
-
})
|
|
524
|
-
|
|
525
|
-
.get('/protected', async (ctx) => {
|
|
526
|
-
// ctx.user available from auth plugin
|
|
527
|
-
return {
|
|
528
|
-
message: 'Protected resource',
|
|
529
|
-
user: ctx.user
|
|
530
|
-
};
|
|
531
|
-
})
|
|
532
|
-
|
|
533
|
-
// Server management endpoints
|
|
534
|
-
.post('/restart', async (ctx) => {
|
|
535
|
-
setTimeout(() => app.restart(3000), 100);
|
|
536
|
-
return { message: 'Server restart initiated' };
|
|
537
|
-
})
|
|
538
|
-
|
|
539
|
-
.get('/status', async (ctx) => {
|
|
540
|
-
return {
|
|
541
|
-
...app.getServerInfo(),
|
|
542
|
-
uptime: process.uptime(),
|
|
543
|
-
memory: process.memoryUsage()
|
|
544
|
-
};
|
|
545
|
-
});
|
|
109
|
+
return plugin;
|
|
110
|
+
}
|
|
546
111
|
|
|
547
|
-
|
|
548
|
-
app.start(3000);
|
|
112
|
+
app.use(loggingPlugin());
|
|
549
113
|
```
|
|
550
114
|
|
|
551
|
-
##
|
|
552
|
-
|
|
553
|
-
With the example server running, test these endpoints:
|
|
554
|
-
|
|
555
|
-
```bash
|
|
556
|
-
# Health check
|
|
557
|
-
curl http://localhost:3000/health
|
|
558
|
-
|
|
559
|
-
# Login to get token
|
|
560
|
-
curl -X POST http://localhost:3000/login \
|
|
561
|
-
-H "Content-Type: application/json" \
|
|
562
|
-
-d '{"username": "admin", "password": "password"}'
|
|
563
|
-
|
|
564
|
-
# Create user
|
|
565
|
-
curl -X POST http://localhost:3000/users \
|
|
566
|
-
-H "Content-Type: application/json" \
|
|
567
|
-
-d '{"name": "John Doe", "email": "john@example.com"}'
|
|
568
|
-
|
|
569
|
-
# Get user with validation
|
|
570
|
-
curl "http://localhost:3000/users/123e4567-e89b-12d3-a456-426614174000?include=profile"
|
|
571
|
-
|
|
572
|
-
# Access protected route (use token from login)
|
|
573
|
-
curl http://localhost:3000/protected \
|
|
574
|
-
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
115
|
+
## Route Validation
|
|
575
116
|
|
|
576
|
-
|
|
577
|
-
curl http://localhost:3000/status
|
|
578
|
-
|
|
579
|
-
# Restart server programmatically
|
|
580
|
-
curl -X POST http://localhost:3000/restart
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
## ๐ API Reference
|
|
584
|
-
|
|
585
|
-
### BXO Class Methods
|
|
586
|
-
|
|
587
|
-
#### HTTP Methods
|
|
588
|
-
- `get(path, handler, config?)` - Handle GET requests
|
|
589
|
-
- `post(path, handler, config?)` - Handle POST requests
|
|
590
|
-
- `put(path, handler, config?)` - Handle PUT requests
|
|
591
|
-
- `delete(path, handler, config?)` - Handle DELETE requests
|
|
592
|
-
- `patch(path, handler, config?)` - Handle PATCH requests
|
|
593
|
-
|
|
594
|
-
#### Plugins & Hooks
|
|
595
|
-
- `use(plugin)` - Add a plugin
|
|
596
|
-
- `onBeforeStart(handler)` - Before server start hook
|
|
597
|
-
- `onAfterStart(handler)` - After server start hook
|
|
598
|
-
- `onBeforeStop(handler)` - Before server stop hook
|
|
599
|
-
- `onAfterStop(handler)` - After server stop hook
|
|
600
|
-
- `onBeforeRestart(handler)` - Before server restart hook
|
|
601
|
-
- `onAfterRestart(handler)` - After server restart hook
|
|
602
|
-
- `onRequest(handler)` - Global request hook
|
|
603
|
-
- `onResponse(handler)` - Global response hook
|
|
604
|
-
- `onError(handler)` - Global error hook
|
|
605
|
-
|
|
606
|
-
#### Server Management
|
|
607
|
-
- `start(port?, hostname?)` - Start the server
|
|
608
|
-
- `stop()` - Stop the server gracefully
|
|
609
|
-
- `restart(port?, hostname?)` - Restart the server
|
|
610
|
-
- `listen(port?, hostname?)` - Start the server (backward compatibility)
|
|
611
|
-
- `isServerRunning()` - Check if server is running
|
|
612
|
-
- `getServerInfo()` - Get server status information
|
|
613
|
-
|
|
614
|
-
#### Hot Reload
|
|
615
|
-
- `enableHotReload(watchPaths?)` - Enable hot reload with file watching
|
|
616
|
-
|
|
617
|
-
### Route Configuration
|
|
117
|
+
Define Zod schemas for request validation:
|
|
618
118
|
|
|
619
119
|
```typescript
|
|
620
|
-
|
|
621
|
-
params?: z.ZodSchema<any>; // Path parameter validation
|
|
622
|
-
query?: z.ZodSchema<any>; // Query string validation
|
|
623
|
-
body?: z.ZodSchema<any>; // Request body validation
|
|
624
|
-
headers?: z.ZodSchema<any>; // Header validation
|
|
625
|
-
}
|
|
626
|
-
```
|
|
120
|
+
import { z } from "bxo";
|
|
627
121
|
|
|
628
|
-
|
|
122
|
+
const UserSchema = z.object({
|
|
123
|
+
name: z.string().min(1),
|
|
124
|
+
email: z.string().email()
|
|
125
|
+
});
|
|
629
126
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
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
|
|
132
|
+
});
|
|
637
133
|
```
|
|
638
134
|
|
|
639
|
-
##
|
|
640
|
-
|
|
641
|
-
### Running the Example
|
|
135
|
+
## Running
|
|
642
136
|
|
|
643
137
|
```bash
|
|
644
|
-
|
|
645
|
-
bun run example.ts
|
|
646
|
-
|
|
647
|
-
# The server will automatically restart when you edit any .ts/.js files!
|
|
138
|
+
bun run ./src/index.ts
|
|
648
139
|
```
|
|
649
140
|
|
|
650
|
-
|
|
141
|
+
Or run the CORS example:
|
|
651
142
|
|
|
143
|
+
```bash
|
|
144
|
+
bun run ./example/cors-example.ts
|
|
652
145
|
```
|
|
653
|
-
bxo/
|
|
654
|
-
โโโ index.ts # Main BXO framework
|
|
655
|
-
โโโ plugins/
|
|
656
|
-
โ โโโ index.ts # Plugin exports
|
|
657
|
-
โ โโโ cors.ts # CORS plugin
|
|
658
|
-
โ โโโ logger.ts # Logger plugin
|
|
659
|
-
โ โโโ auth.ts # Authentication plugin
|
|
660
|
-
โ โโโ ratelimit.ts # Rate limiting plugin
|
|
661
|
-
โโโ example.ts # Usage example
|
|
662
|
-
โโโ package.json
|
|
663
|
-
โโโ README.md
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
## ๐ค Contributing
|
|
667
|
-
|
|
668
|
-
BXO is designed to be simple and extensible. Contributions are welcome!
|
|
669
146
|
|
|
670
|
-
##
|
|
147
|
+
## Examples
|
|
671
148
|
|
|
672
|
-
|
|
149
|
+
Check out the `example/` directory for more usage examples:
|
|
673
150
|
|
|
674
|
-
|
|
151
|
+
- `cors-example.ts` - Demonstrates CORS plugin and lifecycle hooks
|
|
152
|
+
- `index.ts` - Basic routing example
|
|
675
153
|
|
|
676
|
-
|
|
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.
|