bxo 0.0.5-dev.8 → 0.0.5-dev.81

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 CHANGED
@@ -1,579 +1,380 @@
1
- # 🦊 BXO - A Type-Safe Web Framework for Bun
1
+ # bxo
2
2
 
3
- BXO is a fast, lightweight, and fully type-safe web framework built specifically for Bun runtime. Inspired by Elysia, it provides excellent developer experience with automatic type inference, Zod validation, and a powerful plugin system.
3
+ A fast, lightweight web framework for Bun with built-in Zod validation and lifecycle hooks.
4
4
 
5
- ## Features
5
+ ## Features
6
6
 
7
- - 🚀 **Built for Bun** - Leverages Bun's native HTTP server for maximum performance
8
- - 🔒 **Type-Safe** - Full TypeScript support with automatic type inference
9
- - 📋 **Zod Validation** - Built-in request/response validation using Zod schemas
10
- - 🔌 **Plugin System** - Extensible architecture with lifecycle hooks
11
- - 🎣 **Lifecycle Hooks** - Complete control over server and request/response lifecycle
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
+ - **WebSocket support** with clean API
9
+ - **Lifecycle hooks** for middleware and plugins
10
+ - **Plugin system** for extending functionality
11
+ - **Built-in CORS support** via plugin
12
+ - **Fast performance** leveraging Bun's native HTTP server
17
13
 
18
- ## 🚀 Quick Start
19
-
20
- ### Installation
14
+ ## Installation
21
15
 
22
16
  ```bash
23
- bun add zod
17
+ bun install
24
18
  ```
25
19
 
26
- ### Basic Usage
20
+ ## Quick Start
27
21
 
28
22
  ```typescript
29
- import BXO, { z } from './index';
30
-
31
- const app = new BXO()
32
- .get('/', async (ctx) => {
33
- return { message: 'Hello, BXO!' };
34
- })
35
- .start(3000);
36
- ```
23
+ import BXO from "./src/index";
24
+ import { cors } from "./plugins";
37
25
 
38
- ## 📚 Documentation
26
+ const app = new BXO();
39
27
 
40
- ### HTTP Handlers
28
+ // Use CORS plugin
29
+ app.use(cors());
41
30
 
42
- BXO supports all standard HTTP methods with fluent chaining:
31
+ // Define routes
32
+ app.get("/", (ctx) => ctx.json({ message: "Hello World!" }));
43
33
 
44
- ```typescript
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);
34
+ app.start();
79
35
  ```
80
36
 
81
- ### Context Object
37
+ ## WebSocket Support
82
38
 
83
- The context object (`ctx`) provides access to request data and response configuration:
39
+ BXO provides built-in WebSocket support with a clean, intuitive API:
84
40
 
85
41
  ```typescript
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
- user?: any; // Added by auth plugin
97
- [key: string]: any; // Extended by plugins
98
- }
99
- ```
42
+ import BXO from "./src";
100
43
 
101
- ### Validation Configuration
44
+ const app = new BXO();
102
45
 
103
- Each route can specify validation schemas for different parts of the request:
46
+ // WebSocket route
47
+ app.ws("/ws", {
48
+ open(ws) {
49
+ console.log("WebSocket connection opened");
50
+ ws.send("Welcome to BXO WebSocket!");
51
+ },
52
+
53
+ message(ws, message) {
54
+ console.log("Received message:", message);
55
+ // Echo the message back
56
+ ws.send(`Echo: ${message}`);
57
+ },
58
+
59
+ close(ws, code, reason) {
60
+ console.log(`WebSocket connection closed: ${code} ${reason}`);
61
+ },
62
+
63
+ ping(ws, data) {
64
+ console.log("Ping received:", data);
65
+ },
66
+
67
+ pong(ws, data) {
68
+ console.log("Pong received:", data);
69
+ }
70
+ });
71
+
72
+ // WebSocket with path parameters
73
+ app.ws("/chat/:room", {
74
+ open(ws) {
75
+ const room = ws.data?.room || 'unknown';
76
+ console.log(`WebSocket connection opened for room: ${room}`);
77
+ ws.send(`Welcome to chat room: ${room}`);
78
+ },
79
+
80
+ message(ws, message) {
81
+ const room = ws.data?.room || 'unknown';
82
+ console.log(`Message in room ${room}:`, message);
83
+ ws.send(`[${room}] Echo: ${message}`);
84
+ }
85
+ });
104
86
 
105
- ```typescript
106
- const config = {
107
- params: z.object({ id: z.string().uuid() }),
108
- query: z.object({
109
- page: z.coerce.number().default(1),
110
- limit: z.coerce.number().max(100).default(10)
111
- }),
112
- body: z.object({
113
- name: z.string().min(1),
114
- email: z.string().email()
115
- }),
116
- headers: z.object({
117
- 'content-type': z.literal('application/json')
118
- })
119
- };
120
-
121
- app.post('/api/users/:id', async (ctx) => {
122
- // All properties are fully typed based on schemas
123
- const { id } = ctx.params; // string (UUID)
124
- const { page, limit } = ctx.query; // number, number
125
- const { name, email } = ctx.body; // string, string
126
- }, config);
87
+ app.start();
127
88
  ```
128
89
 
129
- ## 🔌 Plugin System
90
+ ### WebSocket Handler Events
130
91
 
131
- BXO has a powerful plugin system with lifecycle hooks. Plugins are separate modules imported from `./plugins`.
92
+ - `open(ws)` - Called when a WebSocket connection is established
93
+ - `message(ws, message)` - Called when a message is received
94
+ - `close(ws, code, reason)` - Called when the connection is closed
95
+ - `drain(ws)` - Called when the WebSocket is ready for more data
96
+ - `ping(ws, data)` - Called when a ping is received
97
+ - `pong(ws, data)` - Called when a pong is received
132
98
 
133
- ### Available Plugins
99
+ ### WebSocket Features
134
100
 
135
- #### CORS Plugin
101
+ - **Path parameters** - Support for dynamic routes like `/chat/:room`
102
+ - **Automatic upgrade** - HTTP requests to WebSocket routes are automatically upgraded
103
+ - **Type safety** - Full TypeScript support with proper typing
104
+ - **Error handling** - Built-in error handling for WebSocket events
105
+ - **Data attachment** - Access to path information via `ws.data`
136
106
 
137
- ```typescript
138
- import { cors } from './plugins';
107
+ ## Lifecycle Hooks
139
108
 
140
- app.use(cors({
141
- origin: ['http://localhost:3000', 'https://example.com'],
142
- methods: ['GET', 'POST', 'PUT', 'DELETE'],
143
- allowedHeaders: ['Content-Type', 'Authorization'],
144
- credentials: true,
145
- maxAge: 86400
146
- }));
147
- ```
109
+ BXO provides powerful lifecycle hooks that allow you to intercept and modify requests and responses at different stages:
148
110
 
149
- #### Logger Plugin
111
+ ### beforeRequest
112
+ Runs before any route processing. Can modify the request or return a response to short-circuit processing.
150
113
 
151
114
  ```typescript
152
- import { logger } from './plugins';
153
-
154
- app.use(logger({
155
- format: 'simple', // 'simple' | 'detailed' | 'json'
156
- includeBody: false, // Log request/response bodies
157
- includeHeaders: false // Log headers
158
- }));
115
+ app.beforeRequest(async (req) => {
116
+ console.log(`${req.method} ${req.url}`);
117
+ return req; // Continue with request
118
+ });
159
119
  ```
160
120
 
161
- #### Authentication Plugin
121
+ ### afterRequest
122
+ Runs after route processing but before the response is sent. Can modify the final response.
162
123
 
163
124
  ```typescript
164
- import { auth, createJWT } from './plugins';
165
-
166
- app.use(auth({
167
- type: 'jwt', // 'jwt' | 'bearer' | 'apikey'
168
- secret: 'your-secret-key',
169
- exclude: ['/login', '/health'], // Skip auth for these paths
170
- verify: async (token, ctx) => {
171
- // Custom token verification
172
- return { user: 'data' };
173
- }
174
- }));
175
-
176
- // Create JWT tokens
177
- const token = createJWT(
178
- { userId: 123, role: 'admin' },
179
- 'secret',
180
- 3600 // expires in 1 hour
181
- );
125
+ app.afterRequest(async (req, res) => {
126
+ res.headers.set("X-Response-Time", Date.now().toString());
127
+ return res;
128
+ });
182
129
  ```
183
130
 
184
- #### Rate Limiting Plugin
131
+ ### beforeResponse
132
+ Runs after the route handler but before response headers are merged. Useful for modifying response metadata.
185
133
 
186
134
  ```typescript
187
- import { rateLimit } from './plugins';
188
-
189
- app.use(rateLimit({
190
- max: 100, // Max requests
191
- window: 60, // Time window in seconds
192
- exclude: ['/health'], // Skip rate limiting for these paths
193
- keyGenerator: (ctx) => { // Custom key generation
194
- return ctx.request.headers.get('x-api-key') || 'default';
195
- },
196
- message: 'Too many requests',
197
- statusCode: 429
198
- }));
135
+ app.beforeResponse(async (res) => {
136
+ res.headers.set("X-Custom-Header", "value");
137
+ return res;
138
+ });
199
139
  ```
200
140
 
201
- ### Creating Custom Plugins
141
+ ### onError
142
+ Runs when an error occurs during request processing. Can return a custom error response.
202
143
 
203
144
  ```typescript
204
- const customPlugin = {
205
- name: 'custom',
206
- onRequest: async (ctx) => {
207
- console.log('Before request processing');
208
- ctx.startTime = Date.now();
209
- },
210
- onResponse: async (ctx, response) => {
211
- console.log(`Request took ${Date.now() - ctx.startTime}ms`);
212
- return response;
213
- },
214
- onError: async (ctx, error) => {
215
- console.error('Request failed:', error.message);
216
- return { error: 'Custom error response' };
217
- }
218
- };
219
-
220
- app.use(customPlugin);
145
+ app.onError(async (error, req) => {
146
+ console.error(`Error: ${error.message}`);
147
+ return new Response("Internal Server Error", { status: 500 });
148
+ });
221
149
  ```
222
150
 
223
- ## 🎣 Lifecycle Hooks
151
+ ## Plugins
224
152
 
225
- BXO provides comprehensive lifecycle hooks with a consistent before/after pattern for both server and request lifecycle:
153
+ ### CORS Plugin
226
154
 
227
- ### Server Lifecycle Hooks
155
+ The CORS plugin provides comprehensive Cross-Origin Resource Sharing support:
228
156
 
229
157
  ```typescript
230
- app
231
- .onBeforeStart(() => {
232
- console.log('🔧 Preparing to start server...');
233
- })
234
- .onAfterStart(() => {
235
- console.log('✅ Server fully started and ready!');
236
- })
237
- .onBeforeStop(() => {
238
- console.log('🔧 Preparing to stop server...');
239
- })
240
- .onAfterStop(() => {
241
- console.log('✅ Server fully stopped!');
242
- })
243
- .onBeforeRestart(() => {
244
- console.log('🔧 Preparing to restart server...');
245
- })
246
- .onAfterRestart(() => {
247
- console.log('✅ Server restart completed!');
248
- });
249
- ```
158
+ import { cors } from "./plugins";
250
159
 
251
- ### Request Lifecycle Hooks
252
-
253
- ```typescript
254
- app
255
- .onRequest((ctx) => {
256
- console.log(`📨 ${ctx.request.method} ${ctx.request.url}`);
257
- })
258
- .onResponse((ctx, response) => {
259
- console.log(`📤 Response sent`);
260
- return response; // Can modify response
261
- })
262
- .onError((ctx, error) => {
263
- console.error(`💥 Error:`, error.message);
264
- return { error: 'Something went wrong' }; // Can provide custom error response
265
- });
160
+ app.use(cors({
161
+ origin: ["http://localhost:3000", "https://myapp.com"],
162
+ methods: ["GET", "POST", "PUT", "DELETE"],
163
+ credentials: true
164
+ }));
266
165
  ```
267
166
 
268
- ## 🔄 Hot Reload & Server Management
269
-
270
- BXO includes built-in hot reload and comprehensive server management capabilities:
167
+ ### OpenAPI Plugin
271
168
 
272
- ### Hot Reload
169
+ The OpenAPI plugin automatically generates OpenAPI 3.0 documentation with support for tags, security schemes, and comprehensive route metadata:
273
170
 
274
171
  ```typescript
275
- const app = new BXO();
276
-
277
- // Enable hot reload - server will restart when files change
278
- app.enableHotReload(['./']); // Watch current directory
279
-
280
- // Hot reload will automatically restart the server when:
281
- // - Any .ts or .js file changes in watched directories
282
- // - Server lifecycle hooks are properly executed during restart
172
+ import { openapi } from "./plugins";
173
+
174
+ app.use(openapi({
175
+ path: "/docs", // Swagger UI endpoint
176
+ jsonPath: "/openapi.json", // OpenAPI JSON endpoint
177
+ defaultTags: ["API"], // Default tags for routes
178
+ securitySchemes: { // Define security schemes
179
+ bearerAuth: {
180
+ type: "http",
181
+ scheme: "bearer",
182
+ bearerFormat: "JWT",
183
+ description: "JWT token for authentication"
184
+ },
185
+ apiKeyAuth: {
186
+ type: "apiKey",
187
+ in: "header",
188
+ name: "X-API-Key",
189
+ description: "API key for authentication"
190
+ }
191
+ },
192
+ globalSecurity: [ // Global security requirements
193
+ { bearerAuth: [] },
194
+ { apiKeyAuth: [] }
195
+ ],
196
+ openapiConfig: { // Additional OpenAPI config
197
+ info: {
198
+ title: "My API",
199
+ version: "1.0.0",
200
+ description: "API description"
201
+ }
202
+ }
203
+ }));
283
204
  ```
284
205
 
285
- ### Server Management
286
-
287
- ```typescript
288
- const app = new BXO();
289
-
290
- // Start server
291
- await app.start(3000, 'localhost');
292
-
293
- // Check if server is running
294
- if (app.isServerRunning()) {
295
- console.log('Server is running!');
296
- }
297
-
298
- // Get server information
299
- const info = app.getServerInfo();
300
- console.log(info); // { running: true, hotReload: true, watchedFiles: ['./'] }
206
+ #### Route Metadata
301
207
 
302
- // Restart server programmatically
303
- await app.restart(3000, 'localhost');
304
-
305
- // Stop server gracefully
306
- await app.stop();
307
-
308
- // Backward compatibility - listen() still works
309
- await app.listen(3000); // Same as app.start(3000)
310
- ```
311
-
312
- ### Development vs Production
208
+ Routes can include detailed metadata for better OpenAPI documentation:
313
209
 
314
210
  ```typescript
315
- const app = new BXO();
316
-
317
- if (process.env.NODE_ENV === 'development') {
318
- // Enable hot reload in development
319
- app.enableHotReload(['./src', './routes']);
320
- }
321
-
322
- // Add server management endpoints for development
323
- if (process.env.NODE_ENV === 'development') {
324
- app.post('/dev/restart', async (ctx) => {
325
- setTimeout(() => app.restart(3000), 100);
326
- return { message: 'Server restart initiated' };
327
- });
328
-
329
- app.get('/dev/status', async (ctx) => {
330
- return {
331
- ...app.getServerInfo(),
332
- uptime: process.uptime(),
333
- memory: process.memoryUsage()
334
- };
335
- });
336
- }
211
+ app.get("/users/:id", (ctx) => {
212
+ const id = ctx.params.id
213
+ return { user: { id, name: "John Doe" } }
214
+ }, {
215
+ detail: {
216
+ tags: ["Users"], // Route tags for grouping
217
+ summary: "Get user by ID", // Operation summary
218
+ description: "Retrieve user details", // Detailed description
219
+ security: [{ bearerAuth: [] }], // Route-specific security
220
+ params: { // Parameter documentation
221
+ id: z.string().describe("User ID")
222
+ }
223
+ }
224
+ })
337
225
  ```
338
226
 
339
- ## 🌟 Complete Example
227
+ #### Supported Metadata Fields
340
228
 
341
- ```typescript
342
- import BXO, { z } from './index';
343
- import { cors, logger, auth, rateLimit, createJWT } from './plugins';
229
+ - `tags`: Array of tags for grouping operations
230
+ - `summary`: Short description of the operation
231
+ - `description`: Detailed description of the operation
232
+ - `security`: Security requirements for the route
233
+ - `params`: Path parameter schemas and descriptions
234
+ - `query`: Query parameter schemas
235
+ - `hidden`: Set to `true` to exclude from OpenAPI docs
344
236
 
345
- const app = new BXO();
237
+ ### Creating Custom Plugins
346
238
 
347
- // Enable hot reload for development
348
- app.enableHotReload(['./']);
239
+ Plugins are just BXO instances with lifecycle hooks:
349
240
 
350
- // Add plugins
351
- app
352
- .use(logger({ format: 'simple' }))
353
- .use(cors({
354
- origin: ['http://localhost:3000'],
355
- credentials: true
356
- }))
357
- .use(rateLimit({
358
- max: 100,
359
- window: 60,
360
- exclude: ['/health']
361
- }))
362
- .use(auth({
363
- type: 'jwt',
364
- secret: 'your-secret-key',
365
- exclude: ['/', '/login', '/health']
366
- }));
367
-
368
- // Comprehensive lifecycle hooks
369
- app
370
- .onBeforeStart(() => console.log('🔧 Preparing server startup...'))
371
- .onAfterStart(() => console.log('✅ Server ready!'))
372
- .onBeforeRestart(() => console.log('🔄 Restarting server...'))
373
- .onAfterRestart(() => console.log('✅ Server restarted!'))
374
- .onError((ctx, error) => ({
375
- error: 'Internal server error',
376
- timestamp: new Date().toISOString()
377
- }));
378
-
379
- // Routes
380
- app
381
- .get('/health', async () => ({
382
- status: 'ok',
383
- timestamp: new Date().toISOString(),
384
- server: app.getServerInfo()
385
- }))
386
-
387
- .post('/login', async (ctx) => {
388
- const { username, password } = ctx.body;
389
-
390
- if (username === 'admin' && password === 'password') {
391
- const token = createJWT(
392
- { username, role: 'admin' },
393
- 'your-secret-key'
394
- );
395
- return { token, user: { username, role: 'admin' } };
396
- }
397
-
398
- ctx.set.status = 401;
399
- return { error: 'Invalid credentials' };
400
- }, {
401
- body: z.object({
402
- username: z.string(),
403
- password: z.string()
404
- })
405
- })
406
-
407
- .get('/users/:id', async (ctx) => {
408
- return {
409
- user: {
410
- id: ctx.params.id,
411
- include: ctx.query.include
412
- }
413
- };
414
- }, {
415
- params: z.object({ id: z.string().uuid() }),
416
- query: z.object({ include: z.string().optional() })
417
- })
418
-
419
- .post('/users', async (ctx) => {
420
- return { created: ctx.body };
421
- }, {
422
- body: z.object({
423
- name: z.string(),
424
- email: z.string().email()
425
- })
426
- })
427
-
428
- .get('/protected', async (ctx) => {
429
- // ctx.user available from auth plugin
430
- return {
431
- message: 'Protected resource',
432
- user: ctx.user
433
- };
434
- })
435
-
436
- // Server management endpoints
437
- .post('/restart', async (ctx) => {
438
- setTimeout(() => app.restart(3000), 100);
439
- return { message: 'Server restart initiated' };
440
- })
241
+ ```typescript
242
+ function loggingPlugin() {
243
+ const plugin = new BXO();
441
244
 
442
- .get('/status', async (ctx) => {
443
- return {
444
- ...app.getServerInfo(),
445
- uptime: process.uptime(),
446
- memory: process.memoryUsage()
447
- };
245
+ plugin.beforeRequest(async (req) => {
246
+ console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
247
+ return req;
448
248
  });
249
+
250
+ return plugin;
251
+ }
449
252
 
450
- // Start server with hot reload
451
- app.start(3000);
253
+ app.use(loggingPlugin());
452
254
  ```
453
255
 
454
- ## 🧪 Testing Endpoints
455
-
456
- With the example server running, test these endpoints:
457
-
458
- ```bash
459
- # Health check
460
- curl http://localhost:3000/health
256
+ ## Route Validation
461
257
 
462
- # Login to get token
463
- curl -X POST http://localhost:3000/login \
464
- -H "Content-Type: application/json" \
465
- -d '{"username": "admin", "password": "password"}'
258
+ Define Zod schemas for request validation:
466
259
 
467
- # Create user
468
- curl -X POST http://localhost:3000/users \
469
- -H "Content-Type: application/json" \
470
- -d '{"name": "John Doe", "email": "john@example.com"}'
260
+ ```typescript
261
+ import { z } from "bxo";
262
+
263
+ const UserSchema = z.object({
264
+ name: z.string().min(1),
265
+ email: z.string().email()
266
+ });
267
+
268
+ app.post("/users", async (ctx) => {
269
+ const user = ctx.body; // Already validated by UserSchema
270
+ return ctx.json({ id: 1, ...user });
271
+ }, {
272
+ body: UserSchema
273
+ });
274
+ ```
471
275
 
472
- # Get user with validation
473
- curl "http://localhost:3000/users/123e4567-e89b-12d3-a456-426614174000?include=profile"
276
+ ## Multipart/Form-Data Parsing
474
277
 
475
- # Access protected route (use token from login)
476
- curl http://localhost:3000/protected \
477
- -H "Authorization: Bearer YOUR_JWT_TOKEN"
278
+ BXO automatically parses multipart/form-data into nested objects and arrays before Zod validation:
478
279
 
479
- # Check server status
480
- curl http://localhost:3000/status
280
+ ### Nested Objects
281
+ Form fields like `profile[name]` are automatically converted to nested objects:
481
282
 
482
- # Restart server programmatically
483
- curl -X POST http://localhost:3000/restart
283
+ ```typescript
284
+ const UserFormSchema = z.object({
285
+ name: z.string(),
286
+ email: z.string().email(),
287
+ profile: z.object({
288
+ name: z.string()
289
+ })
290
+ });
291
+
292
+ app.post("/users", async (ctx) => {
293
+ // Form data: profile[name]="John" becomes { profile: { name: "John" } }
294
+ console.log(ctx.body); // { name: "...", email: "...", profile: { name: "John" } }
295
+ return ctx.json({ success: true, data: ctx.body });
296
+ }, {
297
+ body: UserFormSchema
298
+ });
484
299
  ```
485
300
 
486
- ## 📖 API Reference
487
-
488
- ### BXO Class Methods
489
-
490
- #### HTTP Methods
491
- - `get(path, handler, config?)` - Handle GET requests
492
- - `post(path, handler, config?)` - Handle POST requests
493
- - `put(path, handler, config?)` - Handle PUT requests
494
- - `delete(path, handler, config?)` - Handle DELETE requests
495
- - `patch(path, handler, config?)` - Handle PATCH requests
496
-
497
- #### Plugins & Hooks
498
- - `use(plugin)` - Add a plugin
499
- - `onBeforeStart(handler)` - Before server start hook
500
- - `onAfterStart(handler)` - After server start hook
501
- - `onBeforeStop(handler)` - Before server stop hook
502
- - `onAfterStop(handler)` - After server stop hook
503
- - `onBeforeRestart(handler)` - Before server restart hook
504
- - `onAfterRestart(handler)` - After server restart hook
505
- - `onRequest(handler)` - Global request hook
506
- - `onResponse(handler)` - Global response hook
507
- - `onError(handler)` - Global error hook
508
-
509
- #### Server Management
510
- - `start(port?, hostname?)` - Start the server
511
- - `stop()` - Stop the server gracefully
512
- - `restart(port?, hostname?)` - Restart the server
513
- - `listen(port?, hostname?)` - Start the server (backward compatibility)
514
- - `isServerRunning()` - Check if server is running
515
- - `getServerInfo()` - Get server status information
516
-
517
- #### Hot Reload
518
- - `enableHotReload(watchPaths?)` - Enable hot reload with file watching
519
-
520
- ### Route Configuration
301
+ ### Arrays
302
+ Form fields like `items[0]`, `items[1]` are automatically converted to arrays:
521
303
 
522
304
  ```typescript
523
- interface RouteConfig {
524
- params?: z.ZodSchema<any>; // Path parameter validation
525
- query?: z.ZodSchema<any>; // Query string validation
526
- body?: z.ZodSchema<any>; // Request body validation
527
- headers?: z.ZodSchema<any>; // Header validation
528
- }
305
+ const ItemsSchema = z.object({
306
+ items: z.array(z.string()),
307
+ tags: z.array(z.string()),
308
+ profile: z.object({
309
+ name: z.string(),
310
+ age: z.string().transform(val => parseInt(val, 10))
311
+ })
312
+ });
313
+
314
+ app.post("/items", async (ctx) => {
315
+ // Form data: items[0]="Apple", items[1]="Banana" becomes { items: ["Apple", "Banana"] }
316
+ console.log(ctx.body); // { items: ["Apple", "Banana"], tags: [...], profile: {...} }
317
+ return ctx.json({ success: true, data: ctx.body });
318
+ }, {
319
+ body: ItemsSchema
320
+ });
529
321
  ```
530
322
 
531
- ### Plugin Interface
323
+ ### Deep Nested Array Objects
324
+ Form fields like `workspace_items[0][id]`, `workspace_items[0][type]` are automatically converted to arrays of objects:
532
325
 
533
326
  ```typescript
534
- interface Plugin {
535
- name?: string;
536
- onRequest?: (ctx: Context) => Promise<void> | void;
537
- onResponse?: (ctx: Context, response: any) => Promise<any> | any;
538
- onError?: (ctx: Context, error: Error) => Promise<any> | any;
539
- }
327
+ const WorkspaceSchema = z.object({
328
+ id: z.string(),
329
+ workspace_items: z.array(z.object({
330
+ id: z.string(),
331
+ type: z.string(),
332
+ value: z.string(),
333
+ options: z.string(),
334
+ label: z.string()
335
+ }))
336
+ });
337
+
338
+ app.post("/workspace", async (ctx) => {
339
+ // Form data: workspace_items[0][id]="item1", workspace_items[0][type]="Link"
340
+ // becomes { workspace_items: [{ id: "item1", type: "Link", ... }] }
341
+ console.log(ctx.body); // { id: "...", workspace_items: [{ id: "item1", type: "Link", ... }] }
342
+ return ctx.json({ success: true, data: ctx.body });
343
+ }, {
344
+ body: WorkspaceSchema
345
+ });
540
346
  ```
541
347
 
542
- ## 🛠️ Development
348
+ ### Supported Patterns
349
+ - **Nested objects**: `profile[name]`, `settings[theme]` → `{ profile: { name: "..." }, settings: { theme: "..." } }`
350
+ - **Arrays**: `items[0]`, `items[1]` → `{ items: ["...", "..."] }`
351
+ - **Deep nested array objects**: `workspace_items[0][id]`, `workspace_items[0][type]` → `{ workspace_items: [{ id: "...", type: "..." }] }`
352
+ - **Duplicate keys**: Multiple values with same key → `{ tags: ["tag1", "tag2"] }`
543
353
 
544
- ### Running the Example
354
+ ## Running
545
355
 
546
356
  ```bash
547
- # Run with hot reload enabled
548
- bun run example.ts
549
-
550
- # The server will automatically restart when you edit any .ts/.js files!
357
+ bun run ./src/index.ts
551
358
  ```
552
359
 
553
- ### Project Structure
360
+ Or run the examples:
554
361
 
555
- ```
556
- bxo/
557
- ├── index.ts # Main BXO framework
558
- ├── plugins/
559
- │ ├── index.ts # Plugin exports
560
- │ ├── cors.ts # CORS plugin
561
- │ ├── logger.ts # Logger plugin
562
- │ ├── auth.ts # Authentication plugin
563
- │ └── ratelimit.ts # Rate limiting plugin
564
- ├── example.ts # Usage example
565
- ├── package.json
566
- └── README.md
567
- ```
568
-
569
- ## 🤝 Contributing
362
+ ```bash
363
+ # CORS example
364
+ bun run ./example/cors-example.ts
570
365
 
571
- BXO is designed to be simple and extensible. Contributions are welcome!
366
+ # Multipart form data parsing example
367
+ bun run ./example/multipart-example.ts
368
+ ```
572
369
 
573
- ## 📄 License
370
+ ## Examples
574
371
 
575
- MIT License - feel free to use BXO in your projects!
372
+ Check out the `example/` directory for more usage examples:
576
373
 
577
- ---
374
+ - `cors-example.ts` - Demonstrates CORS plugin and lifecycle hooks
375
+ - `openapi-example.ts` - Demonstrates OpenAPI plugin with tags and security
376
+ - `websocket-example.ts` - Demonstrates WebSocket functionality with interactive HTML client
377
+ - `multipart-example.ts` - Demonstrates multipart/form-data parsing with nested objects and arrays
378
+ - `index.ts` - Basic routing example
578
379
 
579
- Built with ❤️ for the Bun ecosystem
380
+ This project was created using `bun init` in bun v1.2.3. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.