bxo 0.0.5-dev.41 → 0.0.5-dev.43

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/index.ts CHANGED
@@ -1352,7 +1352,7 @@ const redirect = (location: string, status: number = 302) => {
1352
1352
  export { z, error, file, redirect };
1353
1353
 
1354
1354
  // Export types for external use
1355
- export type { RouteConfig, RouteDetail, Handler, WebSocketHandler, WSRoute, Cookie, BXOOptions };
1355
+ export type { RouteConfig, RouteDetail, Handler, WebSocketHandler, WSRoute, Cookie, BXOOptions, Plugin };
1356
1356
 
1357
1357
  // Helper function to create a cookie
1358
1358
  export const createCookie = (
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bxo",
3
3
  "module": "index.ts",
4
- "version": "0.0.5-dev.41",
4
+ "version": "0.0.5-dev.43",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "exports": {
@@ -0,0 +1,160 @@
1
+ # BXO Plugins
2
+
3
+ BXO supports a plugin system that allows you to extend functionality through middleware-style plugins. Plugins can intercept requests, modify responses, and handle errors.
4
+
5
+ ## Plugin Type
6
+
7
+ The `Plugin` type defines the interface for all plugins:
8
+
9
+ ```typescript
10
+ interface Plugin {
11
+ name?: string;
12
+ onRequest?: (ctx: Context) => Promise<void> | void;
13
+ onResponse?: (ctx: Context, response: any) => Promise<any> | any;
14
+ onError?: (ctx: Context, error: Error) => Promise<any> | any;
15
+ }
16
+ ```
17
+
18
+ ## Built-in Plugins
19
+
20
+ ### CORS Plugin
21
+
22
+ The CORS plugin adds Cross-Origin Resource Sharing headers to your responses:
23
+
24
+ ```typescript
25
+ import BXO from 'bxo';
26
+ import { cors } from 'bxo/plugins';
27
+
28
+ const app = new BXO();
29
+
30
+ // Use with default options
31
+ app.use(cors());
32
+
33
+ // Or with custom options
34
+ app.use(cors({
35
+ origin: ['http://localhost:3000', 'https://myapp.com'],
36
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
37
+ allowedHeaders: ['Content-Type', 'Authorization'],
38
+ credentials: true,
39
+ maxAge: 86400
40
+ }));
41
+ ```
42
+
43
+ ### Rate Limit Plugin
44
+
45
+ The rate limit plugin helps protect your API from abuse:
46
+
47
+ ```typescript
48
+ import BXO from 'bxo';
49
+ import { rateLimit } from 'bxo/plugins';
50
+
51
+ const app = new BXO();
52
+
53
+ app.use(rateLimit({
54
+ max: 100, // Maximum requests per window
55
+ window: 60, // Time window in seconds
56
+ message: 'Too many requests',
57
+ statusCode: 429,
58
+ exclude: ['/health', '/metrics'] // Exclude certain paths
59
+ }));
60
+ ```
61
+
62
+ ## Creating Custom Plugins
63
+
64
+ You can create your own plugins by implementing the `Plugin` interface:
65
+
66
+ ```typescript
67
+ import type { Plugin } from 'bxo';
68
+
69
+ // Simple logging plugin
70
+ export function logger(): Plugin {
71
+ return {
72
+ name: 'logger',
73
+ onRequest: async (ctx) => {
74
+ console.log(`${new Date().toISOString()} - ${ctx.request.method} ${ctx.path}`);
75
+ },
76
+ onResponse: async (ctx, response) => {
77
+ if (response instanceof Response) {
78
+ console.log(`${new Date().toISOString()} - ${ctx.request.method} ${ctx.path} - ${response.status}`);
79
+ }
80
+ return response;
81
+ },
82
+ onError: async (ctx, error) => {
83
+ console.error(`${new Date().toISOString()} - Error in ${ctx.request.method} ${ctx.path}:`, error);
84
+ }
85
+ };
86
+ }
87
+
88
+ // Authentication plugin
89
+ export function auth(secret: string): Plugin {
90
+ return {
91
+ name: 'auth',
92
+ onRequest: async (ctx) => {
93
+ const token = ctx.headers.authorization?.replace('Bearer ', '');
94
+
95
+ if (!token) {
96
+ throw new Response(JSON.stringify({ error: 'No token provided' }), {
97
+ status: 401,
98
+ headers: { 'Content-Type': 'application/json' }
99
+ });
100
+ }
101
+
102
+ // Verify token logic here...
103
+ // If invalid, throw a Response with 401 status
104
+ }
105
+ };
106
+ }
107
+ ```
108
+
109
+ ## Using Plugins
110
+
111
+ Plugins are applied using the `use()` method:
112
+
113
+ ```typescript
114
+ import BXO from 'bxo';
115
+ import { cors, rateLimit } from 'bxo/plugins';
116
+ import { logger, auth } from './my-plugins';
117
+
118
+ const app = new BXO();
119
+
120
+ // Apply plugins in order (they will execute in this order)
121
+ app.use(logger());
122
+ app.use(cors());
123
+ app.use(rateLimit({ max: 100, window: 60 }));
124
+ app.use(auth('my-secret'));
125
+
126
+ // Your routes
127
+ app.get('/', (ctx) => {
128
+ return { message: 'Hello World!' };
129
+ });
130
+ ```
131
+
132
+ ## Plugin Execution Order
133
+
134
+ Plugins execute in the order they are added:
135
+
136
+ 1. **onRequest**: All plugins' `onRequest` hooks execute before the route handler
137
+ 2. **Route Handler**: The actual route handler executes
138
+ 3. **onResponse**: All plugins' `onResponse` hooks execute after the route handler
139
+ 4. **onError**: If an error occurs, all plugins' `onError` hooks execute
140
+
141
+ ## Plugin Context
142
+
143
+ Each plugin receives a `Context` object that contains:
144
+
145
+ - `params`: Route parameters
146
+ - `query`: Query string parameters
147
+ - `body`: Request body
148
+ - `headers`: Request headers
149
+ - `cookies`: Request cookies
150
+ - `path`: Request path
151
+ - `request`: The original Request object
152
+ - `set`: Object to set response properties (status, headers, cookies, redirect)
153
+
154
+ ## Best Practices
155
+
156
+ 1. **Keep plugins focused**: Each plugin should handle one specific concern
157
+ 2. **Handle errors gracefully**: Use try-catch blocks in your plugin hooks
158
+ 3. **Return responses properly**: Always return the response from `onResponse` hooks
159
+ 4. **Use meaningful names**: Give your plugins descriptive names for debugging
160
+ 5. **Document your plugins**: Include JSDoc comments explaining what your plugin does
package/plugins/cors.ts CHANGED
@@ -1,4 +1,4 @@
1
- import BXO from '../index';
1
+ import type { Plugin } from '../index';
2
2
 
3
3
  interface CORSOptions {
4
4
  origin?: string | string[] | boolean;
@@ -48,7 +48,7 @@ function validateOrigin(requestOrigin: string | null, allowedOrigins: string | s
48
48
  return null;
49
49
  }
50
50
 
51
- export function cors(options: CORSOptions = {}): any {
51
+ export function cors(options: CORSOptions = {}): Plugin {
52
52
  const {
53
53
  origin = '*',
54
54
  methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
@@ -59,69 +59,53 @@ export function cors(options: CORSOptions = {}): any {
59
59
 
60
60
  return {
61
61
  name: 'cors',
62
- onRequest: async (ctx: any) => {
63
- // Handle preflight OPTIONS request
62
+ onRequest: async (ctx) => {
63
+ // Handle preflight OPTIONS requests
64
64
  if (ctx.request.method === 'OPTIONS') {
65
- const headers: Record<string, string> = {};
66
-
67
- // Get and validate origin
68
65
  const requestOrigin = getRequestOrigin(ctx.request);
69
- const validatedOrigin = validateOrigin(requestOrigin, origin);
70
-
71
- if (validatedOrigin) {
72
- headers['Access-Control-Allow-Origin'] = validatedOrigin;
73
- } else if (typeof origin === 'string' && origin === '*') {
74
- headers['Access-Control-Allow-Origin'] = '*';
75
- }
76
-
77
- headers['Access-Control-Allow-Methods'] = methods.join(', ');
78
- headers['Access-Control-Allow-Headers'] = allowedHeaders.join(', ');
66
+ const allowedOrigin = validateOrigin(requestOrigin, origin);
67
+
68
+ // Set CORS headers for preflight
69
+ ctx.set.headers = {
70
+ ...ctx.set.headers,
71
+ 'Access-Control-Allow-Origin': allowedOrigin || '*',
72
+ 'Access-Control-Allow-Methods': methods.join(', '),
73
+ 'Access-Control-Allow-Headers': allowedHeaders.join(', '),
74
+ 'Access-Control-Max-Age': maxAge.toString()
75
+ };
79
76
 
80
77
  if (credentials) {
81
- headers['Access-Control-Allow-Credentials'] = 'true';
78
+ ctx.set.headers['Access-Control-Allow-Credentials'] = 'true';
82
79
  }
83
-
84
- headers['Access-Control-Max-Age'] = maxAge.toString();
85
-
86
- ctx.set.status = 204;
87
- ctx.set.headers = { ...ctx.set.headers, ...headers };
88
-
89
- throw new Response(null, { status: 204, headers });
90
80
  }
91
81
  },
92
- onResponse: async (ctx: any, response: any) => {
93
- const headers: Record<string, string> = {};
94
-
95
- // Get and validate origin for actual requests
96
- const requestOrigin = getRequestOrigin(ctx.request);
97
- const validatedOrigin = validateOrigin(requestOrigin, origin);
98
-
99
- if (validatedOrigin) {
100
- headers['Access-Control-Allow-Origin'] = validatedOrigin;
101
- } else if (typeof origin === 'string' && origin === '*') {
102
- headers['Access-Control-Allow-Origin'] = '*';
103
- }
104
-
105
- if (credentials) {
106
- headers['Access-Control-Allow-Credentials'] = 'true';
107
- }
108
-
109
- // If response is a Response object, add headers to it
82
+ onResponse: async (ctx, response) => {
83
+ // Handle CORS headers for actual requests
110
84
  if (response instanceof Response) {
111
- const newHeaders = new Headers(response.headers);
112
- Object.entries(headers).forEach(([key, value]) => {
113
- newHeaders.set(key, value);
114
- });
115
- return new Response(response.body, {
85
+ const requestOrigin = getRequestOrigin(ctx.request);
86
+ const allowedOrigin = validateOrigin(requestOrigin, origin);
87
+
88
+ // Clone the response to modify headers
89
+ const newResponse = new Response(response.body, {
116
90
  status: response.status,
117
91
  statusText: response.statusText,
118
- headers: newHeaders
92
+ headers: new Headers(response.headers)
119
93
  });
94
+
95
+ // Set CORS headers
96
+ newResponse.headers.set('Access-Control-Allow-Origin', allowedOrigin || '*');
97
+ newResponse.headers.set('Access-Control-Allow-Methods', methods.join(', '));
98
+ newResponse.headers.set('Access-Control-Allow-Headers', allowedHeaders.join(', '));
99
+ newResponse.headers.set('Access-Control-Max-Age', maxAge.toString());
100
+
101
+ if (credentials) {
102
+ newResponse.headers.set('Access-Control-Allow-Credentials', 'true');
103
+ }
104
+
105
+ return newResponse;
120
106
  }
121
-
122
- // Otherwise, set headers in context for the framework to handle
123
- ctx.set.headers = { ...ctx.set.headers, ...headers };
107
+
124
108
  return response;
125
109
  }
126
110
  };
127
- }
111
+ }
package/plugins/index.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  export { cors } from './cors';
3
3
  export { rateLimit } from './ratelimit';
4
4
 
5
- // Import BXO for plugin typing
6
- import BXO from '../index';
5
+ // Import types for plugin typing
6
+ import type { Plugin } from '../index';
7
7
 
8
- // Plugin functions now return BXO instances
9
- export type PluginFactory<T = any> = (options?: T) => BXO;
8
+ // Plugin functions return Plugin instances
9
+ export type PluginFactory<T = any> = (options?: T) => Plugin;
@@ -1,4 +1,4 @@
1
- import BXO from '../index';
1
+ import type { Plugin } from '../index';
2
2
 
3
3
  interface RateLimitOptions {
4
4
  max: number;
@@ -52,7 +52,7 @@ class RateLimitStore {
52
52
  }
53
53
  }
54
54
 
55
- export function rateLimit(options: RateLimitOptions): any {
55
+ export function rateLimit(options: RateLimitOptions): Plugin {
56
56
  const {
57
57
  max,
58
58
  window,