@dangao/bun-server 1.5.0 → 1.7.0
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/dist/core/server.d.ts.map +1 -1
- package/dist/index.js +142 -32
- package/dist/websocket/registry.d.ts +38 -4
- package/dist/websocket/registry.d.ts.map +1 -1
- package/docs/zh/guide.md +23 -18
- package/docs/zh/migration.md +8 -2
- package/package.json +1 -1
- package/src/core/server.ts +16 -7
- package/src/websocket/registry.ts +224 -30
- package/tests/websocket/gateway.test.ts +1230 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE1D;;OAEG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAE7C;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAgB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAa;gBAElB,OAAO,EAAE,aAAa;IAIzC;;OAEG;IACI,KAAK,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE1D;;OAEG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAE7C;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAgB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAa;gBAElB,OAAO,EAAE,aAAa;IAIzC;;OAEG;IACI,KAAK,IAAI,IAAI;IAoGpB;;OAEG;IACI,IAAI,IAAI,IAAI;IAWnB;;;;;OAKG;IACU,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2C9D;;OAEG;IACI,iBAAiB,IAAI,MAAM;IAIlC;;OAEG;IACI,mBAAmB,IAAI,OAAO;IAIrC;;;OAGG;IACI,SAAS,IAAI,MAAM,CAAC,uBAAuB,CAAC,GAAG,SAAS;IAI/D;;;OAGG;IACI,SAAS,IAAI,OAAO;IAI3B;;OAEG;IACI,OAAO,IAAI,MAAM;IAIxB;;OAEG;IACI,WAAW,IAAI,MAAM,GAAG,SAAS;CAGzC"}
|
package/dist/index.js
CHANGED
|
@@ -3264,8 +3264,14 @@ class BunServer {
|
|
|
3264
3264
|
if (!this.options.websocketRegistry.hasGateway(url.pathname)) {
|
|
3265
3265
|
return new Response("WebSocket gateway not found", { status: 404 });
|
|
3266
3266
|
}
|
|
3267
|
+
const context2 = new Context(request);
|
|
3268
|
+
const queryParams = new URLSearchParams(url.searchParams);
|
|
3267
3269
|
const upgraded = server.upgrade(request, {
|
|
3268
|
-
data: {
|
|
3270
|
+
data: {
|
|
3271
|
+
path: url.pathname,
|
|
3272
|
+
query: queryParams,
|
|
3273
|
+
context: context2
|
|
3274
|
+
}
|
|
3269
3275
|
});
|
|
3270
3276
|
if (upgraded) {
|
|
3271
3277
|
return;
|
|
@@ -3291,14 +3297,14 @@ class BunServer {
|
|
|
3291
3297
|
return responsePromise;
|
|
3292
3298
|
},
|
|
3293
3299
|
websocket: {
|
|
3294
|
-
open: (ws) => {
|
|
3295
|
-
this.options.websocketRegistry?.handleOpen(ws);
|
|
3300
|
+
open: async (ws) => {
|
|
3301
|
+
await this.options.websocketRegistry?.handleOpen(ws);
|
|
3296
3302
|
},
|
|
3297
|
-
message: (ws, message) => {
|
|
3298
|
-
this.options.websocketRegistry?.handleMessage(ws, message);
|
|
3303
|
+
message: async (ws, message) => {
|
|
3304
|
+
await this.options.websocketRegistry?.handleMessage(ws, message);
|
|
3299
3305
|
},
|
|
3300
|
-
close: (ws, code, reason) => {
|
|
3301
|
-
this.options.websocketRegistry?.handleClose(ws, code, reason);
|
|
3306
|
+
close: async (ws, code, reason) => {
|
|
3307
|
+
await this.options.websocketRegistry?.handleClose(ws, code, reason);
|
|
3302
3308
|
}
|
|
3303
3309
|
}
|
|
3304
3310
|
});
|
|
@@ -3403,10 +3409,14 @@ function getHandlerMetadata(target) {
|
|
|
3403
3409
|
}
|
|
3404
3410
|
|
|
3405
3411
|
// src/websocket/registry.ts
|
|
3412
|
+
init_param_binder();
|
|
3413
|
+
init_decorators2();
|
|
3406
3414
|
class WebSocketGatewayRegistry {
|
|
3407
3415
|
static instance;
|
|
3408
3416
|
container;
|
|
3409
3417
|
gateways = new Map;
|
|
3418
|
+
staticGateways = new Map;
|
|
3419
|
+
dynamicGateways = [];
|
|
3410
3420
|
constructor() {
|
|
3411
3421
|
this.container = ControllerRegistry.getInstance().getContainer();
|
|
3412
3422
|
}
|
|
@@ -3416,6 +3426,15 @@ class WebSocketGatewayRegistry {
|
|
|
3416
3426
|
}
|
|
3417
3427
|
return WebSocketGatewayRegistry.instance;
|
|
3418
3428
|
}
|
|
3429
|
+
parsePath(path) {
|
|
3430
|
+
const paramNames = [];
|
|
3431
|
+
const patternString = path.replace(/:([^/]+)/g, (_, paramName) => {
|
|
3432
|
+
paramNames.push(paramName);
|
|
3433
|
+
return "([^/]+)";
|
|
3434
|
+
}).replace(/\*/g, ".*");
|
|
3435
|
+
const pattern = new RegExp(`^${patternString}$`);
|
|
3436
|
+
return { pattern, paramNames };
|
|
3437
|
+
}
|
|
3419
3438
|
register(gatewayClass) {
|
|
3420
3439
|
const metadata = getGatewayMetadata(gatewayClass);
|
|
3421
3440
|
if (!metadata) {
|
|
@@ -3428,62 +3447,153 @@ class WebSocketGatewayRegistry {
|
|
|
3428
3447
|
if (!this.container.isRegistered(gatewayClass)) {
|
|
3429
3448
|
this.container.register(gatewayClass);
|
|
3430
3449
|
}
|
|
3431
|
-
this.
|
|
3450
|
+
const { pattern, paramNames } = this.parsePath(metadata.path);
|
|
3451
|
+
const isStatic = !metadata.path.includes(":") && !metadata.path.includes("*");
|
|
3452
|
+
const definition = {
|
|
3432
3453
|
path: metadata.path,
|
|
3433
3454
|
gatewayClass,
|
|
3434
|
-
handlers
|
|
3435
|
-
|
|
3455
|
+
handlers,
|
|
3456
|
+
pattern,
|
|
3457
|
+
paramNames,
|
|
3458
|
+
isStatic
|
|
3459
|
+
};
|
|
3460
|
+
this.gateways.set(metadata.path, definition);
|
|
3461
|
+
if (isStatic) {
|
|
3462
|
+
this.staticGateways.set(metadata.path, definition);
|
|
3463
|
+
} else {
|
|
3464
|
+
this.dynamicGateways.push(definition);
|
|
3465
|
+
}
|
|
3436
3466
|
}
|
|
3437
3467
|
hasGateway(path) {
|
|
3438
|
-
|
|
3468
|
+
if (this.staticGateways.has(path)) {
|
|
3469
|
+
return true;
|
|
3470
|
+
}
|
|
3471
|
+
for (const gateway of this.dynamicGateways) {
|
|
3472
|
+
if (gateway.pattern.test(path)) {
|
|
3473
|
+
return true;
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
return false;
|
|
3439
3477
|
}
|
|
3440
3478
|
clear() {
|
|
3441
3479
|
this.gateways.clear();
|
|
3480
|
+
this.staticGateways.clear();
|
|
3481
|
+
this.dynamicGateways.length = 0;
|
|
3442
3482
|
}
|
|
3443
3483
|
getGateway(path) {
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3484
|
+
const staticGateway = this.staticGateways.get(path);
|
|
3485
|
+
if (staticGateway) {
|
|
3486
|
+
return { definition: staticGateway, params: {} };
|
|
3487
|
+
}
|
|
3488
|
+
for (const gateway of this.dynamicGateways) {
|
|
3489
|
+
const match = path.match(gateway.pattern);
|
|
3490
|
+
if (match) {
|
|
3491
|
+
const params = {};
|
|
3492
|
+
for (let i = 0;i < gateway.paramNames.length; i++) {
|
|
3493
|
+
params[gateway.paramNames[i]] = match[i + 1] ?? "";
|
|
3494
|
+
}
|
|
3495
|
+
return { definition: gateway, params };
|
|
3496
|
+
}
|
|
3449
3497
|
}
|
|
3450
|
-
return
|
|
3498
|
+
return;
|
|
3499
|
+
}
|
|
3500
|
+
createGatewayInstance(definition) {
|
|
3501
|
+
return this.container.resolve(definition.gatewayClass);
|
|
3451
3502
|
}
|
|
3452
|
-
invokeHandler(ws, definition, handlerName, ...args) {
|
|
3503
|
+
async invokeHandler(ws, definition, handlerName, ...args) {
|
|
3453
3504
|
if (!handlerName) {
|
|
3454
3505
|
return;
|
|
3455
3506
|
}
|
|
3456
|
-
const instance = this.
|
|
3507
|
+
const instance = this.createGatewayInstance(definition);
|
|
3457
3508
|
const handler = instance[handlerName];
|
|
3458
|
-
if (typeof handler
|
|
3509
|
+
if (typeof handler !== "function") {
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
const prototype = definition.gatewayClass.prototype;
|
|
3513
|
+
const paramMetadata = getParamMetadata(prototype, handlerName);
|
|
3514
|
+
if (paramMetadata.length > 0) {
|
|
3515
|
+
let context = ws.data?.context;
|
|
3516
|
+
if (!context) {
|
|
3517
|
+
const url = new URL(ws.data.path || "/", "http://localhost");
|
|
3518
|
+
if (ws.data.query) {
|
|
3519
|
+
ws.data.query.forEach((value, key) => {
|
|
3520
|
+
url.searchParams.set(key, value);
|
|
3521
|
+
});
|
|
3522
|
+
}
|
|
3523
|
+
const request = new Request(url.toString(), {
|
|
3524
|
+
method: "GET",
|
|
3525
|
+
headers: new Headers
|
|
3526
|
+
});
|
|
3527
|
+
context = new Context(request);
|
|
3528
|
+
if (ws.data.params) {
|
|
3529
|
+
context.params = ws.data.params;
|
|
3530
|
+
}
|
|
3531
|
+
ws.data.context = context;
|
|
3532
|
+
} else {
|
|
3533
|
+
if (ws.data.params) {
|
|
3534
|
+
context.params = ws.data.params;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
const boundParams = await ParamBinder.bind(prototype, handlerName, context, this.container);
|
|
3538
|
+
const finalArgs = [];
|
|
3539
|
+
const maxParamIndex = paramMetadata.length > 0 ? Math.max(...paramMetadata.map((m) => m.index)) : -1;
|
|
3540
|
+
const totalParamCount = Math.max(maxParamIndex + 1, args.length + 1);
|
|
3541
|
+
for (let i = 0;i < totalParamCount; i++) {
|
|
3542
|
+
const meta = paramMetadata.find((m) => m.index === i);
|
|
3543
|
+
if (meta) {
|
|
3544
|
+
finalArgs[i] = boundParams[i];
|
|
3545
|
+
} else if (i === 0) {
|
|
3546
|
+
finalArgs[i] = ws;
|
|
3547
|
+
} else {
|
|
3548
|
+
const argIndex = i - 1;
|
|
3549
|
+
if (argIndex < args.length) {
|
|
3550
|
+
finalArgs[i] = args[argIndex];
|
|
3551
|
+
} else {
|
|
3552
|
+
finalArgs[i] = undefined;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
if (args.length > maxParamIndex) {
|
|
3557
|
+
for (let i = maxParamIndex + 1;i < args.length; i++) {
|
|
3558
|
+
finalArgs.push(args[i]);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
handler.apply(instance, finalArgs);
|
|
3562
|
+
} else {
|
|
3459
3563
|
handler.apply(instance, [ws, ...args]);
|
|
3460
3564
|
}
|
|
3461
3565
|
}
|
|
3462
|
-
handleOpen(ws) {
|
|
3566
|
+
async handleOpen(ws) {
|
|
3463
3567
|
const path = ws.data?.path;
|
|
3464
|
-
const
|
|
3465
|
-
if (!
|
|
3568
|
+
const match = path ? this.getGateway(path) : undefined;
|
|
3569
|
+
if (!match) {
|
|
3466
3570
|
ws.close(1008, "Gateway not found");
|
|
3467
3571
|
return;
|
|
3468
3572
|
}
|
|
3469
|
-
|
|
3573
|
+
if (match.params && Object.keys(match.params).length > 0) {
|
|
3574
|
+
ws.data.params = match.params;
|
|
3575
|
+
}
|
|
3576
|
+
await this.invokeHandler(ws, match.definition, match.definition.handlers.open);
|
|
3470
3577
|
}
|
|
3471
|
-
handleMessage(ws, message) {
|
|
3578
|
+
async handleMessage(ws, message) {
|
|
3472
3579
|
const path = ws.data?.path;
|
|
3473
|
-
const
|
|
3474
|
-
if (!
|
|
3580
|
+
const match = path ? this.getGateway(path) : undefined;
|
|
3581
|
+
if (!match) {
|
|
3475
3582
|
ws.close(1008, "Gateway not found");
|
|
3476
3583
|
return;
|
|
3477
3584
|
}
|
|
3478
|
-
|
|
3585
|
+
if (match.params && Object.keys(match.params).length > 0 && !ws.data.params) {
|
|
3586
|
+
ws.data.params = match.params;
|
|
3587
|
+
}
|
|
3588
|
+
await this.invokeHandler(ws, match.definition, match.definition.handlers.message, message);
|
|
3479
3589
|
}
|
|
3480
|
-
handleClose(ws, code, reason) {
|
|
3590
|
+
async handleClose(ws, code, reason) {
|
|
3481
3591
|
const path = ws.data?.path;
|
|
3482
|
-
const
|
|
3483
|
-
if (!
|
|
3592
|
+
const match = path ? this.getGateway(path) : undefined;
|
|
3593
|
+
if (!match) {
|
|
3484
3594
|
return;
|
|
3485
3595
|
}
|
|
3486
|
-
this.invokeHandler(ws, definition, definition.handlers.close, code, reason);
|
|
3596
|
+
await this.invokeHandler(ws, match.definition, match.definition.handlers.close, code, reason);
|
|
3487
3597
|
}
|
|
3488
3598
|
}
|
|
3489
3599
|
|
|
@@ -1,22 +1,56 @@
|
|
|
1
1
|
import type { ServerWebSocket } from 'bun';
|
|
2
|
+
import { Context } from '../core/context';
|
|
2
3
|
import type { Constructor } from '@/core/types';
|
|
3
4
|
export interface WebSocketConnectionData {
|
|
4
5
|
path: string;
|
|
6
|
+
query?: URLSearchParams;
|
|
7
|
+
context?: Context;
|
|
8
|
+
params?: Record<string, string>;
|
|
5
9
|
}
|
|
6
10
|
export declare class WebSocketGatewayRegistry {
|
|
7
11
|
private static instance;
|
|
8
12
|
private readonly container;
|
|
9
13
|
private readonly gateways;
|
|
14
|
+
private readonly staticGateways;
|
|
15
|
+
private readonly dynamicGateways;
|
|
10
16
|
private constructor();
|
|
11
17
|
static getInstance(): WebSocketGatewayRegistry;
|
|
18
|
+
/**
|
|
19
|
+
* 解析路径,生成匹配模式和参数名列表
|
|
20
|
+
* @param path - 路由路径
|
|
21
|
+
* @returns 匹配模式和参数名列表
|
|
22
|
+
*/
|
|
23
|
+
private parsePath;
|
|
12
24
|
register(gatewayClass: Constructor<unknown>): void;
|
|
25
|
+
/**
|
|
26
|
+
* 检查是否有匹配的网关(支持动态路径匹配)
|
|
27
|
+
* @param path - 请求路径
|
|
28
|
+
* @returns 是否有匹配的网关
|
|
29
|
+
*/
|
|
13
30
|
hasGateway(path: string): boolean;
|
|
14
31
|
clear(): void;
|
|
32
|
+
/**
|
|
33
|
+
* 获取匹配的网关(支持动态路径匹配)
|
|
34
|
+
* @param path - 请求路径
|
|
35
|
+
* @returns 匹配的网关定义和路径参数
|
|
36
|
+
*/
|
|
15
37
|
private getGateway;
|
|
16
|
-
|
|
38
|
+
/**
|
|
39
|
+
* 动态创建网关实例(每次连接创建新实例)
|
|
40
|
+
* @param definition - 网关定义
|
|
41
|
+
* @returns 网关实例
|
|
42
|
+
*/
|
|
43
|
+
private createGatewayInstance;
|
|
44
|
+
/**
|
|
45
|
+
* 调用处理器,支持参数绑定
|
|
46
|
+
* @param ws - WebSocket 连接
|
|
47
|
+
* @param definition - 网关定义
|
|
48
|
+
* @param handlerName - 处理器方法名
|
|
49
|
+
* @param args - 原始参数(message, code, reason 等,不包括 ws)
|
|
50
|
+
*/
|
|
17
51
|
private invokeHandler;
|
|
18
|
-
handleOpen(ws: ServerWebSocket<WebSocketConnectionData>): void
|
|
19
|
-
handleMessage(ws: ServerWebSocket<WebSocketConnectionData>, message: string | ArrayBuffer | ArrayBufferView): void
|
|
20
|
-
handleClose(ws: ServerWebSocket<WebSocketConnectionData>, code: number, reason: string): void
|
|
52
|
+
handleOpen(ws: ServerWebSocket<WebSocketConnectionData>): Promise<void>;
|
|
53
|
+
handleMessage(ws: ServerWebSocket<WebSocketConnectionData>, message: string | ArrayBuffer | ArrayBufferView): Promise<void>;
|
|
54
|
+
handleClose(ws: ServerWebSocket<WebSocketConnectionData>, code: number, reason: string): Promise<void>;
|
|
21
55
|
}
|
|
22
56
|
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/websocket/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/websocket/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAC;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAehD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IACjE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwC;IACvE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA2B;IAE3D,OAAO;WAIO,WAAW,IAAI,wBAAwB;IAOrD;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAaV,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;IAsCzD;;;;OAIG;IACI,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAgBjC,KAAK,IAAI,IAAI;IAMpB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAuBlB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAI7B;;;;;;OAMG;YACW,aAAa;IA0Gd,UAAU,CAAC,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAcvE,aAAa,CACxB,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAC5C,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,eAAe,GAC9C,OAAO,CAAC,IAAI,CAAC;IAcH,WAAW,CACtB,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;CAQjB"}
|
package/docs/zh/guide.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
import "reflect-metadata";
|
|
9
|
-
import { Application } from "
|
|
9
|
+
import { Application } from "@dangao/bun-server";
|
|
10
10
|
|
|
11
11
|
const app = new Application({ port: 3000 });
|
|
12
12
|
app.listen();
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
Injectable,
|
|
27
27
|
Param,
|
|
28
28
|
POST,
|
|
29
|
-
} from "
|
|
29
|
+
} from "@dangao/bun-server";
|
|
30
30
|
|
|
31
31
|
@Injectable()
|
|
32
32
|
class UserService {
|
|
@@ -64,7 +64,7 @@ app.listen();
|
|
|
64
64
|
## 3. 使用中间件
|
|
65
65
|
|
|
66
66
|
```ts
|
|
67
|
-
import { createCorsMiddleware, createLoggerMiddleware } from "
|
|
67
|
+
import { createCorsMiddleware, createLoggerMiddleware } from "@dangao/bun-server";
|
|
68
68
|
|
|
69
69
|
const app = new Application();
|
|
70
70
|
app.use(createLoggerMiddleware({ prefix: "[Example]" }));
|
|
@@ -74,7 +74,7 @@ app.use(createCorsMiddleware({ origin: "*" }));
|
|
|
74
74
|
`@UseMiddleware()` 可作用于单个控制器或方法:
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
|
-
import { UseMiddleware } from
|
|
77
|
+
import { UseMiddleware } from "@dangao/bun-server";
|
|
78
78
|
|
|
79
79
|
const auth = async (ctx, next) => {
|
|
80
80
|
if (ctx.getHeader('authorization') !== 'token') {
|
|
@@ -92,7 +92,7 @@ class SecureController { ... }
|
|
|
92
92
|
## 4. 参数验证
|
|
93
93
|
|
|
94
94
|
```ts
|
|
95
|
-
import { Validate, IsEmail, MinLength } from
|
|
95
|
+
import { Validate, IsEmail, MinLength } from "@dangao/bun-server";
|
|
96
96
|
|
|
97
97
|
@POST('/register')
|
|
98
98
|
public register(
|
|
@@ -108,32 +108,37 @@ public register(
|
|
|
108
108
|
## 5. WebSocket 网关
|
|
109
109
|
|
|
110
110
|
```ts
|
|
111
|
-
import { OnMessage, WebSocketGateway } from "
|
|
111
|
+
import { OnMessage, WebSocketGateway } from "@dangao/bun-server";
|
|
112
|
+
import type { ServerWebSocket } from "bun";
|
|
112
113
|
|
|
113
|
-
@WebSocketGateway("/ws
|
|
114
|
+
@WebSocketGateway("/ws")
|
|
114
115
|
class ChatGateway {
|
|
115
116
|
@OnMessage
|
|
116
|
-
public
|
|
117
|
-
ws.send(`
|
|
117
|
+
public handleMessage(ws: ServerWebSocket, message: string) {
|
|
118
|
+
ws.send(`Echo: ${message}`);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
const app = new Application({ port: 3000 });
|
|
121
123
|
app.registerWebSocketGateway(ChatGateway);
|
|
124
|
+
app.listen();
|
|
122
125
|
```
|
|
123
126
|
|
|
124
127
|
## 6. 文件上传与静态资源
|
|
125
128
|
|
|
126
129
|
```ts
|
|
127
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
createFileUploadMiddleware,
|
|
132
|
+
createStaticFileMiddleware,
|
|
133
|
+
} from "@dangao/bun-server";
|
|
128
134
|
|
|
135
|
+
const app = new Application({ port: 3000 });
|
|
136
|
+
|
|
137
|
+
// File upload
|
|
129
138
|
app.use(createFileUploadMiddleware({ maxSize: 5 * 1024 * 1024 }));
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
prefix: "/assets",
|
|
134
|
-
enableCache: true,
|
|
135
|
-
}),
|
|
136
|
-
);
|
|
139
|
+
|
|
140
|
+
// Static files
|
|
141
|
+
app.use(createStaticFileMiddleware({ root: "./public", prefix: "/assets" }));
|
|
137
142
|
```
|
|
138
143
|
|
|
139
144
|
上传后的文件可在 `context.body.files` 中读取;静态资源请求会自动设置
|
|
@@ -142,7 +147,7 @@ Content-Type 与缓存头。
|
|
|
142
147
|
## 7. 错误处理与自定义过滤器
|
|
143
148
|
|
|
144
149
|
```ts
|
|
145
|
-
import { ExceptionFilterRegistry, HttpException } from "
|
|
150
|
+
import { ExceptionFilterRegistry, HttpException } from "@dangao/bun-server";
|
|
146
151
|
|
|
147
152
|
ExceptionFilterRegistry.getInstance().register({
|
|
148
153
|
catch(error, context) {
|
package/docs/zh/migration.md
CHANGED
|
@@ -63,9 +63,15 @@
|
|
|
63
63
|
|
|
64
64
|
- 将原本在 `Bun.serve({ websocket })` 中手写的逻辑迁移到 `@WebSocketGateway`:
|
|
65
65
|
```ts
|
|
66
|
-
@
|
|
66
|
+
import { OnMessage, WebSocketGateway } from "@dangao/bun-server";
|
|
67
|
+
import type { ServerWebSocket } from "bun";
|
|
68
|
+
|
|
69
|
+
@WebSocketGateway('/ws')
|
|
67
70
|
class ChatGateway {
|
|
68
|
-
@OnMessage
|
|
71
|
+
@OnMessage
|
|
72
|
+
public handleMessage(ws: ServerWebSocket, message: string) {
|
|
73
|
+
ws.send(`Echo: ${message}`);
|
|
74
|
+
}
|
|
69
75
|
}
|
|
70
76
|
app.registerWebSocketGateway(ChatGateway);
|
|
71
77
|
```
|
package/package.json
CHANGED
package/src/core/server.ts
CHANGED
|
@@ -86,11 +86,20 @@ export class BunServer {
|
|
|
86
86
|
upgradeHeader.toLowerCase() === "websocket"
|
|
87
87
|
) {
|
|
88
88
|
const url = new URL(request.url);
|
|
89
|
+
// 检查是否有匹配的网关(支持动态路径匹配)
|
|
89
90
|
if (!this.options.websocketRegistry.hasGateway(url.pathname)) {
|
|
90
91
|
return new Response("WebSocket gateway not found", { status: 404 });
|
|
91
92
|
}
|
|
93
|
+
// 创建 Context 以便在 WebSocket 处理器中使用
|
|
94
|
+
const context = new Context(request);
|
|
95
|
+
// 创建 Bun 兼容的 URLSearchParams(需要 toJSON 方法)
|
|
96
|
+
const queryParams = new URLSearchParams(url.searchParams);
|
|
92
97
|
const upgraded = server.upgrade(request, {
|
|
93
|
-
data: {
|
|
98
|
+
data: {
|
|
99
|
+
path: url.pathname,
|
|
100
|
+
query: queryParams,
|
|
101
|
+
context,
|
|
102
|
+
},
|
|
94
103
|
});
|
|
95
104
|
if (upgraded) {
|
|
96
105
|
return undefined;
|
|
@@ -128,14 +137,14 @@ export class BunServer {
|
|
|
128
137
|
return responsePromise;
|
|
129
138
|
},
|
|
130
139
|
websocket: {
|
|
131
|
-
open: (ws) => {
|
|
132
|
-
this.options.websocketRegistry?.handleOpen(ws);
|
|
140
|
+
open: async (ws) => {
|
|
141
|
+
await this.options.websocketRegistry?.handleOpen(ws);
|
|
133
142
|
},
|
|
134
|
-
message: (ws, message) => {
|
|
135
|
-
this.options.websocketRegistry?.handleMessage(ws, message);
|
|
143
|
+
message: async (ws, message) => {
|
|
144
|
+
await this.options.websocketRegistry?.handleMessage(ws, message);
|
|
136
145
|
},
|
|
137
|
-
close: (ws, code, reason) => {
|
|
138
|
-
this.options.websocketRegistry?.handleClose(ws, code, reason);
|
|
146
|
+
close: async (ws, code, reason) => {
|
|
147
|
+
await this.options.websocketRegistry?.handleClose(ws, code, reason);
|
|
139
148
|
},
|
|
140
149
|
},
|
|
141
150
|
});
|