bxo 0.0.5-dev.3 → 0.0.5-dev.5

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 -10
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -3,6 +3,18 @@ import { z } from 'zod';
3
3
  // Type utilities for extracting types from Zod schemas
4
4
  type InferZodType<T> = T extends z.ZodType<infer U> ? U : never;
5
5
 
6
+ // OpenAPI detail information
7
+ interface RouteDetail {
8
+ summary?: string;
9
+ description?: string;
10
+ tags?: string[];
11
+ operationId?: string;
12
+ deprecated?: boolean;
13
+ produces?: string[];
14
+ consumes?: string[];
15
+ [key: string]: any; // Allow additional OpenAPI properties
16
+ }
17
+
6
18
  // Configuration interface for route handlers
7
19
  interface RouteConfig {
8
20
  params?: z.ZodSchema<any>;
@@ -10,6 +22,7 @@ interface RouteConfig {
10
22
  body?: z.ZodSchema<any>;
11
23
  headers?: z.ZodSchema<any>;
12
24
  response?: z.ZodSchema<any>;
25
+ detail?: RouteDetail;
13
26
  }
14
27
 
15
28
  // Context type that's fully typed based on the route configuration
@@ -39,6 +52,20 @@ interface Route {
39
52
  config?: RouteConfig;
40
53
  }
41
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
+
42
69
  // Lifecycle hooks
43
70
  interface LifecycleHooks {
44
71
  onBeforeStart?: () => Promise<void> | void;
@@ -54,6 +81,7 @@ interface LifecycleHooks {
54
81
 
55
82
  export default class BXO {
56
83
  private _routes: Route[] = [];
84
+ private _wsRoutes: WSRoute[] = [];
57
85
  private plugins: BXO[] = [];
58
86
  private hooks: LifecycleHooks = {};
59
87
  private server?: any;
@@ -209,6 +237,12 @@ export default class BXO {
209
237
  return this;
210
238
  }
211
239
 
240
+ // WebSocket route handler
241
+ ws(path: string, handler: WebSocketHandler): this {
242
+ this._wsRoutes.push({ path, handler });
243
+ return this;
244
+ }
245
+
212
246
  // Route matching utility
213
247
  private matchRoute(method: string, pathname: string): { route: Route; params: Record<string, string> } | null {
214
248
  for (const route of this._routes) {
@@ -248,6 +282,43 @@ export default class BXO {
248
282
  return null;
249
283
  }
250
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
+
251
322
  // Parse query string
252
323
  private parseQuery(searchParams: URLSearchParams): Record<string, string | undefined> {
253
324
  const query: Record<string, string | undefined> = {};
@@ -273,11 +344,30 @@ export default class BXO {
273
344
  }
274
345
 
275
346
  // Main request handler
276
- private async handleRequest(request: Request): Promise<Response> {
347
+ private async handleRequest(request: Request, server?: any): Promise<Response | undefined> {
277
348
  const url = new URL(request.url);
278
349
  const method = request.method;
279
350
  const pathname = url.pathname;
280
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
+
281
371
  const matchResult = this.matchRoute(method, pathname);
282
372
  if (!matchResult) {
283
373
  return new Response('Not Found', { status: 404 });
@@ -499,7 +589,27 @@ export default class BXO {
499
589
  this.server = Bun.serve({
500
590
  port,
501
591
  hostname,
502
- 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
+ }
503
613
  });
504
614
 
505
615
  this.isRunning = true;
@@ -627,6 +737,7 @@ export default class BXO {
627
737
 
628
738
  // Application statistics
629
739
  totalRoutes: this._routes.length,
740
+ totalWsRoutes: this._wsRoutes.length,
630
741
  totalPlugins: this.plugins.length,
631
742
 
632
743
  // Hot reload configuration
@@ -648,13 +759,20 @@ export default class BXO {
648
759
  method: route.method,
649
760
  path: route.path,
650
761
  hasConfig: !!route.config,
651
- config: route.config ? {
652
- hasParams: !!route.config.params,
653
- hasQuery: !!route.config.query,
654
- hasBody: !!route.config.body,
655
- hasHeaders: !!route.config.headers,
656
- hasResponse: !!route.config.response
657
- } : null
762
+ config: route.config || null
763
+ }));
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
+ }
658
776
  }));
659
777
  }
660
778
  }
@@ -667,4 +785,4 @@ const error = (error: Error, status: number = 500) => {
667
785
  export { z, error };
668
786
 
669
787
  // Export types for external use
670
- export type { RouteConfig, 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.3",
4
+ "version": "0.0.5-dev.5",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "devDependencies": {