@dangao/bun-server 1.9.0 → 1.12.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.
Files changed (209) hide show
  1. package/README.md +79 -6
  2. package/dist/cache/cache-module.d.ts +6 -0
  3. package/dist/cache/cache-module.d.ts.map +1 -1
  4. package/dist/client/generator.d.ts +16 -0
  5. package/dist/client/generator.d.ts.map +1 -0
  6. package/dist/client/index.d.ts +4 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/runtime.d.ts +15 -0
  9. package/dist/client/runtime.d.ts.map +1 -0
  10. package/dist/client/types.d.ts +36 -0
  11. package/dist/client/types.d.ts.map +1 -0
  12. package/dist/config/config-module.d.ts +7 -0
  13. package/dist/config/config-module.d.ts.map +1 -1
  14. package/dist/config/index.d.ts +1 -1
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/service.d.ts +13 -0
  17. package/dist/config/service.d.ts.map +1 -1
  18. package/dist/config/types.d.ts +10 -0
  19. package/dist/config/types.d.ts.map +1 -1
  20. package/dist/core/application.d.ts +7 -0
  21. package/dist/core/application.d.ts.map +1 -1
  22. package/dist/core/apply-decorators.d.ts +6 -0
  23. package/dist/core/apply-decorators.d.ts.map +1 -0
  24. package/dist/core/cluster.d.ts +47 -0
  25. package/dist/core/cluster.d.ts.map +1 -0
  26. package/dist/core/index.d.ts +1 -0
  27. package/dist/core/index.d.ts.map +1 -1
  28. package/dist/core/server.d.ts +8 -0
  29. package/dist/core/server.d.ts.map +1 -1
  30. package/dist/dashboard/controller.d.ts +55 -0
  31. package/dist/dashboard/controller.d.ts.map +1 -0
  32. package/dist/dashboard/dashboard-extension.d.ts +20 -0
  33. package/dist/dashboard/dashboard-extension.d.ts.map +1 -0
  34. package/dist/dashboard/dashboard-module.d.ts +13 -0
  35. package/dist/dashboard/dashboard-module.d.ts.map +1 -0
  36. package/dist/dashboard/index.d.ts +4 -0
  37. package/dist/dashboard/index.d.ts.map +1 -0
  38. package/dist/dashboard/types.d.ts +16 -0
  39. package/dist/dashboard/types.d.ts.map +1 -0
  40. package/dist/dashboard/ui.d.ts +7 -0
  41. package/dist/dashboard/ui.d.ts.map +1 -0
  42. package/dist/database/database-module.d.ts +7 -0
  43. package/dist/database/database-module.d.ts.map +1 -1
  44. package/dist/debug/debug-module.d.ts +13 -0
  45. package/dist/debug/debug-module.d.ts.map +1 -0
  46. package/dist/debug/debug-ui-middleware.d.ts +8 -0
  47. package/dist/debug/debug-ui-middleware.d.ts.map +1 -0
  48. package/dist/debug/index.d.ts +5 -0
  49. package/dist/debug/index.d.ts.map +1 -0
  50. package/dist/debug/middleware.d.ts +12 -0
  51. package/dist/debug/middleware.d.ts.map +1 -0
  52. package/dist/debug/recorder.d.ts +61 -0
  53. package/dist/debug/recorder.d.ts.map +1 -0
  54. package/dist/debug/types.d.ts +48 -0
  55. package/dist/debug/types.d.ts.map +1 -0
  56. package/dist/debug/ui.d.ts +6 -0
  57. package/dist/debug/ui.d.ts.map +1 -0
  58. package/dist/di/async-module.d.ts +49 -0
  59. package/dist/di/async-module.d.ts.map +1 -0
  60. package/dist/di/lifecycle.d.ts +49 -0
  61. package/dist/di/lifecycle.d.ts.map +1 -0
  62. package/dist/di/module-registry.d.ts +24 -0
  63. package/dist/di/module-registry.d.ts.map +1 -1
  64. package/dist/index.d.ts +9 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +1887 -35
  67. package/dist/router/route.d.ts +5 -7
  68. package/dist/router/route.d.ts.map +1 -1
  69. package/dist/swagger/generator.d.ts +10 -0
  70. package/dist/swagger/generator.d.ts.map +1 -1
  71. package/dist/testing/test-client.d.ts +49 -0
  72. package/dist/testing/test-client.d.ts.map +1 -0
  73. package/dist/testing/testing-module.d.ts +90 -0
  74. package/dist/testing/testing-module.d.ts.map +1 -0
  75. package/dist/websocket/registry.d.ts +1 -6
  76. package/dist/websocket/registry.d.ts.map +1 -1
  77. package/docs/async-module.md +59 -0
  78. package/docs/client-generation.md +100 -0
  79. package/docs/cluster.md +81 -0
  80. package/docs/custom-decorators.md +1 -7
  81. package/docs/dashboard.md +54 -0
  82. package/docs/debug.md +58 -0
  83. package/docs/extensions.md +0 -2
  84. package/docs/guide.md +0 -1
  85. package/docs/lifecycle.md +72 -0
  86. package/docs/testing.md +110 -0
  87. package/docs/zh/async-module.md +98 -0
  88. package/docs/zh/client-generation.md +92 -0
  89. package/docs/zh/cluster.md +74 -0
  90. package/docs/zh/custom-decorators.md +1 -7
  91. package/docs/zh/dashboard.md +69 -0
  92. package/docs/zh/debug.md +81 -0
  93. package/docs/zh/extensions.md +0 -2
  94. package/docs/zh/guide.md +0 -1
  95. package/docs/zh/lifecycle.md +87 -0
  96. package/docs/zh/migration.md +0 -5
  97. package/docs/zh/testing.md +119 -0
  98. package/package.json +4 -4
  99. package/src/cache/cache-module.ts +25 -0
  100. package/src/client/generator.ts +36 -0
  101. package/src/client/index.ts +8 -0
  102. package/src/client/runtime.ts +101 -0
  103. package/src/client/types.ts +38 -0
  104. package/src/config/config-module.ts +44 -4
  105. package/src/config/index.ts +1 -0
  106. package/src/config/service.ts +50 -0
  107. package/src/config/types.ts +12 -0
  108. package/src/core/application.ts +37 -0
  109. package/src/core/apply-decorators.ts +31 -0
  110. package/src/core/cluster.ts +143 -0
  111. package/src/core/index.ts +1 -0
  112. package/src/core/server.ts +14 -1
  113. package/src/dashboard/controller.ts +227 -0
  114. package/src/dashboard/dashboard-extension.ts +26 -0
  115. package/src/dashboard/dashboard-module.ts +38 -0
  116. package/src/dashboard/index.ts +3 -0
  117. package/src/dashboard/types.ts +16 -0
  118. package/src/dashboard/ui.ts +219 -0
  119. package/src/database/database-module.ts +20 -0
  120. package/src/debug/debug-module.ts +70 -0
  121. package/src/debug/debug-ui-middleware.ts +110 -0
  122. package/src/debug/index.ts +9 -0
  123. package/src/debug/middleware.ts +126 -0
  124. package/src/debug/recorder.ts +141 -0
  125. package/src/debug/types.ts +49 -0
  126. package/src/debug/ui.ts +393 -0
  127. package/src/di/async-module.ts +141 -0
  128. package/src/di/lifecycle.ts +117 -0
  129. package/src/di/module-registry.ts +75 -0
  130. package/src/index.ts +35 -0
  131. package/src/router/route.ts +20 -20
  132. package/src/swagger/generator.ts +100 -0
  133. package/src/testing/test-client.ts +112 -0
  134. package/src/testing/testing-module.ts +238 -0
  135. package/src/websocket/registry.ts +3 -16
  136. package/tests/auth/auth-decorators.test.ts +0 -1
  137. package/tests/auth/oauth2-service.test.ts +0 -1
  138. package/tests/cache/cache-decorators-extended.test.ts +0 -1
  139. package/tests/cache/cache-decorators.test.ts +0 -1
  140. package/tests/cache/cache-interceptors.test.ts +0 -1
  141. package/tests/cache/cache-module.test.ts +0 -1
  142. package/tests/cache/cache-service-proxy.test.ts +0 -1
  143. package/tests/client/client-generator.test.ts +142 -0
  144. package/tests/config/config-center-integration.test.ts +0 -1
  145. package/tests/config/config-module-extended.test.ts +0 -1
  146. package/tests/config/config-module.test.ts +0 -1
  147. package/tests/controller/controller.test.ts +0 -1
  148. package/tests/controller/param-binder.test.ts +0 -1
  149. package/tests/controller/path-combination.test.ts +0 -1
  150. package/tests/core/application.test.ts +34 -0
  151. package/tests/core/apply-decorators.test.ts +109 -0
  152. package/tests/core/cluster.test.ts +32 -0
  153. package/tests/dashboard/dashboard-module.test.ts +85 -0
  154. package/tests/database/database-module.test.ts +0 -1
  155. package/tests/database/orm.test.ts +0 -1
  156. package/tests/database/postgres-mysql-integration.test.ts +0 -1
  157. package/tests/database/transaction.test.ts +0 -1
  158. package/tests/debug/debug-module.test.ts +141 -0
  159. package/tests/di/async-module.test.ts +125 -0
  160. package/tests/di/container.test.ts +0 -1
  161. package/tests/di/lifecycle.test.ts +140 -0
  162. package/tests/error/error-handler.test.ts +0 -1
  163. package/tests/events/event-decorators.test.ts +0 -1
  164. package/tests/events/event-listener-scanner.test.ts +0 -1
  165. package/tests/events/event-module.test.ts +0 -1
  166. package/tests/extensions/logger-module.test.ts +0 -1
  167. package/tests/health/health-module.test.ts +0 -1
  168. package/tests/integration/oauth2-e2e.test.ts +0 -1
  169. package/tests/integration/session-e2e.test.ts +0 -1
  170. package/tests/interceptor/base-interceptor.test.ts +0 -1
  171. package/tests/interceptor/builtin/cache-interceptor.test.ts +0 -1
  172. package/tests/interceptor/builtin/log-interceptor.test.ts +0 -1
  173. package/tests/interceptor/builtin/permission-interceptor.test.ts +0 -1
  174. package/tests/interceptor/interceptor-advanced-integration.test.ts +0 -1
  175. package/tests/interceptor/interceptor-chain.test.ts +0 -1
  176. package/tests/interceptor/interceptor-integration.test.ts +0 -1
  177. package/tests/interceptor/interceptor-metadata.test.ts +0 -1
  178. package/tests/interceptor/interceptor-registry.test.ts +0 -1
  179. package/tests/interceptor/perf/interceptor-performance.test.ts +0 -1
  180. package/tests/metrics/metrics-module.test.ts +0 -1
  181. package/tests/microservice/config-center.test.ts +0 -1
  182. package/tests/microservice/service-client-decorators.test.ts +0 -1
  183. package/tests/microservice/service-registry-decorators.test.ts +0 -1
  184. package/tests/microservice/service-registry.test.ts +0 -1
  185. package/tests/middleware/builtin/middleware-builtin-extended.test.ts +0 -1
  186. package/tests/middleware/builtin/rate-limit.test.ts +0 -1
  187. package/tests/middleware/middleware-decorators.test.ts +0 -1
  188. package/tests/middleware/middleware-pipeline.test.ts +0 -1
  189. package/tests/middleware/middleware.test.ts +0 -1
  190. package/tests/perf/optimization.test.ts +0 -1
  191. package/tests/queue/queue-decorators.test.ts +0 -1
  192. package/tests/queue/queue-module.test.ts +0 -1
  193. package/tests/queue/queue-service.test.ts +0 -1
  194. package/tests/router/router-decorators.test.ts +0 -1
  195. package/tests/router/router-extended.test.ts +0 -1
  196. package/tests/security/guards/guards-integration.test.ts +0 -1
  197. package/tests/security/guards/guards.test.ts +0 -1
  198. package/tests/security/guards/reflector.test.ts +0 -1
  199. package/tests/security/security-filter.test.ts +0 -1
  200. package/tests/security/security-module-extended.test.ts +0 -1
  201. package/tests/security/security-module.test.ts +0 -1
  202. package/tests/session/session-decorators.test.ts +0 -1
  203. package/tests/session/session-module.test.ts +0 -1
  204. package/tests/swagger/decorators.test.ts +0 -1
  205. package/tests/swagger/swagger-module.test.ts +0 -1
  206. package/tests/swagger/ui.test.ts +0 -1
  207. package/tests/testing/testing-module.test.ts +129 -0
  208. package/tests/validation/class-validator.test.ts +0 -1
  209. package/tests/validation/controller-validation.test.ts +0 -1
@@ -0,0 +1,87 @@
1
+ # 生命周期钩子
2
+
3
+ Bun Server 提供四个生命周期接口,用于在模块和应用启动、关闭时执行初始化或清理逻辑。
4
+
5
+ ## 接口定义
6
+
7
+ - **OnModuleInit**:`onModuleInit()`,在模块所有 providers 注册完成后调用
8
+ - **OnModuleDestroy**:`onModuleDestroy()`,在应用关闭时调用(反向顺序)
9
+ - **OnApplicationBootstrap**:`onApplicationBootstrap()`,在所有模块初始化完成后、服务器开始监听前调用
10
+ - **OnApplicationShutdown**:`onApplicationShutdown(signal?)`,在优雅停机开始时调用
11
+
12
+ ## 执行顺序
13
+
14
+ **启动阶段**:`onModuleInit` → `onApplicationBootstrap`
15
+
16
+ **关闭阶段**:`onApplicationShutdown` → `onModuleDestroy`(均为反向顺序,即后注册的先执行)
17
+
18
+ ## 示例:DatabaseService 的初始化和销毁
19
+
20
+ ```ts
21
+ import {
22
+ Application,
23
+ Injectable,
24
+ Controller,
25
+ GET,
26
+ Module,
27
+ } from '@dangao/bun-server';
28
+ import type {
29
+ OnModuleInit,
30
+ OnModuleDestroy,
31
+ OnApplicationBootstrap,
32
+ OnApplicationShutdown,
33
+ } from '@dangao/bun-server';
34
+
35
+ @Injectable()
36
+ class DatabaseService implements OnModuleInit, OnModuleDestroy {
37
+ private connected = false;
38
+
39
+ public async onModuleInit(): Promise<void> {
40
+ console.log('[DatabaseService] 正在连接数据库...');
41
+ await new Promise((resolve) => setTimeout(resolve, 100));
42
+ this.connected = true;
43
+ console.log('[DatabaseService] 已连接');
44
+ }
45
+
46
+ public async onModuleDestroy(): Promise<void> {
47
+ console.log('[DatabaseService] 正在关闭数据库连接...');
48
+ this.connected = false;
49
+ console.log('[DatabaseService] 已断开');
50
+ }
51
+
52
+ public isConnected(): boolean {
53
+ return this.connected;
54
+ }
55
+ }
56
+
57
+ @Injectable()
58
+ class AppService implements OnApplicationBootstrap, OnApplicationShutdown {
59
+ public onApplicationBootstrap(): void {
60
+ console.log('[AppService] 应用已启动');
61
+ }
62
+
63
+ public onApplicationShutdown(signal?: string): void {
64
+ console.log(`[AppService] 应用正在关闭 (signal: ${signal ?? 'none'})`);
65
+ }
66
+ }
67
+
68
+ @Controller('/api')
69
+ class AppController {
70
+ @GET('/status')
71
+ public status(): object {
72
+ return { status: 'running', timestamp: Date.now() };
73
+ }
74
+ }
75
+
76
+ @Module({
77
+ controllers: [AppController],
78
+ providers: [DatabaseService, AppService],
79
+ })
80
+ class AppModule {}
81
+
82
+ const app = new Application({ port: 3000 });
83
+ app.registerModule(AppModule);
84
+ await app.listen();
85
+ ```
86
+
87
+ 按 Ctrl+C 触发关闭时,将依次执行 `onApplicationShutdown` 和 `onModuleDestroy`。
@@ -12,11 +12,6 @@
12
12
  "emitDecoratorMetadata": true
13
13
  }
14
14
  ```
15
- - 在应用入口(如 `main.ts`)导入一次 `reflect-metadata`:
16
- ```ts
17
- import 'reflect-metadata';
18
- ```
19
-
20
15
  ## 2. 路由与控制器
21
16
 
22
17
  - 将旧的函数式注册迁移到装饰器:
@@ -0,0 +1,119 @@
1
+ # 测试模块
2
+
3
+ Bun Server 提供 `TestingModule` 用于单元测试和集成测试,支持 provider 覆盖和 HTTP 客户端模拟。
4
+
5
+ ## Test.createTestingModule()
6
+
7
+ 使用 `Test.createTestingModule()` 创建测试模块,传入与 `@Module()` 相同的元数据:
8
+
9
+ ```ts
10
+ import { Test, Controller, GET } from '@dangao/bun-server';
11
+
12
+ const module = await Test.createTestingModule({
13
+ controllers: [UserController],
14
+ providers: [UserService],
15
+ imports: [ConfigModule.forRoot({ defaultConfig: {} })],
16
+ }).compile();
17
+ ```
18
+
19
+ ## Provider 覆盖
20
+
21
+ 通过 `overrideProvider()` 链式调用覆盖依赖,支持三种方式:
22
+
23
+ - **useValue(value)**:使用固定值覆盖
24
+ - **useClass(cls)**:使用替代类覆盖
25
+ - **useFactory(factory)**:使用工厂函数覆盖
26
+
27
+ ```ts
28
+ const mockService = { find: () => ({ id: '1', name: 'Mock' }) };
29
+
30
+ const module = await Test.createTestingModule({
31
+ controllers: [UserController],
32
+ providers: [UserService],
33
+ })
34
+ .overrideProvider(UserService)
35
+ .useValue(mockService)
36
+ .compile();
37
+ ```
38
+
39
+ ## TestingModule 方法
40
+
41
+ - **get(token)**:从 DI 容器获取 provider 实例
42
+ - **createApplication(options?)**:创建 `Application` 实例并注册所有 providers、controllers
43
+ - **createHttpClient(options?)**:创建 HTTP 测试客户端,自动启动应用并返回 `TestHttpClient`
44
+
45
+ ```ts
46
+ const service = module.get(UserService);
47
+ const app = module.createApplication();
48
+ const client = await module.createHttpClient();
49
+ ```
50
+
51
+ ## TestHttpClient
52
+
53
+ `createHttpClient()` 返回的客户端提供以下方法:
54
+
55
+ - **get(path, options?)**:GET 请求
56
+ - **post(path, options?)**:POST 请求
57
+ - **put(path, options?)**:PUT 请求
58
+ - **delete(path, options?)**:DELETE 请求
59
+ - **patch(path, options?)**:PATCH 请求
60
+ - **close()**:关闭测试服务器
61
+
62
+ `options` 支持 `headers`、`body`、`query`。响应对象包含 `status`、`headers`、`body`、`text`、`ok`。
63
+
64
+ ## 配合 bun:test 使用
65
+
66
+ ```ts
67
+ import { describe, test, expect } from 'bun:test';
68
+ import { Test, Controller, GET, Injectable } from '@dangao/bun-server';
69
+
70
+ @Injectable()
71
+ class Greeter {
72
+ public greet(name: string): string {
73
+ return `Hello, ${name}!`;
74
+ }
75
+ }
76
+
77
+ @Controller('/api')
78
+ class GreetController {
79
+ public constructor(private readonly greeter: Greeter) {}
80
+
81
+ @GET('/greet')
82
+ public greet(): object {
83
+ return { message: this.greeter.greet('World') };
84
+ }
85
+ }
86
+
87
+ describe('GreetController', () => {
88
+ test('应返回问候消息', async () => {
89
+ const module = await Test.createTestingModule({
90
+ controllers: [GreetController],
91
+ providers: [Greeter],
92
+ }).compile();
93
+
94
+ const client = await module.createHttpClient();
95
+ const res = await client.get('/api/greet');
96
+ await client.close();
97
+
98
+ expect(res.status).toBe(200);
99
+ expect(res.body).toEqual({ message: 'Hello, World!' });
100
+ });
101
+
102
+ test('可使用 mock 覆盖 provider', async () => {
103
+ const mockGreeter = { greet: (n: string) => `Mock: ${n}` };
104
+ const module = await Test.createTestingModule({
105
+ controllers: [GreetController],
106
+ providers: [Greeter],
107
+ })
108
+ .overrideProvider(Greeter)
109
+ .useValue(mockGreeter)
110
+ .compile();
111
+
112
+ const client = await module.createHttpClient();
113
+ const res = await client.get('/api/greet');
114
+ await client.close();
115
+
116
+ expect(res.body).toEqual({ message: 'Mock: World' });
117
+ });
118
+ });
119
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dangao/bun-server",
3
- "version": "1.9.0",
3
+ "version": "1.12.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "files": [
27
27
  "dist",
28
- "readme.md",
28
+ "README.md",
29
29
  "LICENSE",
30
30
  "src",
31
31
  "tests",
@@ -38,7 +38,7 @@
38
38
  "url": "https://github.com/dangaogit/bun-server/issues"
39
39
  },
40
40
  "engines": {
41
- "bun": ">=1.3.3"
41
+ "bun": ">=1.3.10"
42
42
  },
43
43
  "scripts": {
44
44
  "dev": "bun --watch src/index.ts",
@@ -58,6 +58,6 @@
58
58
  "dependencies": {
59
59
  "reflect-metadata": "^0.2.2",
60
60
  "@dangao/logsmith": "0.1.1",
61
- "@dangao/nacos-client": "0.1.0"
61
+ "@dangao/nacos-client": "0.1.1"
62
62
  }
63
63
  }
@@ -1,4 +1,5 @@
1
1
  import { Module, MODULE_METADATA_KEY, type ModuleProvider } from '../di/module';
2
+ import { type AsyncModuleOptions, registerAsyncProviders } from '../di/async-module';
2
3
 
3
4
  import { CacheService } from './service';
4
5
  import {
@@ -86,6 +87,30 @@ export class CacheModule {
86
87
  return CacheModule;
87
88
  }
88
89
 
90
+ /**
91
+ * 异步创建缓存模块
92
+ * @param asyncOptions - 异步配置选项
93
+ */
94
+ public static forRootAsync(
95
+ asyncOptions: AsyncModuleOptions<CacheModuleOptions>,
96
+ ): typeof CacheModule {
97
+ const tokenMap = new Map<symbol, (config: CacheModuleOptions) => unknown>();
98
+ tokenMap.set(CACHE_SERVICE_TOKEN, (config) => {
99
+ const resolvedConfig: CacheModuleOptions = {
100
+ ...config,
101
+ store: config.store ?? new MemoryCacheStore({ cleanupInterval: 60000 }),
102
+ };
103
+ return new CacheService(resolvedConfig);
104
+ });
105
+ tokenMap.set(CACHE_OPTIONS_TOKEN, (config) => config);
106
+
107
+ return registerAsyncProviders(
108
+ CacheModule,
109
+ asyncOptions,
110
+ tokenMap,
111
+ ) as typeof CacheModule;
112
+ }
113
+
89
114
  /**
90
115
  * 获取缓存后处理器
91
116
  * 用于在应用启动时注册到 DI 容器
@@ -0,0 +1,36 @@
1
+ import { RouteRegistry } from '../router/registry';
2
+ import type { RouteManifest, RouteManifestEntry } from './types';
3
+
4
+ /**
5
+ * 客户端清单生成器
6
+ * 从 RouteRegistry 提取路由元数据,生成路由清单
7
+ */
8
+ export class ClientGenerator {
9
+ /**
10
+ * 从当前 RouteRegistry 生成路由清单
11
+ */
12
+ public static generate(): RouteManifest {
13
+ const registry = RouteRegistry.getInstance();
14
+ const router = registry.getRouter();
15
+ const routes = router.getRoutes();
16
+ const entries: RouteManifestEntry[] = [];
17
+
18
+ for (const route of routes) {
19
+ entries.push({
20
+ method: route.method,
21
+ path: route.path,
22
+ controllerName: route.controllerClass?.name ?? 'unknown',
23
+ methodName: route.methodName ?? 'unknown',
24
+ });
25
+ }
26
+
27
+ return { routes: entries };
28
+ }
29
+
30
+ /**
31
+ * 生成清单的 JSON 字符串
32
+ */
33
+ public static generateJSON(): string {
34
+ return JSON.stringify(ClientGenerator.generate(), null, 2);
35
+ }
36
+ }
@@ -0,0 +1,8 @@
1
+ export { ClientGenerator } from './generator';
2
+ export { createClient } from './runtime';
3
+ export type {
4
+ RouteManifest,
5
+ RouteManifestEntry,
6
+ ClientConfig,
7
+ ClientRequestOptions,
8
+ } from './types';
@@ -0,0 +1,101 @@
1
+ import type { ClientConfig, ClientRequestOptions, RouteManifest } from './types';
2
+
3
+ /**
4
+ * 将路径中的 :param 占位符替换为实际值
5
+ */
6
+ function buildUrl(
7
+ baseUrl: string,
8
+ path: string,
9
+ options?: ClientRequestOptions,
10
+ ): string {
11
+ let resolvedPath = path;
12
+
13
+ if (options?.params) {
14
+ for (const [key, value] of Object.entries(options.params)) {
15
+ resolvedPath = resolvedPath.replace(`:${key}`, encodeURIComponent(value));
16
+ }
17
+ }
18
+
19
+ let url = `${baseUrl}${resolvedPath}`;
20
+
21
+ if (options?.query) {
22
+ const params = new URLSearchParams(options.query);
23
+ url += `?${params.toString()}`;
24
+ }
25
+
26
+ return url;
27
+ }
28
+
29
+ /**
30
+ * 从路由清单创建类型安全的 API 客户端
31
+ *
32
+ * 客户端结构按控制器名称(去除 Controller 后缀,转小写)分组,
33
+ * 每个方法名直接映射为函数调用。
34
+ *
35
+ * 示例:
36
+ * ```
37
+ * const client = createClient(manifest, { baseUrl: 'http://localhost:3000' });
38
+ * const result = await client.user.getUser({ params: { id: '1' } });
39
+ * ```
40
+ */
41
+ export function createClient(
42
+ manifest: RouteManifest,
43
+ config: ClientConfig,
44
+ ): Record<string, Record<string, (options?: ClientRequestOptions) => Promise<unknown>>> {
45
+ const fetchFn = config.fetch ?? globalThis.fetch;
46
+ const groups = new Map<string, Map<string, { method: string; path: string }>>();
47
+
48
+ for (const route of manifest.routes) {
49
+ const groupName = route.controllerName
50
+ .replace(/Controller$/i, '')
51
+ .replace(/^./, (c) => c.toLowerCase());
52
+
53
+ if (!groups.has(groupName)) {
54
+ groups.set(groupName, new Map());
55
+ }
56
+ groups.get(groupName)!.set(route.methodName, {
57
+ method: route.method,
58
+ path: route.path,
59
+ });
60
+ }
61
+
62
+ const client: Record<string, Record<string, (options?: ClientRequestOptions) => Promise<unknown>>> = {};
63
+
64
+ for (const [groupName, methods] of groups) {
65
+ const group: Record<string, (options?: ClientRequestOptions) => Promise<unknown>> = {};
66
+
67
+ for (const [methodName, routeInfo] of methods) {
68
+ group[methodName] = async (options?: ClientRequestOptions) => {
69
+ const url = buildUrl(config.baseUrl, routeInfo.path, options);
70
+
71
+ const fetchOptions: RequestInit = {
72
+ method: routeInfo.method,
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ ...(config.headers || {}),
76
+ ...(options?.headers || {}),
77
+ },
78
+ };
79
+
80
+ if (options?.body && routeInfo.method !== 'GET') {
81
+ fetchOptions.body = typeof options.body === 'string'
82
+ ? options.body
83
+ : JSON.stringify(options.body);
84
+ }
85
+
86
+ const response = await fetchFn(url, fetchOptions);
87
+ const text = await response.text();
88
+
89
+ try {
90
+ return JSON.parse(text);
91
+ } catch {
92
+ return text;
93
+ }
94
+ };
95
+ }
96
+
97
+ client[groupName] = group;
98
+ }
99
+
100
+ return client;
101
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 路由清单中的单个端点描述
3
+ */
4
+ export interface RouteManifestEntry {
5
+ method: string;
6
+ path: string;
7
+ controllerName: string;
8
+ methodName: string;
9
+ }
10
+
11
+ /**
12
+ * 完整的路由清单
13
+ */
14
+ export interface RouteManifest {
15
+ routes: RouteManifestEntry[];
16
+ }
17
+
18
+ /**
19
+ * 客户端请求选项
20
+ */
21
+ export interface ClientRequestOptions {
22
+ params?: Record<string, string>;
23
+ query?: Record<string, string>;
24
+ body?: unknown;
25
+ headers?: Record<string, string>;
26
+ }
27
+
28
+ /**
29
+ * 客户端配置
30
+ */
31
+ export interface ClientConfig {
32
+ baseUrl: string;
33
+ headers?: Record<string, string>;
34
+ /**
35
+ * 自定义 fetch 实现(可选,用于测试)
36
+ */
37
+ fetch?: typeof fetch;
38
+ }
@@ -2,6 +2,7 @@ import { Module, MODULE_METADATA_KEY, type ModuleProvider } from '../di/module';
2
2
  import { CONFIG_CENTER_TOKEN } from '../microservice/config-center/types';
3
3
  import type { ConfigCenter } from '../microservice/config-center/types';
4
4
  import { ControllerRegistry } from '../controller/controller';
5
+ import { type AsyncModuleOptions, registerAsyncProviders } from '../di/async-module';
5
6
 
6
7
  import { ConfigService } from './service';
7
8
  import { CONFIG_SERVICE_TOKEN, type ConfigModuleOptions } from './types';
@@ -70,6 +71,47 @@ export class ConfigModule {
70
71
  return ConfigModule;
71
72
  }
72
73
 
74
+ /**
75
+ * 异步创建配置模块
76
+ * 支持 configFiles 选项从 .json / .jsonc / .json5 文件异步加载配置
77
+ * @param asyncOptions - 异步配置选项
78
+ */
79
+ public static forRootAsync(
80
+ asyncOptions: AsyncModuleOptions<ConfigModuleOptions>,
81
+ ): typeof ConfigModule {
82
+ const tokenMap = new Map<symbol, (config: ConfigModuleOptions) => unknown | Promise<unknown>>();
83
+ tokenMap.set(CONFIG_SERVICE_TOKEN, async (options) => {
84
+ const env = ConfigModule.snapshotEnv();
85
+ const defaultConfig = (options.defaultConfig ?? {}) as Record<string, unknown>;
86
+
87
+ // 从配置文件加载(按数组顺序,后者覆盖前者)
88
+ let fileConfig: Record<string, unknown> = {};
89
+ if (options.configFiles?.length) {
90
+ const fileConfigs = await Promise.all(
91
+ options.configFiles.map((f) => ConfigService.loadConfigFile(f)),
92
+ );
93
+ for (const fc of fileConfigs) {
94
+ fileConfig = { ...fileConfig, ...fc };
95
+ }
96
+ }
97
+
98
+ const loadedConfig = (options.load ? options.load(env) : {}) as Record<string, unknown>;
99
+
100
+ // 优先级:env load > configFiles > defaultConfig
101
+ const mergedConfig = { ...defaultConfig, ...fileConfig, ...loadedConfig };
102
+ if (options.validate) {
103
+ options.validate(mergedConfig);
104
+ }
105
+ return new ConfigService(mergedConfig, options.namespace);
106
+ });
107
+
108
+ return registerAsyncProviders(
109
+ ConfigModule,
110
+ asyncOptions,
111
+ tokenMap,
112
+ ) as typeof ConfigModule;
113
+ }
114
+
73
115
  /**
74
116
  * 获取环境变量快照
75
117
  * 方便测试和未来扩展(如 .env 文件解析)
@@ -168,12 +210,10 @@ export class ConfigModule {
168
210
  configInfo.namespaceId,
169
211
  )
170
212
  .then((result) => {
171
- // 解析配置内容(支持 JSON)
172
213
  try {
173
- const parsed = JSON.parse(result.content);
214
+ const parsed = ConfigService.parseConfigContent(result.content);
174
215
  ConfigModule.setValueByPath(configMap, configPath, parsed);
175
216
  } catch {
176
- // 如果不是 JSON,直接使用字符串值
177
217
  ConfigModule.setValueByPath(configMap, configPath, result.content);
178
218
  }
179
219
  })
@@ -220,7 +260,7 @@ export class ConfigModule {
220
260
  // 解析配置内容
221
261
  let parsedValue: unknown;
222
262
  try {
223
- parsedValue = JSON.parse(result.content);
263
+ parsedValue = ConfigService.parseConfigContent(result.content);
224
264
  } catch {
225
265
  parsedValue = result.content;
226
266
  }
@@ -3,6 +3,7 @@ export { ConfigService } from './service';
3
3
  export {
4
4
  CONFIG_SERVICE_TOKEN,
5
5
  type ConfigModuleOptions,
6
+ type ConfigFileFormat,
6
7
  } from './types';
7
8
 
8
9
 
@@ -1,8 +1,58 @@
1
+ import type { ConfigFileFormat } from './types';
2
+
1
3
  /**
2
4
  * 配置服务
3
5
  * 提供类型安全的配置访问能力
4
6
  */
5
7
  export class ConfigService<TConfig extends Record<string, unknown> = Record<string, unknown>> {
8
+ /**
9
+ * 解析配置内容,按 JSON -> JSONC -> JSON5 顺序自动尝试
10
+ * 利用 Bun 1.3.6+ 的 Bun.JSONC 和 Bun 1.3.7+ 的 Bun.JSON5
11
+ * @param content - 配置文本内容
12
+ * @param format - 强制指定格式(可选),省略则自动检测
13
+ */
14
+ public static parseConfigContent(content: string, format?: ConfigFileFormat): unknown {
15
+ if (format === 'jsonc') {
16
+ return Bun.JSONC.parse(content);
17
+ }
18
+ if (format === 'json5') {
19
+ return Bun.JSON5.parse(content);
20
+ }
21
+ if (format === 'json') {
22
+ return JSON.parse(content);
23
+ }
24
+
25
+ try {
26
+ return JSON.parse(content);
27
+ } catch {
28
+ try {
29
+ return Bun.JSONC.parse(content);
30
+ } catch {
31
+ return Bun.JSON5.parse(content);
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * 从文件加载配置,根据扩展名自动选择解析器
38
+ * @param filePath - 配置文件路径(.json / .jsonc / .json5)
39
+ */
40
+ public static async loadConfigFile(filePath: string): Promise<Record<string, unknown>> {
41
+ const file = Bun.file(filePath);
42
+ const content = await file.text();
43
+
44
+ let format: ConfigFileFormat | undefined;
45
+ if (filePath.endsWith('.json5')) {
46
+ format = 'json5';
47
+ } else if (filePath.endsWith('.jsonc')) {
48
+ format = 'jsonc';
49
+ } else if (filePath.endsWith('.json')) {
50
+ format = 'json';
51
+ }
52
+
53
+ return ConfigService.parseConfigContent(content, format) as Record<string, unknown>;
54
+ }
55
+
6
56
  private config: TConfig;
7
57
  private readonly namespace?: string;
8
58
  private configUpdateListeners: Array<(config: TConfig) => void> = [];
@@ -1,3 +1,8 @@
1
+ /**
2
+ * 配置文件解析格式
3
+ */
4
+ export type ConfigFileFormat = 'json' | 'jsonc' | 'json5';
5
+
1
6
  export interface ConfigModuleOptions<TConfig extends Record<string, unknown> = Record<string, unknown>> {
2
7
  /**
3
8
  * 默认配置对象(最低优先级)
@@ -10,6 +15,13 @@ export interface ConfigModuleOptions<TConfig extends Record<string, unknown> = R
10
15
  */
11
16
  load?: (env: Record<string, string | undefined>) => Partial<TConfig>;
12
17
 
18
+ /**
19
+ * 配置文件路径列表(支持 .json / .jsonc / .json5 格式)
20
+ * 优先级:configFiles > defaultConfig,按数组顺序后者覆盖前者
21
+ * 仅在 forRootAsync 中生效(文件读取为异步操作)
22
+ */
23
+ configFiles?: string[];
24
+
13
25
  /**
14
26
  * 配置验证函数(可抛出错误)
15
27
  * 可用于集成 class-validator 风格的校验逻辑