@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,188 @@
1
+ import { ControllerRegistry } from '../controller/controller';
2
+ import { Container } from './container';
3
+ import { Lifecycle } from './types';
4
+ import { getModuleMetadata, type ModuleClass, type ModuleProvider, type ProviderToken } from './module';
5
+ import type { Constructor } from '@/core/types';
6
+ import type { ApplicationExtension } from '../extensions/types';
7
+ import type { Middleware } from '../middleware';
8
+
9
+ interface ModuleRef {
10
+ moduleClass: ModuleClass;
11
+ metadata: ReturnType<typeof getModuleMetadata>;
12
+ container: Container;
13
+ controllersRegistered: boolean;
14
+ attachedParents: Set<Container>;
15
+ extensions: ApplicationExtension[];
16
+ middlewares: Middleware[];
17
+ }
18
+
19
+ export class ModuleRegistry {
20
+ private static instance: ModuleRegistry;
21
+ private readonly moduleRefs = new Map<ModuleClass, ModuleRef>();
22
+ private readonly processing = new Set<ModuleClass>();
23
+ private rootContainer?: Container;
24
+
25
+ public static getInstance(): ModuleRegistry {
26
+ if (!ModuleRegistry.instance) {
27
+ ModuleRegistry.instance = new ModuleRegistry();
28
+ }
29
+ return ModuleRegistry.instance;
30
+ }
31
+
32
+ public register(moduleClass: ModuleClass, parentContainer: Container): ModuleRef {
33
+ if (!this.rootContainer) {
34
+ this.rootContainer = parentContainer;
35
+ }
36
+ return this.processModule(moduleClass, parentContainer);
37
+ }
38
+
39
+ public getModuleRef(moduleClass: ModuleClass): ModuleRef | undefined {
40
+ return this.moduleRefs.get(moduleClass);
41
+ }
42
+
43
+ public clear(): void {
44
+ this.moduleRefs.clear();
45
+ this.processing.clear();
46
+ this.rootContainer = undefined;
47
+ }
48
+
49
+ private processModule(moduleClass: ModuleClass, parentContainer: Container): ModuleRef {
50
+ if (this.processing.has(moduleClass)) {
51
+ throw new Error(`Circular module dependency detected for ${moduleClass.name}`);
52
+ }
53
+ const moduleRef = this.getOrCreateModuleRef(moduleClass);
54
+ this.processing.add(moduleClass);
55
+ for (const imported of moduleRef.metadata.imports) {
56
+ this.processModule(imported, moduleRef.container);
57
+ }
58
+ this.processing.delete(moduleClass);
59
+ this.attachModuleToParent(moduleRef, parentContainer);
60
+ return moduleRef;
61
+ }
62
+
63
+ private getOrCreateModuleRef(moduleClass: ModuleClass): ModuleRef {
64
+ let ref = this.moduleRefs.get(moduleClass);
65
+ if (ref) {
66
+ return ref;
67
+ }
68
+ if (!this.rootContainer) {
69
+ throw new Error('ModuleRegistry is not initialized with a root container');
70
+ }
71
+ const metadata = getModuleMetadata(moduleClass);
72
+ const container = new Container({ parent: this.rootContainer });
73
+ this.registerProviders(container, metadata.providers);
74
+ ref = {
75
+ moduleClass,
76
+ metadata,
77
+ container,
78
+ controllersRegistered: false,
79
+ attachedParents: new Set<Container>(),
80
+ extensions: metadata.extensions ?? [],
81
+ middlewares: metadata.middlewares ?? [],
82
+ };
83
+ this.registerControllers(ref);
84
+ this.moduleRefs.set(moduleClass, ref);
85
+ return ref;
86
+ }
87
+
88
+ private registerProviders(container: Container, providers: ModuleProvider[]): void {
89
+ for (const provider of providers) {
90
+ if (typeof provider === 'function') {
91
+ if (!container.isRegistered(provider)) {
92
+ container.register(provider);
93
+ }
94
+ continue;
95
+ }
96
+
97
+ if ('useValue' in provider) {
98
+ container.registerInstance(provider.provide, provider.useValue);
99
+ continue;
100
+ }
101
+
102
+ if ('useFactory' in provider) {
103
+ container.register(provider.provide, {
104
+ lifecycle: provider.lifecycle ?? Lifecycle.Singleton,
105
+ factory: () => provider.useFactory(container),
106
+ });
107
+ continue;
108
+ }
109
+
110
+ if ('useClass' in provider) {
111
+ const token = provider.provide ?? provider.useClass;
112
+ container.register(token, {
113
+ lifecycle: provider.lifecycle ?? Lifecycle.Singleton,
114
+ implementation: provider.useClass,
115
+ });
116
+ }
117
+ }
118
+ }
119
+
120
+ private registerControllers(moduleRef: ModuleRef): void {
121
+ if (moduleRef.controllersRegistered) {
122
+ return;
123
+ }
124
+ const controllerRegistry = ControllerRegistry.getInstance();
125
+ for (const controller of moduleRef.metadata.controllers) {
126
+ controllerRegistry.register(controller, moduleRef.container);
127
+ }
128
+ moduleRef.controllersRegistered = true;
129
+ }
130
+
131
+ private attachModuleToParent(moduleRef: ModuleRef, parentContainer: Container): void {
132
+ if (moduleRef.attachedParents.has(parentContainer)) {
133
+ return;
134
+ }
135
+ moduleRef.attachedParents.add(parentContainer);
136
+ for (const exportedToken of moduleRef.metadata.exports) {
137
+ this.registerExport(parentContainer, moduleRef, exportedToken);
138
+ }
139
+ // 收集导入模块的扩展和中间件
140
+ for (const imported of moduleRef.metadata.imports) {
141
+ const importedRef = this.moduleRefs.get(imported);
142
+ if (importedRef) {
143
+ moduleRef.extensions.push(...importedRef.extensions);
144
+ moduleRef.middlewares.push(...importedRef.middlewares);
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 获取模块的所有扩展(包括导入模块的扩展)
151
+ */
152
+ public getModuleExtensions(moduleClass: ModuleClass): ApplicationExtension[] {
153
+ const moduleRef = this.moduleRefs.get(moduleClass);
154
+ if (!moduleRef) {
155
+ return [];
156
+ }
157
+ return moduleRef.extensions;
158
+ }
159
+
160
+ /**
161
+ * 获取模块的所有中间件(包括导入模块的中间件)
162
+ */
163
+ public getModuleMiddlewares(moduleClass: ModuleClass): Middleware[] {
164
+ const moduleRef = this.moduleRefs.get(moduleClass);
165
+ if (!moduleRef) {
166
+ return [];
167
+ }
168
+ return moduleRef.middlewares;
169
+ }
170
+
171
+ private registerExport(parentContainer: Container, moduleRef: ModuleRef, token: ProviderToken): void {
172
+ if (!moduleRef.container.isRegistered(token)) {
173
+ throw new Error(
174
+ `Module ${moduleRef.moduleClass.name} cannot export ${
175
+ typeof token === 'function' ? token.name : String(token)
176
+ } because it is not registered`,
177
+ );
178
+ }
179
+ if (parentContainer.isRegistered(token)) {
180
+ return;
181
+ }
182
+ parentContainer.register(token as Constructor<unknown>, {
183
+ lifecycle: Lifecycle.Singleton,
184
+ factory: () => moduleRef.container.resolve(token as any),
185
+ });
186
+ }
187
+ }
188
+
@@ -0,0 +1,65 @@
1
+ import 'reflect-metadata';
2
+
3
+ import type { Constructor } from '@/core/types';
4
+ import type { Container } from './container';
5
+ import type { Lifecycle } from './types';
6
+ import type { ApplicationExtension } from '../extensions/types';
7
+ import type { Middleware } from '../middleware';
8
+
9
+ export type ModuleClass<T = unknown> = Constructor<T>;
10
+ export type ProviderToken = Constructor<unknown> | string | symbol;
11
+
12
+ export interface ClassProvider {
13
+ provide?: ProviderToken;
14
+ useClass: Constructor<unknown>;
15
+ lifecycle?: Lifecycle;
16
+ }
17
+
18
+ export interface ValueProvider {
19
+ provide: ProviderToken;
20
+ useValue: unknown;
21
+ }
22
+
23
+ export interface FactoryProvider {
24
+ provide: ProviderToken;
25
+ useFactory: (container: Container) => unknown;
26
+ lifecycle?: Lifecycle;
27
+ }
28
+
29
+ export type ModuleProvider = Constructor<unknown> | ClassProvider | ValueProvider | FactoryProvider;
30
+
31
+ export interface ModuleMetadata {
32
+ imports?: ModuleClass[];
33
+ controllers?: Constructor<unknown>[];
34
+ providers?: ModuleProvider[];
35
+ exports?: ProviderToken[];
36
+ /**
37
+ * 应用扩展列表
38
+ */
39
+ extensions?: ApplicationExtension[];
40
+ /**
41
+ * 中间件列表
42
+ */
43
+ middlewares?: Middleware[];
44
+ }
45
+
46
+ export const MODULE_METADATA_KEY = Symbol('@dangao/bun-server:module');
47
+
48
+ export function Module(metadata: ModuleMetadata): ClassDecorator {
49
+ return (target) => {
50
+ Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, target);
51
+ };
52
+ }
53
+
54
+ export function getModuleMetadata(moduleClass: ModuleClass): Required<Omit<ModuleMetadata, 'extensions' | 'middlewares'>> & Pick<ModuleMetadata, 'extensions' | 'middlewares'> {
55
+ const metadata = (Reflect.getMetadata(MODULE_METADATA_KEY, moduleClass) as ModuleMetadata | undefined) ?? {};
56
+ return {
57
+ imports: metadata.imports ?? [],
58
+ controllers: metadata.controllers ?? [],
59
+ providers: metadata.providers ?? [],
60
+ exports: metadata.exports ?? [],
61
+ extensions: metadata.extensions ?? [],
62
+ middlewares: metadata.middlewares ?? [],
63
+ };
64
+ }
65
+
@@ -0,0 +1,67 @@
1
+ import type { Constructor } from '@/core/types'
2
+
3
+ /**
4
+ * 依赖生命周期类型
5
+ */
6
+ export enum Lifecycle {
7
+ /**
8
+ * 单例:整个应用生命周期中只有一个实例
9
+ */
10
+ Singleton = 'singleton',
11
+
12
+ /**
13
+ * 瞬态:每次请求都创建新实例
14
+ */
15
+ Transient = 'transient',
16
+
17
+ /**
18
+ * 作用域:在特定作用域内共享实例(未来扩展)
19
+ */
20
+ Scoped = 'scoped',
21
+ }
22
+
23
+ /**
24
+ * 提供者配置
25
+ */
26
+ export interface ProviderConfig {
27
+ /**
28
+ * 生命周期
29
+ */
30
+ lifecycle?: Lifecycle;
31
+
32
+ /**
33
+ * 自定义工厂函数
34
+ */
35
+ factory?: () => unknown;
36
+
37
+ /**
38
+ * 提供者标识符(用于接口注入)
39
+ */
40
+ token?: string | symbol;
41
+
42
+ /**
43
+ * 实际实现类(当 token 为 string/symbol 时用于实例化)
44
+ */
45
+ implementation?: new (...args: unknown[]) => unknown;
46
+ }
47
+
48
+ /**
49
+ * 依赖元数据
50
+ */
51
+ export interface DependencyMetadata {
52
+ /**
53
+ * 参数索引
54
+ */
55
+ index: number;
56
+
57
+ /**
58
+ * 依赖类型(构造函数)
59
+ */
60
+ type: Constructor<unknown> | string;
61
+
62
+ /**
63
+ * 可选的 token(用于接口注入)
64
+ */
65
+ token?: string | symbol;
66
+ }
67
+
@@ -0,0 +1,222 @@
1
+ /**
2
+ * 错误码定义
3
+ *
4
+ * 错误码命名规范:
5
+ * - 格式:模块_错误类型_具体错误
6
+ * - 使用大写字母和下划线
7
+ * - 模块前缀:AUTH(认证)、VALIDATION(验证)、DATABASE(数据库)、FILE(文件)、MIDDLEWARE(中间件)、OAUTH2(OAuth2)
8
+ * - 示例:AUTH_INVALID_TOKEN, VALIDATION_REQUIRED_FIELD, DATABASE_CONNECTION_FAILED
9
+ *
10
+ * 错误码分类:
11
+ * - 1000-1999: 通用错误
12
+ * - 2000-2999: 认证和授权错误
13
+ * - 3000-3999: 验证错误
14
+ * - 4000-4999: OAuth2 错误
15
+ * - 5000-5999: 数据库错误
16
+ * - 6000-6999: 文件操作错误
17
+ * - 7000-7999: 中间件错误
18
+ * - 8000-8999: 配置错误
19
+ */
20
+ export enum ErrorCode {
21
+ // ========== 通用错误 (1000-1999) ==========
22
+ INTERNAL_ERROR = 'INTERNAL_ERROR',
23
+ INVALID_REQUEST = 'INVALID_REQUEST',
24
+ RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
25
+ METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED',
26
+ RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
27
+ SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
28
+ TIMEOUT = 'TIMEOUT',
29
+
30
+ // ========== 认证和授权错误 (2000-2999) ==========
31
+ AUTH_REQUIRED = 'AUTH_REQUIRED',
32
+ AUTH_INVALID_TOKEN = 'AUTH_INVALID_TOKEN',
33
+ AUTH_TOKEN_EXPIRED = 'AUTH_TOKEN_EXPIRED',
34
+ AUTH_INSUFFICIENT_PERMISSIONS = 'AUTH_INSUFFICIENT_PERMISSIONS',
35
+ AUTH_INVALID_CREDENTIALS = 'AUTH_INVALID_CREDENTIALS',
36
+ AUTH_ACCOUNT_LOCKED = 'AUTH_ACCOUNT_LOCKED',
37
+ AUTH_ACCOUNT_DISABLED = 'AUTH_ACCOUNT_DISABLED',
38
+
39
+ // ========== 验证错误 (3000-3999) ==========
40
+ VALIDATION_FAILED = 'VALIDATION_FAILED',
41
+ VALIDATION_REQUIRED_FIELD = 'VALIDATION_REQUIRED_FIELD',
42
+ VALIDATION_INVALID_FORMAT = 'VALIDATION_INVALID_FORMAT',
43
+ VALIDATION_OUT_OF_RANGE = 'VALIDATION_OUT_OF_RANGE',
44
+ VALIDATION_TYPE_MISMATCH = 'VALIDATION_TYPE_MISMATCH',
45
+ VALIDATION_CONSTRAINT_VIOLATION = 'VALIDATION_CONSTRAINT_VIOLATION',
46
+
47
+ // ========== OAuth2 错误 (4000-4999) ==========
48
+ OAUTH2_INVALID_CLIENT = 'OAUTH2_INVALID_CLIENT',
49
+ OAUTH2_INVALID_GRANT = 'OAUTH2_INVALID_GRANT',
50
+ OAUTH2_INVALID_SCOPE = 'OAUTH2_INVALID_SCOPE',
51
+ OAUTH2_INVALID_REDIRECT_URI = 'OAUTH2_INVALID_REDIRECT_URI',
52
+ OAUTH2_UNSUPPORTED_GRANT_TYPE = 'OAUTH2_UNSUPPORTED_GRANT_TYPE',
53
+ OAUTH2_ACCESS_DENIED = 'OAUTH2_ACCESS_DENIED',
54
+ OAUTH2_SERVER_ERROR = 'OAUTH2_SERVER_ERROR',
55
+
56
+ // ========== 数据库错误 (5000-5999) ==========
57
+ DATABASE_CONNECTION_FAILED = 'DATABASE_CONNECTION_FAILED',
58
+ DATABASE_QUERY_FAILED = 'DATABASE_QUERY_FAILED',
59
+ DATABASE_TRANSACTION_FAILED = 'DATABASE_TRANSACTION_FAILED',
60
+ DATABASE_CONSTRAINT_VIOLATION = 'DATABASE_CONSTRAINT_VIOLATION',
61
+ DATABASE_TIMEOUT = 'DATABASE_TIMEOUT',
62
+ DATABASE_POOL_EXHAUSTED = 'DATABASE_POOL_EXHAUSTED',
63
+ DATABASE_MIGRATION_FAILED = 'DATABASE_MIGRATION_FAILED',
64
+
65
+ // ========== 文件操作错误 (6000-6999) ==========
66
+ FILE_NOT_FOUND = 'FILE_NOT_FOUND',
67
+ FILE_UPLOAD_FAILED = 'FILE_UPLOAD_FAILED',
68
+ FILE_DOWNLOAD_FAILED = 'FILE_DOWNLOAD_FAILED',
69
+ FILE_SIZE_EXCEEDED = 'FILE_SIZE_EXCEEDED',
70
+ FILE_TYPE_NOT_ALLOWED = 'FILE_TYPE_NOT_ALLOWED',
71
+ FILE_ACCESS_DENIED = 'FILE_ACCESS_DENIED',
72
+ FILE_PATH_TRAVERSAL = 'FILE_PATH_TRAVERSAL',
73
+
74
+ // ========== 中间件错误 (7000-7999) ==========
75
+ MIDDLEWARE_EXECUTION_FAILED = 'MIDDLEWARE_EXECUTION_FAILED',
76
+ MIDDLEWARE_TIMEOUT = 'MIDDLEWARE_TIMEOUT',
77
+ CORS_NOT_ALLOWED = 'CORS_NOT_ALLOWED',
78
+
79
+ // ========== 配置错误 (8000-8999) ==========
80
+ CONFIG_INVALID = 'CONFIG_INVALID',
81
+ CONFIG_MISSING = 'CONFIG_MISSING',
82
+ CONFIG_VALIDATION_FAILED = 'CONFIG_VALIDATION_FAILED',
83
+ }
84
+
85
+ /**
86
+ * 错误码到 HTTP 状态码的映射
87
+ */
88
+ export const ERROR_CODE_TO_STATUS: Record<ErrorCode, number> = {
89
+ // 通用错误
90
+ [ErrorCode.INTERNAL_ERROR]: 500,
91
+ [ErrorCode.INVALID_REQUEST]: 400,
92
+ [ErrorCode.RESOURCE_NOT_FOUND]: 404,
93
+ [ErrorCode.METHOD_NOT_ALLOWED]: 405,
94
+ [ErrorCode.RATE_LIMIT_EXCEEDED]: 429,
95
+ [ErrorCode.SERVICE_UNAVAILABLE]: 503,
96
+ [ErrorCode.TIMEOUT]: 408,
97
+
98
+ // 认证和授权错误
99
+ [ErrorCode.AUTH_REQUIRED]: 401,
100
+ [ErrorCode.AUTH_INVALID_TOKEN]: 401,
101
+ [ErrorCode.AUTH_TOKEN_EXPIRED]: 401,
102
+ [ErrorCode.AUTH_INSUFFICIENT_PERMISSIONS]: 403,
103
+ [ErrorCode.AUTH_INVALID_CREDENTIALS]: 401,
104
+ [ErrorCode.AUTH_ACCOUNT_LOCKED]: 423,
105
+ [ErrorCode.AUTH_ACCOUNT_DISABLED]: 403,
106
+
107
+ // 验证错误
108
+ [ErrorCode.VALIDATION_FAILED]: 400,
109
+ [ErrorCode.VALIDATION_REQUIRED_FIELD]: 400,
110
+ [ErrorCode.VALIDATION_INVALID_FORMAT]: 400,
111
+ [ErrorCode.VALIDATION_OUT_OF_RANGE]: 400,
112
+ [ErrorCode.VALIDATION_TYPE_MISMATCH]: 400,
113
+ [ErrorCode.VALIDATION_CONSTRAINT_VIOLATION]: 400,
114
+
115
+ // OAuth2 错误
116
+ [ErrorCode.OAUTH2_INVALID_CLIENT]: 400,
117
+ [ErrorCode.OAUTH2_INVALID_GRANT]: 400,
118
+ [ErrorCode.OAUTH2_INVALID_SCOPE]: 400,
119
+ [ErrorCode.OAUTH2_INVALID_REDIRECT_URI]: 400,
120
+ [ErrorCode.OAUTH2_UNSUPPORTED_GRANT_TYPE]: 400,
121
+ [ErrorCode.OAUTH2_ACCESS_DENIED]: 403,
122
+ [ErrorCode.OAUTH2_SERVER_ERROR]: 500,
123
+
124
+ // 数据库错误
125
+ [ErrorCode.DATABASE_CONNECTION_FAILED]: 503,
126
+ [ErrorCode.DATABASE_QUERY_FAILED]: 500,
127
+ [ErrorCode.DATABASE_TRANSACTION_FAILED]: 500,
128
+ [ErrorCode.DATABASE_CONSTRAINT_VIOLATION]: 409,
129
+ [ErrorCode.DATABASE_TIMEOUT]: 504,
130
+ [ErrorCode.DATABASE_POOL_EXHAUSTED]: 503,
131
+ [ErrorCode.DATABASE_MIGRATION_FAILED]: 500,
132
+
133
+ // 文件操作错误
134
+ [ErrorCode.FILE_NOT_FOUND]: 404,
135
+ [ErrorCode.FILE_UPLOAD_FAILED]: 500,
136
+ [ErrorCode.FILE_DOWNLOAD_FAILED]: 500,
137
+ [ErrorCode.FILE_SIZE_EXCEEDED]: 413,
138
+ [ErrorCode.FILE_TYPE_NOT_ALLOWED]: 415,
139
+ [ErrorCode.FILE_ACCESS_DENIED]: 403,
140
+ [ErrorCode.FILE_PATH_TRAVERSAL]: 400,
141
+
142
+ // 中间件错误
143
+ [ErrorCode.MIDDLEWARE_EXECUTION_FAILED]: 500,
144
+ [ErrorCode.MIDDLEWARE_TIMEOUT]: 504,
145
+ [ErrorCode.CORS_NOT_ALLOWED]: 403,
146
+
147
+ // 配置错误
148
+ [ErrorCode.CONFIG_INVALID]: 500,
149
+ [ErrorCode.CONFIG_MISSING]: 500,
150
+ [ErrorCode.CONFIG_VALIDATION_FAILED]: 500,
151
+ };
152
+
153
+ /**
154
+ * 错误码的默认消息(英文)
155
+ * 支持消息模板,使用 {key} 作为占位符
156
+ * 例如:'Resource {resource} not found',可以通过参数替换 {resource}
157
+ */
158
+ export const ERROR_CODE_MESSAGES: Record<ErrorCode, string> = {
159
+ // 通用错误
160
+ [ErrorCode.INTERNAL_ERROR]: 'Internal Server Error',
161
+ [ErrorCode.INVALID_REQUEST]: 'Invalid Request',
162
+ [ErrorCode.RESOURCE_NOT_FOUND]: 'Resource Not Found',
163
+ [ErrorCode.METHOD_NOT_ALLOWED]: 'Method Not Allowed',
164
+ [ErrorCode.RATE_LIMIT_EXCEEDED]: 'Rate Limit Exceeded',
165
+ [ErrorCode.SERVICE_UNAVAILABLE]: 'Service Unavailable',
166
+ [ErrorCode.TIMEOUT]: 'Request Timeout',
167
+
168
+ // 认证和授权错误
169
+ [ErrorCode.AUTH_REQUIRED]: 'Authentication Required',
170
+ [ErrorCode.AUTH_INVALID_TOKEN]: 'Invalid Authentication Token',
171
+ [ErrorCode.AUTH_TOKEN_EXPIRED]: 'Authentication Token Expired',
172
+ [ErrorCode.AUTH_INSUFFICIENT_PERMISSIONS]: 'Insufficient Permissions',
173
+ [ErrorCode.AUTH_INVALID_CREDENTIALS]: 'Invalid Credentials',
174
+ [ErrorCode.AUTH_ACCOUNT_LOCKED]: 'Account Locked',
175
+ [ErrorCode.AUTH_ACCOUNT_DISABLED]: 'Account Disabled',
176
+
177
+ // 验证错误
178
+ [ErrorCode.VALIDATION_FAILED]: 'Validation Failed',
179
+ [ErrorCode.VALIDATION_REQUIRED_FIELD]: 'Required Field Missing',
180
+ [ErrorCode.VALIDATION_INVALID_FORMAT]: 'Invalid Format',
181
+ [ErrorCode.VALIDATION_OUT_OF_RANGE]: 'Value Out of Range',
182
+ [ErrorCode.VALIDATION_TYPE_MISMATCH]: 'Type Mismatch',
183
+ [ErrorCode.VALIDATION_CONSTRAINT_VIOLATION]: 'Constraint Violation',
184
+
185
+ // OAuth2 错误
186
+ [ErrorCode.OAUTH2_INVALID_CLIENT]: 'Invalid Client',
187
+ [ErrorCode.OAUTH2_INVALID_GRANT]: 'Invalid Grant',
188
+ [ErrorCode.OAUTH2_INVALID_SCOPE]: 'Invalid Scope',
189
+ [ErrorCode.OAUTH2_INVALID_REDIRECT_URI]: 'Invalid Redirect URI',
190
+ [ErrorCode.OAUTH2_UNSUPPORTED_GRANT_TYPE]: 'Unsupported Grant Type',
191
+ [ErrorCode.OAUTH2_ACCESS_DENIED]: 'Access Denied',
192
+ [ErrorCode.OAUTH2_SERVER_ERROR]: 'OAuth2 Server Error',
193
+
194
+ // 数据库错误
195
+ [ErrorCode.DATABASE_CONNECTION_FAILED]: 'Database Connection Failed',
196
+ [ErrorCode.DATABASE_QUERY_FAILED]: 'Database Query Failed',
197
+ [ErrorCode.DATABASE_TRANSACTION_FAILED]: 'Database Transaction Failed',
198
+ [ErrorCode.DATABASE_CONSTRAINT_VIOLATION]: 'Database Constraint Violation',
199
+ [ErrorCode.DATABASE_TIMEOUT]: 'Database Timeout',
200
+ [ErrorCode.DATABASE_POOL_EXHAUSTED]: 'Database Connection Pool Exhausted',
201
+ [ErrorCode.DATABASE_MIGRATION_FAILED]: 'Database Migration Failed',
202
+
203
+ // 文件操作错误
204
+ [ErrorCode.FILE_NOT_FOUND]: 'File Not Found',
205
+ [ErrorCode.FILE_UPLOAD_FAILED]: 'File Upload Failed',
206
+ [ErrorCode.FILE_DOWNLOAD_FAILED]: 'File Download Failed',
207
+ [ErrorCode.FILE_SIZE_EXCEEDED]: 'File Size Exceeded',
208
+ [ErrorCode.FILE_TYPE_NOT_ALLOWED]: 'File Type Not Allowed',
209
+ [ErrorCode.FILE_ACCESS_DENIED]: 'File Access Denied',
210
+ [ErrorCode.FILE_PATH_TRAVERSAL]: 'Path Traversal Detected',
211
+
212
+ // 中间件错误
213
+ [ErrorCode.MIDDLEWARE_EXECUTION_FAILED]: 'Middleware Execution Failed',
214
+ [ErrorCode.MIDDLEWARE_TIMEOUT]: 'Middleware Timeout',
215
+ [ErrorCode.CORS_NOT_ALLOWED]: 'CORS Not Allowed',
216
+
217
+ // 配置错误
218
+ [ErrorCode.CONFIG_INVALID]: 'Invalid Configuration',
219
+ [ErrorCode.CONFIG_MISSING]: 'Missing Configuration',
220
+ [ErrorCode.CONFIG_VALIDATION_FAILED]: 'Configuration Validation Failed',
221
+ };
222
+
@@ -0,0 +1,43 @@
1
+ import type { Context } from '../core/context';
2
+
3
+ export interface ExceptionFilter {
4
+ /**
5
+ * 捕获异常并返回 Response
6
+ * 如果返回 undefined,则继续交给下一个过滤器
7
+ */
8
+ catch(error: unknown, context: Context): Response | Promise<Response> | undefined;
9
+ }
10
+
11
+ export class ExceptionFilterRegistry {
12
+ private static instance: ExceptionFilterRegistry;
13
+ private readonly filters: ExceptionFilter[] = [];
14
+
15
+ private constructor() {}
16
+
17
+ public static getInstance(): ExceptionFilterRegistry {
18
+ if (!ExceptionFilterRegistry.instance) {
19
+ ExceptionFilterRegistry.instance = new ExceptionFilterRegistry();
20
+ }
21
+ return ExceptionFilterRegistry.instance;
22
+ }
23
+
24
+ public register(filter: ExceptionFilter): void {
25
+ this.filters.push(filter);
26
+ }
27
+
28
+ public clear(): void {
29
+ this.filters.length = 0;
30
+ }
31
+
32
+ public async execute(error: unknown, context: Context): Promise<Response | undefined> {
33
+ for (const filter of this.filters) {
34
+ const result = await filter.catch(error, context);
35
+ if (result) {
36
+ return result;
37
+ }
38
+ }
39
+ return undefined;
40
+ }
41
+ }
42
+
43
+
@@ -0,0 +1,66 @@
1
+ import type { Context } from '../core/context';
2
+ import { HttpException } from './http-exception';
3
+ import { ExceptionFilterRegistry } from './filter';
4
+ import { ValidationError } from '../validation';
5
+ import { ErrorMessageI18n } from './i18n';
6
+
7
+ /**
8
+ * 全局错误处理
9
+ */
10
+ export async function handleError(error: unknown, context: Context): Promise<Response> {
11
+ const registry = ExceptionFilterRegistry.getInstance();
12
+ const filterResponse = await registry.execute(error, context);
13
+ if (filterResponse) {
14
+ return filterResponse;
15
+ }
16
+
17
+ if (error instanceof HttpException) {
18
+ context.setStatus(error.status);
19
+
20
+ // 如果异常有错误码,尝试国际化消息
21
+ let errorMessage = error.message;
22
+ if (error.code) {
23
+ const acceptLanguage = context.getHeader('accept-language');
24
+ const language = ErrorMessageI18n.parseLanguageFromHeader(acceptLanguage);
25
+ // 如果提供了消息模板参数,使用参数替换占位符
26
+ errorMessage = ErrorMessageI18n.getMessage(
27
+ error.code,
28
+ language,
29
+ error.messageParams,
30
+ );
31
+ }
32
+
33
+ const responseBody: Record<string, unknown> = {
34
+ error: errorMessage,
35
+ };
36
+
37
+ // 只有当错误码存在时才添加 code 字段
38
+ if (error.code) {
39
+ responseBody.code = error.code;
40
+ }
41
+
42
+ if (error.details !== undefined) {
43
+ responseBody.details = error.details;
44
+ }
45
+
46
+ return context.createResponse(responseBody);
47
+ }
48
+
49
+ if (error instanceof ValidationError) {
50
+ context.setStatus(400);
51
+ return context.createResponse({
52
+ error: error.message,
53
+ code: 'VALIDATION_FAILED',
54
+ issues: error.issues,
55
+ });
56
+ }
57
+
58
+ const message = error instanceof Error ? error.message : String(error);
59
+ context.setStatus(500);
60
+ return context.createResponse({
61
+ error: 'Internal Server Error',
62
+ details: process.env.NODE_ENV === 'production' ? undefined : message,
63
+ });
64
+ }
65
+
66
+