@dangao/bun-server 1.6.0 → 1.7.1

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.
Files changed (43) hide show
  1. package/README.md +72 -3
  2. package/dist/cache/cache-module.d.ts +18 -0
  3. package/dist/cache/cache-module.d.ts.map +1 -1
  4. package/dist/cache/index.d.ts +3 -1
  5. package/dist/cache/index.d.ts.map +1 -1
  6. package/dist/cache/interceptors.d.ts +41 -0
  7. package/dist/cache/interceptors.d.ts.map +1 -0
  8. package/dist/cache/service-proxy.d.ts +62 -0
  9. package/dist/cache/service-proxy.d.ts.map +1 -0
  10. package/dist/controller/controller.d.ts +8 -0
  11. package/dist/controller/controller.d.ts.map +1 -1
  12. package/dist/core/application.d.ts +5 -0
  13. package/dist/core/application.d.ts.map +1 -1
  14. package/dist/core/server.d.ts.map +1 -1
  15. package/dist/di/container.d.ts +18 -1
  16. package/dist/di/container.d.ts.map +1 -1
  17. package/dist/di/index.d.ts +1 -1
  18. package/dist/di/index.d.ts.map +1 -1
  19. package/dist/di/types.d.ts +22 -0
  20. package/dist/di/types.d.ts.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3235 -2875
  24. package/dist/websocket/registry.d.ts +19 -0
  25. package/dist/websocket/registry.d.ts.map +1 -1
  26. package/docs/symbol-interface-pattern.md +431 -0
  27. package/docs/zh/symbol-interface-pattern.md +431 -0
  28. package/package.json +1 -1
  29. package/src/cache/cache-module.ts +37 -0
  30. package/src/cache/index.ts +16 -1
  31. package/src/cache/interceptors.ts +295 -0
  32. package/src/cache/service-proxy.ts +219 -0
  33. package/src/controller/controller.ts +30 -6
  34. package/src/core/application.ts +25 -1
  35. package/src/core/server.ts +1 -0
  36. package/src/di/container.ts +57 -7
  37. package/src/di/index.ts +7 -1
  38. package/src/di/types.ts +29 -0
  39. package/src/index.ts +7 -0
  40. package/src/websocket/registry.ts +114 -14
  41. package/tests/cache/cache-decorators.test.ts +284 -0
  42. package/tests/controller/path-combination.test.ts +353 -0
  43. package/tests/websocket/gateway.test.ts +207 -1
@@ -5,16 +5,35 @@ export interface WebSocketConnectionData {
5
5
  path: string;
6
6
  query?: URLSearchParams;
7
7
  context?: Context;
8
+ params?: Record<string, string>;
8
9
  }
9
10
  export declare class WebSocketGatewayRegistry {
10
11
  private static instance;
11
12
  private readonly container;
12
13
  private readonly gateways;
14
+ private readonly staticGateways;
15
+ private readonly dynamicGateways;
13
16
  private constructor();
14
17
  static getInstance(): WebSocketGatewayRegistry;
18
+ /**
19
+ * 解析路径,生成匹配模式和参数名列表
20
+ * @param path - 路由路径
21
+ * @returns 匹配模式和参数名列表
22
+ */
23
+ private parsePath;
15
24
  register(gatewayClass: Constructor<unknown>): void;
25
+ /**
26
+ * 检查是否有匹配的网关(支持动态路径匹配)
27
+ * @param path - 请求路径
28
+ * @returns 是否有匹配的网关
29
+ */
16
30
  hasGateway(path: string): boolean;
17
31
  clear(): void;
32
+ /**
33
+ * 获取匹配的网关(支持动态路径匹配)
34
+ * @param path - 请求路径
35
+ * @returns 匹配的网关定义和路径参数
36
+ */
18
37
  private getGateway;
19
38
  /**
20
39
  * 动态创建网关实例(每次连接创建新实例)
@@ -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;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAYhD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAEjE,OAAO;WAIO,WAAW,IAAI,wBAAwB;IAO9C,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;IAsBlD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC,KAAK,IAAI,IAAI;IAIpB,OAAO,CAAC,UAAU;IAIlB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAI7B;;;;;;OAMG;YACW,aAAa;IAiGd,UAAU,CAAC,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvE,aAAa,CACxB,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAC5C,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,eAAe,GAC9C,OAAO,CAAC,IAAI,CAAC;IAUH,WAAW,CACtB,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;CAQjB"}
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"}
@@ -0,0 +1,431 @@
1
+ # Symbol + Interface 同名模式详解
2
+
3
+ ## 📖 背景
4
+
5
+ TypeScript 在编译为 JavaScript 后,所有类型信息都会丢失。这给依赖注入框架带来了挑战:如何在运行时识别注入的依赖类型?
6
+
7
+ ## 🎯 解决方案
8
+
9
+ Bun Server Framework 采用 **Symbol + Interface 同名模式**,优雅地解决了这个问题。
10
+
11
+ ## 💡 核心概念
12
+
13
+ ### 传统方式的问题
14
+
15
+ ```typescript
16
+ // ❌ 传统方式:只能注入具体类
17
+ interface UserService {
18
+ find(id: string): Promise<User>;
19
+ }
20
+
21
+ @Injectable()
22
+ class UserServiceImpl implements UserService {
23
+ async find(id: string) { ... }
24
+ }
25
+
26
+ // 问题:TypeScript 编译后 interface 消失
27
+ // 无法在运行时通过 interface 类型进行注入
28
+ public constructor(
29
+ private readonly userService: UserService // 编译后类型信息丢失
30
+ ) {}
31
+ ```
32
+
33
+ ### Symbol + Interface 同名模式
34
+
35
+ ```typescript
36
+ // ✅ Bun Server 方式:Symbol + Interface 同名
37
+
38
+ // 1. 定义接口(编译时类型检查)
39
+ interface UserService {
40
+ find(id: string): Promise<User>;
41
+ create(user: User): Promise<User>;
42
+ }
43
+
44
+ // 2. 定义同名 Symbol(运行时 Token)
45
+ // 注意:声明为 const,与 interface 同名
46
+ const UserService = Symbol('UserService');
47
+
48
+ // 3. 实现接口
49
+ @Injectable()
50
+ class UserServiceImpl implements UserService {
51
+ public async find(id: string): Promise<User> {
52
+ // 实现...
53
+ }
54
+
55
+ public async create(user: User): Promise<User> {
56
+ // 实现...
57
+ }
58
+ }
59
+
60
+ // 4. 在 Module 中配置
61
+ @Module({
62
+ providers: [{
63
+ provide: UserService, // Symbol Token(运行时)
64
+ useClass: UserServiceImpl, // 实现类
65
+ }],
66
+ exports: [UserServiceImpl], // 导出实现类(可选)
67
+ })
68
+ class UserModule {}
69
+
70
+ // 5. 注入使用
71
+ @Controller('/users')
72
+ class UserController {
73
+ public constructor(
74
+ // 类型是 interface UserService(编译时检查)
75
+ // 实际注入的是 Symbol('UserService') 对应的实例(运行时)
76
+ private readonly userService: UserService,
77
+ ) {}
78
+
79
+ @GET('/:id')
80
+ public async getUser(@Param('id') id: string) {
81
+ // TypeScript 知道 userService 有 find 方法
82
+ return await this.userService.find(id);
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## 🔑 关键要点
88
+
89
+ ### 1. 导入时不能使用 `import type`
90
+
91
+ ```typescript
92
+ // ✅ 正确:同时导入 Symbol 和 interface
93
+ import { UserService } from './user-service';
94
+
95
+ // ❌ 错误:只导入类型,Symbol 丢失
96
+ import type { UserService } from './user-service';
97
+
98
+ // ❌ 错误:混合导入会导致混淆
99
+ import { type UserService } from './user-service';
100
+ ```
101
+
102
+ **原因**:`import type` 只导入类型信息,编译后会被完全移除,导致 Symbol 丢失。
103
+
104
+ ### 2. 导出顺序
105
+
106
+ ```typescript
107
+ // 推荐的文件组织方式
108
+
109
+ // user-service.ts
110
+ // 1. 导入依赖
111
+ import { Injectable } from '@dangao/bun-server';
112
+
113
+ // 2. 定义接口
114
+ export interface UserService {
115
+ find(id: string): Promise<User>;
116
+ }
117
+
118
+ // 3. 定义 Symbol(与接口同名)
119
+ export const UserService = Symbol('UserService');
120
+
121
+ // 4. 实现类
122
+ @Injectable()
123
+ export class UserServiceImpl implements UserService {
124
+ public async find(id: string): Promise<User> {
125
+ // ...
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### 3. Module 配置
131
+
132
+ ```typescript
133
+ @Module({
134
+ providers: [
135
+ {
136
+ provide: UserService, // 使用 Symbol 作为 Token
137
+ useClass: UserServiceImpl, // 指定实现类
138
+ }
139
+ ],
140
+ exports: [UserServiceImpl], // 导出实现类(供其他模块使用)
141
+ })
142
+ class UserModule {}
143
+ ```
144
+
145
+ **注意**:
146
+ - `provide` 使用 Symbol Token
147
+ - `exports` 导出实现类(不是 Symbol)
148
+
149
+ ### 4. 构造函数注入
150
+
151
+ ```typescript
152
+ // ✅ 推荐:默认注入(无需装饰器)
153
+ public constructor(
154
+ private readonly userService: UserService, // 框架自动识别类型
155
+ ) {}
156
+
157
+ // ⚠️ 仅在以下情况使用 @Inject
158
+ public constructor(
159
+ @Inject(UserService) private readonly userService: UserService,
160
+ ) {}
161
+ ```
162
+
163
+ ## 📋 完整示例
164
+
165
+ ### 步骤 1:定义服务接口和实现
166
+
167
+ ```typescript
168
+ // src/user/user-service.ts
169
+
170
+ import { Injectable } from '@dangao/bun-server';
171
+
172
+ // 1. 定义用户实体
173
+ export interface User {
174
+ id: string;
175
+ name: string;
176
+ email: string;
177
+ }
178
+
179
+ // 2. 定义服务接口
180
+ export interface UserService {
181
+ find(id: string): Promise<User | undefined>;
182
+ create(name: string, email: string): Promise<User>;
183
+ findAll(): Promise<User[]>;
184
+ }
185
+
186
+ // 3. 定义同名 Symbol
187
+ export const UserService = Symbol('UserService');
188
+
189
+ // 4. 实现服务
190
+ @Injectable()
191
+ export class UserServiceImpl implements UserService {
192
+ private readonly users = new Map<string, User>();
193
+
194
+ public async find(id: string): Promise<User | undefined> {
195
+ return this.users.get(id);
196
+ }
197
+
198
+ public async create(name: string, email: string): Promise<User> {
199
+ const id = String(this.users.size + 1);
200
+ const user = { id, name, email };
201
+ this.users.set(id, user);
202
+ return user;
203
+ }
204
+
205
+ public async findAll(): Promise<User[]> {
206
+ return Array.from(this.users.values());
207
+ }
208
+ }
209
+ ```
210
+
211
+ ### 步骤 2:创建控制器
212
+
213
+ ```typescript
214
+ // src/user/user-controller.ts
215
+
216
+ import { Controller, GET, POST, Body, Param } from '@dangao/bun-server';
217
+ // ✅ 注意:不要用 import type
218
+ import { UserService } from './user-service';
219
+
220
+ @Controller('/api/users')
221
+ export class UserController {
222
+ // 构造函数注入,框架自动识别类型
223
+ public constructor(
224
+ private readonly userService: UserService,
225
+ ) {}
226
+
227
+ @GET('/')
228
+ public async getAllUsers() {
229
+ return await this.userService.findAll();
230
+ }
231
+
232
+ @GET('/:id')
233
+ public async getUser(@Param('id') id: string) {
234
+ const user = await this.userService.find(id);
235
+ if (!user) {
236
+ return { error: 'User not found' };
237
+ }
238
+ return user;
239
+ }
240
+
241
+ @POST('/')
242
+ public async createUser(@Body() body: { name: string; email: string }) {
243
+ return await this.userService.create(body.name, body.email);
244
+ }
245
+ }
246
+ ```
247
+
248
+ ### 步骤 3:配置模块
249
+
250
+ ```typescript
251
+ // src/user/user-module.ts
252
+
253
+ import { Module } from '@dangao/bun-server';
254
+ import { UserController } from './user-controller';
255
+ import { UserService, UserServiceImpl } from './user-service';
256
+
257
+ @Module({
258
+ controllers: [UserController],
259
+ providers: [
260
+ {
261
+ provide: UserService, // Symbol Token
262
+ useClass: UserServiceImpl, // 实现类
263
+ }
264
+ ],
265
+ exports: [UserServiceImpl], // 导出供其他模块使用
266
+ })
267
+ export class UserModule {}
268
+ ```
269
+
270
+ ### 步骤 4:启动应用
271
+
272
+ ```typescript
273
+ // src/main.ts
274
+
275
+ import { Application } from '@dangao/bun-server';
276
+ import { UserModule } from './user/user-module';
277
+
278
+ const app = new Application({ port: 3000 });
279
+ app.registerModule(UserModule);
280
+ app.listen();
281
+
282
+ console.log('Server running on http://localhost:3000');
283
+ ```
284
+
285
+ ## 🎨 高级用法
286
+
287
+ ### 多实现切换
288
+
289
+ ```typescript
290
+ // 定义接口和 Symbol
291
+ export interface CacheService {
292
+ get(key: string): Promise<string | null>;
293
+ set(key: string, value: string): Promise<void>;
294
+ }
295
+ export const CacheService = Symbol('CacheService');
296
+
297
+ // 内存实现
298
+ @Injectable()
299
+ export class MemoryCacheService implements CacheService {
300
+ private cache = new Map<string, string>();
301
+
302
+ async get(key: string) {
303
+ return this.cache.get(key) ?? null;
304
+ }
305
+
306
+ async set(key: string, value: string) {
307
+ this.cache.set(key, value);
308
+ }
309
+ }
310
+
311
+ // Redis 实现
312
+ @Injectable()
313
+ export class RedisCacheService implements CacheService {
314
+ async get(key: string) {
315
+ // Redis 实现...
316
+ }
317
+
318
+ async set(key: string, value: string) {
319
+ // Redis 实现...
320
+ }
321
+ }
322
+
323
+ // 根据环境切换实现
324
+ const isProduction = process.env.NODE_ENV === 'production';
325
+
326
+ @Module({
327
+ providers: [
328
+ {
329
+ provide: CacheService,
330
+ useClass: isProduction ? RedisCacheService : MemoryCacheService,
331
+ }
332
+ ],
333
+ })
334
+ export class CacheModule {}
335
+ ```
336
+
337
+ ### 工厂函数
338
+
339
+ ```typescript
340
+ // 使用工厂函数创建实例
341
+ @Module({
342
+ providers: [
343
+ {
344
+ provide: UserService,
345
+ useFactory: (container: Container) => {
346
+ const config = container.resolve<ConfigService>(CONFIG_SERVICE_TOKEN);
347
+ if (config.get('database.type') === 'mongodb') {
348
+ return new MongoUserService();
349
+ }
350
+ return new PostgresUserService();
351
+ },
352
+ }
353
+ ],
354
+ })
355
+ export class UserModule {}
356
+ ```
357
+
358
+ ## ❓ 常见问题
359
+
360
+ ### Q1: 为什么不直接使用类作为 Token?
361
+
362
+ **A**: 使用类作为 Token 有以下问题:
363
+ 1. 无法实现面向接口编程
364
+ 2. 紧耦合实现类,不利于测试
365
+ 3. 无法在运行时动态切换实现
366
+
367
+ Symbol + Interface 模式提供了更好的灵活性。
368
+
369
+ ### Q2: Symbol 和 String Token 有什么区别?
370
+
371
+ ```typescript
372
+ // Symbol Token(推荐)
373
+ const UserService = Symbol('UserService');
374
+
375
+ // String Token(不推荐)
376
+ const USER_SERVICE_TOKEN = 'UserService';
377
+ ```
378
+
379
+ **区别**:
380
+ - Symbol 是唯一的,避免命名冲突
381
+ - String 可能在大型项目中重复,导致注入错误
382
+ - Symbol 配合 interface 同名,语义更清晰
383
+
384
+ ### Q3: 什么时候必须用 @Inject 装饰器?
385
+
386
+ 只有以下情况需要:
387
+ 1. 使用 Symbol Token(虽然默认注入也支持,但显式使用更清晰)
388
+ 2. 参数类型无法推断(如 interface)
389
+ 3. 需要注入特定的实现
390
+
391
+ ```typescript
392
+ // 需要 @Inject 的情况
393
+ public constructor(
394
+ @Inject(CONFIG_SERVICE_TOKEN) private config: ConfigService,
395
+ @Inject(LOGGER_TOKEN) private logger: Logger,
396
+ ) {}
397
+
398
+ // 不需要 @Inject(推荐)
399
+ public constructor(
400
+ private readonly userService: UserService,
401
+ private readonly productService: ProductService,
402
+ ) {}
403
+ ```
404
+
405
+ ### Q4: exports 为什么导出实现类而不是 Symbol?
406
+
407
+ ```typescript
408
+ @Module({
409
+ providers: [{
410
+ provide: UserService, // Symbol Token
411
+ useClass: UserServiceImpl,
412
+ }],
413
+ exports: [UserServiceImpl], // 导出实现类
414
+ })
415
+ ```
416
+
417
+ **原因**:
418
+ - `exports` 的作用是让其他模块可以导入该模块的 providers
419
+ - 导出的是 providers 数组中的元素(实现类)
420
+ - 其他模块通过 `imports` 导入后,可以使用 Symbol Token 注入
421
+
422
+ ## 📚 相关资源
423
+
424
+ - [依赖注入指南](./guide.md#dependency-injection)
425
+ - [模块系统详解](./guide.md#module-system)
426
+ - [最佳实践](./best-practices.md)
427
+ - [示例代码](../examples/basic-app.ts)
428
+
429
+ ---
430
+
431
+ **提示**:这个模式是 Bun Server Framework 的核心特性之一,理解它能帮助你更好地设计可维护的应用架构。