@dangao/bun-server 1.0.0 → 1.0.3

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 (200) hide show
  1. package/package.json +4 -2
  2. package/readme.md +163 -2
  3. package/src/auth/controller.ts +148 -0
  4. package/src/auth/decorators.ts +81 -0
  5. package/src/auth/index.ts +12 -0
  6. package/src/auth/jwt.ts +169 -0
  7. package/src/auth/oauth2.ts +244 -0
  8. package/src/auth/types.ts +248 -0
  9. package/src/cache/cache-module.ts +67 -0
  10. package/src/cache/decorators.ts +202 -0
  11. package/src/cache/index.ts +27 -0
  12. package/src/cache/service.ts +151 -0
  13. package/src/cache/types.ts +420 -0
  14. package/src/config/config-module.ts +76 -0
  15. package/src/config/index.ts +8 -0
  16. package/src/config/service.ts +93 -0
  17. package/src/config/types.ts +27 -0
  18. package/src/controller/controller.ts +251 -0
  19. package/src/controller/decorators.ts +84 -0
  20. package/src/controller/index.ts +7 -0
  21. package/src/controller/metadata.ts +27 -0
  22. package/src/controller/param-binder.ts +157 -0
  23. package/src/core/application.ts +233 -0
  24. package/src/core/context.ts +228 -0
  25. package/src/core/index.ts +4 -0
  26. package/src/core/server.ts +128 -0
  27. package/src/core/types.ts +2 -0
  28. package/src/database/connection-manager.ts +239 -0
  29. package/src/database/connection-pool.ts +322 -0
  30. package/src/database/database-extension.ts +62 -0
  31. package/src/database/database-module.ts +115 -0
  32. package/src/database/health-indicator.ts +51 -0
  33. package/src/database/index.ts +47 -0
  34. package/src/database/orm/decorators.ts +155 -0
  35. package/src/database/orm/drizzle-repository.ts +39 -0
  36. package/src/database/orm/index.ts +23 -0
  37. package/src/database/orm/repository-decorator.ts +39 -0
  38. package/src/database/orm/repository.ts +103 -0
  39. package/src/database/orm/service.ts +49 -0
  40. package/src/database/orm/transaction-decorator.ts +45 -0
  41. package/src/database/orm/transaction-interceptor.ts +243 -0
  42. package/src/database/orm/transaction-manager.ts +276 -0
  43. package/src/database/orm/transaction-types.ts +140 -0
  44. package/src/database/orm/types.ts +99 -0
  45. package/src/database/service.ts +221 -0
  46. package/src/database/types.ts +171 -0
  47. package/src/di/container.ts +398 -0
  48. package/src/di/decorators.ts +228 -0
  49. package/src/di/index.ts +4 -0
  50. package/src/di/module-registry.ts +188 -0
  51. package/src/di/module.ts +65 -0
  52. package/src/di/types.ts +67 -0
  53. package/src/error/error-codes.ts +222 -0
  54. package/src/error/filter.ts +43 -0
  55. package/src/error/handler.ts +66 -0
  56. package/src/error/http-exception.ts +115 -0
  57. package/src/error/i18n.ts +217 -0
  58. package/src/error/index.ts +16 -0
  59. package/src/extensions/index.ts +5 -0
  60. package/src/extensions/logger-extension.ts +31 -0
  61. package/src/extensions/logger-module.ts +69 -0
  62. package/src/extensions/types.ts +14 -0
  63. package/src/files/index.ts +5 -0
  64. package/src/files/static-middleware.ts +53 -0
  65. package/src/files/storage.ts +67 -0
  66. package/src/files/types.ts +33 -0
  67. package/src/files/upload-middleware.ts +45 -0
  68. package/src/health/controller.ts +76 -0
  69. package/src/health/health-module.ts +51 -0
  70. package/src/health/index.ts +12 -0
  71. package/src/health/types.ts +28 -0
  72. package/src/index.ts +270 -0
  73. package/src/metrics/collector.ts +209 -0
  74. package/src/metrics/controller.ts +40 -0
  75. package/src/metrics/index.ts +15 -0
  76. package/src/metrics/metrics-module.ts +58 -0
  77. package/src/metrics/middleware.ts +46 -0
  78. package/src/metrics/prometheus.ts +79 -0
  79. package/src/metrics/types.ts +103 -0
  80. package/src/middleware/builtin/cors.ts +60 -0
  81. package/src/middleware/builtin/error-handler.ts +90 -0
  82. package/src/middleware/builtin/file-upload.ts +42 -0
  83. package/src/middleware/builtin/index.ts +14 -0
  84. package/src/middleware/builtin/logger.ts +91 -0
  85. package/src/middleware/builtin/rate-limit.ts +252 -0
  86. package/src/middleware/builtin/static-file.ts +88 -0
  87. package/src/middleware/decorators.ts +91 -0
  88. package/src/middleware/index.ts +11 -0
  89. package/src/middleware/middleware.ts +13 -0
  90. package/src/middleware/pipeline.ts +93 -0
  91. package/src/queue/decorators.ts +110 -0
  92. package/src/queue/index.ts +26 -0
  93. package/src/queue/queue-module.ts +64 -0
  94. package/src/queue/service.ts +302 -0
  95. package/src/queue/types.ts +341 -0
  96. package/src/request/body-parser.ts +133 -0
  97. package/src/request/file-handler.ts +46 -0
  98. package/src/request/index.ts +5 -0
  99. package/src/request/request.ts +107 -0
  100. package/src/request/response.ts +150 -0
  101. package/src/router/decorators.ts +122 -0
  102. package/src/router/index.ts +6 -0
  103. package/src/router/registry.ts +98 -0
  104. package/src/router/route.ts +140 -0
  105. package/src/router/router.ts +241 -0
  106. package/src/router/types.ts +27 -0
  107. package/src/security/access-decision-manager.ts +34 -0
  108. package/src/security/authentication-manager.ts +47 -0
  109. package/src/security/context.ts +92 -0
  110. package/src/security/filter.ts +162 -0
  111. package/src/security/index.ts +8 -0
  112. package/src/security/providers/index.ts +3 -0
  113. package/src/security/providers/jwt-provider.ts +60 -0
  114. package/src/security/providers/oauth2-provider.ts +70 -0
  115. package/src/security/security-module.ts +145 -0
  116. package/src/security/types.ts +165 -0
  117. package/src/session/decorators.ts +45 -0
  118. package/src/session/index.ts +19 -0
  119. package/src/session/middleware.ts +143 -0
  120. package/src/session/service.ts +218 -0
  121. package/src/session/session-module.ts +69 -0
  122. package/src/session/types.ts +373 -0
  123. package/src/swagger/decorators.ts +133 -0
  124. package/src/swagger/generator.ts +234 -0
  125. package/src/swagger/index.ts +7 -0
  126. package/src/swagger/swagger-extension.ts +41 -0
  127. package/src/swagger/swagger-module.ts +83 -0
  128. package/src/swagger/types.ts +188 -0
  129. package/src/swagger/ui.ts +98 -0
  130. package/src/testing/harness.ts +96 -0
  131. package/src/validation/decorators.ts +95 -0
  132. package/src/validation/errors.ts +28 -0
  133. package/src/validation/index.ts +14 -0
  134. package/src/validation/types.ts +35 -0
  135. package/src/validation/validator.ts +63 -0
  136. package/src/websocket/decorators.ts +51 -0
  137. package/src/websocket/index.ts +12 -0
  138. package/src/websocket/registry.ts +133 -0
  139. package/tests/cache/cache-module.test.ts +212 -0
  140. package/tests/config/config-module.test.ts +151 -0
  141. package/tests/controller/controller.test.ts +189 -0
  142. package/tests/core/application.test.ts +57 -0
  143. package/tests/core/context-body.test.ts +44 -0
  144. package/tests/core/context.test.ts +86 -0
  145. package/tests/core/edge-cases.test.ts +432 -0
  146. package/tests/database/database-module.test.ts +385 -0
  147. package/tests/database/orm.test.ts +164 -0
  148. package/tests/database/postgres-mysql-integration.test.ts +395 -0
  149. package/tests/database/transaction.test.ts +238 -0
  150. package/tests/di/container.test.ts +264 -0
  151. package/tests/di/module.test.ts +128 -0
  152. package/tests/error/error-codes.test.ts +121 -0
  153. package/tests/error/error-handler.test.ts +68 -0
  154. package/tests/error/error-handling.test.ts +254 -0
  155. package/tests/error/http-exception.test.ts +37 -0
  156. package/tests/error/i18n-integration.test.ts +175 -0
  157. package/tests/extensions/logger-extension.test.ts +40 -0
  158. package/tests/files/static-middleware.test.ts +67 -0
  159. package/tests/files/upload-middleware.test.ts +43 -0
  160. package/tests/health/health-module.test.ts +116 -0
  161. package/tests/integration/application-router.test.ts +85 -0
  162. package/tests/integration/body-parsing.test.ts +88 -0
  163. package/tests/integration/cache-e2e.test.ts +114 -0
  164. package/tests/integration/oauth2-e2e.test.ts +615 -0
  165. package/tests/integration/session-e2e.test.ts +207 -0
  166. package/tests/metrics/metrics-module.test.ts +178 -0
  167. package/tests/middleware/builtin.test.ts +206 -0
  168. package/tests/middleware/file-upload.test.ts +41 -0
  169. package/tests/middleware/middleware.test.ts +120 -0
  170. package/tests/middleware/pipeline.test.ts +72 -0
  171. package/tests/middleware/rate-limit.test.ts +314 -0
  172. package/tests/middleware/static-file.test.ts +62 -0
  173. package/tests/perf/harness.test.ts +48 -0
  174. package/tests/perf/optimization.test.ts +183 -0
  175. package/tests/perf/regression.test.ts +120 -0
  176. package/tests/queue/queue-module.test.ts +217 -0
  177. package/tests/request/body-parser.test.ts +96 -0
  178. package/tests/request/response.test.ts +99 -0
  179. package/tests/router/decorators.test.ts +48 -0
  180. package/tests/router/registry.test.ts +51 -0
  181. package/tests/router/route.test.ts +71 -0
  182. package/tests/router/router-normalization.test.ts +106 -0
  183. package/tests/router/router.test.ts +133 -0
  184. package/tests/security/access-decision-manager.test.ts +84 -0
  185. package/tests/security/authentication-manager.test.ts +81 -0
  186. package/tests/security/context.test.ts +302 -0
  187. package/tests/security/filter.test.ts +225 -0
  188. package/tests/security/jwt-provider.test.ts +106 -0
  189. package/tests/security/oauth2-provider.test.ts +269 -0
  190. package/tests/security/security-module.test.ts +143 -0
  191. package/tests/session/session-module.test.ts +307 -0
  192. package/tests/stress/di-stress.test.ts +30 -0
  193. package/tests/swagger/decorators.test.ts +153 -0
  194. package/tests/swagger/generator.test.ts +202 -0
  195. package/tests/swagger/swagger-extension.test.ts +72 -0
  196. package/tests/swagger/swagger-module.test.ts +79 -0
  197. package/tests/utils/test-port.ts +10 -0
  198. package/tests/validation/controller-validation.test.ts +64 -0
  199. package/tests/validation/validation.test.ts +42 -0
  200. package/tests/websocket/gateway.test.ts +68 -0
@@ -0,0 +1,420 @@
1
+ /**
2
+ * 缓存存储接口
3
+ */
4
+ export interface CacheStore {
5
+ /**
6
+ * 获取缓存值
7
+ * @param key - 缓存键
8
+ * @returns 缓存值,如果不存在则返回 undefined
9
+ */
10
+ get<T = unknown>(key: string): Promise<T | undefined>;
11
+
12
+ /**
13
+ * 设置缓存值
14
+ * @param key - 缓存键
15
+ * @param value - 缓存值
16
+ * @param ttl - 过期时间(毫秒),0 表示永不过期
17
+ * @returns 是否设置成功
18
+ */
19
+ set<T = unknown>(key: string, value: T, ttl?: number): Promise<boolean>;
20
+
21
+ /**
22
+ * 删除缓存
23
+ * @param key - 缓存键
24
+ * @returns 是否删除成功
25
+ */
26
+ delete(key: string): Promise<boolean>;
27
+
28
+ /**
29
+ * 检查缓存是否存在
30
+ * @param key - 缓存键
31
+ * @returns 是否存在
32
+ */
33
+ has(key: string): Promise<boolean>;
34
+
35
+ /**
36
+ * 清空所有缓存
37
+ * @returns 是否清空成功
38
+ */
39
+ clear(): Promise<boolean>;
40
+
41
+ /**
42
+ * 获取多个缓存值
43
+ * @param keys - 缓存键数组
44
+ * @returns 缓存值映射
45
+ */
46
+ getMany<T = unknown>(keys: string[]): Promise<Map<string, T>>;
47
+
48
+ /**
49
+ * 设置多个缓存值
50
+ * @param entries - 缓存条目数组
51
+ * @param ttl - 过期时间(毫秒),0 表示永不过期
52
+ * @returns 是否设置成功
53
+ */
54
+ setMany<T = unknown>(
55
+ entries: Array<{ key: string; value: T }>,
56
+ ttl?: number,
57
+ ): Promise<boolean>;
58
+
59
+ /**
60
+ * 删除多个缓存
61
+ * @param keys - 缓存键数组
62
+ * @returns 删除成功的键数组
63
+ */
64
+ deleteMany(keys: string[]): Promise<string[]>;
65
+ }
66
+
67
+ /**
68
+ * 内存缓存存储实现
69
+ */
70
+ export class MemoryCacheStore implements CacheStore {
71
+ private store: Map<
72
+ string,
73
+ { value: unknown; expiresAt?: number }
74
+ > = new Map();
75
+
76
+ private cleanupInterval?: ReturnType<typeof setInterval>;
77
+
78
+ public constructor(options?: { cleanupInterval?: number }) {
79
+ // 定期清理过期条目
80
+ if (options?.cleanupInterval !== undefined) {
81
+ this.cleanupInterval = setInterval(() => {
82
+ this.cleanup();
83
+ }, options.cleanupInterval);
84
+ }
85
+ }
86
+
87
+ public async get<T = unknown>(key: string): Promise<T | undefined> {
88
+ const entry = this.store.get(key);
89
+ if (!entry) {
90
+ return undefined;
91
+ }
92
+
93
+ // 检查是否过期
94
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
95
+ this.store.delete(key);
96
+ return undefined;
97
+ }
98
+
99
+ return entry.value as T;
100
+ }
101
+
102
+ public async set<T = unknown>(
103
+ key: string,
104
+ value: T,
105
+ ttl?: number,
106
+ ): Promise<boolean> {
107
+ const expiresAt = ttl && ttl > 0 ? Date.now() + ttl : undefined;
108
+ this.store.set(key, { value, expiresAt });
109
+ return true;
110
+ }
111
+
112
+ public async delete(key: string): Promise<boolean> {
113
+ return this.store.delete(key);
114
+ }
115
+
116
+ public async has(key: string): Promise<boolean> {
117
+ const entry = this.store.get(key);
118
+ if (!entry) {
119
+ return false;
120
+ }
121
+
122
+ // 检查是否过期
123
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
124
+ this.store.delete(key);
125
+ return false;
126
+ }
127
+
128
+ return true;
129
+ }
130
+
131
+ public async clear(): Promise<boolean> {
132
+ this.store.clear();
133
+ return true;
134
+ }
135
+
136
+ public async getMany<T = unknown>(
137
+ keys: string[],
138
+ ): Promise<Map<string, T>> {
139
+ const result = new Map<string, T>();
140
+ const now = Date.now();
141
+
142
+ for (const key of keys) {
143
+ const entry = this.store.get(key);
144
+ if (entry) {
145
+ // 检查是否过期
146
+ if (entry.expiresAt && now > entry.expiresAt) {
147
+ this.store.delete(key);
148
+ continue;
149
+ }
150
+ result.set(key, entry.value as T);
151
+ }
152
+ }
153
+
154
+ return result;
155
+ }
156
+
157
+ public async setMany<T = unknown>(
158
+ entries: Array<{ key: string; value: T }>,
159
+ ttl?: number,
160
+ ): Promise<boolean> {
161
+ const expiresAt = ttl && ttl > 0 ? Date.now() + ttl : undefined;
162
+
163
+ for (const { key, value } of entries) {
164
+ this.store.set(key, { value, expiresAt });
165
+ }
166
+
167
+ return true;
168
+ }
169
+
170
+ public async deleteMany(keys: string[]): Promise<string[]> {
171
+ const deleted: string[] = [];
172
+
173
+ for (const key of keys) {
174
+ if (this.store.delete(key)) {
175
+ deleted.push(key);
176
+ }
177
+ }
178
+
179
+ return deleted;
180
+ }
181
+
182
+ /**
183
+ * 清理过期条目
184
+ */
185
+ private cleanup(): void {
186
+ const now = Date.now();
187
+ for (const [key, entry] of this.store.entries()) {
188
+ if (entry.expiresAt && now > entry.expiresAt) {
189
+ this.store.delete(key);
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * 销毁存储,清理定时器
196
+ */
197
+ public destroy(): void {
198
+ if (this.cleanupInterval) {
199
+ clearInterval(this.cleanupInterval);
200
+ this.cleanupInterval = undefined;
201
+ }
202
+ this.store.clear();
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Redis 缓存存储实现(需要 redis 包)
208
+ */
209
+ export interface RedisCacheStoreOptions {
210
+ /**
211
+ * Redis 客户端(需要用户提供)
212
+ */
213
+ client: {
214
+ get(key: string): Promise<string | null>;
215
+ set(key: string, value: string, options?: { PX?: number }): Promise<void>;
216
+ del(key: string): Promise<void>;
217
+ exists(key: string): Promise<number>;
218
+ mget(keys: string[]): Promise<(string | null)[]>;
219
+ mset(entries: Array<{ key: string; value: string }>): Promise<void>;
220
+ flushdb(): Promise<void>;
221
+ };
222
+ /**
223
+ * 键前缀
224
+ * @default 'cache:'
225
+ */
226
+ keyPrefix?: string;
227
+ }
228
+
229
+ export class RedisCacheStore implements CacheStore {
230
+ private client: RedisCacheStoreOptions['client'];
231
+ private keyPrefix: string;
232
+
233
+ public constructor(options: RedisCacheStoreOptions) {
234
+ this.client = options.client;
235
+ this.keyPrefix = options.keyPrefix ?? 'cache:';
236
+ }
237
+
238
+ private getKey(key: string): string {
239
+ return `${this.keyPrefix}${key}`;
240
+ }
241
+
242
+ public async get<T = unknown>(key: string): Promise<T | undefined> {
243
+ const value = await this.client.get(this.getKey(key));
244
+ if (value === null) {
245
+ return undefined;
246
+ }
247
+ try {
248
+ return JSON.parse(value) as T;
249
+ } catch {
250
+ return undefined;
251
+ }
252
+ }
253
+
254
+ public async set<T = unknown>(
255
+ key: string,
256
+ value: T,
257
+ ttl?: number,
258
+ ): Promise<boolean> {
259
+ try {
260
+ const serialized = JSON.stringify(value);
261
+ if (ttl && ttl > 0) {
262
+ await this.client.set(this.getKey(key), serialized, { PX: ttl });
263
+ } else {
264
+ await this.client.set(this.getKey(key), serialized);
265
+ }
266
+ return true;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+
272
+ public async delete(key: string): Promise<boolean> {
273
+ try {
274
+ await this.client.del(this.getKey(key));
275
+ return true;
276
+ } catch {
277
+ return false;
278
+ }
279
+ }
280
+
281
+ public async has(key: string): Promise<boolean> {
282
+ try {
283
+ const result = await this.client.exists(this.getKey(key));
284
+ return result === 1;
285
+ } catch {
286
+ return false;
287
+ }
288
+ }
289
+
290
+ public async clear(): Promise<boolean> {
291
+ try {
292
+ await this.client.flushdb();
293
+ return true;
294
+ } catch {
295
+ return false;
296
+ }
297
+ }
298
+
299
+ public async getMany<T = unknown>(
300
+ keys: string[],
301
+ ): Promise<Map<string, T>> {
302
+ const result = new Map<string, T>();
303
+ if (keys.length === 0) {
304
+ return result;
305
+ }
306
+
307
+ try {
308
+ const prefixedKeys = keys.map((k) => this.getKey(k));
309
+ const values = await this.client.mget(prefixedKeys);
310
+
311
+ for (let i = 0; i < keys.length; i++) {
312
+ const value = values[i];
313
+ if (value !== null) {
314
+ try {
315
+ result.set(keys[i], JSON.parse(value) as T);
316
+ } catch {
317
+ // 忽略解析错误
318
+ }
319
+ }
320
+ }
321
+ } catch {
322
+ // 忽略错误,返回空 Map
323
+ }
324
+
325
+ return result;
326
+ }
327
+
328
+ public async setMany<T = unknown>(
329
+ entries: Array<{ key: string; value: T }>,
330
+ ttl?: number,
331
+ ): Promise<boolean> {
332
+ if (entries.length === 0) {
333
+ return true;
334
+ }
335
+
336
+ try {
337
+ const redisEntries = entries.map(({ key, value }) => ({
338
+ key: this.getKey(key),
339
+ value: JSON.stringify(value),
340
+ }));
341
+
342
+ await this.client.mset(redisEntries);
343
+
344
+ // 如果设置了 TTL,需要单独为每个键设置过期时间
345
+ // 注意:这里假设 Redis 客户端支持 PX 选项,如果不支持,需要单独调用 EXPIRE
346
+ if (ttl && ttl > 0) {
347
+ // 由于 mset 可能不支持批量设置 TTL,这里需要单独处理
348
+ // 实际实现可能需要根据具体的 Redis 客户端调整
349
+ for (const entry of redisEntries) {
350
+ await this.client.set(entry.key, entry.value, { PX: ttl });
351
+ }
352
+ }
353
+
354
+ return true;
355
+ } catch {
356
+ return false;
357
+ }
358
+ }
359
+
360
+ public async deleteMany(keys: string[]): Promise<string[]> {
361
+ if (keys.length === 0) {
362
+ return [];
363
+ }
364
+
365
+ try {
366
+ const prefixedKeys = keys.map((k) => this.getKey(k));
367
+ const deleted: string[] = [];
368
+
369
+ // 注意:这里假设 Redis 客户端支持批量删除
370
+ // 如果不支持,需要循环调用 del
371
+ for (const key of prefixedKeys) {
372
+ try {
373
+ await this.client.del(key);
374
+ deleted.push(key.replace(this.keyPrefix, ''));
375
+ } catch {
376
+ // 忽略单个删除错误
377
+ }
378
+ }
379
+
380
+ return deleted;
381
+ } catch {
382
+ return [];
383
+ }
384
+ }
385
+ }
386
+
387
+ /**
388
+ * CacheModule 配置选项
389
+ */
390
+ export interface CacheModuleOptions {
391
+ /**
392
+ * 缓存存储实现
393
+ * @default MemoryCacheStore
394
+ */
395
+ store?: CacheStore;
396
+
397
+ /**
398
+ * 默认 TTL(毫秒)
399
+ * @default 3600000 (1 小时)
400
+ */
401
+ defaultTtl?: number;
402
+
403
+ /**
404
+ * 键前缀
405
+ * @default ''
406
+ */
407
+ keyPrefix?: string;
408
+ }
409
+
410
+ /**
411
+ * CacheService Token
412
+ */
413
+ export const CACHE_SERVICE_TOKEN = Symbol(
414
+ '@dangao/bun-server:cache:service',
415
+ );
416
+
417
+ /**
418
+ * CacheModule Options Token
419
+ */
420
+ export const CACHE_OPTIONS_TOKEN = Symbol('@dangao/bun-server:cache:options');
@@ -0,0 +1,76 @@
1
+ import { Module, MODULE_METADATA_KEY, type ModuleProvider } from '../di/module';
2
+
3
+ import { ConfigService } from './service';
4
+ import { CONFIG_SERVICE_TOKEN, type ConfigModuleOptions } from './types';
5
+
6
+ @Module({
7
+ providers: [],
8
+ })
9
+ export class ConfigModule {
10
+ /**
11
+ * 创建配置模块
12
+ * @param options - 模块配置
13
+ */
14
+ public static forRoot(
15
+ options: ConfigModuleOptions = {},
16
+ ): typeof ConfigModule {
17
+ const providers: ModuleProvider[] = [];
18
+
19
+ const env = ConfigModule.snapshotEnv();
20
+ const defaultConfig = (options.defaultConfig ?? {}) as Record<
21
+ string,
22
+ unknown
23
+ >;
24
+ const loadedConfig =
25
+ (options.load ? options.load(env) : {}) as Record<string, unknown>;
26
+
27
+ // 默认配置 < 环境变量加载配置
28
+ const mergedConfig = {
29
+ ...defaultConfig,
30
+ ...loadedConfig,
31
+ } as Record<string, unknown>;
32
+
33
+ const service = new ConfigService(mergedConfig, options.namespace);
34
+
35
+ if (options.validate) {
36
+ options.validate(mergedConfig);
37
+ }
38
+
39
+ providers.push(
40
+ {
41
+ provide: CONFIG_SERVICE_TOKEN,
42
+ useValue: service,
43
+ },
44
+ ConfigService,
45
+ );
46
+
47
+ const existingMetadata =
48
+ Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule) || {};
49
+ const metadata = {
50
+ ...existingMetadata,
51
+ providers: [...(existingMetadata.providers || []), ...providers],
52
+ exports: [
53
+ ...(existingMetadata.exports || []),
54
+ CONFIG_SERVICE_TOKEN,
55
+ ConfigService,
56
+ ],
57
+ };
58
+ Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, ConfigModule);
59
+
60
+ return ConfigModule;
61
+ }
62
+
63
+ /**
64
+ * 获取环境变量快照
65
+ * 方便测试和未来扩展(如 .env 文件解析)
66
+ */
67
+ private static snapshotEnv(): Record<string, string | undefined> {
68
+ const env: Record<string, string | undefined> = {};
69
+ for (const [key, value] of Object.entries(process.env)) {
70
+ env[key] = value;
71
+ }
72
+ return env;
73
+ }
74
+ }
75
+
76
+
@@ -0,0 +1,8 @@
1
+ export { ConfigModule } from './config-module';
2
+ export { ConfigService } from './service';
3
+ export {
4
+ CONFIG_SERVICE_TOKEN,
5
+ type ConfigModuleOptions,
6
+ } from './types';
7
+
8
+
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 配置服务
3
+ * 提供类型安全的配置访问能力
4
+ */
5
+ export class ConfigService<TConfig extends Record<string, unknown> = Record<string, unknown>> {
6
+ private readonly config: TConfig;
7
+ private readonly namespace?: string;
8
+
9
+ public constructor(config: TConfig, namespace?: string) {
10
+ this.config = config;
11
+ this.namespace = namespace;
12
+ }
13
+
14
+ /**
15
+ * 获取完整配置对象
16
+ */
17
+ public getAll(): TConfig {
18
+ return this.config;
19
+ }
20
+
21
+ /**
22
+ * 获取配置值(支持点号路径)
23
+ * @param key - 配置键(如 "db.host")
24
+ * @param defaultValue - 默认值(可选)
25
+ */
26
+ public get<T = unknown>(key: string, defaultValue?: T): T | undefined {
27
+ const namespacedKey = this.applyNamespace(key);
28
+ const value = this.getValueByPath(this.config, namespacedKey);
29
+ if (value === undefined) {
30
+ return defaultValue;
31
+ }
32
+ return value as T;
33
+ }
34
+
35
+ /**
36
+ * 获取必需的配置值,如果不存在则抛出错误
37
+ * @param key - 配置键
38
+ */
39
+ public getRequired<T = unknown>(key: string): T {
40
+ const value = this.get<T>(key);
41
+ if (value === undefined) {
42
+ throw new Error(`Config value required for key: ${key}`);
43
+ }
44
+ return value;
45
+ }
46
+
47
+ /**
48
+ * 创建带命名空间的 ConfigService 视图
49
+ * @param namespace - 命名空间前缀
50
+ */
51
+ public withNamespace(namespace: string): ConfigService<TConfig> {
52
+ return new ConfigService<TConfig>(this.config, namespace);
53
+ }
54
+
55
+ private applyNamespace(key: string): string {
56
+ if (!this.namespace) {
57
+ return key;
58
+ }
59
+ if (!key) {
60
+ return this.namespace;
61
+ }
62
+ if (key.startsWith(this.namespace + '.')) {
63
+ return key;
64
+ }
65
+ return `${this.namespace}.${key}`;
66
+ }
67
+
68
+ private getValueByPath(
69
+ obj: Record<string, unknown>,
70
+ path: string,
71
+ ): unknown {
72
+ if (!path) {
73
+ return obj;
74
+ }
75
+ const segments = path.split('.');
76
+ let current: unknown = obj;
77
+
78
+ for (const segment of segments) {
79
+ if (
80
+ current === undefined ||
81
+ current === null ||
82
+ typeof current !== 'object'
83
+ ) {
84
+ return undefined;
85
+ }
86
+ current = (current as Record<string, unknown>)[segment];
87
+ }
88
+
89
+ return current;
90
+ }
91
+ }
92
+
93
+
@@ -0,0 +1,27 @@
1
+ export interface ConfigModuleOptions<TConfig extends Record<string, unknown> = Record<string, unknown>> {
2
+ /**
3
+ * 默认配置对象(最低优先级)
4
+ */
5
+ defaultConfig?: Partial<TConfig>;
6
+
7
+ /**
8
+ * 从环境变量加载配置
9
+ * @param env - process.env 快照
10
+ */
11
+ load?: (env: Record<string, string | undefined>) => Partial<TConfig>;
12
+
13
+ /**
14
+ * 配置验证函数(可抛出错误)
15
+ * 可用于集成 class-validator 风格的校验逻辑
16
+ */
17
+ validate?: (config: TConfig) => void;
18
+
19
+ /**
20
+ * 命名空间前缀,用于逻辑分组(可选)
21
+ */
22
+ namespace?: string;
23
+ }
24
+
25
+ export const CONFIG_SERVICE_TOKEN = Symbol('@dangao/bun-server:config:service');
26
+
27
+