@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.
@@ -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 type { Constructor } from '@/core/types'
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
- instance?: unknown;
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
- this.gateways.set(metadata.path, {
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
- return this.gateways.has(path);
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
- private getGateway(path: string): GatewayDefinition | undefined {
70
- return this.gateways.get(path);
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
- private getGatewayInstance(definition: GatewayDefinition): unknown {
74
- if (!definition.instance) {
75
- definition.instance = this.container.resolve(definition.gatewayClass);
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
- return definition.instance;
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
- private invokeHandler(
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
- const instance = this.getGatewayInstance(definition);
185
+
186
+ // 动态创建实例(每次连接创建新实例)
187
+ const instance = this.createGatewayInstance(definition);
90
188
  const handler = (instance as Record<string, unknown>)[handlerName];
91
- if (typeof handler === 'function') {
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 definition = path ? this.getGateway(path) : undefined;
99
- if (!definition) {
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
- this.invokeHandler(ws, definition, definition.handlers.open);
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 definition = path ? this.getGateway(path) : undefined;
112
- if (!definition) {
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
- this.invokeHandler(ws, definition, definition.handlers.message, message);
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 definition = path ? this.getGateway(path) : undefined;
126
- if (!definition) {
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