bxo 0.0.5-dev.42 → 0.0.5-dev.44

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.42",
4
+ "version": "0.0.5-dev.44",
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,12 +59,53 @@ export function cors(options: CORSOptions = {}): any {
59
59
 
60
60
  return {
61
61
  name: 'cors',
62
- onRequest: async (ctx: any) => {
62
+ onRequest: async (ctx) => {
63
+ // Handle preflight OPTIONS requests
64
+ if (ctx.request.method === 'OPTIONS') {
65
+ const requestOrigin = getRequestOrigin(ctx.request);
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
+ };
76
+
77
+ if (credentials) {
78
+ ctx.set.headers['Access-Control-Allow-Credentials'] = 'true';
79
+ }
80
+ }
63
81
  },
64
- onResponse: async (ctx: any, response: any) => {
65
- response.headers.set('Access-Control-Allow-Origin', '*');
66
- response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
82
+ onResponse: async (ctx, response) => {
83
+ // Handle CORS headers for actual requests
84
+ if (response instanceof Response) {
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, {
90
+ status: response.status,
91
+ statusText: response.statusText,
92
+ headers: new Headers(response.headers)
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;
106
+ }
107
+
67
108
  return response;
68
109
  }
69
110
  };
70
- }
111
+ }
@@ -0,0 +1,173 @@
1
+ import type { Plugin } from '../index';
2
+
3
+ // Example 1: Simple logging plugin
4
+ export function logger(): Plugin {
5
+ return {
6
+ name: 'logger',
7
+ onRequest: async (ctx) => {
8
+ console.log(`[${new Date().toISOString()}] ${ctx.request.method} ${ctx.path}`);
9
+ },
10
+ onResponse: async (ctx, response) => {
11
+ if (response instanceof Response) {
12
+ console.log(`[${new Date().toISOString()}] ${ctx.request.method} ${ctx.path} - ${response.status}`);
13
+ }
14
+ return response;
15
+ },
16
+ onError: async (ctx, error) => {
17
+ console.error(`[${new Date().toISOString()}] Error in ${ctx.request.method} ${ctx.path}:`, error);
18
+ }
19
+ };
20
+ }
21
+
22
+ // Example 2: Authentication plugin
23
+ export function auth(secret: string): Plugin {
24
+ return {
25
+ name: 'auth',
26
+ onRequest: async (ctx) => {
27
+ const token = ctx.headers.authorization?.replace('Bearer ', '');
28
+
29
+ if (!token) {
30
+ throw new Response(JSON.stringify({ error: 'No token provided' }), {
31
+ status: 401,
32
+ headers: { 'Content-Type': 'application/json' }
33
+ });
34
+ }
35
+
36
+ // Simple token validation (replace with your actual logic)
37
+ if (token !== secret) {
38
+ throw new Response(JSON.stringify({ error: 'Invalid token' }), {
39
+ status: 401,
40
+ headers: { 'Content-Type': 'application/json' }
41
+ });
42
+ }
43
+
44
+ // Add user info to context
45
+ (ctx as any).user = { id: 'user-123', token };
46
+ }
47
+ };
48
+ }
49
+
50
+ // Example 3: Request timing plugin
51
+ export function timing(): Plugin {
52
+ return {
53
+ name: 'timing',
54
+ onRequest: async (ctx) => {
55
+ (ctx as any).startTime = Date.now();
56
+ },
57
+ onResponse: async (ctx, response) => {
58
+ const startTime = (ctx as any).startTime;
59
+ if (startTime) {
60
+ const duration = Date.now() - startTime;
61
+ console.log(`Request to ${ctx.path} took ${duration}ms`);
62
+
63
+ // Add timing header to response
64
+ if (response instanceof Response) {
65
+ const newResponse = new Response(response.body, {
66
+ status: response.status,
67
+ statusText: response.statusText,
68
+ headers: new Headers(response.headers)
69
+ });
70
+ newResponse.headers.set('X-Response-Time', `${duration}ms`);
71
+ return newResponse;
72
+ }
73
+ }
74
+ return response;
75
+ }
76
+ };
77
+ }
78
+
79
+ // Example 4: Request ID plugin
80
+ export function requestId(): Plugin {
81
+ return {
82
+ name: 'requestId',
83
+ onRequest: async (ctx) => {
84
+ const requestId = crypto.randomUUID();
85
+ (ctx as any).requestId = requestId;
86
+
87
+ // Add request ID to response headers
88
+ ctx.set.headers = {
89
+ ...ctx.set.headers,
90
+ 'X-Request-ID': requestId
91
+ };
92
+ }
93
+ };
94
+ }
95
+
96
+ // Example 5: Conditional plugin (only applies to certain paths)
97
+ export function conditionalAuth(secret: string, paths: string[]): Plugin {
98
+ return {
99
+ name: 'conditionalAuth',
100
+ onRequest: async (ctx) => {
101
+ // Only apply auth to specified paths
102
+ if (!paths.some(path => ctx.path.startsWith(path))) {
103
+ return; // Skip authentication for this path
104
+ }
105
+
106
+ const token = ctx.headers.authorization?.replace('Bearer ', '');
107
+
108
+ if (!token || token !== secret) {
109
+ throw new Response(JSON.stringify({ error: 'Authentication required' }), {
110
+ status: 401,
111
+ headers: { 'Content-Type': 'application/json' }
112
+ });
113
+ }
114
+ }
115
+ };
116
+ }
117
+
118
+ // Example 6: Response transformation plugin
119
+ export function responseTransformer(): Plugin {
120
+ return {
121
+ name: 'responseTransformer',
122
+ onResponse: async (ctx, response) => {
123
+ // Only transform JSON responses
124
+ if (response instanceof Response) {
125
+ const contentType = response.headers.get('content-type');
126
+ if (contentType?.includes('application/json')) {
127
+ try {
128
+ const data = await response.json();
129
+
130
+ // Transform the response data
131
+ const transformed = {
132
+ success: true,
133
+ data,
134
+ timestamp: new Date().toISOString(),
135
+ path: ctx.path
136
+ };
137
+
138
+ return new Response(JSON.stringify(transformed), {
139
+ status: response.status,
140
+ headers: response.headers
141
+ });
142
+ } catch (error) {
143
+ // If response is not JSON, return as-is
144
+ return response;
145
+ }
146
+ }
147
+ }
148
+
149
+ return response;
150
+ }
151
+ };
152
+ }
153
+
154
+ // Example 7: Error handling plugin
155
+ export function errorHandler(): Plugin {
156
+ return {
157
+ name: 'errorHandler',
158
+ onError: async (ctx, error) => {
159
+ console.error(`Error in ${ctx.request.method} ${ctx.path}:`, error);
160
+
161
+ // Return a standardized error response
162
+ return new Response(JSON.stringify({
163
+ error: 'Internal Server Error',
164
+ message: error.message,
165
+ timestamp: new Date().toISOString(),
166
+ path: ctx.path
167
+ }), {
168
+ status: 500,
169
+ headers: { 'Content-Type': 'application/json' }
170
+ });
171
+ }
172
+ };
173
+ }
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,