@dangao/bun-server 3.1.0 → 3.3.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 (161) hide show
  1. package/dist/ai/types.d.ts +4 -0
  2. package/dist/ai/types.d.ts.map +1 -1
  3. package/dist/auth/types.d.ts +4 -0
  4. package/dist/auth/types.d.ts.map +1 -1
  5. package/dist/cache/interceptors.d.ts +3 -3
  6. package/dist/cache/interceptors.d.ts.map +1 -1
  7. package/dist/cache/service.d.ts +6 -6
  8. package/dist/cache/service.d.ts.map +1 -1
  9. package/dist/cache/types.d.ts +12 -12
  10. package/dist/cache/types.d.ts.map +1 -1
  11. package/dist/config/service.d.ts +6 -4
  12. package/dist/config/service.d.ts.map +1 -1
  13. package/dist/core/application.d.ts +4 -0
  14. package/dist/core/application.d.ts.map +1 -1
  15. package/dist/core/context.d.ts +4 -2
  16. package/dist/core/context.d.ts.map +1 -1
  17. package/dist/database/connection-manager.d.ts +2 -2
  18. package/dist/database/connection-manager.d.ts.map +1 -1
  19. package/dist/database/connection-pool.d.ts +4 -4
  20. package/dist/database/connection-pool.d.ts.map +1 -1
  21. package/dist/database/database-module.d.ts.map +1 -1
  22. package/dist/database/db-proxy.d.ts.map +1 -1
  23. package/dist/database/driver.d.ts +83 -0
  24. package/dist/database/driver.d.ts.map +1 -0
  25. package/dist/database/index.d.ts +2 -1
  26. package/dist/database/index.d.ts.map +1 -1
  27. package/dist/database/service.d.ts +0 -10
  28. package/dist/database/service.d.ts.map +1 -1
  29. package/dist/database/sql-manager.d.ts.map +1 -1
  30. package/dist/database/sqlite-adapter.d.ts +4 -2
  31. package/dist/database/sqlite-adapter.d.ts.map +1 -1
  32. package/dist/database/types.d.ts +26 -0
  33. package/dist/database/types.d.ts.map +1 -1
  34. package/dist/di/container.d.ts +2 -0
  35. package/dist/di/container.d.ts.map +1 -1
  36. package/dist/di/module-registry.d.ts.map +1 -1
  37. package/dist/di/module.d.ts +11 -1
  38. package/dist/di/module.d.ts.map +1 -1
  39. package/dist/di/types.d.ts +1 -1
  40. package/dist/di/types.d.ts.map +1 -1
  41. package/dist/error/handler.d.ts.map +1 -1
  42. package/dist/error/http-exception.d.ts +8 -8
  43. package/dist/error/http-exception.d.ts.map +1 -1
  44. package/dist/error/index.d.ts +1 -0
  45. package/dist/error/index.d.ts.map +1 -1
  46. package/dist/events/service.d.ts +2 -2
  47. package/dist/events/service.d.ts.map +1 -1
  48. package/dist/events/types.d.ts +12 -3
  49. package/dist/events/types.d.ts.map +1 -1
  50. package/dist/index.js +5951 -5820
  51. package/dist/index.node.mjs +310 -139
  52. package/dist/interceptor/base-interceptor.d.ts +3 -3
  53. package/dist/interceptor/base-interceptor.d.ts.map +1 -1
  54. package/dist/interceptor/builtin/log-interceptor.d.ts +1 -1
  55. package/dist/interceptor/builtin/log-interceptor.d.ts.map +1 -1
  56. package/dist/interceptor/builtin/permission-interceptor.d.ts +1 -1
  57. package/dist/interceptor/builtin/permission-interceptor.d.ts.map +1 -1
  58. package/dist/interceptor/interceptor-chain.d.ts +1 -1
  59. package/dist/interceptor/interceptor-chain.d.ts.map +1 -1
  60. package/dist/interceptor/interceptor-registry.d.ts +3 -1
  61. package/dist/interceptor/interceptor-registry.d.ts.map +1 -1
  62. package/dist/interceptor/types.d.ts +6 -1
  63. package/dist/interceptor/types.d.ts.map +1 -1
  64. package/dist/microservice/service-client/types.d.ts +1 -0
  65. package/dist/microservice/service-client/types.d.ts.map +1 -1
  66. package/dist/microservice/tracing/tracer.d.ts +1 -0
  67. package/dist/microservice/tracing/tracer.d.ts.map +1 -1
  68. package/dist/middleware/builtin/file-upload.d.ts +2 -0
  69. package/dist/middleware/builtin/file-upload.d.ts.map +1 -1
  70. package/dist/middleware/builtin/rate-limit.d.ts +9 -1
  71. package/dist/middleware/builtin/rate-limit.d.ts.map +1 -1
  72. package/dist/queue/service.d.ts +2 -2
  73. package/dist/queue/service.d.ts.map +1 -1
  74. package/dist/queue/types.d.ts +25 -1
  75. package/dist/queue/types.d.ts.map +1 -1
  76. package/dist/router/decorators.d.ts +1 -2
  77. package/dist/router/decorators.d.ts.map +1 -1
  78. package/dist/security/guards/types.d.ts +1 -0
  79. package/dist/security/guards/types.d.ts.map +1 -1
  80. package/dist/security/types.d.ts +1 -1
  81. package/dist/security/types.d.ts.map +1 -1
  82. package/dist/session/types.d.ts +8 -0
  83. package/dist/session/types.d.ts.map +1 -1
  84. package/dist/swagger/decorators.d.ts +1 -1
  85. package/dist/swagger/decorators.d.ts.map +1 -1
  86. package/dist/swagger/types.d.ts +1 -1
  87. package/dist/swagger/types.d.ts.map +1 -1
  88. package/dist/testing/harness.d.ts +1 -1
  89. package/dist/testing/harness.d.ts.map +1 -1
  90. package/dist/testing/test-client.d.ts +1 -1
  91. package/dist/testing/test-client.d.ts.map +1 -1
  92. package/dist/testing/testing-module.d.ts +2 -2
  93. package/dist/testing/testing-module.d.ts.map +1 -1
  94. package/dist/validation/errors.d.ts +5 -1
  95. package/dist/validation/errors.d.ts.map +1 -1
  96. package/docs/database.md +44 -0
  97. package/docs/zh/database.md +44 -0
  98. package/package.json +3 -3
  99. package/src/ai/types.ts +5 -0
  100. package/src/auth/types.ts +4 -1
  101. package/src/cache/interceptors.ts +6 -6
  102. package/src/cache/service-proxy.ts +2 -2
  103. package/src/cache/service.ts +17 -8
  104. package/src/cache/types.ts +12 -12
  105. package/src/config/service.ts +8 -6
  106. package/src/core/application.ts +7 -1
  107. package/src/core/context.ts +6 -3
  108. package/src/database/connection-manager.ts +5 -46
  109. package/src/database/connection-pool.ts +26 -49
  110. package/src/database/database-module.ts +6 -0
  111. package/src/database/db-proxy.ts +3 -2
  112. package/src/database/driver.ts +368 -0
  113. package/src/database/index.ts +8 -0
  114. package/src/database/service.ts +3 -74
  115. package/src/database/sql-manager.ts +38 -24
  116. package/src/database/sqlite-adapter.ts +4 -3
  117. package/src/database/types.ts +27 -2
  118. package/src/di/container.ts +13 -0
  119. package/src/di/module-registry.ts +2 -3
  120. package/src/di/module.ts +21 -2
  121. package/src/di/types.ts +1 -2
  122. package/src/error/handler.ts +3 -4
  123. package/src/error/http-exception.ts +11 -11
  124. package/src/error/index.ts +1 -1
  125. package/src/events/service.ts +4 -2
  126. package/src/events/types.ts +14 -3
  127. package/src/interceptor/base-interceptor.ts +5 -6
  128. package/src/interceptor/builtin/log-interceptor.ts +2 -3
  129. package/src/interceptor/builtin/permission-interceptor.ts +2 -3
  130. package/src/interceptor/interceptor-chain.ts +6 -7
  131. package/src/interceptor/interceptor-registry.ts +5 -3
  132. package/src/interceptor/types.ts +9 -4
  133. package/src/microservice/service-client/types.ts +1 -1
  134. package/src/microservice/tracing/tracer.ts +15 -3
  135. package/src/middleware/builtin/file-upload.ts +3 -2
  136. package/src/middleware/builtin/rate-limit.ts +22 -5
  137. package/src/queue/service.ts +1 -1
  138. package/src/queue/types.ts +40 -1
  139. package/src/router/decorators.ts +2 -3
  140. package/src/security/guards/types.ts +2 -1
  141. package/src/security/types.ts +1 -2
  142. package/src/session/service.ts +1 -1
  143. package/src/session/types.ts +10 -0
  144. package/src/swagger/decorators.ts +15 -4
  145. package/src/swagger/generator.ts +2 -3
  146. package/src/swagger/types.ts +1 -2
  147. package/src/testing/harness.ts +1 -2
  148. package/src/testing/test-client.ts +2 -2
  149. package/src/testing/testing-module.ts +5 -5
  150. package/src/validation/errors.ts +6 -2
  151. package/tests/bun-test-shim.d.ts +11 -0
  152. package/tests/controller/context-decorator.test.ts +1 -2
  153. package/tests/database/database-module.test.ts +4 -0
  154. package/tests/database/driver-mysql2.test.ts +95 -0
  155. package/tests/database/driver.test.ts +234 -0
  156. package/tests/database/orm.test.ts +4 -0
  157. package/tests/di/module.test.ts +199 -1
  158. package/tests/events/event-emitter.test.ts +2 -2
  159. package/tests/global.d.ts +30 -0
  160. package/tests/queue/queue-service.test.ts +14 -0
  161. package/tests/testing/testing-module.test.ts +20 -0
@@ -88,7 +88,12 @@ export interface RateLimitOptions {
88
88
  /**
89
89
  * 时间窗口内的最大请求数
90
90
  */
91
- max: number;
91
+ max?: number;
92
+
93
+ /**
94
+ * 时间窗口内的最大请求数别名,兼容装饰器早期写法。
95
+ */
96
+ limit?: number;
92
97
 
93
98
  /**
94
99
  * 时间窗口(毫秒)
@@ -108,6 +113,11 @@ export interface RateLimitOptions {
108
113
  */
109
114
  keyGenerator?: (context: Context) => string | Promise<string>;
110
115
 
116
+ /**
117
+ * 返回 true 时跳过限流。
118
+ */
119
+ skip?: (context: Context) => boolean | Promise<boolean>;
120
+
111
121
  /**
112
122
  * 是否跳过成功响应(只对错误响应计数)
113
123
  * @default false
@@ -157,9 +167,11 @@ function defaultKeyGenerator(context: Context): string {
157
167
  export function createRateLimitMiddleware(options: RateLimitOptions): Middleware {
158
168
  const {
159
169
  max,
170
+ limit,
160
171
  windowMs = 60000, // 默认 1 分钟
161
172
  store = new MemoryRateLimitStore(),
162
173
  keyGenerator = defaultKeyGenerator,
174
+ skip,
163
175
  skipSuccessfulRequests = false,
164
176
  skipFailedRequests = false,
165
177
  message = 'Too Many Requests',
@@ -167,31 +179,36 @@ export function createRateLimitMiddleware(options: RateLimitOptions): Middleware
167
179
  standardHeaders = true,
168
180
  legacyHeaders = true,
169
181
  } = options;
182
+ const requestLimit = limit ?? max ?? 100;
170
183
 
171
184
  return async (context: Context, next) => {
185
+ if (skip && await skip(context)) {
186
+ return await next();
187
+ }
188
+
172
189
  // 生成限流键
173
190
  const key = await keyGenerator(context);
174
191
  const currentCount = await store.increment(key, windowMs);
175
192
 
176
193
  // 计算剩余请求数和重置时间
177
- const remaining = Math.max(0, max - currentCount);
194
+ const remaining = Math.max(0, requestLimit - currentCount);
178
195
  const resetTime = Date.now() + windowMs;
179
196
 
180
197
  // 设置响应头
181
198
  if (standardHeaders) {
182
- context.setHeader('RateLimit-Limit', max.toString());
199
+ context.setHeader('RateLimit-Limit', requestLimit.toString());
183
200
  context.setHeader('RateLimit-Remaining', remaining.toString());
184
201
  context.setHeader('RateLimit-Reset', Math.ceil(resetTime / 1000).toString());
185
202
  }
186
203
 
187
204
  if (legacyHeaders) {
188
- context.setHeader('X-RateLimit-Limit', max.toString());
205
+ context.setHeader('X-RateLimit-Limit', requestLimit.toString());
189
206
  context.setHeader('X-RateLimit-Remaining', remaining.toString());
190
207
  context.setHeader('X-RateLimit-Reset', Math.ceil(resetTime / 1000).toString());
191
208
  }
192
209
 
193
210
  // 检查是否超过限制
194
- if (currentCount > max) {
211
+ if (currentCount > requestLimit) {
195
212
  context.setStatus(statusCode);
196
213
  return context.createErrorResponse({
197
214
  error: message,
@@ -17,7 +17,7 @@ import { getRuntime } from '../platform/runtime';
17
17
  */
18
18
  @Injectable()
19
19
  export class QueueService {
20
- private store: QueueStore;
20
+ public readonly store: QueueStore;
21
21
  private defaultQueue: string;
22
22
  private enableWorker: boolean;
23
23
  private concurrency: number;
@@ -24,6 +24,11 @@ export interface JobOptions {
24
24
  */
25
25
  attempts?: number;
26
26
 
27
+ /**
28
+ * 任务重试次数别名,兼容早期 API。
29
+ */
30
+ retries?: number;
31
+
27
32
  /**
28
33
  * 任务重试延迟(毫秒)
29
34
  */
@@ -90,7 +95,22 @@ export interface Job<T = JobData> {
90
95
  /**
91
96
  * 更新时间
92
97
  */
93
- updatedAt: number;
98
+ updatedAt?: number;
99
+
100
+ /**
101
+ * 完成结果
102
+ */
103
+ result?: unknown;
104
+
105
+ /**
106
+ * 完成时间
107
+ */
108
+ completedAt?: number;
109
+
110
+ /**
111
+ * 失败错误信息
112
+ */
113
+ error?: string;
94
114
  }
95
115
 
96
116
  /**
@@ -141,6 +161,25 @@ export interface QueueStore {
141
161
  status: Job['status'],
142
162
  ): Promise<boolean>;
143
163
 
164
+ complete?(
165
+ queueName: string,
166
+ jobId: string,
167
+ result?: unknown,
168
+ ): Promise<boolean>;
169
+
170
+ fail?(
171
+ queueName: string,
172
+ jobId: string,
173
+ error: Error,
174
+ ): Promise<boolean>;
175
+
176
+ getStats?(queueName: string): Promise<{
177
+ waiting: number;
178
+ active: number;
179
+ completed: number;
180
+ failed: number;
181
+ }>;
182
+
144
183
  /**
145
184
  * 删除任务
146
185
  * @param queueName - 队列名称
@@ -1,5 +1,4 @@
1
1
  import 'reflect-metadata';
2
- import type { RouteHandler } from './types';
3
2
  import { RouteRegistry } from './registry';
4
3
  import type { HttpMethod } from './types';
5
4
  import { CONTROLLER_METADATA_KEY } from '../controller/controller';
@@ -15,7 +14,7 @@ export const ROUTE_METADATA_KEY = Symbol('route');
15
14
  export interface RouteMetadata {
16
15
  method: HttpMethod;
17
16
  path: string;
18
- handler: RouteHandler;
17
+ handler: (...args: any[]) => unknown;
19
18
  /**
20
19
  * 属性键(用于控制器方法)
21
20
  */
@@ -75,7 +74,7 @@ function createRouteDecorator(method: HttpMethod, path: string = '') {
75
74
  existingRoutes.push({
76
75
  method,
77
76
  path: path ?? '',
78
- handler: descriptor.value as RouteHandler,
77
+ handler: descriptor.value as (...args: any[]) => unknown,
79
78
  propertyKey: propertyKeyStr || undefined,
80
79
  });
81
80
  Reflect.defineMetadata(ROUTE_METADATA_KEY, existingRoutes, target);
@@ -16,6 +16,8 @@ export interface CanActivate {
16
16
  canActivate(context: ExecutionContext): boolean | Promise<boolean>;
17
17
  }
18
18
 
19
+ export type Guard = CanActivate;
20
+
19
21
  /**
20
22
  * HTTP 参数主机接口
21
23
  * 提供 HTTP 请求相关的上下文信息
@@ -141,4 +143,3 @@ export const GUARD_REGISTRY_TOKEN = Symbol('@dangao/bun-server:guard-registry');
141
143
  * Roles 元数据键
142
144
  */
143
145
  export const ROLES_METADATA_KEY = Symbol('@dangao/bun-server:roles');
144
-
@@ -35,7 +35,7 @@ export interface Credentials {
35
35
  /**
36
36
  * 凭据数据
37
37
  */
38
- data: unknown;
38
+ data: any;
39
39
  }
40
40
 
41
41
  /**
@@ -162,4 +162,3 @@ export interface SecurityContext {
162
162
  */
163
163
  getAuthorities(): string[];
164
164
  }
165
-
@@ -25,7 +25,7 @@ export class SessionService {
25
25
  ) {
26
26
  this.store = options.store!;
27
27
  this.name = options.name ?? 'sessionId';
28
- this.maxAge = options.maxAge ?? 86400000; // 24 小时
28
+ this.maxAge = options.maxAge ?? options.ttl ?? 86400000; // 24 小时
29
29
  this.rolling = options.rolling ?? true;
30
30
  this.cookieOptions = {
31
31
  secure: options.cookie?.secure ?? false,
@@ -317,6 +317,11 @@ export interface SessionModuleOptions {
317
317
  */
318
318
  maxAge?: number;
319
319
 
320
+ /**
321
+ * Session 最大存活时间别名。
322
+ */
323
+ ttl?: number;
324
+
320
325
  /**
321
326
  * 是否在每次访问时更新过期时间
322
327
  * @default true
@@ -327,6 +332,11 @@ export interface SessionModuleOptions {
327
332
  * Cookie 选项
328
333
  */
329
334
  cookie?: {
335
+ /**
336
+ * Cookie 名称别名,优先使用顶层 name。
337
+ */
338
+ name?: string;
339
+
330
340
  /**
331
341
  * 是否只在 HTTPS 下发送
332
342
  * @default false
@@ -50,13 +50,25 @@ export function ApiOperation(metadata: ApiOperationMetadata): MethodDecorator {
50
50
  * 用于描述 API 参数
51
51
  * @param metadata - 参数元数据
52
52
  */
53
- export function ApiParam(metadata: ApiParamMetadata): ParameterDecorator {
54
- return function (target: unknown, propertyKey: string | symbol | undefined, parameterIndex: number) {
53
+ export function ApiParam(metadata: ApiParamMetadata): ParameterDecorator & MethodDecorator {
54
+ const decorator = (
55
+ target: Object,
56
+ propertyKey: string | symbol | undefined,
57
+ descriptorOrParameterIndex: TypedPropertyDescriptor<any> | number,
58
+ ): void => {
59
+ const paramMetadata: ApiParamMetadata = {
60
+ in: 'path',
61
+ ...metadata,
62
+ };
55
63
  const existingParams: Array<{ index: number; metadata: ApiParamMetadata }> =
56
64
  Reflect.getMetadata(API_PARAM_METADATA_KEY, target as Object, propertyKey!) || [];
57
- existingParams.push({ index: parameterIndex, metadata });
65
+ const index = typeof descriptorOrParameterIndex === 'number'
66
+ ? descriptorOrParameterIndex
67
+ : -1;
68
+ existingParams.push({ index, metadata: paramMetadata });
58
69
  Reflect.defineMetadata(API_PARAM_METADATA_KEY, existingParams, target as Object, propertyKey!);
59
70
  };
71
+ return decorator as ParameterDecorator & MethodDecorator;
60
72
  }
61
73
 
62
74
  /**
@@ -130,4 +142,3 @@ export function getApiResponses(
130
142
  ): ApiResponseMetadata[] {
131
143
  return Reflect.getMetadata(API_RESPONSE_METADATA_KEY, target as Object, propertyKey) || [];
132
144
  }
133
-
@@ -139,7 +139,7 @@ export class SwaggerGenerator {
139
139
  for (const match of pathParamMatches) {
140
140
  const paramName = match[1];
141
141
  // 检查是否已经有手动定义的参数
142
- const existingParam = params.find((p) => p.metadata.name === paramName && p.metadata.in === 'path');
142
+ const existingParam = params.find((p) => p.metadata.name === paramName && (p.metadata.in ?? 'path') === 'path');
143
143
  if (!existingParam) {
144
144
  // 自动添加路径参数
145
145
  pathParams.push({
@@ -155,7 +155,7 @@ export class SwaggerGenerator {
155
155
  for (const param of params) {
156
156
  pathParams.push({
157
157
  name: param.metadata.name,
158
- in: param.metadata.in,
158
+ in: param.metadata.in ?? 'path',
159
159
  description: param.metadata.description,
160
160
  required: param.metadata.required,
161
161
  schema: param.metadata.schema,
@@ -332,4 +332,3 @@ export class SwaggerGenerator {
332
332
  return path;
333
333
  }
334
334
  }
335
-
@@ -60,7 +60,7 @@ export interface ApiParamMetadata {
60
60
  enum?: unknown[];
61
61
  default?: unknown;
62
62
  };
63
- in: 'query' | 'path' | 'header' | 'cookie';
63
+ in?: 'query' | 'path' | 'header' | 'cookie';
64
64
  }
65
65
 
66
66
  /**
@@ -185,4 +185,3 @@ export interface SwaggerDocument {
185
185
  patch?: SwaggerPathItem;
186
186
  }>;
187
187
  }
188
-
@@ -22,7 +22,7 @@ export class PerformanceHarness {
22
22
  public static async benchmark(
23
23
  name: string,
24
24
  iterations: number,
25
- runner: (iteration: number) => void | Promise<void>,
25
+ runner: (iteration: number) => unknown | Promise<unknown>,
26
26
  ): Promise<BenchmarkResult> {
27
27
  if (iterations <= 0) {
28
28
  throw new Error('iterations must be greater than 0');
@@ -93,4 +93,3 @@ export class StressTester {
93
93
  };
94
94
  }
95
95
  }
96
-
@@ -9,7 +9,7 @@ interface RequestOptions {
9
9
  interface TestResponse {
10
10
  status: number;
11
11
  headers: Headers;
12
- body: unknown;
12
+ body: any;
13
13
  text: string;
14
14
  ok: boolean;
15
15
  }
@@ -94,7 +94,7 @@ export class TestHttpClient {
94
94
  const response = await fetch(url, fetchOptions);
95
95
  const text = await response.text();
96
96
 
97
- let body: unknown;
97
+ let body: any;
98
98
  try {
99
99
  body = JSON.parse(text);
100
100
  } catch (_error) {
@@ -5,7 +5,7 @@ import { Container } from '../di/container';
5
5
  import { ModuleRegistry } from '../di/module-registry';
6
6
  import { ControllerRegistry } from '../controller/controller';
7
7
  import { RouteRegistry } from '../router/registry';
8
- import { MODULE_METADATA_KEY, type ModuleMetadata, type ModuleClass, type ModuleProvider, type ProviderToken } from '../di/module';
8
+ import { MODULE_METADATA_KEY, invokeFactoryProvider, type ModuleMetadata, type ModuleClass, type ModuleProvider, type ProviderToken } from '../di/module';
9
9
  import type { Constructor } from '../core/types';
10
10
  import { TestHttpClient } from './test-client';
11
11
 
@@ -13,7 +13,7 @@ interface ProviderOverride {
13
13
  token: ProviderToken;
14
14
  useValue?: unknown;
15
15
  useClass?: Constructor<unknown>;
16
- useFactory?: () => unknown;
16
+ useFactory?: (...args: any[]) => unknown;
17
17
  }
18
18
 
19
19
  /**
@@ -75,7 +75,7 @@ export class TestingModuleBuilder {
75
75
  } else if (override.useClass) {
76
76
  result.push({ provide: override.token, useClass: override.useClass });
77
77
  } else if (override.useFactory) {
78
- result.push({ provide: override.token, useFactory: override.useFactory as (container: Container) => unknown });
78
+ result.push({ provide: override.token, useFactory: override.useFactory });
79
79
  }
80
80
  overrideMap.delete(key as string | symbol);
81
81
  } else {
@@ -121,7 +121,7 @@ class ProviderOverrideBuilder {
121
121
  /**
122
122
  * 使用工厂函数覆盖
123
123
  */
124
- public useFactory(factory: () => unknown): TestingModuleBuilder {
124
+ public useFactory(factory: (...args: any[]) => unknown): TestingModuleBuilder {
125
125
  this.builder.addOverride({ token: this.token, useFactory: factory });
126
126
  return this.builder;
127
127
  }
@@ -177,7 +177,7 @@ export class TestingModule {
177
177
  container.registerInstance(provider.provide, provider.useValue);
178
178
  } else if ('useFactory' in provider && provider.provide) {
179
179
  container.register(provider.provide as Constructor<unknown>, {
180
- factory: () => (provider as { useFactory: (container: Container) => unknown }).useFactory(container),
180
+ factory: () => invokeFactoryProvider(provider, container),
181
181
  });
182
182
  } else if ('useClass' in provider) {
183
183
  const token = (provider as { provide?: ProviderToken }).provide ?? (provider as { useClass: Constructor<unknown> }).useClass;
@@ -9,10 +9,15 @@ export interface ValidationIssue {
9
9
  */
10
10
  property?: string;
11
11
 
12
+ /**
13
+ * 属性路径别名,兼容早期错误对象形状。
14
+ */
15
+ path?: string;
16
+
12
17
  /**
13
18
  * 失败的规则名称
14
19
  */
15
- rule: string;
20
+ rule?: string;
16
21
 
17
22
  /**
18
23
  * 错误信息
@@ -73,4 +78,3 @@ export class ValidationError extends Error {
73
78
  }
74
79
  }
75
80
 
76
-
@@ -0,0 +1,11 @@
1
+ declare module 'bun:test' {
2
+ export const describe: any;
3
+ export const test: any;
4
+ export const it: any;
5
+ export const expect: any;
6
+ export const beforeEach: any;
7
+ export const afterEach: any;
8
+ export const beforeAll: any;
9
+ export const afterAll: any;
10
+ export const mock: any;
11
+ }
@@ -155,7 +155,7 @@ describe('@Context() Decorator', () => {
155
155
  // 这个测试验证 ContextService 可以在服务层使用
156
156
  const { ContextService, CONTEXT_SERVICE_TOKEN } = await import('../../src/core/context-service');
157
157
  const container = app.getContainer();
158
- const contextService = container.resolve<ContextService>(CONTEXT_SERVICE_TOKEN);
158
+ const contextService = container.resolve<InstanceType<typeof ContextService>>(CONTEXT_SERVICE_TOKEN);
159
159
 
160
160
  @Controller('/api/test')
161
161
  class TestController {
@@ -180,4 +180,3 @@ describe('@Context() Decorator', () => {
180
180
  expect(data.method).toBe('GET');
181
181
  });
182
182
  });
183
-
@@ -9,9 +9,13 @@ import {
9
9
  SQLITE_MANAGER_TOKEN,
10
10
  } from '../../src/database';
11
11
  import { MODULE_METADATA_KEY } from '../../src/di/module';
12
+ import { initRuntime } from '../../src/platform/runtime';
12
13
 
13
14
  describe('DatabaseModule V2', () => {
14
15
  beforeEach(() => {
16
+ // forRoot() 会在创建 sqlite/sql 管理器时读取 getRuntime(),确保运行时已初始化
17
+ // (避免依赖其他测试文件的执行顺序)
18
+ initRuntime('bun');
15
19
  Reflect.deleteMetadata(MODULE_METADATA_KEY, DatabaseModule);
16
20
  });
17
21
 
@@ -0,0 +1,95 @@
1
+ import { describe, expect, test, mock, beforeAll } from 'bun:test';
2
+
3
+ import { initRuntime, getRuntime } from '../../src/platform/runtime';
4
+
5
+ /**
6
+ * 验证:在 Bun 运行时下显式选用 driver: 'mysql2' 时,
7
+ * 连接创建 / 参数化查询 / health check / close 全部走 mysql2(而非 Bun.SQL)。
8
+ *
9
+ * 通过 mock 'mysql2/promise' 模块注入假连接,无需真实 MySQL。
10
+ */
11
+
12
+ const createdConnections: Array<Record<string, unknown>> = [];
13
+ const queryCalls: Array<{ sql: string; params: unknown[] }> = [];
14
+ let endCount = 0;
15
+
16
+ function makeFakeConnection() {
17
+ const conn = {
18
+ query: async (sql: string, params: unknown[]) => {
19
+ queryCalls.push({ sql, params });
20
+ // mysql2 返回 [rows, fields]
21
+ if (/select\s+1/i.test(sql)) {
22
+ return [[{ '1': 1 }], []];
23
+ }
24
+ return [[{ id: 1, name: 'alice' }], [{ name: 'id' }, { name: 'name' }]];
25
+ },
26
+ end: async () => {
27
+ endCount += 1;
28
+ },
29
+ close: async () => {
30
+ throw new Error('mysql2 connection should be closed via end(), not close()');
31
+ },
32
+ };
33
+ createdConnections.push(conn);
34
+ return conn;
35
+ }
36
+
37
+ mock.module('mysql2/promise', () => {
38
+ const createConnection = async (_opts: unknown) => makeFakeConnection();
39
+ const createPool = (_opts: unknown) => makeFakeConnection();
40
+ return {
41
+ default: { createConnection, createPool },
42
+ createConnection,
43
+ createPool,
44
+ };
45
+ });
46
+
47
+ describe('DatabaseService with driver: mysql2 on Bun runtime', () => {
48
+ beforeAll(() => {
49
+ initRuntime('bun');
50
+ });
51
+
52
+ test('runs the full lifecycle (create/query/health/close) through mysql2', async () => {
53
+ // 前置确认:当前进程运行在 Bun,但我们强制 mysql2
54
+ expect(getRuntime().engine).toBe('bun');
55
+
56
+ const { DatabaseService } = await import('../../src/database/service');
57
+
58
+ const svc = new DatabaseService({
59
+ database: {
60
+ type: 'mysql',
61
+ driver: 'mysql2',
62
+ config: {
63
+ host: 'localhost',
64
+ port: 3306,
65
+ database: 'testdb',
66
+ user: 'root',
67
+ password: 'secret',
68
+ },
69
+ },
70
+ enableHealthCheck: true,
71
+ });
72
+
73
+ await svc.initialize();
74
+
75
+ // 连接由 mysql2.createConnection 创建
76
+ expect(createdConnections.length).toBeGreaterThan(0);
77
+
78
+ // 参数化查询走 mysql2.query(sql, params),并返回 rows(而非 [rows, fields])
79
+ const rows = await svc.query<{ id: number; name: string }>(
80
+ 'SELECT * FROM users WHERE id = ?',
81
+ [1],
82
+ );
83
+ expect(rows).toEqual([{ id: 1, name: 'alice' }]);
84
+ expect(queryCalls.some((c) => c.sql === 'SELECT * FROM users WHERE id = ?' && c.params[0] === 1)).toBe(true);
85
+
86
+ // health check 走 mysql2.query('SELECT 1')
87
+ const healthy = await svc.healthCheck();
88
+ expect(healthy).toBe(true);
89
+ expect(queryCalls.some((c) => /select\s+1/i.test(c.sql))).toBe(true);
90
+
91
+ // close 走 mysql2 的 end()
92
+ await svc.closePool();
93
+ expect(endCount).toBeGreaterThan(0);
94
+ });
95
+ });