bxo 0.0.5-dev.44 → 0.0.5-dev.45

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
@@ -136,7 +136,7 @@ interface BXOOptions {
136
136
  // Plugin interface for middleware-style plugins
137
137
  interface Plugin {
138
138
  name?: string;
139
- onRequest?: (ctx: Context) => Promise<void> | void;
139
+ onRequest?: (ctx: Context) => Promise<Response | void> | Response | void;
140
140
  onResponse?: (ctx: Context, response: any) => Promise<any> | any;
141
141
  onError?: (ctx: Context, error: Error) => Promise<any> | any;
142
142
  }
@@ -153,7 +153,7 @@ export default class BXO {
153
153
  onAfterRestart?: (instance: BXO) => Promise<void> | void;
154
154
  onBeforeStop?: (instance: BXO) => Promise<void> | void;
155
155
  onAfterStop?: (instance: BXO) => Promise<void> | void;
156
- onRequest?: (ctx: Context, instance: BXO) => Promise<void> | void;
156
+ onRequest?: (ctx: Context, instance: BXO) => Promise<Response | void> | Response | void;
157
157
  onResponse?: (ctx: Context, response: any, instance: BXO) => Promise<any> | any;
158
158
  onError?: (ctx: Context, error: Error, instance: BXO) => Promise<any> | any;
159
159
  } = {};
@@ -721,6 +721,84 @@ export default class BXO {
721
721
  return new Response('WebSocket upgrade failed', { status: 400 });
722
722
  }
723
723
 
724
+ // Handle OPTIONS requests for CORS preflight before route matching
725
+ if (method === 'OPTIONS') {
726
+ // Create a minimal context for OPTIONS requests
727
+ const ctx: Context = {
728
+ params: {},
729
+ query: {},
730
+ body: {},
731
+ headers: this.parseHeaders(request.headers),
732
+ cookies: {},
733
+ path: pathname,
734
+ request,
735
+ set: {},
736
+ status: ((code: number, data?: any) => {
737
+ ctx.set.status = code;
738
+ return data;
739
+ }) as any,
740
+ redirect: ((location: string, status: number = 302) => {
741
+ ctx.set.redirect = { location, status };
742
+ const responseHeaders: Record<string, string> = {
743
+ Location: location,
744
+ ...(ctx.set.headers || {})
745
+ };
746
+ return new Response(null, {
747
+ status,
748
+ headers: responseHeaders
749
+ });
750
+ }) as any,
751
+ clearRedirect: (() => {
752
+ delete ctx.set.redirect;
753
+ if (ctx.set.headers) {
754
+ for (const key of Object.keys(ctx.set.headers)) {
755
+ if (key.toLowerCase() === 'location') {
756
+ delete ctx.set.headers[key];
757
+ }
758
+ }
759
+ }
760
+ if (typeof ctx.set.status === 'number' && ctx.set.status >= 300 && ctx.set.status < 400) {
761
+ delete ctx.set.status;
762
+ }
763
+ }) as any
764
+ };
765
+
766
+ // Run middleware onRequest hooks for OPTIONS requests
767
+ for (const plugin of this.middleware) {
768
+ if (plugin.onRequest) {
769
+ const result = await plugin.onRequest(ctx);
770
+ // If middleware returns a response, return it immediately
771
+ if (result instanceof Response) {
772
+ return result;
773
+ }
774
+ }
775
+ }
776
+
777
+ // Run global onRequest hook
778
+ if (this.hooks.onRequest) {
779
+ const result = await this.hooks.onRequest(ctx, this);
780
+ if (result instanceof Response) {
781
+ return result;
782
+ }
783
+ }
784
+
785
+ // Run BXO instance onRequest hooks
786
+ for (const bxoInstance of this.plugins) {
787
+ if (bxoInstance.hooks.onRequest) {
788
+ const result = await bxoInstance.hooks.onRequest(ctx, this);
789
+ if (result instanceof Response) {
790
+ return result;
791
+ }
792
+ }
793
+ }
794
+
795
+ // If no middleware handled the OPTIONS request, return a default response
796
+ return new Response(null, {
797
+ status: 204,
798
+ headers: ctx.set.headers || {}
799
+ });
800
+ }
801
+
724
802
  const matchResult = this.matchRoute(method, pathname);
725
803
  if (!matchResult) {
726
804
  return new Response('Not Found', { status: 404 });
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.44",
4
+ "version": "0.0.5-dev.45",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "exports": {
package/plugins/cors.ts CHANGED
@@ -60,6 +60,7 @@ export function cors(options: CORSOptions = {}): Plugin {
60
60
  return {
61
61
  name: 'cors',
62
62
  onRequest: async (ctx) => {
63
+ console.log('onRequest', ctx.request.method);
63
64
  // Handle preflight OPTIONS requests
64
65
  if (ctx.request.method === 'OPTIONS') {
65
66
  const requestOrigin = getRequestOrigin(ctx.request);
@@ -77,6 +78,12 @@ export function cors(options: CORSOptions = {}): Plugin {
77
78
  if (credentials) {
78
79
  ctx.set.headers['Access-Control-Allow-Credentials'] = 'true';
79
80
  }
81
+
82
+ // Return a proper Response for OPTIONS requests
83
+ return new Response(null, {
84
+ status: 204,
85
+ headers: ctx.set.headers
86
+ });
80
87
  }
81
88
  },
82
89
  onResponse: async (ctx, response) => {
@@ -1,173 +0,0 @@
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
- }