bxo 0.0.1 โ 0.0.3
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 +570 -6
- package/example.ts +70 -24
- package/index.ts +200 -48
- package/package.json +1 -1
- package/plugins/auth.ts +68 -65
- package/plugins/cors.ts +44 -40
- package/plugins/index.ts +5 -7
- package/plugins/logger.ts +88 -83
- package/plugins/ratelimit.ts +61 -57
package/README.md
CHANGED
@@ -1,15 +1,579 @@
|
|
1
|
-
#
|
1
|
+
# ๐ฆ BXO - A Type-Safe Web Framework for Bun
|
2
2
|
|
3
|
-
|
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.
|
4
|
+
|
5
|
+
## โจ Features
|
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
|
17
|
+
|
18
|
+
## ๐ Quick Start
|
19
|
+
|
20
|
+
### Installation
|
21
|
+
|
22
|
+
```bash
|
23
|
+
bun add zod
|
24
|
+
```
|
25
|
+
|
26
|
+
### Basic Usage
|
27
|
+
|
28
|
+
```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
|
+
```
|
37
|
+
|
38
|
+
## ๐ Documentation
|
39
|
+
|
40
|
+
### HTTP Handlers
|
41
|
+
|
42
|
+
BXO supports all standard HTTP methods with fluent chaining:
|
43
|
+
|
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);
|
79
|
+
```
|
80
|
+
|
81
|
+
### Context Object
|
82
|
+
|
83
|
+
The context object (`ctx`) provides access to request data and response configuration:
|
84
|
+
|
85
|
+
```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
|
+
```
|
100
|
+
|
101
|
+
### Validation Configuration
|
102
|
+
|
103
|
+
Each route can specify validation schemas for different parts of the request:
|
104
|
+
|
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);
|
127
|
+
```
|
128
|
+
|
129
|
+
## ๐ Plugin System
|
130
|
+
|
131
|
+
BXO has a powerful plugin system with lifecycle hooks. Plugins are separate modules imported from `./plugins`.
|
132
|
+
|
133
|
+
### Available Plugins
|
134
|
+
|
135
|
+
#### CORS Plugin
|
136
|
+
|
137
|
+
```typescript
|
138
|
+
import { cors } from './plugins';
|
139
|
+
|
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
|
+
```
|
148
|
+
|
149
|
+
#### Logger Plugin
|
150
|
+
|
151
|
+
```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
|
+
}));
|
159
|
+
```
|
160
|
+
|
161
|
+
#### Authentication Plugin
|
162
|
+
|
163
|
+
```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
|
+
);
|
182
|
+
```
|
183
|
+
|
184
|
+
#### Rate Limiting Plugin
|
185
|
+
|
186
|
+
```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
|
+
}));
|
199
|
+
```
|
200
|
+
|
201
|
+
### Creating Custom Plugins
|
202
|
+
|
203
|
+
```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);
|
221
|
+
```
|
222
|
+
|
223
|
+
## ๐ฃ Lifecycle Hooks
|
224
|
+
|
225
|
+
BXO provides comprehensive lifecycle hooks with a consistent before/after pattern for both server and request lifecycle:
|
226
|
+
|
227
|
+
### Server Lifecycle Hooks
|
228
|
+
|
229
|
+
```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
|
+
```
|
250
|
+
|
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
|
+
});
|
266
|
+
```
|
267
|
+
|
268
|
+
## ๐ Hot Reload & Server Management
|
269
|
+
|
270
|
+
BXO includes built-in hot reload and comprehensive server management capabilities:
|
271
|
+
|
272
|
+
### Hot Reload
|
273
|
+
|
274
|
+
```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
|
283
|
+
```
|
284
|
+
|
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: ['./'] }
|
301
|
+
|
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
|
313
|
+
|
314
|
+
```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
|
+
}
|
337
|
+
```
|
338
|
+
|
339
|
+
## ๐ Complete Example
|
340
|
+
|
341
|
+
```typescript
|
342
|
+
import BXO, { z } from './index';
|
343
|
+
import { cors, logger, auth, rateLimit, createJWT } from './plugins';
|
344
|
+
|
345
|
+
const app = new BXO();
|
346
|
+
|
347
|
+
// Enable hot reload for development
|
348
|
+
app.enableHotReload(['./']);
|
349
|
+
|
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
|
+
})
|
441
|
+
|
442
|
+
.get('/status', async (ctx) => {
|
443
|
+
return {
|
444
|
+
...app.getServerInfo(),
|
445
|
+
uptime: process.uptime(),
|
446
|
+
memory: process.memoryUsage()
|
447
|
+
};
|
448
|
+
});
|
449
|
+
|
450
|
+
// Start server with hot reload
|
451
|
+
app.start(3000);
|
452
|
+
```
|
453
|
+
|
454
|
+
## ๐งช Testing Endpoints
|
455
|
+
|
456
|
+
With the example server running, test these endpoints:
|
4
457
|
|
5
458
|
```bash
|
6
|
-
|
459
|
+
# Health check
|
460
|
+
curl http://localhost:3000/health
|
461
|
+
|
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"}'
|
466
|
+
|
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"}'
|
471
|
+
|
472
|
+
# Get user with validation
|
473
|
+
curl "http://localhost:3000/users/123e4567-e89b-12d3-a456-426614174000?include=profile"
|
474
|
+
|
475
|
+
# Access protected route (use token from login)
|
476
|
+
curl http://localhost:3000/protected \
|
477
|
+
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
478
|
+
|
479
|
+
# Check server status
|
480
|
+
curl http://localhost:3000/status
|
481
|
+
|
482
|
+
# Restart server programmatically
|
483
|
+
curl -X POST http://localhost:3000/restart
|
484
|
+
```
|
485
|
+
|
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
|
521
|
+
|
522
|
+
```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
|
+
}
|
7
529
|
```
|
8
530
|
|
9
|
-
|
531
|
+
### Plugin Interface
|
532
|
+
|
533
|
+
```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
|
+
}
|
540
|
+
```
|
541
|
+
|
542
|
+
## ๐ ๏ธ Development
|
543
|
+
|
544
|
+
### Running the Example
|
10
545
|
|
11
546
|
```bash
|
12
|
-
|
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!
|
551
|
+
```
|
552
|
+
|
553
|
+
### Project Structure
|
554
|
+
|
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
|
13
567
|
```
|
14
568
|
|
15
|
-
|
569
|
+
## ๐ค Contributing
|
570
|
+
|
571
|
+
BXO is designed to be simple and extensible. Contributions are welcome!
|
572
|
+
|
573
|
+
## ๐ License
|
574
|
+
|
575
|
+
MIT License - feel free to use BXO in your projects!
|
576
|
+
|
577
|
+
---
|
578
|
+
|
579
|
+
Built with โค๏ธ for the Bun ecosystem
|