bxo 0.0.4 → 0.0.5-dev.2
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 +99 -11
- package/package.json +1 -1
package/index.ts
CHANGED
@@ -9,6 +9,7 @@ interface RouteConfig {
|
|
9
9
|
query?: z.ZodSchema<any>;
|
10
10
|
body?: z.ZodSchema<any>;
|
11
11
|
headers?: z.ZodSchema<any>;
|
12
|
+
response?: z.ZodSchema<any>;
|
12
13
|
}
|
13
14
|
|
14
15
|
// Context type that's fully typed based on the route configuration
|
@@ -52,13 +53,14 @@ interface LifecycleHooks {
|
|
52
53
|
}
|
53
54
|
|
54
55
|
export default class BXO {
|
55
|
-
private
|
56
|
+
private _routes: Route[] = [];
|
56
57
|
private plugins: BXO[] = [];
|
57
58
|
private hooks: LifecycleHooks = {};
|
58
59
|
private server?: any;
|
59
60
|
private isRunning: boolean = false;
|
60
61
|
private hotReloadEnabled: boolean = false;
|
61
62
|
private watchedFiles: Set<string> = new Set();
|
63
|
+
private watchedExclude: Set<string> = new Set();
|
62
64
|
|
63
65
|
constructor() { }
|
64
66
|
|
@@ -129,7 +131,7 @@ export default class BXO {
|
|
129
131
|
handler: Handler<TConfig>,
|
130
132
|
config?: TConfig
|
131
133
|
): this {
|
132
|
-
this.
|
134
|
+
this._routes.push({ method: 'GET', path, handler, config });
|
133
135
|
return this;
|
134
136
|
}
|
135
137
|
|
@@ -147,7 +149,7 @@ export default class BXO {
|
|
147
149
|
handler: Handler<TConfig>,
|
148
150
|
config?: TConfig
|
149
151
|
): this {
|
150
|
-
this.
|
152
|
+
this._routes.push({ method: 'POST', path, handler, config });
|
151
153
|
return this;
|
152
154
|
}
|
153
155
|
|
@@ -165,7 +167,7 @@ export default class BXO {
|
|
165
167
|
handler: Handler<TConfig>,
|
166
168
|
config?: TConfig
|
167
169
|
): this {
|
168
|
-
this.
|
170
|
+
this._routes.push({ method: 'PUT', path, handler, config });
|
169
171
|
return this;
|
170
172
|
}
|
171
173
|
|
@@ -183,7 +185,7 @@ export default class BXO {
|
|
183
185
|
handler: Handler<TConfig>,
|
184
186
|
config?: TConfig
|
185
187
|
): this {
|
186
|
-
this.
|
188
|
+
this._routes.push({ method: 'DELETE', path, handler, config });
|
187
189
|
return this;
|
188
190
|
}
|
189
191
|
|
@@ -201,13 +203,13 @@ export default class BXO {
|
|
201
203
|
handler: Handler<TConfig>,
|
202
204
|
config?: TConfig
|
203
205
|
): this {
|
204
|
-
this.
|
206
|
+
this._routes.push({ method: 'PATCH', path, handler, config });
|
205
207
|
return this;
|
206
208
|
}
|
207
209
|
|
208
210
|
// Route matching utility
|
209
211
|
private matchRoute(method: string, pathname: string): { route: Route; params: Record<string, string> } | null {
|
210
|
-
for (const route of this.
|
212
|
+
for (const route of this._routes) {
|
211
213
|
if (route.method !== method) continue;
|
212
214
|
|
213
215
|
const routeSegments = route.path.split('/').filter(Boolean);
|
@@ -338,6 +340,20 @@ export default class BXO {
|
|
338
340
|
}
|
339
341
|
}
|
340
342
|
|
343
|
+
// Validate response against schema if provided
|
344
|
+
if (route.config?.response && !(response instanceof Response)) {
|
345
|
+
try {
|
346
|
+
response = this.validateData(route.config.response, response);
|
347
|
+
} catch (validationError) {
|
348
|
+
// Response validation failed
|
349
|
+
const errorMessage = validationError instanceof Error ? validationError.message : 'Response validation failed';
|
350
|
+
return new Response(JSON.stringify({ error: `Response validation error: ${errorMessage}` }), {
|
351
|
+
status: 500,
|
352
|
+
headers: { 'Content-Type': 'application/json' }
|
353
|
+
});
|
354
|
+
}
|
355
|
+
}
|
356
|
+
|
341
357
|
// Convert response to Response object
|
342
358
|
if (response instanceof Response) {
|
343
359
|
return response;
|
@@ -394,12 +410,49 @@ export default class BXO {
|
|
394
410
|
}
|
395
411
|
|
396
412
|
// Hot reload functionality
|
397
|
-
enableHotReload(watchPaths: string[] = ['./']): this {
|
413
|
+
enableHotReload(watchPaths: string[] = ['./'], excludePatterns: string[] = []): this {
|
398
414
|
this.hotReloadEnabled = true;
|
399
415
|
watchPaths.forEach(path => this.watchedFiles.add(path));
|
416
|
+
excludePatterns.forEach(pattern => this.watchedExclude.add(pattern));
|
400
417
|
return this;
|
401
418
|
}
|
402
419
|
|
420
|
+
private shouldExcludeFile(filename: string): boolean {
|
421
|
+
for (const pattern of this.watchedExclude) {
|
422
|
+
// Handle exact match
|
423
|
+
if (pattern === filename) {
|
424
|
+
return true;
|
425
|
+
}
|
426
|
+
|
427
|
+
// Handle directory patterns (e.g., "node_modules/", "dist/")
|
428
|
+
if (pattern.endsWith('/')) {
|
429
|
+
if (filename.startsWith(pattern) || filename.includes(`/${pattern}`)) {
|
430
|
+
return true;
|
431
|
+
}
|
432
|
+
}
|
433
|
+
|
434
|
+
// Handle wildcard patterns (e.g., "*.log", "temp*")
|
435
|
+
if (pattern.includes('*')) {
|
436
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
437
|
+
if (regex.test(filename)) {
|
438
|
+
return true;
|
439
|
+
}
|
440
|
+
}
|
441
|
+
|
442
|
+
// Handle file extension patterns (e.g., ".log", ".tmp")
|
443
|
+
if (pattern.startsWith('.') && filename.endsWith(pattern)) {
|
444
|
+
return true;
|
445
|
+
}
|
446
|
+
|
447
|
+
// Handle substring matches for directories
|
448
|
+
if (filename.includes(pattern)) {
|
449
|
+
return true;
|
450
|
+
}
|
451
|
+
}
|
452
|
+
|
453
|
+
return false;
|
454
|
+
}
|
455
|
+
|
403
456
|
private async setupFileWatcher(port: number, hostname: string): Promise<void> {
|
404
457
|
if (!this.hotReloadEnabled) return;
|
405
458
|
|
@@ -409,11 +462,19 @@ export default class BXO {
|
|
409
462
|
try {
|
410
463
|
fs.watch(watchPath, { recursive: true }, async (eventType: string, filename: string) => {
|
411
464
|
if (filename && (filename.endsWith('.ts') || filename.endsWith('.js'))) {
|
465
|
+
// Check if file should be excluded
|
466
|
+
if (this.shouldExcludeFile(filename)) {
|
467
|
+
return;
|
468
|
+
}
|
469
|
+
|
412
470
|
console.log(`🔄 File changed: ${filename}, restarting server...`);
|
413
471
|
await this.restart(port, hostname);
|
414
472
|
}
|
415
473
|
});
|
416
474
|
console.log(`👀 Watching ${watchPath} for changes...`);
|
475
|
+
if (this.watchedExclude.size > 0) {
|
476
|
+
console.log(`🚫 Excluding patterns: ${Array.from(this.watchedExclude).join(', ')}`);
|
477
|
+
}
|
417
478
|
} catch (error) {
|
418
479
|
console.warn(`⚠️ Could not watch ${watchPath}:`, error);
|
419
480
|
}
|
@@ -535,13 +596,40 @@ export default class BXO {
|
|
535
596
|
return this.isRunning;
|
536
597
|
}
|
537
598
|
|
538
|
-
getServerInfo(): { running: boolean; hotReload: boolean; watchedFiles: string[] } {
|
599
|
+
getServerInfo(): { running: boolean; hotReload: boolean; watchedFiles: string[]; excludePatterns: string[] } {
|
539
600
|
return {
|
540
601
|
running: this.isRunning,
|
541
602
|
hotReload: this.hotReloadEnabled,
|
542
|
-
watchedFiles: Array.from(this.watchedFiles)
|
603
|
+
watchedFiles: Array.from(this.watchedFiles),
|
604
|
+
excludePatterns: Array.from(this.watchedExclude)
|
605
|
+
};
|
606
|
+
}
|
607
|
+
|
608
|
+
// Get server information (alias for getServerInfo)
|
609
|
+
get info() {
|
610
|
+
return {
|
611
|
+
...this.getServerInfo(),
|
612
|
+
totalRoutes: this._routes.length,
|
613
|
+
totalPlugins: this.plugins.length,
|
614
|
+
server: this.server ? 'Bun' : null
|
543
615
|
};
|
544
616
|
}
|
617
|
+
|
618
|
+
// Get all routes information
|
619
|
+
get routes() {
|
620
|
+
return this._routes.map((route: Route) => ({
|
621
|
+
method: route.method,
|
622
|
+
path: route.path,
|
623
|
+
hasConfig: !!route.config,
|
624
|
+
config: route.config ? {
|
625
|
+
hasParams: !!route.config.params,
|
626
|
+
hasQuery: !!route.config.query,
|
627
|
+
hasBody: !!route.config.body,
|
628
|
+
hasHeaders: !!route.config.headers,
|
629
|
+
hasResponse: !!route.config.response
|
630
|
+
} : null
|
631
|
+
}));
|
632
|
+
}
|
545
633
|
}
|
546
634
|
|
547
635
|
const error = (error: Error, status: number = 500) => {
|
@@ -552,4 +640,4 @@ const error = (error: Error, status: number = 500) => {
|
|
552
640
|
export { z, error };
|
553
641
|
|
554
642
|
// Export types for external use
|
555
|
-
export type { RouteConfig };
|
643
|
+
export type { RouteConfig, Handler };
|