bxo 0.0.5-dev.4 → 0.0.5-dev.6
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 +128 -17
- package/package.json +1 -1
package/index.ts
CHANGED
@@ -52,6 +52,20 @@ interface Route {
|
|
52
52
|
config?: RouteConfig;
|
53
53
|
}
|
54
54
|
|
55
|
+
// WebSocket handler interface
|
56
|
+
interface WebSocketHandler {
|
57
|
+
onOpen?: (ws: any) => void;
|
58
|
+
onMessage?: (ws: any, message: string | Buffer) => void;
|
59
|
+
onClose?: (ws: any, code?: number, reason?: string) => void;
|
60
|
+
onError?: (ws: any, error: Error) => void;
|
61
|
+
}
|
62
|
+
|
63
|
+
// WebSocket route definition
|
64
|
+
interface WSRoute {
|
65
|
+
path: string;
|
66
|
+
handler: WebSocketHandler;
|
67
|
+
}
|
68
|
+
|
55
69
|
// Lifecycle hooks
|
56
70
|
interface LifecycleHooks {
|
57
71
|
onBeforeStart?: () => Promise<void> | void;
|
@@ -67,6 +81,7 @@ interface LifecycleHooks {
|
|
67
81
|
|
68
82
|
export default class BXO {
|
69
83
|
private _routes: Route[] = [];
|
84
|
+
private _wsRoutes: WSRoute[] = [];
|
70
85
|
private plugins: BXO[] = [];
|
71
86
|
private hooks: LifecycleHooks = {};
|
72
87
|
private server?: any;
|
@@ -222,6 +237,12 @@ export default class BXO {
|
|
222
237
|
return this;
|
223
238
|
}
|
224
239
|
|
240
|
+
// WebSocket route handler
|
241
|
+
ws(path: string, handler: WebSocketHandler): this {
|
242
|
+
this._wsRoutes.push({ path, handler });
|
243
|
+
return this;
|
244
|
+
}
|
245
|
+
|
225
246
|
// Route matching utility
|
226
247
|
private matchRoute(method: string, pathname: string): { route: Route; params: Record<string, string> } | null {
|
227
248
|
for (const route of this._routes) {
|
@@ -261,6 +282,43 @@ export default class BXO {
|
|
261
282
|
return null;
|
262
283
|
}
|
263
284
|
|
285
|
+
// WebSocket route matching utility
|
286
|
+
private matchWSRoute(pathname: string): { route: WSRoute; params: Record<string, string> } | null {
|
287
|
+
for (const route of this._wsRoutes) {
|
288
|
+
const routeSegments = route.path.split('/').filter(Boolean);
|
289
|
+
const pathSegments = pathname.split('/').filter(Boolean);
|
290
|
+
|
291
|
+
if (routeSegments.length !== pathSegments.length) continue;
|
292
|
+
|
293
|
+
const params: Record<string, string> = {};
|
294
|
+
let isMatch = true;
|
295
|
+
|
296
|
+
for (let i = 0; i < routeSegments.length; i++) {
|
297
|
+
const routeSegment = routeSegments[i];
|
298
|
+
const pathSegment = pathSegments[i];
|
299
|
+
|
300
|
+
if (!routeSegment || !pathSegment) {
|
301
|
+
isMatch = false;
|
302
|
+
break;
|
303
|
+
}
|
304
|
+
|
305
|
+
if (routeSegment.startsWith(':')) {
|
306
|
+
const paramName = routeSegment.slice(1);
|
307
|
+
params[paramName] = decodeURIComponent(pathSegment);
|
308
|
+
} else if (routeSegment !== pathSegment) {
|
309
|
+
isMatch = false;
|
310
|
+
break;
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
if (isMatch) {
|
315
|
+
return { route, params };
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
return null;
|
320
|
+
}
|
321
|
+
|
264
322
|
// Parse query string
|
265
323
|
private parseQuery(searchParams: URLSearchParams): Record<string, string | undefined> {
|
266
324
|
const query: Record<string, string | undefined> = {};
|
@@ -286,11 +344,30 @@ export default class BXO {
|
|
286
344
|
}
|
287
345
|
|
288
346
|
// Main request handler
|
289
|
-
private async handleRequest(request: Request): Promise<Response> {
|
347
|
+
private async handleRequest(request: Request, server?: any): Promise<Response | undefined> {
|
290
348
|
const url = new URL(request.url);
|
291
349
|
const method = request.method;
|
292
350
|
const pathname = url.pathname;
|
293
351
|
|
352
|
+
// Check for WebSocket upgrade
|
353
|
+
if (request.headers.get('upgrade') === 'websocket') {
|
354
|
+
const wsMatchResult = this.matchWSRoute(pathname);
|
355
|
+
if (wsMatchResult && server) {
|
356
|
+
const success = server.upgrade(request, {
|
357
|
+
data: {
|
358
|
+
handler: wsMatchResult.route.handler,
|
359
|
+
params: wsMatchResult.params,
|
360
|
+
pathname
|
361
|
+
}
|
362
|
+
});
|
363
|
+
|
364
|
+
if (success) {
|
365
|
+
return; // undefined response means upgrade was successful
|
366
|
+
}
|
367
|
+
}
|
368
|
+
return new Response('WebSocket upgrade failed', { status: 400 });
|
369
|
+
}
|
370
|
+
|
294
371
|
const matchResult = this.matchRoute(method, pathname);
|
295
372
|
if (!matchResult) {
|
296
373
|
return new Response('Not Found', { status: 404 });
|
@@ -438,14 +515,14 @@ export default class BXO {
|
|
438
515
|
if (pattern === filename) {
|
439
516
|
return true;
|
440
517
|
}
|
441
|
-
|
518
|
+
|
442
519
|
// Handle directory patterns (e.g., "node_modules/", "dist/")
|
443
520
|
if (pattern.endsWith('/')) {
|
444
521
|
if (filename.startsWith(pattern) || filename.includes(`/${pattern}`)) {
|
445
522
|
return true;
|
446
523
|
}
|
447
524
|
}
|
448
|
-
|
525
|
+
|
449
526
|
// Handle wildcard patterns (e.g., "*.log", "temp*")
|
450
527
|
if (pattern.includes('*')) {
|
451
528
|
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
@@ -453,18 +530,18 @@ export default class BXO {
|
|
453
530
|
return true;
|
454
531
|
}
|
455
532
|
}
|
456
|
-
|
533
|
+
|
457
534
|
// Handle file extension patterns (e.g., ".log", ".tmp")
|
458
535
|
if (pattern.startsWith('.') && filename.endsWith(pattern)) {
|
459
536
|
return true;
|
460
537
|
}
|
461
|
-
|
538
|
+
|
462
539
|
// Handle substring matches for directories
|
463
540
|
if (filename.includes(pattern)) {
|
464
541
|
return true;
|
465
542
|
}
|
466
543
|
}
|
467
|
-
|
544
|
+
|
468
545
|
return false;
|
469
546
|
}
|
470
547
|
|
@@ -481,7 +558,7 @@ export default class BXO {
|
|
481
558
|
if (this.shouldExcludeFile(filename)) {
|
482
559
|
return;
|
483
560
|
}
|
484
|
-
|
561
|
+
|
485
562
|
console.log(`🔄 File changed: ${filename}, restarting server...`);
|
486
563
|
await this.restart(port, hostname);
|
487
564
|
}
|
@@ -512,7 +589,27 @@ export default class BXO {
|
|
512
589
|
this.server = Bun.serve({
|
513
590
|
port,
|
514
591
|
hostname,
|
515
|
-
fetch: (request) => this.handleRequest(request),
|
592
|
+
fetch: (request, server) => this.handleRequest(request, server),
|
593
|
+
websocket: {
|
594
|
+
message: (ws: any, message: any) => {
|
595
|
+
const handler = ws.data?.handler;
|
596
|
+
if (handler?.onMessage) {
|
597
|
+
handler.onMessage(ws, message);
|
598
|
+
}
|
599
|
+
},
|
600
|
+
open: (ws: any) => {
|
601
|
+
const handler = ws.data?.handler;
|
602
|
+
if (handler?.onOpen) {
|
603
|
+
handler.onOpen(ws);
|
604
|
+
}
|
605
|
+
},
|
606
|
+
close: (ws: any, code?: number, reason?: string) => {
|
607
|
+
const handler = ws.data?.handler;
|
608
|
+
if (handler?.onClose) {
|
609
|
+
handler.onClose(ws, code, reason);
|
610
|
+
}
|
611
|
+
}
|
612
|
+
}
|
516
613
|
});
|
517
614
|
|
518
615
|
this.isRunning = true;
|
@@ -630,23 +727,24 @@ export default class BXO {
|
|
630
727
|
// Server status
|
631
728
|
running: this.isRunning,
|
632
729
|
server: this.server ? 'Bun' : null,
|
633
|
-
|
730
|
+
|
634
731
|
// Connection details
|
635
732
|
hostname: this.serverHostname,
|
636
733
|
port: this.serverPort,
|
637
|
-
url: this.isRunning && this.serverHostname && this.serverPort
|
638
|
-
? `http://${this.serverHostname}:${this.serverPort}`
|
734
|
+
url: this.isRunning && this.serverHostname && this.serverPort
|
735
|
+
? `http://${this.serverHostname}:${this.serverPort}`
|
639
736
|
: null,
|
640
|
-
|
737
|
+
|
641
738
|
// Application statistics
|
642
739
|
totalRoutes: this._routes.length,
|
740
|
+
totalWsRoutes: this._wsRoutes.length,
|
643
741
|
totalPlugins: this.plugins.length,
|
644
|
-
|
742
|
+
|
645
743
|
// Hot reload configuration
|
646
744
|
hotReload: this.hotReloadEnabled,
|
647
745
|
watchedFiles: Array.from(this.watchedFiles),
|
648
746
|
excludePatterns: Array.from(this.watchedExclude),
|
649
|
-
|
747
|
+
|
650
748
|
// System information
|
651
749
|
runtime: 'Bun',
|
652
750
|
version: typeof Bun !== 'undefined' ? Bun.version : 'unknown',
|
@@ -664,14 +762,27 @@ export default class BXO {
|
|
664
762
|
config: route.config || null
|
665
763
|
}));
|
666
764
|
}
|
765
|
+
|
766
|
+
// Get all WebSocket routes information
|
767
|
+
get wsRoutes() {
|
768
|
+
return this._wsRoutes.map((route: WSRoute) => ({
|
769
|
+
path: route.path,
|
770
|
+
hasHandlers: {
|
771
|
+
onOpen: !!route.handler.onOpen,
|
772
|
+
onMessage: !!route.handler.onMessage,
|
773
|
+
onClose: !!route.handler.onClose,
|
774
|
+
onError: !!route.handler.onError
|
775
|
+
}
|
776
|
+
}));
|
777
|
+
}
|
667
778
|
}
|
668
779
|
|
669
|
-
const error = (error: Error, status: number = 500) => {
|
670
|
-
return new Response(JSON.stringify({ error: error.message }), { status });
|
780
|
+
const error = (error: Error | string, status: number = 500) => {
|
781
|
+
return new Response(JSON.stringify({ error: error instanceof Error ? error.message : error }), { status });
|
671
782
|
}
|
672
783
|
|
673
784
|
// Export Zod for convenience
|
674
785
|
export { z, error };
|
675
786
|
|
676
787
|
// Export types for external use
|
677
|
-
export type { RouteConfig, RouteDetail, Handler };
|
788
|
+
export type { RouteConfig, RouteDetail, Handler, WebSocketHandler, WSRoute };
|