bxo 0.0.5-dev.6 → 0.0.5-dev.61

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
@@ -1,788 +1,5 @@
1
- import { z } from 'zod';
1
+ // Re-export everything from the refactored source
2
+ export * from './src/index';
2
3
 
3
- // Type utilities for extracting types from Zod schemas
4
- type InferZodType<T> = T extends z.ZodType<infer U> ? U : never;
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
-
18
- // Configuration interface for route handlers
19
- interface RouteConfig {
20
- params?: z.ZodSchema<any>;
21
- query?: z.ZodSchema<any>;
22
- body?: z.ZodSchema<any>;
23
- headers?: z.ZodSchema<any>;
24
- response?: z.ZodSchema<any>;
25
- detail?: RouteDetail;
26
- }
27
-
28
- // Context type that's fully typed based on the route configuration
29
- export type Context<TConfig extends RouteConfig = {}> = {
30
- params: TConfig['params'] extends z.ZodSchema<any> ? InferZodType<TConfig['params']> : Record<string, string>;
31
- query: TConfig['query'] extends z.ZodSchema<any> ? InferZodType<TConfig['query']> : Record<string, string | undefined>;
32
- body: TConfig['body'] extends z.ZodSchema<any> ? InferZodType<TConfig['body']> : unknown;
33
- headers: TConfig['headers'] extends z.ZodSchema<any> ? InferZodType<TConfig['headers']> : Record<string, string>;
34
- request: Request;
35
- set: {
36
- status?: number;
37
- headers?: Record<string, string>;
38
- };
39
- // Extended properties that can be added by plugins
40
- user?: any;
41
- [key: string]: any;
42
- };
43
-
44
- // Handler function type
45
- type Handler<TConfig extends RouteConfig = {}> = (ctx: Context<TConfig>) => Promise<any> | any;
46
-
47
- // Route definition
48
- interface Route {
49
- method: string;
50
- path: string;
51
- handler: Handler<any>;
52
- config?: RouteConfig;
53
- }
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
-
69
- // Lifecycle hooks
70
- interface LifecycleHooks {
71
- onBeforeStart?: () => Promise<void> | void;
72
- onAfterStart?: () => Promise<void> | void;
73
- onBeforeStop?: () => Promise<void> | void;
74
- onAfterStop?: () => Promise<void> | void;
75
- onBeforeRestart?: () => Promise<void> | void;
76
- onAfterRestart?: () => Promise<void> | void;
77
- onRequest?: (ctx: Context) => Promise<void> | void;
78
- onResponse?: (ctx: Context, response: any) => Promise<any> | any;
79
- onError?: (ctx: Context, error: Error) => Promise<any> | any;
80
- }
81
-
82
- export default class BXO {
83
- private _routes: Route[] = [];
84
- private _wsRoutes: WSRoute[] = [];
85
- private plugins: BXO[] = [];
86
- private hooks: LifecycleHooks = {};
87
- private server?: any;
88
- private isRunning: boolean = false;
89
- private hotReloadEnabled: boolean = false;
90
- private watchedFiles: Set<string> = new Set();
91
- private watchedExclude: Set<string> = new Set();
92
- private serverPort?: number;
93
- private serverHostname?: string;
94
-
95
- constructor() { }
96
-
97
- // Lifecycle hook methods
98
- onBeforeStart(handler: () => Promise<void> | void): this {
99
- this.hooks.onBeforeStart = handler;
100
- return this;
101
- }
102
-
103
- onAfterStart(handler: () => Promise<void> | void): this {
104
- this.hooks.onAfterStart = handler;
105
- return this;
106
- }
107
-
108
- onBeforeStop(handler: () => Promise<void> | void): this {
109
- this.hooks.onBeforeStop = handler;
110
- return this;
111
- }
112
-
113
- onAfterStop(handler: () => Promise<void> | void): this {
114
- this.hooks.onAfterStop = handler;
115
- return this;
116
- }
117
-
118
- onBeforeRestart(handler: () => Promise<void> | void): this {
119
- this.hooks.onBeforeRestart = handler;
120
- return this;
121
- }
122
-
123
- onAfterRestart(handler: () => Promise<void> | void): this {
124
- this.hooks.onAfterRestart = handler;
125
- return this;
126
- }
127
-
128
- onRequest(handler: (ctx: Context) => Promise<void> | void): this {
129
- this.hooks.onRequest = handler;
130
- return this;
131
- }
132
-
133
- onResponse(handler: (ctx: Context, response: any) => Promise<any> | any): this {
134
- this.hooks.onResponse = handler;
135
- return this;
136
- }
137
-
138
- onError(handler: (ctx: Context, error: Error) => Promise<any> | any): this {
139
- this.hooks.onError = handler;
140
- return this;
141
- }
142
-
143
- // Plugin system - now accepts other BXO instances
144
- use(bxoInstance: BXO): this {
145
- this.plugins.push(bxoInstance);
146
- return this;
147
- }
148
-
149
- // HTTP method handlers with overloads for type safety
150
- get<TConfig extends RouteConfig = {}>(
151
- path: string,
152
- handler: Handler<TConfig>
153
- ): this;
154
- get<TConfig extends RouteConfig = {}>(
155
- path: string,
156
- handler: Handler<TConfig>,
157
- config: TConfig
158
- ): this;
159
- get<TConfig extends RouteConfig = {}>(
160
- path: string,
161
- handler: Handler<TConfig>,
162
- config?: TConfig
163
- ): this {
164
- this._routes.push({ method: 'GET', path, handler, config });
165
- return this;
166
- }
167
-
168
- post<TConfig extends RouteConfig = {}>(
169
- path: string,
170
- handler: Handler<TConfig>
171
- ): this;
172
- post<TConfig extends RouteConfig = {}>(
173
- path: string,
174
- handler: Handler<TConfig>,
175
- config: TConfig
176
- ): this;
177
- post<TConfig extends RouteConfig = {}>(
178
- path: string,
179
- handler: Handler<TConfig>,
180
- config?: TConfig
181
- ): this {
182
- this._routes.push({ method: 'POST', path, handler, config });
183
- return this;
184
- }
185
-
186
- put<TConfig extends RouteConfig = {}>(
187
- path: string,
188
- handler: Handler<TConfig>
189
- ): this;
190
- put<TConfig extends RouteConfig = {}>(
191
- path: string,
192
- handler: Handler<TConfig>,
193
- config: TConfig
194
- ): this;
195
- put<TConfig extends RouteConfig = {}>(
196
- path: string,
197
- handler: Handler<TConfig>,
198
- config?: TConfig
199
- ): this {
200
- this._routes.push({ method: 'PUT', path, handler, config });
201
- return this;
202
- }
203
-
204
- delete<TConfig extends RouteConfig = {}>(
205
- path: string,
206
- handler: Handler<TConfig>
207
- ): this;
208
- delete<TConfig extends RouteConfig = {}>(
209
- path: string,
210
- handler: Handler<TConfig>,
211
- config: TConfig
212
- ): this;
213
- delete<TConfig extends RouteConfig = {}>(
214
- path: string,
215
- handler: Handler<TConfig>,
216
- config?: TConfig
217
- ): this {
218
- this._routes.push({ method: 'DELETE', path, handler, config });
219
- return this;
220
- }
221
-
222
- patch<TConfig extends RouteConfig = {}>(
223
- path: string,
224
- handler: Handler<TConfig>
225
- ): this;
226
- patch<TConfig extends RouteConfig = {}>(
227
- path: string,
228
- handler: Handler<TConfig>,
229
- config: TConfig
230
- ): this;
231
- patch<TConfig extends RouteConfig = {}>(
232
- path: string,
233
- handler: Handler<TConfig>,
234
- config?: TConfig
235
- ): this {
236
- this._routes.push({ method: 'PATCH', path, handler, config });
237
- return this;
238
- }
239
-
240
- // WebSocket route handler
241
- ws(path: string, handler: WebSocketHandler): this {
242
- this._wsRoutes.push({ path, handler });
243
- return this;
244
- }
245
-
246
- // Route matching utility
247
- private matchRoute(method: string, pathname: string): { route: Route; params: Record<string, string> } | null {
248
- for (const route of this._routes) {
249
- if (route.method !== method) continue;
250
-
251
- const routeSegments = route.path.split('/').filter(Boolean);
252
- const pathSegments = pathname.split('/').filter(Boolean);
253
-
254
- if (routeSegments.length !== pathSegments.length) continue;
255
-
256
- const params: Record<string, string> = {};
257
- let isMatch = true;
258
-
259
- for (let i = 0; i < routeSegments.length; i++) {
260
- const routeSegment = routeSegments[i];
261
- const pathSegment = pathSegments[i];
262
-
263
- if (!routeSegment || !pathSegment) {
264
- isMatch = false;
265
- break;
266
- }
267
-
268
- if (routeSegment.startsWith(':')) {
269
- const paramName = routeSegment.slice(1);
270
- params[paramName] = decodeURIComponent(pathSegment);
271
- } else if (routeSegment !== pathSegment) {
272
- isMatch = false;
273
- break;
274
- }
275
- }
276
-
277
- if (isMatch) {
278
- return { route, params };
279
- }
280
- }
281
-
282
- return null;
283
- }
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
-
322
- // Parse query string
323
- private parseQuery(searchParams: URLSearchParams): Record<string, string | undefined> {
324
- const query: Record<string, string | undefined> = {};
325
- for (const [key, value] of searchParams.entries()) {
326
- query[key] = value;
327
- }
328
- return query;
329
- }
330
-
331
- // Parse headers
332
- private parseHeaders(headers: Headers): Record<string, string> {
333
- const headerObj: Record<string, string> = {};
334
- for (const [key, value] of headers.entries()) {
335
- headerObj[key] = value;
336
- }
337
- return headerObj;
338
- }
339
-
340
- // Validate data against Zod schema
341
- private validateData<T>(schema: z.ZodSchema<T> | undefined, data: any): T {
342
- if (!schema) return data;
343
- return schema.parse(data);
344
- }
345
-
346
- // Main request handler
347
- private async handleRequest(request: Request, server?: any): Promise<Response | undefined> {
348
- const url = new URL(request.url);
349
- const method = request.method;
350
- const pathname = url.pathname;
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
-
371
- const matchResult = this.matchRoute(method, pathname);
372
- if (!matchResult) {
373
- return new Response('Not Found', { status: 404 });
374
- }
375
-
376
- const { route, params } = matchResult;
377
- const query = this.parseQuery(url.searchParams);
378
- const headers = this.parseHeaders(request.headers);
379
-
380
- let body: any;
381
- if (request.method !== 'GET' && request.method !== 'HEAD') {
382
- const contentType = request.headers.get('content-type');
383
- if (contentType?.includes('application/json')) {
384
- try {
385
- body = await request.json();
386
- } catch {
387
- body = {};
388
- }
389
- } else if (contentType?.includes('application/x-www-form-urlencoded')) {
390
- const formData = await request.formData();
391
- body = Object.fromEntries(formData.entries());
392
- } else {
393
- body = await request.text();
394
- }
395
- }
396
-
397
- // Create context
398
- const ctx: Context = {
399
- params: route.config?.params ? this.validateData(route.config.params, params) : params,
400
- query: route.config?.query ? this.validateData(route.config.query, query) : query,
401
- body: route.config?.body ? this.validateData(route.config.body, body) : body,
402
- headers: route.config?.headers ? this.validateData(route.config.headers, headers) : headers,
403
- request,
404
- set: {}
405
- };
406
-
407
- try {
408
- // Run global onRequest hook
409
- if (this.hooks.onRequest) {
410
- await this.hooks.onRequest(ctx);
411
- }
412
-
413
- // Run BXO instance onRequest hooks
414
- for (const bxoInstance of this.plugins) {
415
- if (bxoInstance.hooks.onRequest) {
416
- await bxoInstance.hooks.onRequest(ctx);
417
- }
418
- }
419
-
420
- // Execute route handler
421
- let response = await route.handler(ctx);
422
-
423
- // Run global onResponse hook
424
- if (this.hooks.onResponse) {
425
- response = await this.hooks.onResponse(ctx, response) || response;
426
- }
427
-
428
- // Run BXO instance onResponse hooks
429
- for (const bxoInstance of this.plugins) {
430
- if (bxoInstance.hooks.onResponse) {
431
- response = await bxoInstance.hooks.onResponse(ctx, response) || response;
432
- }
433
- }
434
-
435
- // Validate response against schema if provided
436
- if (route.config?.response && !(response instanceof Response)) {
437
- try {
438
- response = this.validateData(route.config.response, response);
439
- } catch (validationError) {
440
- // Response validation failed
441
- const errorMessage = validationError instanceof Error ? validationError.message : 'Response validation failed';
442
- return new Response(JSON.stringify({ error: `Response validation error: ${errorMessage}` }), {
443
- status: 500,
444
- headers: { 'Content-Type': 'application/json' }
445
- });
446
- }
447
- }
448
-
449
- // Convert response to Response object
450
- if (response instanceof Response) {
451
- return response;
452
- }
453
-
454
- const responseInit: ResponseInit = {
455
- status: ctx.set.status || 200,
456
- headers: ctx.set.headers || {}
457
- };
458
-
459
- if (typeof response === 'string') {
460
- return new Response(response, responseInit);
461
- }
462
-
463
- return new Response(JSON.stringify(response), {
464
- ...responseInit,
465
- headers: {
466
- 'Content-Type': 'application/json',
467
- ...responseInit.headers
468
- }
469
- });
470
-
471
- } catch (error) {
472
- // Run error hooks
473
- let errorResponse: any;
474
-
475
- if (this.hooks.onError) {
476
- errorResponse = await this.hooks.onError(ctx, error as Error);
477
- }
478
-
479
- for (const bxoInstance of this.plugins) {
480
- if (bxoInstance.hooks.onError) {
481
- errorResponse = await bxoInstance.hooks.onError(ctx, error as Error) || errorResponse;
482
- }
483
- }
484
-
485
- if (errorResponse) {
486
- if (errorResponse instanceof Response) {
487
- return errorResponse;
488
- }
489
- return new Response(JSON.stringify(errorResponse), {
490
- status: 500,
491
- headers: { 'Content-Type': 'application/json' }
492
- });
493
- }
494
-
495
- // Default error response
496
- const errorMessage = error instanceof Error ? error.message : 'Internal Server Error';
497
- return new Response(JSON.stringify({ error: errorMessage }), {
498
- status: 500,
499
- headers: { 'Content-Type': 'application/json' }
500
- });
501
- }
502
- }
503
-
504
- // Hot reload functionality
505
- enableHotReload(watchPaths: string[] = ['./'], excludePatterns: string[] = []): this {
506
- this.hotReloadEnabled = true;
507
- watchPaths.forEach(path => this.watchedFiles.add(path));
508
- excludePatterns.forEach(pattern => this.watchedExclude.add(pattern));
509
- return this;
510
- }
511
-
512
- private shouldExcludeFile(filename: string): boolean {
513
- for (const pattern of this.watchedExclude) {
514
- // Handle exact match
515
- if (pattern === filename) {
516
- return true;
517
- }
518
-
519
- // Handle directory patterns (e.g., "node_modules/", "dist/")
520
- if (pattern.endsWith('/')) {
521
- if (filename.startsWith(pattern) || filename.includes(`/${pattern}`)) {
522
- return true;
523
- }
524
- }
525
-
526
- // Handle wildcard patterns (e.g., "*.log", "temp*")
527
- if (pattern.includes('*')) {
528
- const regex = new RegExp(pattern.replace(/\*/g, '.*'));
529
- if (regex.test(filename)) {
530
- return true;
531
- }
532
- }
533
-
534
- // Handle file extension patterns (e.g., ".log", ".tmp")
535
- if (pattern.startsWith('.') && filename.endsWith(pattern)) {
536
- return true;
537
- }
538
-
539
- // Handle substring matches for directories
540
- if (filename.includes(pattern)) {
541
- return true;
542
- }
543
- }
544
-
545
- return false;
546
- }
547
-
548
- private async setupFileWatcher(port: number, hostname: string): Promise<void> {
549
- if (!this.hotReloadEnabled) return;
550
-
551
- const fs = require('fs');
552
-
553
- for (const watchPath of this.watchedFiles) {
554
- try {
555
- fs.watch(watchPath, { recursive: true }, async (eventType: string, filename: string) => {
556
- if (filename && (filename.endsWith('.ts') || filename.endsWith('.js'))) {
557
- // Check if file should be excluded
558
- if (this.shouldExcludeFile(filename)) {
559
- return;
560
- }
561
-
562
- console.log(`🔄 File changed: ${filename}, restarting server...`);
563
- await this.restart(port, hostname);
564
- }
565
- });
566
- console.log(`👀 Watching ${watchPath} for changes...`);
567
- if (this.watchedExclude.size > 0) {
568
- console.log(`🚫 Excluding patterns: ${Array.from(this.watchedExclude).join(', ')}`);
569
- }
570
- } catch (error) {
571
- console.warn(`⚠️ Could not watch ${watchPath}:`, error);
572
- }
573
- }
574
- }
575
-
576
- // Server management methods
577
- async start(port: number = 3000, hostname: string = 'localhost'): Promise<void> {
578
- if (this.isRunning) {
579
- console.log('⚠️ Server is already running');
580
- return;
581
- }
582
-
583
- try {
584
- // Before start hook
585
- if (this.hooks.onBeforeStart) {
586
- await this.hooks.onBeforeStart();
587
- }
588
-
589
- this.server = Bun.serve({
590
- port,
591
- hostname,
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
- }
613
- });
614
-
615
- this.isRunning = true;
616
- this.serverPort = port;
617
- this.serverHostname = hostname;
618
-
619
- console.log(`🦊 BXO server running at http://${hostname}:${port}`);
620
-
621
- // After start hook
622
- if (this.hooks.onAfterStart) {
623
- await this.hooks.onAfterStart();
624
- }
625
-
626
- // Setup hot reload
627
- await this.setupFileWatcher(port, hostname);
628
-
629
- // Handle graceful shutdown
630
- const shutdownHandler = async () => {
631
- await this.stop();
632
- process.exit(0);
633
- };
634
-
635
- process.on('SIGINT', shutdownHandler);
636
- process.on('SIGTERM', shutdownHandler);
637
-
638
- } catch (error) {
639
- console.error('❌ Failed to start server:', error);
640
- throw error;
641
- }
642
- }
643
-
644
- async stop(): Promise<void> {
645
- if (!this.isRunning) {
646
- console.log('⚠️ Server is not running');
647
- return;
648
- }
649
-
650
- try {
651
- // Before stop hook
652
- if (this.hooks.onBeforeStop) {
653
- await this.hooks.onBeforeStop();
654
- }
655
-
656
- if (this.server) {
657
- this.server.stop();
658
- this.server = null;
659
- }
660
-
661
- this.isRunning = false;
662
- this.serverPort = undefined;
663
- this.serverHostname = undefined;
664
-
665
- console.log('🛑 BXO server stopped');
666
-
667
- // After stop hook
668
- if (this.hooks.onAfterStop) {
669
- await this.hooks.onAfterStop();
670
- }
671
-
672
- } catch (error) {
673
- console.error('❌ Error stopping server:', error);
674
- throw error;
675
- }
676
- }
677
-
678
- async restart(port: number = 3000, hostname: string = 'localhost'): Promise<void> {
679
- try {
680
- // Before restart hook
681
- if (this.hooks.onBeforeRestart) {
682
- await this.hooks.onBeforeRestart();
683
- }
684
-
685
- console.log('🔄 Restarting BXO server...');
686
-
687
- await this.stop();
688
-
689
- // Small delay to ensure cleanup
690
- await new Promise(resolve => setTimeout(resolve, 100));
691
-
692
- await this.start(port, hostname);
693
-
694
- // After restart hook
695
- if (this.hooks.onAfterRestart) {
696
- await this.hooks.onAfterRestart();
697
- }
698
-
699
- } catch (error) {
700
- console.error('❌ Error restarting server:', error);
701
- throw error;
702
- }
703
- }
704
-
705
- // Backward compatibility
706
- async listen(port: number = 3000, hostname: string = 'localhost'): Promise<void> {
707
- return this.start(port, hostname);
708
- }
709
-
710
- // Server status
711
- isServerRunning(): boolean {
712
- return this.isRunning;
713
- }
714
-
715
- getServerInfo(): { running: boolean; hotReload: boolean; watchedFiles: string[]; excludePatterns: string[] } {
716
- return {
717
- running: this.isRunning,
718
- hotReload: this.hotReloadEnabled,
719
- watchedFiles: Array.from(this.watchedFiles),
720
- excludePatterns: Array.from(this.watchedExclude)
721
- };
722
- }
723
-
724
- // Get server information (alias for getServerInfo)
725
- get info() {
726
- return {
727
- // Server status
728
- running: this.isRunning,
729
- server: this.server ? 'Bun' : null,
730
-
731
- // Connection details
732
- hostname: this.serverHostname,
733
- port: this.serverPort,
734
- url: this.isRunning && this.serverHostname && this.serverPort
735
- ? `http://${this.serverHostname}:${this.serverPort}`
736
- : null,
737
-
738
- // Application statistics
739
- totalRoutes: this._routes.length,
740
- totalWsRoutes: this._wsRoutes.length,
741
- totalPlugins: this.plugins.length,
742
-
743
- // Hot reload configuration
744
- hotReload: this.hotReloadEnabled,
745
- watchedFiles: Array.from(this.watchedFiles),
746
- excludePatterns: Array.from(this.watchedExclude),
747
-
748
- // System information
749
- runtime: 'Bun',
750
- version: typeof Bun !== 'undefined' ? Bun.version : 'unknown',
751
- pid: process.pid,
752
- uptime: this.isRunning ? process.uptime() : 0
753
- };
754
- }
755
-
756
- // Get all routes information
757
- get routes() {
758
- return this._routes.map((route: Route) => ({
759
- method: route.method,
760
- path: route.path,
761
- hasConfig: !!route.config,
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
- }
776
- }));
777
- }
778
- }
779
-
780
- const error = (error: Error | string, status: number = 500) => {
781
- return new Response(JSON.stringify({ error: error instanceof Error ? error.message : error }), { status });
782
- }
783
-
784
- // Export Zod for convenience
785
- export { z, error };
786
-
787
- // Export types for external use
788
- export type { RouteConfig, RouteDetail, Handler, WebSocketHandler, WSRoute };
4
+ // Also export the default BXO class for backward compatibility
5
+ export { default } from './src/index';