bxo 0.0.5-dev.16 → 0.0.5-dev.18

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
@@ -447,20 +447,50 @@ export default class BXO {
447
447
  const formData = await request.formData();
448
448
  body = Object.fromEntries(formData.entries());
449
449
  } else {
450
- body = await request.text();
450
+ // Try to parse as JSON if it looks like JSON, otherwise treat as text
451
+ const textBody = await request.text();
452
+ try {
453
+ // Check if the text looks like JSON
454
+ if (textBody.trim().startsWith('{') || textBody.trim().startsWith('[')) {
455
+ body = JSON.parse(textBody);
456
+ } else {
457
+ body = textBody;
458
+ }
459
+ } catch {
460
+ body = textBody;
461
+ }
451
462
  }
452
463
  }
453
464
 
454
- // Create context
455
- const ctx: Context = {
456
- params: route.config?.params ? this.validateData(route.config.params, params) : params,
457
- query: route.config?.query ? this.validateData(route.config.query, query) : query,
458
- body: route.config?.body ? this.validateData(route.config.body, body) : body,
459
- headers: route.config?.headers ? this.validateData(route.config.headers, headers) : headers,
460
- path: pathname,
461
- request,
462
- set: {}
463
- };
465
+ // Create context with validation
466
+ let ctx: Context;
467
+ try {
468
+ // Validate each part separately to get better error messages
469
+ const validatedParams = route.config?.params ? this.validateData(route.config.params, params) : params;
470
+ const validatedQuery = route.config?.query ? this.validateData(route.config.query, query) : query;
471
+ const validatedBody = route.config?.body ? this.validateData(route.config.body, body) : body;
472
+ const validatedHeaders = route.config?.headers ? this.validateData(route.config.headers, headers) : headers;
473
+
474
+ ctx = {
475
+ params: validatedParams,
476
+ query: validatedQuery,
477
+ body: validatedBody,
478
+ headers: validatedHeaders,
479
+ path: pathname,
480
+ request,
481
+ set: {}
482
+ };
483
+ } catch (validationError) {
484
+ // Validation failed - return error response
485
+ const errorMessage = validationError instanceof Error ? validationError.message : 'Validation failed';
486
+ return new Response(JSON.stringify({
487
+ error: `Validation error: ${errorMessage}`,
488
+ details: validationError instanceof Error && 'errors' in validationError ? validationError.errors : undefined
489
+ }), {
490
+ status: 400,
491
+ headers: { 'Content-Type': 'application/json' }
492
+ });
493
+ }
464
494
 
465
495
  try {
466
496
  // Run global onRequest hook
@@ -493,6 +523,7 @@ export default class BXO {
493
523
  // Validate response against schema if provided
494
524
  if (route.config?.response && !(response instanceof Response)) {
495
525
  try {
526
+ console.log('response', response);
496
527
  response = this.validateData(route.config.response, response);
497
528
  } catch (validationError) {
498
529
  // Response validation failed
@@ -629,6 +660,11 @@ export default class BXO {
629
660
  }
630
661
  });
631
662
 
663
+ // Verify server was created successfully
664
+ if (!this.server) {
665
+ throw new Error('Failed to create server instance');
666
+ }
667
+
632
668
  this.isRunning = true;
633
669
  this.serverPort = port;
634
670
  this.serverHostname = hostname;
@@ -666,10 +702,22 @@ export default class BXO {
666
702
  }
667
703
 
668
704
  if (this.server) {
669
- this.server.stop();
670
- this.server = null;
705
+ try {
706
+ // Try to stop the server gracefully
707
+ if (typeof this.server.stop === 'function') {
708
+ this.server.stop();
709
+ } else {
710
+ console.warn('⚠️ Server stop method not available');
711
+ }
712
+ } catch (stopError) {
713
+ console.error('❌ Error calling server.stop():', stopError);
714
+ }
715
+
716
+ // Clear the server reference
717
+ this.server = undefined;
671
718
  }
672
719
 
720
+ // Reset state regardless of server.stop() success
673
721
  this.isRunning = false;
674
722
  this.serverPort = undefined;
675
723
  this.serverHostname = undefined;
@@ -679,8 +727,15 @@ export default class BXO {
679
727
  await this.hooks.onAfterStop(this);
680
728
  }
681
729
 
730
+ console.log('✅ Server stopped successfully');
731
+
682
732
  } catch (error) {
683
733
  console.error('❌ Error stopping server:', error);
734
+ // Even if there's an error, reset the state
735
+ this.isRunning = false;
736
+ this.server = undefined;
737
+ this.serverPort = undefined;
738
+ this.serverHostname = undefined;
684
739
  throw error;
685
740
  }
686
741
  }
@@ -694,7 +749,7 @@ export default class BXO {
694
749
 
695
750
  // Server status
696
751
  isServerRunning(): boolean {
697
- return this.isRunning;
752
+ return this.isRunning && this.server !== undefined;
698
753
  }
699
754
 
700
755
  getServerInfo(): { running: boolean } {
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.16",
4
+ "version": "0.0.5-dev.18",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "devDependencies": {
package/plugins/auth.ts DELETED
@@ -1,119 +0,0 @@
1
- import BXO from '../index';
2
-
3
- interface AuthOptions {
4
- type: 'jwt' | 'bearer' | 'apikey';
5
- secret?: string;
6
- header?: string;
7
- verify?: (token: string, ctx: any) => Promise<any> | any;
8
- exclude?: string[];
9
- }
10
-
11
- export function auth(options: AuthOptions): BXO {
12
- const {
13
- type,
14
- secret,
15
- header = 'authorization',
16
- verify,
17
- exclude = []
18
- } = options;
19
-
20
- const authInstance = new BXO();
21
-
22
- authInstance.onRequest(async (ctx: any) => {
23
- const url = new URL(ctx.request.url);
24
- const pathname = url.pathname;
25
-
26
- // Skip auth for excluded paths
27
- if (exclude.some(path => {
28
- if (path.includes('*')) {
29
- const regex = new RegExp(path.replace(/\*/g, '.*'));
30
- return regex.test(pathname);
31
- }
32
- return pathname === path || pathname.startsWith(path);
33
- })) {
34
- return;
35
- }
36
-
37
- const authHeader = ctx.request.headers.get(header.toLowerCase());
38
-
39
- if (!authHeader) {
40
- throw new Response(JSON.stringify({ error: 'Authorization header required' }), {
41
- status: 401,
42
- headers: { 'Content-Type': 'application/json' }
43
- });
44
- }
45
-
46
- let token: string;
47
-
48
- if (type === 'jwt' || type === 'bearer') {
49
- if (!authHeader.startsWith('Bearer ')) {
50
- throw new Response(JSON.stringify({ error: 'Invalid authorization format. Use Bearer <token>' }), {
51
- status: 401,
52
- headers: { 'Content-Type': 'application/json' }
53
- });
54
- }
55
- token = authHeader.slice(7);
56
- } else if (type === 'apikey') {
57
- token = authHeader;
58
- } else {
59
- token = authHeader;
60
- }
61
-
62
- try {
63
- let user: any;
64
-
65
- if (verify) {
66
- user = await verify(token, ctx);
67
- } else if (type === 'jwt' && secret) {
68
- // Simple JWT verification (in production, use a proper JWT library)
69
- const [headerB64, payloadB64, signature] = token.split('.');
70
- if (!headerB64 || !payloadB64 || !signature) {
71
- throw new Error('Invalid JWT format');
72
- }
73
-
74
- const payload = JSON.parse(atob(payloadB64));
75
-
76
- // Check expiration
77
- if (payload.exp && Date.now() >= payload.exp * 1000) {
78
- throw new Error('Token expired');
79
- }
80
-
81
- user = payload;
82
- } else {
83
- user = { token };
84
- }
85
-
86
- // Attach user to context
87
- ctx.user = user;
88
-
89
- } catch (error) {
90
- const message = error instanceof Error ? error.message : 'Invalid token';
91
- throw new Response(JSON.stringify({ error: message }), {
92
- status: 401,
93
- headers: { 'Content-Type': 'application/json' }
94
- });
95
- }
96
- });
97
-
98
- return authInstance;
99
- }
100
-
101
- // Helper function for creating JWT tokens (simple implementation)
102
- export function createJWT(payload: any, secret: string, expiresIn: number = 3600): string {
103
- const header = { alg: 'HS256', typ: 'JWT' };
104
- const now = Math.floor(Date.now() / 1000);
105
-
106
- const jwtPayload = {
107
- ...payload,
108
- iat: now,
109
- exp: now + expiresIn
110
- };
111
-
112
- const headerB64 = btoa(JSON.stringify(header));
113
- const payloadB64 = btoa(JSON.stringify(jwtPayload));
114
-
115
- // Simple signature (in production, use proper HMAC-SHA256)
116
- const signature = btoa(`${headerB64}.${payloadB64}.${secret}`);
117
-
118
- return `${headerB64}.${payloadB64}.${signature}`;
119
- }
package/plugins/logger.ts DELETED
@@ -1,109 +0,0 @@
1
- import BXO from '../index';
2
-
3
- interface LoggerOptions {
4
- format?: 'simple' | 'detailed' | 'json';
5
- includeBody?: boolean;
6
- includeHeaders?: boolean;
7
- }
8
-
9
- export function logger(options: LoggerOptions = {}): BXO {
10
- const {
11
- format = 'simple',
12
- includeBody = false,
13
- includeHeaders = false
14
- } = options;
15
-
16
- const loggerInstance = new BXO();
17
-
18
- loggerInstance.onRequest(async (ctx: any) => {
19
- ctx._startTime = Date.now();
20
-
21
- if (format === 'json') {
22
- const logData: any = {
23
- timestamp: new Date().toISOString(),
24
- method: ctx.request.method,
25
- url: ctx.request.url,
26
- type: 'request'
27
- };
28
-
29
- if (includeHeaders) {
30
- logData.headers = Object.fromEntries(ctx.request.headers.entries());
31
- }
32
-
33
- if (includeBody && ctx.body) {
34
- logData.body = ctx.body;
35
- }
36
-
37
- console.log(JSON.stringify(logData));
38
- } else if (format === 'detailed') {
39
- console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
40
- if (includeHeaders) {
41
- console.log(' Headers:', Object.fromEntries(ctx.request.headers.entries()));
42
- }
43
- if (includeBody && ctx.body) {
44
- console.log(' Body:', ctx.body);
45
- }
46
- } else {
47
- console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
48
- }
49
- });
50
-
51
- loggerInstance.onResponse(async (ctx: any, response: any) => {
52
- const duration = Date.now() - (ctx._startTime || 0);
53
- const status = ctx.set.status || 200;
54
-
55
- if (format === 'json') {
56
- const logData: any = {
57
- timestamp: new Date().toISOString(),
58
- method: ctx.request.method,
59
- url: ctx.request.url,
60
- status,
61
- duration: `${duration}ms`,
62
- type: 'response'
63
- };
64
-
65
- if (includeHeaders && ctx.set.headers) {
66
- logData.responseHeaders = ctx.set.headers;
67
- }
68
-
69
- if (includeBody && response) {
70
- logData.response = response;
71
- }
72
-
73
- console.log(JSON.stringify(logData));
74
- } else if (format === 'detailed') {
75
- console.log(`← ${ctx.request.method} ${ctx.request.url} ${status} ${duration}ms`);
76
- if (includeHeaders && ctx.set.headers) {
77
- console.log(' Response Headers:', ctx.set.headers);
78
- }
79
- if (includeBody && response) {
80
- console.log(' Response:', response);
81
- }
82
- } else {
83
- const statusColor = status >= 400 ? '\x1b[31m' : status >= 300 ? '\x1b[33m' : '\x1b[32m';
84
- const resetColor = '\x1b[0m';
85
- console.log(`← ${ctx.request.method} ${ctx.request.url} ${statusColor}${status}${resetColor} ${duration}ms`);
86
- }
87
-
88
- return response;
89
- });
90
-
91
- loggerInstance.onError(async (ctx: any, error: Error) => {
92
- const duration = Date.now() - (ctx._startTime || 0);
93
-
94
- if (format === 'json') {
95
- console.log(JSON.stringify({
96
- timestamp: new Date().toISOString(),
97
- method: ctx.request.method,
98
- url: ctx.request.url,
99
- error: error.message,
100
- duration: `${duration}ms`,
101
- type: 'error'
102
- }));
103
- } else {
104
- console.log(`✗ ${ctx.request.method} ${ctx.request.url} \x1b[31mERROR\x1b[0m ${duration}ms: ${error.message}`);
105
- }
106
- });
107
-
108
- return loggerInstance;
109
- }