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.
Files changed (2) hide show
  1. package/index.ts +128 -17
  2. 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 };
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.4",
4
+ "version": "0.0.5-dev.6",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "devDependencies": {