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 +69 -14
- package/package.json +1 -1
- package/plugins/auth.ts +0 -119
- package/plugins/logger.ts +0 -109
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
|
-
|
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
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
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
|
-
|
670
|
-
|
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
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
|
-
}
|