bxo 0.0.5-dev.4 → 0.0.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.
- package/index.ts +11 -133
- package/package.json +1 -1
package/index.ts
CHANGED
@@ -3,26 +3,12 @@ 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
|
-
|
18
6
|
// Configuration interface for route handlers
|
19
7
|
interface RouteConfig {
|
20
8
|
params?: z.ZodSchema<any>;
|
21
9
|
query?: z.ZodSchema<any>;
|
22
10
|
body?: z.ZodSchema<any>;
|
23
11
|
headers?: z.ZodSchema<any>;
|
24
|
-
response?: z.ZodSchema<any>;
|
25
|
-
detail?: RouteDetail;
|
26
12
|
}
|
27
13
|
|
28
14
|
// Context type that's fully typed based on the route configuration
|
@@ -66,16 +52,13 @@ interface LifecycleHooks {
|
|
66
52
|
}
|
67
53
|
|
68
54
|
export default class BXO {
|
69
|
-
private
|
55
|
+
private routes: Route[] = [];
|
70
56
|
private plugins: BXO[] = [];
|
71
57
|
private hooks: LifecycleHooks = {};
|
72
58
|
private server?: any;
|
73
59
|
private isRunning: boolean = false;
|
74
60
|
private hotReloadEnabled: boolean = false;
|
75
61
|
private watchedFiles: Set<string> = new Set();
|
76
|
-
private watchedExclude: Set<string> = new Set();
|
77
|
-
private serverPort?: number;
|
78
|
-
private serverHostname?: string;
|
79
62
|
|
80
63
|
constructor() { }
|
81
64
|
|
@@ -146,7 +129,7 @@ export default class BXO {
|
|
146
129
|
handler: Handler<TConfig>,
|
147
130
|
config?: TConfig
|
148
131
|
): this {
|
149
|
-
this.
|
132
|
+
this.routes.push({ method: 'GET', path, handler, config });
|
150
133
|
return this;
|
151
134
|
}
|
152
135
|
|
@@ -164,7 +147,7 @@ export default class BXO {
|
|
164
147
|
handler: Handler<TConfig>,
|
165
148
|
config?: TConfig
|
166
149
|
): this {
|
167
|
-
this.
|
150
|
+
this.routes.push({ method: 'POST', path, handler, config });
|
168
151
|
return this;
|
169
152
|
}
|
170
153
|
|
@@ -182,7 +165,7 @@ export default class BXO {
|
|
182
165
|
handler: Handler<TConfig>,
|
183
166
|
config?: TConfig
|
184
167
|
): this {
|
185
|
-
this.
|
168
|
+
this.routes.push({ method: 'PUT', path, handler, config });
|
186
169
|
return this;
|
187
170
|
}
|
188
171
|
|
@@ -200,7 +183,7 @@ export default class BXO {
|
|
200
183
|
handler: Handler<TConfig>,
|
201
184
|
config?: TConfig
|
202
185
|
): this {
|
203
|
-
this.
|
186
|
+
this.routes.push({ method: 'DELETE', path, handler, config });
|
204
187
|
return this;
|
205
188
|
}
|
206
189
|
|
@@ -218,13 +201,13 @@ export default class BXO {
|
|
218
201
|
handler: Handler<TConfig>,
|
219
202
|
config?: TConfig
|
220
203
|
): this {
|
221
|
-
this.
|
204
|
+
this.routes.push({ method: 'PATCH', path, handler, config });
|
222
205
|
return this;
|
223
206
|
}
|
224
207
|
|
225
208
|
// Route matching utility
|
226
209
|
private matchRoute(method: string, pathname: string): { route: Route; params: Record<string, string> } | null {
|
227
|
-
for (const route of this.
|
210
|
+
for (const route of this.routes) {
|
228
211
|
if (route.method !== method) continue;
|
229
212
|
|
230
213
|
const routeSegments = route.path.split('/').filter(Boolean);
|
@@ -355,20 +338,6 @@ export default class BXO {
|
|
355
338
|
}
|
356
339
|
}
|
357
340
|
|
358
|
-
// Validate response against schema if provided
|
359
|
-
if (route.config?.response && !(response instanceof Response)) {
|
360
|
-
try {
|
361
|
-
response = this.validateData(route.config.response, response);
|
362
|
-
} catch (validationError) {
|
363
|
-
// Response validation failed
|
364
|
-
const errorMessage = validationError instanceof Error ? validationError.message : 'Response validation failed';
|
365
|
-
return new Response(JSON.stringify({ error: `Response validation error: ${errorMessage}` }), {
|
366
|
-
status: 500,
|
367
|
-
headers: { 'Content-Type': 'application/json' }
|
368
|
-
});
|
369
|
-
}
|
370
|
-
}
|
371
|
-
|
372
341
|
// Convert response to Response object
|
373
342
|
if (response instanceof Response) {
|
374
343
|
return response;
|
@@ -425,49 +394,12 @@ export default class BXO {
|
|
425
394
|
}
|
426
395
|
|
427
396
|
// Hot reload functionality
|
428
|
-
enableHotReload(watchPaths: string[] = ['./']
|
397
|
+
enableHotReload(watchPaths: string[] = ['./']): this {
|
429
398
|
this.hotReloadEnabled = true;
|
430
399
|
watchPaths.forEach(path => this.watchedFiles.add(path));
|
431
|
-
excludePatterns.forEach(pattern => this.watchedExclude.add(pattern));
|
432
400
|
return this;
|
433
401
|
}
|
434
402
|
|
435
|
-
private shouldExcludeFile(filename: string): boolean {
|
436
|
-
for (const pattern of this.watchedExclude) {
|
437
|
-
// Handle exact match
|
438
|
-
if (pattern === filename) {
|
439
|
-
return true;
|
440
|
-
}
|
441
|
-
|
442
|
-
// Handle directory patterns (e.g., "node_modules/", "dist/")
|
443
|
-
if (pattern.endsWith('/')) {
|
444
|
-
if (filename.startsWith(pattern) || filename.includes(`/${pattern}`)) {
|
445
|
-
return true;
|
446
|
-
}
|
447
|
-
}
|
448
|
-
|
449
|
-
// Handle wildcard patterns (e.g., "*.log", "temp*")
|
450
|
-
if (pattern.includes('*')) {
|
451
|
-
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
452
|
-
if (regex.test(filename)) {
|
453
|
-
return true;
|
454
|
-
}
|
455
|
-
}
|
456
|
-
|
457
|
-
// Handle file extension patterns (e.g., ".log", ".tmp")
|
458
|
-
if (pattern.startsWith('.') && filename.endsWith(pattern)) {
|
459
|
-
return true;
|
460
|
-
}
|
461
|
-
|
462
|
-
// Handle substring matches for directories
|
463
|
-
if (filename.includes(pattern)) {
|
464
|
-
return true;
|
465
|
-
}
|
466
|
-
}
|
467
|
-
|
468
|
-
return false;
|
469
|
-
}
|
470
|
-
|
471
403
|
private async setupFileWatcher(port: number, hostname: string): Promise<void> {
|
472
404
|
if (!this.hotReloadEnabled) return;
|
473
405
|
|
@@ -477,19 +409,11 @@ export default class BXO {
|
|
477
409
|
try {
|
478
410
|
fs.watch(watchPath, { recursive: true }, async (eventType: string, filename: string) => {
|
479
411
|
if (filename && (filename.endsWith('.ts') || filename.endsWith('.js'))) {
|
480
|
-
// Check if file should be excluded
|
481
|
-
if (this.shouldExcludeFile(filename)) {
|
482
|
-
return;
|
483
|
-
}
|
484
|
-
|
485
412
|
console.log(`🔄 File changed: ${filename}, restarting server...`);
|
486
413
|
await this.restart(port, hostname);
|
487
414
|
}
|
488
415
|
});
|
489
416
|
console.log(`👀 Watching ${watchPath} for changes...`);
|
490
|
-
if (this.watchedExclude.size > 0) {
|
491
|
-
console.log(`🚫 Excluding patterns: ${Array.from(this.watchedExclude).join(', ')}`);
|
492
|
-
}
|
493
417
|
} catch (error) {
|
494
418
|
console.warn(`⚠️ Could not watch ${watchPath}:`, error);
|
495
419
|
}
|
@@ -516,8 +440,6 @@ export default class BXO {
|
|
516
440
|
});
|
517
441
|
|
518
442
|
this.isRunning = true;
|
519
|
-
this.serverPort = port;
|
520
|
-
this.serverHostname = hostname;
|
521
443
|
|
522
444
|
console.log(`🦊 BXO server running at http://${hostname}:${port}`);
|
523
445
|
|
@@ -562,8 +484,6 @@ export default class BXO {
|
|
562
484
|
}
|
563
485
|
|
564
486
|
this.isRunning = false;
|
565
|
-
this.serverPort = undefined;
|
566
|
-
this.serverHostname = undefined;
|
567
487
|
|
568
488
|
console.log('🛑 BXO server stopped');
|
569
489
|
|
@@ -615,55 +535,13 @@ export default class BXO {
|
|
615
535
|
return this.isRunning;
|
616
536
|
}
|
617
537
|
|
618
|
-
getServerInfo(): { running: boolean; hotReload: boolean; watchedFiles: string[]
|
619
|
-
return {
|
620
|
-
running: this.isRunning,
|
621
|
-
hotReload: this.hotReloadEnabled,
|
622
|
-
watchedFiles: Array.from(this.watchedFiles),
|
623
|
-
excludePatterns: Array.from(this.watchedExclude)
|
624
|
-
};
|
625
|
-
}
|
626
|
-
|
627
|
-
// Get server information (alias for getServerInfo)
|
628
|
-
get info() {
|
538
|
+
getServerInfo(): { running: boolean; hotReload: boolean; watchedFiles: string[] } {
|
629
539
|
return {
|
630
|
-
// Server status
|
631
540
|
running: this.isRunning,
|
632
|
-
server: this.server ? 'Bun' : null,
|
633
|
-
|
634
|
-
// Connection details
|
635
|
-
hostname: this.serverHostname,
|
636
|
-
port: this.serverPort,
|
637
|
-
url: this.isRunning && this.serverHostname && this.serverPort
|
638
|
-
? `http://${this.serverHostname}:${this.serverPort}`
|
639
|
-
: null,
|
640
|
-
|
641
|
-
// Application statistics
|
642
|
-
totalRoutes: this._routes.length,
|
643
|
-
totalPlugins: this.plugins.length,
|
644
|
-
|
645
|
-
// Hot reload configuration
|
646
541
|
hotReload: this.hotReloadEnabled,
|
647
|
-
watchedFiles: Array.from(this.watchedFiles)
|
648
|
-
excludePatterns: Array.from(this.watchedExclude),
|
649
|
-
|
650
|
-
// System information
|
651
|
-
runtime: 'Bun',
|
652
|
-
version: typeof Bun !== 'undefined' ? Bun.version : 'unknown',
|
653
|
-
pid: process.pid,
|
654
|
-
uptime: this.isRunning ? process.uptime() : 0
|
542
|
+
watchedFiles: Array.from(this.watchedFiles)
|
655
543
|
};
|
656
544
|
}
|
657
|
-
|
658
|
-
// Get all routes information
|
659
|
-
get routes() {
|
660
|
-
return this._routes.map((route: Route) => ({
|
661
|
-
method: route.method,
|
662
|
-
path: route.path,
|
663
|
-
hasConfig: !!route.config,
|
664
|
-
config: route.config || null
|
665
|
-
}));
|
666
|
-
}
|
667
545
|
}
|
668
546
|
|
669
547
|
const error = (error: Error, status: number = 500) => {
|
@@ -674,4 +552,4 @@ const error = (error: Error, status: number = 500) => {
|
|
674
552
|
export { z, error };
|
675
553
|
|
676
554
|
// Export types for external use
|
677
|
-
export type { RouteConfig,
|
555
|
+
export type { RouteConfig, Handler };
|