@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
|
@@ -3,7 +3,10 @@ import type { ServerWebSocket } from 'bun';
|
|
|
3
3
|
import { Container } from '../di/container';
|
|
4
4
|
import { ControllerRegistry } from '../controller/controller';
|
|
5
5
|
import { getGatewayMetadata, getHandlerMetadata } from './decorators';
|
|
6
|
-
import
|
|
6
|
+
import { ParamBinder } from '../controller/param-binder';
|
|
7
|
+
import { getParamMetadata } from '../controller/decorators';
|
|
8
|
+
import { Context } from '../core/context';
|
|
9
|
+
import type { Constructor } from '@/core/types';
|
|
7
10
|
|
|
8
11
|
interface GatewayDefinition {
|
|
9
12
|
path: string;
|
|
@@ -13,17 +16,24 @@ interface GatewayDefinition {
|
|
|
13
16
|
message?: string;
|
|
14
17
|
close?: string;
|
|
15
18
|
};
|
|
16
|
-
|
|
19
|
+
pattern: RegExp;
|
|
20
|
+
paramNames: string[];
|
|
21
|
+
isStatic: boolean;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export interface WebSocketConnectionData {
|
|
20
25
|
path: string;
|
|
26
|
+
query?: URLSearchParams;
|
|
27
|
+
context?: Context;
|
|
28
|
+
params?: Record<string, string>;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export class WebSocketGatewayRegistry {
|
|
24
32
|
private static instance: WebSocketGatewayRegistry;
|
|
25
33
|
private readonly container: Container;
|
|
26
34
|
private readonly gateways = new Map<string, GatewayDefinition>();
|
|
35
|
+
private readonly staticGateways = new Map<string, GatewayDefinition>();
|
|
36
|
+
private readonly dynamicGateways: GatewayDefinition[] = [];
|
|
27
37
|
|
|
28
38
|
private constructor() {
|
|
29
39
|
this.container = ControllerRegistry.getInstance().getContainer();
|
|
@@ -36,6 +46,24 @@ export class WebSocketGatewayRegistry {
|
|
|
36
46
|
return WebSocketGatewayRegistry.instance;
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
/**
|
|
50
|
+
* 解析路径,生成匹配模式和参数名列表
|
|
51
|
+
* @param path - 路由路径
|
|
52
|
+
* @returns 匹配模式和参数名列表
|
|
53
|
+
*/
|
|
54
|
+
private parsePath(path: string): { pattern: RegExp; paramNames: string[] } {
|
|
55
|
+
const paramNames: string[] = [];
|
|
56
|
+
const patternString = path
|
|
57
|
+
.replace(/:([^/]+)/g, (_, paramName) => {
|
|
58
|
+
paramNames.push(paramName);
|
|
59
|
+
return '([^/]+)';
|
|
60
|
+
})
|
|
61
|
+
.replace(/\*/g, '.*');
|
|
62
|
+
|
|
63
|
+
const pattern = new RegExp(`^${patternString}$`);
|
|
64
|
+
return { pattern, paramNames };
|
|
65
|
+
}
|
|
66
|
+
|
|
39
67
|
public register(gatewayClass: Constructor<unknown>): void {
|
|
40
68
|
const metadata = getGatewayMetadata(gatewayClass);
|
|
41
69
|
if (!metadata) {
|
|
@@ -51,82 +79,248 @@ export class WebSocketGatewayRegistry {
|
|
|
51
79
|
this.container.register(gatewayClass);
|
|
52
80
|
}
|
|
53
81
|
|
|
54
|
-
|
|
82
|
+
// 解析路径
|
|
83
|
+
const { pattern, paramNames } = this.parsePath(metadata.path);
|
|
84
|
+
const isStatic = !metadata.path.includes(':') && !metadata.path.includes('*');
|
|
85
|
+
|
|
86
|
+
const definition: GatewayDefinition = {
|
|
55
87
|
path: metadata.path,
|
|
56
88
|
gatewayClass,
|
|
57
89
|
handlers,
|
|
58
|
-
|
|
90
|
+
pattern,
|
|
91
|
+
paramNames,
|
|
92
|
+
isStatic,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.gateways.set(metadata.path, definition);
|
|
96
|
+
|
|
97
|
+
// 分别存储静态和动态路由
|
|
98
|
+
if (isStatic) {
|
|
99
|
+
this.staticGateways.set(metadata.path, definition);
|
|
100
|
+
} else {
|
|
101
|
+
this.dynamicGateways.push(definition);
|
|
102
|
+
}
|
|
59
103
|
}
|
|
60
104
|
|
|
105
|
+
/**
|
|
106
|
+
* 检查是否有匹配的网关(支持动态路径匹配)
|
|
107
|
+
* @param path - 请求路径
|
|
108
|
+
* @returns 是否有匹配的网关
|
|
109
|
+
*/
|
|
61
110
|
public hasGateway(path: string): boolean {
|
|
62
|
-
|
|
111
|
+
// 先检查静态路由
|
|
112
|
+
if (this.staticGateways.has(path)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 遍历动态路由
|
|
117
|
+
for (const gateway of this.dynamicGateways) {
|
|
118
|
+
if (gateway.pattern.test(path)) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return false;
|
|
63
124
|
}
|
|
64
125
|
|
|
65
126
|
public clear(): void {
|
|
66
127
|
this.gateways.clear();
|
|
128
|
+
this.staticGateways.clear();
|
|
129
|
+
this.dynamicGateways.length = 0;
|
|
67
130
|
}
|
|
68
131
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
132
|
+
/**
|
|
133
|
+
* 获取匹配的网关(支持动态路径匹配)
|
|
134
|
+
* @param path - 请求路径
|
|
135
|
+
* @returns 匹配的网关定义和路径参数
|
|
136
|
+
*/
|
|
137
|
+
private getGateway(path: string): { definition: GatewayDefinition; params: Record<string, string> } | undefined {
|
|
138
|
+
// 先检查静态路由
|
|
139
|
+
const staticGateway = this.staticGateways.get(path);
|
|
140
|
+
if (staticGateway) {
|
|
141
|
+
return { definition: staticGateway, params: {} };
|
|
142
|
+
}
|
|
72
143
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
144
|
+
// 遍历动态路由
|
|
145
|
+
for (const gateway of this.dynamicGateways) {
|
|
146
|
+
const match = path.match(gateway.pattern);
|
|
147
|
+
if (match) {
|
|
148
|
+
// 提取路径参数
|
|
149
|
+
const params: Record<string, string> = {};
|
|
150
|
+
for (let i = 0; i < gateway.paramNames.length; i++) {
|
|
151
|
+
params[gateway.paramNames[i]] = match[i + 1] ?? '';
|
|
152
|
+
}
|
|
153
|
+
return { definition: gateway, params };
|
|
154
|
+
}
|
|
76
155
|
}
|
|
77
|
-
|
|
156
|
+
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 动态创建网关实例(每次连接创建新实例)
|
|
162
|
+
* @param definition - 网关定义
|
|
163
|
+
* @returns 网关实例
|
|
164
|
+
*/
|
|
165
|
+
private createGatewayInstance(definition: GatewayDefinition): unknown {
|
|
166
|
+
return this.container.resolve(definition.gatewayClass);
|
|
78
167
|
}
|
|
79
168
|
|
|
80
|
-
|
|
169
|
+
/**
|
|
170
|
+
* 调用处理器,支持参数绑定
|
|
171
|
+
* @param ws - WebSocket 连接
|
|
172
|
+
* @param definition - 网关定义
|
|
173
|
+
* @param handlerName - 处理器方法名
|
|
174
|
+
* @param args - 原始参数(message, code, reason 等,不包括 ws)
|
|
175
|
+
*/
|
|
176
|
+
private async invokeHandler(
|
|
81
177
|
ws: ServerWebSocket<WebSocketConnectionData>,
|
|
82
178
|
definition: GatewayDefinition,
|
|
83
179
|
handlerName: string | undefined,
|
|
84
180
|
...args: unknown[]
|
|
85
|
-
): void {
|
|
181
|
+
): Promise<void> {
|
|
86
182
|
if (!handlerName) {
|
|
87
183
|
return;
|
|
88
184
|
}
|
|
89
|
-
|
|
185
|
+
|
|
186
|
+
// 动态创建实例(每次连接创建新实例)
|
|
187
|
+
const instance = this.createGatewayInstance(definition);
|
|
90
188
|
const handler = (instance as Record<string, unknown>)[handlerName];
|
|
91
|
-
|
|
189
|
+
|
|
190
|
+
if (typeof handler !== 'function') {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 获取参数元数据
|
|
195
|
+
const prototype = definition.gatewayClass.prototype;
|
|
196
|
+
const paramMetadata = getParamMetadata(prototype, handlerName);
|
|
197
|
+
|
|
198
|
+
// 如果有参数装饰器,使用参数绑定
|
|
199
|
+
if (paramMetadata.length > 0) {
|
|
200
|
+
// 创建或获取 Context
|
|
201
|
+
let context = ws.data?.context;
|
|
202
|
+
if (!context) {
|
|
203
|
+
// 从 WebSocket 连接数据创建 Context
|
|
204
|
+
const url = new URL(ws.data.path || '/', 'http://localhost');
|
|
205
|
+
if (ws.data.query) {
|
|
206
|
+
ws.data.query.forEach((value, key) => {
|
|
207
|
+
url.searchParams.set(key, value);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const request = new Request(url.toString(), {
|
|
211
|
+
method: 'GET',
|
|
212
|
+
headers: new Headers(),
|
|
213
|
+
});
|
|
214
|
+
context = new Context(request);
|
|
215
|
+
// 设置路径参数到 Context
|
|
216
|
+
if (ws.data.params) {
|
|
217
|
+
context.params = ws.data.params;
|
|
218
|
+
}
|
|
219
|
+
ws.data.context = context;
|
|
220
|
+
} else {
|
|
221
|
+
// 如果 Context 已存在,更新路径参数
|
|
222
|
+
if (ws.data.params) {
|
|
223
|
+
context.params = ws.data.params;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 使用 ParamBinder 绑定参数
|
|
228
|
+
const boundParams = await ParamBinder.bind(
|
|
229
|
+
prototype,
|
|
230
|
+
handlerName,
|
|
231
|
+
context,
|
|
232
|
+
this.container,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// 构建最终参数数组
|
|
236
|
+
// WebSocket 处理器的参数顺序:ws, [message/code/reason], [装饰器参数...]
|
|
237
|
+
const finalArgs: unknown[] = [];
|
|
238
|
+
const maxParamIndex = paramMetadata.length > 0
|
|
239
|
+
? Math.max(...paramMetadata.map((m) => m.index))
|
|
240
|
+
: -1;
|
|
241
|
+
|
|
242
|
+
// 按方法签名顺序填充参数
|
|
243
|
+
// 首先确定方法的总参数数量(包括 ws 和原始参数)
|
|
244
|
+
const totalParamCount = Math.max(
|
|
245
|
+
maxParamIndex + 1,
|
|
246
|
+
args.length + 1, // +1 是 ws 参数
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < totalParamCount; i++) {
|
|
250
|
+
const meta = paramMetadata.find((m) => m.index === i);
|
|
251
|
+
if (meta) {
|
|
252
|
+
// 使用绑定参数(从 ParamBinder 获取)
|
|
253
|
+
finalArgs[i] = boundParams[i];
|
|
254
|
+
} else if (i === 0) {
|
|
255
|
+
// 第一个参数是 ws
|
|
256
|
+
finalArgs[i] = ws;
|
|
257
|
+
} else {
|
|
258
|
+
// 其他参数是原始参数(message, code, reason 等)
|
|
259
|
+
const argIndex = i - 1;
|
|
260
|
+
if (argIndex < args.length) {
|
|
261
|
+
finalArgs[i] = args[argIndex];
|
|
262
|
+
} else {
|
|
263
|
+
finalArgs[i] = undefined;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 如果方法签名参数少于实际参数,添加剩余参数
|
|
269
|
+
if (args.length > maxParamIndex) {
|
|
270
|
+
for (let i = maxParamIndex + 1; i < args.length; i++) {
|
|
271
|
+
finalArgs.push(args[i]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
handler.apply(instance, finalArgs);
|
|
276
|
+
} else {
|
|
277
|
+
// 没有参数装饰器,直接调用:ws, ...args
|
|
92
278
|
handler.apply(instance, [ws, ...args]);
|
|
93
279
|
}
|
|
94
280
|
}
|
|
95
281
|
|
|
96
|
-
public handleOpen(ws: ServerWebSocket<WebSocketConnectionData>): void {
|
|
282
|
+
public async handleOpen(ws: ServerWebSocket<WebSocketConnectionData>): Promise<void> {
|
|
97
283
|
const path = ws.data?.path;
|
|
98
|
-
const
|
|
99
|
-
if (!
|
|
284
|
+
const match = path ? this.getGateway(path) : undefined;
|
|
285
|
+
if (!match) {
|
|
100
286
|
ws.close(1008, 'Gateway not found');
|
|
101
287
|
return;
|
|
102
288
|
}
|
|
103
|
-
|
|
289
|
+
// 保存路径参数到 WebSocket 连接数据
|
|
290
|
+
if (match.params && Object.keys(match.params).length > 0) {
|
|
291
|
+
ws.data.params = match.params;
|
|
292
|
+
}
|
|
293
|
+
await this.invokeHandler(ws, match.definition, match.definition.handlers.open);
|
|
104
294
|
}
|
|
105
295
|
|
|
106
|
-
public handleMessage(
|
|
296
|
+
public async handleMessage(
|
|
107
297
|
ws: ServerWebSocket<WebSocketConnectionData>,
|
|
108
298
|
message: string | ArrayBuffer | ArrayBufferView,
|
|
109
|
-
): void {
|
|
299
|
+
): Promise<void> {
|
|
110
300
|
const path = ws.data?.path;
|
|
111
|
-
const
|
|
112
|
-
if (!
|
|
301
|
+
const match = path ? this.getGateway(path) : undefined;
|
|
302
|
+
if (!match) {
|
|
113
303
|
ws.close(1008, 'Gateway not found');
|
|
114
304
|
return;
|
|
115
305
|
}
|
|
116
|
-
|
|
306
|
+
// 保存路径参数到 WebSocket 连接数据(如果还没有)
|
|
307
|
+
if (match.params && Object.keys(match.params).length > 0 && !ws.data.params) {
|
|
308
|
+
ws.data.params = match.params;
|
|
309
|
+
}
|
|
310
|
+
await this.invokeHandler(ws, match.definition, match.definition.handlers.message, message);
|
|
117
311
|
}
|
|
118
312
|
|
|
119
|
-
public handleClose(
|
|
313
|
+
public async handleClose(
|
|
120
314
|
ws: ServerWebSocket<WebSocketConnectionData>,
|
|
121
315
|
code: number,
|
|
122
316
|
reason: string,
|
|
123
|
-
): void {
|
|
317
|
+
): Promise<void> {
|
|
124
318
|
const path = ws.data?.path;
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
319
|
+
const match = path ? this.getGateway(path) : undefined;
|
|
320
|
+
if (!match) {
|
|
127
321
|
return;
|
|
128
322
|
}
|
|
129
|
-
this.invokeHandler(ws, definition, definition.handlers.close, code, reason);
|
|
323
|
+
await this.invokeHandler(ws, match.definition, match.definition.handlers.close, code, reason);
|
|
130
324
|
}
|
|
131
325
|
}
|
|
132
326
|
|