bxo 0.0.5-dev.6 → 0.0.5-dev.60
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/README.md +99 -2
- package/index.ts +4 -787
- package/package.json +11 -5
- package/plugins/README.md +160 -0
- package/plugins/cors.ts +81 -57
- package/plugins/index.ts +4 -6
- package/plugins/ratelimit.ts +55 -59
- package/src/core/bxo.ts +438 -0
- package/src/handlers/request-handler.ts +229 -0
- package/src/index.ts +59 -0
- package/src/types/index.ts +170 -0
- package/src/utils/context-factory.ts +158 -0
- package/src/utils/helpers.ts +40 -0
- package/src/utils/index.ts +390 -0
- package/src/utils/response-handler.ts +286 -0
- package/src/utils/route-matcher.ts +191 -0
- package/tests/README.md +359 -0
- package/tests/integration/bxo.test.ts +598 -0
- package/tests/run-tests.ts +44 -0
- package/tests/unit/context-factory.test.ts +386 -0
- package/tests/unit/helpers.test.ts +253 -0
- package/tests/unit/response-handler.test.ts +301 -0
- package/tests/unit/route-matcher.test.ts +181 -0
- package/tests/unit/utils.test.ts +431 -0
- package/example.ts +0 -183
- package/plugins/auth.ts +0 -119
- package/plugins/logger.ts +0 -109
package/src/core/bxo.ts
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Route,
|
|
3
|
+
WSRoute,
|
|
4
|
+
Handler,
|
|
5
|
+
WebSocketHandler,
|
|
6
|
+
RouteConfig,
|
|
7
|
+
LifecycleHooks,
|
|
8
|
+
Plugin,
|
|
9
|
+
BXOOptions
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { RequestHandler } from '../handlers/request-handler';
|
|
12
|
+
|
|
13
|
+
export default class BXO {
|
|
14
|
+
private _routes: Route[] = [];
|
|
15
|
+
private _wsRoutes: WSRoute[] = [];
|
|
16
|
+
private plugins: BXO[] = [];
|
|
17
|
+
private middleware: Plugin[] = [];
|
|
18
|
+
private hooks: LifecycleHooks = {};
|
|
19
|
+
private server?: any;
|
|
20
|
+
private isRunning: boolean = false;
|
|
21
|
+
private serverPort?: number;
|
|
22
|
+
private serverHostname?: string;
|
|
23
|
+
private enableValidation: boolean = true;
|
|
24
|
+
private requestHandler: RequestHandler;
|
|
25
|
+
|
|
26
|
+
constructor(options?: BXOOptions) {
|
|
27
|
+
this.enableValidation = options?.enableValidation ?? true;
|
|
28
|
+
this.requestHandler = new RequestHandler(
|
|
29
|
+
this._routes,
|
|
30
|
+
this._wsRoutes,
|
|
31
|
+
this.plugins,
|
|
32
|
+
this.middleware,
|
|
33
|
+
this.hooks,
|
|
34
|
+
this.enableValidation
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Lifecycle hook methods
|
|
39
|
+
onBeforeStart(handler: (instance: BXO) => Promise<void> | void): this {
|
|
40
|
+
this.hooks.onBeforeStart = handler;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onAfterStart(handler: (instance: BXO) => Promise<void> | void): this {
|
|
45
|
+
this.hooks.onAfterStart = handler;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onBeforeStop(handler: (instance: BXO) => Promise<void> | void): this {
|
|
50
|
+
this.hooks.onBeforeStop = handler;
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
onAfterStop(handler: (instance: BXO) => Promise<void> | void): this {
|
|
55
|
+
this.hooks.onAfterStop = handler;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onRequest(handler: (ctx: any, instance: BXO) => Promise<void> | void): this {
|
|
60
|
+
this.hooks.onRequest = handler;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onResponse(handler: (ctx: any, response: any, instance: BXO) => Promise<any> | any): this {
|
|
65
|
+
this.hooks.onResponse = handler;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onError(handler: (ctx: any, error: Error, instance: BXO) => Promise<any> | any): this {
|
|
70
|
+
this.hooks.onError = handler;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Plugin system - now accepts both BXO instances and middleware plugins
|
|
75
|
+
use(plugin: BXO | Plugin): this {
|
|
76
|
+
if ('_routes' in plugin) {
|
|
77
|
+
// It's a BXO instance
|
|
78
|
+
this.plugins.push(plugin);
|
|
79
|
+
} else {
|
|
80
|
+
// It's a middleware plugin
|
|
81
|
+
this.middleware.push(plugin);
|
|
82
|
+
}
|
|
83
|
+
this.updateRequestHandler();
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// HTTP method handlers with overloads for type safety
|
|
88
|
+
get<TConfig extends RouteConfig = {}>(
|
|
89
|
+
path: string,
|
|
90
|
+
handler: Handler<TConfig>
|
|
91
|
+
): this;
|
|
92
|
+
get<TConfig extends RouteConfig = {}>(
|
|
93
|
+
path: string,
|
|
94
|
+
handler: Handler<TConfig>,
|
|
95
|
+
config: TConfig
|
|
96
|
+
): this;
|
|
97
|
+
get<TConfig extends RouteConfig = {}>(
|
|
98
|
+
path: string,
|
|
99
|
+
handler: Handler<TConfig>,
|
|
100
|
+
config?: TConfig
|
|
101
|
+
): this {
|
|
102
|
+
this._routes.push({ method: 'GET', path, handler, config });
|
|
103
|
+
this.updateRequestHandler();
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
post<TConfig extends RouteConfig = {}>(
|
|
108
|
+
path: string,
|
|
109
|
+
handler: Handler<TConfig>
|
|
110
|
+
): this;
|
|
111
|
+
post<TConfig extends RouteConfig = {}>(
|
|
112
|
+
path: string,
|
|
113
|
+
handler: Handler<TConfig>,
|
|
114
|
+
config: TConfig
|
|
115
|
+
): this;
|
|
116
|
+
post<TConfig extends RouteConfig = {}>(
|
|
117
|
+
path: string,
|
|
118
|
+
handler: Handler<TConfig>,
|
|
119
|
+
config?: TConfig
|
|
120
|
+
): this {
|
|
121
|
+
this._routes.push({ method: 'POST', path, handler, config });
|
|
122
|
+
this.updateRequestHandler();
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
put<TConfig extends RouteConfig = {}>(
|
|
127
|
+
path: string,
|
|
128
|
+
handler: Handler<TConfig>
|
|
129
|
+
): this;
|
|
130
|
+
put<TConfig extends RouteConfig = {}>(
|
|
131
|
+
path: string,
|
|
132
|
+
handler: Handler<TConfig>,
|
|
133
|
+
config: TConfig
|
|
134
|
+
): this;
|
|
135
|
+
put<TConfig extends RouteConfig = {}>(
|
|
136
|
+
path: string,
|
|
137
|
+
handler: Handler<TConfig>,
|
|
138
|
+
config?: TConfig
|
|
139
|
+
): this {
|
|
140
|
+
this._routes.push({ method: 'PUT', path, handler, config });
|
|
141
|
+
this.updateRequestHandler();
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
delete<TConfig extends RouteConfig = {}>(
|
|
146
|
+
path: string,
|
|
147
|
+
handler: Handler<TConfig>
|
|
148
|
+
): this;
|
|
149
|
+
delete<TConfig extends RouteConfig = {}>(
|
|
150
|
+
path: string,
|
|
151
|
+
handler: Handler<TConfig>,
|
|
152
|
+
config: TConfig
|
|
153
|
+
): this;
|
|
154
|
+
delete<TConfig extends RouteConfig = {}>(
|
|
155
|
+
path: string,
|
|
156
|
+
handler: Handler<TConfig>,
|
|
157
|
+
config?: TConfig
|
|
158
|
+
): this {
|
|
159
|
+
this._routes.push({ method: 'DELETE', path, handler, config });
|
|
160
|
+
this.updateRequestHandler();
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
patch<TConfig extends RouteConfig = {}>(
|
|
165
|
+
path: string,
|
|
166
|
+
handler: Handler<TConfig>
|
|
167
|
+
): this;
|
|
168
|
+
patch<TConfig extends RouteConfig = {}>(
|
|
169
|
+
path: string,
|
|
170
|
+
handler: Handler<TConfig>,
|
|
171
|
+
config: TConfig
|
|
172
|
+
): this;
|
|
173
|
+
patch<TConfig extends RouteConfig = {}>(
|
|
174
|
+
path: string,
|
|
175
|
+
handler: Handler<TConfig>,
|
|
176
|
+
config?: TConfig
|
|
177
|
+
): this {
|
|
178
|
+
this._routes.push({ method: 'PATCH', path, handler, config });
|
|
179
|
+
this.updateRequestHandler();
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// WebSocket route handler
|
|
184
|
+
ws(path: string, handler: WebSocketHandler): this {
|
|
185
|
+
this._wsRoutes.push({ path, handler });
|
|
186
|
+
this.updateRequestHandler();
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Update the request handler with current routes and configuration
|
|
191
|
+
private updateRequestHandler(): void {
|
|
192
|
+
this.requestHandler = new RequestHandler(
|
|
193
|
+
this.getAllRoutes(),
|
|
194
|
+
this.getAllWSRoutes(),
|
|
195
|
+
this.plugins,
|
|
196
|
+
this.middleware,
|
|
197
|
+
this.hooks,
|
|
198
|
+
this.enableValidation
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Helper methods to get all routes including plugin routes
|
|
203
|
+
private getAllRoutes(): Route[] {
|
|
204
|
+
const allRoutes = [...this._routes];
|
|
205
|
+
for (const plugin of this.plugins) {
|
|
206
|
+
allRoutes.push(...plugin._routes);
|
|
207
|
+
}
|
|
208
|
+
return allRoutes;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private getAllWSRoutes(): WSRoute[] {
|
|
212
|
+
const allWSRoutes = [...this._wsRoutes];
|
|
213
|
+
for (const plugin of this.plugins) {
|
|
214
|
+
allWSRoutes.push(...plugin._wsRoutes);
|
|
215
|
+
}
|
|
216
|
+
return allWSRoutes;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Server management methods
|
|
220
|
+
async start(port: number = 3000, hostname: string = 'localhost'): Promise<void> {
|
|
221
|
+
if (this.isRunning) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// Before start hook
|
|
227
|
+
if (this.hooks.onBeforeStart) {
|
|
228
|
+
await this.hooks.onBeforeStart(this);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this.server = Bun.serve({
|
|
232
|
+
port,
|
|
233
|
+
hostname,
|
|
234
|
+
fetch: (request, server) => this.requestHandler.handleRequest(request, server),
|
|
235
|
+
websocket: {
|
|
236
|
+
message: (ws: any, message: any) => {
|
|
237
|
+
const handler = ws.data?.handler;
|
|
238
|
+
if (handler?.onMessage) {
|
|
239
|
+
handler.onMessage(ws, message);
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
open: (ws: any) => {
|
|
243
|
+
const handler = ws.data?.handler;
|
|
244
|
+
if (handler?.onOpen) {
|
|
245
|
+
handler.onOpen(ws);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
close: (ws: any, code?: number, reason?: string) => {
|
|
249
|
+
const handler = ws.data?.handler;
|
|
250
|
+
if (handler?.onClose) {
|
|
251
|
+
handler.onClose(ws, code, reason);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Verify server was created successfully
|
|
258
|
+
if (!this.server) {
|
|
259
|
+
throw new Error('Failed to create server instance');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.isRunning = true;
|
|
263
|
+
this.serverPort = port;
|
|
264
|
+
this.serverHostname = hostname;
|
|
265
|
+
|
|
266
|
+
// After start hook
|
|
267
|
+
if (this.hooks.onAfterStart) {
|
|
268
|
+
await this.hooks.onAfterStart(this);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Handle graceful shutdown
|
|
272
|
+
const shutdownHandler = async () => {
|
|
273
|
+
await this.stop();
|
|
274
|
+
process.exit(0);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
process.on('SIGINT', shutdownHandler);
|
|
278
|
+
process.on('SIGTERM', shutdownHandler);
|
|
279
|
+
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.error('❌ Failed to start server:', error);
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async stop(): Promise<void> {
|
|
287
|
+
if (!this.isRunning) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
// Before stop hook
|
|
293
|
+
if (this.hooks.onBeforeStop) {
|
|
294
|
+
await this.hooks.onBeforeStop(this);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (this.server) {
|
|
298
|
+
try {
|
|
299
|
+
// Try to stop the server gracefully
|
|
300
|
+
if (typeof this.server.stop === 'function') {
|
|
301
|
+
this.server.stop();
|
|
302
|
+
} else {
|
|
303
|
+
console.warn('⚠️ Server stop method not available');
|
|
304
|
+
}
|
|
305
|
+
} catch (stopError) {
|
|
306
|
+
console.error('❌ Error calling server.stop():', stopError);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Clear the server reference
|
|
310
|
+
this.server = undefined;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Reset state regardless of server.stop() success
|
|
314
|
+
this.isRunning = false;
|
|
315
|
+
this.serverPort = undefined;
|
|
316
|
+
this.serverHostname = undefined;
|
|
317
|
+
|
|
318
|
+
// After stop hook
|
|
319
|
+
if (this.hooks.onAfterStop) {
|
|
320
|
+
await this.hooks.onAfterStop(this);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error('❌ Error stopping server:', error);
|
|
325
|
+
// Even if there's an error, reset the state
|
|
326
|
+
this.isRunning = false;
|
|
327
|
+
this.server = undefined;
|
|
328
|
+
this.serverPort = undefined;
|
|
329
|
+
this.serverHostname = undefined;
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Backward compatibility
|
|
335
|
+
async listen(port: number = 3000, hostname: string = 'localhost'): Promise<void> {
|
|
336
|
+
return this.start(port, hostname);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Server status
|
|
340
|
+
isServerRunning(): boolean {
|
|
341
|
+
return this.isRunning && this.server !== undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
getServerInfo(): { running: boolean } {
|
|
345
|
+
return {
|
|
346
|
+
running: this.isRunning
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Get server information (alias for getServerInfo)
|
|
351
|
+
get info() {
|
|
352
|
+
// Calculate total routes including plugins
|
|
353
|
+
const totalRoutes = this._routes.length + this.plugins.reduce((total, plugin) => total + plugin._routes.length, 0);
|
|
354
|
+
const totalWsRoutes = this._wsRoutes.length + this.plugins.reduce((total, plugin) => total + plugin._wsRoutes.length, 0);
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
// Server status
|
|
358
|
+
running: this.isRunning,
|
|
359
|
+
server: this.server ? 'Bun' : null,
|
|
360
|
+
|
|
361
|
+
// Connection details
|
|
362
|
+
hostname: this.serverHostname,
|
|
363
|
+
port: this.serverPort,
|
|
364
|
+
url: this.isRunning && this.serverHostname && this.serverPort
|
|
365
|
+
? `http://${this.serverHostname}:${this.serverPort}`
|
|
366
|
+
: null,
|
|
367
|
+
|
|
368
|
+
// Application statistics
|
|
369
|
+
totalRoutes,
|
|
370
|
+
totalWsRoutes,
|
|
371
|
+
totalPlugins: this.plugins.length,
|
|
372
|
+
|
|
373
|
+
// System information
|
|
374
|
+
runtime: 'Bun',
|
|
375
|
+
version: typeof Bun !== 'undefined' ? Bun.version : 'unknown',
|
|
376
|
+
pid: process.pid,
|
|
377
|
+
uptime: this.isRunning ? process.uptime() : 0
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Get all routes information
|
|
382
|
+
get routes() {
|
|
383
|
+
// Get routes from main instance
|
|
384
|
+
const mainRoutes = this._routes.map((route: Route) => ({
|
|
385
|
+
method: route.method,
|
|
386
|
+
path: route.path,
|
|
387
|
+
hasConfig: !!route.config,
|
|
388
|
+
config: route.config || null,
|
|
389
|
+
source: 'main' as const
|
|
390
|
+
}));
|
|
391
|
+
|
|
392
|
+
// Get routes from all plugins
|
|
393
|
+
const pluginRoutes = this.plugins.flatMap((plugin, pluginIndex) =>
|
|
394
|
+
plugin._routes.map((route: Route) => ({
|
|
395
|
+
method: route.method,
|
|
396
|
+
path: route.path,
|
|
397
|
+
hasConfig: !!route.config,
|
|
398
|
+
config: route.config || null,
|
|
399
|
+
source: 'plugin' as const,
|
|
400
|
+
pluginIndex
|
|
401
|
+
}))
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
return [...mainRoutes, ...pluginRoutes];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Get all WebSocket routes information
|
|
408
|
+
get wsRoutes() {
|
|
409
|
+
// Get WebSocket routes from main instance
|
|
410
|
+
const mainWsRoutes = this._wsRoutes.map((route: WSRoute) => ({
|
|
411
|
+
path: route.path,
|
|
412
|
+
hasHandlers: {
|
|
413
|
+
onOpen: !!route.handler.onOpen,
|
|
414
|
+
onMessage: !!route.handler.onMessage,
|
|
415
|
+
onClose: !!route.handler.onClose,
|
|
416
|
+
onError: !!route.handler.onError
|
|
417
|
+
},
|
|
418
|
+
source: 'main' as const
|
|
419
|
+
}));
|
|
420
|
+
|
|
421
|
+
// Get WebSocket routes from all plugins
|
|
422
|
+
const pluginWsRoutes = this.plugins.flatMap((plugin, pluginIndex) =>
|
|
423
|
+
plugin._wsRoutes.map((route: WSRoute) => ({
|
|
424
|
+
path: route.path,
|
|
425
|
+
hasHandlers: {
|
|
426
|
+
onOpen: !!route.handler.onOpen,
|
|
427
|
+
onMessage: !!route.handler.onMessage,
|
|
428
|
+
onClose: !!route.handler.onClose,
|
|
429
|
+
onError: !!route.handler.onError
|
|
430
|
+
},
|
|
431
|
+
source: 'plugin' as const,
|
|
432
|
+
pluginIndex
|
|
433
|
+
}))
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
return [...mainWsRoutes, ...pluginWsRoutes];
|
|
437
|
+
}
|
|
438
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { Context, Route, WSRoute, Plugin, LifecycleHooks } from '../types';
|
|
2
|
+
import { parseQuery, parseHeaders, parseCookies, parseRequestBody } from '../utils';
|
|
3
|
+
import { matchRoute, matchWSRoute } from '../utils/route-matcher';
|
|
4
|
+
import { createContext, createOptionsContext, getInternalCookies } from '../utils/context-factory';
|
|
5
|
+
import { processResponse, createValidationErrorResponse, createErrorResponse } from '../utils/response-handler';
|
|
6
|
+
|
|
7
|
+
export class RequestHandler {
|
|
8
|
+
constructor(
|
|
9
|
+
private routes: Route[],
|
|
10
|
+
private wsRoutes: WSRoute[],
|
|
11
|
+
private plugins: any[],
|
|
12
|
+
private middleware: Plugin[],
|
|
13
|
+
private hooks: LifecycleHooks,
|
|
14
|
+
private enableValidation: boolean
|
|
15
|
+
) { }
|
|
16
|
+
|
|
17
|
+
async handleRequest(request: Request, server?: any): Promise<Response | undefined> {
|
|
18
|
+
const url = new URL(request.url);
|
|
19
|
+
const method = request.method;
|
|
20
|
+
const rawPathname = url.pathname;
|
|
21
|
+
let pathname: string;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
pathname = decodeURI(rawPathname);
|
|
25
|
+
} catch {
|
|
26
|
+
pathname = rawPathname;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check for WebSocket upgrade
|
|
30
|
+
if (request.headers.get('upgrade') === 'websocket') {
|
|
31
|
+
return this.handleWebSocketUpgrade(request, pathname, server);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle OPTIONS requests for CORS preflight before route matching
|
|
35
|
+
if (method === 'OPTIONS') {
|
|
36
|
+
return this.handleOptionsRequest(request, pathname);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Find matching route
|
|
40
|
+
const matchResult = matchRoute(method, pathname, this.routes);
|
|
41
|
+
if (!matchResult) {
|
|
42
|
+
return new Response('Not Found', { status: 404 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { route, params } = matchResult;
|
|
46
|
+
let ctx: Context | null = null
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Parse request data
|
|
50
|
+
const query = parseQuery(url.searchParams);
|
|
51
|
+
const headers = parseHeaders(request.headers);
|
|
52
|
+
const cookies = parseCookies(request.headers.get('cookie'));
|
|
53
|
+
const body = await parseRequestBody(request);
|
|
54
|
+
|
|
55
|
+
// Create context
|
|
56
|
+
ctx = createContext(
|
|
57
|
+
params,
|
|
58
|
+
query,
|
|
59
|
+
body,
|
|
60
|
+
headers,
|
|
61
|
+
cookies,
|
|
62
|
+
pathname,
|
|
63
|
+
request,
|
|
64
|
+
route.config,
|
|
65
|
+
this.enableValidation
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Run middleware and hooks
|
|
69
|
+
await this.runRequestHooks(ctx);
|
|
70
|
+
|
|
71
|
+
// Execute route handler
|
|
72
|
+
let response = await route.handler(ctx);
|
|
73
|
+
|
|
74
|
+
// Run response hooks
|
|
75
|
+
response = await this.runResponseHooks(ctx, response);
|
|
76
|
+
|
|
77
|
+
// Process and return response
|
|
78
|
+
const internalCookies = getInternalCookies(ctx);
|
|
79
|
+
return processResponse(response, ctx, internalCookies, this.enableValidation, route.config);
|
|
80
|
+
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Run error hooks
|
|
83
|
+
const errorResponse = await this.runErrorHooks(ctx as Context, error as Error);
|
|
84
|
+
if (errorResponse) {
|
|
85
|
+
return errorResponse;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Default error response
|
|
89
|
+
if (error instanceof Error && ('errors' in error || 'issues' in error)) {
|
|
90
|
+
return createValidationErrorResponse(error, 400);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return createErrorResponse(error as Error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private handleWebSocketUpgrade(request: Request, pathname: string, server?: any): Response | undefined {
|
|
98
|
+
const wsMatchResult = matchWSRoute(pathname, this.wsRoutes);
|
|
99
|
+
if (wsMatchResult && server) {
|
|
100
|
+
const success = server.upgrade(request, {
|
|
101
|
+
data: {
|
|
102
|
+
handler: wsMatchResult.route.handler,
|
|
103
|
+
params: wsMatchResult.params,
|
|
104
|
+
pathname
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (success) {
|
|
109
|
+
return; // undefined response means upgrade was successful
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return new Response('WebSocket upgrade failed', { status: 400 });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async handleOptionsRequest(request: Request, pathname: string): Promise<Response> {
|
|
116
|
+
const headers = parseHeaders(request.headers);
|
|
117
|
+
const ctx = createOptionsContext(pathname, request, headers);
|
|
118
|
+
|
|
119
|
+
// Run middleware onRequest hooks for OPTIONS requests
|
|
120
|
+
for (const plugin of this.middleware) {
|
|
121
|
+
if (plugin.onRequest) {
|
|
122
|
+
const result = await plugin.onRequest(ctx);
|
|
123
|
+
if (result instanceof Response) {
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Run global onRequest hook
|
|
130
|
+
if (this.hooks.onRequest) {
|
|
131
|
+
const result = await this.hooks.onRequest(ctx, this) as any
|
|
132
|
+
if (result instanceof Response) {
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Run BXO instance onRequest hooks
|
|
138
|
+
for (const bxoInstance of this.plugins) {
|
|
139
|
+
if (bxoInstance.hooks?.onRequest) {
|
|
140
|
+
const result = await bxoInstance.hooks.onRequest(ctx, this);
|
|
141
|
+
if (result instanceof Response) {
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// If no middleware handled the OPTIONS request, return a default response
|
|
148
|
+
return new Response(null, {
|
|
149
|
+
status: 204,
|
|
150
|
+
headers: ctx.set.headers || {}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private async runRequestHooks(ctx: Context): Promise<void> {
|
|
155
|
+
// Run middleware onRequest hooks
|
|
156
|
+
for (const plugin of this.middleware) {
|
|
157
|
+
if (plugin.onRequest) {
|
|
158
|
+
await plugin.onRequest(ctx);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Run global onRequest hook
|
|
163
|
+
if (this.hooks.onRequest) {
|
|
164
|
+
await this.hooks.onRequest(ctx, this);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Run BXO instance onRequest hooks
|
|
168
|
+
for (const bxoInstance of this.plugins) {
|
|
169
|
+
if (bxoInstance.hooks?.onRequest) {
|
|
170
|
+
await bxoInstance.hooks.onRequest(ctx, this);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private async runResponseHooks(ctx: Context, response: any): Promise<any> {
|
|
176
|
+
// Run middleware onResponse hooks
|
|
177
|
+
for (const plugin of this.middleware) {
|
|
178
|
+
if (plugin.onResponse) {
|
|
179
|
+
response = await plugin.onResponse(ctx, response) || response;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Run global onResponse hook
|
|
184
|
+
if (this.hooks.onResponse) {
|
|
185
|
+
response = await this.hooks.onResponse(ctx, response, this) || response;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Run BXO instance onResponse hooks
|
|
189
|
+
for (const bxoInstance of this.plugins) {
|
|
190
|
+
if (bxoInstance.hooks?.onResponse) {
|
|
191
|
+
response = await bxoInstance.hooks.onResponse(ctx, response, this) || response;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return response;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async runErrorHooks(ctx: Context, error: Error): Promise<Response | null> {
|
|
199
|
+
let errorResponse: any = null;
|
|
200
|
+
|
|
201
|
+
// Run middleware onError hooks
|
|
202
|
+
for (const plugin of this.middleware) {
|
|
203
|
+
if (plugin.onError) {
|
|
204
|
+
errorResponse = await plugin.onError(ctx, error) || errorResponse;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Run global onError hook
|
|
209
|
+
if (this.hooks.onError) {
|
|
210
|
+
errorResponse = await this.hooks.onError(ctx, error, this);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Run BXO instance onError hooks
|
|
214
|
+
for (const bxoInstance of this.plugins) {
|
|
215
|
+
if (bxoInstance.hooks?.onError) {
|
|
216
|
+
errorResponse = await bxoInstance.hooks.onError(ctx, error, this) || errorResponse;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (errorResponse) {
|
|
221
|
+
if (errorResponse instanceof Response) {
|
|
222
|
+
return errorResponse;
|
|
223
|
+
}
|
|
224
|
+
return createErrorResponse(errorResponse, 500);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|